Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 67 additions & 15 deletions .github/workflows/julia-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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'
2 changes: 2 additions & 0 deletions .github/workflows/selftest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ 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`.

## Inputs

| 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. |

Expand All @@ -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
Expand Down
Loading