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

317 lines
10 KiB
Markdown

# 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:
```cmake
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:
```cmake
cc_library(core
TYPE STATIC
SRCS core.cc
ALIAS MyPackage::core
INSTALL
)
```
Bad:
```cmake
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:
```text
include/<project>/
```
The destination is:
```text
<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:
```cpp
#include "ConsumerTestLib/math_utils.h"
#include "ConsumerTestLib/str_utils.h"
```
Its package fixture also adds install-tree include usage requirements:
```cmake
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 ...)`:
```cmake
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:
```cmake
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:
```cmake
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:
```cmake
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:
```sh
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:
```cmake
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()`:
```cmake
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:
```cmake
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:
```cmake
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.