Skip to content

Feat/standalone module configuration - #223

Draft
wyli wants to merge 18 commits into
mainfrom
feat/standalone-module-configuration
Draft

Feat/standalone module configuration#223
wyli wants to merge 18 commits into
mainfrom
feat/standalone-module-configuration

Conversation

@wyli

@wyli wyli commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

wyli and others added 18 commits August 11, 2026 16:09
Generate launcher-free Modules with an exact requirements-cli.txt contract, activate metadata-derived project defaults before CLI imports, and reject host or container version drift before lifecycle work.\n\nSupport safe missing, empty, and Git-only creation targets, package the canonical wheelhouse/Docker flow, and cover installed-artifact creation.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
…e-create

Signed-off-by: Wenqi Li <wenqil@nvidia.com>

# Conflicts:
#	src/holoscan_cli/setup_scripts/requirements.template.txt
Add a versioned [tool.holoscan] table so a source project states its own
lifecycle policy instead of exporting it from a repository shell wrapper. The
5.0 early-access samples use this to delete ./holohub without requiring every
future Holoscan repository to become wrapperless.

Schema version 1 covers project identity prefixes, metadata search paths, CMake
build type, the project CTest script, the CUDA line, and an SDK block: exact
version, architecture-keyed base images, relative search hints carrying an
{arch} token, and parent-search and read-only mount capabilities. Unknown keys,
wrong types, unsupported schema versions, absolute or escaping paths, and
unsupported placeholders fail before any lifecycle work starts, so this stays
capability configuration rather than a general-purpose task runner.

Resolution order is command option, typed environment override, committed
value, metadata-derived default, then CLI default. The resolver owns
architecture normalization so a project never writes $(uname -m) in a path,
HOLOSCAN_SDK_ROOT remains the single machine-local escape hatch and now fails
closed instead of silently resolving another tree, and container recursion
prefers the CLI-owned /workspace/holoscan-sdk mount over host-side hints that
do not describe the container.

Add --project-root as a global option, and report the resolved project profile
with the provenance of each value through `holoscan version --json`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
The generated Module's quick start creates .venv at the repository root and
installs holoscan-cli into it. That wheel ships the unrendered cookiecutter
module template, so the metadata validator walked into site-packages and
validated template files: placeholder versions fail the version pattern, and
files carrying Jinja control blocks are not valid JSON at all. Following the
quick start therefore broke the module's own lint.

Add .venv and venv to the excluded directory names, alongside .git, which the
sample repository's validator already excluded.

Verified by generating a Module, creating .venv per its quick start, installing
the CLI wheel into it, and running the generated validator: it reported four
template metadata.json files under .venv before the change and validates only
the module's own five files after it.

Note the template is rendered through Jinja, so `{{ ... }}` is executable even
inside comments; the explanatory comment here deliberately avoids it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Both mechanisms existed to run an unpublished CLI build, which is a
CLI-maintenance task rather than something Holoscan developers do, so neither
earns its place in every generated Module.

The generated Dockerfile bind-mounted a .holoscan-cli-wheelhouse directory for
the install step and passed --find-links, requiring the directory, a committed
.gitignore inside it, wheel-content assertions, and matching instructions in the
Module README and CI workflow. It now installs the exact pin from the configured
package index like any other requirement.

local_source_build_context_args() emitted `--build-context holoscan-cli-src=...`
when HOLOSCAN_CLI_SOURCE was set. Nothing consumed that context: no Dockerfile in
the template or in any consuming repository mounted it, so the flag was passed to
every docker build and ignored. HOLOSCAN_CLI_SOURCE itself is unchanged; it
remains a wrapper convention that env-info reports.

An unpublished build is still usable. `pip install <wheel>` satisfies the exact
version contract on the host, and the Dockerfile already honors PIP_INDEX_URL and
PIP_EXTRA_INDEX_URL for a local or internal index, which the Module DEVELOPER.md
now documents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
…table

Two defects had the same cause: project discovery runs before the command
parser, so decisions it made could not be revised by a command option.

Discovery rejected an invalid HOLOSCAN_SDK_ROOT by raising. Because that
happened before --local-sdk-root was parsed, a valid command-line SDK path could
not override a broken environment value, contradicting the documented order of
command option, then environment, then committed configuration. The same raise
also failed commands that never need an SDK. Discovery now records the problem
as a warning and still refuses to fall back to a committed hint, so an explicit
override is never silently replaced by a different tree.

Validation moves to the point of use, where both sources are visible at once.
get_local_sdk_options() previously logged an advisory line when the path was not
an SDK installation and mounted it anyway, so an invalid --local-sdk-root became
a bind mount of a nonexistent directory. It now validates whichever source it
selected, names that source in the error, and fails closed.

`--version` no longer performs project discovery at all: it answers which CLI is
installed, and an unreadable project or a schema newer than this CLI must not
make that unanswerable. `version` still reports the project, but a resolution
failure becomes a warning plus a project_error field rather than exit 2, which
matters most for an older CLI meeting a newer project. Lifecycle commands remain
fail-closed.

Verified: an invalid environment plus a valid --local-sdk-root now mounts the
flag's path; an invalid --local-sdk-root exits 1 instead of mounting it; an
invalid environment alone still exits 1 when an SDK is required; and both
--version and version report 5.0.0a14 against a project declaring schema-version
99.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
--project-root rejected a directory that is not a recognized source-project or
Module root, but the environment form accepted any directory that merely
existed. Pointing it at an arbitrary path therefore produced an empty project
whose requirements-cli.txt and [tool.holoscan] were never read, so the Module
version contract and all project configuration were silently skipped rather
than enforced.

