push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

77
.github/workflows/README.md vendored Normal file
View File

@@ -0,0 +1,77 @@
# GitHub Actions Workflows
Some architectural notes about key decisions and concepts in our workflows:
- Instead of `pull_request` we use [`pull_request_target`](https://docs.github.com/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target) for all PR-related workflows.
This has the advantage that those workflows will run without prior approval for external contributors.
- Running on `pull_request_target` also optionally provides us with a GH_TOKEN with elevated privileges (write access), which we need to do things like adding labels, requesting reviewers or pushing branches.
**Note about security:** We need to be careful to limit the scope of elevated privileges as much as possible.
Thus they should be lowered to the minimum with `permissions: {}` in every workflow by default.
- By definition `pull_request_target` runs in the context of the **base** of the pull request.
This means, that the workflow files to run will be taken from the base branch, not the PR, and actions/checkout will not checkout the PR, but the base branch, by default.
To protect our secrets, we need to make sure to **never execute code** from the pull request and always evaluate or build nix code from the pull request with the **sandbox enabled**.
- To test the pull request's contents, we checkout the "test merge commit".
This is a temporary commit that GitHub creates automatically as "what would happen, if this PR was merged into the base branch now?".
The checkout could be done via the virtual branch `refs/pull/<pr-number>/merge`, but doing so would cause failures when this virtual branch doesn't exist (anymore).
This can happen when the PR has conflicts, in which case the virtual branch is not created, or when the PR is getting merged while workflows are still running, in which case the branch won't exist anymore at the time of checkout.
Thus, we use the `prepare` job to check whether the PR is mergeable and the test merge commit exists and only then run the relevant jobs.
- Various workflows need to make comparisons against the base branch.
In this case, we checkout the parent of the "test merge commit" for best results.
Note, that this is not necessarily the same as the default commit that actions/checkout would use, which is also a commit from the base branch (see above), but might be older.
## Terminology
- **base commit**: The pull_request_target event's context commit, i.e. the base commit given by GitHub Actions.
Same as `github.event.pull_request.base.sha`.
- **head commit**: The HEAD commit in the pull request's branch.
Same as `github.event.pull_request.head.sha`.
- **merge commit**: The temporary "test merge commit" that GitHub Actions creates and updates for the pull request.
Same as `refs/pull/${{ github.event.pull_request.number }}/merge`.
- **target commit**: The base branch's parent of the "test merge commit" to compare against.
## Concurrency Groups
We use [GitHub's Concurrency Groups](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs) to cancel older jobs on pushes to Pull Requests.
When two workflows are in the same group, a newer workflow cancels an older workflow.
Thus, it is important how to construct the group keys:
- Because we want to run jobs for different events at same time, we add `github.event_name` to the key.
This is the case for the `pull_request` which runs on changes to the workflow files to test the new files and the same workflow from the base branch run via `pull_request_event`.
- We don't want workflows of different Pull Requests to cancel each other, so we include `github.event.pull_request.number`.
The [GitHub docs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs#example-using-a-fallback-value) show using `github.head_ref` for this purpose, but this doesn't work well with forks: Different users could have the same head branch name in their forks and run CI for their PRs at the same time.
- Sometimes, there is no `pull_request.number`.
To ensure non-PR runs are never cancelled, we add a fallback of `github.run_id`.
This is a unique value for each workflow run.
- Of course, we run multiple workflows at the same time, so we add `github.workflow` to the key.
Otherwise workflows would cancel each other.
- There is a special case for reusable workflows called via `workflow_call` - they will have `github.workflow` set to their parent workflow's name.
Thus, they would cancel each other.
That's why we additionally hardcode the name of the workflow as well.
This results in a key with the following semantics:
```
<running-workflow>-<triggering-workflow>-<triggered-event>-<pull-request/fallback>
```
## Required Status Checks
The "Required Status Checks" branch ruleset is implemented in two top-level workflows: `pr.yml` and `merge-group.yml`.
The PR workflow defines all checks that need to succeed to add a Pull Request to the Merge Queue.
If no Merge Queue is set up for a branch, the PR workflow defines the checks required to merge into the target branch.
The Merge Group workflow defines all checks that are run as part of the Merge Queue.
Only when these pass, a Pull Request is finally merged into the target branch.
They don't apply when no Merge Queue is set up.
Both workflows work with the same `no PR failures` status check.
This name can never be changed, because it's used in the branch ruleset for these rules.

78
.github/workflows/backport.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
# WARNING:
# When extending this action, be aware that $GITHUB_TOKEN allows write access to
# the GitHub repository. This means that it should not evaluate user input in a
# way that allows code injection.
name: Backport
on:
pull_request_target:
types: [closed, labeled]
permissions:
contents: read
issues: write
pull-requests: write
defaults:
run:
shell: bash
jobs:
backport:
name: Backport Pull Request
if: vars.NIXPKGS_CI_APP_ID && github.event.pull_request.merged == true && (github.event.action != 'labeled' || startsWith(github.event.label.name, 'backport'))
runs-on: ubuntu-24.04-arm
timeout-minutes: 3
steps:
# Use a GitHub App to create the PR so that CI gets triggered
# The App is scoped to Repository > Contents and Pull Requests: write for Nixpkgs
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
permission-workflows: write
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
token: ${{ steps.app-token.outputs.token }}
- name: Log current API rate limits
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api /rate_limit | jq
- name: Create backport PRs
id: backport
uses: korthout/backport-action@ca4972adce8039ff995e618f5fc02d1b7961f27a # v3.3.0
with:
# Config README: https://github.com/korthout/backport-action#backport-action
copy_labels_pattern: 'severity:\ssecurity'
github_token: ${{ steps.app-token.outputs.token }}
pull_description: |-
Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}.
* [ ] Before merging, ensure that this backport is [acceptable for the release](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#changes-acceptable-for-releases).
* Even as a non-committer, if you find that it is not acceptable, leave a comment.
- name: Log current API rate limits
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api /rate_limit | jq
- name: "Add 'has: port to stable' label"
if: steps.backport.outputs.created_pull_numbers != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
# Not using the app on purpose to avoid triggering another workflow run after adding this label.
script: |
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [ '8.has: port to stable' ]
})

103
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Build
on:
workflow_call:
inputs:
baseBranch:
required: true
type: string
mergedSha:
required: true
type: string
secrets:
CACHIX_AUTH_TOKEN:
required: true
permissions: {}
defaults:
run:
shell: bash
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
name: x86_64-linux
systems: x86_64-linux
builds: [shell, manual-nixos, lib-tests, tarball]
desc: shell, docs, lib, tarball
- runner: ubuntu-24.04-arm
name: aarch64-linux
systems: aarch64-linux
builds: [shell, manual-nixos, manual-nixpkgs, manual-nixpkgs-tests]
desc: shell, docs
- runner: macos-14
name: darwin
systems: aarch64-darwin x86_64-darwin
builds: [shell]
desc: shell
name: '${{ matrix.name }}: ${{ matrix.desc }}'
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Checkout the merge commit
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
- uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
with:
# Sandbox is disabled on MacOS by default.
extra_nix_config: sandbox = true
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
# The nixpkgs-ci cache should not be trusted or used outside of Nixpkgs and its forks' CI.
name: ${{ vars.CACHIX_NAME || 'nixpkgs-ci' }}
extraPullNames: nixpkgs-ci
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
pushFilter: '(-source$|-nixpkgs-tarball-)'
- run: nix-env --install -f nixpkgs/untrusted-pinned -A nix-build-uncached
- name: Build shell
if: contains(matrix.builds, 'shell')
run: echo "${{ matrix.systems }}" | xargs -n1 nix-build-uncached nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A shell --argstr system
- name: Build NixOS manual
if: |
contains(matrix.builds, 'manual-nixos') && !cancelled() &&
contains(fromJSON(inputs.baseBranch).type, 'primary')
run: nix-build-uncached nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A manual-nixos --out-link nixos-manual
- name: Build Nixpkgs manual
if: contains(matrix.builds, 'manual-nixpkgs') && !cancelled()
run: nix-build-uncached nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A manual-nixpkgs -A manual-nixpkgs-tests
- name: Build Nixpkgs manual tests
if: contains(matrix.builds, 'manual-nixpkgs-tests') && !cancelled()
run: nix-build-uncached nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A manual-nixpkgs-tests
- name: Build lib tests
if: contains(matrix.builds, 'lib-tests') && !cancelled()
run: nix-build-uncached nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A lib-tests
- name: Build tarball
if: contains(matrix.builds, 'tarball') && !cancelled()
run: nix-build-uncached nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A tarball
- name: Upload NixOS manual
if: |
contains(matrix.builds, 'manual-nixos') && !cancelled() &&
contains(fromJSON(inputs.baseBranch).type, 'primary')
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: nixos-manual-${{ matrix.name }}
path: nixos-manual

104
.github/workflows/check.yml vendored Normal file
View File

@@ -0,0 +1,104 @@
name: Check
on:
workflow_call:
inputs:
baseBranch:
required: true
type: string
headBranch:
required: true
type: string
mergedSha:
required: true
type: string
targetSha:
required: true
type: string
secrets:
CACHIX_AUTH_TOKEN:
required: true
permissions: {}
defaults:
run:
shell: bash
jobs:
commits:
permissions:
pull-requests: write
runs-on: ubuntu-24.04-arm
timeout-minutes: 3
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: trusted
sparse-checkout: |
ci/github-script
- name: Install dependencies
run: npm install bottleneck
- name: Log current API rate limits
env:
GH_TOKEN: ${{ github.token }}
run: gh api /rate_limit | jq
- name: Check commits
id: check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
TARGETS_STABLE: ${{ fromJSON(inputs.baseBranch).stable && !contains(fromJSON(inputs.headBranch).type, 'development') }}
with:
script: |
const targetsStable = JSON.parse(process.env.TARGETS_STABLE)
require('./trusted/ci/github-script/commits.js')({
github,
context,
core,
dry: context.eventName == 'pull_request',
cherryPicks: context.eventName == 'pull_request' || targetsStable,
})
- name: Log current API rate limits
env:
GH_TOKEN: ${{ github.token }}
run: gh api /rate_limit | jq
owners:
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Checkout merge and target commits
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
- uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
# The nixpkgs-ci cache should not be trusted or used outside of Nixpkgs and its forks' CI.
name: ${{ vars.CACHIX_NAME || 'nixpkgs-ci' }}
extraPullNames: nixpkgs-ci
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
pushFilter: -source$
- name: Build codeowners validator
run: nix-build nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A codeownersValidator
- name: Validate codeowners
env:
OWNERS_FILE: nixpkgs/untrusted/ci/OWNERS
REPOSITORY_PATH: nixpkgs/untrusted
# Omits "owners", which checks whether GitHub handles exist, but fails with nested team
# structures.
CHECKS: "duppatterns,files,syntax"
# Set this to "notowned,avoid-shadowing" to check that all files are owned by somebody
EXPERIMENTAL_CHECKS: "avoid-shadowing"
run: result/bin/codeowners-validator

66
.github/workflows/dismissed-review.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Dismissed review
on:
workflow_run:
workflows:
- Review dismissed
types: [completed]
concurrency:
group: dismissed-review-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions:
pull-requests: write
defaults:
run:
shell: bash
jobs:
# The `check-cherry-picks` workflow creates review comments which reviewers
# are encouraged to manually dismiss if they're not relevant.
# When a CI-generated review is dismissed, this job automatically minimizes
# it, preventing it from cluttering the PR.
minimize:
name: Minimize as resolved
runs-on: ubuntu-24.04-arm
timeout-minutes: 2
steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
// PRs from forks don't have any PRs associated by default.
// Thus, we request the PR number with an API call *to* the fork's repo.
// Multiple pull requests can be open from the same head commit, either via
// different base branches or head branches.
const { head_repository, head_sha, repository } = context.payload.workflow_run
await Promise.all(
(await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, {
owner: head_repository.owner.login,
repo: head_repository.name,
commit_sha: head_sha
}))
.filter(pull_request => pull_request.base.repo.id == repository.id)
.map(async (pull_request) =>
Promise.all(
(await github.paginate(github.rest.pulls.listReviews, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull_request.number
})).filter(review =>
review.user?.login == 'github-actions[bot]' &&
review.state == 'DISMISSED'
).map(review => github.graphql(`
mutation($node_id:ID!) {
minimizeComment(input: {
classifier: RESOLVED,
subjectId: $node_id
})
{ clientMutationId }
}`,
{ node_id: review.node_id }
))
)
)
)

