317 lines
10 KiB
Markdown
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.
|