Repository navigation
feat(assets): records travel with what needs them, open locations through adapters, the tier per asset, the guard, the AR4 out of the library #807
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Four test lanes and a typing gate. `default` installs the locked PyPI releases on every supported Python and | |
| # blocks. The other three leave the run's conclusion alone (job-level `continue-on-error`: a failure there marks its | |
| # own job red): `openmaterica-main` and `maddening-main` swap one upstream for its `main`, and `usd-core-newest` | |
| # re-resolves usd-core to the newest release. v0.3's branch protection requires `default` on each Python, | |
| # `maddening-main`, `openmaterica-main`, `typecheck` and `confidential` by these jobs' names, so a red one of them blocks | |
| # a merge, and tests/test_ci_wiring.py holds the names and the checks' wiring. Each lane names itself in MIME_CI_LANE, | |
| # and tests/test_ci_lane.py asserts from the installed metadata that it installed what it claims. | |
| # Every lane also builds and installs lab_fixture/, which tests/test_lab_fixture.py and the registry walk require | |
| # under CI. `uv sync` installs the `dev` dependency group (pytest, Hypothesis, pyright, hatchling) by default. | |
| # `typecheck` blocks: pyright, pinned, strict over src/mime at zero errors, on a run that must be trustworthy. | |
| # `confidential` blocks: the confidentiality scan over the tracked tree and its paths, the commits' diffs and messages | |
| # and the branch's name. | |
| # `default`, `openmaterica-main` and `maddening-main` each run their tests as shard jobs (`<lane>-tests`), ten unless | |
| # the repository variable MIME_CI_SHARDS or a manual run's `shards` input says otherwise (the `shards` job, | |
| # scripts/shard_matrix.py, CONTRIBUTING.md). The split is in tests/_sharding.py, by file and stable. The job under the | |
| # lane's own name, which branch protection requires, passes only when every shard passed, their selections partition | |
| # one collection and the shards' JUnit | |
| # reports agree (every test has an outcome in one report, none failed, none skipped; scripts/check_shards.py); it runs | |
| # after a red shard too, since a job skipped by its `if` passes a required check. | |
| # A shard job's report is named by the run attempt (`...-shard-<n>-attempt-<k>`), so that re-running one failed shard job | |
| # adds a report beside the first attempt's instead of a second one under the same name, and the lane's job keeps each | |
| # shard's newest attempt before it checks (scripts/latest_attempt.py): a re-run of that shard alone clears the lane when | |
| # it passes and leaves it red when it fails again. `default` also reads the run's jobs at their latest attempts and requires | |
| # its Python's shard jobs to have concluded success (scripts/check_shard_jobs.py), which a re-run that failed before it | |
| # uploaded would otherwise hide behind the first attempt's report. | |
| # Each test's child processes get a compilation cache of their own where this workflow sets the shared one | |
| # (tests/_child_cache.py). | |
| # `usd-core-newest`, weekly, runs as one job. Each shard runs its tests on pytest-xdist workers and writes JUnit XML, | |
| # and its `Test durations` step holds MADDENING's budget (scripts/report_test_durations.py, tests/README.md): an | |
| # unlisted test over 5 s gets a warning, and one over 20 s, once re-run alone, fails the job; the lane's job writes | |
| # where the time went to the run page. Each test's JAX tracing, lowering and compiling are recorded beside its times | |
| # (MIME_TEST_JAX_TIMING, tests/_jax_timing.py), and each shard's report is uploaded as a run artifact. Each shard keeps | |
| # JAX's persistent compilation cache, per lane, shard, jaxlib and runner CPU model: a pull request restores its base's | |
| # and never saves, a push starts empty and saves (scripts/compilation_cache_mode.py). setup-uv's own cache is saved the | |
| # same way, by pushes only: pull requests and branches each saved a copy of about 0.5 GB, which filled GitHub's 10 GB | |
| # per-repository limit and evicted the compilation cache. Every shard and lane job has a `timeout-minutes`; a shard's is | |
| # 20. The longest of the 45 shard jobs of run 41724784021 (a push at nine shards, so every shard cold) took 18.7 min, a | |
| # margin of 1.07 (the limit was set at about 3.5 times the 5.7 min measured then); the tenth shard was added to move | |
| # that margin, and tests/shard_weights.json is rebuilt from a full run's reports (scripts/shard_weights.py) when the | |
| # suite has grown. A pull request's newer push cancels its older run | |
| # (`concurrency` below). | |
| # Two modes (CONTRIBUTING.md, "CI: the `ready` label"). The full matrix (every lane and Python, 58 jobs) runs on a push to | |
| # a branch, a manual run, and a pull request that carries the label `ready`. A pull request without the label runs a | |
| # reduced set: `typecheck`, `no private name or path`, the docs check and the default lane on Python 3.12 with all its | |
| # shards (18 jobs), and the required jobs it does not run for (`default` on 3.13 and 3.14, `maddening-main`, | |
| # `openmaterica-main`) still run and fail with "the full matrix has not run on this head: add the `ready` label", so | |
| # that no required check passes by being skipped. An agent adds `ready` when its head is final. | |
| # The mode is read when the run starts, from the pull request's labels as they are then (the `shards` job), not from the | |
| # event: so a re-run reads the label too. This workflow does not listen to `labeled`: a label event would start a run | |
| # whose jobs are all skipped, and a wholly skipped run, the newest of this workflow on the head, left v0.3's branch | |
| # protection reporting the head blocked with every required check green. Adding `ready` starts ready.yml instead, which | |
| # re-runs the head's newest run of this workflow, now in full: one check suite per head, its newest attempt counting. | |
| # tests/test_ci_ready_label.py reads this. | |
| # Every test job stores the asset `unit_tube_R0.997` (209 MB, the tube wall kind's table) before its tests, by the local | |
| # action .github/actions/unit-tube: one actions/cache entry keyed by the asset's sha256, shared by every lane, Python and | |
| # shard, and `mime assets fetch` into MIME_ASSETS_DIR. The tests never download; a missing asset fails by name. | |
| on: | |
| push: | |
| branches: [main, v0.3] | |
| # No `labeled`: a label starts no run here (ready.yml re-runs this one for `ready`). | |
| pull_request: | |
| branches: [main, v0.3] | |
| types: [opened, synchronize, reopened] | |
| # Weekly, for usd-core-newest. GitHub runs schedules from the default branch's workflow file only. | |
| schedule: | |
| - cron: "23 5 * * 1" | |
| workflow_dispatch: | |
| inputs: | |
| plant_failure: | |
| description: "Plant a failing canary test in every lane: the control that no lane is silently skipped" | |
| type: boolean | |
| default: false | |
| plant_slow_tests: | |
| description: "Plant an unlisted 21 s test and an unlisted 6 s test in every lane: the control that the time budget fails the job and warns" | |
| type: boolean | |
| default: false | |
| plant_shard_failure: | |
| description: "Fail this shard number of the Python 3.12 default lane on the run's first attempt only: the control that re-running just that shard clears the lane (scripts/latest_attempt.py)" | |
| type: string | |
| default: "" | |
| shards: | |
| description: "Shards per lane for this run, 1 to 12 (empty: the repository variable MIME_CI_SHARDS, else 10; scripts/shard_matrix.py)" | |
| type: string | |
| default: "" | |
| compilation_cache: | |
| description: "cold: start from an empty compilation cache and save it; warm: restore the branch's last saved cache (scripts/compilation_cache_mode.py)" | |
| type: choice | |
| options: [cold, warm] | |
| default: cold | |
| permissions: | |
| contents: read | |
| # A pull request's newer push cancels its older run, which would otherwise hold up to 37 of the organisation's 60 | |
| # concurrent jobs for nothing; a re-run is in its pull request's group too. A push to a branch and a manual or scheduled | |
| # run each get a group of their own (the run's id), so none is ever cancelled: v0.3's pushes build the compilation cache | |
| # and are the cold run that holds the time budget. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| JAX_PLATFORMS: cpu | |
| UV_VERSION: "0.13.0" | |
| MIME_CI_PLANT_FAILURE: ${{ inputs.plant_failure && '1' || '0' }} | |
| MIME_CI_PLANT_SLOW_TESTS: ${{ inputs.plant_slow_tests && '1' || '0' }} | |
| MIME_CI_PLANT_SHARD_FAILURE: ${{ inputs.plant_shard_failure }} | |
| jobs: | |
| # How many shards each lane runs as: the repository variable MIME_CI_SHARDS, a manual run's `shards` input over it, or 10 | |
| # (scripts/shard_matrix.py, CONTRIBUTING.md). A count outside 1 to 12 fails this job by name, and every job that needs | |
| # it, the lanes' required jobs included, then fails too. | |
| # And the run's mode (`full`): the full matrix on a push or a manual run, and on a pull request whose labels, read | |
| # now and not from the event, hold `ready`. The step after it runs only in the full matrix, so the run's jobs say which | |
| # mode it took (scripts/ci_ready.py reads it). A label that cannot be read fails this job, and the lanes with it. | |
| shards: | |
| name: shard count | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| count: ${{ steps.count.outputs.count }} | |
| list: ${{ steps.count.outputs.list }} | |
| full: ${{ steps.mode.outputs.full }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: The shard count | |
| id: count | |
| env: | |
| VARIABLE: ${{ vars.MIME_CI_SHARDS }} | |
| INPUT: ${{ inputs.shards }} | |
| run: python3 scripts/shard_matrix.py --variable "$VARIABLE" --input "$INPUT" | |
| - name: The run's mode, from the pull request's labels as they are now | |
| id: mode | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| EVENT: ${{ github.event_name }} | |
| PULL_REQUEST: repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} | |
| run: | | |
| full=true | |
| if [ "$EVENT" = pull_request ]; then | |
| full=$(gh api "$PULL_REQUEST" --jq 'any(.labels[].name; ascii_downcase == "ready")') | |
| fi | |
| case "$full" in | |
| true|false) echo "full=$full" >> "$GITHUB_OUTPUT" ;; | |
| *) echo "::error::the pull request's labels could not be read ($full)"; exit 1 ;; | |
| esac | |
| - name: The full matrix runs | |
| if: steps.mode.outputs.full == 'true' | |
| run: echo "the full matrix runs on this head" | |
| default-tests: | |
| name: default tests (Python ${{ matrix.python-version }}, shard ${{ matrix.shard }} of ${{ needs.shards.outputs.count }}) | |
| needs: shards | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12", "3.13", "3.14"] | |
| shard: ${{ fromJSON(needs.shards.outputs.list) }} | |
| # A reduced run (a pull request without `ready`) runs Python 3.12 alone. | |
| exclude: ${{ fromJSON(needs.shards.outputs.full == 'true' && '[]' || '[{"python-version":"3.13"},{"python-version":"3.14"}]') }} | |
| env: | |
| MIME_CI_LANE: default | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 2 | |
| - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| python-version: ${{ matrix.python-version }} | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.event_name != 'pull_request' }} | |
| - name: Install the locked PyPI releases | |
| run: uv sync --locked --extra verify | |
| - name: Build and install the lab fixture package, registered from outside MIME | |
| run: uv pip install --no-deps ./lab_fixture | |
| - name: Install the shells the completion tests ask (zsh and fish; bash is on the runner) | |
| run: sudo apt-get install -y --no-install-recommends zsh fish | |
| - name: The unit tube table, in the cache shared by every job | |
| uses: ./.github/actions/unit-tube | |
| - name: Report what this lane installed | |
| run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Compilation cache mode | |
| id: cc | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| CHOICE: ${{ inputs.compilation_cache }} | |
| run: >- | |
| python3 scripts/compilation_cache_mode.py --event "$EVENT" --choice "$CHOICE" --lane "default-py${{ matrix.python-version }}" | |
| --jaxlib "$(uv run --no-sync python -c 'import jaxlib; print(jaxlib.__version__)')" | |
| --shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" | |
| - name: Restore the compilation cache | |
| id: cc-restore | |
| if: steps.cc.outputs.mode == 'warm' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/jax-cache | |
| key: ${{ steps.cc.outputs.key }}-${{ github.sha }} | |
| restore-keys: ${{ steps.cc.outputs.key }}- | |
| - name: Tests | |
| id: tests | |
| env: | |
| MIME_TEST_JAX_TIMING: "1" | |
| JAX_COMPILATION_CACHE_DIR: ${{ runner.temp }}/jax-cache | |
| JAX_PERSISTENT_CACHE_MIN_COMPILE_TIME_SECS: "0" | |
| JAX_PERSISTENT_CACHE_MIN_ENTRY_SIZE_BYTES: "-1" | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --mime-shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" \ | |
| --mime-shard-record "$RUNNER_TEMP/report/shard.json" \ | |
| --junitxml="$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" | |
| - name: Test durations | |
| if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }} | |
| run: | | |
| echo "${{ (steps.cc.outputs.mode == 'warm' && steps.cc-restore.outputs.cache-matched-key != '') && 'warm' || 'cold' }}" \ | |
| > "$RUNNER_TEMP/report/cache-mode-shard${{ matrix.shard }}.txt" | |
| uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode mixed --markdown /dev/null | |
| - name: Plant a failing shard (a manual run's first attempt only) | |
| if: ${{ !cancelled() && env.MIME_CI_PLANT_SHARD_FAILURE == format('{0}', matrix.shard) && matrix.python-version == '3.12' && github.run_attempt == 1 }} | |
| run: | | |
| echo "::error::planted: shard ${{ matrix.shard }} fails on its first attempt, to be re-run alone" | |
| exit 1 | |
| - name: Record the shard's outcome | |
| if: ${{ !cancelled() }} | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| echo "${{ job.status }}" > "$RUNNER_TEMP/report/outcome.txt" | |
| - name: Drop unreadable compilation-cache entries | |
| id: prune-cache | |
| if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' }} | |
| run: | | |
| uv run --no-sync python scripts/prune_jax_cache.py "$RUNNER_TEMP/jax-cache" | |
| du -sh "$RUNNER_TEMP/jax-cache" | |
| - name: Save the compilation cache | |
| if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' && steps.prune-cache.outcome == 'success' }} | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/jax-cache | |
| key: ${{ steps.cc.outputs.key }}-${{ github.sha }} | |
| - name: Upload the shard's report | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: durations-default-python-${{ matrix.python-version }}-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/report | |
| if-no-files-found: error | |
| # The lane's result, under the name branch protection requires: the documentation's checks and pages, then every | |
| # shard passed and their selections partition one collection, and the lane's durations summary. It runs after a red | |
| # shard too (a job skipped by its `if` passes a required check), and fails then. | |
| default: | |
| name: default (Python ${{ matrix.python-version }}) | |
| needs: [shards, default-tests] | |
| if: ${{ !cancelled() && github.event_name != 'schedule' }} | |
| # contents for the checkout, actions to read the run's own jobs (scripts/check_shard_jobs.py). | |
| permissions: | |
| contents: read | |
| actions: read | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12", "3.13", "3.14"] | |
| env: | |
| MIME_CI_LANE: default | |
| # Python 3.12's job runs in every mode; the others run the lane only when the full matrix runs, and otherwise | |
| # their first step fails the job (a required check never passes by being skipped). | |
| MIME_CI_RUNS: ${{ needs.shards.outputs.full == 'true' || matrix.python-version == '3.12' }} | |
| steps: | |
| - name: Refuse a head the full matrix has not run on | |
| if: ${{ env.MIME_CI_RUNS != 'true' }} | |
| run: | | |
| echo '::error::the full matrix has not run on this head: add the `ready` label' | |
| exit 1 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| python-version: ${{ matrix.python-version }} | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.event_name != 'pull_request' }} | |
| - name: Install the locked PyPI releases | |
| run: uv sync --locked --extra verify | |
| - name: Build and install the lab fixture package, registered from outside MIME | |
| run: uv pip install --no-deps ./lab_fixture | |
| # The default lane's reports come first: the evidence counts page (docs build, below) reads each cited test's outcome | |
| # in them, and they are uploaded as the lane's JUnit artifact, the very files the page read. | |
| - name: Download the shards' reports | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| pattern: durations-default-python-${{ matrix.python-version }}-shard-* | |
| path: ${{ runner.temp }}/shards | |
| - name: Keep each shard's newest attempt | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| run: python3 scripts/latest_attempt.py "$RUNNER_TEMP"/shards | |
| - name: Gather the default lane's JUnit reports | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/junit" | |
| cp "$RUNNER_TEMP"/shards/*/test-results-shard*.xml "$RUNNER_TEMP/junit/" | |
| - name: Upload the default lane's JUnit reports | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: junit-default-python-${{ matrix.python-version }} | |
| path: ${{ runner.temp }}/junit | |
| if-no-files-found: error | |
| # Every check of the documentation's sources (src/mime/docs/README.md): the sources' forms, the docs walk and | |
| # its ledger, the number rule, the citation check over the built pages, then scripts/check_anomalies.py, | |
| # unchanged. Its summary, each list's length, the entries whose derivative is not exact and the word-numeral | |
| # warnings among it, goes to the run page, and so does its wall time, the check's budget. The README start list | |
| # and the none-declared list are held against origin/v0.3's, fetched when the checkout lacks it. | |
| - name: Docs check | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| run: | | |
| status=0 | |
| git rev-parse --verify --quiet origin/v0.3 > /dev/null \ | |
| || git fetch --no-tags --depth=1 origin +refs/heads/v0.3:refs/remotes/origin/v0.3 | |
| started=$(date +%s%N) | |
| uv run --no-sync mime docs check --base origin/v0.3 > "$RUNNER_TEMP/docs-check.txt" 2>&1 || status=$? | |
| echo "the command's wall time, start-up included: $(( ($(date +%s%N) - started) / 1000000 )) ms" \ | |
| >> "$RUNNER_TEMP/docs-check.txt" | |
| cat "$RUNNER_TEMP/docs-check.txt" | |
| { echo "### mime docs check (Python ${{ matrix.python-version }})"; echo '```' | |
| cat "$RUNNER_TEMP/docs-check.txt"; echo '```'; } >> "$GITHUB_STEP_SUMMARY" | |
| exit "$status" | |
| # The pages, built and never committed (src/mime/docs/README.md), then scanned for a private name or path by the | |
| # confidentiality script copied here, over the built directory, with the same key and the same fork exception as | |
| # the tracked tree's scan (the pages hold text found in no tracked file), then uploaded as a run artifact. The scan | |
| # fails the job and the upload runs only if every step before it passed, which tests/test_ci_docs_artifact.py | |
| # reads. The build's wall time, start-up included, is the generation budget. | |
| - name: Build the documentation pages | |
| run: | | |
| failed=0 | |
| started=$(date +%s%N) | |
| uv run --no-sync mime docs build --out "$RUNNER_TEMP/docs-pages" --junit "$RUNNER_TEMP/junit" \ | |
| > "$RUNNER_TEMP/docs-build.txt" 2>&1 \ | |
| || failed=$? | |
| echo "the command's wall time, start-up included: $(( ($(date +%s%N) - started) / 1000000 )) ms" \ | |
| >> "$RUNNER_TEMP/docs-build.txt" | |
| cat "$RUNNER_TEMP/docs-build.txt" | |
| { echo "### mime docs build (Python ${{ matrix.python-version }})"; echo '```' | |
| cat "$RUNNER_TEMP/docs-build.txt"; echo '```'; } >> "$GITHUB_STEP_SUMMARY" | |
| exit "$failed" | |
| - name: No private name or path in the built pages | |
| env: | |
| CONFIDENTIAL_SCAN_KEY: ${{ secrets.CONFIDENTIAL_SCAN_KEY }} | |
| run: python3 scripts/check_confidential.py --directory "$RUNNER_TEMP/docs-pages" | |
| - name: Upload the built pages | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: docs-pages-python-${{ matrix.python-version }} | |
| path: ${{ runner.temp }}/docs-pages | |
| if-no-files-found: error | |
| - name: Every shard job, at its latest attempt, passed | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| JOBS_URL: repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs?filter=latest&per_page=100 | |
| run: | | |
| set -o pipefail | |
| gh api "$JOBS_URL" --paginate --jq '.jobs[] | {name, conclusion}' \ | |
| | python3 scripts/check_shard_jobs.py --prefix "default tests (Python ${{ matrix.python-version }}, " --shards "${{ needs.shards.outputs.count }}" | |
| - name: Every shard passed, and together they ran the lane | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| run: python3 scripts/check_shards.py --shards "${{ needs.shards.outputs.count }}" --max-skipped 0 "$RUNNER_TEMP"/shards/*/ | |
| - name: Test durations | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| env: | |
| TITLE: "Test durations: default, Python ${{ matrix.python-version }}, ${{ needs.shards.outputs.count }} shards" | |
| run: >- | |
| python3 scripts/report_test_durations.py "$RUNNER_TEMP"/shards/*/test-results-shard*.xml | |
| --allowlist tests/duration_allowlist.txt --fail-over 0 --cache-mode mixed --title "$TITLE" | |
| openmaterica-main-tests: | |
| name: openmaterica-main tests (shard ${{ matrix.shard }} of ${{ needs.shards.outputs.count }}) | |
| needs: shards | |
| if: ${{ github.event_name != 'schedule' && needs.shards.outputs.full == 'true' }} | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: ${{ fromJSON(needs.shards.outputs.list) }} | |
| env: | |
| MIME_CI_LANE: openmaterica-main | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 2 | |
| - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| python-version: "3.12" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.event_name != 'pull_request' }} | |
| - name: Install the locked releases, then OpenMaterica from its main | |
| run: | | |
| uv sync --locked --extra verify | |
| uv pip install --reinstall-package openmaterica \ | |
| "openmaterica @ git+https://github.com/OpenMaterica/OpenMaterica@main" | |
| - name: Build and install the lab fixture package, registered from outside MIME | |
| run: uv pip install --no-deps ./lab_fixture | |
| - name: Install the shells the completion tests ask (zsh and fish; bash is on the runner) | |
| run: sudo apt-get install -y --no-install-recommends zsh fish | |
| - name: The unit tube table, in the cache shared by every job | |
| uses: ./.github/actions/unit-tube | |
| - name: Report what this lane installed | |
| run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Compilation cache mode | |
| id: cc | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| CHOICE: ${{ inputs.compilation_cache }} | |
| run: >- | |
| python3 scripts/compilation_cache_mode.py --event "$EVENT" --choice "$CHOICE" --lane "openmaterica-main" | |
| --jaxlib "$(uv run --no-sync python -c 'import jaxlib; print(jaxlib.__version__)')" | |
| --shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" | |
| - name: Restore the compilation cache | |
| id: cc-restore | |
| if: steps.cc.outputs.mode == 'warm' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/jax-cache | |
| key: ${{ steps.cc.outputs.key }}-${{ github.sha }} | |
| restore-keys: ${{ steps.cc.outputs.key }}- | |
| - name: Tests | |
| id: tests | |
| env: | |
| MIME_TEST_JAX_TIMING: "1" | |
| JAX_COMPILATION_CACHE_DIR: ${{ runner.temp }}/jax-cache | |
| JAX_PERSISTENT_CACHE_MIN_COMPILE_TIME_SECS: "0" | |
| JAX_PERSISTENT_CACHE_MIN_ENTRY_SIZE_BYTES: "-1" | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --mime-shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" \ | |
| --mime-shard-record "$RUNNER_TEMP/report/shard.json" \ | |
| --junitxml="$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" | |
| - name: Test durations | |
| if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }} | |
| run: | | |
| echo "${{ (steps.cc.outputs.mode == 'warm' && steps.cc-restore.outputs.cache-matched-key != '') && 'warm' || 'cold' }}" \ | |
| > "$RUNNER_TEMP/report/cache-mode-shard${{ matrix.shard }}.txt" | |
| uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode mixed --markdown /dev/null | |
| - name: Record the shard's outcome | |
| if: ${{ !cancelled() }} | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| echo "${{ job.status }}" > "$RUNNER_TEMP/report/outcome.txt" | |
| - name: Drop unreadable compilation-cache entries | |
| id: prune-cache | |
| if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' }} | |
| run: | | |
| uv run --no-sync python scripts/prune_jax_cache.py "$RUNNER_TEMP/jax-cache" | |
| du -sh "$RUNNER_TEMP/jax-cache" | |
| - name: Save the compilation cache | |
| if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' && steps.prune-cache.outcome == 'success' }} | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/jax-cache | |
| key: ${{ steps.cc.outputs.key }}-${{ github.sha }} | |
| - name: Upload the shard's report | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: durations-openmaterica-main-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/report | |
| if-no-files-found: error | |
| openmaterica-main: | |
| name: openmaterica-main | |
| needs: [shards, openmaterica-main-tests] | |
| if: ${{ !cancelled() && github.event_name != 'schedule' }} | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| MIME_CI_RUNS: ${{ needs.shards.outputs.full == 'true' }} | |
| steps: | |
| - name: Refuse a head the full matrix has not run on | |
| if: ${{ env.MIME_CI_RUNS != 'true' }} | |
| run: | | |
| echo '::error::the full matrix has not run on this head: add the `ready` label' | |
| exit 1 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Download the shards' reports | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| pattern: durations-openmaterica-main-shard-* | |
| path: ${{ runner.temp }}/shards | |
| - name: Keep each shard's newest attempt | |
| run: python3 scripts/latest_attempt.py "$RUNNER_TEMP"/shards | |
| - name: Every shard passed, and together they ran the lane | |
| run: python3 scripts/check_shards.py --shards "${{ needs.shards.outputs.count }}" --max-skipped 0 "$RUNNER_TEMP"/shards/*/ | |
| - name: Test durations | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| env: | |
| TITLE: "Test durations: openmaterica-main, ${{ needs.shards.outputs.count }} shards" | |
| run: >- | |
| python3 scripts/report_test_durations.py "$RUNNER_TEMP"/shards/*/test-results-shard*.xml | |
| --allowlist tests/duration_allowlist.txt --fail-over 0 --cache-mode mixed --title "$TITLE" | |
| maddening-main-tests: | |
| name: maddening-main tests (shard ${{ matrix.shard }} of ${{ needs.shards.outputs.count }}) | |
| needs: shards | |
| if: ${{ github.event_name != 'schedule' && needs.shards.outputs.full == 'true' }} | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: ${{ fromJSON(needs.shards.outputs.list) }} | |
| env: | |
| MIME_CI_LANE: maddening-main | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 2 | |
| - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| python-version: "3.12" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.event_name != 'pull_request' }} | |
| # MADDENING is private: git reads it with MADDENING_TOKEN (Contents: read), through a URL rewrite, so the | |
| # install records the plain repository URL and the token reaches no metadata. | |
| - name: Install the locked releases, then MADDENING from its main | |
| env: | |
| MADDENING_TOKEN: ${{ secrets.MADDENING_TOKEN }} | |
| run: | | |
| uv sync --locked --extra verify | |
| rewrite="url.https://x-access-token:${MADDENING_TOKEN}@github.com/Microrobotics-Simulation-Framework/MADDENING" | |
| git config --global "${rewrite}.insteadOf" https://github.com/Microrobotics-Simulation-Framework/MADDENING | |
| uv pip install --reinstall-package maddening \ | |
| "maddening[verify] @ git+https://github.com/Microrobotics-Simulation-Framework/MADDENING@main" | |
| git config --global --remove-section "${rewrite}" | |
| - name: Build and install the lab fixture package, registered from outside MIME | |
| run: uv pip install --no-deps ./lab_fixture | |
| - name: Install the shells the completion tests ask (zsh and fish; bash is on the runner) | |
| run: sudo apt-get install -y --no-install-recommends zsh fish | |
| - name: The unit tube table, in the cache shared by every job | |
| uses: ./.github/actions/unit-tube | |
| - name: Report what this lane installed | |
| run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Compilation cache mode | |
| id: cc | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| CHOICE: ${{ inputs.compilation_cache }} | |
| run: >- | |
| python3 scripts/compilation_cache_mode.py --event "$EVENT" --choice "$CHOICE" --lane "maddening-main" | |
| --jaxlib "$(uv run --no-sync python -c 'import jaxlib; print(jaxlib.__version__)')" | |
| --shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" | |
| - name: Restore the compilation cache | |
| id: cc-restore | |
| if: steps.cc.outputs.mode == 'warm' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/jax-cache | |
| key: ${{ steps.cc.outputs.key }}-${{ github.sha }} | |
| restore-keys: ${{ steps.cc.outputs.key }}- | |
| - name: Tests | |
| id: tests | |
| env: | |
| MIME_TEST_JAX_TIMING: "1" | |
| JAX_COMPILATION_CACHE_DIR: ${{ runner.temp }}/jax-cache | |
| JAX_PERSISTENT_CACHE_MIN_COMPILE_TIME_SECS: "0" | |
| JAX_PERSISTENT_CACHE_MIN_ENTRY_SIZE_BYTES: "-1" | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --mime-shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" \ | |
| --mime-shard-record "$RUNNER_TEMP/report/shard.json" \ | |
| --junitxml="$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" | |
| - name: Test durations | |
| if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }} | |
| run: | | |
| echo "${{ (steps.cc.outputs.mode == 'warm' && steps.cc-restore.outputs.cache-matched-key != '') && 'warm' || 'cold' }}" \ | |
| > "$RUNNER_TEMP/report/cache-mode-shard${{ matrix.shard }}.txt" | |
| uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode mixed --markdown /dev/null | |
| - name: Record the shard's outcome | |
| if: ${{ !cancelled() }} | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| echo "${{ job.status }}" > "$RUNNER_TEMP/report/outcome.txt" | |
| - name: Drop unreadable compilation-cache entries | |
| id: prune-cache | |
| if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' }} | |
| run: | | |
| uv run --no-sync python scripts/prune_jax_cache.py "$RUNNER_TEMP/jax-cache" | |
| du -sh "$RUNNER_TEMP/jax-cache" | |
| - name: Save the compilation cache | |
| if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' && steps.prune-cache.outcome == 'success' }} | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/jax-cache | |
| key: ${{ steps.cc.outputs.key }}-${{ github.sha }} | |
| - name: Upload the shard's report | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: durations-maddening-main-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/report | |
| if-no-files-found: error | |
| maddening-main: | |
| name: maddening-main | |
| needs: [shards, maddening-main-tests] | |
| if: ${{ !cancelled() && github.event_name != 'schedule' }} | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| MIME_CI_RUNS: ${{ needs.shards.outputs.full == 'true' }} | |
| steps: | |
| - name: Refuse a head the full matrix has not run on | |
| if: ${{ env.MIME_CI_RUNS != 'true' }} | |
| run: | | |
| echo '::error::the full matrix has not run on this head: add the `ready` label' | |
| exit 1 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Download the shards' reports | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| pattern: durations-maddening-main-shard-* | |
| path: ${{ runner.temp }}/shards | |
| - name: Keep each shard's newest attempt | |
| run: python3 scripts/latest_attempt.py "$RUNNER_TEMP"/shards | |
| - name: Every shard passed, and together they ran the lane | |
| run: python3 scripts/check_shards.py --shards "${{ needs.shards.outputs.count }}" --max-skipped 0 "$RUNNER_TEMP"/shards/*/ | |
| - name: Test durations | |
| if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }} | |
| env: | |
| TITLE: "Test durations: maddening-main, ${{ needs.shards.outputs.count }} shards" | |
| run: >- | |
| python3 scripts/report_test_durations.py "$RUNNER_TEMP"/shards/*/test-results-shard*.xml | |
| --allowlist tests/duration_allowlist.txt --fail-over 0 --cache-mode mixed --title "$TITLE" | |
| usd-core-newest: | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| env: | |
| MIME_CI_LANE: usd-core-newest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| python-version: "3.12" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.event_name != 'pull_request' }} | |
| - name: Install the locked releases, with usd-core re-resolved to the newest | |
| run: uv sync --upgrade-package usd-core --extra verify | |
| - name: Build and install the lab fixture package, registered from outside MIME | |
| run: uv pip install --no-deps ./lab_fixture | |
| - name: Install the shells the completion tests ask (zsh and fish; bash is on the runner) | |
| run: sudo apt-get install -y --no-install-recommends zsh fish | |
| - name: The unit tube table, in the cache shared by every job | |
| uses: ./.github/actions/unit-tube | |
| - name: Report what this lane installed | |
| run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Tests | |
| id: tests | |
| env: | |
| MIME_TEST_JAX_TIMING: "1" | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/report" | |
| uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --junitxml="$RUNNER_TEMP/report/test-results.xml" | |
| - name: Test durations | |
| if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }} | |
| env: | |
| TITLE: "Test durations: ${{ github.job }}${{ matrix.python-version && format(', Python {0}', matrix.python-version) || '' }}" | |
| run: >- | |
| uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results.xml" | |
| --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode off --title "$TITLE" | |
| - name: Upload the test durations | |
| if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: durations-${{ github.job }}${{ matrix.python-version && format('-python-{0}', matrix.python-version) || '' }} | |
| path: ${{ runner.temp }}/report | |
| if-no-files-found: error | |
| # scripts/check_confidential.py, OpenMaterica's scan copied (CONTRIBUTING.md, "Confidentiality"), on the standard | |
| # library alone: the tracked files and their paths, the diffs and messages of the commits this event brings (on a | |
| # push, every commit's when the commit before it is unknown), and a pull request's branch name, public from its | |
| # push. A change into main leaves out v0.3's commits, public already and scanned on their way into v0.3. Without | |
| # the secret the scan fails, so it cannot pass having checked nothing; a pull request from a fork, which GitHub | |
| # gives no secrets, is the one exception, told apart by the event's payload: it checks the private paths and warns | |
| # that the names were not, and the push after its merge scans with the key. A manual run scans every commit of its | |
| # branch that v0.3 does not hold, and the branch's name: what a pull request of that head into v0.3 scans, and more | |
| # than one into main (which leaves out v0.3's commits), so it is never weaker than a pull request's scan of the head. | |
| confidential: | |
| name: no private name or path | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: No private name or path in the tracked files, the commits or the branch's name | |
| env: | |
| CONFIDENTIAL_SCAN_KEY: ${{ secrets.CONFIDENTIAL_SCAN_KEY }} | |
| EVENT: ${{ github.event_name }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| REF_NAME: ${{ github.ref_name }} | |
| TARGET: ${{ github.base_ref || github.ref_name }} | |
| PR_BASE: ${{ github.event.pull_request.base.sha }} | |
| PR_HEAD: ${{ github.event.pull_request.head.sha }} | |
| PUSH_BEFORE: ${{ github.event.before }} | |
| PUSH_AFTER: ${{ github.sha }} | |
| run: | | |
| public="" | |
| if [ "$TARGET" = main ]; then public="origin/v0.3"; fi | |
| name="$HEAD_REF" | |
| case "$EVENT" in | |
| pull_request) range="$PR_BASE..$PR_HEAD" ;; | |
| push) | |
| range="$PUSH_AFTER" | |
| if git cat-file -e "$PUSH_BEFORE^{commit}" 2>/dev/null; then range="$PUSH_BEFORE..$PUSH_AFTER"; fi ;; | |
| *) range="HEAD"; public="origin/v0.3"; name="$REF_NAME" ;; | |
| esac | |
| python3 scripts/check_confidential.py ${range:+--messages "$range" --diffs "$range"} \ | |
| ${public:+--public "$public"} ${name:+--ref-name "$name"} | |
| typecheck: | |
| name: typecheck (pyright, Python 3.12) | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| # The floor, which pyrightconfig.json's pythonVersion also names. | |
| python-version: "3.12" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.event_name != 'pull_request' }} | |
| - name: Install the locked releases, pyright among them | |
| run: uv sync --locked --extra verify | |
| - name: Plant a type error in src/mime (the planted-failure run only) | |
| if: env.MIME_CI_PLANT_FAILURE == '1' | |
| run: | | |
| printf 'planted: int = "a planted type error"\n' > src/mime/_planted_type_error.py | |
| # scripts/typing_check.py exits 2 when the run cannot be trusted (pyright missing or another release than the | |
| # pin, an exit code other than 0 or 1, no JSON, too few files analysed, a core import unresolved), 1 on any | |
| # error, 0 otherwise; either of the first two fails the job. | |
| - name: pyright, strict over src/mime, at zero errors | |
| run: | | |
| set -o pipefail | |
| uv run --no-sync pyright --version | |
| uv run --no-sync python scripts/typing_check.py --markdown | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Control, a planted type error in src/mime fails the check | |
| run: | | |
| printf 'planted: int = "a planted type error"\n' > src/mime/_planted_type_error.py | |
| if uv run --no-sync python scripts/typing_check.py; then | |
| echo "::error::the typing gate passed a planted type error, so it cannot fail" | |
| exit 1 | |
| fi | |
| rm src/mime/_planted_type_error.py | |
| - name: Build the wheel | |
| run: uv build --wheel | |
| # The guard (mime assets audit): neither distribution, nor the tree a release's source archive and Zenodo's | |
| # snapshot of a tag hold, carries a file whose terms let no copy be hosted (the AR4's meshes among them, by the | |
| # manifest's hashes), a mesh baked from one, or a mesh with no record. A plugin's CI runs the same command. | |
| - name: No file that may not be redistributed in the wheel, the sdist or the committed tree | |
| run: | | |
| uv build --sdist | |
| git archive --format=tar -o "$RUNNER_TEMP/tree.tar" HEAD | |
| uv run --no-sync mime assets audit dist/*.whl dist/*.tar.gz "$RUNNER_TEMP/tree.tar" \ | |
| --manifest examples/descriptions/ar4/visuals.json | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Control, a made mesh planted in a copy of the wheel fails the guard | |
| run: | | |
| cp dist/*.whl "$RUNNER_TEMP/planted.whl" | |
| uv run --no-sync python -c " | |
| import struct, sys, zipfile | |
| with zipfile.ZipFile(sys.argv[1], 'a') as wheel: | |
| wheel.writestr('mime/data/planted.STL', b'planted'.ljust(80, b'\0') + struct.pack('<I', 1) + bytes(50)) | |
| " "$RUNNER_TEMP/planted.whl" | |
| if uv run --no-sync mime assets audit "$RUNNER_TEMP/planted.whl"; then | |
| echo "::error::the guard passed a wheel holding a planted mesh, so it cannot fail" | |
| exit 1 | |
| fi | |
| - name: A consumer's code type-checks against the installed wheel | |
| run: | | |
| uv venv "$RUNNER_TEMP/consumer" --python 3.12 | |
| uv pip install --python "$RUNNER_TEMP/consumer/bin/python" dist/*.whl | |
| mkdir "$RUNNER_TEMP/snippet" | |
| cp tests/typing/consumer_snippet.py tests/typing/pyrightconfig.json "$RUNNER_TEMP/snippet/" | |
| cd "$RUNNER_TEMP/snippet" | |
| uv run --project "$GITHUB_WORKSPACE" --no-sync pyright --pythonpath "$RUNNER_TEMP/consumer/bin/python" consumer_snippet.py | |
| - name: Control, the snippet is refused once a write into the read-only lock is not ignored | |
| run: | | |
| cd "$RUNNER_TEMP/snippet" | |
| sed 's/ # type: ignore$//' consumer_snippet.py > writes_the_lock.py | |
| if uv run --project "$GITHUB_WORKSPACE" --no-sync pyright --pythonpath "$RUNNER_TEMP/consumer/bin/python" writes_the_lock.py; then | |
| echo "::error::pyright accepted a write into the read-only lock, so the snippet's check cannot fail" | |
| exit 1 | |
| fi | |
| - name: Control, the snippet is refused once py.typed is gone | |
| run: | | |
| rm "$("$RUNNER_TEMP/consumer/bin/python" -c 'import mime, pathlib; print(pathlib.Path(mime.__file__).parent / "py.typed")')" | |
| cd "$RUNNER_TEMP/snippet" | |
| if uv run --project "$GITHUB_WORKSPACE" --no-sync pyright --pythonpath "$RUNNER_TEMP/consumer/bin/python" consumer_snippet.py; then | |
| echo "::error::the snippet type-checked without py.typed, so the check above cannot fail" | |
| exit 1 | |
| fi |