feat: support revised nightly releases (nightly-YYYY-MM-DD-revK) (#12461)
This PR adds support for manually re-releasing nightlies when a build issue or critical fix requires it. When a `workflow_dispatch` triggers the nightly release job and a `nightly-YYYY-MM-DD` tag already exists, the CI now creates `nightly-YYYY-MM-DD-rev1` (then `-rev2`, etc.) instead of silently skipping. ### Lake `ToolchainVer` - Extend `ToolchainVer.nightly` with an optional `rev : Option Nat` field - Parse `-revK` suffixes from nightly tags in `ofString` - Ordering: `nightly-YYYY-MM-DD` < `nightly-YYYY-MM-DD-rev1` < `-rev2` < `nightly-YYYY-MM-DD+1` - Round-trip: `toString (ofString s) == s` for both variants ### CI workflow - "Set Nightly" step probes existing tags on `workflow_dispatch` to find next available `-revK` - Scheduled nightlies retain existing behavior (skip if commit already tagged) - Changelog grep updated from `nightly-[-0-9]*` to `nightly-[^ ,)]*` to match `-revK` suffixes ### `lean-bisect` - Updated `NIGHTLY_PATTERN` regex, sort key, error messages, and help text ### Companion PRs - https://github.com/leanprover-community/mathlib4/pull/35220: update `nightly_bump_and_merge.yml` tag grep and `nightly_detect_failure.yml` warning message - https://github.com/leanprover-community/leanprover-community.github.io/pull/787: update `tags_and_branches.md` documentation 🤖 Prepared with Claude Code Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
56bd8cd0c2
commit
d7e57b66d5
5 changed files with 71 additions and 22 deletions
23
.github/workflows/ci.yml
vendored
23
.github/workflows/ci.yml
vendored
|
|
@ -60,10 +60,25 @@ jobs:
|
||||||
if [[ -n '${{ secrets.PUSH_NIGHTLY_TOKEN }}' ]]; then
|
if [[ -n '${{ secrets.PUSH_NIGHTLY_TOKEN }}' ]]; then
|
||||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||||
git fetch nightly --tags
|
git fetch nightly --tags
|
||||||
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
|
BASE_NIGHTLY="nightly-$(date -u +%F)"
|
||||||
# do nothing if commit already has a different tag
|
if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then
|
||||||
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
# Manual re-release: find next available revision if base nightly exists
|
||||||
|
if git rev-parse "refs/tags/$BASE_NIGHTLY" >/dev/null 2>&1; then
|
||||||
|
REV=1
|
||||||
|
while git rev-parse "refs/tags/${BASE_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
|
||||||
|
REV=$((REV + 1))
|
||||||
|
done
|
||||||
|
LEAN_VERSION_STRING="${BASE_NIGHTLY}-rev${REV}"
|
||||||
|
else
|
||||||
|
LEAN_VERSION_STRING="$BASE_NIGHTLY"
|
||||||
|
fi
|
||||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
# Scheduled: do nothing if commit already has a different tag
|
||||||
|
LEAN_VERSION_STRING="$BASE_NIGHTLY"
|
||||||
|
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
||||||
|
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -475,7 +490,7 @@ jobs:
|
||||||
git tag "${{ needs.configure.outputs.nightly }}"
|
git tag "${{ needs.configure.outputs.nightly }}"
|
||||||
git push nightly "${{ needs.configure.outputs.nightly }}"
|
git push nightly "${{ needs.configure.outputs.nightly }}"
|
||||||
git push -f origin refs/tags/${{ needs.configure.outputs.nightly }}:refs/heads/nightly
|
git push -f origin refs/tags/${{ needs.configure.outputs.nightly }}:refs/heads/nightly
|
||||||
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[-0-9]*" | head -n 1)"
|
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[^ ,)]*" | head -n 1)"
|
||||||
echo -e "*Changes since ${last_tag}:*\n\n" > diff.md
|
echo -e "*Changes since ${last_tag}:*\n\n" > diff.md
|
||||||
git show "$last_tag":RELEASES.md > old.md
|
git show "$last_tag":RELEASES.md > old.md
|
||||||
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
|
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ sys.path.insert(0, str(Path(__file__).parent))
|
||||||
import build_artifact
|
import build_artifact
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
NIGHTLY_PATTERN = re.compile(r'^nightly-(\d{4})-(\d{2})-(\d{2})$')
|
NIGHTLY_PATTERN = re.compile(r'^nightly-(\d{4})-(\d{2})-(\d{2})(-rev(\d+))?$')
|
||||||
VERSION_PATTERN = re.compile(r'^v4\.(\d+)\.(\d+)(-rc\d+)?$')
|
VERSION_PATTERN = re.compile(r'^v4\.(\d+)\.(\d+)(-rc\d+)?$')
|
||||||
# Accept short SHAs (7+ chars) - we'll resolve to full SHA later
|
# Accept short SHAs (7+ chars) - we'll resolve to full SHA later
|
||||||
SHA_PATTERN = re.compile(r'^[0-9a-f]{7,40}$')
|
SHA_PATTERN = re.compile(r'^[0-9a-f]{7,40}$')
|
||||||
|
|
@ -158,7 +158,7 @@ def parse_identifier(s: str) -> Tuple[str, str]:
|
||||||
return ('sha', full_sha)
|
return ('sha', full_sha)
|
||||||
error(f"Invalid identifier format: '{s}'\n"
|
error(f"Invalid identifier format: '{s}'\n"
|
||||||
f"Expected one of:\n"
|
f"Expected one of:\n"
|
||||||
f" - nightly-YYYY-MM-DD (e.g., nightly-2024-06-15)\n"
|
f" - nightly-YYYY-MM-DD or nightly-YYYY-MM-DD-revK (e.g., nightly-2024-06-15, nightly-2024-06-15-rev1)\n"
|
||||||
f" - v4.X.Y or v4.X.Y-rcK (e.g., v4.8.0, v4.9.0-rc1)\n"
|
f" - v4.X.Y or v4.X.Y-rcK (e.g., v4.8.0, v4.9.0-rc1)\n"
|
||||||
f" - commit SHA (short or full)")
|
f" - commit SHA (short or full)")
|
||||||
|
|
||||||
|
|
@ -244,8 +244,13 @@ def fetch_nightly_tags() -> List[str]:
|
||||||
if len(data) < 100:
|
if len(data) < 100:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Sort by date (nightly-YYYY-MM-DD format sorts lexicographically)
|
# Sort by date and revision (nightly-YYYY-MM-DD-revK needs numeric comparison on rev)
|
||||||
tags.sort()
|
def nightly_sort_key(tag):
|
||||||
|
m = NIGHTLY_PATTERN.match(tag)
|
||||||
|
if m:
|
||||||
|
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(5) or 0))
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
tags.sort(key=nightly_sort_key)
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
def get_commit_for_nightly(nightly: str) -> str:
|
def get_commit_for_nightly(nightly: str) -> str:
|
||||||
|
|
@ -1024,6 +1029,7 @@ Range Syntax:
|
||||||
Identifier Formats:
|
Identifier Formats:
|
||||||
|
|
||||||
nightly-YYYY-MM-DD Nightly build date (e.g., nightly-2024-06-15)
|
nightly-YYYY-MM-DD Nightly build date (e.g., nightly-2024-06-15)
|
||||||
|
nightly-YYYY-MM-DD-revK Revised nightly (e.g., nightly-2024-06-15-rev1)
|
||||||
Uses pre-built toolchains from leanprover/lean4-nightly.
|
Uses pre-built toolchains from leanprover/lean4-nightly.
|
||||||
Fast: downloads via elan (~30s each).
|
Fast: downloads via elan (~30s each).
|
||||||
|
|
||||||
|
|
@ -1151,9 +1157,9 @@ Examples:
|
||||||
# Validate --nightly-only
|
# Validate --nightly-only
|
||||||
if args.nightly_only:
|
if args.nightly_only:
|
||||||
if from_val is not None and from_type != 'nightly':
|
if from_val is not None and from_type != 'nightly':
|
||||||
error("--nightly-only requires FROM to be a nightly identifier (nightly-YYYY-MM-DD)")
|
error("--nightly-only requires FROM to be a nightly identifier (nightly-YYYY-MM-DD or nightly-YYYY-MM-DD-revK)")
|
||||||
if to_type != 'nightly':
|
if to_type != 'nightly':
|
||||||
error("--nightly-only requires TO to be a nightly identifier (nightly-YYYY-MM-DD)")
|
error("--nightly-only requires TO to be a nightly identifier (nightly-YYYY-MM-DD or nightly-YYYY-MM-DD-revK)")
|
||||||
|
|
||||||
if from_val:
|
if from_val:
|
||||||
info(f"From: {from_val} ({from_type})")
|
info(f"From: {from_val} ({from_type})")
|
||||||
|
|
|
||||||
|
|
@ -235,13 +235,14 @@ open ToolchainVer in
|
||||||
/-- A Lean toolchain version. -/
|
/-- A Lean toolchain version. -/
|
||||||
inductive ToolchainVer
|
inductive ToolchainVer
|
||||||
| release (ver : LeanVer)
|
| release (ver : LeanVer)
|
||||||
| nightly (date : Date)
|
| nightly (date : Date) (rev : Option Nat := none)
|
||||||
| pr (n : Nat)
|
| pr (n : Nat)
|
||||||
| other (v : String)
|
| other (v : String)
|
||||||
with
|
with
|
||||||
@[computed_field] toString : ToolchainVer → String
|
@[computed_field] toString : ToolchainVer → String
|
||||||
| .release ver => s!"{defaultOrigin}:v{ver}"
|
| .release ver => s!"{defaultOrigin}:v{ver}"
|
||||||
| .nightly date => s!"{defaultOrigin}:nightly-{date}"
|
| .nightly date rev => s!"{defaultOrigin}:nightly-{date}" ++
|
||||||
|
match rev with | none => "" | some r => s!"-rev{r}"
|
||||||
| .pr n => s!"{prOrigin}:pr-release-{n}"
|
| .pr n => s!"{prOrigin}:pr-release-{n}"
|
||||||
| .other v => v
|
| .other v => v
|
||||||
deriving Repr, DecidableEq
|
deriving Repr, DecidableEq
|
||||||
|
|
@ -264,13 +265,20 @@ public def ofString (ver : String) : ToolchainVer := Id.run do
|
||||||
if let .ok ver := StdVer.parse (tag.drop 1).copy then
|
if let .ok ver := StdVer.parse (tag.drop 1).copy then
|
||||||
if noOrigin|| origin == defaultOrigin then
|
if noOrigin|| origin == defaultOrigin then
|
||||||
return .release ver
|
return .release ver
|
||||||
else if let some date := tag.dropPrefix? "nightly-" then
|
else if let some rest := tag.dropPrefix? "nightly-" then
|
||||||
if let some date := Date.ofString? date.toString then
|
let rest := rest.toString
|
||||||
if noOrigin then
|
-- Dates are exactly 10 characters (YYYY-MM-DD); anything after must be a -revK suffix
|
||||||
return .nightly date
|
if let some date := Date.ofString? (rest.take 10).toString then
|
||||||
else if let some suffix := origin.dropPrefix? defaultOrigin then
|
let rev? : Option Nat := do
|
||||||
if suffix.isEmpty || suffix == "-nightly" then
|
let suffix ← (rest.drop 10).dropPrefix? "-rev"
|
||||||
return .nightly date
|
suffix.toString.toNat?
|
||||||
|
-- Accept if no suffix (plain nightly) or valid -revK suffix
|
||||||
|
if rest.length ≤ 10 || rev?.isSome then
|
||||||
|
if noOrigin then
|
||||||
|
return .nightly date rev?
|
||||||
|
else if let some suffix := origin.dropPrefix? defaultOrigin then
|
||||||
|
if suffix.isEmpty || suffix == "-nightly" then
|
||||||
|
return .nightly date rev?
|
||||||
else if let some n := tag.dropPrefix? "pr-release-" then
|
else if let some n := tag.dropPrefix? "pr-release-" then
|
||||||
if let some n := n.toNat? then
|
if let some n := n.toNat? then
|
||||||
if noOrigin || origin == prOrigin then
|
if noOrigin || origin == prOrigin then
|
||||||
|
|
@ -301,7 +309,8 @@ public instance : FromJson ToolchainVer := ⟨(ToolchainVer.ofString <$> fromJso
|
||||||
public def blt (a b : ToolchainVer) : Bool :=
|
public def blt (a b : ToolchainVer) : Bool :=
|
||||||
match a, b with
|
match a, b with
|
||||||
| .release v1, .release v2 => v1 < v2
|
| .release v1, .release v2 => v1 < v2
|
||||||
| .nightly d1, .nightly d2 => d1 < d2
|
| .nightly d1 r1, .nightly d2 r2 =>
|
||||||
|
d1 < d2 || (d1 == d2 && (r1.getD 0) < (r2.getD 0))
|
||||||
| _, _ => false
|
| _, _ => false
|
||||||
|
|
||||||
public instance : LT ToolchainVer := ⟨(·.blt ·)⟩
|
public instance : LT ToolchainVer := ⟨(·.blt ·)⟩
|
||||||
|
|
@ -311,7 +320,8 @@ public instance decLt (a b : ToolchainVer) : Decidable (a < b) :=
|
||||||
public def ble (a b : ToolchainVer) : Bool :=
|
public def ble (a b : ToolchainVer) : Bool :=
|
||||||
match a, b with
|
match a, b with
|
||||||
| .release v1, .release v2 => v1 ≤ v2
|
| .release v1, .release v2 => v1 ≤ v2
|
||||||
| .nightly d1, .nightly d2 => d1 ≤ d2
|
| .nightly d1 r1, .nightly d2 r2 =>
|
||||||
|
d1 < d2 || (d1 == d2 && (r1.getD 0) ≤ (r2.getD 0))
|
||||||
| .pr n1, .pr n2 => n1 = n2
|
| .pr n1, .pr n2 => n1 = n2
|
||||||
| .other v1, .other v2 => v1 = v2
|
| .other v1, .other v2 => v1 = v2
|
||||||
| _, _ => false
|
| _, _ => false
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,33 @@ def test := do
|
||||||
IO.println ""
|
IO.println ""
|
||||||
checkParse "leanprover/lean4:v4.13.0-rc1"
|
checkParse "leanprover/lean4:v4.13.0-rc1"
|
||||||
checkParse "leanprover/lean4:nightly-2024-09-15"
|
checkParse "leanprover/lean4:nightly-2024-09-15"
|
||||||
|
checkParse "leanprover/lean4:nightly-2024-09-15-rev1"
|
||||||
|
checkParse "nightly-2024-09-15-rev2"
|
||||||
checkParse "leanprover/lean4-pr-releases:pr-release-101"
|
checkParse "leanprover/lean4-pr-releases:pr-release-101"
|
||||||
checkParse "leanprover/lean:v4.1.0"
|
checkParse "leanprover/lean:v4.1.0"
|
||||||
checkParse "4.12.0"
|
checkParse "4.12.0"
|
||||||
|
checkParse "nightly-2024-09-15-foo" -- malformed suffix → .other
|
||||||
checkLt "4.6.0-rc1" "v4.6.0"
|
checkLt "4.6.0-rc1" "v4.6.0"
|
||||||
checkLt "4.12.0" "leanprover/lean4:v4.13.0-rc1"
|
checkLt "4.12.0" "leanprover/lean4:v4.13.0-rc1"
|
||||||
checkLt "nightly-2024-09-08" "nightly-2024-10-09"
|
checkLt "nightly-2024-09-08" "nightly-2024-10-09"
|
||||||
|
checkLt "nightly-2024-09-08" "nightly-2024-09-08-rev1"
|
||||||
|
checkLt "nightly-2024-09-08-rev1" "nightly-2024-09-08-rev2"
|
||||||
|
checkLt "nightly-2024-09-08-rev2" "nightly-2024-09-09"
|
||||||
checkLt "nightly-2024-09-08" "4.0.0"
|
checkLt "nightly-2024-09-08" "4.0.0"
|
||||||
|
|
||||||
/--
|
/--
|
||||||
info:
|
info:
|
||||||
Lake.ToolchainVer.release { toSemVerCore := { major := 4, minor := 13, patch := 0 }, specialDescr := "rc1" }
|
Lake.ToolchainVer.release { toSemVerCore := { major := 4, minor := 13, patch := 0 }, specialDescr := "rc1" }
|
||||||
Lake.ToolchainVer.nightly { year := 2024, month := 9, day := 15 }
|
Lake.ToolchainVer.nightly { year := 2024, month := 9, day := 15 } none
|
||||||
|
Lake.ToolchainVer.nightly { year := 2024, month := 9, day := 15 } (some 1)
|
||||||
|
Lake.ToolchainVer.nightly { year := 2024, month := 9, day := 15 } (some 2)
|
||||||
Lake.ToolchainVer.pr 101
|
Lake.ToolchainVer.pr 101
|
||||||
Lake.ToolchainVer.other "leanprover/lean:v4.1.0"
|
Lake.ToolchainVer.other "leanprover/lean:v4.1.0"
|
||||||
Lake.ToolchainVer.release { toSemVerCore := { major := 4, minor := 12, patch := 0 }, specialDescr := "" }
|
Lake.ToolchainVer.release { toSemVerCore := { major := 4, minor := 12, patch := 0 }, specialDescr := "" }
|
||||||
|
Lake.ToolchainVer.other "nightly-2024-09-15-foo"
|
||||||
|
true
|
||||||
|
true
|
||||||
|
true
|
||||||
true
|
true
|
||||||
true
|
true
|
||||||
true
|
true
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ echo nightly-2024-10-01 > a/lean-toolchain
|
||||||
echo nightly-2024-01-10 > b/lean-toolchain
|
echo nightly-2024-01-10 > b/lean-toolchain
|
||||||
test_update leanprover/lean4:nightly-2024-10-01
|
test_update leanprover/lean4:nightly-2024-10-01
|
||||||
|
|
||||||
|
# Test nightly revision comparison
|
||||||
|
./clean.sh
|
||||||
|
echo nightly-2024-10-01-rev1 > a/lean-toolchain
|
||||||
|
echo nightly-2024-01-10 > b/lean-toolchain
|
||||||
|
test_update leanprover/lean4:nightly-2024-10-01-rev1
|
||||||
|
|
||||||
# Test up-to-date root
|
# Test up-to-date root
|
||||||
./clean.sh
|
./clean.sh
|
||||||
echo v4.4.0 > a/lean-toolchain
|
echo v4.4.0 > a/lean-toolchain
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue