Skip to content

Commit 315df84

Browse files
committed
Merge remote-tracking branch 'upstream/master' into kim/to-dual-fixed-points
# Conflicts: # Mathlib/Order/CompleteLattice/Basic.lean # Mathlib/Order/ConditionallyCompleteLattice/Basic.lean # Mathlib/Order/ConditionallyCompleteLattice/Indexed.lean
2 parents 34d2cee + 3773dce commit 315df84

File tree

386 files changed

+6460
-2489
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

386 files changed

+6460
-2489
lines changed

.github/workflows/actionlint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ jobs:
1616
uses: reviewdog/action-actionlint@e58ee9d111489c31395fbe4857b0be6e7635dbda # v1.70.0
1717
with:
1818
tool_name: actionlint
19-
fail_level: error
19+
fail_level: any

.github/workflows/build_template.yml

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -399,17 +399,12 @@ jobs:
399399
../tools-branch/scripts/lake-build-with-retry.sh Counterexamples
400400
# results of build at pr-branch/.lake/build_summary_Counterexamples.json
401401
402-
- name: prepare cache staging directory
403-
if: ${{ steps.build.outcome == 'success' }}
402+
- name: stage Mathlib cache files
403+
if: ${{ always() && (steps.build.outcome == 'success' || steps.build.outcome == 'failure' || steps.build.outcome == 'cancelled') }}
404404
shell: bash
405405
run: |
406406
rm -rf cache-staging
407407
mkdir -p cache-staging
408-
409-
- name: stage Mathlib cache files
410-
if: ${{ steps.build.outcome == 'success' }}
411-
shell: bash
412-
run: |
413408
cd pr-branch
414409
lake env ../tools-branch/.lake/build/bin/cache --staging-dir="../cache-staging" stage
415410
@@ -429,7 +424,7 @@ jobs:
429424
430425
- name: check cache staging contents
431426
id: cache_staging_check
432-
if: ${{ steps.build.outcome == 'success' }}
427+
if: ${{ always() && (steps.build.outcome == 'success' || steps.build.outcome == 'failure' || steps.build.outcome == 'cancelled') }}
433428
shell: bash
434429
run: |
435430
if find cache-staging -type f -name '*.ltar' -print -quit | grep -q .; then
@@ -439,7 +434,7 @@ jobs:
439434
fi
440435
441436
- name: upload cache staging artifact
442-
if: ${{ steps.cache_staging_check.outputs.has_files == 'true' }}
437+
if: ${{ always() && steps.cache_staging_check.outputs.has_files == 'true' }}
443438
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
444439
with:
445440
name: cache-staging
@@ -609,7 +604,7 @@ jobs:
609604
# We only upload the cache if the build started (whether succeeding, failing, or cancelled)
610605
# but not if any earlier step failed or was cancelled.
611606
# See discussion at https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Some.20files.20not.20found.20in.20the.20cache/near/407183836
612-
if: ${{ always() && needs.build.outputs.build-outcome == 'success' && needs.build.outputs.get-cache-outcome == 'success' && needs.build.outputs.cache-staging-has-files == 'true' }}
607+
if: ${{ always() && needs.build.outputs.cache-staging-has-files == 'true' }}
613608
steps:
614609
- name: Checkout tools branch
615610
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -655,6 +650,7 @@ jobs:
655650
post_steps:
656651
name: Post-Build Step
657652
needs: [build, upload_cache]
653+
if: ${{ always() && needs.build.result == 'success' && (needs.upload_cache.result == 'success' || needs.upload_cache.result == 'skipped') }}
658654
runs-on: ubuntu-latest # Note these steps run on disposable GitHub runners, so no landrun sandboxing is needed.
659655
steps:
660656

@@ -744,7 +740,8 @@ jobs:
744740

