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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ if(HOLOHUB_BUILD_PYTHON)
install(
DIRECTORY "${HOLOHUB_PYTHON_MODULE_OUT_DIR}"
DESTINATION "${_holohub_py_dest}"
COMPONENT holohub-python
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
PATTERN "__pycache__" EXCLUDE
Expand Down
144 changes: 144 additions & 0 deletions cmake/modules/holohub_configure_tgz.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# holohub_configure_tgz(NAME <name> VERSION <version>
# [EXPORT_NAME <export>]
# [COMPONENTS <comp> ...])
#
# Generates a CPack TGZ configuration file at
# ${CMAKE_BINARY_DIR}/pkg/CPackConfig-<name>-TGZ.cmake
#
# The resulting tarball follows the KitMaker multi-variant naming convention:
# <name_underscored>-<os>-<arch>-<version>.tar.gz
# with archive root <name_underscored>/ and library variant subdirectory
# lib/<cuda_major>/ (e.g. lib/13/) per the multi-variant layout.
#
# EXPORT_NAME: when provided, generates and installs cmake config/version files
# for the named export target, mirroring holohub_configure_deb behaviour.
#
# COMPONENTS: when provided, only those cmake install components (plus the
# cmake export component, if EXPORT_NAME is set) are included in the archive.
# When omitted, CMAKE_INSTALL_DEFAULT_COMPONENT_NAME is used instead.
function(holohub_configure_tgz)
set(requiredArgs NAME VERSION)
set(oneValueArgs NAME VERSION EXPORT_NAME)
set(multiValueArgs COMPONENTS)
cmake_parse_arguments(ARG "" "${oneValueArgs}" "${multiValueArgs}" ${ARGV})

# KitMaker multi-variant layout requires libraries under lib/<cuda_major>/.
# Override CMAKE_INSTALL_LIBDIR in the cache so operator install() rules —
# processed after modules in HoloHub's build order — use the versioned path.
# Only active when the CLI sets HOLOHUB_PKG_TGZ=ON (i.e. --pkg-generator TGZ);
# all other builds (normal installs, DEB, wheel) keep the default lib/ path.
if(HOLOHUB_PKG_TGZ)
find_package(CUDAToolkit QUIET)
if(CUDAToolkit_FOUND)
set(CMAKE_INSTALL_LIBDIR "lib/${CUDAToolkit_VERSION_MAJOR}"
CACHE STRING "Library install directory (KitMaker multi-variant layout)" FORCE)
endif()
Comment on lines +43 to +50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cache modification runs before argument validation

The CMAKE_INSTALL_LIBDIR cache override with FORCE executes unconditionally (when HOLOHUB_PKG_TGZ is set) before the required-argument check that follows. If a caller omits NAME or VERSION, the global cache is already mutated before message(FATAL_ERROR) fires. While FATAL_ERROR prevents the build from proceeding, a partially modified cache can cause surprising behavior on the next reconfigure attempt. Consider moving argument validation before any side-effecting cache writes.

endif()

foreach(arg ${requiredArgs})
if(NOT ARG_${arg})
list(APPEND _missing ${arg})
endif()
endforeach()
if(_missing)
message(FATAL_ERROR "holohub_configure_tgz: missing required arguments: ${_missing}")
endif()

if(ARG_EXPORT_NAME)
set(config_install_dir "lib/cmake/${ARG_NAME}")
set(export_component ${ARG_NAME}-cmake)
install(
EXPORT ${ARG_EXPORT_NAME}
DESTINATION ${config_install_dir}
NAMESPACE holoscan::
COMPONENT ${export_component}
)
include(CMakePackageConfigHelpers)
configure_package_config_file("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}Config.cmake"
INSTALL_DESTINATION ${config_install_dir}
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}ConfigVersion.cmake"
VERSION "${ARG_VERSION}"
COMPATIBILITY AnyNewerVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}ConfigVersion.cmake
DESTINATION ${config_install_dir}
COMPONENT ${export_component}
)
endif()
Comment on lines +68 to +89

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 EXPORT_NAME cmake config path diverges from CMAKE_INSTALL_LIBDIR

When HOLOHUB_PKG_TGZ=ON, the CMAKE_INSTALL_LIBDIR override runs at the top of the function and sets it to lib/<cuda_major>/. The EXPORT_NAME block then hardcodes config_install_dir to lib/cmake/${ARG_NAME}, which no longer matches ${CMAKE_INSTALL_LIBDIR}/cmake/${ARG_NAME}. Any downstream consumer using find_package() against the TGZ tree would fail because the library is at lib/13/ but the config is at lib/cmake/. Consider using ${CMAKE_INSTALL_LIBDIR}/cmake/${ARG_NAME} for config_install_dir when in TGZ mode.