59
.github/workflows/edited.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
# Some workflows depend on the base branch of the PR, but changing the base branch is not included in the default trigger events, which would be `opened`, `synchronize` or `reopened`.
# Instead it causes an `edited` event.
# Since `edited` is also triggered when PR title/body is changed, we use this wrapper workflow, to run the other workflows conditionally only.
# There are already feature requests for adding a `base_changed` event:
# - https://github.com/orgs/community/discussions/35058
# - https://github.com/orgs/community/discussions/64119
#
# Instead of adding this to each workflow's pull_request_target event, we trigger this in a separate workflow.
# This has the advantage, that we can actually skip running those jobs for simple edits like changing the title or description.
# The actual trigger happens by closing and re-opening the pull request, which triggers the default pull_request_target events.
# This is much simpler and reliable than other approaches.
name: "Edited base branch"
on:
pull_request_target:
types: [edited]
concurrency:
group: edited-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions: {}
defaults:
run:
shell: bash
jobs:
base:
name: Trigger jobs
runs-on: ubuntu-24.04
if: github.event.changes.base.ref.from && github.event.changes.base.ref.from != github.event.pull_request.base.ref
timeout-minutes: 2
steps:
# Use a GitHub App to create the PR so that CI gets triggered
# The App is scoped to Repository > Contents and Pull Requests: write for Nixpkgs
# We only need Pull Requests: write here, but the app is also used for backports.
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-pull-requests: write
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
function changeState(state) {
return github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
state
})
}
await changeState('closed')
await changeState('open')

