Skip to content
Draft
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
44 changes: 37 additions & 7 deletions src/holoscan_cli/commands/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def register_package_parser(
type=str,
default="DEB",
dest="pkg_generator",
help="Comma-separated package generators: DEB, WHEEL (default: DEB)",
help="Comma-separated package generators: DEB, TGZ, WHEEL (default: DEB)",
)
parser.add_argument("--language", choices=["cpp", "python"], default=None)
parser.add_argument("--verbose", action="store_true")
Expand Down Expand Up @@ -236,6 +236,8 @@ def _package_locally(cli, args: argparse.Namespace, project_data: dict) -> None:
f"-DMODULE_{package_slug}=ON",
f"-DPKG_{package_slug}=ON",
]
if "TGZ" in cpack_generators:
cmake_args.append("-DHOLOHUB_PKG_TGZ=ON")
if shutil.which("ninja"):
cmake_args.extend(["-G", "Ninja"])
run_command(cmake_args, dry_run=dryrun, env=build_env)
Expand All @@ -252,18 +254,46 @@ def _package_locally(cli, args: argparse.Namespace, project_data: dict) -> None:
run_command(build_cmd, dry_run=dryrun, env=build_env)

pkg_config_dir = build_dir / "pkg"
cpack_configs = (
all_cpack_configs = (
list(pkg_config_dir.glob("CPackConfig-*.cmake")) if pkg_config_dir.exists() else []
)
if not cpack_configs and dryrun:
if not all_cpack_configs and dryrun:
bare = project_name.replace("_", "-")
if bare.startswith("holoscan-"):
bare = bare[len("holoscan-") :]
cpack_configs = [pkg_config_dir / f"CPackConfig-holoscan-{bare}.cmake"]
for cpack_config in cpack_configs:
for generator in cpack_generators:
base_name = f"CPackConfig-holoscan-{bare}"
all_cpack_configs = [pkg_config_dir / f"{base_name}.cmake"]
all_cpack_configs += [
pkg_config_dir / f"{base_name}-{g}.cmake" for g in cpack_generators
]
elif not all_cpack_configs:
fatal(
f"No CPack config files were generated in {pkg_config_dir}. "
"Check module packaging configuration."
)

_KNOWN_GEN_SUFFIXES = ("TGZ", "DEB", "RPM", "ZIP")
gen_specific_configs: dict = {}
base_configs = []
for c in all_cpack_configs:
stem_upper = c.stem.upper()
matched = next((g for g in _KNOWN_GEN_SUFFIXES if stem_upper.endswith(f"-{g}")), None)
if matched:
gen_specific_configs.setdefault(matched, []).append(c)
else:
base_configs.append(c)

for gen in cpack_generators:
configs_for_gen = gen_specific_configs.get(gen) or base_configs
if not configs_for_gen:
available = ", ".join(sorted(gen_specific_configs.keys())) or "none"
fatal(
f"No CPack config found for generator '{gen}' in {pkg_config_dir}. "
f"Available generator-specific configs: {available}."
)
for cpack_config in configs_for_gen:
run_command(
["cpack", "--config", str(cpack_config), "-G", generator],
["cpack", "--config", str(cpack_config), "-G", gen],
dry_run=dryrun,
env=build_env,
)
Expand Down
136 changes: 136 additions & 0 deletions tests/unit/test_package_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def test_package_deb_emits_module_cmake_flag_for_in_tree_module(tmp_path, monkey
assert "-DMODULE_test_module_fixture=ON" in cmake_args
assert "-DPKG_test_module_fixture=ON" in cmake_args
assert "-DBUILD_ALL=OFF" in cmake_args
assert "-DHOLOHUB_PKG_TGZ=ON" not in cmake_args
assert calls[2][0] == "cpack"


Expand Down Expand Up @@ -233,3 +234,138 @@ def test_resolve_module_project_falls_back_to_source_tree_when_cwd_metadata_inva
)

assert project_data["project_name"] == "test-module-fixture"


def _tgz_project_data(tmp_path):
return {
"project_name": "test-module-fixture",
"project_type": "module",
"source_folder": tmp_path / "repo" / "modules" / "test-module-fixture",
"metadata": {"language": ["C++"]},
}


def test_package_tgz_sets_cmake_flag(tmp_path, monkeypatch):
"""TGZ generator adds -DHOLOHUB_PKG_TGZ=ON to the cmake configure call."""
cli = _cli(tmp_path, _tgz_project_data(tmp_path))
calls = []
monkeypatch.setattr(package_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd))
monkeypatch.setattr(package_cmd.shutil, "which", lambda _: None)

package_cmd.handle_package(cli, _args(project="test-module-fixture", pkg_generator="TGZ"))

cmake_args = " ".join(str(a) for a in calls[0])
assert "-DHOLOHUB_PKG_TGZ=ON" in cmake_args