if(ARG_COMPONENTS)
set(_components "${ARG_COMPONENTS}")
else()
# Fall back to the cmake default component name. The caller is responsible
# for setting CMAKE_INSTALL_DEFAULT_COMPONENT_NAME before any install()
# rules so that unqualified installs land in the intended component.
set(_components "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}")
endif()
if(ARG_EXPORT_NAME)
list(APPEND _components "${export_component}")
endif()

# Filter to only the desired components by enumerating them in
# CPACK_INSTALL_CMAKE_PROJECTS. CPack.cmake only sets this variable when it
# is unset, so our value is preserved through include(CPack). This approach
# avoids CPACK_ARCHIVE_COMPONENT_INSTALL, which strips the root directory
# from the archive when used with the TGZ generator.
set(_install_projects "")
foreach(_comp IN LISTS _components)
list(APPEND _install_projects
"${CMAKE_BINARY_DIR}" "${PROJECT_NAME}" "${_comp}" "/")
endforeach()
set(CPACK_INSTALL_CMAKE_PROJECTS "${_install_projects}")

# KitMaker requires underscores in the component name (no hyphens).
string(REPLACE "-" "_" _name_us "${ARG_NAME}")

# KitMaker requires lowercase OS token.
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _os)

# Arch token comes from CMAKE_SYSTEM_PROCESSOR (x86_64, aarch64, sbsa, etc.).
set(_arch "${CMAKE_SYSTEM_PROCESSOR}")

# KitMaker naming: <name>-<os>-<arch>-<version>
set(CPACK_PACKAGE_FILE_NAME "${_name_us}-${_os}-${_arch}-${ARG_VERSION}")

# The archive root directory is the component name alone (KitMaker multi-variant
# convention). Inject it as the install prefix so files land at
# <component_name>/lib/..., <component_name>/include/..., etc.
# CPACK_INCLUDE_TOPLEVEL_DIRECTORY is disabled so CPack does not also prepend
# the full package-file-name stem, which would double-nest the root directory.
set(CPACK_PACKAGING_INSTALL_PREFIX "/${_name_us}")
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)

set(CPACK_GENERATOR "TGZ")
set(CPACK_STRIP_FILES TRUE)

set(CPACK_OUTPUT_CONFIG_FILE
"${CMAKE_BINARY_DIR}/pkg/CPackConfig-${ARG_NAME}-TGZ.cmake")
set(CPACK_SOURCE_OUTPUT_CONFIG_FILE
"${CMAKE_BINARY_DIR}/pkg/CPackSourceConfig-${ARG_NAME}-TGZ.cmake")

include(CPack)
endfunction()
15 changes: 15 additions & 0 deletions modules/holoscan-gstreamer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License.

include(holohub_configure_deb)
include(holohub_configure_tgz)

# Debian packaging
set(_deb_depends
Expand All @@ -37,8 +38,22 @@ holohub_configure_deb(
DEPENDS "${_deb_depends_str}"
)

holohub_configure_tgz(
NAME holoscan-gstreamer
VERSION 1.0.0
COMPONENTS holoscan-gstreamer
)

set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME holoscan-gstreamer)

install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/NOTICE.md"
DESTINATION "share/doc/holoscan-gstreamer"
COMPONENT holoscan-gstreamer
)

install(FILES "${CMAKE_SOURCE_DIR}/LICENSE"
DESTINATION "."
COMPONENT holoscan-gstreamer
)

# Wheel packaging: see pyproject.toml
14 changes: 7 additions & 7 deletions operators/gstreamer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ endif()
install(TARGETS holoscan_gstreamer_bridge
EXPORT holoscan-gstreamer-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT gstreamer
COMPONENT holoscan-gstreamer
)
# Write the holoscan-gstreamer-targets.cmake file
install(EXPORT holoscan-gstreamer-targets
NAMESPACE holoscan::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/holoscan-gstreamer
COMPONENT gstreamer
COMPONENT holoscan-gstreamer
)

# Generate holoscan-gstreamer-config.cmake and version file so that downstream
Expand All @@ -156,7 +156,7 @@ install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/cmake/holoscan-gstreamer-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/holoscan-gstreamer-config-version.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/holoscan-gstreamer"
COMPONENT gstreamer
COMPONENT holoscan-gstreamer
)

# Install headers. gst/ headers go into the gst/ subdestination so that the
Expand All @@ -174,7 +174,7 @@ install(FILES
gst_pipeline_bus_monitor.hpp
gst_wait_group.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/holoscan/operators/gstreamer
COMPONENT gstreamer)
COMPONENT holoscan-gstreamer)

install(FILES
gst/allocator.hpp
Expand All @@ -197,7 +197,7 @@ install(FILES
gst/video_info.hpp
gst/wrapper_base.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/holoscan/operators/gstreamer/gst
COMPONENT gstreamer)
COMPONENT holoscan-gstreamer)

# CTest: install the gstreamer component into a temp prefix, then verify
# find_package(holoscan-gstreamer) resolves correctly against the install tree.
Expand All @@ -206,13 +206,13 @@ if(BUILD_TESTING)

