diff --git a/.github/workflows/julia-ci.yml b/.github/workflows/julia-ci.yml index e443eaa..3374a23 100644 --- a/.github/workflows/julia-ci.yml +++ b/.github/workflows/julia-ci.yml @@ -9,7 +9,7 @@ on: workflow_call: inputs: registered: - description: 'The package is registered: adds the downgrade job and the monthly Julia pre run.' + description: 'The package is registered: adds the downgrade job and the monthly Julia pre run (public repositories).' type: boolean default: false runic: @@ -29,15 +29,45 @@ env: # mix AMD EPYC 7763, 9V74 and Intel Xeon 8370C, 8573C, and a job landing on an incompatible # one rejects the cache ("different system or CPU target") and rebuilds every dependency. JULIA_CPU_TARGET: 'generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1);x86-64-v4,-rdrnd,base(1)' - # Only the default branch saves Julia caches; PR and other branch runs restore the default - # branch's cache and save nothing, so they cannot evict it from the 10 GB repository budget. - # Scheduled runs always run on the default branch. - JULIA_CACHE_SAVE: ${{ github.event_name == 'schedule' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + # In a public repository only the default branch saves Julia caches; PR and other branch runs + # restore the default branch's cache and save nothing, so they cannot evict it from the 10 GB + # repository budget. Scheduled runs always run on the default branch, but their event carries + # no repository. A private repository runs no push event, so every one of its runs saves. + JULIA_CACHE_SAVE: ${{ github.event_name == 'schedule' || github.event.repository.private || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} jobs: + # Admiral decision 0025: a public repository, whose Actions minutes are free, runs on every event + # its caller sends. A private one runs only on pull requests and manual dispatch, at Julia 1 + # only; every other event skips every job. The conditions list what may run, so an event a + # caller adds later is covered. Most events carry github.event.repository.private; a scheduled + # event carries no repository, so the visibility job looks it up, on scheduled runs only. Never + # test private == false: on an event with no repository, null == false is true. + visibility: + name: Visibility + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + outputs: + public: ${{ steps.lookup.outputs.public }} + steps: + - id: lookup + env: + GH_TOKEN: ${{ github.token }} + run: | + visibility=$(gh api "repos/$GITHUB_REPOSITORY" --jq .visibility) + echo "public=$([ "$visibility" = public ] && echo true || echo false)" >> "$GITHUB_OUTPUT" + test: name: Core - Julia ${{ matrix.version }} - if: github.event_name != 'pull_request' || !github.event.pull_request.draft + needs: visibility + if: >- + !cancelled() + && (github.event_name != 'pull_request' || !github.event.pull_request.draft) + && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (github.event.repository && !github.event.repository.private) + || needs.visibility.outputs.public == 'true') runs-on: ubuntu-latest timeout-minutes: 60 # a cold portable-target build of a large package runs ~30 min permissions: @@ -46,7 +76,8 @@ jobs: strategy: fail-fast: false matrix: - version: ['min', '1'] + # A private repository tests Julia 1 only; its floor is checked in the local record. + version: ${{ fromJSON(github.event.repository.private && '["1"]' || '["min", "1"]') }} env: GROUP: Core steps: @@ -73,7 +104,13 @@ jobs: qa: name: QA - if: github.event_name != 'pull_request' || !github.event.pull_request.draft + needs: visibility + if: >- + !cancelled() + && (github.event_name != 'pull_request' || !github.event.pull_request.draft) + && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (github.event.repository && !github.event.repository.private) + || needs.visibility.outputs.public == 'true') runs-on: ubuntu-latest timeout-minutes: 60 # a cold portable-target build of a large package runs ~30 min permissions: @@ -105,9 +142,16 @@ jobs: downgrade: # Direct dependencies at the lowest versions [compat] allows. Weak dependencies are left # alone (mode deps): their extensions do not load in Core, and flooring them would install - # packages such as CUDA for nothing. + # packages such as CUDA for nothing. Public repositories only: it runs at the Julia floor, which + # a private repository checks in its local record. name: Core - lowest compat - if: inputs.registered && (github.event_name != 'pull_request' || !github.event.pull_request.draft) + needs: visibility + if: >- + !cancelled() + && (github.event_name != 'pull_request' || !github.event.pull_request.draft) + && ((github.event.repository && !github.event.repository.private) + || needs.visibility.outputs.public == 'true') + && inputs.registered runs-on: ubuntu-latest timeout-minutes: 60 # a cold portable-target build of a large package runs ~30 min permissions: @@ -146,7 +190,10 @@ jobs: pre: # Monthly look at the next Julia release; allowed to fail, reported by admiral's sweep. name: Core - Julia pre - if: inputs.registered && github.event_name == 'schedule' + needs: visibility + if: >- + !cancelled() && inputs.registered + && github.event_name == 'schedule' && needs.visibility.outputs.public == 'true' runs-on: ubuntu-latest timeout-minutes: 60 # a cold portable-target build of a large package runs ~30 min continue-on-error: true @@ -178,10 +225,18 @@ jobs: format: name: Runic - if: inputs.runic && (github.event_name != 'pull_request' || !github.event.pull_request.draft) + needs: visibility + if: >- + !cancelled() + && (github.event_name != 'pull_request' || !github.event.pull_request.draft) + && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + || (github.event.repository && !github.event.repository.private) + || needs.visibility.outputs.public == 'true') + && inputs.runic runs-on: ubuntu-latest timeout-minutes: 60 # a cold portable-target build of a large package runs ~30 min permissions: + actions: write # private PR runs save, and the cache step deletes the caches they replace contents: read steps: - uses: actions/checkout@v7 @@ -196,9 +251,6 @@ jobs: cache-name: julia-cache;workflow=${{ github.workflow }};job=${{ github.job }};cpu=portable save-always: ${{ env.JULIA_CACHE_SAVE }} _job-status: ${{ env.JULIA_CACHE_SAVE == 'true' && job.status || 'not-saved-off-default-branch' }} - # Off the default branch nothing is saved (JULIA_CACHE_SAVE); on it, julia-actions/cache - # never deletes old caches, so deletion would need actions: write for nothing. - delete-old-caches: 'false' - uses: fredrikekre/runic-action@v1 with: version: '1' diff --git a/.github/workflows/selftest.yml b/.github/workflows/selftest.yml index 62c01ec..d5df895 100644 --- a/.github/workflows/selftest.yml +++ b/.github/workflows/selftest.yml @@ -5,6 +5,8 @@ name: Self-test on: push: pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/README.md b/README.md index 60ea8d5..759a09f 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,13 @@ that need a GPU, lab data, long run times or an instrument run on a lab machine | Job | When | What | |---|---|---| -| `test` | always | `GROUP=Core` tests at Julia `min` (the `julia` compat lower bound) and `1` | +| `test` | always | `GROUP=Core` tests at Julia `min` (the `julia` compat lower bound) and `1`; a private repository runs `1` only | | `qa` | always | `GROUP=QA` tests (Aqua, ExplicitImports) at Julia `1` | -| `downgrade` | `registered: true` | `GROUP=Core` at Julia `min`, the oldest supported Julia, with direct dependencies at the lowest versions `[compat]` allows | -| `pre` | `registered: true`, scheduled runs only | `GROUP=Core` at the Julia prerelease; allowed to fail | +| `downgrade` | `registered: true`, public repositories | `GROUP=Core` at Julia `min`, the oldest supported Julia, with direct dependencies at the lowest versions `[compat]` allows | +| `pre` | `registered: true`, public repositories, scheduled runs only | `GROUP=Core` at the Julia prerelease; allowed to fail | | `format` | `runic: true` | Runic formatting check | -All jobs run on ubuntu-latest, x64, with a 30-minute timeout, and skip draft pull requests. There +All jobs run on ubuntu-latest, x64, with a 60-minute timeout, and skip draft pull requests. There is no coverage upload and no docs job. The `GROUP` variable is read by the lab's standard `test/runtests.jl`, which runs the test groups declared in `test/test_groups.toml`. @@ -29,7 +29,7 @@ is no coverage upload and no docs job. The `GROUP` variable is read by the lab's | Input | Type | Default | Meaning | |---|---|---|---| -| `registered` | boolean | `false` | The package is in the General registry: adds `downgrade` and the monthly `pre` run. | +| `registered` | boolean | `false` | The package is in the General registry: adds `downgrade` and the monthly `pre` run, in a public repository. | | `runic` | boolean | `false` | Adds the Runic formatting check. Runic checks every `.jl` file in the repository. | | `project` | string | `.` | Path to the package within the repository. | @@ -49,6 +49,16 @@ The caller runs on pushes to `main` or `master` and on tags, on pull requests (i draft is marked ready), on manual dispatch, and monthly. Changes only to Markdown files, `dev/` or `.claude/` do not start a run. A newer push to a pull request cancels the older run. +In a private repository, whose Actions minutes are paid, the jobs run only on pull requests and +manual dispatch and skip every other event (admiral decision 0025: the lab tests on its own +machines, and its local record checks the Julia floor). Public repositories run on every event. + +A public repository saves its Julia cache only from the default branch, and pull requests restore +that cache. A private repository has no push runs, so every one of its runs saves: later runs on +the same pull request start warm, and a new pull request restores only the default branch's +cache, which a manual `workflow_dispatch` run on `main` seeds. GitHub drops a cache unused for 7 +days, so the first run after a quiet week starts cold. + ## Versions Callers use `@v2`. The `v2` tag is moved forward only for backward-compatible changes: a new input