Skip to content

CI: pixi lockfile refresh #7

CI: pixi lockfile refresh

CI: pixi lockfile refresh #7

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
# Scheduled (and on-demand) pixi lockfile refresh. All workspaces are updated
# in one job and, when anything changes, one PR. See #2298 and #2849.
name: "CI: pixi lockfile refresh"
on:
schedule:
# Monday 03:17 Eastern. GitHub handles the DST offset for the IANA zone,
# and a non-zero minute avoids the start-of-hour window where GitHub
# documents scheduled runs being delayed or dropped.
- cron: "17 3 * * 1"
timezone: "America/New_York"
workflow_dispatch: {}
defaults:
run:
shell: bash --noprofile --norc -xeuo pipefail {0}
permissions: {}
jobs:
refresh:
name: pixi update (all workspaces)
if: ${{ github.repository_owner == 'nvidia' }}
runs-on: ubuntu-latest
timeout-minutes: 195
permissions:
contents: write
pull-requests: write
concurrency:
group: pixi-lock-refresh
cancel-in-progress: false
steps:
- name: Checkout ${{ github.event.repository.name }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
ref: ${{ github.event.repository.default_branch }}
- name: Setup pixi
id: setup-pixi
uses: ./.github/actions/setup-pixi
# Pixi 0.73.0's update and lock-read paths can serialize the same solve
# differently (#2804). Canonicalize with the same operation used by the
# freshness workflow before opening the PR.
- name: Update and canonicalize all lockfiles
id: update
env:
PIXI_VERSION: ${{ steps.setup-pixi.outputs.pixi-version }}
WORKSPACE_TIMEOUT: 30m
run: |
workspaces="$(python3 ci/tools/list_pixi_workspaces.py)"
workspace_rows="$(
jq -ce '
if type != "array" or length == 0 then
error("workspace inventory must be a non-empty array")
elif any(.[];
type != "object"
or (.manifest | type != "string")
or (.lockfile | type != "string")
or .manifest == ""
or .lockfile == ""
) then
error("each workspace must have non-empty string manifest and lockfile fields")
else
.[]
end
' <<<"${workspaces}"
)"
while IFS= read -r workspace; do
manifest="$(jq -r '.manifest' <<<"${workspace}")"
lockfile="$(jq -r '.lockfile' <<<"${workspace}")"
echo "::group::pixi update (${manifest})"
workspace_status=0
# The single-quoted script expands its variables in the child shell.
# shellcheck disable=SC2016
timeout --kill-after=1m "${WORKSPACE_TIMEOUT}" \
bash --noprofile --norc -xeuo pipefail -c '
manifest="$1"
lockfile="$2"
pixi_version="$3"
pixi update --no-install --manifest-path "${manifest}"
pixi lock --check --manifest-path "${manifest}"
canonical_blob="$(git hash-object "${lockfile}")"
pixi lock --check --manifest-path "${manifest}"
verified_blob="$(git hash-object "${lockfile}")"
if [ "${canonical_blob}" != "${verified_blob}" ]; then
echo "::error::pixi ${pixi_version} rewrote ${lockfile} on a second consecutive pixi lock --check; refusing to open an unstable refresh PR."
exit 1
fi
' bash "${manifest}" "${lockfile}" "${PIXI_VERSION}" \
|| workspace_status=$?
if ((workspace_status == 124 || workspace_status == 137)); then
echo "::error::Timed out after ${WORKSPACE_TIMEOUT} while refreshing '${manifest}'."
elif ((workspace_status != 0)); then
echo "::error::Failed to refresh '${manifest}' (exit ${workspace_status})."
fi
echo "::endgroup::"
if ((workspace_status != 0)); then
exit "${workspace_status}"
fi
done <<<"${workspace_rows}"
{
echo "lockfiles<<EOF"
jq -r '.[].lockfile' <<<"${workspaces}"
echo "EOF"
} >> "${GITHUB_OUTPUT}"
- name: Compose PR body
id: pr-body
env:
PIXI_VERSION: ${{ steps.setup-pixi.outputs.pixi-version }}
run: |
body_path="${RUNNER_TEMP}/pixi-lock-refresh-body.md"
{
echo "Automated lockfile refresh for all workspaces using pixi" \
"\`${PIXI_VERSION}\` (\`pixi update --no-install\`, followed by" \
"byte-stable \`pixi lock --check\` canonicalization)."
echo
echo "Lockfiles included in the refresh:"
python3 ci/tools/list_pixi_workspaces.py \
| jq -r '.[] | "- `\(.lockfile)`"'
echo
echo "See #2298 and #2849."
echo
echo "> [!IMPORTANT]"
echo "> This PR was opened with \`GITHUB_TOKEN\`. A maintainer with write access must:"
echo ">"
echo "> 1. Select **Approve workflows to run** in the merge box."
echo "> 2. Assign themselves to this PR. The human-generated \`assigned\` event"
echo "> creates the required **PR has assignee, labels, and milestone** check"
echo "> and gives the PR a clear owner."
echo
echo "If the diff looks wrong, close this PR and leave the branch deleted; the"
echo "next scheduled run opens a fresh one if an update is still needed."
} > "${body_path}"
echo "path=${body_path}" >> "${GITHUB_OUTPUT}"
- name: Open or update a lockfile-only PR
id: cpr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ github.token }}
add-paths: ${{ steps.update.outputs.lockfiles }}
commit-message: "chore: refresh pixi lockfiles"
signoff: true
sign-commits: true
branch: ci/pixi-lock-refresh/all
delete-branch: true
title: "chore: refresh pixi lockfiles"
labels: |
CI/CD
dependencies
body-path: ${{ steps.pr-body.outputs.path }}
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- name: Warn about required maintainer action
if: ${{ steps.cpr.outputs.pull-request-operation == 'created' || steps.cpr.outputs.pull-request-operation == 'updated' }}
run: echo "::warning::The refresh PR requires a maintainer to approve its workflow runs and assign themselves to create the required metadata check."
- name: Summarize
run: |
{
echo "### pixi lock refresh (all workspaces)"
echo ""
echo "- Operation: \`${{ steps.cpr.outputs.pull-request-operation }}\`"
if [ -n "${{ steps.cpr.outputs.pull-request-url }}" ]; then
echo "- PR: ${{ steps.cpr.outputs.pull-request-url }}"
else
echo "- No lockfile diff; PR not opened."
fi
} >> "$GITHUB_STEP_SUMMARY"