Skip to content

Release

Release #120

Workflow file for this run

# 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