diff --git a/.github/workflows/openshell-next.yml b/.github/workflows/openshell-next.yml index 977e7b9..5966f1b 100644 --- a/.github/workflows/openshell-next.yml +++ b/.github/workflows/openshell-next.yml @@ -8,6 +8,8 @@ name: openshell-next # Pushes made with GITHUB_TOKEN do not trigger other workflows, so this job runs the test suite itself and then # dispatches ci.yml on the pushed commit (workflow_dispatch is the one event GITHUB_TOKEN may start), which puts the # required `check` / `audit` results on the tracking PR's head commit. +# The scripts that move the branch (scripts/openshell-next.sh, scripts/openshell-tags.sh) always come from main: they +# are copied to a temp dir before openshell-next is checked out and run against that checkout (OPENSHELL_ANNOTATE_ROOT). # Local equivalent: scripts/openshell-next.sh sync && scripts/openshell-next.sh update on: @@ -27,6 +29,12 @@ on: permissions: contents: read +# `shell: bash` runs steps with `bash --noprofile --norc -eo pipefail`; the implicit default (`bash -e`) has no pipefail, +# so a failing command piped into `tee` would pass. +defaults: + run: + shell: bash + concurrency: group: openshell-next cancel-in-progress: false @@ -50,6 +58,15 @@ jobs: with: fetch-depth: 0 + - name: Keep main's branch tooling + run: | + mkdir -p "$RUNNER_TEMP/tools" + cp scripts/openshell-next.sh scripts/openshell-tags.sh "$RUNNER_TEMP/tools/" + { + echo "TOOLS=$RUNNER_TEMP/tools" + echo "OPENSHELL_ANNOTATE_ROOT=$GITHUB_WORKSPACE" + } >> "$GITHUB_ENV" + - name: Check out openshell-next (created from main if missing) id: branch run: | @@ -63,6 +80,7 @@ jobs: echo "created=true" >> "$GITHUB_OUTPUT" fi echo "before=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "pin=$("$TOOLS/openshell-tags.sh" pinned)" >> "$GITHUB_OUTPUT" - name: Install Rust run: rustup toolchain install 1.94 --profile minimal --component rustfmt,clippy && rustup default 1.94 @@ -78,17 +96,17 @@ jobs: - name: Merge main into openshell-next id: sync - run: scripts/openshell-next.sh sync origin/main 2>&1 | tee "$RUNNER_TEMP/sync.log" + run: '"$TOOLS/openshell-next.sh" sync origin/main 2>&1 | tee "$RUNNER_TEMP/sync.log"' - name: Bump to the newest OpenShell tag id: bump env: WANT: ${{ inputs.tag }} run: | - scripts/openshell-next.sh update "$WANT" | tee "$RUNNER_TEMP/update.out" + "$TOOLS/openshell-next.sh" update "$WANT" 2>&1 | tee "$RUNNER_TEMP/update.log" if ! git diff --quiet; then - tag=$(sed -n 's/^tag=//p' "$RUNNER_TEMP/update.out") - prev=$(sed -n 's/^previous=//p' "$RUNNER_TEMP/update.out") + tag=$(sed -n 's/^tag=//p' "$RUNNER_TEMP/update.log") + prev=$(sed -n 's/^previous=//p' "$RUNNER_TEMP/update.log") git commit -a --signoff -m "chore(deps): track OpenShell $tag on openshell-next" \ -m "Automated by .github/workflows/openshell-next.yml. Upstream changes: $UPSTREAM/compare/$prev...$tag" fi @@ -97,7 +115,6 @@ jobs: id: test continue-on-error: true run: | - set -o pipefail { cargo fmt --check && cargo clippy --all-targets --locked -- -D warnings && @@ -131,7 +148,9 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SYNC: ${{ steps.sync.outcome }} + BUMP: ${{ steps.bump.outcome }} TEST: ${{ steps.test.outcome }} + START_PIN: ${{ steps.branch.outputs.pin }} CHANGED: ${{ steps.bump.outputs.changed }} PREV: ${{ steps.bump.outputs.previous }} TAG: ${{ steps.bump.outputs.tag }} @@ -142,15 +161,19 @@ jobs: RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} REPO_URL: ${{ github.server_url }}/${{ github.repository }} run: | - icon() { case "$1" in success) echo "✅ passed" ;; failure) echo "❌ failed" ;; *) echo "⏭️ skipped" ;; esac; } - if [ "$CHANGED" != true ] && [ "$SYNC" = success ] && [ "$TEST" = success ] && [ "$CREATED" != true ] && [ "$FORCE_REPORT" != true ]; then + icon() { case "$1" in success) echo "✅ passed" ;; failure) echo "❌ failed" ;; cancelled) echo "🚫 cancelled" ;; *) echo "⏭️ skipped" ;; esac; } + # The pin as the branch has it now (after any bump), else as the bump step or the checkout saw it. + TAG=$("$TOOLS/openshell-tags.sh" pinned 2>/dev/null || echo "${TAG:-${START_PIN:-unknown}}") + if [ "$CHANGED" != true ] && [ "$SYNC" = success ] && [ "$BUMP" = success ] && [ "$TEST" = success ] && [ "$CREATED" != true ] && [ "$FORCE_REPORT" != true ]; then echo "pin unchanged (${TAG:-?}) and everything passed: nothing to report" | tee -a "$GITHUB_STEP_SUMMARY" exit 0 fi sha=$(git rev-parse HEAD) pr=$(gh pr list --head "$BRANCH" --base main --state open --json url --jq '.[0].url // empty') { - if [ "$CHANGED" = true ]; then echo "### OpenShell \`$TAG\` is now pinned on \`$BRANCH\`"; else echo "### \`$BRANCH\` check (pin \`${TAG:-unknown}\`)"; fi + failed="" + for o in "$SYNC" "$BUMP" "$TEST"; do [ "$o" = failure ] && failed=" ❌"; done + if [ "$CHANGED" = true ]; then echo "### OpenShell \`$TAG\` is now pinned on \`$BRANCH\`$failed"; else echo "### \`$BRANCH\` check (pin \`$TAG\`)$failed"; fi echo echo "| | |" echo "|---|---|" @@ -159,17 +182,22 @@ jobs: echo "| Upstream | [release]($UPSTREAM/releases/tag/$TAG) · [changes since \`$PREV\`]($UPSTREAM/compare/$PREV...$TAG) |" fi echo "| Merge of main | $(icon "$SYNC") |" + echo "| Pin update | $(icon "$BUMP")$([ "$CHANGED" = true ] || [ "$BUMP" != success ] || echo ", already on the newest tag") |" echo "| Tests (fmt, clippy, test, docs) | $(icon "$TEST") |" echo "| Branch | [\`$BRANCH\` @ \`${sha:0:12}\`]($REPO_URL/commit/$sha)$([ "$PUSHED" = true ] && echo ", pushed; ci.yml dispatched") |" echo "| Tracking PR | ${pr:-none open: [open one]($REPO_URL/compare/main...$BRANCH?expand=1) as a draft} |" echo "| Run | $RUN_URL |" if [ "$SYNC" = failure ]; then - echo; echo "Merging main hit conflicts outside Cargo.toml/Cargo.lock; nothing was pushed. Resolve them by hand:" - echo '```'; tail -n 20 "$RUNNER_TEMP/sync.log" 2>/dev/null; echo '```' + echo; echo "Merging main failed (usually a conflict outside Cargo.toml/Cargo.lock, which needs resolving by hand); nothing was pushed:" + echo '```'; tail -n 20 "$RUNNER_TEMP/sync.log" 2>/dev/null || echo "(no log captured)"; echo '```' + fi + if [ "$BUMP" = failure ]; then + echo; echo "Moving the pin failed; nothing was pushed:" + echo '```'; tail -n 20 "$RUNNER_TEMP/update.log" 2>/dev/null || echo "(no log captured)"; echo '```' fi if [ "$TEST" = failure ]; then echo; echo "Last lines of the test log (fix on \`$BRANCH\`; fixes that also hold on main's pin go to main first):" - echo '```'; tail -n 40 "$RUNNER_TEMP/test.log" 2>/dev/null; echo '```' + echo '```'; tail -n 40 "$RUNNER_TEMP/test.log" 2>/dev/null || echo "(no log captured)"; echo '```' fi if [ "$CHANGED" = true ] && [ "$STABLE" = true ]; then echo @@ -188,6 +216,6 @@ jobs: --body-file "$RUNNER_TEMP/body.md" fi - - name: Fail the run when the merge or the tests failed - if: steps.sync.outcome == 'failure' || steps.test.outcome == 'failure' + - name: Fail the run when the merge, the pin update or the tests failed + if: always() && (steps.sync.outcome == 'failure' || steps.bump.outcome == 'failure' || steps.test.outcome == 'failure') run: exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 005a687..2c7a3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ version section and adds the squash-merged PR titles since the previous tag. Ver ## [Unreleased] - ci: `openshell-next` branch automation: a daily workflow merges main into `openshell-next`, moves its OpenShell pin to the newest upstream tag (prereleases included), runs the tests and reports on the tracking issue; `scripts/openshell-tags.sh` and `scripts/openshell-next.sh` do the same by hand +- fix(ci): the `openshell-next` workflow runs main's copy of its scripts against the branch (`OPENSHELL_ANNOTATE_ROOT`), runs steps with `pipefail` so merge/bump/test failures fail the run, and its issue comment shows the pin and every failed step - fix(ci): `scripts/upstream-compat.sh` repoints both OpenShell crates (it left `openshell-policy` on the old tag) and runs on macOS; `upstream-compat.yml` also runs, non-blocking, on every push to main - chore(audit): ignore RUSTSEC-2026-0459 (`libsqlite3-sys` 0.37.0, pinned by OpenShell v0.1.7 through `sqlx` 0.9.0) in `.cargo/audit.toml` until OpenShell upgrades it (#76) - chore(deps): build against OpenShell v0.1.7 (`openshell-policy-schema`, `openshell-policy`); docs and generated policy headers now name v0.1.7 diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index f80ee52..5b0932b 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -115,7 +115,11 @@ specific tag, `report` to comment even when nothing changed). It: adds a note to promote the branch (see "Promoting `openshell-next`" in `docs/RELEASING.md`). It uses only `GITHUB_TOKEN` (`contents: write` for `openshell-next`, `issues: write`, `actions: write` to dispatch CI, -`pull-requests: read`). It never pushes to `main` or opens pull requests. +`pull-requests: read`). It never pushes to `main` or opens pull requests. The branch tooling always comes from `main`: +the workflow copies `scripts/openshell-next.sh` and `scripts/openshell-tags.sh` to a temp dir before checking out +`openshell-next` and runs them against that checkout (`OPENSHELL_ANNOTATE_ROOT=`), so a fix to the scripts +takes effect on the next run and the branch does not need its own copy. Steps run with `shell: bash` (`-eo pipefail`), +so a failure piped into `tee` still fails the step, the run, and the issue comment. Working on the branch by hand: @@ -124,6 +128,7 @@ git fetch origin && git switch openshell-next && git pull --ff-only scripts/openshell-next.sh sync # merge origin/main (what the workflow does first) scripts/openshell-next.sh update # move the pin if upstream has a newer tag (edits files only) scripts/openshell-next.sh bump v0.1.8-pre.3 # or pin an exact tag +OPENSHELL_ANNOTATE_ROOT=../other-checkout scripts/openshell-next.sh update # work on another checkout TOOLCHAIN=1.94 scripts/ci-local.sh ``` diff --git a/scripts/openshell-next.sh b/scripts/openshell-next.sh index fa3b2a1..e0658d9 100755 --- a/scripts/openshell-next.sh +++ b/scripts/openshell-next.sh @@ -10,10 +10,15 @@ # branch both changed the pin, `sync` keeps the newer of the two tags and re-resolves Cargo.lock; any other conflict # aborts the merge (exit 3) so a human can resolve it. # `update` prints `previous=`, `tag=`, `changed=` and `stable=` lines (also appended to $GITHUB_OUTPUT when set). +# Env: OPENSHELL_ANNOTATE_ROOT, the checkout to work on (default: the one this script lives in). The workflow runs +# main's copy of these scripts from a temp dir against the openshell-next checkout, so the branch needs no copy. # Exit: 0 ok, 2 usage / bad tag, 3 merge conflict that needs a human. set -euo pipefail -cd "$(dirname "$0")/.." -TAGS=scripts/openshell-tags.sh +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "${OPENSHELL_ANNOTATE_ROOT:-$HERE/..}" && pwd) +export OPENSHELL_ANNOTATE_ROOT="$ROOT" # so openshell-tags.sh reads the same Cargo.toml +cd "$ROOT" +TAGS="$HERE/openshell-tags.sh" CRATES=(openshell-policy-schema openshell-policy) bump() { diff --git a/scripts/openshell-tags.sh b/scripts/openshell-tags.sh index c5ad8dd..2ca6934 100755 --- a/scripts/openshell-tags.sh +++ b/scripts/openshell-tags.sh @@ -10,10 +10,11 @@ # # Only vX.Y.Z and vX.Y.Z-pre.N tags count; anything else upstream (`dev`, `vm-runtime`, ...) is ignored. # Env: UPSTREAM_REPO (default https://github.com/NVIDIA/OpenShell); OPENSHELL_TAGS (newline-separated tag list to use -# instead of asking the remote, for tests and offline runs). +# instead of asking the remote, for tests and offline runs); OPENSHELL_ANNOTATE_ROOT (checkout whose Cargo.toml +# `pinned` reads; default: the checkout this script lives in). # Exit: 0 ok / true, 1 false, 2 usage or no tags found. set -euo pipefail -cd "$(dirname "$0")/.." +cd "${OPENSHELL_ANNOTATE_ROOT:-$(dirname "$0")/..}" REPO="${UPSTREAM_REPO:-https://github.com/NVIDIA/OpenShell}" RE='^v[0-9]+\.[0-9]+\.[0-9]+(-pre\.[0-9]+)?$'