Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/workflows/ubuntu-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ jobs:
working-directory: build
run: ctest --output-on-failure

- name: Test embedding as a subproject
run: |
cmake -S cloudini_lib/test/subproject -B "$RUNNER_TEMP/subproject"
cmake --build "$RUNNER_TEMP/subproject" -j$(nproc)
"$RUNNER_TEMP/subproject/consumer"
cmake --install "$RUNNER_TEMP/subproject" --prefix "$RUNNER_TEMP/subproject-prefix"
if find "$RUNNER_TEMP/subproject-prefix" -iname '*cloudini*' | grep -q .; then
echo "embedded Cloudini added install rules to the parent project"; exit 1
fi

- name: Test installed static and shared packages
shell: bash
run: |
Expand Down
12 changes: 12 additions & 0 deletions cloudini_lib/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
Changelog for package cloudini_lib
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1.3.1 (2026-09-20)
------------------
* build: make cloudini_lib embeddable with add_subdirectory / FetchContent (`#142 <https://github.com/facontidavide/cloudini/issues/142>`_)
Reuses zstd/lz4 targets defined by a parent project, ignores an inherited ament_cmake,
and keeps the build type, tests, tools, benchmarks, PCL and install rules out of the
parent. New options CLOUDINI_WITH_PCL and CLOUDINI_INSTALL; cloudini::cloudini_lib is
always defined.
* fix: link Threads::Threads (static consumers failed with undefined pthread_create)
* fix: reject inconsistent point clouds; make ros_message_definitions.hpp includable twice (`#141 <https://github.com/facontidavide/cloudini/issues/141>`_)
* The CMake project version now matches package.xml (it was left at 1.2.4 in 1.3.0)
* Contributors: Davide Faconti

1.3.0 (2026-09-20)
------------------
* feat(packaging): conda/pixi package for prefix.dev + conda-forge (`#133 <https://github.com/facontidavide/cloudini/issues/133>`_)
Expand Down
173 changes: 103 additions & 70 deletions cloudini_lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
cmake_minimum_required(VERSION 3.16)
# NOTE: keep the default languages (C and CXX). The vendored zstd/lz4 fallback
# compiles .c sources, so restricting to CXX only would break standalone builds.
project(cloudini_lib VERSION 1.2.4)
project(cloudini_lib VERSION 1.3.1)

# When another project embeds this one (add_subdirectory / FetchContent), build
# only the library: no forced build type, no ament packaging, no tests, tools,
# benchmarks, PCL or install rules unless the parent asks for them.
# (PROJECT_IS_TOP_LEVEL needs CMake 3.21.)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(CLOUDINI_IS_TOP_LEVEL ON)
else()
set(CLOUDINI_IS_TOP_LEVEL OFF)
endif()

# Make installed executables relocatable: find the co-installed shared library
# through an $ORIGIN-relative RPATH (bin/ -> ../lib). conda/conda-forge tooling
Expand All @@ -13,15 +23,17 @@ else()
endif()

# If not specified, set the default build type to Release
if (NOT CMAKE_BUILD_TYPE)
if (CLOUDINI_IS_TOP_LEVEL AND NOT CMAKE_BUILD_TYPE)
message(STATUS "No build type selected, default to Release")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel")
endif()
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")

option(CLOUDINI_BUILD_TOOLS "Build Tools" ON)
option(CLOUDINI_BUILD_TOOLS "Build Tools" ${CLOUDINI_IS_TOP_LEVEL})
option(CLOUDINI_FORCE_VENDORED_DEPS "Force vendored dependencies" ON)
option(CLOUDINI_WITH_PCL "Build the PCL conversions when PCL is found" ${CLOUDINI_IS_TOP_LEVEL})
option(CLOUDINI_INSTALL "Generate install rules" ${CLOUDINI_IS_TOP_LEVEL})


# create compile_commands.json
Expand All @@ -33,7 +45,12 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# add cmake folder to path
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

find_package(ament_cmake QUIET)
if(CLOUDINI_IS_TOP_LEVEL)
find_package(ament_cmake QUIET)
else()
# A ROS parent has found ament_cmake already; embedded, we are not a package.
set(ament_cmake_FOUND FALSE)
endif()
if(ament_cmake_FOUND)
message(STATUS "Found ament_cmake")
set(CLOUDINI_FORCE_VENDORED_DEPS OFF)
Expand All @@ -52,13 +69,15 @@ find_or_download_lz4(${CLOUDINI_FORCE_VENDORED_DEPS})

