Skip to content

Commit 900ecff

Browse files
committed
feat: improvements to release automation
1 parent 7632cef commit 900ecff

File tree

3 files changed

+222
-21
lines changed

3 files changed

+222
-21
lines changed

.claude/commands/release.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Release Management Command
2+
3+
Execute the release process for a given version by running the release checklist and following its instructions.
4+
5+
## Before Starting
6+
7+
**IMPORTANT**: Before beginning the release process, read the in-file documentation:
8+
- Read `script/release_checklist.py` for what the checklist script does
9+
- Read `script/release_steps.py` for what the release steps script does
10+
11+
These comments explain the scripts' behavior, which repositories get special handling, and how errors are handled.
12+
13+
## Arguments
14+
- `version`: The version to release (e.g., v4.24.0)
15+
16+
## Process
17+
18+
1. Run `script/release_checklist.py {version}` to check the current status
19+
2. Create a todo list tracking all repositories that need updates
20+
3. For each repository that needs updating:
21+
- Run `script/release_steps.py {version} {repo_name}` to create the PR
22+
- Mark it complete when the PR is created
23+
4. After creating PRs, notify the user which PRs need review and merging
24+
5. Continuously rerun `script/release_checklist.py {version}` to check progress
25+
6. As PRs are merged, dependent repositories will become ready - create PRs for those as well
26+
7. Continue until all repositories are updated and the release is complete
27+
28+
## Important Notes
29+
30+
- The `release_steps.py` script is idempotent - it's safe to rerun
31+
- The `release_checklist.py` script is idempotent - it's safe to rerun
32+
- Some repositories depend on others (e.g., mathlib4 depends on batteries, aesop, etc.)
33+
- Wait for user to merge PRs before dependent repos can be updated
34+
- Alert user if anything unusual or scary happens
35+
- Use appropriate timeouts for long-running builds (verso can take 10+ minutes)
36+
- ProofWidgets4 uses semantic versioning (v0.0.X) - it's okay to create and push the next sequential tag yourself when needed for a release
37+
38+
## PR Status Reporting
39+
40+
Every time you run `release_checklist.py`, you MUST:
41+
1. Parse the output to identify ALL open PRs mentioned (lines with "✅ PR with title ... exists")
42+
2. Provide a summary to the user listing ALL open PRs that need review
43+
3. Group them by status:
44+
- PRs for repositories that are blocked by dependencies (show these but note they're blocked)
45+
- PRs for repositories that are ready to merge (highlight these)
46+
4. Format the summary clearly with PR numbers and URLs
47+
48+
This summary should be provided EVERY time you run the checklist, not just after creating new PRs.
49+
The user needs to see the complete picture of what's waiting for review.
50+
51+
## Error Handling
52+
53+
**CRITICAL**: If something goes wrong or a command fails:
54+
- **DO NOT** try to manually reproduce the failing steps yourself
55+
- **DO NOT** try to fix things by running git commands or other manual operations
56+
- Both scripts are idempotent and designed to handle partial completion gracefully
57+
- If a script continues to fail after retrying, report the error to the user and wait for instructions

script/release_checklist.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,51 @@
11
#!/usr/bin/env python3
22

3+
"""
4+
Release Checklist for Lean4 and Downstream Repositories
5+
6+
This script validates the status of a Lean4 release across all dependent repositories.
7+
It checks whether repositories are ready for release and identifies missing steps.
8+
9+
IMPORTANT: Keep this documentation up-to-date when modifying the script's behavior!
10+
11+
What this script does:
12+
1. Validates preliminary Lean4 release infrastructure:
13+
- Checks that the release branch (releases/vX.Y.0) exists
14+
- Verifies CMake version settings are correct
15+
- Confirms the release tag exists
16+
- Validates the release page exists on GitHub
17+
- Checks the release notes page on lean-lang.org
18+
19+
2. For each downstream repository (batteries, mathlib4, etc.):
20+
- Checks if dependencies are ready (e.g., mathlib4 depends on batteries)
21+
- Verifies the main branch is on the target toolchain (or newer)
22+
- Checks if a PR exists to bump the toolchain (if not yet updated)
23+
- Validates tags exist for the release version
24+
- Ensures tags are merged into stable branches (for non-RC releases)
25+
- Verifies bump branches exist and are configured correctly
26+
- Special handling for ProofWidgets4 release tags
27+
28+
3. Optionally automates missing steps (when not in --dry-run mode):
29+
- Creates missing release tags using push_repo_release_tag.py
30+
- Merges tags into stable branches using merge_remote.py
31+
32+
Usage:
33+
./release_checklist.py v4.24.0 # Check release status
34+
./release_checklist.py v4.24.0 --verbose # Show detailed debug info
35+
./release_checklist.py v4.24.0 --dry-run # Check only, don't execute fixes
36+
37+
For automated release management with Claude Code:
38+
/release v4.24.0 # Run full release process with Claude
39+
40+
The script reads repository configurations from release_repos.yml and reports:
41+
- ✅ for completed requirements
42+
- ❌ for missing requirements (with instructions to fix)
43+
- 🟡 for repositories waiting on dependencies
44+
- ⮕ for automated actions being taken
45+
46+
This script is idempotent and safe to rerun multiple times.
47+
"""
48+
349
import argparse
450
import yaml
551
import requests
@@ -286,6 +332,68 @@ def check_bump_branch_toolchain(url, bump_branch, github_token):
286332
print(f" ✅ Bump branch correctly uses toolchain: {content}")
287333
return True
288334

335+
def get_pr_ci_status(repo_url, pr_number, github_token):
336+
"""Get the CI status for a pull request."""
337+
api_base = repo_url.replace("https://github.com/", "https://api.github.com/repos/")
338+
headers = {'Authorization': f'token {github_token}'} if github_token else {}
339+
340+
# Get PR details to find the head SHA
341+
pr_response = requests.get(f"{api_base}/pulls/{pr_number}", headers=headers)
342+
if pr_response.status_code != 200:
343+
return "unknown", "Could not fetch PR details"
344+
345+
pr_data = pr_response.json()
346+
head_sha = pr_data['head']['sha']
347+
348+
# Get check runs for the commit
349+
check_runs_response = requests.get(
350+
f"{api_base}/commits/{head_sha}/check-runs",
351+
headers=headers
352+
)
353+
354+
if check_runs_response.status_code != 200:
355+
return "unknown", "Could not fetch check runs"
356+
357+
check_runs_data = check_runs_response.json()
358+
check_runs = check_runs_data.get('check_runs', [])
359+
360+
if not check_runs:
361+
# No check runs, check for status checks (legacy)
362+
status_response = requests.get(
363+
f"{api_base}/commits/{head_sha}/status",
364+
headers=headers
365+
)
366+
if status_response.status_code == 200:
367+
status_data = status_response.json()
368+
state = status_data.get('state', 'unknown')
369+
if state == 'success':
370+
return "success", "All status checks passed"
371+
elif state == 'failure':
372+
return "failure", "Some status checks failed"
373+
elif state == 'pending':
374+
return "pending", "Status checks in progress"
375+
return "unknown", "No CI checks found"
376+
377+
# Analyze check runs
378+
conclusions = [run['conclusion'] for run in check_runs if run.get('status') == 'completed']
379+
in_progress = [run for run in check_runs if run.get('status') in ['queued', 'in_progress']]
380+
381+
if in_progress:
382+
return "pending", f"{len(in_progress)} check(s) in progress"
383+
384+
if not conclusions:
385+
return "pending", "Checks queued"
386+
387+
if all(c == 'success' for c in conclusions):
388+
return "success", f"All {len(conclusions)} checks passed"
389+
390+
failed = sum(1 for c in conclusions if c in ['failure', 'timed_out', 'action_required'])
391+
if failed > 0:
392+
return "failure", f"{failed} check(s) failed"
393+
394+
# Some checks are cancelled, skipped, or neutral
395+
return "warning", f"Some checks did not complete normally"
396+
289397
def pr_exists_with_title(repo_url, title, github_token):
290398
api_url = repo_url.replace("https://github.com/", "https://api.github.com/repos/") + "/pulls"
291399
headers = {'Authorization': f'token {github_token}'} if github_token else {}
@@ -471,6 +579,19 @@ def main():
471579
if pr_info:
472580
pr_number, pr_url = pr_info
473581
print(f" ✅ PR with title '{pr_title}' exists: #{pr_number} ({pr_url})")
582+
583+
# Check CI status
584+
ci_status, ci_message = get_pr_ci_status(url, pr_number, github_token)
585+
if ci_status == "success":
586+
print(f" ✅ CI: {ci_message}")
587+
elif ci_status == "failure":
588+
print(f" ❌ CI: {ci_message}")
589+
elif ci_status == "pending":
590+
print(f" 🔄 CI: {ci_message}")
591+
elif ci_status == "warning":
592+
print(f" ⚠️ CI: {ci_message}")
593+
else:
594+
print(f" ❓ CI: {ci_message}")
474595
else:
475596
print(f" ❌ PR with title '{pr_title}' does not exist")
476597
print(f" Run `script/release_steps.py {toolchain} {name}` to create it")

script/release_steps.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
11
#!/usr/bin/env python3
22

33
"""
4-
Execute release steps for Lean4 repositories.
4+
Execute Release Steps for Lean4 Downstream Repositories
55
6-
This script helps automate the release process for Lean4 and its dependent repositories
7-
by actually executing the step-by-step instructions for updating toolchains, creating tags,
8-
and managing branches.
6+
This script automates the process of updating a downstream repository to a new Lean4 release.
7+
It handles creating branches, updating toolchains, merging changes, building, testing, and
8+
creating pull requests.
9+
10+
IMPORTANT: Keep this documentation up-to-date when modifying the script's behavior!
11+
12+
What this script does:
13+
1. Sets up the downstream_releases/ directory for cloning repositories
14+
15+
2. Clones or updates the target repository
16+
17+
3. Creates a branch named bump_to_{version} for the changes
18+
19+
4. Updates the lean-toolchain file to the target version
20+
21+
5. Handles repository-specific variations:
22+
- Different dependency update mechanisms
23+
- Special merging strategies for repositories with nightly-testing branches
24+
- Safety checks for repositories using bump branches
25+
- Custom build and test procedures
26+
27+
6. Commits the changes with message "chore: bump toolchain to {version}"
28+
29+
7. Builds the project (with a clean .lake cache)
30+
31+
8. Runs tests if available
32+
33+
9. Pushes the branch to GitHub
34+
35+
10. Creates a pull request (or reports if one already exists)
936
1037
Usage:
11-
python3 release_steps.py <version> <repo>
12-
13-
Arguments:
14-
version: The version to set in the lean-toolchain file (e.g., v4.6.0)
15-
repo: The repository name as specified in release_repos.yml
16-
17-
Example:
18-
python3 release_steps.py v4.6.0 mathlib4
19-
python3 release_steps.py v4.6.0 batteries
20-
21-
The script reads repository configurations from release_repos.yml in the same directory.
22-
Each repository may have specific requirements for:
23-
- Branch management
24-
- Toolchain updates
25-
- Dependency updates
26-
- Tagging conventions
27-
- Stable branch handling
38+
./release_steps.py v4.24.0 batteries # Update batteries to v4.24.0
39+
./release_steps.py v4.24.0-rc1 mathlib4 # Update mathlib4 to v4.24.0-rc1
40+
41+
The script reads repository configurations from release_repos.yml.
42+
Each repository has specific handling for merging, dependencies, and testing.
43+
44+
This script is idempotent - it's safe to rerun if it fails partway through.
45+
Existing branches, commits, and PRs will be reused rather than duplicated.
46+
47+
Error handling:
48+
- If build or tests fail, the script continues to create the PR anyway
49+
- Manual conflicts must be resolved by the user
50+
- Network issues during push/PR creation are reported with manual instructions
2851
"""
2952

3053
import argparse

0 commit comments

Comments
 (0)