forked from isaac-sim/IsaacLab
-
Notifications
You must be signed in to change notification settings - Fork 3
263 lines (249 loc) · 13 KB
/
Copy pathnightly-changelog.yml
File metadata and controls
263 lines (249 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# Nightly auto-compile: rolls accumulated fragments under
# ``source/<pkg>/changelog.d/`` into per-package ``CHANGELOG.rst`` entries,
# bumps each ``extension.toml``, deletes consumed fragments, and pushes the
# result back to each branch in the configured list. Keeps every tracked
# branch's changelog current without requiring a maintainer to run
# ``compile`` by hand.
#
# Scheduled workflow — must live on the default branch (``main``) for the
# cron to register. See ``.github/workflows/README.md``.
#
# Adding a branch to the nightly set is a one-line edit to ``env.CRON_BRANCHES``
# below. Each target branch uses its own ``tools/changelog/cli.py`` (the
# same copy the PR gate already runs), so the nightly compile honours the
# same rules that validated the fragments.
#
# The push uses a short-lived GitHub App installation token minted from
# ``CHANGELOG_APP_ID`` + ``CHANGELOG_APP_PRIVATE_KEY`` (repo secrets). The
# App must be installed on this repository with ``contents: write`` and
# added to the bypass-actor list of each target branch's ruleset so the
# auto-commit can push directly without satisfying required-checks /
# required-approval gates.
# Commits signed by an App token (unlike ``GITHUB_TOKEN``) are treated as
# external pushes, so they DO trigger downstream workflow runs (Docker
# rebuild, docs, etc.) without needing a separate PAT.
name: Nightly Changelog Compilation
# Branches the nightly cron compiles. Single source of truth — append a
# ref here to extend the nightly set (the active release branch belongs
# here). Each branch must carry ``tools/changelog/cli.py`` and the
# isaaclab-bot App must be in its branch-ruleset bypass list.
# Surrounding whitespace per entry is stripped by the resolver below.
env:
CRON_BRANCHES: develop,release/3.0.0-beta2
on:
schedule:
# Run nightly at 5 AM UTC (one hour after daily-compatibility, so we
# don't compete for runner capacity).
- cron: '0 5 * * *'
workflow_dispatch:
inputs:
branch:
# Manual trigger is always a single branch. Free-text on purpose
# — scales to any branch (e.g. a new ``release/*`` cut from
# develop) without needing to update the workflow. A branch
# that lacks ``tools/changelog/cli.py`` fails the verify step
# below with a clear error, which is the desired failure mode.
description: 'Branch to compile (e.g. develop, release/3.0.0-beta2). Must carry tools/changelog/cli.py.'
required: true
type: string
dry_run:
description: 'Preview only — do not commit / push'
required: false
type: boolean
default: false
permissions:
# Reduced: the App installation token below carries its own write scope.
# GITHUB_TOKEN only needs read access for the standard checkout machinery.
contents: read
jobs:
resolve-branches:
# CSV → JSON array bridge. ``workflow_dispatch`` inputs can only be
# string / bool / choice, so the branch list arrives as a comma-
# separated string; the matrix below needs a JSON list to fan out.
# Mirrors the ``setup-versions`` job in ``daily-compatibility.yml``.
name: Resolve branch list
runs-on: ubuntu-latest
timeout-minutes: 1
outputs:
branches: ${{ steps.b.outputs.branches }}
steps:
- id: b
env:
# Schedule → the CRON_BRANCHES list. Manual → the single branch
# the maintainer entered. The two paths are intentionally
# asymmetric: cron is the configured set, manual is exactly one
# branch (required input).
BRANCHES: ${{ github.event_name == 'schedule' && env.CRON_BRANCHES || inputs.branch }}
# ``EVENT_NAME`` mirrors ``github.event_name`` so the guard below
# branches on it without re-interpolating into the shell.
EVENT_NAME: ${{ github.event_name }}
run: |
# CSV → JSON array, trimming surrounding whitespace per entry.
# Manual produces a 1-element array; cron produces N elements.
arr=$(echo "$BRANCHES" | tr ',' '\n' | xargs -n1 | jq -R . | jq -s -c .)
# Manual trigger contract: exactly one branch. A maintainer who
# pastes a comma-separated list into the dispatch form should
# see a clear error, not a silent multi-branch fan-out.
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$(echo "$arr" | jq 'length')" -ne 1 ]; then
echo "::error::Manual trigger accepts exactly one branch; got $arr. Fire the workflow separately per branch."
exit 1
fi
echo "branches=$arr" >> "$GITHUB_OUTPUT"
echo "Resolved branches: $arr"
compile-changelog:
name: Compile changelog fragments (${{ matrix.branch }})
needs: resolve-branches
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
# Independent branches: one failing shouldn't cancel the others.
# Each matrix entry shows up as a separate job tile in the Actions
# UI, so ``release/3.0.0-beta2`` failing doesn't hide ``develop``'s
# success (and ``gh run rerun --failed`` re-runs only the failed
# entry). Mirrors ``daily-compatibility.yml``'s matrix style.
fail-fast: false
matrix:
branch: ${{ fromJson(needs.resolve-branches.outputs.branches) }}
concurrency:
# Per-branch group: two runs against the same ref queue, but
# different refs (develop and a release branch) compile in parallel.
group: nightly-changelog-${{ matrix.branch }}
cancel-in-progress: false
steps:
# Mint a short-lived (1 h) installation access token for the
# ``isaaclab-bot`` GitHub App. The App is on each target branch's
# ruleset bypass list, so its push lands without needing the
# standard required-checks / required-approval pipeline.
- uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
id: app-token
with:
# ``client-id`` (the App's OAuth client ID, ``Iv23...``) supersedes
# the deprecated ``app-id`` integer input as of v3.x.
client-id: ${{ secrets.CHANGELOG_APP_CLIENT_ID }}
private-key: ${{ secrets.CHANGELOG_APP_PRIVATE_KEY }}
# Declare the scope the App must have so token mint fails loudly
# if the App is misconfigured, instead of failing silently at the
# push step.
permission-contents: write
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
# Operate on the target branch, not the repo's default branch.
# Scheduled workflows fire from the default branch's workflow
# file, but the *checkout* is the branch we're compiling so the
# compile sees that branch's accumulated fragments and the push
# writes back to it.
ref: ${{ matrix.branch }}
# App token (vs. GITHUB_TOKEN) means the push is signed by
# ``isaaclab-bot`` — the bypass identity — and downstream CI
# workflows DO trigger on the resulting commit.
token: ${{ steps.app-token.outputs.token }}
# Full history so the compiler can resolve each fragment's merge
# time via ``git log --diff-filter=A --first-parent``.
fetch-depth: 0
- name: Verify changelog tooling exists on target branch
env:
TARGET_BRANCH: ${{ matrix.branch }}
run: |
# Loud-fail this matrix entry (not the whole run — ``fail-fast:
# false`` keeps siblings going) when the target branch lacks
# ``tools/changelog/cli.py``. A red tile is the desired signal:
# a branch in the nightly set without the compile tooling is a
# configuration error, not a "no-op" condition.
if [ ! -f tools/changelog/cli.py ]; then
echo "::error::Branch '$TARGET_BRANCH' is missing tools/changelog/cli.py — drop it from env.CRON_BRANCHES (or the dispatch input) or restore the tooling on that branch."
exit 1
fi
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- name: Compile fragments
# Capture the compile exit code so the next step can commit/push
# whatever compiled successfully, then re-propagate the failure at
# the end. Without ``continue-on-error: true`` the workflow would
# short-circuit and discard the successful packages' on-disk state,
# which is exactly the wedge mode that motivated this design.
id: compile
continue-on-error: true
run: |
ARGS="--all"
if [ "${{ inputs.dry_run }}" = "true" ]; then
ARGS="$ARGS --dry-run"
fi
echo "Running: python3 tools/changelog/cli.py compile $ARGS"
python3 tools/changelog/cli.py compile $ARGS
- name: Commit and push if fragments were compiled
# ``always()`` lets this step run even when the compile step above
# reported partial failure (one package's malformed file fails, the
# rest still wrote their CHANGELOG.rst / deleted fragments / bumped
# extension.toml). The staged-diff check below short-circuits cleanly
# if the failed package was the only one with pending work.
if: ${{ always() && !inputs.dry_run }}
env:
# Pass the matrix branch through an env var rather than
# interpolating ``${{ matrix.branch }}`` directly into ``run:``.
# The interpolation happens *before* shell quoting, so an
# adversarial input could escape the surrounding quotes; the
# env passthrough keeps the value inside the shell's variable
# space where standard quoting protects it.
TARGET_BRANCH: ${{ matrix.branch }}
run: |
# Author commits as the App's bot user so the GitHub UI attributes
# them correctly. ID 282401363 is isaaclab-bot[bot]'s user ID.
git config user.name "isaaclab-bot[bot]"
git config user.email "282401363+isaaclab-bot[bot]@users.noreply.github.com"
git add source/*/changelog.d/ \
source/*/docs/CHANGELOG.rst \
source/*/pyproject.toml
if git diff --staged --quiet; then
echo "No changelog fragments found — nothing to commit."
else
# Convention for CI-driven auto-commits on this repo:
# [CI][<Specific Action>] <one-line summary>
# The leading ``[CI]`` tag groups every machine-driven commit
# (so future automations — auto image rebuilds, auto publish,
# etc. — all share the prefix and are easy to find/filter in
# ``git log --grep``). The second tag names the specific
# action. The trigger event suffix (``schedule`` vs
# ``workflow_dispatch``) is preserved for traceability.
#
# The body lists every package that bumped, derived from the
# staged pyproject.toml diff so the entries are accurate
# regardless of which packages happen to have pending
# fragments this run.
MSG_FILE=$(mktemp)
{
echo "[CI][Auto Version Bump] Compile changelog fragments (${{ github.event_name }})"
echo
echo "Bumped packages:"
for tom in $(git diff --staged --name-only -- 'source/*/pyproject.toml'); do
pkg=$(echo "$tom" | sed -E 's|source/([^/]+)/pyproject.toml|\1|')
old=$(git diff --staged "$tom" | awk -F'"' '/^-version/{print $2; exit}')
new=$(git diff --staged "$tom" | awk -F'"' '/^\+version/{print $2; exit}')
echo "- $pkg: $old → $new"
done
} > "$MSG_FILE"
git commit -F "$MSG_FILE"
# Rebase onto the target branch's current tip in case a human
# commit landed during this run (~2 min window between
# checkout and push). Without this the push fails non-fast-
# forward and the batch waits for the next run. ``refs/heads/``
# is explicit so a same-named tag (if one ever exists) can't
# disambiguate the wrong way.
git pull --rebase origin "refs/heads/$TARGET_BRANCH"
# Push explicitly to the target branch so we don't accidentally
# write to the source ref of a workflow_dispatch run.
git push origin "HEAD:refs/heads/$TARGET_BRANCH"
fi
- name: Re-propagate compile failure
# ``continue-on-error: true`` above lets the commit/push step run on
# partial success, but the job tile must still stay red so the
# maintainer notices the failed package. This step re-asserts the
# original exit code AFTER successful packages have shipped.
if: ${{ steps.compile.outcome == 'failure' }}
run: |
echo "::error::One or more packages failed to compile (see the Compile fragments step above)."
exit 1