Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 11 additions & 152 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ jobs:
with:
version: "0.12.1"
enable-cache: true
- run: uv python install 3.11
- run: uv sync --locked --extra dev --python 3.11
- run: uv python install 3.12
- run: uv sync --locked --extra dev --python 3.12
- run: make ci-lint

test-cpu:
Expand All @@ -46,9 +46,6 @@ jobs:
fail-fast: false
matrix:
include:
- name: linux-x64
runner: ubuntu-24.04
python: "3.11"
- name: linux-x64
runner: ubuntu-24.04
python: "3.12"
Expand All @@ -60,7 +57,7 @@ jobs:
python: "3.14"
- name: linux-arm64
runner: ubuntu-24.04-arm
python: "3.11"
python: "3.12"
runs-on: ${{ matrix.runner }}
env:
CUPHOTON_XREP_TORCH_DEVICE: cpu
Expand All @@ -77,155 +74,22 @@ jobs:
- run: uv python install "${PYTHON_VERSION}"
- run: >-
uv sync --locked --python "${PYTHON_VERSION}"
--extra dev --extra torch --extra viz
--extra dev --extra torch --extra viz --extra photometry
- run: make ci-test-cpu
- name: Run synthetic CPU quickstarts
if: matrix.name == 'linux-x64' && matrix.python == '3.11'
if: matrix.name == 'linux-x64' && matrix.python == '3.12'
run: >-
uv run --locked --extra dev --extra torch --extra viz
uv run --locked --extra dev --extra torch --extra viz --extra photometry
python examples/run_quickstarts.py --profile cpu
--output-dir "${RUNNER_TEMP}/cuphoton-quickstart"

package:
timeout-minutes: 30
name: build / metadata / install smoke
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.12.1"
enable-cache: true
- run: uv python install 3.11
- run: uv sync --locked --extra dev --python 3.11
- run: make package-check
- name: Verify the distribution contract
run: |
python - <<'PY'
from pathlib import Path
import re
import tarfile
import zipfile

def is_shared_library(name):
filename = Path(name).name.lower()
return filename.endswith((".dll", ".dylib", ".pyd", ".so")) or (
re.search(r"\.so(?:\.[0-9]+)+\Z", filename) is not None
)


dist = Path("dist")
wheels = list(dist.glob("*.whl"))
sdists = list(dist.glob("*.tar.gz"))
assert len(wheels) == 1, wheels
assert len(sdists) == 1, sdists

wheel = wheels[0]
assert wheel.name.endswith("-py3-none-any.whl"), wheel.name
with zipfile.ZipFile(wheel) as archive:
names = archive.namelist()
shared_libraries = [
name
for name in names
if is_shared_library(name)
]
assert not shared_libraries, shared_libraries
metadata_name = next(
name for name in names if name.endswith(".dist-info/WHEEL")
)
metadata = archive.read(metadata_name).decode("utf-8")
assert "Root-Is-Purelib: true" in metadata
assert "Tag: py3-none-any" in metadata