# ---- Trick to pass an absolute path to C++ code ----
get_filename_component(DATA_PATH "${CMAKE_CURRENT_LIST_DIR}/samples" ABSOLUTE)
configure_file (data_path.hpp.in "${CMAKE_BINARY_DIR}/include/data_path.hpp" @ONLY)
configure_file (data_path.hpp.in "${CMAKE_CURRENT_BINARY_DIR}/include/data_path.hpp" @ONLY)
add_library(data_path INTERFACE IMPORTED)
target_include_directories(data_path INTERFACE ${CMAKE_BINARY_DIR}/include)
target_include_directories(data_path INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/include)

##################################################################################

find_package(PCL COMPONENTS common io QUIET)
if(CLOUDINI_WITH_PCL)
find_package(PCL COMPONENTS common io QUIET)
endif()

if(PCL_FOUND)
message(STATUS "PCL found in system")
Expand All @@ -79,7 +98,7 @@ endif()
# endif()
# endif()

option(CLOUDINI_BUILD_BENCHMARKS "Build benchmarks" ON)
option(CLOUDINI_BUILD_BENCHMARKS "Build benchmarks" ${CLOUDINI_IS_TOP_LEVEL})

# Use SHARED library when building with ament, STATIC otherwise
SET(CLOUDINI_SRC
Expand Down Expand Up @@ -107,6 +126,10 @@ else()
)
endif()

# Same name as the installed (non-ament) target, so consumers link
# cloudini::cloudini_lib whether Cloudini is installed or embedded.
add_library(cloudini::cloudini_lib ALIAS cloudini_lib)

target_compile_features(cloudini_lib PUBLIC cxx_std_20)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
Expand Down Expand Up @@ -136,11 +159,17 @@ else()
set(CLOUDINI_LZ4_TARGET LZ4::lz4_static)
endif()

# PointcloudEncoder uses std::thread. PUBLIC: a static cloudini_lib leaves the
# pthread references for whoever links the final binary.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)

target_link_libraries(cloudini_lib
PRIVATE
$<BUILD_INTERFACE:${CLOUDINI_LZ4_TARGET}>
$<BUILD_INTERFACE:${CLOUDINI_ZSTD_TARGET}>
PUBLIC
Threads::Threads
${PCL_COMMON_LIBRARIES}
${PCL_IO_LIBRARIES}
)
Expand All @@ -157,10 +186,12 @@ if(NOT EMSCRIPTEN)
add_subdirectory(benchmarks)
endif()

include(CTest)
message( STATUS "BUILD_TESTING: ${BUILD_TESTING} " )
if (BUILD_TESTING)
add_subdirectory(test)
if(CLOUDINI_IS_TOP_LEVEL) # include(CTest) would define BUILD_TESTING in the parent's cache
include(CTest)
message( STATUS "BUILD_TESTING: ${BUILD_TESTING} " )
if (BUILD_TESTING)
add_subdirectory(test)
endif()
endif()

else()
Expand Down Expand Up @@ -215,69 +246,71 @@ if(CLOUDINI_BUILD_TOOLS AND NOT ament_cmake_FOUND)
endif()
endif()

