A collection of extensions (functions and special variables) that I think should be included in CMake.
Both installation scripts accept a Git commit-ish (e.g. hash, branch, or tag)
as a parameter. With Bash, it is a positional parameter; with Powershell, it
must be an environment variable. You can see how to pass these parameters in
the one-liners below. If not given, the default value is master. This lets
you fix the version you want to install, which can be handy, e.g., in
a continuous integration environment.
With Conan
$ conan remote add jfreeman https://api.bintray.com/conan/jfreeman/jfreeman# conanfile.txt
[build_requires]
future/[*]@jfreeman/testing
You'll need sudo to install these extensions in the right place. You can
read the script
to see that it's very short and not dangerous.
$ curl -L https://raw.githubusercontent.com/thejohnfreeman/cmake-future/master/install.sh \
| sudo env "PATH=$PATH" bash -s -- masterYou'll need Administrator privileges to install these extensions in the right place. You can read the script to see that it's very short and not dangerous.
> Invoke-RestMethod https://raw.githubusercontent.com/thejohnfreeman/cmake-future/master/install.ps `
| powershell -Command "`$env:commit = 'master'; powershell -Command -"mkdir build
cd build
cmake ..
sudo cmake --build . --target installAfter installation, each extension can be imported separately by name with
find_package(<name>), or you can import all of them at once with
find_package(future). Extensions are semantically versioned, so you can make
sure you get the latest version you have installed by using the version sort
capability
added to find_package in CMake
3.7:
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
find_package(future CONFIG REQUIRED)
You must place the import after the call to project.
These extensions respect and embody a few popular conventions that the community seems to consider best practices. In the spirit of "convention over configuration", they try to minimize boilerplate, which helps newcomers get up and running sooner and helps experienced users avoid common pitfalls.
- All targets should be
aliased
and exported under a Project
Namespace called
${PROJECT_NAME}::. That way, targetbthat depends on targetacan use the same syntax (target_link_libraries(b project::a)) whetherbis in the same project or in another project. - All exported targets should be added to a Project Export Set. The exact
name of this export set does not matter, but it should be used consistently
throughout the project. These extensions use the variable
FUTURE_PROJECT_EXPORT_SETto hold the name.
The section headings below link to their corresponding module file so that you can double-check its implementation.
future_project(LICENSE <license> [REPOSITORY_URL <url>] [AUTHORS <author>...])future_project(
LICENSE "ISC"
REPOSITORY_URL "https://github.com/thejohnfreeman/cmake-future.git"
AUTHORS "John Freeman <jfreeman08@gmail.com>"
)future_project sets variables for common project metadata that are not set
by project:
${PROJECT_NAME}_FOUNDPROJECT_LICENSEPROJECT_REPOSITORY_URLPROJECT_AUTHORS
${PROJECT_NAME}_FOUND helps to short-circuit calls to find_package or
future_add_dependency in subprojects looking for this
project. Think examples.
Examples should be their own projects so that they can be compiled both as
part of the main project and separately, to test the packaging and
installation of the main project.
A call to find_package or future_add_dependency in an example should
short-circuit when compiled as part of the main project, but search when
compiled separately.
If called from the top-level project, future_project sets these CACHE
variables:
CMAKE_PROJECT_LICENSECMAKE_PROJECT_REPOSITORY_URLCMAKE_PROJECT_AUTHORS
These variables are used by my Conan autorecipe to set the corresponding attributes on the package.
Because CMake requires the top-level CMakeLists.txt to contain a literal,
direct call to the project command, no extension can substitute for it.
Instead, this command complements it.
${FUTURE_PROJECT_EXPORT_SET}
future_install(...)
future_get_export_set(<variable> [<name>])future_get_export_set(export_set ${FUTURE_PROJECT_EXPORT_SET})This extension exports a few pieces:
- A variable
FUTURE_PROJECT_EXPORT_SETfor the name of the Project Export Set. The sibling extensionsfuture_add_headersandfuture_add_librarywill add their installed targets to this export set. The sibling extensionfuture_install_projectuses this export set for the package's export file. - A function
future_installthat wraps the built-ininstallbut records the targets added to each export set. You only need to use this function for thefuture_install(TARGETS <target>... EXPORT <export-set>)form, but it won't hurt to use it for every form. (This function cannot be namedinstallbecause it is presently impossible to safely call an overridden function.) - A function
future_get_export_setthat will get the list of targets in the export set<name>and store it in<variable>.
future_add_headers(<name> [DIRECTORY <dir>] [DESTINATION <dir>])future_add_headers(${PROJECT_NAME}_headers DIRECTORY include)- Adds an
INTERFACElibrary target called<name>for the headers inDIRECTORY(which defaults toCMAKE_CURRENT_SOURCE_DIR) - Aliases the library in the Project Namespace.
- Adds the library to the Project Export Set.
- Installs the headers at
DESTINATION(defaultCMAKE_INSTALL_INCLUDEDIR).
future_add_library(<name> [...])future_add_library(${PROJECT_NAME} a.cpp b.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt)- Adds a library target called
<name>, passing any remaining arguments toadd_library. - Aliases the library in the Project Namespace.
- Adds the library to the Project Export Set.
- Installs the library to destinations according to
GNUInstallDirs.
future_add_test_executable(<name> [...])enable_testing() # Do not forget this!
future_add_test_executable(my_test EXCLUDE_FROM_ALL my_test.cpp)Like add_executable plus add_test, this will add an executable target
called <name> as a test, but unlike add_test, it will add a test fixture
that rebuilds the target if its dependencies have changed. Inspired by
this answer on Stack Overflow.
With this, you will never run out-of-date tests. Remaining arguments are
passed through to
add_executable.
future_get_names_with_file_suffix(<variable> <suffix>)future_get_names_with_file_suffix(tests ".cpp")
foreach(test ${tests})
future_add_test_executable(${test} EXCLUDE_FROM_ALL ${test}.cpp)
target_link_libraries(${test} doctest::doctest)
endforeach()This will search the current source directory for files with the given suffix,
extract their filenames (minus the suffix), and collect them as a list in the
given <variable>.
future_get_targets(<variable> [<directory>])Get a list of all the targets defined in <directory> or its subdirectories
and store it in <variable>. If no <directory> is given, the current
directory will be used. The list does not include any Imported
Targets
or Alias
Targets.
This is built on top of
BUILDSYSTEM_TARGETS
and thus subject to all of the same limitations.
${FUTURE_INSTALL_PACKAGEDIR}
${FUTURE_INSTALL_FULL_PACKAGEDIR}This extension is modeled after
GNUInstallDirs,
and it exports "missing" variables.
Like GNUInstallDirs, there are two versions of each variable:
-
FUTURE_INSTALL_<dir>: A relative path suitable as theDESTINATIONargument to aninstallcommand. -
FUTURE_INSTALL_FULL_<dir>: An absoluate path constructed by appending the correspondingFUTURE_INSTALL_<dir>toCMAKE_INSTALL_PREFIX.
where <dir> is one of:
PACKAGEDIR: A relative path that can be sandwiched between the defaultCMAKE_INSTALL_PREFIXand a directory name starting with${PROJECT_NAME}to yield one of the paths on the default search path forfind_package. (Read more.)
install(
EXPORT ${FUTURE_PROJECT_EXPORT_SET}
FILE ${PROJECT_NAME}-targets.cmake
NAMESPACE ${PROJECT_NAME}::
DESTINATION "${FUTURE_INSTALL_PACKAGEDIR}/${PROJECT_NAME}-${PROJECT_VERSION}"
)(This example is based on this pattern for writing package exports.)
future_add_dependency([PUBLIC|PRIVATE] <name> [OPTIONAL] [...])
future_install_project()future_add_dependency(PUBLIC Boost)
future_add_dependency(PRIVATE doctest)
future_add_library(my_library my_library.cpp)
target_link_libraries(my_library PUBLIC Boost::Boost)
future_add_test_executable(my_test my_test.cpp)
target_link_libraries(my_test
${PROJECT_NAME}::my_library
doctest::doctest
)
future_install_project()This module has a few functions to help you install your project according to the best practices of Modern CMake.
-
future_add_dependencyworks likefind_package.PUBLICdependencies are imported by your package configuration file (usingfind_dependency), so that your dependents will transitively import them.PRIVATEdependencies are not. Good candidates forPRIVATEdependencies are build-time-only dependencies like test frameworks. Dependencies are required by default; you can passOPTIONALif you want CMake to continue even when the dependency cannot be found. All remaining arguments will be passed through tofind_package. -
You should add all your dependencies near the beginning of your top-level
CMakeLists.txt, much like you where you would put#includes orimportstatements in a program, and for the same reasons. Before calling the next and last function, remember to add any targets you want to the Project Export Set. -
future_install_projectinstalls your project. It creates a package configuration file; a package version file (using theSameMajorVersionpolicy to approximate semantic versioning); and an export file for the Project Export Set (scoped to the Project Namespace).