#!/usr/bin/env python3 """ Generate release steps script for Lean4 repositories. This script helps automate the release process for Lean4 and its dependent repositories by generating step-by-step instructions for updating toolchains, creating tags, and managing branches. Usage: python3 release_steps.py Arguments: version: The version to set in the lean-toolchain file (e.g., v4.6.0) repo: A substring of the repository name as specified in release_repos.yml Example: python3 release_steps.py v4.6.0 mathlib python3 release_steps.py v4.6.0 batt The script reads repository configurations from release_repos.yml in the same directory. Each repository may have specific requirements for: - Branch management - Toolchain updates - Dependency updates - Tagging conventions - Stable branch handling """ import argparse import yaml import os import sys import re def load_repos_config(file_path): with open(file_path, "r") as f: return yaml.safe_load(f)["repositories"] def find_repo(repo_substring, config): pattern = re.compile(re.escape(repo_substring), re.IGNORECASE) matching_repos = [r for r in config if pattern.search(r["name"])] if not matching_repos: print(f"Error: No repository matching '{repo_substring}' found in configuration.") sys.exit(1) if len(matching_repos) > 1: print(f"Error: Multiple repositories matching '{repo_substring}' found in configuration: {', '.join(r['name'] for r in matching_repos)}") sys.exit(1) return matching_repos[0] def generate_script(repo, version, config): repo_config = find_repo(repo, config) repo_name = repo_config['name'] repo_url = repo_config['url'] # Extract the last component of the URL, removing the .git extension if present repo_dir = repo_url.split('/')[-1].replace('.git', '') default_branch = repo_config.get("branch", "main") dependencies = repo_config.get("dependencies", []) requires_tagging = repo_config.get("toolchain-tag", True) has_stable_branch = repo_config.get("stable-branch", True) script_lines = [ f"cd {repo_dir}", "git fetch", f"git checkout {default_branch} && git pull", f"git checkout -b bump_to_{version}", f"echo leanprover/lean4:{version} > lean-toolchain", ] # Special cases for specific repositories if repo_name == "REPL": script_lines.extend([ "lake update", "cd test/Mathlib", f"perl -pi -e 's/rev = \"v\\d+\\.\\d+\\.\\d+(-rc\\d+)?\"/rev = \"{version}\"/g' lakefile.toml", f"echo leanprover/lean4:{version} > lean-toolchain", "lake update", "cd ../..", "./test.sh" ]) elif dependencies: script_lines.append('echo "Please update the dependencies in lakefile.{lean,toml}"') script_lines.append("lake update") script_lines.append("") script_lines.extend([ f'git commit -am "chore: bump toolchain to {version}"', "" ]) if re.search(r'rc\d+$', version) and repo_name in ["Batteries", "Mathlib"]: script_lines.extend([ "echo 'This repo has nightly-testing infrastructure'", f"git merge origin/bump/{version.split('-rc')[0]}", "echo 'Please resolve any conflicts.'", "" ]) if repo_name != "Mathlib": script_lines.extend([ "lake build && if lake check-test; then lake test; fi", "" ]) script_lines.extend([ 'gh pr create --title "chore: bump toolchain to ' + version + '" --body ""', "echo 'Please review the PR and merge it.'", "" ]) return "\n".join(script_lines) def main(): parser = argparse.ArgumentParser( description="Generate release steps script for Lean4 repositories.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s v4.6.0 mathlib Generate steps for updating Mathlib to v4.6.0 %(prog)s v4.6.0 batt Generate steps for updating Batteries to v4.6.0 The script will generate shell commands to: 1. Update the lean-toolchain file 2. Create appropriate branches and commits 3. Create pull requests (Note that the steps of creating toolchain version tags, and merging these into `stable` branches, are handled by `script/release_checklist.py`.) """ ) parser.add_argument("version", help="The version to set in the lean-toolchain file (e.g., v4.6.0)") parser.add_argument("repo", help="A substring of the repository name as specified in release_repos.yml") args = parser.parse_args() config_path = os.path.join(os.path.dirname(__file__), "release_repos.yml") config = load_repos_config(config_path) script = generate_script(args.repo, args.version, config) print(script) if __name__ == "__main__": main()