-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
871 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# Exporting | ||
|
||
For exporting the project, we will focus on two aspects: what and how to export | ||
([Targets and components]), and how to make the export equivalent to the | ||
[`FetchContent`] users ([`<Project>Config.cmake` and `FetchContent`]). | ||
|
||
## Targets and components | ||
|
||
Here we have to consider what are the main targets that are installed and | ||
exported by default, and which ones are linked to a `COMPONENT` as used by | ||
[`find_package`]. Generally this is determined by what library or executable | ||
files are linked to the target. | ||
|
||
Unfortunately exporting and importing the targets linked to `COMPONENT` is not | ||
straightforward, and it involves quite a lot of boilerplate. To try to decompose | ||
this process, it requires: | ||
- [`install(TARGETS)`][]: Add the installation instructions for the actual | ||
target. | ||
- [`install(EXPORT)`] and [`export(EXPORT)`][]: Link the targets to a specific | ||
`<TargetFile>`/`COMPONENT`. A good format for the `<TargetFile>` is | ||
`<Project>Targets_<Component>.cmake`. | ||
- Configure [`<Project>Config.cmake`] to import the `<TargetFile>` as requested | ||
by the equivalent `COMPONENT`. | ||
|
||
This is more easily shown through an example. Let's consider and example project | ||
`Example` with the main library `hello` and optional executable `say-hello`. | ||
Disregarding the most of the paradigms for the sake of brevity, the project | ||
files would look like this: | ||
|
||
```{code-block} cmake | ||
: caption: CMakeLists.txt | ||
: emphasize-lines: 9-17,25-33 | ||
option(EXAMPLE_EXECUTABLE "Build the say-hello executable" OFF) | ||
add_library(hello) | ||
install(TARGETS hello | ||
EXPORT ExampleTargets | ||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | ||
) | ||
export(EXPORT ExampleTargets | ||
FILE ExampleTargets.cmake | ||
NAMESPACE Example:: | ||
) | ||
install(EXPORT ExampleTargets | ||
FILE ExampleTargets.cmake | ||
NAMESPACE Example:: | ||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Example | ||
) | ||
if (EXAMPLE_EXECUTABLE) | ||
add_executable(say-hello) | ||
install(TARGETS say-hello | ||
EXPORT ExampleTargets_say-hello | ||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} | ||
) | ||
export(EXPORT ExampleTargets_say-hello | ||
FILE ExampleTargets_say-hello.cmake | ||
NAMESPACE Example:: | ||
) | ||
install(EXPORT ExampleTargets_say-hello | ||
FILE ExampleTargets_say-hello.cmake | ||
NAMESPACE Example:: | ||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Example | ||
) | ||
endif () | ||
configure_package_config_file( | ||
ExampleConfig.cmake.in ExampleConfig.cmake | ||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Example | ||
) | ||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ExampleConfig.cmake | ||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Example | ||
) | ||
``` | ||
```{code-block} | ||
: caption: ExampleConfig.cmake.in | ||
: emphasize-lines: 3-6 | ||
@PACKAGE_INIT@ | ||
include(${CMAKE_CURRENT_LIST_DIR}/ExampleTargets.cmake) | ||
if (say-hello IN_LIST Example_FIND_COMPONENTS) | ||
include(${CMAKE_CURRENT_LIST_DIR}/ExampleTargets_say-hello.cmake OPTIONAL RESULT_VARIABLE Example_say-hello_FOUND) | ||
endif () | ||
check_required_components(Example) | ||
``` | ||
|
||
:::{caution} | ||
Do not use `-` as separator in the names of the Targets file. The generated | ||
target files will glob for `<TargetFile>-*` and include them automatically. | ||
::: | ||
|
||
### `shared` and `static` components | ||
|
||
Supporting both shared and static installations can be quite tricky, especially | ||
when these can span multiple additional components. A good resource is Alex | ||
Reinking's [blog post] on this subject, however, implementing this can be quite | ||
tricky. For this I have a helper module [`PackageComps`] that simplifies this | ||
implementation, and abstracts all of this boilerplate. | ||
|
||
## `<Project>Config.cmake` and `FetchContent` | ||
|
||
In order to make these two approaches equivalent, you only need to make sure | ||
that the targets have appropriate namespaced aliases and that the variables are | ||
exported appropriately. | ||
|
||
|
||
```{code-block} cmake | ||
: caption: CMakeLists.txt | ||
: emphasize-lines: 2,6-10 | ||
add_library(hello) | ||
add_library(Example::hello ALIAS hello) | ||
if(NOT PROJECT_IS_TOP_LEVEL) | ||
return(PROPAGATE | ||
Example_VERSION | ||
Example_VERSION_MAJOR | ||
Example_VERSION_MINOR | ||
Example_VERSION_PATCH | ||
Example_VERSION_TWEAK | ||
) | ||
endif () | ||
``` | ||
|
||
:::{attention} | ||
Make sure that [`<Project>_VERSION`] and its equivalents are exported | ||
::: | ||
|
||
[blog post]: https://alexreinking.com/blog/building-a-dual-shared-and-static-library-with-cmake.html | ||
|
||
[Targets and components]: #targets-and-components | ||
[`<Project>Config.cmake` and `FetchContent`]: #projectconfigcmake-and-fetchcontent | ||
[namespace]: namespace.md | ||
|
||
[`FetchContent`]: inv:cmake:cmake:module#module:FetchContent | ||
[`PackageComps`]: inv:cmake-extra:std:doc#cmake_modules/PackageComps | ||
[`find_package`]: inv:cmake:cmake:command#command:find_package | ||
[`install(TARGETS)`]: inv:cmake:cmake:command#command:install(targets) | ||
[`install(EXPORT)`]: inv:cmake:cmake:command#command:install(export) | ||
[`export(EXPORT)`]: inv:cmake:std:label#export(export) | ||
[`<Project>Config.cmake`]: <inv:cmake:std:label#full signature> | ||
[`<Project>_VERSION`]: inv:cmake:cmake:variable#variable:<PROJECT-NAME>_VERSION |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# Importing dependencies | ||
|
||
The modern method of importing dependencies is to use | ||
[`FetchContent_Declare(FIND_PACKAGE_ARGS)`] which allows the user to choose how | ||
to import the dependency: from the system ([`find_package`]) or downloaded/local | ||
source ([`FetchContent`]). | ||
|
||
## Required dependency | ||
|
||
If the dependency is required, and it is a CMake based project, you can | ||
straightforwardly use [`FetchContent_Declare(FIND_PACKAGE_ARGS)`]. Note that | ||
this feature is only introduced in CMake 3.24. Back-porting this feature can be | ||
quite involved: | ||
```cmake | ||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) | ||
FetchContent_Declare(ProjectB | ||
... | ||
FIND_PACKAGE_ARGS CONFIG | ||
) | ||
else () | ||
if (NOT DEFINED FETCHCONTENT_SOURCE_DIR_PROJECTB) | ||
if (NOT DEFINED FETCHCONTENT_TRY_FIND_PACKAGE_MODE) | ||
set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE OPT_IN) | ||
endif () | ||
if (FETCHCONTENT_TRY_FIND_PACKAGE_MODE MATCHES "(OPT_IN|ALWAYS)") | ||
find_package(ProjectB QUIET CONFIG) | ||
endif () | ||
endif () | ||
if (NOT ProjectB_FOUND) | ||
FetchContent_Declare(ProjectB | ||
... | ||
) | ||
endif () | ||
endif () | ||
``` | ||
|
||
See [controlling dependency import] for more details on how to control the | ||
import. | ||
|
||
## Optional dependency | ||
|
||
For optional dependencies, unfortunately the fallthrough using | ||
[`FetchContent_Declare(FIND_PACKAGE_ARGS)`] does not allow the dependency to not | ||
be imported. For this the only clean design is to use `find_package`, with | ||
[`CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>`] and | ||
[`CMAKE_DISABLE_FIND_PACKAGE_<PackageName>`] to control the dependencies. | ||
|
||
## `Find<PackageName>.cmake` | ||
|
||
In order to provide compatibility with non-CMake projects, you can provide a | ||
[`Find<PackageName>.cmake`] file to search for this dependency. But keep in mind | ||
that integrating this to make it work with the project's [export] can be quite | ||
tricky, since these modules should not be exported outside of the project, in | ||
order to avoid conflicts with other similarly defined modules. | ||
|
||
If you still need such a module, an example module can look as follows: | ||
|
||
```{code-block} cmake | ||
: caption: FindExample.cmake | ||
set(CMAKE_REQUIRE_FIND_PACKAGE_Example FALSE) | ||
find_package(Example CONFIG QUIET) | ||
if (Example_FOUND) | ||
find_package_handle_standard_args(Example CONFIG_MODE) | ||
return() | ||
endif () | ||
pkg_search_module(Example IMPORTED_TARGET | ||
example | ||
) | ||
if (Example_FOUND) | ||
add_library(Example::hello ALIAS PkgConfig::Example) | ||
return () | ||
endif () | ||
find_library(Hello_LIBRARY | ||
NAMES hello | ||
) | ||
mark_as_advanced(Hello_LIBRARY) | ||
find_path(Example_INCLUDE_DIR | ||
NAMES hello.h | ||
PATH_SUFFIXES example | ||
) | ||
mark_as_advanced(Example_INCLUDE_DIR) | ||
find_package_handle_standard_args(Example | ||
REQUIRED_VARS Hello_LIBRARY Example_INCLUDE_DIR | ||
) | ||
if(Example_FOUND) | ||
set(Example_INCLUDE_DIRS ${Example_INCLUDE_DIR}) | ||
set(Example_LIBRARIES ${Hello_LIBRARY}) | ||
add_library(Example::hello UNKNOWN IMPORTED) | ||
set_target_properties(Example::hello PROPERTIES | ||
IMPORTED_LOCATION ${Hello_LIBRARY} | ||
) | ||
target_include_directories(Example::hello INTERFACE ${Example_INCLUDE_DIRS}) | ||
target_link_libraries(Example::hello INTERFACE ${Example_LIBRARIES}) | ||
endif() | ||
``` | ||
|
||
[controlling dependency import]: #dependency-import-options | ||
[export]: export.md | ||
|
||
[`FetchContent_Declare(FIND_PACKAGE_ARGS)`]: inv:cmake:std:label#fetchcontent-find_package-integration-examples | ||
[`find_package`]: inv:cmake:cmake:command#command:find_package | ||
[`FetchContent`]: inv:cmake:cmake:module#module:FetchContent | ||
[`CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>`]: inv:cmake:cmake:variable#variable:CMAKE_REQUIRE_FIND_PACKAGE_<PackageName> | ||
[`CMAKE_DISABLE_FIND_PACKAGE_<PackageName>`]: inv:cmake:cmake:variable#variable:CMAKE_DISABLE_FIND_PACKAGE_<PackageName> | ||
[`Find<PackageName>.cmake`]: <inv:cmake:std:label#find modules> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Design paradigms | ||
|
||
```{toctree} | ||
: maxdepth: 1 | ||
: titlesonly: true | ||
top-level-cmake | ||
namespace | ||
subproject | ||
test-suite | ||
export | ||
options | ||
import | ||
``` |
Oops, something went wrong.