Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8a754a0
feat: use global CLI for standalone Modules
wyli Aug 11, 2026
2d68bc0
Merge remote-tracking branch 'origin/main' into feat/standalone-modul…
wyli Aug 14, 2026
815992a
feat: declare project build policy in pyproject.toml
wyli Aug 14, 2026
a13cd38
fix: skip virtual environments when validating generated Module metadata
wyli Aug 14, 2026
bbc0f30
refactor: drop the local wheelhouse and source build-context plumbing
wyli Aug 14, 2026
69d2911
fix: honor CLI-over-environment SDK precedence and keep version repor…
wyli Aug 14, 2026
3653ef1
fix: apply the project recognition check to HOLOSCAN_CLI_ROOT
wyli Aug 14, 2026
a0d5524
fix: install generated Module Python tooling into the CLI environment
wyli Aug 14, 2026
93e8f5e
refactor: install the CLI into the image interpreter instead of a venv
wyli Aug 14, 2026
fb219f9
refactor: drop the pip index build arguments from the Module template
wyli Aug 14, 2026
fd4ec45
docs: shorten the comments and guidance added with these changes
wyli Aug 14, 2026
842e207
feat: declare docker arguments and forwarded environment in project c…
wyli Aug 14, 2026
6b97a8e
feat: complete standalone Module configuration lifecycle
wyli Aug 17, 2026
f86fa24
fix: prepare container tests with Xvfb
wyli Aug 17, 2026
72a4c1f
fix: run bundled CTest from project root
wyli Aug 17, 2026
8f7a3f8
fix: clean repository-wide test diagnostics
wyli Aug 17, 2026
9061152
test: make create version scenario deterministic
wyli Aug 19, 2026
d6c1a59
test: align CPU smoke with Docker option redaction
wyli Aug 19, 2026
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
7 changes: 6 additions & 1 deletion .github/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ surface is exercised before merge. Jobs run in this order:
| `test` matrix | `poetry run pytest` on Python 3.10, 3.11, 3.12, and 3.13 (Ubuntu). |
| `HoloHub project integration` | Test current CLI against HoloHub's real project tree and wrapper suite. |
| `build wheel + sdist` | `poetry build` + `twine check` + `assert_wheel_contents.sh`. |
| `installed artifact smoke` | Test clean wheel and sdist installs, the `create` extra, uvx, and pipx. |
| `installed artifact smoke` | Test clean installs, installed-wheel Module creation, uvx, and pipx. |
| `CPU CLI + Docker smoke test` | Installed-wheel source-project dry-runs plus a tiny CPU Docker build. |

The 3.12 `test` entry uploads coverage to Coveralls; the other matrix entries
Expand Down Expand Up @@ -76,6 +76,8 @@ Pipeline:
RC dispatches do not leave stray refs.
3. **`smoke-test`** — runs `scripts/smoke_test.sh` against clean installs of
both the wheel and sdist, and verifies the wheel's `create` extra resolves.
Normal push/PR CI also creates one Module from that installed wheel and
checks its exact CLI requirement and launcher-free scaffold.
4. **`publish-test-pypi`** — runs for both GA and non-GA dispatches.
Publishes via PyPA's trusted-publisher action
(`pypa/gh-action-pypi-publish@release/v1`), no API token. Trust is
Expand Down Expand Up @@ -265,10 +267,13 @@ each pattern in two lists:
* `holoscan_cli/metadata/*.schema.json`
* `holoscan_cli/setup_scripts/*`
* `holoscan_cli/testing/`
* the packaged Module template, including `requirements-cli.txt`
and `.dockerignore`
* **forbidden** — paths that must NOT be present (regressions from past
cleanups):
* `holoscan_cli/cmake/` (moved to HoloHub in commit `6aeb611`)
* `holoscan_cli/testing/test_all_applications/` (decoupled in `2d2f44a`)
* a generated Module-root `holohub` launcher

