Skip to content

Commit 655a34b

Browse files
wylicodex
andauthored
Add layered project configuration for standalone Modules (#226)
* feat: add layered project configuration for standalone Modules Linearize the standalone Module project configuration changes on the current main branch while preserving the reviewed tree. Co-authored-by: Codex <noreply@openai.com> * refactor: simplify SDK layout candidate ranking Extract GPU preference and per-entry validation from candidate enumeration while preserving existing SDK selection behavior. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Wenqi Li <wenqil@nvidia.com> --------- Signed-off-by: Wenqi Li <wenqil@nvidia.com> Co-authored-by: Codex <noreply@openai.com>
1 parent b203c06 commit 655a34b

51 files changed

Lines changed: 3370 additions & 897 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/CI.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@ surface is exercised before merge. Jobs run in this order:
3434
| Job | Purpose |
3535
| ----------------------------- | -------------------------------------------------------------------------- |
3636
| `pre-commit` | Run all hooks listed in `.pre-commit-config.yaml` on Python 3.12. |
37-
| `test` matrix | `poetry run pytest` on Python 3.10, 3.11, 3.12, and 3.13 (Ubuntu). |
37+
| `test` matrix | `poetry run pytest` on Python 3.11, 3.12, and 3.13 (Ubuntu). |
3838
| `HoloHub project integration` | Test current CLI against HoloHub's real project tree and wrapper suite. |
3939
| `build wheel + sdist` | `poetry build` + `twine check` + `assert_wheel_contents.sh`. |
4040
| `installed artifact smoke` | Test clean wheel and sdist installs, the `create` extra, uvx, and pipx. |
4141
| `CPU CLI + Docker smoke test` | Installed-wheel source-project dry-runs plus a tiny CPU Docker build. |
4242

4343
The 3.12 `test` entry uploads coverage to Coveralls; the other matrix entries
44-
exist purely to catch version-specific regressions (e.g. `tomllib` is stdlib
45-
on 3.11+ but missing on 3.10).
44+
exist purely to catch version-specific regressions across supported runtimes.
4645

4746
`coveralls` itself is only pulled in for `python_version < '3.13'`; on Python
4847
3.13 the test job skips the upload step.

‎.github/scripts/cpu_cli_docker_smoke.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ run_fixture run-container --dryrun --no-docker-build --img "$image" \
7979
--docker-opts "--memory 128m" --add-volume "$tmpdir" -- echo hello \
8080
> "$tmpdir/run-container.log"
8181
grep -q -- "docker run" "$tmpdir/run-container.log"
82-
grep -q -- "--memory 128m" "$tmpdir/run-container.log"
82+
grep -Fq -- "<2 configured Docker run option token(s) hidden>" "$tmpdir/run-container.log"
8383
grep -q -- "$tmpdir" "$tmpdir/run-container.log"
8484
grep -q -- "echo hello" "$tmpdir/run-container.log"
8585

‎.github/workflows/main.yaml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ jobs:
8181
os: [ubuntu-latest]
8282
python:
8383
[
84-
{ version: "3.10", coveralls: false },
8584
{ version: "3.11", coveralls: false },
8685
{ version: "3.12", coveralls: true }, # publish coverage with latest version of Python
8786
{ version: "3.13", coveralls: false },

‎.github/workflows/release.yaml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ jobs:
8787
os: [ubuntu-latest]
8888
python:
8989
[
90-
{ version: "3.10", coveralls: false },
9190
{ version: "3.11", coveralls: false },
9291
{ version: "3.12", coveralls: true }, # publish coverage with latest version of Python
9392
{ version: "3.13", coveralls: false },

‎CONFIGURATION.md‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Configuring Holoscan CLI
2+
3+
Run `holoscan` inside a source project. To select another project, put the
4+
global option before the command:
5+
6+
```bash
7+
holoscan --project-root /path/to/module list
8+
```
9+
10+
The root is selected from `--project-root`, `HOLOSCAN_CLI_ROOT`, then the current
11+
directory and its ancestors.
12+
13+
For standalone Modules, `metadata.json` supplies the identity, minimum SDK
14+
version, Dockerfile, and modes. The CLI otherwise uses these defaults:
15+
16+
- Target architecture: `HOLOSCAN_CLI_TARGET_ARCH`, then the host architecture.
17+
- CUDA: `--cuda`, `HOLOSCAN_CLI_DEFAULT_CUDA_VERSION`, `tool.holoscan.cuda`,
18+
then host detection.
19+
- CTest script: `--ctest-script`, `HOLOSCAN_CLI_CTEST_SCRIPT`,
20+
`tool.holoscan.ctest-script`, then the bundled script.
21+
- Execution: `--local`, `HOLOSCAN_CLI_BUILD_LOCAL`, the selected mode, then a container.
22+
- Build type: `--build-type`, `CMAKE_BUILD_TYPE`, the selected mode, then release.
23+
- Local SDK: `--local-sdk-root`, `HOLOSCAN_SDK_ROOT`, `/workspace/holoscan-sdk`
24+
for local container builds, a nearby `holoscan-sdk` install or configured source
25+
build, then `/opt/nvidia/holoscan`. Both 4.x CUDA-qualified directories such as
26+
`install-cu13-x86_64` and 5.x architecture-only directories such as
27+
`install-x86_64` are supported; installs are preferred over builds. An invalid
28+
`HOLOSCAN_SDK_ROOT` warns and does not fall back.
29+
30+
## `pyproject.toml` settings
31+
32+
For a standalone Module, Holoscan CLI reads only the `[tool.holoscan]` table.
33+
Other `pyproject.toml` tables belong to Python packaging and development tools.
34+
35+
These are all currently supported Holoscan CLI settings:
36+
37+
| TOML path | Type and default | Behavior |
38+
| --- | --- | --- |
39+
| `tool.holoscan.cuda` | Integer; default detected from the host | Module-wide CUDA major version. |
40+
| `tool.holoscan.ctest-script` | Relative path; default is the bundled script | Module-specific CTest driver. |
41+
| `tool.holoscan.forward-env` | Array of strings; default `[]` | Names of host environment variables allowed into project containers. |
42+
| `tool.holoscan.docker-build-args` | Array of non-empty string tokens; default `[]` | Module-wide Docker build options. |
43+
| `tool.holoscan.docker-run-args` | Array of non-empty string tokens; default `[]` | Module-wide Docker run options. |
44+
| `tool.holoscan.base-images` | Table; default absent | Exact base images selected by target architecture. Only the two keys below are accepted. |
45+
| `tool.holoscan.base-images.x86_64` | String; no default | Base image used when the normalized target architecture is `x86_64`. |
46+
| `tool.holoscan.base-images.aarch64` | String; no default | Base image used when the normalized target architecture is `aarch64`. |
47+
48+
For example:
49+
50+
```toml
51+
[tool.holoscan]
52+
cuda = 13
53+
ctest-script = "ci/container.ctest"
54+
forward-env = ["IS_CI_BUILD"]
55+
docker-build-args = ["--build-arg", "PROJECT_FEATURE=ON"]
56+
docker-run-args = ["--network=host"]
57+
58+
[tool.holoscan.base-images]
59+
x86_64 = "registry.example.com/holoscan/sdk-build-x86_64:5.0.0-cuda13"
60+
aarch64 = "registry.example.com/holoscan/sdk-build-aarch64:5.0.0-cuda13"
61+
```
62+
63+
`cuda` selects the Module-wide CUDA major.
64+
`ctest-script` must stay within the Module and is resolved from its root.
65+
Environment variables and command options override both project defaults.
66+
67+
`forward-env` entries must be valid environment variable names. Values are
68+
never stored in the file or placed on the Docker command line; Docker inherits
69+
the value only when that name exists on the host. Project entries are additive
70+
with `HOLOSCAN_CLI_FORWARD_ENV` and repeated `--forward-env NAME` options.
71+
`NVIDIA_DRIVER_CAPABILITIES`, `NVIDIA_VISIBLE_DEVICES`, `HOME`,
72+
`CUPY_CACHE_DIR`, and `HOLOSCAN_CLI_BUILD_LOCAL` are CLI-owned and cannot be
73+
listed.
74+
75+
Each Docker argument array element is one command token. Arguments are composed
76+
in this order: project, selected `metadata.json` mode, environment
77+
(`HOLOSCAN_CLI_DEFAULT_DOCKER_BUILD_ARGS` or
78+
`HOLOSCAN_CLI_DEFAULT_DOCKER_RUN_ARGS`), then command line. Later options can
79+
therefore override earlier ones when Docker uses last-option-wins behavior.
80+
81+
Each `base-images` value must be a non-empty image reference without whitespace
82+
and is used exactly as written. If the table exists, it must contain an entry
83+
for the selected target architecture. Provide both entries when the Module
84+
supports both architectures. `--base-img` and an explicit
85+
`HOLOSCAN_CLI_BASE_IMAGE` override the project value.
86+
87+
Unknown keys in either `[tool.holoscan]` or `[tool.holoscan.base-images]` are
88+
rejected. No other Holoscan CLI `pyproject.toml` settings are currently
89+
supported.
90+
91+
Do not put credentials or machine-specific paths in project configuration.
92+
93+
## Command options
94+
95+
```bash
96+
holoscan build my_app --local --build-type debug
97+
holoscan build my_app --cuda 13 --base-img registry.example.com/sdk:reviewed
98+
```
99+
100+
`--build-args` adds Docker build options. `--docker-opts`, `--configure-args`,
101+
and `--forward-env NAME` are repeatable and additive.
102+
103+
`--build-with` replaces the selected mode's operator dependencies; use
104+
`--build-with=` to select none.
105+
106+
Add `--dryrun` to a lifecycle command to see the resolved configuration and commands
107+
without executing them. Scalar values include their source. Docker and CMake options show
108+
which layers configured them without exposing their values; forwarded environment entries
109+
show names but not values.
110+
111+
For example, a container build using the project settings above reports a summary like:
112+
113+
```text
114+
Effective configuration (opaque option values hidden):
115+
project root: /workspace/my-module (directory discovery)
116+
mode: release (project default)
117+
execution: container (built-in default)
118+
build type: Release (built-in default)
119+
CUDA: 13 (project (tool.holoscan.cuda))
120+
local SDK: none (container image)
121+
base image: registry.example.com/holoscan/sdk-build-x86_64:5.0.0-cuda13 (project (tool.holoscan.base-images))
122+
Docker build options: configured by project (values hidden)
123+
run image: holoscan-my-module:review (derived default)
124+
Docker run options: configured by project (values hidden)
125+
forward-env: IS_CI_BUILD (project)
126+
CMake configure options: none
127+
```

‎CONTRIBUTING.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ avoids tying up shared runners.
5858

5959
### Set up the development environment
6060

61-
Python 3.10+ and [Poetry 2.0+](https://python-poetry.org/docs/#installation)
61+
Python 3.11+ and [Poetry 2.0+](https://python-poetry.org/docs/#installation)
6262
are required. From a fresh clone:
6363

6464
```bash
@@ -81,8 +81,8 @@ poetry run pytest -q # run the unit test suite.
8181

8282
If `pre-commit run --all-files` passes locally, `Code Check`'s `pre-commit`
8383
job will pass on push. If `poetry run pytest` passes, the `test` matrix will
84-
pass on the same Python version locally (CI also runs 3.10 / 3.11 / 3.12 /
85-
3.13; for full matrix coverage either use the Python you don't normally use,
84+
pass on the same Python version locally (CI also runs 3.11 / 3.12 / 3.13;
85+
for full matrix coverage either use the Python you don't normally use,
8686
or rely on CI).
8787

8888
### Smoke-test the installed wheel

‎README.md‎

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Common env vars:
2929
- `HOLOSCAN_CLI_SEARCH_PATH` — subdirs to scan for `metadata.json`
3030
- `HOLOSCAN_CLI_PATH_PREFIX` — placeholder prefix in metadata templates
3131
- `HOLOSCAN_CLI_REPO_PREFIX` — container image name prefix
32+
- `HOLOSCAN_CLI_CONTAINER_PREFIX` — standalone Module image name prefix
3233
- `HOLOSCAN_CLI_CREATE_TEMPLATE` — default template for `holoscan create`
3334

3435
`holoscan env-info` lists every env var the CLI reads in the current shell.
@@ -71,6 +72,7 @@ removal or rename bumps `schema_version`.
7172
```text
7273
src/holoscan_cli/
7374
cli.py top-level argparse + dispatch (HoloscanCLI)
75+
configuration.py effective-configuration reporting
7476
commands/ one file per subcommand + a central registry
7577
container/ HoloscanContainer + docker arg helpers + parser builders
7678
cmake/ packaged CMake support copied into standalone Modules
@@ -127,6 +129,31 @@ global `--project-root PATH` before the subcommand (equivalent to setting
127129
holoscan --project-root ~/holoscan-my-sensor list
128130
```
129131

132+
### Project configuration
133+
134+
Standalone Modules can persist a small set of settings that cannot be inferred
135+
from `metadata.json` or the host:
136+
137+
```toml
138+
[tool.holoscan]
139+
cuda = 13
140+
ctest-script = "ci/container.ctest"
141+
forward-env = ["IS_CI_BUILD"]
142+
docker-build-args = ["--build-arg", "PROJECT_FEATURE=ON"]
143+
docker-run-args = ["--network=host"]
144+
145+
[tool.holoscan.base-images]
146+
x86_64 = "registry.example.com/holoscan/sdk-build-x86_64:4.5.0-cuda13"
147+
aarch64 = "registry.example.com/holoscan/sdk-build-aarch64:4.5.0-cuda13"
148+
```
149+
150+
`cuda` and `ctest-script` provide Module-wide toolchain defaults,
151+
`forward-env` contains names only, the Docker argument arrays provide static
152+
defaults, and `base-images` contains exact images for the supported target
153+
architectures. Use command options for one-off choices. See
154+
[Configuring Holoscan CLI](https://github.com/nvidia-holoscan/holoscan-cli/blob/main/CONFIGURATION.md)
155+
for the complete user-facing behavior.
156+
130157
## Versioning
131158

132159
`holoscan-cli` release versions are aligned with Holoscan SDK GA release
@@ -149,8 +176,8 @@ with `FROM ${BASE_IMAGE}`, using any of the methods below:
149176
holoscan build-container my_app --base-img nvcr.io/nvidia/clara-holoscan/holoscan:v4.4.0-cuda13
150177
```
151178

152-
2. Set the `HOLOSCAN_CLI_BASE_IMAGE` environment variable to a fully qualified
153-
image path:
179+
2. Set `HOLOSCAN_CLI_BASE_IMAGE` to an exact tagged or digested image. It is
180+
used without adding another tag:
154181

155182
```bash
156183
export HOLOSCAN_CLI_BASE_IMAGE=nvcr.io/nvidia/clara-holoscan/holoscan:v4.4.0-cuda13
@@ -173,9 +200,16 @@ with `FROM ${BASE_IMAGE}`, using any of the methods below:
173200
If none of these is configured, the CLI asks for a base image instead of
174201
inferring one from its own package version.
175202

203+
Advanced wrappers can set `HOLOSCAN_CLI_BASE_IMAGE_FORMAT` with
204+
`{base_image}`, `{sdk_version}`, and `{cuda_tag}`, or
205+
`HOLOSCAN_CLI_DEFAULT_IMAGE_FORMAT` with `{container_prefix}`,
206+
`{sdk_version}`, and `{cuda_tag}`. An explicit base-image format controls
207+
composition; without one, tagged images and digests are exact while an
208+
untagged environment repository uses the SDK/CUDA-derived tag.
209+
176210
## Build from source
177211

178-
Python 3.10+ and [Poetry 2.0+](https://python-poetry.org/docs/#installation) required.
212+
Python 3.11+ and [Poetry 2.0+](https://python-poetry.org/docs/#installation) required.
179213

180214
```bash
181215
# Create + activate a virtual environment

‎pyproject.toml‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ description = "Command line interface for HoloHub-style Holoscan source-project
1919
readme = "README.md"
2020
license = "Apache-2.0"
2121
license-files = ["LICENSE"]
22-
requires-python = ">=3.10,<3.14"
22+
requires-python = ">=3.11,<3.14"
2323
authors = [{ name = "NVIDIA" }]
2424
maintainers = [{ name = "NVIDIA" }]
2525
keywords = [
@@ -44,7 +44,6 @@ classifiers = [
4444
"Intended Audience :: Healthcare Industry",
4545
"Operating System :: POSIX :: Linux",
4646
"Programming Language :: Python :: 3",
47-
"Programming Language :: Python :: 3.10",
4847
"Programming Language :: Python :: 3.11",
4948
"Programming Language :: Python :: 3.12",
5049
"Programming Language :: Python :: 3.13",
@@ -96,7 +95,6 @@ pytest-cov= "^7.0.0"
9695
pytest-mock= "^3.15.1"
9796
pytest = "^9.1.1"
9897
coveralls = { version = "^4.1.0", markers = "python_version < '3.13'" }
99-
tomli = { version = "^2.4", markers = "python_version < '3.11'" }
10098
# Mirrors the runtime ``[create]`` extra so the unit tests for the
10199
# schema validator (``test_metadata_validator.py``) and the smoke
102100
# fixture (``test_smoke_fixture.py``) can import them without
@@ -131,7 +129,7 @@ skip_glob = [
131129

132130
[tool.black]
133131
line-length = 100
134-
target-version = ["py310", "py311", "py312", "py313"]
132+
target-version = ["py311", "py312", "py313"]
135133
force-exclude = 'src/holoscan_cli/templates/module/'
136134
extend-exclude = '''
137135
(
@@ -145,7 +143,7 @@ extend-exclude = '''
145143

146144
[tool.ruff]
147145
line-length = 100
148-
target-version = "py310"
146+
target-version = "py311"
149147
cache-dir = ".cache/ruff"
150148
exclude = [
151149
".cache",

0 commit comments

Comments
 (0)