INSTALL(TARGETS cloudini_lib
EXPORT cloudini_libTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
if(CLOUDINI_INSTALL)
INSTALL(TARGETS cloudini_lib
EXPORT cloudini_libTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)

INSTALL( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION include
FILES_MATCHING PATTERN "*.h*")
INSTALL( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION include
FILES_MATCHING PATTERN "*.h*")

if(ament_cmake_FOUND)
ament_export_targets(cloudini_libTargets HAS_LIBRARY_TARGET)
ament_export_dependencies(PCL)
ament_package()
else()
# Standalone (non-ament) install: generate a CMake package config so downstream
# projects can do find_package(cloudini_lib) and link cloudini::cloudini_lib.
# Static archives retain compressor references: ship vendored archives or
# rediscover system libraries in the installed package config.
include(CMakePackageConfigHelpers)

set(CLOUDINI_SYSTEM_LIBRARIES "")
if(_cloudini_lib_type STREQUAL "STATIC_LIBRARY")
foreach(_dependency IN ITEMS lz4 zstd)
set(_vendored_target "${_dependency}_static")
if(_dependency STREQUAL "zstd")
set(_vendored_target libzstd_static)
endif()
if(TARGET ${_vendored_target})
set_target_properties(${_vendored_target} PROPERTIES OUTPUT_NAME cloudini_${_dependency})
install(TARGETS ${_vendored_target} EXPORT cloudini_libTargets ARCHIVE DESTINATION lib)
target_link_libraries(cloudini_lib INTERFACE
$<INSTALL_INTERFACE:$<LINK_ONLY:cloudini::${_vendored_target}>>)
else()
list(APPEND CLOUDINI_SYSTEM_LIBRARIES ${_dependency})
endif()
endforeach()
endif()
if(ament_cmake_FOUND)
ament_export_targets(cloudini_libTargets HAS_LIBRARY_TARGET)
ament_export_dependencies(PCL Threads)
ament_package()
else()
# Standalone (non-ament) install: generate a CMake package config so downstream
# projects can do find_package(cloudini_lib) and link cloudini::cloudini_lib.
# Static archives retain compressor references: ship vendored archives or
# rediscover system libraries in the installed package config.
include(CMakePackageConfigHelpers)

set(CLOUDINI_SYSTEM_LIBRARIES "")
if(_cloudini_lib_type STREQUAL "STATIC_LIBRARY")
foreach(_dependency IN ITEMS lz4 zstd)
set(_vendored_target "${_dependency}_static")
if(_dependency STREQUAL "zstd")
set(_vendored_target libzstd_static)
endif()
if(TARGET ${_vendored_target})
set_target_properties(${_vendored_target} PROPERTIES OUTPUT_NAME cloudini_${_dependency})
install(TARGETS ${_vendored_target} EXPORT cloudini_libTargets ARCHIVE DESTINATION lib)
target_link_libraries(cloudini_lib INTERFACE
$<INSTALL_INTERFACE:$<LINK_ONLY:cloudini::${_vendored_target}>>)
else()
list(APPEND CLOUDINI_SYSTEM_LIBRARIES ${_dependency})
endif()
endforeach()
endif()

install(EXPORT cloudini_libTargets
FILE cloudini_libTargets.cmake
NAMESPACE cloudini::
DESTINATION lib/cmake/cloudini_lib)
install(EXPORT cloudini_libTargets
FILE cloudini_libTargets.cmake
NAMESPACE cloudini::
DESTINATION lib/cmake/cloudini_lib)

set(CLOUDINI_HAS_PCL 0)
if(PCL_FOUND)
set(CLOUDINI_HAS_PCL 1)
endif()
set(CLOUDINI_HAS_PCL 0)
if(PCL_FOUND)
set(CLOUDINI_HAS_PCL 1)
endif()

configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/cloudini_libConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfig.cmake
INSTALL_DESTINATION lib/cmake/cloudini_lib)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/cloudini_libConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfig.cmake
INSTALL_DESTINATION lib/cmake/cloudini_lib)

write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)

install(FILES
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfigVersion.cmake
DESTINATION lib/cmake/cloudini_lib)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/cloudini_libConfigVersion.cmake
DESTINATION lib/cmake/cloudini_lib)
endif()
endif()
3 changes: 3 additions & 0 deletions cloudini_lib/cmake/cloudini_libConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

include(CMakeFindDependencyMacro)

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_dependency(Threads)

# PCL only leaks into the interface when cloudini_lib was built with PCL support.
if(@CLOUDINI_HAS_PCL@)
find_dependency(PCL COMPONENTS common io)
Expand Down
13 changes: 10 additions & 3 deletions cloudini_lib/cmake/find_or_download_lz4.cmake
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
function(find_or_download_lz4 FORCE_VENDORED)

if(NOT FORCE_VENDORED)
find_package(lz4 CONFIG QUIET)
if(NOT TARGET LZ4::lz4_static)
find_package(LZ4 QUIET)
# Same as for zstd: reuse targets a parent project already defined.
if(NOT TARGET LZ4::lz4_static AND NOT TARGET LZ4::lz4_shared)
find_package(lz4 CONFIG QUIET)
if(NOT TARGET LZ4::lz4_static)
find_package(LZ4 QUIET)
endif()
endif()
if(NOT TARGET LZ4::lz4_static AND TARGET LZ4::lz4_shared)
add_library(LZ4::lz4_static INTERFACE IMPORTED)
set_target_properties(LZ4::lz4_static PROPERTIES INTERFACE_LINK_LIBRARIES LZ4::lz4_shared)
endif()
# Fallback for systems without cmake config (e.g. Ubuntu Jammy liblz4-dev)
if(NOT TARGET LZ4::lz4_static)
Expand Down
11 changes: 8 additions & 3 deletions cloudini_lib/cmake/find_or_download_zstd.cmake
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
function(find_or_download_zstd FORCE_VENDORED)

