Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

cmake-future

Bintray release Travis: build on Linux and OSX AppVeyor: build on Windows

A collection of extensions (functions and special variables) that I think should be included in CMake.

Install

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

Linux and OSX

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 -- master

Windows

You'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 -"

From source

mkdir build
cd build
cmake ..
sudo cmake --build . --target install

Use

After 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.

Conventions

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, target b that depends on target a can use the same syntax (target_link_libraries(b project::a)) whether b is 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_SET to hold the name.

Modules

The section headings below link to their corresponding module file so that you can double-check its implementation.

future_project

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}_FOUND
  • PROJECT_LICENSE
  • PROJECT_REPOSITORY_URL
  • PROJECT_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_LICENSE
  • CMAKE_PROJECT_REPOSITORY_URL
  • CMAKE_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_export_sets

${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_SET for the name of the Project Export Set. The sibling extensions future_add_headers and future_add_library will add their installed targets to this export set. The sibling extension future_install_project uses this export set for the package's export file.
  • A function future_install that wraps the built-in install but records the targets added to each export set. You only need to use this function for the future_install(TARGETS <target>... EXPORT <export-set>) form, but it won't hurt to use it for every form. (This function cannot be named install because it is presently impossible to safely call an overridden function.)
  • A function future_get_export_set that will get the list of targets in the export set <name> and store it in <variable>.

future_add_headers

future_add_headers(<name> [DIRECTORY <dir>] [DESTINATION <dir>])
future_add_headers(${PROJECT_NAME}_headers DIRECTORY include)

future_add_library

future_add_library(<name> [...])
future_add_library(${PROJECT_NAME} a.cpp b.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt)

future_add_test_executable

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

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

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.

FutureInstallDirs

${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 the DESTINATION argument to an install command.

  • FUTURE_INSTALL_FULL_<dir>: An absoluate path constructed by appending the corresponding FUTURE_INSTALL_<dir> to CMAKE_INSTALL_PREFIX.

where <dir> is one of:

  • PACKAGEDIR: A relative path that can be sandwiched between the default CMAKE_INSTALL_PREFIX and a directory name starting with ${PROJECT_NAME} to yield one of the paths on the default search path for find_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_install_project

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_dependency works like find_package. PUBLIC dependencies are imported by your package configuration file (using find_dependency), so that your dependents will transitively import them. PRIVATE dependencies are not. Good candidates for PRIVATE dependencies are build-time-only dependencies like test frameworks. Dependencies are required by default; you can pass OPTIONAL if you want CMake to continue even when the dependency cannot be found. All remaining arguments will be passed through to find_package.

  • You should add all your dependencies near the beginning of your top-level CMakeLists.txt, much like you where you would put #includes or import statements 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_project installs your project. It creates a package configuration file; a package version file (using the SameMajorVersion policy to approximate semantic versioning); and an export file for the Project Export Set (scoped to the Project Namespace).

About

Extensions that I think should be included with CMake.

Resources

License

Packages

No packages published