Repository navigation
ci: cache the Lean toolchain and retry its installation #1427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| name: Install Lean | ||
| description: >- | ||
| Restore elan and the toolchain named in `lean-toolchain` from the Actions cache, or install them | ||
| with bounded retries and save them (#1426). Run it before `leanprover/lean-action`: that action | ||
| always reinstalls elan from GitHub, but then finds the toolchain installed and does not download | ||
| it from releases.lean-lang.org. | ||
| runs: | ||
| using: composite | ||
| steps: | ||
| # The key changes only with `lean-toolchain`. The entry (about 600 MB) is saved right after the | ||
| # installation, before any Lake command runs: Lake keeps its artifact cache under the toolchain | ||
| # directory (`~/.elan/toolchains/<toolchain>/lake`), which must not enter this entry. | ||
| - name: Restore the Lean toolchain from the Actions cache | ||
| id: restore | ||
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | ||
| with: | ||
| path: ~/.elan | ||
| key: elan-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }} | ||
| - name: Install elan and the Lean toolchain, retrying transient failures | ||
| if: steps.restore.outputs.cache-hit != 'true' | ||
| shell: bash | ||
| run: '"$GITHUB_ACTION_PATH/install.sh" lean-toolchain' | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion (non-blocking, recommended): on a miss, start from an empty The case this covers: if |
||
| # Merge-group refs are discarded after the merge, so their copies would only take space from | ||
| # `main`'s entries. Pull requests restore `main`'s entry; one saves its own copy only when it | ||
| # changes the toolchain or before `main` has saved the current one. | ||
| - name: Save the Lean toolchain to the Actions cache | ||
| if: steps.restore.outputs.cache-hit != 'true' && github.event_name != 'merge_group' | ||
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | ||
| with: | ||
| path: ~/.elan | ||
| key: elan-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }} | ||
| - name: Put elan on the path | ||
| shell: bash | ||
| run: echo "$HOME/.elan/bin" >> "$GITHUB_PATH" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #!/usr/bin/env bash | ||
| # Install elan and the toolchain named in `lean-toolchain`, retrying transient download failures | ||
| # (#1426). Each step is retried up to LEAN_INSTALL_ATTEMPTS times (default 4), waiting | ||
| # LEAN_INSTALL_DELAY seconds (default 15) before the second attempt and three times longer before | ||
| # each later one. An attempt that runs longer than LEAN_INSTALL_TIMEOUT seconds (default 180) is | ||
| # stopped and counts as a failure: elan can stall indefinitely on a failed TLS connection instead | ||
| # of exiting. An existing elan or toolchain is reused without contacting the network. | ||
| # | ||
| # Usage: install.sh [lean-toolchain path] (default: ./lean-toolchain) | ||
| set -euo pipefail | ||
|
|
||
| toolchain_file="${1:-lean-toolchain}" | ||
| attempts="${LEAN_INSTALL_ATTEMPTS:-4}" | ||
| first_delay="${LEAN_INSTALL_DELAY:-15}" | ||
| attempt_timeout="${LEAN_INSTALL_TIMEOUT:-180}" | ||
| elan_home="${ELAN_HOME:-$HOME/.elan}" | ||
| elan="$elan_home/bin/elan" | ||
|
|
||
| retry() { | ||
| local description="$1" attempt=1 delay="$first_delay" | ||
| shift | ||
| until timeout --kill-after=10 "$attempt_timeout" "$@"; do | ||
| if ((attempt >= attempts)); then | ||
| echo "::error::$description failed after $attempt attempts" | ||
| return 1 | ||
| fi | ||
| echo "::warning::$description failed (attempt $attempt of $attempts); retrying in ${delay}s" | ||
| sleep "$delay" | ||
| attempt=$((attempt + 1)) | ||
| delay=$((delay * 3)) | ||
| done | ||
| } | ||
|
|
||
| # `timeout` runs a command, not a shell function, so the installer runs in its own shell. | ||
| install_elan='init="$(mktemp)" && | ||
| curl --proto =https --tlsv1.2 -sSfL https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -o "$init" && | ||
| sh "$init" -y --default-toolchain none --no-modify-path; status=$?; rm -f "$init"; exit "$status"' | ||
|
|
||
| if [[ -x "$elan" ]]; then | ||
| echo "Reusing elan at $elan" | ||
| else | ||
| retry "elan installation" bash -c "$install_elan" | ||
| fi | ||
|
|
||
| toolchain="$(tr -d '[:space:]' < "$toolchain_file")" | ||
| # `elan toolchain install` fails on an installed toolchain, so check first. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: with current elan, |
||
| if "$elan" toolchain list | awk '{print $1}' | grep -qxF "$toolchain"; then | ||
| echo "Reusing installed toolchain $toolchain" | ||
| else | ||
| retry "installation of $toolchain" "$elan" toolchain install "$toolchain" | ||
| fi | ||
| "$elan" run "$toolchain" lean --version | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the saved entry is 791,298,091 B (~755 MB compressed) per the Guide log and
gh cache list, not ~600 MB. The README paragraph and PR body say 600 MB too.Optional hardening: the guarantee that Lake's cache stays out depends on step order. You could make it explicit with an exclusion, e.g.
path: |~/.elan!~/.elan/toolchains/*/lake. If you do, use the identical path list in the restore step, because the path list is part of the cache version and a mismatch makes every restore miss. The current ordering is correct as is.