The same script runs in both pipelines so a wheel that passes
`main.yaml` will pass `release.yaml`.
Expand Down
4 changes: 4 additions & 0 deletions .github/scripts/assert_wheel_contents.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ required=(
'holoscan_cli/setup_scripts/.+'
'holoscan_cli/setup_scripts/requirements\.template\.txt$'
'holoscan_cli/testing/'
'holoscan_cli/templates/module/cookiecutter\.json$'
'holoscan_cli/templates/module/.+/requirements-cli\.txt$'
'holoscan_cli/templates/module/.+/\.dockerignore$'
)
for pattern in "${required[@]}"; do
if ! echo "$listing" | grep -qE "$pattern"; then
Expand All @@ -37,6 +40,7 @@ done
forbidden=(
'holoscan_cli/cmake/'
'holoscan_cli/testing/test_all_applications/'
'holoscan_cli/templates/module/.+/holohub$'
)
for pattern in "${forbidden[@]}"; do
if echo "$listing" | grep -qE "$pattern"; then
Expand Down
21 changes: 16 additions & 5 deletions .github/scripts/cpu_cli_docker_smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,24 @@ run_fixture() {
"$holoscan" "$@"
}

assert_file_contains() {
local needle=$1
local output=$2
if ! grep -Fq -- "$needle" "$output"; then
printf 'Expected command output to contain: %s\n' "$needle" >&2
cat "$output" >&2
return 1
fi
}

check_id=0
assert_run_fixture_contains() {
local needle=$1
shift
local output="$tmpdir/check-${check_id}.log"
check_id=$((check_id + 1))
run_fixture "$@" > "$output"
grep -q -- "$needle" "$output"
assert_file_contains "$needle" "$output"
}

echo "--- CPU CLI fixture checks"
Expand Down Expand Up @@ -78,10 +88,11 @@ assert_run_fixture_contains "BASE_IMAGE=$tiny_base" \
run_fixture run-container --dryrun --no-docker-build --img "$image" \
--docker-opts "--memory 128m" --add-volume "$tmpdir" -- echo hello \
> "$tmpdir/run-container.log"
grep -q -- "docker run" "$tmpdir/run-container.log"
grep -q -- "--memory 128m" "$tmpdir/run-container.log"
grep -q -- "$tmpdir" "$tmpdir/run-container.log"
grep -q -- "echo hello" "$tmpdir/run-container.log"
assert_file_contains "docker run" "$tmpdir/run-container.log"
assert_file_contains \
"<2 configured Docker run option token(s) hidden>" "$tmpdir/run-container.log"
assert_file_contains "$tmpdir" "$tmpdir/run-container.log"
assert_file_contains "echo hello" "$tmpdir/run-container.log"

echo "--- installed-wheel entrypoint helper checks"
"$python" - <<'PY'
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,24 @@ jobs:
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'
'import cookiecutter, jsonschema, packaging, referencing'
create_root=$(mktemp -d)
(
cd "$create_root"
/tmp/holoscan-cli-smoke/bin/holoscan create "Artifact Smoke" --interactive false
)
module_root="$create_root/holoscan-artifact-smoke"
installed_version=$(/tmp/holoscan-cli-smoke/bin/python -c \
'from importlib.metadata import version; print(version("holoscan-cli"))')
test -f "$module_root/metadata.json"
/tmp/holoscan-cli-smoke/bin/python -c \
'import json,sys; data=json.load(open(sys.argv[1])); assert "module" in data' \
"$module_root/metadata.json"
grep -Fx "holoscan-cli==$installed_version" "$module_root/requirements-cli.txt"
test ! -e "$module_root/holohub"
test ! -e "$module_root/holoscan"
test -f "$module_root/CMakeLists.txt"
test -f "$module_root/applications/artifact_smoke_pipeline/python/metadata.json"

- name: Install sdist in clean venv
run: |
Expand Down
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@

# HoloHub-derived lint baseline for the consolidated Holoscan CLI.
# Run `pre-commit autoupdate` to refresh to latest compatible versions.
#
# The cookiecutter output tree contains Jinja in source filenames and file
# bodies, so raw Python/JSON/YAML linters cannot parse it. Generated Python and
# metadata are exercised by tests/unit/test_create_module.py instead.
exclude: '^src/holoscan_cli/templates/module/\{\{cookiecutter\.module_repo_name\}\}/'

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
148 changes: 148 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Configuring Holoscan CLI

Most standalone Module projects need only a small `pyproject.toml` section.
Put shared defaults there, use environment variables for machine-specific
values, and use command options for one-off choices. HoloHub-style source trees
continue to use their component metadata and environment settings.

When the same setting appears in several places, the order is:

```text
command line > environment > selected mode > pyproject.toml > CLI default
```

## A good starting point