361
.github/workflows/eval.yml vendored Normal file
View File

@@ -0,0 +1,361 @@
name: Eval
on:
workflow_call:
inputs:
mergedSha:
required: true
type: string
targetSha:
type: string
systems:
required: true
type: string
testVersions:
required: false
default: false
type: boolean
secrets:
CACHIX_AUTH_TOKEN:
required: true
permissions: {}
defaults:
run:
shell: bash
jobs:
versions:
if: inputs.testVersions
runs-on: ubuntu-24.04-arm
outputs:
versions: ${{ steps.versions.outputs.versions }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: trusted
sparse-checkout: |
ci/supportedVersions.nix
- name: Check out the PR at the test merge commit
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ inputs.mergedSha }}
path: untrusted
sparse-checkout: |
ci/pinned.json
- name: Install Nix
uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- name: Load supported versions
id: versions
run: |
echo "versions=$(trusted/ci/supportedVersions.nix --arg pinnedJson untrusted/ci/pinned.json)" >> "$GITHUB_OUTPUT"
eval:
runs-on: ubuntu-24.04-arm
needs: versions
if: ${{ !cancelled() && !failure() }}
strategy:
fail-fast: false
matrix:
system: ${{ fromJSON(inputs.systems) }}
version:
- "" # Default Eval triggering rebuild labels and such.
- ${{ fromJSON(needs.versions.outputs.versions || '[]') }} # Only for ci/pinned.json updates.
# Failures for versioned Evals will be collected in a separate job below
# to not interrupt main Eval's compare step.
continue-on-error: ${{ matrix.version != '' }}
name: ${{ matrix.system }}${{ matrix.version && format(' @ {0}', matrix.version) || '' }}
timeout-minutes: 15
steps:
# This is not supposed to be used and just acts as a fallback.
# Without swap, when Eval runs OOM, it will fail badly with a
# job that is sometimes not interruptible anymore.
# If Eval starts swapping, decrease chunkSize to keep it fast.
- name: Enable swap
run: |
sudo fallocate -l 10G /swap
sudo chmod 600 /swap
sudo mkswap /swap
sudo swapon /swap
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Check out the PR at merged and target commits
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
target-as-trusted-at: ${{ inputs.targetSha }}
- name: Install Nix
uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
# The nixpkgs-ci cache should not be trusted or used outside of Nixpkgs and its forks' CI.
name: ${{ vars.CACHIX_NAME || 'nixpkgs-ci' }}
extraPullNames: nixpkgs-ci
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
pushFilter: '(-source|-single-chunk)$'
- name: Evaluate the ${{ matrix.system }} output paths at the merge commit
env:
MATRIX_SYSTEM: ${{ matrix.system }}
MATRIX_VERSION: ${{ matrix.version || 'nixVersions.latest' }}
run: |
nix-build nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A eval.singleSystem \
--argstr evalSystem "$MATRIX_SYSTEM" \
--arg chunkSize 8000 \
--argstr nixPath "$MATRIX_VERSION" \
--out-link merged
# If it uses too much memory, slightly decrease chunkSize.
# Note: Keep the same further down in sync!
# Running the attrpath generation step separately from the outpath step afterwards.
# The idea is that, *if* Eval on the target branch has not finished, yet, we will
# generate the attrpaths in the meantime - and the separate command command afterwards
# will check cachix again for whether Eval has finished. If no Eval result from the
# target branch can be found the second time, we proceed to run it in here. Attrpaths
# generation takes roughly 30 seconds, so for every normal use-case this should be more
# than enough of a head start for Eval on the target branch to finish.
# This edge-case, that Eval on the target branch is delayed is unlikely to happen anyway:
# For a commit to become the target commit of a PR, it must *already* be on the branch.
# Normally, CI should always start running on that push event *before* it starts running
# on the PR.
- name: Evaluate the ${{ matrix.system }} attribute paths at the target commit
if: inputs.targetSha
env:
MATRIX_SYSTEM: ${{ matrix.system }}
run: |
nix-build nixpkgs/trusted/ci --arg nixpkgs ./nixpkgs/trusted-pinned -A eval.attrpathsSuperset \
--argstr evalSystem "$MATRIX_SYSTEM" \
--argstr nixPath "nixVersions.latest"
- name: Evaluate the ${{ matrix.system }} output paths at the target commit
if: inputs.targetSha
env:
MATRIX_SYSTEM: ${{ matrix.system }}
# This should be very quick, because it pulls the eval results from Cachix.
run: |
nix-build nixpkgs/trusted/ci --arg nixpkgs ./nixpkgs/trusted-pinned -A eval.singleSystem \
--argstr evalSystem "$MATRIX_SYSTEM" \
--arg chunkSize 8000 \
--argstr nixPath "nixVersions.latest" \
--out-link target
- name: Compare outpaths against the target branch
if: inputs.targetSha
env:
MATRIX_SYSTEM: ${{ matrix.system }}
run: |
nix-build nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A eval.diff \
--arg beforeDir ./target \
--arg afterDir ./merged \
--argstr evalSystem "$MATRIX_SYSTEM" \
--out-link diff
- name: Upload outpaths diff and stats
if: inputs.targetSha
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ matrix.version && format('{0}-', matrix.version) || '' }}diff-${{ matrix.system }}
path: diff/*
compare:
runs-on: ubuntu-24.04-arm
needs: [eval]
if: inputs.targetSha && !cancelled() && !failure()
permissions:
statuses: write
timeout-minutes: 5
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Check out the PR at the target commit
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
target-as-trusted-at: ${{ inputs.targetSha }}
- name: Download output paths and eval stats for all systems
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: diff-*
path: diff
merge-multiple: true
- name: Install Nix
uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- name: Combine all output paths and eval stats
run: |
nix-build nixpkgs/trusted/ci --arg nixpkgs ./nixpkgs/trusted-pinned -A eval.combine \
--arg diffDir ./diff \
--out-link combined
- name: Compare against the target branch
env:
AUTHOR_ID: ${{ github.event.pull_request.user.id }}
run: |
git -C nixpkgs/trusted diff --name-only ${{ inputs.mergedSha }} \
| jq --raw-input --slurp 'split("\n")[:-1]' > touched-files.json
# Use the target branch to get accurate maintainer info
nix-build nixpkgs/trusted/ci --arg nixpkgs ./nixpkgs/trusted-pinned -A eval.compare \
--arg combinedDir ./combined \
--arg touchedFilesJson ./touched-files.json \
--argstr githubAuthorId "$AUTHOR_ID" \
--out-link comparison
cat comparison/step-summary.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload the comparison results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: comparison
path: comparison/*
- name: Add eval summary to commit statuses
if: ${{ github.event_name == 'pull_request_target' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const { readFile } = require('node:fs/promises')
const changed = JSON.parse(await readFile('comparison/changed-paths.json', 'utf-8'))
const description =
'Package: ' + [
`added ${changed.attrdiff.added.length}`,
`removed ${changed.attrdiff.removed.length}`,
`changed ${changed.attrdiff.changed.length}`
].join(', ') +
' — Rebuild: ' + [
`linux ${changed.rebuildCountByKernel.linux}`,
`darwin ${changed.rebuildCountByKernel.darwin}`
].join(', ')
const { serverUrl, repo, runId, payload } = context
const target_url =
`${serverUrl}/${repo.owner}/${repo.repo}/actions/runs/${runId}?pr=${payload.pull_request.number}`
await github.rest.repos.createCommitStatus({
...repo,
sha: payload.pull_request.head.sha,
context: 'Eval Summary',
state: 'success',
description,
target_url
})
# Creates a matrix of Eval performance for various versions and systems.
report:
runs-on: ubuntu-24.04-arm
needs: [versions, eval]
steps:
- name: Download output paths and eval stats for all versions
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: "*-diff-*"
path: versions
- name: Add version comparison table to job summary
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
SYSTEMS: ${{ inputs.systems }}
VERSIONS: ${{ needs.versions.outputs.versions }}
with:
script: |
const { readFileSync } = require('node:fs')
const path = require('node:path')
const systems = JSON.parse(process.env.SYSTEMS)
const versions = JSON.parse(process.env.VERSIONS)
core.summary.addHeading('Lix/Nix version comparison')
core.summary.addTable(
[].concat(
[
[{ data: 'Version', header: true }].concat(
systems.map((system) => ({ data: system, header: true })),
),
],
versions.map((version) =>
[{ data: version }].concat(
systems.map((system) => {
try {
const artifact = path.join('versions', `${version}-diff-${system}`)
const time = Math.round(
parseFloat(
readFileSync(
path.join(artifact, 'after', system, 'total-time'),
'utf-8',
),
),
)
const diff = JSON.parse(
readFileSync(path.join(artifact, system, 'diff.json'), 'utf-8'),
)
const attrs = [].concat(
diff.added,
diff.removed,
diff.changed,
diff.rebuilds
).filter(attr =>
// Exceptions related to dev shells, which changed at some time between 2.18 and 2.24.
!attr.startsWith('tests.devShellTools.nixos.') &&
!attr.startsWith('tests.devShellTools.unstructuredDerivationInputEnv.')
)
if (attrs.length > 0) {
core.setFailed(
`${version} on ${system} has changed outpaths!\nNote: Please make sure to update ci/pinned.json separately from changes to other packages.`,
)
return { data: ':x:' }
}
return { data: time }
} catch {
core.warning(`${version} on ${system} did not produce artifact.`)
return { data: ':warning:' }
}
}),
),
),
),
)
core.summary.addRaw(
'\n*Evaluation time in seconds without downloading dependencies.*',
true,
)
core.summary.addRaw('\n*:warning: Job did not report a result.*', true)
core.summary.addRaw(
'\n*:x: Job produced different outpaths than the target branch.*',
true,
)
core.summary.write()
misc:
if: ${{ github.event_name != 'push' }}
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Checkout the merge commit
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
- name: Install Nix
uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- name: Run misc eval tasks in parallel
run: |
# Ensure flake outputs on all systems still evaluate
nix flake check --all-systems --no-build './nixpkgs/untrusted?shallow=1' &
# Query nixpkgs with aliases enabled to check for basic syntax errors
nix-env -I ./nixpkgs/untrusted -f ./nixpkgs/untrusted -qa '*' --option restrict-eval true --option allow-import-from-derivation false >/dev/null &
wait

120
.github/workflows/labels.yml vendored Normal file
View File

@@ -0,0 +1,120 @@
# WARNING:
# When extending this action, be aware that $GITHUB_TOKEN allows some write
# access to the GitHub API. This means that it should not evaluate user input in
# a way that allows code injection.
name: Labels
on:
schedule:
- cron: '07,17,27,37,47,57 * * * *'
workflow_call:
inputs:
headBranch:
required: true
type: string
secrets:
NIXPKGS_CI_APP_PRIVATE_KEY:
required: true
workflow_dispatch:
concurrency:
# This explicitly avoids using `run_id` for the concurrency key to make sure that only
# *one* scheduled run can run at a time.
group: labels-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number }}
# PR-triggered runs will be cancelled, but scheduled runs will be queued.
cancel-in-progress: ${{ github.event_name != 'schedule' }}
# This is used as fallback without app only.
# This happens when testing in forks without setting up that app.
permissions:
issues: write
pull-requests: write
defaults:
run:
shell: bash
jobs:
update:
runs-on: ubuntu-24.04-arm
if: github.event_name != 'schedule' || github.repository_owner == 'NixOS'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: |
ci/github-script
- name: Install dependencies
run: npm install @actions/artifact bottleneck
# Use a GitHub App, because it has much higher rate limits: 12,500 instead of 5,000 req / hour.
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
if: github.event_name != 'pull_request' && vars.NIXPKGS_CI_APP_ID
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-issues: write
permission-pull-requests: write
- name: Log current API rate limits
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
run: gh api /rate_limit | jq
- name: Labels from API data and Eval results
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
retries: 3
script: |
require('./ci/github-script/labels.js')({
github,
context,
core,
dry: context.eventName == 'pull_request'
})
- name: Log current API rate limits
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
run: gh api /rate_limit | jq
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
name: Labels from touched files
if: |
github.event_name == 'pull_request_target' &&
!contains(fromJSON(inputs.headBranch).type, 'development')
with:
repo-token: ${{ steps.app-token.outputs.token }}
configuration-path: .github/labeler.yml # default
sync-labels: true
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
name: Labels from touched files (no sync)
if: |
github.event_name == 'pull_request_target' &&
!contains(fromJSON(inputs.headBranch).type, 'development')
with:
repo-token: ${{ steps.app-token.outputs.token }}
configuration-path: .github/labeler-no-sync.yml
sync-labels: false
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
name: Labels from touched files (development branches)
# Development branches like staging-next, haskell-updates and python-updates get special labels.
# This is to avoid the mass of labels there, which is mostly useless - and really annoying for
# the backport labels.
if: |
github.event_name == 'pull_request_target' &&
contains(fromJSON(inputs.headBranch).type, 'development')
with:
repo-token: ${{ steps.app-token.outputs.token }}
configuration-path: .github/labeler-development-branches.yml
sync-labels: true
- name: Log current API rate limits
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
run: gh api /rate_limit | jq

119
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,119 @@
name: Lint
on:
workflow_call:
inputs:
mergedSha:
required: true
type: string
targetSha:
required: true
type: string
secrets:
CACHIX_AUTH_TOKEN:
required: true
permissions: {}
defaults:
run:
shell: bash
jobs:
treefmt:
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Checkout the merge commit
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
- uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
# TODO: Figure out how to best enable caching for the treefmt job. Cachix won't work well,
# because the cache would be invalidated on every commit - treefmt checks every file.
# Maybe we can cache treefmt's eval-cache somehow.
- name: Check that files are formatted
run: |
# Note that it's fine to run this on untrusted code because:
# - There's no secrets accessible here
# - The build is sandboxed
if ! nix-build nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A fmt.check; then
echo "Some files are not properly formatted"
echo "Please format them by going to the Nixpkgs root directory and running one of:"
echo " nix-shell --run treefmt"
echo " nix develop --command treefmt"
echo " nix fmt"
echo "Make sure your branch is up to date with master; rebase if not."
echo "If you're having trouble, please ping @NixOS/nix-formatting"
exit 1
fi
parse:
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Checkout the merge commit
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
- uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
# The nixpkgs-ci cache should not be trusted or used outside of Nixpkgs and its forks' CI.
name: ${{ vars.CACHIX_NAME || 'nixpkgs-ci' }}
extraPullNames: nixpkgs-ci
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
pushFilter: -source$
- name: Parse all nix files
run: |
# Tests multiple versions at once, let's make sure all of them run, so keep-going.
nix-build nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A parse --keep-going
nixpkgs-vet:
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: .github/actions
- name: Checkout merge and target commits
uses: ./.github/actions/checkout
with:
merged-as-untrusted-at: ${{ inputs.mergedSha }}
target-as-trusted-at: ${{ inputs.targetSha }}
- uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
# The nixpkgs-ci cache should not be trusted or used outside of Nixpkgs and its forks' CI.
name: ${{ vars.CACHIX_NAME || 'nixpkgs-ci' }}
extraPullNames: nixpkgs-ci
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
pushFilter: -source$
- name: Running nixpkgs-vet
env:
# Force terminal colors to be enabled. The library that `nixpkgs-vet` uses respects https://bixense.com/clicolors/
CLICOLOR_FORCE: 1
run: |
if nix-build nixpkgs/untrusted/ci --arg nixpkgs ./nixpkgs/untrusted-pinned -A nixpkgs-vet --arg base "./nixpkgs/trusted" --arg head "./nixpkgs/untrusted"; then
exit 0
else
exitCode=$?
echo "To run locally: ./ci/nixpkgs-vet.sh $GITHUB_BASE_REF https://github.com/$GITHUB_REPOSITORY.git"
echo "If you're having trouble, ping @NixOS/nixpkgs-vet"
exit "$exitCode"
fi

57
.github/workflows/merge-group.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Merge Group
on:
merge_group:
workflow_call:
inputs:
mergedSha:
required: true
type: string
targetSha:
required: true
type: string
secrets:
CACHIX_AUTH_TOKEN:
required: true
permissions: {}
jobs:
lint:
name: Lint
uses: ./.github/workflows/lint.yml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
mergedSha: ${{ inputs.mergedSha || github.event.merge_group.head_sha }}
targetSha: ${{ inputs.targetSha || github.event.merge_group.base_sha }}
# This job's only purpose is to create the target for the "Required Status Checks" branch ruleset.
# It "needs" all the jobs that should block the Merge Queue.
unlock:
if: github.event_name != 'pull_request' && always()
# Modify this list to add or remove jobs from required status checks.
needs:
- lint
runs-on: ubuntu-24.04-arm
permissions:
statuses: write
steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
RESULTS: ${{ toJSON(needs.*.result) }}
with:
script: |
const { serverUrl, repo, runId, payload } = context
const target_url =
`${serverUrl}/${repo.owner}/${repo.repo}/actions/runs/${runId}`
await github.rest.repos.createCommitStatus({
...repo,
sha: payload.merge_group.head_sha,
// WARNING:
// Do NOT change the name of this, otherwise the rule will not catch it anymore.
// This would prevent all PRs from merging.
context: 'no PR failures',
state: JSON.parse(process.env.RESULTS).every(result => result == 'success') ? 'success' : 'error',
target_url,
})

View File

@@ -0,0 +1,46 @@
# This action periodically merges base branches into staging branches.
# This is done to
# * prevent conflicts or rather resolve them early
# * make all potential breakage happen on the staging branch
# * and make sure that all major rebuilds happen before the staging
# branch gets merged back into its base branch.
name: "Periodic Merges (24h)"
on:
schedule:
# * is a special character in YAML so you have to quote this string
# Merge every 24 hours
- cron: '0 0 * * *'
workflow_dispatch:
permissions: {}
defaults:
run:
shell: bash
jobs:
periodic-merge:
if: github.repository_owner == 'NixOS'
strategy:
# don't fail fast, so that all pairs are tried
fail-fast: false
# certain branches need to be merged in order, like master->staging-next->staging
# and disabling parallelism ensures the order of the pairs below.
max-parallel: 1
matrix:
pairs:
- from: release-25.05
into: staging-next-25.05
- from: staging-next-25.05
into: staging-25.05
- name: merge-base(master,staging) → haskell-updates
from: master staging
into: haskell-updates
uses: ./.github/workflows/periodic-merge.yml
with:
from: ${{ matrix.pairs.from }}
into: ${{ matrix.pairs.into }}
name: ${{ matrix.pairs.name || format('{0} → {1}', matrix.pairs.from, matrix.pairs.into) }}
secrets: inherit

45
.github/workflows/periodic-merge-6h.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
# This action periodically merges base branches into staging branches.
# This is done to
# * prevent conflicts or rather resolve them early
# * make all potential breakage happen on the staging branch
# * and make sure that all major rebuilds happen before the staging
# branch gets merged back into its base branch.
name: "Periodic Merges (6h)"
on:
schedule:
# * is a special character in YAML so you have to quote this string
# Merge every 6 hours
- cron: '0 */6 * * *'
workflow_dispatch:
permissions: {}
defaults:
run:
shell: bash
jobs:
periodic-merge:
if: github.repository_owner == 'NixOS'
strategy:
# don't fail fast, so that all pairs are tried
fail-fast: false
# certain branches need to be merged in order, like master->staging-next->staging
# and disabling parallelism ensures the order of the pairs below.
max-parallel: 1
matrix:
pairs:
- from: master
into: staging-next
- from: staging-next
into: staging
- from: master
into: staging-nixos
uses: ./.github/workflows/periodic-merge.yml
with:
from: ${{ matrix.pairs.from }}
into: ${{ matrix.pairs.into }}
name: ${{ format('{0} → {1}', matrix.pairs.from, matrix.pairs.into) }}
secrets: inherit