if(NOT FORCE_VENDORED)
find_package(zstd CONFIG QUIET)
if(NOT TARGET zstd::libzstd_static)
find_package(ZSTD QUIET)
# A parent project may have defined (some of) these targets already, e.g. from
# a Find module. Looking zstd up again would then fail with "some (but not all)
# targets in this export set were already defined": reuse what is there.
if(NOT TARGET zstd::libzstd_static AND NOT TARGET zstd::libzstd_shared AND NOT TARGET zstd::libzstd)
find_package(zstd CONFIG QUIET)
if(NOT TARGET zstd::libzstd_static)
find_package(ZSTD QUIET)
endif()
endif()

# Normalize target names. This project links zstd::libzstd_static, but some
Expand Down
2 changes: 1 addition & 1 deletion cloudini_lib/package.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<package format="3">
<name>cloudini_lib</name>
<version>1.3.0</version>
<version>1.3.1</version>
<description>
Main library of Cloudini, the pointcloud compression library
</description>
Expand Down
48 changes: 48 additions & 0 deletions cloudini_lib/test/subproject/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Builds cloudini_lib the way a parent project embeds it (add_subdirectory or
# FetchContent) and fails at configure time if Cloudini leaks into the parent.
# cmake -S cloudini_lib/test/subproject -B build_subproject && cmake --build build_subproject
cmake_minimum_required(VERSION 3.16)
project(cloudini_subproject_consumer LANGUAGES C CXX)

# A parent that already provides zstd under the conventional target names, as a
# Find module does. Cloudini must reuse it instead of looking zstd up again.
find_library(CONSUMER_ZSTD_LIBRARY NAMES zstd REQUIRED)
find_path(CONSUMER_ZSTD_INCLUDE_DIR NAMES zstd.h REQUIRED)
add_library(zstd::libzstd_shared SHARED IMPORTED GLOBAL)
set_target_properties(zstd::libzstd_shared PROPERTIES
IMPORTED_LOCATION "${CONSUMER_ZSTD_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${CONSUMER_ZSTD_INCLUDE_DIR}")

# A ROS parent has already found ament_cmake; that must not turn the embedded
# Cloudini into a second ament package.
find_package(ament_cmake QUIET)

set(CLOUDINI_FORCE_VENDORED_DEPS OFF CACHE BOOL "" FORCE)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. ${CMAKE_CURRENT_BINARY_DIR}/cloudini_lib)

if(NOT TARGET cloudini::cloudini_lib)
message(FATAL_ERROR "embedded Cloudini does not define cloudini::cloudini_lib")
endif()
get_target_property(_type cloudini_lib TYPE)
if(NOT _type STREQUAL "STATIC_LIBRARY")
message(FATAL_ERROR "embedded Cloudini is ${_type}, expected STATIC_LIBRARY")
endif()
if(CMAKE_BUILD_TYPE)
message(FATAL_ERROR "embedded Cloudini forced CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} on the parent")
endif()
foreach(_leaked test_cloudini cloudini_rosbag_converter)
if(TARGET ${_leaked})
message(FATAL_ERROR "embedded Cloudini built '${_leaked}' inside the parent")
endif()
endforeach()
get_target_property(_links cloudini_lib INTERFACE_LINK_LIBRARIES)
if(_links MATCHES "pcl|PCL")
message(FATAL_ERROR "embedded Cloudini made PCL a link dependency of the parent: ${_links}")
endif()
if(EXISTS "${CMAKE_BINARY_DIR}/include/data_path.hpp")
message(FATAL_ERROR "embedded Cloudini wrote data_path.hpp into the parent's include directory")
endif()

add_executable(consumer main.cpp)
target_link_libraries(consumer PRIVATE cloudini::cloudini_lib)
install(TARGETS consumer DESTINATION bin)
Loading
Loading