10 KiB
Install/export and dependency policy
This project exposes install/export support through the CMake helpers in
cmake/cc_targets.cmake and cmake/cc_install.cmake. Install/export is explicit:
only targets marked with INSTALL or cc_install(<target>) are installed, and
external packages are rediscovered by consumers rather than bundled into the install
prefix.
Install/export entry points
Install/export is enabled when CC_ENABLE_INSTALL is true. The top-level project
uses install/export by default; subprojects can disable it through the usual project
configuration.
Targets enter the export set in one of two ways:
cc_library(my_lib
TYPE STATIC
SRCS my_lib.cc
ALIAS MyPackage::my_lib
INSTALL
)
cc_library(other_lib
TYPE STATIC
SRCS other_lib.cc
ALIAS MyPackage::other_lib
)
cc_install(other_lib)
cc_finalize_install()
cc_finalize_install() is idempotent and normally runs at generate time. It creates
install rules for marked targets, exports <project>Targets.cmake, generates
<project>Config.cmake, generates <project>ConfigVersion.cmake, and installs public
headers from include/<project>/ when that directory exists.
Unmarked targets are not installed automatically. This is intentional: a private or internal build target stays out of the installed package unless it is explicitly marked for install.
Required install metadata
Every installed target needs a valid export identity:
- Provide
ALIAS namespace::nameoncc_library(... INSTALL)orcc_executable(... INSTALL)targets. - For targets installed by direct
cc_install(), set the_CC_ALIAStarget property yourself if the target was not created by a helper that acceptsALIAS. - The alias must contain exactly one
::separator. - Namespace and target name must both be non-empty.
- All targets in one generated package must share the same namespace.
Good:
cc_library(core
TYPE STATIC
SRCS core.cc
ALIAS MyPackage::core
INSTALL
)
Bad:
cc_library(core TYPE STATIC SRCS core.cc INSTALL) # missing ALIAS
cc_library(core TYPE STATIC SRCS core.cc ALIAS core INSTALL) # not namespaced
cc_library(core TYPE STATIC SRCS core.cc ALIAS a::b::c INSTALL) # too many separators
During finalization the alias leaf is used as EXPORT_NAME, so a raw target such as
core_impl can be exported as MyPackage::core by setting ALIAS MyPackage::core.
Header install policy
cc_finalize_install() installs headers only from:
include/<project>/
The destination is:
<prefix>/include/<project>/
Only *.h and *.hpp files under that directory are installed by this helper. Headers
stored elsewhere are not automatically discovered or installed by cc_finalize_install().
If a package needs a different layout, add explicit install rules and keep the exported
include directories consistent with the installed tree.
The install-consumer fixture uses this convention with headers such as:
#include "ConsumerTestLib/math_utils.h"
#include "ConsumerTestLib/str_utils.h"
Its package fixture also adds install-tree include usage requirements:
target_include_directories(consumer_test_math PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
Do not assume the helper rewrites arbitrary source-tree include directories into
install-tree paths. Keep installed public headers under include/<project>/ and set
explicit BUILD_INTERFACE / INSTALL_INTERFACE include directories when consumer
usage requires them.
Dependency registry and generated find_dependency()
External install-time dependencies are declared with cc_register_dependency() and
referenced from cc_install(... DEPS ...):
cc_register_dependency(fmt
PACKAGE fmt
VERSION 9.0
TARGETS fmt::fmt
)
cc_register_dependency(spdlog
PACKAGE spdlog
VERSION 1.12
COMPONENTS fmt
TARGETS spdlog::spdlog
)
cc_library(dep_lib
TYPE STATIC
SRCS dep_lib.cc
ALIAS MyPackage::dep_lib
INSTALL
)
cc_install(dep_lib DEPS fmt spdlog)
For every referenced logical dependency, cc_finalize_install() validates that the
logical name was registered. It then writes include(CMakeFindDependencyMacro) and
find_dependency(...) calls into the generated package config before including the
exported targets file. The example above generates lines equivalent to:
include(CMakeFindDependencyMacro)
find_dependency(fmt 9.0)
find_dependency(spdlog 1.12 COMPONENTS fmt)
include("${CMAKE_CURRENT_LIST_DIR}/MyPackageTargets.cmake")
Supported registration metadata:
PACKAGE <PackageName>: required. This is the name passed tofind_dependency().VERSION <version-or-range>: optional.EXACT: optional exact version requirement.COMPONENTS <component>...: optional required components.OPTIONAL_COMPONENTS <component>...: optional components.TARGETS <imported-target>...: metadata used by static private dependency validation.
Duplicate logical names are allowed only when every metadata field matches the previous registration exactly.
External dependencies are not copied, vendored, bundled, or installed into this package's
prefix by these helpers. Consumers must have those dependencies available to find_package()
through their normal package manager, CMAKE_PREFIX_PATH, toolchain file, or other CMake
package discovery mechanism.
Static PRIVATE dependency policy
Installed static libraries can leak PRIVATE link dependencies into their exported
usage requirements as link-only dependencies. The helpers therefore validate static
libraries marked for install.
For a PRIVATE project-target dependency:
cc_library(private_dep
TYPE STATIC
SRCS private_dep.cc
ALIAS MyPackage::private_dep
INSTALL
)
cc_library(installed_lib
TYPE STATIC
SRCS installed_lib.cc
DEPS PRIVATE private_dep
ALIAS MyPackage::installed_lib
INSTALL
)
This is valid only because private_dep is also installed/exported and has a valid
namespaced alias. If private_dep is not installed, finalization rejects the package.
Fix one of these ways:
- install/export the project dependency too and give it a valid namespaced alias;
- make the dependency
PUBLICorINTERFACEif consumers are intended to link it; - for true external packages, use
cc_register_dependency()pluscc_install(... DEPS ...).
For a PRIVATE imported dependency:
add_library(ExternalPkg::external INTERFACE IMPORTED)
cc_register_dependency(external_pkg
PACKAGE ExternalPkg
TARGETS ExternalPkg::external
)
cc_library(installed_lib
TYPE STATIC
SRCS installed_lib.cc
DEPS PRIVATE ExternalPkg::external
ALIAS MyPackage::installed_lib
INSTALL
)
cc_install(installed_lib DEPS external_pkg)
This is valid because the imported target is listed in the registered dependency's
TARGETS metadata and the installed target declares that logical dependency with
cc_install(... DEPS external_pkg). Without that metadata, finalization rejects the
installed static library.
Raw link items, linker flags, and generator expressions that are not CMake targets are left untouched by this validation.
Consumer verification
The narrow install-consumer fixture validates the intended installed-package contract:
- configure, build, and install a package fixture;
- configure a separate consumer using only
find_package(ConsumerTestLib CONFIG REQUIRED); - link against imported targets
ConsumerTestLib::consumer_test_mathandConsumerTestLib::consumer_test_str; - build and run the consumer executable.
Run it with:
python3 scripts/dev_check.py install-consumer
The fixture is intentionally narrow. It verifies package config generation, target export, installed headers, and separate consumer usage; it does not imply arbitrary transitive dependencies are automatically installed or bundled.
Troubleshooting
target "..." is marked for install but has no ALIAS
The installed target has no export identity. Add a namespaced alias:
cc_library(my_lib TYPE STATIC SRCS my_lib.cc ALIAS MyPackage::my_lib INSTALL)
For manually created targets, set _CC_ALIAS before cc_install():
add_library(tooling INTERFACE)
set_target_properties(tooling PROPERTIES _CC_ALIAS "MyPackage::tooling")
cc_install(tooling)
ALIAS "..." is not namespaced or must contain exactly one "::" separator
Use exactly namespace::name. Do not use plain names (my_lib) or nested aliases
(a::b::c). Keep one namespace for all installed targets in the package.
target(s) reference unknown install-time dependency "..."
A target declares cc_install(target DEPS logical_name) but no matching
cc_register_dependency(logical_name PACKAGE ...) exists. Register the dependency before
finalization.
cc_register_dependency(...): PACKAGE is required
Every dependency registration must name the package that consumers will find:
cc_register_dependency(fmt PACKAGE fmt TARGETS fmt::fmt)
PRIVATE imported dependency "..." is not covered by a registered install dependency
An installed static library links a CMake imported target privately, but that imported
target is not covered by dependency registry metadata. Register the external package with
TARGETS <imported-target> and declare the logical dependency on the installed target:
cc_register_dependency(fmt PACKAGE fmt TARGETS fmt::fmt)
cc_install(my_static_lib DEPS fmt)
PRIVATE project-target dependency "..." that is not installed in the same export set
An installed static library privately links another project target that is not itself installed/exported. Install/export that dependency with a valid alias, or change the link visibility if consumers should depend on it directly.
Installed headers are missing
Confirm the headers live under include/<project>/ and use .h or .hpp extensions.
Headers elsewhere are outside the helper's automatic header install policy. Also verify
that exported targets expose install-tree include directories such as
$<INSTALL_INTERFACE:include> when consumers include package headers.