required_native_sources = {
"build.sh",
"io.cpp",
"memory_manager.cpp",
"nvcomp_batch_ext.cpp",
"nvcomp_batch_ext.h",
}
with tarfile.open(sdists[0], "r:gz") as archive:
names = archive.getnames()
source_names = {
Path(name).name
for name in names
if "/src/cuphoton/xdr/src/" in name
}
shared_libraries = [
name
for name in names
if is_shared_library(name)
]
assert required_native_sources <= source_names, source_names
assert not shared_libraries, shared_libraries
PY
- run: uv venv --seed --python 3.11 .wheel-venv
- run: .wheel-venv/bin/python -m pip install dist/*.whl
- run: .wheel-venv/bin/python -m pip check
- run: uv venv --seed --python 3.11 .sdist-venv
- run: .sdist-venv/bin/python -m pip install dist/*.tar.gz
- run: .sdist-venv/bin/python -m pip check
- name: Smoke-test installed distributions and CLIs
run: |
for environment in .wheel-venv .sdist-venv; do
"${environment}"/bin/python -c \
"import cuphoton; import cuphoton.xdr; import cuphoton.xfit; import cuphoton.xpois; import cuphoton.xscan; import cuphoton.xrep; import cuphoton.xray; print(cuphoton.__version__)"
"${environment}"/bin/cuphoton --help >/dev/null
"${environment}"/bin/cuphoton --version
for group in xdr xfit xpois xscan xrep xray; do
"${environment}"/bin/cuphoton "${group}" --help >/dev/null
done
done
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cuphoton-distributions
path: dist/*
if-no-files-found: error
retention-days: 7

package-compatibility:
timeout-minutes: 20
name: install smoke / py${{ matrix.python }}
needs: package
strategy:
fail-fast: false
matrix:
python: ["3.12", "3.13", "3.14"]
runs-on: ubuntu-24.04
env:
PYTHON_VERSION: ${{ matrix.python }}
steps:
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.12.1"
- run: uv python install "${PYTHON_VERSION}"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cuphoton-distributions
path: dist
- run: uv venv --seed --python "${PYTHON_VERSION}" .wheel-venv
- run: .wheel-venv/bin/python -m pip install dist/*.whl
- run: .wheel-venv/bin/python -m pip check
- run: uv venv --seed --python "${PYTHON_VERSION}" .sdist-venv
- run: .sdist-venv/bin/python -m pip install dist/*.tar.gz
- run: .sdist-venv/bin/python -m pip check
- name: Smoke-test installed distributions and CLIs
run: |
for environment in .wheel-venv .sdist-venv; do
"${environment}"/bin/python -c \
"import cuphoton; import cuphoton.xdr; import cuphoton.xfit; import cuphoton.xpois; import cuphoton.xscan; import cuphoton.xrep; import cuphoton.xray; print(cuphoton.__version__)"
"${environment}"/bin/cuphoton --help >/dev/null
"${environment}"/bin/cuphoton --version
for group in xdr xfit xpois xscan xrep xray; do
"${environment}"/bin/cuphoton "${group}" --help >/dev/null
done
done
name: native distributions
uses: ./.github/workflows/wheels.yml

ci-required:
name: ci-required
needs: [lint, test-cpu, package, package-compatibility]
needs: [lint, test-cpu, package]
if: ${{ always() }}
runs-on: ubuntu-24.04
timeout-minutes: 5
Expand All @@ -235,12 +99,7 @@ jobs:
LINT_RESULT: ${{ needs.lint.result }}
CPU_RESULT: ${{ needs.test-cpu.result }}
PACKAGE_RESULT: ${{ needs.package.result }}
COMPATIBILITY_RESULT: ${{ needs.package-compatibility.result }}
run: |
for result in "${LINT_RESULT}" "${CPU_RESULT}" "${PACKAGE_RESULT}" "${COMPATIBILITY_RESULT}"; do
if [[ "${result}" != success ]]; then
printf 'Required CI jobs did not succeed: lint=%s, tests=%s, package=%s, compatibility=%s\n' \
"${LINT_RESULT}" "${CPU_RESULT}" "${PACKAGE_RESULT}" "${COMPATIBILITY_RESULT}"
exit 1
fi
for result in "${LINT_RESULT}" "${CPU_RESULT}" "${PACKAGE_RESULT}"; do
test "${result}" = success || exit 1
done
152 changes: 152 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

name: publish

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
version:
description: Existing release tag version, such as 0.1.3rc0
required: true
type: string
target:
description: Protected publishing environment
required: true
type: choice
options: [testpypi, pypi]
default: testpypi
run-id:
description: Reuse this release build run instead of rebuilding (optional)
required: false
type: string

permissions:
contents: read

concurrency:
group: publish-${{ inputs.target || 'testpypi' }}-${{ github.event_name == 'push' && github.ref_name || format('v{0}', inputs.version) }}
cancel-in-progress: false

jobs:
prepare:
if: >-
(github.event_name == 'push' && !github.event.deleted) ||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
version: ${{ steps.release.outputs.version }}
sha: ${{ steps.release.outputs.sha }}
tag: ${{ steps.release.outputs.tag }}
target: ${{ inputs.target || 'testpypi' }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
fetch-depth: 0
- name: Resolve the release tag to a reviewed source commit
id: release
env:
RELEASE_TAG: ${{ github.event_name == 'push' && github.ref_name || format('v{0}', inputs.version) }}
EXPECTED_SHA: ${{ github.event_name == 'push' && github.sha || '' }}
BUILD_RUN_ID: ${{ inputs.run-id }}
run: |
if test -n "$BUILD_RUN_ID"; then
[[ "$BUILD_RUN_ID" =~ ^[1-9][0-9]*$ ]]
fi
python scripts/wheels/release.py resolve "$RELEASE_TAG" --expected-sha "$EXPECTED_SHA"

build:
name: release wheels
needs: prepare
if: inputs.run-id == ''
uses: ./.github/workflows/wheels.yml
with:
source-ref: ${{ needs.prepare.outputs.sha }}
release-version: ${{ needs.prepare.outputs.version }}

stage:
needs: [prepare, build]
if: >-
!cancelled() && needs.prepare.result == 'success' &&
(needs.build.result == 'success' ||
(needs.build.result == 'skipped' && inputs.run-id != ''))
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
actions: read
contents: read
outputs:
pending: ${{ steps.upload.outputs.pending }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
fetch-depth: 0
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.12.1"
Comment thread
tpn marked this conversation as resolved.
enable-cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cuphoton-distributions
path: dist
run-id: ${{ inputs.run-id || github.run_id }}
github-token: ${{ github.token }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cuphoton-build-provenance
path: provenance
run-id: ${{ inputs.run-id || github.run_id }}
github-token: ${{ github.token }}
- name: Verify the build, tag, complete matrix, and unchanged bytes
env:
GH_TOKEN: ${{ github.token }}
BUILD_RUN_ID: ${{ inputs.run-id || github.run_id }}
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
SOURCE_SHA: ${{ needs.prepare.outputs.sha }}
run: |
python scripts/wheels/release.py resolve "$RELEASE_TAG" --expected-sha "$SOURCE_SHA"
python scripts/wheels/check_distributions.py dist --version "$RELEASE_VERSION"
python scripts/wheels/release.py verify dist provenance/provenance.json \
--tag "$RELEASE_TAG" --sha "$SOURCE_SHA" --run-id "$BUILD_RUN_ID"
uvx --isolated --from twine==6.2.0 twine check --strict dist/*
cat provenance/provenance.json >> "$GITHUB_STEP_SUMMARY"
- name: Exclude already published files only when their hashes match
id: upload
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
TARGET: ${{ needs.prepare.outputs.target }}
run: |
python scripts/wheels/release.py pending dist --version "$RELEASE_VERSION" --target "$TARGET"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: steps.upload.outputs.pending != '0'
with:
name: publish-distributions
path: dist/*
if-no-files-found: error

publish:
needs: [prepare, stage]
if: >-
!cancelled() && needs.prepare.result == 'success' &&
needs.stage.result == 'success' && needs.stage.outputs.pending != '0'
runs-on: ubuntu-24.04
timeout-minutes: 10
environment:
name: ${{ needs.prepare.outputs.target }}
permissions:
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: publish-distributions
path: dist
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
repository-url: ${{ needs.prepare.outputs.target == 'testpypi' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
Loading
Loading