set(_gst_test_prefix "${CMAKE_CURRENT_BINARY_DIR}/tests/cmake/install_prefix")

# Fixture: cmake --install with --component gstreamer so only this operator's
# Fixture: cmake --install with --component holoscan-gstreamer so only this operator's
# artifacts land in the temp prefix (safe in standalone and superbuild contexts).
add_test(
NAME holoscan_gstreamer_install
COMMAND "${CMAKE_COMMAND}"
--install "${CMAKE_BINARY_DIR}"
--component gstreamer
--component holoscan-gstreamer
--prefix "${_gst_test_prefix}"
)
set_tests_properties(holoscan_gstreamer_install PROPERTIES
Expand Down
47 changes: 41 additions & 6 deletions utilities/cli/holohub.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ def _create_parser(self) -> argparse.ArgumentParser:
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)",
)
package.add_argument("--language", choices=["cpp", "python"], default=None)
package.add_argument("--verbose", action="store_true")
Expand Down Expand Up @@ -2284,6 +2284,8 @@ def handle_package(self, args: argparse.Namespace) -> None:
f"-DMODULE_{pkg_slug}=ON",
f"-DPKG_{pkg_slug}=ON",
]
if "TGZ" in cpack_generators:
cmake_args.append("-DHOLOHUB_PKG_TGZ=ON")
Comment on lines +2287 to +2288

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 DEB package gets wrong lib path when combined with TGZ

-DHOLOHUB_PKG_TGZ=ON is appended whenever TGZ is among the requested generators, which causes holohub_configure_tgz to force CMAKE_INSTALL_LIBDIR to lib/<cuda_major>/ for the entire configure step. All install() rules using ${CMAKE_INSTALL_LIBDIR} — including those for the DEB component — then install the library to lib/13/ instead of the expected lib/. Running --pkg-generator DEB,TGZ therefore silently produces a malformed Debian package with the wrong library path.

Comment on lines +2287 to +2288

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Split TGZ and non-TGZ configure paths to avoid mutating DEB layout

Line 2287 enables HOLOHUB_PKG_TGZ for the whole configure when --pkg-generator DEB,TGZ is used. Because TGZ mode forces CMAKE_INSTALL_LIBDIR globally during configure, the DEB package from the same build can inherit TGZ-specific install paths. This breaks generator isolation.

Suggested fix direction
- if "TGZ" in cpack_generators:
-     cmake_args.append("-DHOLOHUB_PKG_TGZ=ON")
- holohub_cli_util.run_command(cmake_args, ...)
- holohub_cli_util.run_command(build_cmd, ...)
- # then cpack for all generators
+ # configure/build per generator class to isolate install layout
+ # (e.g., one build dir with HOLOHUB_PKG_TGZ=ON for TGZ, another without it for DEB/RPM/ZIP)
+ # then run cpack against each generator’s corresponding config set
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utilities/cli/holohub.py` around lines 2287 - 2288, The current logic sets
HOLOHUB_PKG_TGZ in the global cmake_args when "TGZ" is present in
cpack_generators causing TGZ-specific CMAKE_INSTALL_LIBDIR to leak into DEB
builds; instead, detect TGZ in cpack_generators and run a separate
configure/generate/install flow for TGZ without mutating the shared cmake_args
used for DEB (i.e., do not append "-DHOLOHUB_PKG_TGZ=ON" to the global
cmake_args); update holohub.py to keep cmake_args immutable for non-TGZ
generators and create a copy or new cmake_args_tgz with "-DHOLOHUB_PKG_TGZ=ON"
only when invoking the TGZ configure/generate steps so CMAKE_INSTALL_LIBDIR
remains correct for DEB builds.

if shutil.which("ninja"):
cmake_args.extend(["-G", "Ninja"])
holohub_cli_util.run_command(cmake_args, dry_run=dryrun, env=build_env)
Expand All @@ -2300,18 +2302,51 @@ def handle_package(self, args: argparse.Namespace) -> None:
holohub_cli_util.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 gen in cpack_generators:
base_name = f"CPackConfig-holoscan-{bare}"
all_cpack_configs = [pkg_config_dir / f"{base_name}.cmake"]
# Also synthesize generator-specific configs so dry-run routing matches reality.
all_cpack_configs += [
pkg_config_dir / f"{base_name}-{g}.cmake" for g in cpack_generators
]
elif not all_cpack_configs:
holohub_cli_util.fatal(
f"No CPack config files were generated in {pkg_config_dir}. "
"Check module packaging configuration."
)

# Separate generator-specific configs (e.g. CPackConfig-*-TGZ.cmake)
# from the base config so each generator uses the right one.
_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"
holohub_cli_util.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:
holohub_cli_util.run_command(
["cpack", "--config", str(cpack_config), "-G", gen],
dry_run=dryrun,
Expand Down
Loading
Loading