From 5a99bdf41f25da60834fa4b77d7b39306c034ceb Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Wed, 17 Dec 2025 10:27:55 -0800 Subject: [PATCH 1/3] Update checkout action to v6 in CI workflow Fixing minor version inconsistency in actions/checkout. --- templates/.github/workflows/ci.yml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/.github/workflows/ci.yml.in b/templates/.github/workflows/ci.yml.in index a2d6c5e..ff83786 100644 --- a/templates/.github/workflows/ci.yml.in +++ b/templates/.github/workflows/ci.yml.in @@ -34,7 +34,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Configure CMake run: cmake --preset=test From 9e5228e16e443a840d5a07c05a3aa321053bff9d Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Wed, 17 Dec 2025 14:00:14 -0800 Subject: [PATCH 2/3] Improve install workflow and clarify dependency export Expanded README with detailed instructions for testing installations and re-exporting dependencies using BUILD_INTERFACE, clarifying CMake best practices. Updated cpp-library-install.cmake to extract dependencies from BUILD_INTERFACE generator expressions for correct find_dependency() generation. Added an 'install' preset to CMakePresets.json for streamlined installation and testing of installed packages. --- README.md | 44 ++++++++++++++++++++++++++++++++- cmake/cpp-library-install.cmake | 10 ++++++-- templates/CMakePresets.json | 18 +++++++++++++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6f90a09..c1a7dc0 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ The library will be automatically fetched and built as part of your project. #### Installation (optional) -Installation is optional and typically not required when using CPM. If you need to install your library (e.g., for system-wide deployment or use with a package manager) use: +Installation is optional and typically not required when using CPM. If you need to install your library (e.g., for system-wide deployment or use with a package manager): ```bash # Build and install to default system location @@ -202,8 +202,48 @@ cmake --install build/default cmake --install build/default --prefix /opt/mylib ``` +**Testing your installation:** + +Use the `install` preset to verify that installed packages can be found correctly: + +```bash +# Build with CPM_USE_LOCAL_PACKAGES to test finding installed dependencies +cmake --preset=install +cmake --build --preset=install + +# Install and test +cmake --install build/install --prefix /tmp/test-install +CMAKE_PREFIX_PATH=/tmp/test-install cmake --preset=install +``` + +The `install` preset enables `CPM_USE_LOCAL_PACKAGES`, which makes CPM call `find_package()` first before fetching. This verifies your generated Config.cmake works correctly. + For information about using installed packages with `find_package()`, see the [CPM.cmake documentation](https://github.com/cpm-cmake/CPM.cmake) about [controlling how dependencies are found](https://github.com/cpm-cmake/CPM.cmake#cpm_use_local_packages). +**Re-exporting dependencies from external packages:** + +When your library re-exports dependencies (via `INTERFACE` linkage) that come from CPMAddPackage, you must wrap them in `BUILD_INTERFACE` to avoid CMake export errors: + +```cmake +# Correct: Wrap CPM-fetched dependencies in BUILD_INTERFACE +CPMAddPackage("gh:other-org/some-package@1.0.0") +target_link_libraries(my-library INTERFACE $) +``` + +**Why this is needed:** CPMAddPackage creates regular (non-IMPORTED) targets in your build tree. When CMake exports your library, it sees these dependencies but cannot include them in your export set (they belong to a different package). The `BUILD_INTERFACE` wrapper tells CMake: +- Use this dependency during build +- Don't include it in the export +- Let consumers find it themselves via `find_dependency()` in the generated Config.cmake + +cpp-library automatically extracts dependencies from `BUILD_INTERFACE` expressions and generates the appropriate `find_dependency()` calls in your package configuration. + +**When BUILD_INTERFACE is NOT needed:** +- Dependencies from `find_package()` (already IMPORTED targets) +- System libraries like `Threads::Threads` +- Internal (PRIVATE) dependencies (not applicable to INTERFACE libraries) + +This is a standard CMake pattern for re-exporting external package dependencies, not specific to cpp-library. + #### Dependency Handling in Installed Packages cpp-library automatically generates `find_dependency()` calls in the installed CMake package configuration. Call `cpp_library_enable_dependency_tracking()` before `project()`: @@ -470,6 +510,8 @@ cpp-library generates a `CMakePresets.json` file with the following configuratio All presets automatically configure `CPM_SOURCE_CACHE` to `${sourceDir}/.cache/cpm` for faster dependency resolution. You can override this by setting the `CPM_SOURCE_CACHE` environment variable. +**Best Practice:** Set `CPM_SOURCE_CACHE` in presets or via environment variable/command line, not in CMakeLists.txt. Setting it in CMakeLists.txt with `FORCE` can override parent project settings when used as a subdirectory, and prevents users from configuring it themselves. + ### Version Management Version is automatically detected from git tags: diff --git a/cmake/cpp-library-install.cmake b/cmake/cpp-library-install.cmake index c895803..541f764 100644 --- a/cmake/cpp-library-install.cmake +++ b/cmake/cpp-library-install.cmake @@ -56,8 +56,14 @@ function(_cpp_library_generate_dependencies OUTPUT_VAR TARGET_NAME NAMESPACE) # Process each linked library foreach(LIB IN LISTS LINK_LIBS) - # Skip generator expressions (typically BUILD_INTERFACE dependencies) - if(LIB MATCHES "^\\$<") + # Handle BUILD_INTERFACE generator expressions + # When re-exporting dependencies from external packages, they must be wrapped in BUILD_INTERFACE + # to avoid CMake export errors, but we still want to track them for find_dependency() + if(LIB MATCHES "^\\$]+)>$") + set(LIB "${CMAKE_MATCH_1}") + message(DEBUG "cpp-library: Extracted ${LIB} from BUILD_INTERFACE generator expression") + elseif(LIB MATCHES "^\\$<") + # Skip other generator expressions (INSTALL_INTERFACE, etc.) continue() endif() diff --git a/templates/CMakePresets.json b/templates/CMakePresets.json index 98acc1b..99ba84a 100644 --- a/templates/CMakePresets.json +++ b/templates/CMakePresets.json @@ -73,6 +73,21 @@ "CPP_LIBRARY_FORCE_INIT": "ON", "CPM_SOURCE_CACHE": "${sourceDir}/.cache/cpm" } + }, + { + "name": "install", + "displayName": "Install Configuration", + "description": "Release build for installation with CPM_USE_LOCAL_PACKAGES enabled for testing installed packages", + "binaryDir": "${sourceDir}/build/install", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "BUILD_TESTING": "OFF", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_CXX_EXTENSIONS": "OFF", + "CPM_USE_LOCAL_PACKAGES": "ON", + "CPM_SOURCE_CACHE": "${sourceDir}/.cache/cpm" + } } ], "buildPresets": [ @@ -80,7 +95,8 @@ { "name": "test", "displayName": "Build Tests", "configurePreset": "test" }, { "name": "docs", "displayName": "Build Docs", "configurePreset": "docs", "targets": "docs" }, { "name": "clang-tidy", "displayName": "Build with Clang-Tidy", "configurePreset": "clang-tidy" }, - { "name": "init", "displayName": "Initialize Templates", "configurePreset": "init" } + { "name": "init", "displayName": "Initialize Templates", "configurePreset": "init" }, + { "name": "install", "displayName": "Build for Installation", "configurePreset": "install" } ], "testPresets": [ { "name": "test", "displayName": "Run All Tests", "configurePreset": "test", "output": { "outputOnFailure": true } }, From 2e9b271d9ba11f4c778be9b0ce5262e8060af372 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Wed, 17 Dec 2025 14:06:13 -0800 Subject: [PATCH 3/3] Skip non-namespaced BUILD_INTERFACE targets in dependency export Updates the dependency export logic to only process BUILD_INTERFACE targets that are namespaced (external dependencies). Non-namespaced BUILD_INTERFACE targets, which are local build targets, are now skipped to avoid incorrect dependency tracking. --- cmake/cpp-library-install.cmake | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmake/cpp-library-install.cmake b/cmake/cpp-library-install.cmake index 541f764..2a55af0 100644 --- a/cmake/cpp-library-install.cmake +++ b/cmake/cpp-library-install.cmake @@ -60,8 +60,17 @@ function(_cpp_library_generate_dependencies OUTPUT_VAR TARGET_NAME NAMESPACE) # When re-exporting dependencies from external packages, they must be wrapped in BUILD_INTERFACE # to avoid CMake export errors, but we still want to track them for find_dependency() if(LIB MATCHES "^\\$]+)>$") - set(LIB "${CMAKE_MATCH_1}") - message(DEBUG "cpp-library: Extracted ${LIB} from BUILD_INTERFACE generator expression") + set(EXTRACTED_TARGET "${CMAKE_MATCH_1}") + # Only process if it's a namespaced target (external dependency) + # Non-namespaced targets in BUILD_INTERFACE are local build targets + if(EXTRACTED_TARGET MATCHES "::") + set(LIB "${EXTRACTED_TARGET}") + message(DEBUG "cpp-library: Extracted ${LIB} from BUILD_INTERFACE generator expression") + else() + # Skip non-namespaced BUILD_INTERFACE targets (local build targets) + message(DEBUG "cpp-library: Skipping non-namespaced BUILD_INTERFACE target: ${EXTRACTED_TARGET}") + continue() + endif() elseif(LIB MATCHES "^\\$<") # Skip other generator expressions (INSTALL_INTERFACE, etc.) continue()