745741
final:
746742
name: Post-CI job
747-
if: ${{ inputs.run_post_ci }}
743+
# ensure that this runs iff direct dependencies succeeded even if transitive dependencies were skipped
744+
if: ${{ always() && inputs.run_post_ci && needs.style_lint.result == 'success' && needs.build.result == 'success' && needs.post_steps.result == 'success' }}
748745
needs: [style_lint, build, post_steps]
749746
runs-on: ubuntu-latest
750747
steps:
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
name: Zulip emoji CI status
2+
3+
on:
4+
workflow_run:
5+
workflows: ["continuous integration", "continuous integration (mathlib forks)"]
6+
types: [requested, completed]
7+
8+
# Limit permissions for GITHUB_TOKEN for the entire workflow
9+
permissions:
10+
contents: read
11+
pull-requests: read
12+
# All other permissions are implicitly 'none'
13+
14+
jobs:
15+
update_ci_emoji:
16+
runs-on: ubuntu-latest
17+
if: github.repository == 'leanprover-community/mathlib4'
18+
steps:
19+
- name: Determine PR number
20+
id: pr
21+
env:
22+
GH_TOKEN: ${{ github.token }}
23+
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
24+
HEAD_REPO_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }}
25+
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
26+
run: |
27+
# Try to get PR number from the workflow_run event
28+
PR_NUMBER=$(echo '${{ toJSON(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number // empty')
29+
# For push-triggered CI (non-fork PRs), pull_requests may be empty;
30+
# fall back to looking up the PR by branch name.
31+
# Use owner:branch format to avoid matching the wrong PR when
32+
# multiple forks use the same branch name.
33+
if [ -z "$PR_NUMBER" ]; then
34+
PR_NUMBER=$(gh pr list --repo "${{ github.repository }}" --head "$HEAD_REPO_OWNER:$HEAD_BRANCH" --state open --json number,headRefOid --jq ".[] | select(.headRefOid == \"$HEAD_SHA\") | .number" 2>/dev/null || true)
35+
fi
36+
# If owner-qualified lookup failed, try without owner (for same-repo branches)
37+
if [ -z "$PR_NUMBER" ]; then
38+
PR_NUMBER=$(gh pr list --repo "${{ github.repository }}" --head "$HEAD_BRANCH" --state open --json number,headRefOid --jq ".[] | select(.headRefOid == \"$HEAD_SHA\") | .number" 2>/dev/null || true)
39+
fi
40+
if [ -z "$PR_NUMBER" ]; then
41+
echo "No PR found for branch $HEAD_REPO_OWNER:$HEAD_BRANCH at $HEAD_SHA, skipping"
42+
echo "skip=true" >> "$GITHUB_OUTPUT"
43+
else
44+
echo "Found PR #$PR_NUMBER"
45+
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
46+
echo "skip=false" >> "$GITHUB_OUTPUT"
47+
fi
48+
49+
- name: Determine CI action
50+
id: action
51+
if: steps.pr.outputs.skip != 'true'
52+
run: |
53+
EVENT_ACTION="${{ github.event.action }}"
54+
CONCLUSION="${{ github.event.workflow_run.conclusion }}"
55+
echo "Event action: $EVENT_ACTION, conclusion: $CONCLUSION"
56+
if [ "$EVENT_ACTION" = "requested" ]; then
57+
echo "ci_action=ci-running" >> "$GITHUB_OUTPUT"
58+
elif [ "$EVENT_ACTION" = "completed" ]; then
59+
case "$CONCLUSION" in
60+
success)
61+
echo "ci_action=ci-success" >> "$GITHUB_OUTPUT"
62+
;;
63+
cancelled)
64+
# Clear the running emoji. A new run may or may not follow
65+
# (manual cancel, branch deleted, etc.), so don't leave stale 🟡.
66+
echo "ci_action=ci-cancelled" >> "$GITHUB_OUTPUT"
67+
;;
68+
*)
69+
echo "ci_action=ci-failure" >> "$GITHUB_OUTPUT"
70+
;;
71+
esac
72+
fi
73+
74+
- name: Checkout mathlib repository
75+
if: steps.pr.outputs.skip != 'true' && steps.action.outputs.ci_action != 'skip'
76+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
77+
with:
78+
ref: master
79+
sparse-checkout: |
80+
scripts/zulip_emoji_reactions.py
81+
82+
- name: Set up Python
83+
if: steps.pr.outputs.skip != 'true' && steps.action.outputs.ci_action != 'skip'
84+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
85+
with:
86+
python-version: '3.x'
87+
88+
- name: Install dependencies
89+
if: steps.pr.outputs.skip != 'true' && steps.action.outputs.ci_action != 'skip'
90+
run: |
91+
python -m pip install --upgrade pip
92+
pip install zulip
93+
94+
- name: Update CI emoji
95+
if: steps.pr.outputs.skip != 'true' && steps.action.outputs.ci_action != 'skip'
96+
env:
97+
ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }}
98+
ZULIP_EMAIL: github-mathlib4-bot@leanprover.zulipchat.com
99+
ZULIP_SITE: https://leanprover.zulipchat.com
100+
run: |
101+
python scripts/zulip_emoji_reactions.py "$ZULIP_API_KEY" "$ZULIP_EMAIL" "$ZULIP_SITE" "${{ steps.action.outputs.ci_action }}" "none" "${{ steps.pr.outputs.pr_number }}"

