Release #120
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # Manual release workflow: lint, unit tests, wheel + sdist build, | |
| # package metadata + content validation, installed-wheel smoke tests, | |
| # and TestPyPI publish (for both GA and non-GA dispatches). The K2 | |
| # Kitmaker wheel-release flow promotes the staged wheel from TestPyPI | |
| # (preferred — via the new `release_kitmaker_wheel.py upload | |
| # --wheel-url https://test.pypi.org/project/holoscan-cli/<v>/` shape) | |
| # or, as a fallback, from the `wheel-artifact` uploaded here. | |
| # | |
| # For more information see: | |
| # https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions | |
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version (e.g. v1.2.34)' | |
| required: true | |
| type: string | |
| rc: | |
| description: 'RC Build Number' | |
| required: false | |
| type: number | |
| ga: | |
| description: 'GA (Official) Build' | |
| required: true | |
| type: boolean | |
| default: false | |
| jobs: | |
| pre-commit: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| python-version: ["3.12"] | |
| env: | |
| PYTHON_VERSION: ${{ matrix.python-version }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install Poetry | |
| # Install via pip on the matrix Python rather than a third-party | |
| # action: no allowlist coupling, no Node-20 runtime to chase, and | |
| # plugins resolve in the same site-packages so `requires-plugins` | |
| # in pyproject.toml is satisfied without `poetry self add`. | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install "poetry>=2.0" "poetry-dynamic-versioning>=1.7.1,<2.0.0" | |
| - name: Install library and dependencies | |
| run: | | |
| poetry run pip install --upgrade pip setuptools | |
| poetry -vvv install --with test | |
| - name: Run pre-commit Check | |
| uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 | |
| test: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| python: | |
| [ | |
| { version: "3.10", coveralls: false }, | |
| { version: "3.11", coveralls: false }, | |
| { version: "3.12", coveralls: true }, # publish coverage with latest version of Python | |
| { version: "3.13", coveralls: false }, | |
| ] | |
| env: | |
| PYTHON_VERSION: ${{ matrix.python.version }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| - name: Set up Python ${{ matrix.python.version }} | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: ${{ matrix.python.version }} | |
| - name: Install Poetry | |
| # Install via pip on the matrix Python rather than a third-party | |
| # action: no allowlist coupling, no Node-20 runtime to chase, and | |
| # plugins resolve in the same site-packages so `requires-plugins` | |
| # in pyproject.toml is satisfied without `poetry self add`. | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install "poetry>=2.0" "poetry-dynamic-versioning>=1.7.1,<2.0.0" | |
| - name: Install library and dependencies | |
| run: | | |
| poetry run pip install --upgrade pip setuptools | |
| poetry install --with test | |
| - name: Run Pytest + Coverage | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PLATFORM: ${{ matrix.os }} | |
| run: | | |
| poetry run pytest | |
| - name: Upload Coverage Report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: ${{ matrix.python.coveralls }} | |
| with: | |
| name: coverage | |
| path: tests/reports/.coverage.lcov | |
| - name: Upload Coveralls Report | |
| # Pinned to the SHA the repo's Actions allowlist permits (v2.3.4); | |
| # bumping requires extending the allowlist. | |
| uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 | |
| if: ${{ matrix.python.coveralls }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| file: tests/reports/.coverage.lcov | |
| build: | |
| name: build wheel | |
| runs-on: ubuntu-latest | |
| needs: [pre-commit, test] | |
| permissions: | |
| id-token: write | |
| contents: write | |
| steps: | |
| - name: Validate release inputs | |
| env: | |
| VERSION: ${{ github.event.inputs.version }} | |
| RC: ${{ github.event.inputs.rc }} | |
| GA: ${{ github.event.inputs.ga }} | |
| run: | | |
| if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "version must look like vX.Y.Z, got '$VERSION'" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$GA" == "true" && -n "$RC" ]]; then | |
| echo "rc must be empty for GA releases" >&2 | |
| exit 1 | |
| fi | |
| - name: Create tag | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| script: | | |
| github.rest.git.createRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: 'refs/tags/${{ github.event.inputs.version }}', | |
| sha: context.sha | |
| }) | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python 3.12 | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Poetry | |
| # See note in the pre-commit job above. | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install "poetry>=2.0" "poetry-dynamic-versioning>=1.7.1,<2.0.0" | |
| - name: Build wheel and sdist | |
| env: | |
| rc: ${{ github.event.inputs.rc }} | |
| ga: ${{ github.event.inputs.ga }} | |
| run: | | |
| git tag -l | |
| poetry run which python | |
| source $(poetry env info --path)/bin/activate | |
| poetry install | |
| poetry dynamic-versioning -vvv | |
| poetry build -vvv --clean | |
| - name: Validate package metadata | |
| run: | | |
| source $(poetry env info --path)/bin/activate | |
| python -m pip install --upgrade twine | |
| python -m twine check dist/* | |
| - name: Assert wheel package contents | |
| run: .github/scripts/assert_wheel_contents.sh dist | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| name: Upload build | |
| with: | |
| name: build-artifact | |
| path: dist/* | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| name: Upload wheel for K2 Kitmaker | |
| with: | |
| name: wheel-artifact | |
| path: dist/holoscan_cli-*.whl | |
| if-no-files-found: error | |
| - name: Remove tag | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| if: ${{ inputs.ga == false }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| script: | | |
| try { | |
| await github.rest.git.deleteRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: 'tags/${{ github.event.inputs.version }}' | |
| }); | |
| console.log('Tag ${{ github.event.inputs.version }} deleted successfully'); | |
| } catch (error) { | |
| if (error.status === 422) { | |
| console.log('Tag ${{ github.event.inputs.version }} does not exist, skipping deletion'); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| smoke-test: | |
| runs-on: ${{ matrix.os }} | |
| needs: [build] | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| python-version: ["3.12"] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| - name: Download build | |
| uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 | |
| with: | |
| name: build-artifact | |
| path: dist/ | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install Holoscan CLI in clean venv | |
| run: | | |
| python -m venv /tmp/holoscan-cli-smoke | |
| wheel=$(find dist -name 'holoscan_cli-*.whl' | head -n1) | |
| /tmp/holoscan-cli-smoke/bin/pip install "${wheel}" | |
| - name: Smoke Test | |
| run: .github/scripts/smoke_test.sh /tmp/holoscan-cli-smoke/bin | |
| - name: Validate create extra from wheel | |
| run: | | |
| wheel=$(find dist -name 'holoscan_cli-*.whl' | head -n1) | |
| /tmp/holoscan-cli-smoke/bin/pip install "${wheel}[create]" | |
| /tmp/holoscan-cli-smoke/bin/python -c \ | |
| 'import cookiecutter, jsonschema, referencing' | |
| - name: Install sdist in clean venv | |
| run: | | |
| python -m venv /tmp/holoscan-cli-sdist-smoke | |
| sdist=$(find dist -name '*.tar.gz' | head -n1) | |
| test -n "$sdist" | |
| /tmp/holoscan-cli-sdist-smoke/bin/pip install "$sdist" | |
| - name: Source-distribution smoke test | |
| run: .github/scripts/smoke_test.sh /tmp/holoscan-cli-sdist-smoke/bin | |
| - name: Install tool runners | |
| run: python -m pip install --upgrade uv pipx | |
| - name: Tool-runner smoke test | |
| run: .github/scripts/tool_runner_smoke.sh dist | |
| testpypi-deploy: | |
| name: publish-test-pypi | |
| permissions: | |
| id-token: write | |
| runs-on: ubuntu-latest | |
| needs: [smoke-test] | |
| steps: | |
| - name: Download build | |
| uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 | |
| with: | |
| name: build-artifact | |
| path: dist/ | |
| - name: Publish package distributions to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ | |
| verbose: true | |
| attestations: true | |
| testpypi-smoke: | |
| name: testpypi-installed smoke test | |
| runs-on: ubuntu-latest | |
| needs: [testpypi-deploy] | |
| strategy: | |
| matrix: | |
| python-version: ["3.12"] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| - name: Download build | |
| uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 | |
| with: | |
| name: build-artifact | |
| path: dist/ | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install Holoscan CLI from TestPyPI | |
| # Poll the TestPyPI simple index until the just-published version | |
| # shows up, then install it into a clean venv. Catches publish-time | |
| # breakage (file rejected, metadata wrong, index serving stale) that | |
| # the artifact-based smoke test cannot see. | |
| run: | | |
| set -euo pipefail | |
| wheel=$(find dist -name 'holoscan_cli-*.whl' | head -n1) | |
| version=$(basename "$wheel" | awk -F'-' '{print $2}') | |
| echo "Looking for holoscan-cli==$version on TestPyPI" | |
| python -m venv /tmp/holoscan-cli-testpypi | |
| for i in 1 2 3 4 5 6 7 8 9 10; do | |
| if /tmp/holoscan-cli-testpypi/bin/pip install \ | |
| --index-url https://test.pypi.org/simple/ \ | |
| --extra-index-url https://pypi.org/simple/ \ | |
| "holoscan-cli==$version"; then | |
| echo "Installed on attempt $i" | |
| exit 0 | |
| fi | |
| echo "Attempt $i failed; sleeping 30s then retrying" | |
| sleep 30 | |
| done | |
| echo "TestPyPI install did not succeed after 10 attempts" | |
| exit 1 | |
| - name: Smoke test | |
| run: .github/scripts/smoke_test.sh /tmp/holoscan-cli-testpypi/bin |