```toml
[tool.holoscan]
schema-version = 1
build-type = "Release"
cuda = 13
forward-env = ["IS_CI_BUILD"]

[tool.holoscan.sdk]
version = "5.0.0"
search = ["../holoscan-sdk/install-{arch}"]
allow-parent-search = true
mount-read-only = true

[tool.holoscan.sdk.base-images]
x86_64 = "registry.example.com/holoscan/sdk-build-x86_64:5.0.0-cuda13"
aarch64 = "registry.example.com/holoscan/sdk-build-aarch64:5.0.0-cuda13"
```

Keep absolute SDK paths and credentials out of this file. On each development
machine, select an SDK installation or its parent directory with:

```bash
export HOLOSCAN_SDK_ROOT=/opt/nvidia/holoscan
# or for one command
holoscan build my_app --local-sdk-root /opt/nvidia/holoscan
```

If `sdk.search` finds an installation, the CLI uses it for local builds and
mounts it into containers. Its Python package takes priority over the SDK copy
inside the image. SDK-root selection does not change the base image; configure
the local SDK and container image independently.

## One-command choices

Use command options when experimenting or overriding a project default:

```bash
holoscan build my_app --build-type debug --cuda 13
holoscan build my_app --base-img registry.example.com/sdk:reviewed
holoscan run my_app --container --no-docker-build
holoscan run my_app dev # select a metadata mode named "dev"
```

`--base-img` is used exactly as written. A tagged or digested
`HOLOSCAN_CLI_BASE_IMAGE` is also exact. An untagged environment image repository
is combined with the selected SDK version and CUDA tag when an SDK version is
configured; otherwise it is used as written. Images pinned under
`tool.holoscan.sdk.base-images` are exact architecture-specific choices.

## Adding Docker, CMake, and environment options

These settings add to lower project defaults:

- `--build-args` for Docker build options;
- repeatable `--docker-opts` for Docker run options;
- `--configure-args` for CMake options;
- repeated `--forward-env NAME` for host environment names.

For example, repeated Docker-run fragments keep their token boundaries and are
appended in the order written:

```bash
holoscan run my_app \
--docker-opts='--network=host' \
--docker-opts='--read-only'
```

Replace inherited Docker-run options in one operation, or omit the value to
clear them:

```bash
holoscan run my_app --replace-docker-opts='--network=none'
holoscan run my_app --replace-docker-opts
```

`--docker-opts="$EXTRA_OPTS"` always means append. If `EXTRA_OPTS` is empty,
it is a harmless no-op; it never clears project, mode, or wrapper options.
The older two-flag spelling, `--replace-docker-opts --docker-opts='...'`, is
also accepted.

The other additive surfaces use a reset plus their normal addition option:

```bash
holoscan build my_app \
--replace-build-args \
--build-args='--network=none'

holoscan build my_app \
--replace-configure-args \
--configure-args=-DFEATURE=ON
```

`--replace-forward-env` similarly clears the lower allowlist before repeated
`--forward-env NAME` additions. These resets affect configurable raw options,
not the CLI's generated safety and identity arguments.

To ignore a whole inherited additive layer for one command, use:

- `--no-project-config` for `[tool.holoscan]` Docker and forwarded-name vectors;
- `--no-mode-config` for the selected mode's Docker and CMake vectors;
- `--no-inherited-config` for project, mode, and environment vectors, including
wrapper-provided Docker defaults.

Despite their short names, these switches affect only additive option vectors.
They do not disable project discovery, scalar settings such as `build-type`, the
mode command or environment, SDK/image selection, or CLI-generated invariants.

`--build-with` is intentionally different: it is a complete operator selection,
not an additive option. It replaces a mode's `build.depends`; use
`--build-with=` to select no optional operators.

Forward environment variables by name instead of putting secret values in raw
Docker arguments:

```bash
export API_TOKEN=...
holoscan run my_app --forward-env API_TOKEN
```

## See what was selected

Add `--verbose` to a lifecycle command. The report shows the project, mode,
build type, CUDA choice, SDK, images, and the contributing option layers.
Opaque Docker/CMake values and mode-environment values are hidden; only safe
source and contribution details are shown.

```bash
holoscan run my_app --verbose
```

The CLI validates paths and settings it can inspect. It cannot prove that an
arbitrary prebuilt image contains a compatible SDK/CUDA combination, so keep
fixed images aligned with the profile tested by your project.
Loading
Loading