Skip to content
Open
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
36 changes: 29 additions & 7 deletions src/holoscan_cli/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def make_local_build_command(
command += f" --parallel {args.parallel}"
if args.verbose:
command += " --verbose"
if getattr(args, "configure_only", False):
command += " --configure-only"
if getattr(args, "benchmark", False):
command += " --benchmark"
for configure_arg in getattr(args, "configure_args", None) or []:
Expand Down Expand Up @@ -104,6 +106,12 @@ def register_build_parser(
dest="with_operators",
help="Optional operators that should be built, separated by semicolons (;)",
)
parser.add_argument(
"--configure-only",
action="store_true",
help="Stop after the CMake configure step instead of compiling. Validates that a "
"project configures without paying for a full build",
)
parser.add_argument(
"--dryrun", action="store_true", help="Print commands without executing them"
)
Expand Down Expand Up @@ -176,6 +184,7 @@ def handle_build(cli, args: argparse.Namespace) -> None:
benchmark=getattr(args, "benchmark", False),
configure_args=build_args.get("configure_args"),
extra_env=build_mode_env,
configure_only=getattr(args, "configure_only", False),
)
else:
# Build in container
Expand Down Expand Up @@ -230,6 +239,15 @@ def handle_build(cli, args: argparse.Namespace) -> None:
)


def _restore_benchmark_patch(cli, app_source_path, project_type: str, dryrun: bool) -> None:
"""Revert the flow-benchmarking patch applied before configure, if there was one."""
if app_source_path and project_type in ["application", "benchmark"]:
restore_script = (
cli.HOLOHUB_ROOT / "benchmarks/holoscan_flow_benchmarking/restore_application.sh"
)
run_command([str(restore_script), str(app_source_path)], dry_run=dryrun)


def build_project_locally(
cli,
project_name: str,
Expand All @@ -243,8 +261,13 @@ def build_project_locally(
benchmark: bool = False,
configure_args: Optional[list[str]] = None,
extra_env: Optional[dict] = None,
configure_only: bool = False,
) -> tuple[Path, dict]:
"""Helper to build a project locally (cmake + cmake --build)."""
"""Helper to build a project locally (cmake + cmake --build).

With *configure_only*, stop after the configure step. Any benchmark patch applied
beforehand is still reverted, so the source tree is left as it was found.
"""
Comment thread
wyli marked this conversation as resolved.
project_data = cli.find_project(project_name=project_name, language=language)
project_type = project_data.get("project_type", "application")

Expand Down Expand Up @@ -396,6 +419,10 @@ def build_project_locally(

run_command(cmake_args, dry_run=dryrun, env=build_env)

if configure_only:
_restore_benchmark_patch(cli, app_source_path, project_type, dryrun)
return build_dir, project_data

# Build the project with optional parallel jobs
build_cmd = ["cmake", "--build", str(build_dir), "--config", build_type]
# Determine the number of parallel jobs (user input > env var > CPU count):
Expand Down Expand Up @@ -439,11 +466,6 @@ def build_project_locally(
env=build_env,
)

# Handle benchmark restoration after building
if benchmark and app_source_path and project_type in ["application", "benchmark"]:
restore_script = (
cli.HOLOHUB_ROOT / "benchmarks/holoscan_flow_benchmarking/restore_application.sh"
)
run_command([str(restore_script), str(app_source_path)], dry_run=dryrun)
_restore_benchmark_patch(cli, app_source_path, project_type, dryrun)

return build_dir, project_data
60 changes: 60 additions & 0 deletions tests/unit/test_lifecycle_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,63 @@ def test_handle_test_local_runs_ctest_in_repo_with_environment(tmp_path, monkeyp
assert "-S local.ctest" in command[2]
assert kwargs["dry_run"] is True
assert str(cli.HOLOHUB_ROOT) in kwargs["env"]["PYTHONPATH"]


def test_build_project_locally_configure_only_skips_the_compile(tmp_path, monkeypatch):
cli = RecordingCLI(tmp_path)
calls = []
monkeypatch.setattr(build_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd))
monkeypatch.setattr(build_cmd.shutil, "which", lambda name: None)

build_dir, _ = build_cmd.build_project_locally(
cli,
"smoke_app",
dryrun=True,
configure_only=True,
)

assert len(calls) == 1, f"expected only the configure step, got {calls}"
assert calls[0][0] == "cmake"
assert "--build" not in calls[0]
assert build_dir == tmp_path / "build" / "smoke_app"


def test_build_project_locally_configure_only_still_restores_benchmark_patch(tmp_path, monkeypatch):
"""The patch is applied before configure, so stopping early must not leave it behind."""
cli = RecordingCLI(tmp_path)
calls = []
monkeypatch.setattr(build_cmd, "run_command", lambda cmd, **kwargs: calls.append(cmd))
monkeypatch.setattr(build_cmd.shutil, "which", lambda name: None)

build_cmd.build_project_locally(
cli,
"smoke_app",
dryrun=True,
benchmark=True,
configure_only=True,
)

rendered = [" ".join(str(part) for part in cmd) for cmd in calls]
assert any("patch_application.sh" in cmd for cmd in rendered)
assert any("restore_application.sh" in cmd for cmd in rendered)
assert not any("--build" in cmd for cmd in rendered)


def test_make_local_build_command_forwards_configure_only():
args = Namespace(
project="smoke_app",
mode=None,
build_type=None,
with_operators=None,
pkg_generator=None,
parallel=None,
verbose=False,
configure_only=True,
benchmark=False,
configure_args=None,
)

command = build_cmd.make_local_build_command("holoscan", args, None, None)

assert "--configure-only" in command
assert command.startswith("holoscan build smoke_app --local")
Loading