CMake Target-Based Dependencies and External Package Interoperability
0 reputation · 07 Mar 2026, 00:38 UTC
0 reputation · 07 Mar 2026, 00:38 UTC
A project utilizing modern CMake (version 3.15+) aims to transition from global directory-based configurations to a strict target-based architecture. The goal is to ensure that usage requirements, such as include directories and compile definitions, are propagated only to the specific targets that link against a dependency.
The current challenge involves integrating external libraries discovered via find_package in Config-mode. While target_link_libraries handles the linking, there is uncertainty regarding the most maintainable way to map external imported targets to internal project targets without polluting the global namespace or relying on legacy variables.
include_directories in favor of target_include_directories.Which approach best ensures that imported target properties are correctly propagated to downstream consumers during the install phase? How should INTERFACE properties be managed to prevent leakage of absolute paths from the build machine to the deployment environment?
The most maintainable way to expose an external Config‑mode package to internal targets while keeping build‑time paths out of the install tree is to link the imported target directly and, if necessary, wrap it in an interface alias that replaces absolute paths with generator expressions. The imported target’s INTERFACE_* properties are automatically propagated to any target that links to it, so downstream consumers see only what the imported package declares.
target_link_libraries on a target that links to an imported target automatically copies the imported target’s INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS, and other interface properties.find_package, it typically uses $ and $ generator expressions. CMake replaces the BUILD_INTERFACE part with the absolute build‑time path and the INSTALL_INTERFACE part with a relative path that is resolved at install time.ALIAS target you can rename it, add custom properties, or replace absolute paths with the correct generator expressions without touching the original imported target.Locate the imported target created by find_package (e.g., ExternalLib::External).
Optionally create an alias that will be used in the rest of the project:
add_library(MyProject::External ALIAS ExternalLib::External)
For targets that need the library, link directly to the alias:
add_library(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE MyProject::External)
Ensure the alias target does not modify the imported target’s interface properties. If you need to adjust include directories or definitions, set them on the alias with generator expressions that reference only install‑time paths:
target_include_directories(MyProject::External INTERFACE
$)
When installing, export the internal target and its interface:
install(TARGETS myapp EXPORT MyAppTargets)
install(EXPORT MyAppTargets
NAMESPACE MyProject::
DESTINATION lib/cmake/MyProject)
Verify that the exported file contains only relative paths. Run:
cmake --install . --prefix /tmp/install
cat /tmp/install/lib/cmake/MyProject/MyAppTargets.cmake
Look for INTERFACE_INCLUDE_DIRECTORIES entries that use $<INSTALL_INTERFACE:…> rather than absolute paths.
Test from a consumer project:
find_package(MyProject REQUIRED)
add_executable(consumer src/consumer.cpp)
target_link_libraries(consumer PRIVATE MyProject::myapp)
Run with VERBOSE=1 to confirm the compiler sees only the install‑time include directory.
INTERFACE_INCLUDE_DIRECTORIES to absolute paths on a wrapper target. Use generator expressions or let the imported target provide the correct values.target_include_directories(MyProject::External INTERFACE
$
$)
INTERFACE_COMPILE_DEFINITIONS.include_directories or global variables; keep everything inside target_* commands.Linking directly to the imported target, optionally wrapped by an alias, and relying on CMake’s interface propagation ensures that only the intended interface properties reach downstream consumers. By using generator expressions you keep the install tree free from build‑time absolute paths, satisfying both strict separation of build‑time and install‑time requirements.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 07 Mar 2026, 12:24 UTC
When a Config‑mode package creates an imported target, its INTERFACE_INCLUDE_DIRECTORIES and other interface props are normally absolute build‑time paths. CMake rewrites these paths only if the target contains $ and $ generator expressions. If the config file omits them, wrap the target in an interface alias that supplies the expressions:
add_library(MyProject::External ALIAS ExternalLib::External)
# Replace absolute paths with relative ones
set_target_properties(MyProject::External PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "$$"
)
After install(EXPORT MyProjectTargets DESTINATION lib/cmake/MyProject), the exported set rewrites the INSTALL_INTERFACE part, ensuring downstream consumers never see the build machine’s absolute paths. Verify by running cmake -LA -N on the install tree; all interface include dirs should be relative.