diff --git a/src/holoscan_cli/commands/install.py b/src/holoscan_cli/commands/install.py index ca49174..a514e11 100644 --- a/src/holoscan_cli/commands/install.py +++ b/src/holoscan_cli/commands/install.py @@ -102,6 +102,14 @@ def register_install_parser( help="Additional configuration arguments for cmake " "example: --configure-args='-DCUSTOM_OPTION=ON' --configure-args='-Dtest=ON'", ) + parser.add_argument( + "--install-args", + action="append", + help="Additional arguments for `cmake --install`, applied at install time so the " + "build tree's CMake cache is left untouched " + "example: --install-args='--prefix /opt/holohub' --install-args=--strip. " + "When installation runs in a container, paths are resolved inside the container.", + ) parser.set_defaults(func=lambda args: handle_install(cli, args)) return parser @@ -174,7 +182,10 @@ def handle_install(cli, args: argparse.Namespace) -> None: ) # Install the project - run_command(["cmake", "--install", str(build_dir)], dry_run=args.dryrun, env=install_env) + cmake_install_cmd = ["cmake", "--install", str(build_dir)] + for install_arg in getattr(args, "install_args", None) or []: + cmake_install_cmd += shlex.split(os.path.expandvars(install_arg)) + run_command(cmake_install_cmd, dry_run=args.dryrun, env=install_env) if not args.dryrun: print(f"{Color.green('Successfully installed')} {args.project}") else: @@ -213,6 +224,8 @@ def handle_install(cli, args: argparse.Namespace) -> None: if getattr(args, "configure_args", None): for configure_arg in args.configure_args: install_cmd += f" --configure-args={shlex.quote(configure_arg)}" + for install_arg in getattr(args, "install_args", None) or []: + install_cmd += f" --install-args={shlex.quote(install_arg)}" img = getattr(args, "img", None) or container.image_name docker_opts = build_args.get("docker_opts", "") diff --git a/tests/unit/test_lifecycle_commands.py b/tests/unit/test_lifecycle_commands.py index f88a87b..2b3ac55 100644 --- a/tests/unit/test_lifecycle_commands.py +++ b/tests/unit/test_lifecycle_commands.py @@ -143,6 +143,7 @@ def _project_args(**overrides): "uninstall": False, "build_dir": None, "site_dir": None, + "install_args": None, } ) defaults.update(overrides) @@ -504,6 +505,33 @@ def test_handle_install_local_installs_built_project(tmp_path, monkeypatch): assert calls == [["cmake", "--install", str(build_dir)]] +def test_handle_install_local_forwards_install_args_at_install_time(tmp_path, monkeypatch): + """`--install-args` reaches `cmake --install` without touching the configure step.""" + cli = RecordingCLI(tmp_path) + build_dir = tmp_path / "build" / "smoke_app" + calls = [] + build_kwargs = {} + + def record_build(*args, **kwargs): + build_kwargs.update(kwargs) + return build_dir, cli.project_data + + monkeypatch.setattr(install_cmd, "build_project_locally", record_build) + monkeypatch.setattr(install_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd)) + + install_cmd.handle_install( + cli, + _project_args( + local=True, + install_args=["--prefix /opt/holohub", "--strip"], + ), + ) + + # Each value is split into cmake tokens; nothing leaks into the configure step. + assert calls == [["cmake", "--install", str(build_dir), "--prefix", "/opt/holohub", "--strip"]] + assert build_kwargs["configure_args"] is None + + def test_handle_install_container_branch_passes_recursive_local_command(tmp_path, monkeypatch): cli = RecordingCLI(tmp_path) captured = {} @@ -522,6 +550,7 @@ def capture_entrypoint(img, cmd, opts, dry_run=False): with_operators="op_a", parallel="4", configure_args=["-DDEV=ON"], + install_args=["--prefix /opt/smoke app", "--component dev"], docker_opts="--ipc=host", verbose=True, ), @@ -537,6 +566,8 @@ def capture_entrypoint(img, cmd, opts, dry_run=False): assert '--build-with "op_a"' in command assert "--parallel 4" in command assert "--configure-args=-DDEV=ON" in command + assert "--install-args='--prefix /opt/smoke app'" in command + assert "--install-args='--component dev'" in command assert cli.container.run_calls[0]["extra_args"] == ["-c", command]