From c58d7bddbed8954250ea85ab30b7fbb17fa3b6b9 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 19 Aug 2026 09:13:38 +0200 Subject: [PATCH] feat: add a release-assets action Packages that ship more than their distributions -- generated protobuf bindings, schema bundles -- were each writing the same four steps: check out, set up Python, run an Invoke task, upload what it produced. compas_pb and antikythera both had a copy. prepare-release can already carry extra assets through its release-assets input, but that ties generating them to `invoke pre-build`, which runs on every matrix job. Generating bindings for seven languages there would mean running protoc seven times per platform for a wheel build that does not need any of it. This action separates the two, uploading to the same artifact name so github-release attaches whatever it finds. The invoke-tasks input follows ci@v1 rather than introducing a generic shell-command action, which ARCHITECTURE.md rules out. Co-Authored-By: Claude Opus 5 --- ARCHITECTURE.md | 1 + README.md | 21 ++++++ release-assets/action.yml | 130 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 release-assets/action.yml diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8c98391..7dd0de4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -23,6 +23,7 @@ Every public automation is a composite action in a named directory: ```text compas-actions/ci@v1 compas-actions/prepare-release@v1 +compas-actions/release-assets@v1 compas-actions/github-release@v1 compas-actions/docs@v1 compas-actions/pr-checks@v1 diff --git a/README.md b/README.md index 608d7d2..8d0e840 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ is a clean break rather than a compatibility bundle. | `release-pr` | Bump the version and open a human-reviewed release PR | | `release-check` | Validate release metadata in PR and post-merge workflows | | `prepare-release` | Build distributions and upload release artifacts | +| `release-assets` | Run an Invoke task and upload what it produces as release assets | | `github-release` | Download artifacts and create a tagged GitHub release | | `docs` | Build MkDocs or deploy a version with Mike | | `pr-checks` | Require a changelog update or an explicit skip label | @@ -61,6 +62,26 @@ uses four separate jobs: `id-token: write`. 4. `github-release` creates the release with `contents: write`. +A package that ships more than its distributions -- generated protobuf +bindings, schema bundles, compiled assets -- adds a `release-assets` job +alongside `prepare-release`. Both upload to the same artifact name, so +`github-release` attaches whatever is there: + +```yaml +assets: + needs: release + runs-on: ubuntu-latest + steps: + - uses: compas-dev/compas-actions/release-assets@v1 + with: + invoke-tasks: create-class-assets + paths: dist/proto/*.zip +``` + +Use it instead of `prepare-release`'s `release-assets` input when the build is +slow or needs tools the wheel build does not, since that input ties asset +generation to `invoke pre-build` on every matrix job. + The trusted publisher remains explicit in the caller: ```yaml diff --git a/release-assets/action.yml b/release-assets/action.yml new file mode 100644 index 0000000..a5d52f0 --- /dev/null +++ b/release-assets/action.yml @@ -0,0 +1,130 @@ +name: Build release assets +description: Run an Invoke task and upload what it produces as release assets + +inputs: + python-version: + description: Python version used to build the assets + default: "3.12" + management-tool: + description: Dependency manager used for the build (uv or conda) + default: uv + extras: + description: Extras needed by the build tasks + default: dev + conda-environment: + description: Name of the Conda environment + default: compas + environment-file: + description: Explicit Conda environment file; OS-specific files are detected when omitted + default: "" + invoke-tasks: + description: Space-separated Invoke tasks that produce the assets + default: create-class-assets + paths: + description: Multiline paths/globs to upload + default: dist/proto/*.zip + artifact-name: + description: Workflow artifact the assets are uploaded as + default: github-release-assets + if-no-files-found: + description: Behaviour when no file matches paths (error, warn or ignore) + default: error + submodules: + description: Checkout submodules (false, true, or recursive) + default: "false" + +runs: + using: composite + steps: + - uses: actions/checkout@v7.0.1 + with: + submodules: ${{ inputs.submodules }} + persist-credentials: false + + - uses: actions/setup-python@v7.0.0 + if: inputs.management-tool == 'uv' + with: + python-version: ${{ inputs.python-version }} + + - uses: astral-sh/setup-uv@v10.0.0 + if: inputs.management-tool == 'uv' + + - name: Validate management tool + shell: bash + env: + MANAGEMENT_TOOL: ${{ inputs.management-tool }} + run: | + case "$MANAGEMENT_TOOL" in + uv|conda) ;; + *) echo "::error::management-tool must be uv or conda"; exit 1 ;; + esac + + - name: Set up Conda + if: inputs.management-tool == 'conda' + uses: conda-incubator/setup-miniconda@v4.0.1 + with: + miniconda-version: latest + channels: conda-forge + activate-environment: ${{ inputs.conda-environment }} + python-version: ${{ inputs.python-version }} + + - name: Select Conda environment file + if: inputs.management-tool == 'conda' + id: conda-file + shell: bash -el {0} + env: + EXPLICIT_FILE: ${{ inputs.environment-file }} + run: | + if [[ -n "$EXPLICIT_FILE" ]]; then + candidate="$EXPLICIT_FILE" + elif [[ "$RUNNER_OS" == "Windows" && -f env_win.yml ]]; then + candidate=env_win.yml + elif [[ "$RUNNER_OS" == "macOS" && -f env_osx.yml ]]; then + candidate=env_osx.yml + elif [[ "$RUNNER_OS" == "Linux" && -f env_linux.yml ]]; then + candidate=env_linux.yml + else + candidate=environment.yml + fi + + if [[ ! -f "$candidate" ]]; then + echo "::error::Conda environment file not found: $candidate" + exit 1 + fi + echo "path=$candidate" >> "$GITHUB_OUTPUT" + + - name: Install Conda environment + if: inputs.management-tool == 'conda' + shell: bash -el {0} + env: + CONDA_ENVIRONMENT: ${{ inputs.conda-environment }} + ENVIRONMENT_FILE: ${{ steps.conda-file.outputs.path }} + run: conda env update -f "$ENVIRONMENT_FILE" -n "$CONDA_ENVIRONMENT" + + - name: Install project + if: inputs.management-tool == 'uv' + shell: bash + env: + EXTRAS: ${{ inputs.extras }} + run: | + requirement="." + [[ -n "$EXTRAS" ]] && requirement=".[$EXTRAS]" + uv pip install --system -e "$requirement" + + - name: Build assets + shell: bash -el {0} + env: + INVOKE_TASKS: ${{ inputs.invoke-tasks }} + run: | + if [[ -z "$INVOKE_TASKS" ]]; then + echo "::error::invoke-tasks must name at least one task" + exit 1 + fi + invoke $INVOKE_TASKS + + - name: Upload release assets + uses: actions/upload-artifact@v7.0.1 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.paths }} + if-no-files-found: ${{ inputs.if-no-files-found }}