Archive/Wiedijk100Theorems/Konigsberg.lean

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ def graph : SimpleGraph Verts where
5252
symm := by
5353
dsimp [Symmetric, adj]
5454
decide
55-
loopless := by
56-
dsimp [Irreflexive, adj]
57-
decide
55+
loopless := ⟨by decide⟩
5856

5957
instance : DecidableRel graph.Adj := fun a b => inferInstanceAs <| Decidable (adj a b)
6058

Cache/IO.lean

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,78 @@ def getLocalCacheSet : IO <| Std.TreeSet String compare := do
390390
def isFromMathlib (mod : Name) : Bool :=
391391
mod.getRoot == `Mathlib
392392

393+
/-- Get the trace file path for a module. -/
394+
def getTracePath (mod : Name) : CacheM FilePath := do
395+
let sp := (← read).srcSearchPath
396+
let packageDir ← getSrcDir sp mod
397+
let path := (System.mkFilePath <| mod.components.map toString)
398+
return packageDir / LIBDIR / path.withExtension "trace"
399+
400+
/-- Read the `depHash` from a trace file, if it exists and is valid.
401+
Returns the hash as a UInt64. -/
402+
def readTraceHash (tracePath : FilePath) : IO (Option UInt64) := do
403+
let contents ← try IO.FS.readFile tracePath
404+
catch _ => return none
405+
-- Try to parse as JSON and extract depHash
406+
let some json := Lean.Json.parse contents |>.toOption | return none
407+
let some depHashStr := json.getObjValAs? String "depHash" |>.toOption | return none
408+
-- Parse hex string to UInt64
409+
return depHashStr.parseHexToUInt64?
410+
411+
/-- Read the Lake depHash from an ltar file header.
412+
The ltar format is: 4-byte magic (LTAR/LTR2/LTR3) + 8-byte little-endian u64 hash. -/
413+
def readLtarHash (ltarPath : FilePath) : IO (Option UInt64) := do
414+
let some handle ← try
415+
some <$> IO.FS.Handle.mk ltarPath .read
416+
catch _ => pure none | return none
417+
-- Read 12 bytes: 4 magic + 8 hash
418+
let bytes ← handle.read 12
419+
if bytes.size < 12 then return none
420+
-- Verify magic (LTAR, LTR2, or LTR3)
421+
let magic := String.fromUTF8! (bytes.extract 0 4)
422+
if magic != "LTAR" && magic != "LTR2" && magic != "LTR3" then return none
423+
-- Read little-endian u64 hash
424+
let mut hash : UInt64 := 0
425+
for i in [0:8] do
426+
hash := hash ||| ((bytes.get! (4 + i)).toUInt64 <<< (i * 8).toUInt64)
427+
return some hash
428+
429+
/-- Check if a module's trace file indicates it is already decompressed with the correct hash.
430+
The hash to compare comes from the ltar file header, not the mathlib cache hash.
431+
Returns `true` if the module needs decompression, `false` if it can be skipped. -/
432+
def needsDecompression (mod : Name) (mathlibHash : UInt64) : CacheM Bool := do
433+
-- Read the Lake depHash from the ltar file header
434+
let ltarPath := CACHEDIR / mathlibHash.asLTar
435+
let some ltarHash ← readLtarHash ltarPath | return true
436+
-- Read the trace file hash
437+
let tracePath ← getTracePath mod
438+
let some traceHash ← readTraceHash tracePath | return true
439+
-- They should match if the file is already decompressed
440+
return ltarHash != traceHash
441+
442+
/-- Filter the hashmap to only include modules that need decompression.
443+
A module needs decompression if its trace file doesn't exist or has a different hash. -/
444+
def ModuleHashMap.filterNeedsDecompression (hashMap : ModuleHashMap) : CacheM ModuleHashMap :=
445+
hashMap.foldM (init := ∅) fun acc mod hash => do
446+
if ← needsDecompression mod hash then
447+
return acc.insert mod hash
448+
else
449+
return acc
450+
393451
/-- Decompresses build files into their respective folders -/
394452
def unpackCache (hashMap : ModuleHashMap) (force : Bool) : CacheM Unit := do
395453
let hashMap ← hashMap.filterExists true
454+
let totalCached := hashMap.size
455+
-- Unless force is set, filter to only modules that actually need decompression
456+
let hashMap ← if force then pure hashMap else hashMap.filterNeedsDecompression
396457
let size := hashMap.size
458+
let skipped := totalCached - size
397459
if size > 0 then
398460
let now ← IO.monoMsNow
399-
IO.println s!"Decompressing {size} file(s)"
461+
if skipped > 0 then
462+
IO.println s!"Decompressing {size} file(s) ({skipped} already decompressed)"
463+
else
464+
IO.println s!"Decompressing {size} file(s)"
400465
let args := (if force then #["-f"] else #[]) ++ #["-x", "--delete-corrupted", "-j", "-"]
401466
let child ← IO.Process.spawn { cmd := ← getLeanTar, args, stdin := .piped }
402467
let (stdin, child) ← child.takeStdin
@@ -427,9 +492,12 @@ def unpackCache (hashMap : ModuleHashMap) (force : Bool) : CacheM Unit := do
427492
stdin.putStr <| Lean.Json.compress <| .arr config
428493
let exitCode ← child.wait
429494
if exitCode != 0 then throw <| IO.userError s!"leantar failed with error code {exitCode}"
430-
IO.println s!"Unpacked in {(← IO.monoMsNow) - now} ms"
495+
IO.println s!"Decompressed in {(← IO.monoMsNow) - now} ms"
431496
IO.println "Completed successfully!"
432-
else IO.println "No cache files to decompress"
497+
else if totalCached > 0 then
498+
IO.println s!"Already decompressed {totalCached} file(s)"
499+
else
500+
IO.println "No cache files to decompress"
433501

434502
instance : Ord FilePath where
435503
compare x y := compare x.toString y.toString

Cache/Lean.lean

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ def Nat.toHexDigits (n : Nat) : Nat → (res : String := "") → String
2828
def UInt64.asLTar (n : UInt64) : String :=
2929
s!"{Nat.toHexDigits n.toNat 8}.ltar"
3030

31+
/-- Parse a single hex digit character to its numeric value. -/
32+
def Char.hexDigitToNat? (c : Char) : Option Nat :=
33+
if '0' ≤ c ∧ c ≤ '9' then some (c.toNat - '0'.toNat)
34+
else if 'a' ≤ c ∧ c ≤ 'f' then some (c.toNat - 'a'.toNat + 10)
35+
else if 'A' ≤ c ∧ c ≤ 'F' then some (c.toNat - 'A'.toNat + 10)
36+
else none
37+
38+
/-- Parse a 16-character hex string (like "4bd6700ff435e8d0") to a UInt64. -/
39+
def String.parseHexToUInt64? (s : String) : Option UInt64 := do
40+
if s.length != 16 then failure
41+
let mut result : UInt64 := 0
42+
for c in s.toList do
43+
let digit ← c.hexDigitToNat?
44+
result := (result <<< 4) ||| digit.toUInt64
45+
return result
46+
3147
-- copied from Mathlib
3248
/-- Create a `Name` from a list of components. -/
3349
def Lean.Name.fromComponents : List Name → Name := go .anonymous where

0 commit comments

Comments
 (0)