From 88c6a53d5fa139aa354ce818d8c897a7960c8d95 Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Fri, 24 Jul 2026 22:32:28 -0400 Subject: [PATCH 1/6] actions: publish-to-gitlab: add dry run support Provide a means for publish-to-gitlab to provide mock output for the actions it would take on main whenever a pipeline is triggered from another branch. This helps improve testing when building projects where the output paths for artifacts are not consistent. AI-Generated: Uses Claude Fable 5 Signed-off-by: Trevor Gamblin --- actions/publish-to-gitlab/action.yml | 51 +++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/actions/publish-to-gitlab/action.yml b/actions/publish-to-gitlab/action.yml index a75bde9..3441ee8 100644 --- a/actions/publish-to-gitlab/action.yml +++ b/actions/publish-to-gitlab/action.yml @@ -74,9 +74,18 @@ outputs: ${{ inputs.gitlab-host }}/-/packages uploaded-files: - description: Newline-separated list of files that twine successfully uploaded. + description: > + Newline-separated list of files that twine successfully uploaded. Empty + when running in dry-run mode (see `dry-run` output). value: ${{ steps.upload.outputs.uploaded_files }} + dry-run: + description: > + "true" when the action ran outside of the main branch and only printed + a mock of what would have been uploaded, "false" when it actually + uploaded via twine. + value: ${{ steps.upload.outputs.dry_run }} + runs: using: 'composite' steps: @@ -166,6 +175,33 @@ runs: printf ' %s\n' "${matched_files[@]}" echo "::endgroup::" + + if [[ "${{ github.ref }}" != 'refs/heads/main' ]]; then + echo "::group::Dry run (not on main branch — no upload will happen)" + + echo "dry_run=true" >> "$GITHUB_OUTPUT" + + skip_flag='' + if [[ "${{ inputs.skip-existing }}" == 'true' ]]; then + skip_flag=' --skip-existing' + fi + + printf 'Would run: twine upload --disable-progress-bar%s \n' "$skip_flag" + printf 'Would upload %d file(s) to %s:\n' "${#matched_files[@]}" "${{ inputs.gitlab-host }}" + printf ' %s\n' "${matched_files[@]}" + + echo "::endgroup::" + + { + echo "uploaded_files<> "$GITHUB_OUTPUT" + + exit 0 + fi + + echo "dry_run=false" >> "$GITHUB_OUTPUT" + echo "::group::Uploading via twine" twine_from='twine' @@ -200,7 +236,12 @@ runs: echo "" >> "$GITHUB_STEP_SUMMARY" echo "**Registry endpoint:** \`${{ inputs.gitlab-host }}/api/v4/projects/${{ inputs.gitlab-project-id }}/packages/pypi\`" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "**Uploaded files:**" >> "$GITHUB_STEP_SUMMARY" - while IFS= read -r f; do - [[ -n "$f" ]] && echo "- \`$f\`" >> "$GITHUB_STEP_SUMMARY" - done <<< "${{ steps.upload.outputs.uploaded_files }}" + + if [[ "${{ steps.upload.outputs.dry_run }}" == 'true' ]]; then + echo "**DRY RUN** — not on main branch, nothing was uploaded." >> "$GITHUB_STEP_SUMMARY" + else + echo "**Uploaded files:**" >> "$GITHUB_STEP_SUMMARY" + while IFS= read -r f; do + [[ -n "$f" ]] && echo "- \`$f\`" >> "$GITHUB_STEP_SUMMARY" + done <<< "${{ steps.upload.outputs.uploaded_files }}" + fi From 37d151b1aff16b7d69f85877d1931987a30da8ad Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Fri, 24 Jul 2026 22:35:04 -0400 Subject: [PATCH 2/6] actions: publish-wheels: support dry-run tests for publishing Signed-off-by: Trevor Gamblin --- actions/publish-wheels/action.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/actions/publish-wheels/action.yml b/actions/publish-wheels/action.yml index 4fc98f1..48838b9 100644 --- a/actions/publish-wheels/action.yml +++ b/actions/publish-wheels/action.yml @@ -134,6 +134,19 @@ runs: fi asset="gpl-sources/gpl-sources.tar" + url="https://github.com/${{ github.repository }}/releases/download/$tag/gpl-sources.tar" + + if [[ "${{ github.ref }}" != 'refs/heads/main' ]]; then + echo "::group::Dry run (not on main branch — no release will be published)" + if gh release view "$tag" --repo "${{ github.repository }}" >/dev/null 2>&1; then + echo "Would run: gh release upload $tag $asset --clobber" + else + echo "Would run: gh release create $tag $asset --title $tag" + fi + echo "Would set GPL_SOURCES_URL=$url" + echo "::endgroup::" + exit 0 + fi if gh release view "$tag" --repo "${{ github.repository }}" >/dev/null 2>&1; then gh release upload "$tag" "$asset" --repo "${{ github.repository }}" --clobber @@ -144,7 +157,7 @@ runs: --notes "Bundled GPL source packages for $tag." fi - echo "GPL_SOURCES_URL=https://github.com/${{ github.repository }}/releases/download/$tag/gpl-sources.tar" >> "$GITHUB_ENV" + echo "GPL_SOURCES_URL=$url" >> "$GITHUB_ENV" - uses: actions/setup-python@v5 with: @@ -161,4 +174,5 @@ runs: ARTIFACTS_PATH: ${{ inputs.artifact-path }} GPL_SOURCES_URL: ${{ env.GPL_SOURCES_URL }} GPL_SOURCES_DESCRIPTION: ${{ inputs.gpl-sources-description }} + DRY_RUN: ${{ github.ref != 'refs/heads/main' && 'true' || 'false' }} run: python3 ci_scripts/update_doc.py From b1464f23499836196ad65fb70226588a42eedebd Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Fri, 24 Jul 2026 22:36:27 -0400 Subject: [PATCH 3/6] ci_scripts: update_doc.py: add dry-run logic Signed-off-by: Trevor Gamblin --- ci_scripts/update_doc.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/ci_scripts/update_doc.py b/ci_scripts/update_doc.py index 086a2f3..1da22e0 100644 --- a/ci_scripts/update_doc.py +++ b/ci_scripts/update_doc.py @@ -12,6 +12,7 @@ truth; it never touches docs/packages/*.md or index.md directly. """ +import difflib import os import re import string @@ -30,6 +31,7 @@ ARTIFACTS_PATH = os.environ.get("ARTIFACTS_PATH", "dist") GPL_SOURCES_URL = os.environ.get("GPL_SOURCES_URL") GPL_SOURCES_DESCRIPTION = os.environ.get("GPL_SOURCES_DESCRIPTION", "").strip() +DRY_RUN = os.environ.get("DRY_RUN", "false").strip().lower() == "true" def find_wheel_file(path): @@ -221,25 +223,40 @@ def main(): comment = render_gpl_sources_comment() yaml_path = DOCS_DIR / f"{slug}.yaml" is_new = not yaml_path.exists() + old_content = None if is_new else yaml_path.read_text() if is_new: - yaml_path.write_text( - render_new_yaml(slug, source_code, license, version, patch_dir, comment) - ) + new_content = render_new_yaml(slug, source_code, license, version, patch_dir, comment) else: - content = yaml_path.read_text() - package_data = yaml.safe_load(content) or {} - updated = append_version( - content, package_data, version, license, patch_dir, comment + package_data = yaml.safe_load(old_content) or {} + new_content = append_version( + old_content, package_data, version, license, patch_dir, comment ) - if updated is None: + if new_content is None: print(f"{slug} {version} is already documented; nothing to do") return - yaml_path.write_text(updated) + branch = f"github-actions/{'add' if is_new else 'update'}-doc-for-{slug}" + pr_title = f"docs: {'add' if is_new else 'update'} {slug}" + + if DRY_RUN: + print("[dry-run] Not on main branch — no branch, commit, or PR will be created.") + print(f"[dry-run] Would write {yaml_path}:") + diff = difflib.unified_diff( + (old_content or "").splitlines(keepends=True), + new_content.splitlines(keepends=True), + fromfile=str(yaml_path) if old_content is not None else "/dev/null", + tofile=str(yaml_path), + ) + sys.stdout.writelines(diff) + if is_new: + print(f"[dry-run] Would add '{slug}' to {PACKAGES_FILE}") + print(f"[dry-run] Would open PR '{pr_title}' from branch '{branch}' against main") + return + + yaml_path.write_text(new_content) configure_git_identity() - branch = f"github-actions/{'add' if is_new else 'update'}-doc-for-{slug}" git_run("switch", "-c", branch) git_run("add", str(yaml_path)) @@ -259,7 +276,7 @@ def main(): "--base", "main", "--head", branch, "--reviewer", "threexc,justeph", - "--title", f"docs: {'add' if is_new else 'update'} {slug}", + "--title", pr_title, "--body", "Automatically generated PR to document a newly published wheel. " "Please review it carefully before merging.\n\n" From 6c71c87207103a5ff34265fcc13916724af1171b Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Sat, 25 Jul 2026 07:25:51 -0400 Subject: [PATCH 4/6] docs: development: update 'Releasing a Wheel' section Signed-off-by: Trevor Gamblin --- docs/development.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/development.md b/docs/development.md index 5d14ea6..eb7acf0 100644 --- a/docs/development.md +++ b/docs/development.md @@ -144,10 +144,6 @@ workflow needs is `publish-wheels`, which performs the following steps: publish: name: Publish numpy ${{ inputs.version || '2.5.0' }} to GitLab needs: build_wheels - # Only publish when the workflow was triggered from main with a specific - # version. Manual trigger is the only entry point, so checking the ref is - # enough to gate uploads. - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write @@ -231,11 +227,16 @@ user experience when consuming wheels from RISE's package registry.** ## Releasing a Wheel -The `publish-to-gitlab` action does not run unless the workflow is triggered -from main. This is intentional, and is meant to ensure that only those workflows +The `publish-to-gitlab` and `publish-wheels` actions only perform their real +side effects (twine upload, GPL sources release, docs PR) when the workflow is +triggered from `main`. On any other ref they print a dry-run instead — the +resolved file globs, the twine command that would have run, and the branch/PR +title `update_doc.py` would have used — without uploading anything or opening +a PR. This is intentional, and is meant to ensure that only those workflows which have been fully tested, reviewed, and merged are used to build and push -packages. Following the merge of a PR, the workflow(s) must be re-triggered from -the `main` branch in order to release the wheels to the package registry. +packages, while still letting a PR run demonstrate what publishing would do. +Following the merge of a PR, the workflow(s) must be re-triggered from the +`main` branch in order to actually release the wheels to the package registry. ## Other Workflow Tips and Tricks From 42fe4d115eb802dc9c155013203de8174827a73d Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Sat, 25 Jul 2026 07:27:09 -0400 Subject: [PATCH 5/6] workflows: remove publish comment in build workflows Signed-off-by: Trevor Gamblin --- .github/workflows/build-cffi.yml | 4 ---- .github/workflows/build-matplotlib.yml | 4 ---- .github/workflows/build-numpy.yml | 4 ---- .github/workflows/build-pillow.yml | 4 ---- 4 files changed, 16 deletions(-) diff --git a/.github/workflows/build-cffi.yml b/.github/workflows/build-cffi.yml index eb66f3a..c496ef5 100644 --- a/.github/workflows/build-cffi.yml +++ b/.github/workflows/build-cffi.yml @@ -132,10 +132,6 @@ jobs: publish: name: Publish cffi ${{ inputs.version || '2.1.0' }} to GitLab needs: [linux] - # Only publish when the workflow was triggered from main with a specific - # version. Manual trigger is the only entry point, so checking the ref is - # enough to gate uploads. - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/build-matplotlib.yml b/.github/workflows/build-matplotlib.yml index d97151f..1baf820 100644 --- a/.github/workflows/build-matplotlib.yml +++ b/.github/workflows/build-matplotlib.yml @@ -169,10 +169,6 @@ jobs: publish: name: Publish matplotlib ${{ inputs.version || '3.11.1' }} to GitLab needs: build_wheels - # Only publish when the workflow was triggered from main with a specific - # version. Manual trigger is the only entry point that reaches main; - # PR runs sit on refs/pull//merge and skip this job. - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/build-numpy.yml b/.github/workflows/build-numpy.yml index bf23c41..99a40a8 100644 --- a/.github/workflows/build-numpy.yml +++ b/.github/workflows/build-numpy.yml @@ -101,10 +101,6 @@ jobs: publish: name: Publish numpy ${{ inputs.version || '2.5.1' }} to GitLab needs: [build_wheels] - # Only publish when the workflow was triggered from main with a specific - # version. Manual trigger is the only entry point, so checking the ref is - # enough to gate uploads. - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/build-pillow.yml b/.github/workflows/build-pillow.yml index c129fa0..68ffa16 100644 --- a/.github/workflows/build-pillow.yml +++ b/.github/workflows/build-pillow.yml @@ -94,10 +94,6 @@ jobs: publish: name: Publish pillow ${{ inputs.version || '12.3.0' }} to GitLab needs: [build_wheels] - # Only publish when the workflow was triggered from main with a specific - # version. Manual trigger is the only entry point, so checking the ref is - # enough to gate uploads. - if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write From a7f663f253579132f3325ed44cc2cb464cab727f Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Sat, 25 Jul 2026 19:41:29 -0400 Subject: [PATCH 6/6] actions: publish-to-gitlab: provide file hint when not found Provide some indication of where the wheel files might be if the listed file path doesn't contain them. AI-Generated: Uses Claude Fable 5 Signed-off-by: Trevor Gamblin --- actions/publish-to-gitlab/action.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/actions/publish-to-gitlab/action.yml b/actions/publish-to-gitlab/action.yml index 3441ee8..a03ce7a 100644 --- a/actions/publish-to-gitlab/action.yml +++ b/actions/publish-to-gitlab/action.yml @@ -153,6 +153,15 @@ runs: expanded=( $pattern ) if [[ ${#expanded[@]} -eq 0 ]] || [[ ! -e "${expanded[0]}" ]]; then echo "::warning::Pattern '${pattern}' did not match any files — skipping." + + dir=$(dirname -- "$pattern") + if [[ -d "$dir" ]]; then + echo " '$dir' exists but contains:" + find "$dir" -mindepth 1 -maxdepth 1 -printf ' %f\n' 2>/dev/null + [[ -z "$(ls -A "$dir" 2>/dev/null)" ]] && echo " (empty)" + else + echo " Directory '$dir' does not exist from the working directory $(pwd)." + fi continue fi @@ -167,6 +176,17 @@ runs: if [[ ${#matched_files[@]} -eq 0 ]]; then echo "::error::No files matched the provided globs; nothing to upload." + + echo "Looking for wheels/sdists elsewhere under $(pwd) (max depth 4) as a hint:" + hits="$(find . -maxdepth 4 \( -iname '*.whl' -o -iname '*.tar.gz' \) 2>/dev/null)" + if [[ -n "$hits" ]]; then + echo "$hits" | sed 's/^/ /' + echo " ^ found here instead — check the 'files' input and the artifact download path." + else + echo " None found within 4 directories of $(pwd) either." + echo " Check that the build/download-artifact step actually produced files before this action ran." + fi + echo "::endgroup::" exit 1 fi