def test_package_tgz_invokes_cpack_tgz(tmp_path, monkeypatch):
"""TGZ generator calls cpack with -G TGZ using a generator-specific config."""
cli = _cli(tmp_path, _tgz_project_data(tmp_path))
pkg_dir = cli.DEFAULT_BUILD_PARENT_DIR / "test_module_fixture" / "package" / "pkg"
pkg_dir.mkdir(parents=True)
(pkg_dir / "CPackConfig-test-module-fixture-TGZ.cmake").touch()

calls = []
monkeypatch.setattr(package_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd))
monkeypatch.setattr(package_cmd.shutil, "which", lambda _: None)

package_cmd.handle_package(
cli, _args(project="test-module-fixture", pkg_generator="TGZ", dryrun=False)
)

assert len(calls) == 3 # cmake configure, cmake build, cpack
cmake_args = " ".join(str(a) for a in calls[0])
assert "-DHOLOHUB_PKG_TGZ=ON" in cmake_args
cpack_args = calls[2]
assert cpack_args[0] == "cpack"
assert "-G" in cpack_args
assert "TGZ" in cpack_args


def test_package_multi_generator_deb_tgz(tmp_path, monkeypatch):
"""DEB,TGZ produces two cpack calls, one per generator."""
cli = _cli(tmp_path, _tgz_project_data(tmp_path))
pkg_dir = cli.DEFAULT_BUILD_PARENT_DIR / "test_module_fixture" / "package" / "pkg"
pkg_dir.mkdir(parents=True)
(pkg_dir / "CPackConfig-test-module-fixture.cmake").touch()
(pkg_dir / "CPackConfig-test-module-fixture-TGZ.cmake").touch()

calls = []
monkeypatch.setattr(package_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd))
monkeypatch.setattr(package_cmd.shutil, "which", lambda _: None)

package_cmd.handle_package(
cli, _args(project="test-module-fixture", pkg_generator="DEB,TGZ", dryrun=False)
)

assert len(calls) == 4 # cmake configure, cmake build, cpack DEB, cpack TGZ
cmake_args = " ".join(str(a) for a in calls[0])
assert "-DHOLOHUB_PKG_TGZ=ON" in cmake_args
assert "DEB" in calls[2]
assert "TGZ" in calls[3]


def test_package_tgz_routes_to_generator_specific_config(tmp_path, monkeypatch):
"""Generator-specific config is used for TGZ; base config is used for DEB."""
cli = _cli(tmp_path, _tgz_project_data(tmp_path))
pkg_dir = cli.DEFAULT_BUILD_PARENT_DIR / "test_module_fixture" / "package" / "pkg"
pkg_dir.mkdir(parents=True)
base_cfg = pkg_dir / "CPackConfig-test-module-fixture.cmake"
tgz_cfg = pkg_dir / "CPackConfig-test-module-fixture-TGZ.cmake"
base_cfg.touch()
tgz_cfg.touch()

calls = []
monkeypatch.setattr(package_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd))
monkeypatch.setattr(package_cmd.shutil, "which", lambda _: None)

package_cmd.handle_package(
cli, _args(project="test-module-fixture", pkg_generator="DEB,TGZ", dryrun=False)
)

deb_args = " ".join(str(a) for a in calls[2])
assert str(base_cfg) in deb_args
assert str(tgz_cfg) not in deb_args

tgz_args = " ".join(str(a) for a in calls[3])
assert str(tgz_cfg) in tgz_args
assert str(base_cfg) not in tgz_args


def test_package_missing_cpack_configs_fatal(tmp_path, monkeypatch, capsys):
"""When the build produces no CPack configs, fatal is called with a clear message."""
cli = _cli(tmp_path, _tgz_project_data(tmp_path))
pkg_dir = cli.DEFAULT_BUILD_PARENT_DIR / "test_module_fixture" / "package" / "pkg"
pkg_dir.mkdir(parents=True)

monkeypatch.setattr(package_cmd, "run_command", lambda cmd, **kwargs: None)
monkeypatch.setattr(package_cmd.shutil, "which", lambda _: None)

with pytest.raises(SystemExit) as excinfo:
package_cmd.handle_package(
cli, _args(project="test-module-fixture", pkg_generator="TGZ", dryrun=False)
)

assert excinfo.value.code == 1
assert "No CPack config files" in capsys.readouterr().err


def test_package_missing_generator_config_fatal(tmp_path, monkeypatch, capsys):
"""When no config exists for the requested generator, fatal lists available generators."""
cli = _cli(tmp_path, _tgz_project_data(tmp_path))
pkg_dir = cli.DEFAULT_BUILD_PARENT_DIR / "test_module_fixture" / "package" / "pkg"
pkg_dir.mkdir(parents=True)
(pkg_dir / "CPackConfig-test-module-fixture-DEB.cmake").touch()

monkeypatch.setattr(package_cmd, "run_command", lambda cmd, **kwargs: None)
monkeypatch.setattr(package_cmd.shutil, "which", lambda _: None)

with pytest.raises(SystemExit) as excinfo:
package_cmd.handle_package(
cli, _args(project="test-module-fixture", pkg_generator="TGZ", dryrun=False)
)

assert excinfo.value.code == 1
err = capsys.readouterr().err
assert "TGZ" in err
assert "DEB" in err
Loading