64
.github/workflows/periodic-merge.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: "Merge"
on:
workflow_call:
inputs:
from:
description: Branch to merge into target branch. Can also be two branches separated by space to find the merge base between them.
required: true
type: string
into:
description: Target branch to merge into.
required: true
type: string
defaults:
run:
shell: bash
jobs:
merge:
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
steps:
# Use a GitHub App to create the PR so that CI gets triggered
# The App is scoped to Repository > Contents and Pull Requests: write for Nixpkgs
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
id: app-token
with:
app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Find merge base between two branches
if: contains(inputs.from, ' ')
id: merge_base
env:
branches: ${{ inputs.from }}
run: |
# turn into bash array, split on space
read -ra branches <<< "$branches"
git fetch --shallow-since="1 month ago" origin "${branches[@]}"
merge_base="$(git merge-base "refs/remotes/origin/${branches[0]}" "refs/remotes/origin/${branches[1]}")"
echo "Found merge base: $merge_base" >&2
echo "merge_base=$merge_base" >> "$GITHUB_OUTPUT"
- name: ${{ inputs.from }} → ${{ inputs.into }}
uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # 1.4.0
with:
type: now
from_branch: ${{ steps.merge_base.outputs.merge_base || inputs.from }}
target_branch: ${{ inputs.into }}
github_token: ${{ steps.app-token.outputs.token }}
- name: Comment on failure
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
if: ${{ failure() }}
with:
issue-number: 105153
body: |
Periodic merge from `${{ inputs.from }}` into `${{ inputs.into }}` has [failed](https://github.com/NixOS/nixpkgs/actions/runs/${{ github.run_id }}).
token: ${{ steps.app-token.outputs.token }}

154
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,154 @@
name: PR
on:
pull_request_target:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: true
NIXPKGS_CI_APP_PRIVATE_KEY:
required: true
OWNER_APP_PRIVATE_KEY:
# The Test workflow should not actually request reviews from owners.
required: false
concurrency:
group: pr-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions: {}
jobs:
prepare:
runs-on: ubuntu-24.04-arm
permissions:
# wrong branch review comment
pull-requests: write
outputs:
baseBranch: ${{ steps.prepare.outputs.base }}
headBranch: ${{ steps.prepare.outputs.head }}
mergedSha: ${{ steps.prepare.outputs.mergedSha }}
targetSha: ${{ steps.prepare.outputs.targetSha }}
systems: ${{ steps.prepare.outputs.systems }}
touched: ${{ steps.prepare.outputs.touched }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout-cone-mode: true # default, for clarity
sparse-checkout: |
ci/github-script
- id: prepare
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
require('./ci/github-script/prepare.js')({
github,
context,
core,
dry: context.eventName == 'pull_request',
})
check:
name: Check
needs: [prepare]
uses: ./.github/workflows/check.yml
permissions:
# cherry-picks
pull-requests: write
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
baseBranch: ${{ needs.prepare.outputs.baseBranch }}
headBranch: ${{ needs.prepare.outputs.headBranch }}
mergedSha: ${{ needs.prepare.outputs.mergedSha }}
targetSha: ${{ needs.prepare.outputs.targetSha }}
lint:
name: Lint
needs: [prepare]
uses: ./.github/workflows/lint.yml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
mergedSha: ${{ needs.prepare.outputs.mergedSha }}
targetSha: ${{ needs.prepare.outputs.targetSha }}
eval:
name: Eval
needs: [prepare]
uses: ./.github/workflows/eval.yml
permissions:
# compare
statuses: write
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
mergedSha: ${{ needs.prepare.outputs.mergedSha }}
targetSha: ${{ needs.prepare.outputs.targetSha }}
systems: ${{ needs.prepare.outputs.systems }}
testVersions: ${{ contains(fromJSON(needs.prepare.outputs.touched), 'pinned') && !contains(fromJSON(needs.prepare.outputs.headBranch).type, 'development') }}
labels:
name: Labels
needs: [prepare, eval]
uses: ./.github/workflows/labels.yml
permissions:
issues: write
pull-requests: write
secrets:
NIXPKGS_CI_APP_PRIVATE_KEY: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
with:
headBranch: ${{ needs.prepare.outputs.headBranch }}
reviewers:
name: Reviewers
needs: [prepare, eval]
if: |
needs.prepare.outputs.targetSha &&
!contains(fromJSON(needs.prepare.outputs.headBranch).type, 'development')
uses: ./.github/workflows/reviewers.yml
secrets:
OWNER_APP_PRIVATE_KEY: ${{ secrets.OWNER_APP_PRIVATE_KEY }}
build:
name: Build
needs: [prepare]
uses: ./.github/workflows/build.yml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
baseBranch: ${{ needs.prepare.outputs.baseBranch }}
mergedSha: ${{ needs.prepare.outputs.mergedSha }}
# This job's only purpose is to create the target for the "Required Status Checks" branch ruleset.
# It "needs" all the jobs that should block merging a PR.
unlock:
if: github.event_name != 'pull_request' && always()
# Modify this list to add or remove jobs from required status checks.
needs:
- check
- lint
- eval
- build
runs-on: ubuntu-24.04-arm
permissions:
statuses: write
steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
RESULTS: ${{ toJSON(needs.*.result) }}
with:
script: |
const { serverUrl, repo, runId, payload } = context
const target_url =
`${serverUrl}/${repo.owner}/${repo.repo}/actions/runs/${runId}?pr=${payload.pull_request.number}`
await github.rest.repos.createCommitStatus({
...repo,
sha: payload.pull_request.head.sha,
// WARNING:
// Do NOT change the name of this, otherwise the rule will not catch it anymore.
// This would prevent all PRs from merging.
context: 'no PR failures',
state: JSON.parse(process.env.RESULTS).every(status => status == 'success') ? 'success' : 'error',
target_url,
})

50
.github/workflows/push.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Push
on:
push:
branches:
- master
- staging
- release-*
- staging-*
- haskell-updates
workflow_call:
inputs:
mergedSha:
required: true
type: string
secrets:
CACHIX_AUTH_TOKEN:
required: true
permissions: {}
jobs:
prepare:
runs-on: ubuntu-24.04-arm
outputs:
systems: ${{ steps.systems.outputs.systems }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout: |
ci/supportedSystems.json
- name: Load supported systems
id: systems
run: |
echo "systems=$(jq -c <ci/supportedSystems.json)" >> "$GITHUB_OUTPUT"
eval:
name: Eval
needs: [prepare]
uses: ./.github/workflows/eval.yml
# Those are not actually used on push, but will throw an error if not set.
permissions:
# compare
statuses: write
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
mergedSha: ${{ inputs.mergedSha || github.sha }}
systems: ${{ needs.prepare.outputs.systems }}

17
.github/workflows/review-dismissed.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Review dismissed
on:
pull_request_review:
types: [dismissed]
permissions: {}
defaults:
run:
shell: bash
jobs:
trigger:
runs-on: ubuntu-24.04-arm
steps:
- run: echo This is a no-op only used as a trigger for workflow_run.

170
.github/workflows/reviewers.yml vendored Normal file
View File

@@ -0,0 +1,170 @@
# This workflow will request reviews from the maintainers of each package
# listed in the PR's most recent eval comparison artifact.
name: Reviewers
on:
pull_request_target:
types: [ready_for_review]
workflow_call:
secrets:
OWNER_APP_PRIVATE_KEY:
required: true
concurrency:
group: reviewers-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions: {}
defaults:
run:
shell: bash
jobs:
request:
runs-on: ubuntu-24.04-arm
timeout-minutes: 20
steps:
- name: Check out the PR at the base commit
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: trusted
sparse-checkout: ci
- name: Install Nix
uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31
- name: Build the requestReviews derivation
run: nix-build trusted/ci -A requestReviews
# For requesting reviewers, this job depends on a GitHub App with the following permissions:
# - Permissions:
# - Repository > Administration: read-only
# - Organization > Members: read-only
# - Repository > Pull Requests: read-write
# - Install App on this repository, setting these variables:
# - OWNER_APP_ID (variable)
# - OWNER_APP_PRIVATE_KEY (secret)
#
# Can't use the token received from permissions above, because it can't get enough permissions.
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
if: github.event_name == 'pull_request_target' && vars.OWNER_APP_ID
id: app-token
with:
app-id: ${{ vars.OWNER_APP_ID }}
private-key: ${{ secrets.OWNER_APP_PRIVATE_KEY }}
permission-administration: read
permission-members: read
permission-pull-requests: write
- name: Log current API rate limits (app-token)
if: ${{ steps.app-token.outputs.token }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api /rate_limit | jq
- name: Requesting code owner reviews
if: steps.app-token.outputs.token
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPOSITORY: ${{ github.repository }}
NUMBER: ${{ github.event.number }}
# Don't do anything on draft PRs
DRY_MODE: ${{ github.event.pull_request.draft && '1' || '' }}
run: result/bin/request-code-owner-reviews.sh "$REPOSITORY" "$NUMBER" ci/OWNERS
- name: Log current API rate limits (app-token)
if: ${{ steps.app-token.outputs.token }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api /rate_limit | jq
- name: Log current API rate limits (github.token)
env:
GH_TOKEN: ${{ github.token }}
run: gh api /rate_limit | jq
# In the regular case, this workflow is called via workflow_call from the eval workflow directly.
# In the more special case, when a PR is undrafted an eval run will have started already.
- name: Wait for comparison to be done
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
id: eval
with:
script: |
const run_id = (await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: context.eventName === 'pull_request' ? 'test.yml' : 'pr.yml',
event: context.eventName,
head_sha: context.payload.pull_request.head.sha
})).data.workflow_runs[0].id
core.setOutput('run-id', run_id)
// Waiting 120 * 5 sec = 10 min. max.
// The extreme case is an Eval run that just started when the PR is undrafted.
// Eval takes max 5-6 minutes, normally.
for (let i = 0; i < 120; i++) {
const result = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id,
name: 'comparison'
})
if (result.data.total_count > 0) return
await new Promise(resolve => setTimeout(resolve, 5000))
}
throw new Error("No comparison artifact found.")
- name: Log current API rate limits (github.token)
env:
GH_TOKEN: ${{ github.token }}
run: gh api /rate_limit | jq
- name: Download the comparison results
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
run-id: ${{ steps.eval.outputs.run-id }}
github-token: ${{ github.token }}
pattern: comparison
path: comparison
merge-multiple: true
- name: Log current API rate limits (app-token)
if: ${{ steps.app-token.outputs.token }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api /rate_limit | jq
- name: Log current API rate limits (github.token)
env:
GH_TOKEN: ${{ github.token }}
run: gh api /rate_limit | jq
- name: Requesting maintainer reviews
if: ${{ steps.app-token.outputs.token }}
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
NUMBER: ${{ github.event.number }}
AUTHOR: ${{ github.event.pull_request.user.login }}
# Don't request reviewers on draft PRs
DRY_MODE: ${{ github.event.pull_request.draft && '1' || '' }}
run: |
# maintainers.json contains GitHub IDs. Look up handles to request reviews from.
# There appears to be no API to request reviews based on GitHub IDs
jq -r 'keys[]' comparison/maintainers.json \
| while read -r id; do gh api /user/"$id" --jq .login; done \
| GH_TOKEN=${{ steps.app-token.outputs.token }} result/bin/request-reviewers.sh "$REPOSITORY" "$NUMBER" "$AUTHOR"
- name: Log current API rate limits (app-token)
if: ${{ steps.app-token.outputs.token }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh api /rate_limit | jq
- name: Log current API rate limits (github.token)
env:
GH_TOKEN: ${{ github.token }}
run: gh api /rate_limit | jq

113
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
name: Test
on:
pull_request:
concurrency:
group: test-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions: {}
jobs:
prepare:
runs-on: ubuntu-24.04-arm
outputs:
merge-group: ${{ steps.files.outputs.merge-group }}
mergedSha: ${{ steps.prepare.outputs.mergedSha }}
pr: ${{ steps.files.outputs.pr }}
push: ${{ steps.files.outputs.push }}
targetSha: ${{ steps.prepare.outputs.targetSha }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
sparse-checkout-cone-mode: true # default, for clarity
sparse-checkout: |
ci/github-script
- id: prepare
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
require('./ci/github-script/prepare.js')({
github,
context,
core,
// Review comments will be posted by the main PR workflow on the pull_request_target event.
dry: false,
})
- name: Determine changed files
id: files
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const files = (await github.paginate(github.rest.pulls.listFiles, {
...context.repo,
pull_number: context.payload.pull_request.number,
per_page: 100,
})).map(file => file.filename)
if (files.some(file => [
'.github/workflows/lint.yml',
'.github/workflows/merge-group.yml',
'.github/workflows/test.yml',
].includes(file))) core.setOutput('merge-group', true)
if (files.some(file => [
'.github/actions/checkout/action.yml',
'.github/workflows/build.yml',
'.github/workflows/check.yml',
'.github/workflows/eval.yml',
'.github/workflows/labels.yml',
'.github/workflows/lint.yml',
'.github/workflows/pr.yml',
'.github/workflows/reviewers.yml',
'.github/workflows/test.yml',
].includes(file))) core.setOutput('pr', true)
if (files.some(file => [
'.github/workflows/eval.yml',
'.github/workflows/push.yml',
'.github/workflows/test.yml',
].includes(file))) core.setOutput('push', true)
merge-group:
if: needs.prepare.outputs.merge-group
name: Merge Group
needs: [prepare]
uses: ./.github/workflows/merge-group.yml
# Those are actually only used on the merge_group event, but will throw an error if not set.
permissions:
statuses: write
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
mergedSha: ${{ needs.prepare.outputs.mergedSha }}
targetSha: ${{ needs.prepare.outputs.targetSha }}
pr:
if: needs.prepare.outputs.pr
name: PR
needs: [prepare]
uses: ./.github/workflows/pr.yml
# Those are actually only used on the pull_request_target event, but will throw an error if not set.
permissions:
issues: write
pull-requests: write
statuses: write
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
NIXPKGS_CI_APP_PRIVATE_KEY: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
push:
if: needs.prepare.outputs.push
name: Push
needs: [prepare]
uses: ./.github/workflows/push.yml
# Those are not actually used on the push or pull_request events, but will throw an error if not set.
permissions:
statuses: write
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
with:
mergedSha: ${{ needs.prepare.outputs.mergedSha }}