Files
tqcq 05eabe7cd4
cpp-template / format (push) Failing after 3m38s
cpp-template / debug (push) Failing after 5m18s
cpp-template / clang-tidy (push) Failing after 1m26s
cpp-template / release (push) Failing after 1m42s
cpp-template / cxx11-smoke (push) Failing after 1m43s
cpp-template / install-consumer (push) Failing after 1m37s
cpp-template / asan (push) Failing after 1m23s
cpp-template / tsan (push) Failing after 1m26s
cpp-template / fuzz-smoke (push) Failing after 1m15s
cpp-template / no-network-negative (push) Failing after 7m7s
docs: document template workflows
2026-05-18 09:41:16 +08:00

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::name on cc_library(... INSTALL) or cc_executable(... INSTALL) targets.
  • For targets installed by direct cc_install(), set the _CC_ALIAS target property yourself if the target was not created by a helper that accepts ALIAS.
  • 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 to find_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 PUBLIC or INTERFACE if consumers are intended to link it;
  • for true external packages, use cc_register_dependency() plus cc_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:

  1. configure, build, and install a package fixture;
  2. configure a separate consumer using only find_package(ConsumerTestLib CONFIG REQUIRED);
  3. link against imported targets ConsumerTestLib::consumer_test_math and ConsumerTestLib::consumer_test_str;
  4. 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.