Apply the same recognition check to the environment root. The two forms still
differ in remedy, deliberately: an explicit --project-root is an assertion and
fails, while HOLOSCAN_CLI_ROOT is ambient and warns before falling back to
discovery from the current directory, matching the existing behavior for a root
that does not exist.

Verified that HOLOSCAN_CLI_ROOT=/tmp now warns and falls back, and that the
HoloHub, Isaac OS, and early-access sample roots are still accepted without
warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
The generated Dockerfile installed pytest-timeout, build, and scikit-build-core
with the system pip, but every command that uses them resolves them from the
CLI environment instead.

holoscan build and holoscan package configure CMake with
-DPython3_EXECUTABLE=<sys.executable>, which is /opt/holoscan-cli/bin/python in
the generated image. The Module's tests/CMakeLists.txt runs
${Python3_EXECUTABLE} -m pytest, and package --pkg-generator WHEEL runs
<that python> -m build after checking for the module with importlib. Both
therefore looked in an environment where nothing had been installed, so wheel
packaging failed on a missing build frontend and the pytest target had no test
runner.

Install all three into /opt/holoscan-cli. The early-access sample keeps its
model-export dependencies on the system interpreter, which is correct there
because prepare_model.sh invokes python3 directly rather than through CMake.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
The generated Dockerfile created /opt/holoscan-cli, installed the pin there,
symlinked the console script onto PATH, re-derived the expected version from
requirements-cli.txt in an inline Python one-liner, and asserted it matched --
ten lines and an extra apt package for python3-venv.

The isolation it bought does not exist. holoscan-cli declares no runtime
dependencies on Python 3.11+, so installing it into the image interpreter adds
nothing else to the image. The separation was also never real: build and package
configure CMake with -DPython3_EXECUTABLE=<the CLI's python>, so the venv was
already the interpreter that compiled the project, ran pytest, and built wheels.
That split is what made the packaging tooling land in an interpreter nothing
used.

Install with the image's own pip. The console script lands on PATH by itself, so
the symlink and the `command -v` check go away, and pip already guarantees the
pinned version that the removed assertion re-checked -- as does the CLI's own
runtime contract. Verified against the pinned SDK image: holoscan resolves to
/usr/local/bin/holoscan with a /usr/bin/python3 shebang, matching sys.executable,
and `pip show holoscan-cli` reports no requirements.

PEP 668 does not apply here. The marker covers the distro tree at
/usr/lib/python3.12, while the image's pip targets /usr/local, so no
--break-system-packages is needed; a real build confirms it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
The generated Dockerfile declared ARG PIP_INDEX_URL, PIP_EXTRA_INDEX_URL, and
PIP_NO_INDEX so a caller could redirect pip during the image build. Nothing in
the normal path needs them: holoscan-cli is published to PyPI, so the pinned
requirement resolves from the default index with no configuration, and
PIP_NO_INDEX existed only for the removed local wheelhouse.

Declaring an ARG is what makes `--build-arg` reach the build environment, so
removing them also removes the container-side index override. The host-side
`--extra-index-url` guidance stays, since that is where a pre-release actually
needs it, and the generated requirements-cli.txt still carries that hint for a
pre-release pin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Several comments and template docs explained the reasoning at more length than
the code needs. Keep the fact and drop the argument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
…onfig

A project could only supply persistent docker arguments through a wrapper that
exported HOLOSCAN_CLI_DEFAULT_DOCKER_BUILD_ARGS and _RUN_ARGS. Those values are
usually static -- capability flags, ulimits, namespace sharing, a secret
reference -- so they belong in committed configuration where the CLI can report
them.

Add three keys to [tool.holoscan]:

    docker-build-args = ["--secret", "id=token,env=GITLAB_ACCESS_TOKEN"]
    docker-run-args = ["--privileged", "--pid=host", "--ulimit", "rtprio=10"]
    forward-env = ["IS_CI_BUILD", "SCCACHE_MEMCACHED_ENDPOINT"]

The argument arrays feed the existing environment variables, so nothing
downstream changes. forward-env names host variables to pass into the container,
and only those that are actually set are forwarded. All three appear in
`holoscan version --json`, so a reader -- or an agent -- can see what a project
adds to its docker commands without reading a shell script.

This covers values that already exist in the environment. Computing a value
still needs a script; that remains outside committed configuration.

Also make schema-version optional. It was required boilerplate in every project
while the strict unknown-key check already rejects a newer schema. Declaring it
still pins the version, so an older CLI rejects schema-version 2 by name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Add layered project configuration, safe effective-value reporting, additive option resets, and the clean-SDK standalone Module workflow.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Automatically add the bundled Xvfb setup layer when container tests build, preserve the --no-xvfb opt-out, and report an actionable error when a reused image lacks xvfb-run.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Pass the actual test working directory to CTest and let the bundled driver preserve it, so standalone Modules do not configure the CLI installation directory.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Give the setup layer a valid fallback base image for BuildKit checks and report unnamed project selection as an informational repository-wide default.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Wenqi Li <wenqil@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions

Copy link
Copy Markdown

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 2 package(s) with unknown licenses.
See the Details below.

License Issues

pyproject.toml

PackageVersionLicenseIssue Type
tomli>= 2.0.1NullUnknown License

src/holoscan_cli/setup_scripts/requirements.template.txt

PackageVersionLicenseIssue Type
packaging>= 23.0NullUnknown License
Allowed Licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MIT, MPL-2.0, Python-2.0, Unlicense, 0BSD

OpenSSF Scorecard

PackageVersionScoreDetails
pip/tomli >= 2.0.1 UnknownUnknown
pip/packaging >= 23.0 UnknownUnknown

Scanned Files

  • pyproject.toml
  • src/holoscan_cli/setup_scripts/requirements.template.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant