1
0
mirror of synced 2024-12-22 04:30:01 +01:00

feat: move to JupyterBook

This commit is contained in:
Henry Schreiner 2024-08-15 07:04:35 +00:00
parent d6a5c28955
commit 6400c6bfef
33 changed files with 11990 additions and 4297 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# GitHub syntax highlighting
pixi.lock linguist-language=YAML linguist-generated=true

5
.gitignore vendored
View File

@ -6,3 +6,8 @@
/modern-cmake.pdf
/book.mobi
/book.epub
# pixi environments
.pixi
*.egg-info
_build
build

View File

@ -1,35 +1,27 @@
test_code:
image: rootproject/root:6.26.14-ubuntu22.04
stage: test
default:
image: ghcr.io/prefix-dev/pixi:latest
before_script:
- apt-get update && apt-get install -y make cmake libboost-dev git
# We will install latest CMake, even though Ubuntu has a recent one
- mkdir -p $HOME/.local
- curl -s "https://cmake.org/files/v3.30/cmake-3.30.2-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C $HOME/.local
- export PATH=$HOME/.local/bin:$PATH
- apt-get update && apt-get install -y texlive texlive-xetex texlive-fonts-extra latexmk
variables:
DEBIAN_FRONTEND: noninteractive
test:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- cmake -S examples -B build
- cmake --build build -v
- cmake --build build -t test
- pixi run test
- pixi run build
- pixi run pdf
pages:
image: node:14-buster
stage: deploy
only:
- master
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
before_script:
- apt-get update && apt-get install -y calibre calibre-bin libxss1 libasound2
- npm install --unsafe-perm
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- npx gitbook build . public
- npx gitbook pdf . public/modern-cmake.pdf
- pixi run build
- pixi run pdf
- cp _build/latex/modern-cmake.pdf _build/html/
artifacts:
paths:
- public
- _build/html
expire_in: 1 week

View File

@ -11,20 +11,26 @@ I'm talking about Modern CMake. CMake 3.5+, maybe even CMake 3.30+!
It's clean, powerful, and elegant, so you can spend most of your time coding, not adding lines to an unreadable, unmaintainable Make (Or CMake 2) file.
And CMake 3.11+ is supposed to be significantly faster, as well!
{% hint %}
:::{note}
Are you interested in using CMake to build Python packages? I'm working on scikit-build-core, [proposal described here][skprop]! Let me know if you have a use case!
[skprop]: https://iscinumpy.gitlab.io/post/scikit-build-proposal/
{% endhint %}
:::
:::{attention}
{% hint style='working' %}
This book is meant to be a living document. You can raise an issue or put in a merge request on [GitLab](https://gitlab.com/CLIUtils/modern-cmake).
You can also [download a copy as a PDF](https://CLIUtils.gitlab.io/modern-cmake/modern-cmake.pdf). Be sure to check the [HSF CMake Training][], as well!
[hsf cmake training]: https://hsf-training.github.io/hsf-training-cmake-webpage/01-intro/index.html
:::
{% endhint %}
:::{warning}
This book has just been moved from GitBook to JupyterBook. There might be some rough edges after the transition - feel free to report or fix issues!
:::
In short, here are the most likely questions in your mind if you are considering Modern CMake:

42
_config.yml Normal file
View File

@ -0,0 +1,42 @@
# Book settings
# Learn more at https://jupyterbook.org/customize/config.html
title: Modern CMake
author: Henry Schreiner
copyright: "2024"
logo: logo.png
only_build_toc_files: true
exclude_patterns:
- _build
- build
- Thumbs.db
- .DS_Store
- "**.ipynb_checkpoints"
- .pixi
- "**/_deps"
latex:
latex_documents:
targetname: modern-cmake.tex
repository:
url: https://gitlab.com/CLIUtils/modern-cmake
branch: master
html:
use_issues_button: true
use_repository_button: true
parse:
myst_substitutions:
cmake: "[{0}](https://cmake.org/cmake/help/latest/manual/cmake-{0}.7.html)"
command: "[`{0}`](https://cmake.org/cmake/help/latest/command/{0}.html)"
envvar: "[`{0}`](https://cmake.org/cmake/help/latest/envvar/{0}.html)"
module: "[{0}](https://cmake.org/cmake/help/latest/module/{0}.html)"
policy: "[{0}](https://cmake.org/cmake/help/latest/policy/{0}.html)"
variable: "[`{0}`](https://cmake.org/cmake/help/latest/variable/{0}.html)"
prop: "[`{1}`](https://cmake.org/cmake/help/latest/prop-{0}/{1}.html)"
myst_enable_extensions:
- colon_fence
- substitution

60
_toc.yml Normal file
View File

@ -0,0 +1,60 @@
# Table of contents
# Learn more at https://jupyterbook.org/customize/toc.html
format: jb-book
root: README
parts:
- caption: Getting Started
chapters:
- file: chapters/intro/installing
- file: chapters/intro/running
- file: chapters/intro/dodonot
- file: chapters/intro/newcmake
- caption: The Basics
chapters:
- file: chapters/basics
- file: chapters/basics/variables
- file: chapters/basics/functions
- file: chapters/basics/comms
- file: chapters/basics/structure
- file: chapters/basics/programs
- file: chapters/basics/example
- caption: Extra features
chapters:
- file: chapters/features
- file: chapters/features/cpp11
- file: chapters/features/small
- file: chapters/features/utilities
- file: chapters/features/modules
- file: chapters/features/ides
- file: chapters/features/debug
- caption: Using Other Projects
chapters:
- file: chapters/projects
- file: chapters/projects/submodule
- file: chapters/projects/download
- file: chapters/projects/fetch
- caption: Testing
chapters:
- file: chapters/testing
- file: chapters/testing/googletest
- file: chapters/testing/catch
- caption: Exporting and Installing
chapters:
- file: chapters/install
- file: chapters/install/installing
- file: chapters/install/exporting
- file: chapters/install/packaging
- caption: Libraries
chapters:
- file: chapters/packages
- file: chapters/packages/CUDA
- file: chapters/packages/OpenMP
- file: chapters/packages/Boost
- file: chapters/packages/MPI
- file: chapters/packages/ROOT
sections:
- file: examples/root-usefile/README
- file: examples/root-simple/README
- file: examples/root-dict/README
- file: chapters/packages/Minuit2

View File

@ -1,50 +0,0 @@
{
"title": "Modern CMake",
"description": "A guide to writing simple, powerful, and clean CMake 3.1+ builds.",
"author": "Henry Schreiner",
"plugins": ["replace", "hints", "term", "include-codeblock", "ace"],
"pluginsConfig": {
"include-codeblock": {
"fixlang": true
},
"replace": {
"substitutes": [
{
"pattern": "«cmake:([^`^»]+)»",
"flags": "g",
"substitute": "[$1](https://cmake.org/cmake/help/latest/manual/cmake-$1.7.html)"
},
{
"pattern": "«command:`?([^`^»]+)`?»",
"flags": "g",
"substitute": "[`$1`](https://cmake.org/cmake/help/latest/command/$1.html)"
},
{
"pattern": "«envvar:`?([^`^»]+)`?»",
"flags": "g",
"substitute": "[`$1`](https://cmake.org/cmake/help/latest/envvar/$1.html)"
},
{
"pattern": "«module:([^`^»]+)»",
"flags": "g",
"substitute": "[$1](https://cmake.org/cmake/help/latest/module/$1.html)"
},
{
"pattern": "«policy:([^`^»]+)»",
"flags": "g",
"substitute": "[$1](https://cmake.org/cmake/help/latest/policy/$1.html)"
},
{
"pattern": "«variable:`?([^`^»]+)`?»",
"flags": "g",
"substitute": "[`$1`](https://cmake.org/cmake/help/latest//$1.html)"
},
{
"pattern": "«prop:([^:]+):`?([^`^»]+)`?»",
"flags": "g",
"substitute": "[`$2`](https://cmake.org/cmake/help/latest/prop_$1/$2.html)"
}
]
}
}
}

View File

@ -10,7 +10,7 @@ cmake_minimum_required(VERSION 3.1)
```
Let's mention a bit of CMake syntax. The command name
«command:`cmake_minimum_required`» is case insensitive, so the common practice
{{ command.format('cmake_minimum_required') }} is case insensitive, so the common practice
is to use lower case. [^1] The `VERSION` is a special keyword for this
function. And the value of the version follows the keyword. Like everywhere in
this book, just click on the command name to see the official documentation,
@ -20,7 +20,7 @@ This line is special! [^2] The version of CMake will also dictate the policies,
which define behavior changes. So, if you set `minimum_required` to `VERSION 2.8`, you'll get the wrong linking behavior on macOS, for example, even in the
newest CMake versions. If you set it to 3.3 or less, you'll get the wrong
hidden symbols behaviour, etc. A list of policies and versions is available at
«cmake:policies».
{{ cmake.format('policies') }}.
Starting in CMake 3.12, this supports a range, such as `VERSION 3.1...3.15`;
this means you support as low as 3.1 but have also tested it with the new
@ -61,11 +61,13 @@ else()
endif()
```
{% hint style='info' %}
:::{tip}
If you really need to set to a low value here, you can use
«command:`cmake_policy`» to conditionally increase the policy level or set a
{{ command.format('cmake_policy') }} to conditionally increase the policy level or set a
specific policy. Please at least do this for your macOS users!
{% endhint %}
:::
## Setting a project
@ -84,14 +86,16 @@ arguments here are optional. The version sets a bunch of variables, like
`Fortran`, `ASM`, `CUDA` (CMake 3.8+), `CSharp` (3.8+), and `SWIFT` (CMake
3.15+ experimental). `C CXX` is the default. In CMake 3.9, `DESCRIPTION` was
added to set a project description, as well. The documentation for
«command:`project`» may be helpful.
{{ command.format('project') }} may be helpful.
:::{tip}
{% hint style='info' %}
You can add
[comments](https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#comments)
with the `#` character. CMake does have an inline syntax for comments too, but
it's rarely used.
{% endhint %}
:::
There's really nothing special about the project name. No targets are added at this point.
@ -104,11 +108,11 @@ with them, let's start with a simple executable.
add_executable(one two.cpp three.h)
```
There are several things to unpack here. `one` is both the name of the executable file generated, and the name of the CMake target created (you'll hear a lot more about targets soon, I promise). The source file list comes next, and you can list as many as you'd like. CMake is smart, and will only compile source file extensions. The headers will be, for most intents and purposes, ignored; the only reason to list them is to get them to show up in IDEs. Targets show up as folders in many IDEs. More about the general build system and targets is available at «cmake:buildsystem».
There are several things to unpack here. `one` is both the name of the executable file generated, and the name of the CMake target created (you'll hear a lot more about targets soon, I promise). The source file list comes next, and you can list as many as you'd like. CMake is smart, and will only compile source file extensions. The headers will be, for most intents and purposes, ignored; the only reason to list them is to get them to show up in IDEs. Targets show up as folders in many IDEs. More about the general build system and targets is available at {{ cmake.format('buildsystem') }}.
## Making a library
Making a library is done with «command:`add_library`», and is just about as simple:
Making a library is done with {{ command.format('add_library') }}, and is just about as simple:
```cmake
add_library(one STATIC two.cpp three.h)
@ -128,7 +132,7 @@ Now we've specified a target, how do we add information about it? For example, m
target_include_directories(one PUBLIC include)
```
«command:`target_include_directories`» adds an include directory to a target. `PUBLIC` doesn't mean much for an executable; for a library it lets CMake know that any targets that link to this target must also need that include directory. Other options are`PRIVATE`(only affect the current target, not dependencies), and `INTERFACE` (only needed for dependencies).
{{ command.format('target_include_directories') }} adds an include directory to a target. `PUBLIC` doesn't mean much for an executable; for a library it lets CMake know that any targets that link to this target must also need that include directory. Other options are `PRIVATE` (only affect the current target, not dependencies), and `INTERFACE` (only needed for dependencies).
We can then chain targets:
@ -137,7 +141,7 @@ add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
```
«command:`target_link_libraries`» is probably the most useful and confusing command in CMake. It takes a target (`another`) and adds a dependency if a target is given. If no target of that name (`one`) exists, then it adds a link to a library called `one` on your path (hence the name of the command). Or you can give it a full path to a library. Or a linker flag. Just to add a final bit of confusion, classic CMake allowed you to skip the keyword selection of `PUBLIC`, etc. If this was done on a target, you'll get an error if you try to mix styles further down the chain.
{{ command.format('target_link_libraries') }} is probably the most useful and confusing command in CMake. It takes a target (`another`) and adds a dependency if a target is given. If no target of that name (`one`) exists, then it adds a link to a library called `one` on your path (hence the name of the command). Or you can give it a full path to a library. Or a linker flag. Just to add a final bit of confusion, classic CMake allowed you to skip the keyword selection of `PUBLIC`, etc. If this was done on a target, you'll get an error if you try to mix styles further down the chain.
Focus on using targets everywhere, and keywords everywhere, and you'll be fine.

View File

@ -6,7 +6,7 @@ CMake allows you to access CMake variables from your code using `configure_file`
This functionality is used quite frequently; for example, on `Version.h.in`:
#### Version.h.in
### Version.h.in
```cpp
#pragma once
@ -18,7 +18,7 @@ This functionality is used quite frequently; for example, on `Version.h.in`:
#define MY_VERSION "@PROJECT_VERSION@"
```
#### CMake lines:
### CMake lines:
```cmake
configure_file (

View File

@ -3,7 +3,11 @@
This is a simple yet complete example of a proper CMakeLists. For this program, we have one library (MyLibExample) with a header file and a source file,
and one application, MyExample, with one source file.
[import:'main', lang:'cmake'](../../examples/simple-project/CMakeLists.txt)
```{literalinclude} ../../examples/simple-project/CMakeLists.txt
:start-after: "[main]"
:end-before: "[main]"
:language: cmake
```
The complete example is available in [examples folder](https://gitlab.com/CLIUtils/modern-cmake/tree/master/examples/simple-project).

View File

@ -2,7 +2,7 @@
## Control flow
CMake has an «command:`if`» statement, though over the years it has become rather complex. There are a series of all caps keywords you can use inside an if statement, and you can often refer to variables by either directly by name or using the `${}` syntax (the if statement historically predates variable expansion). An example if statement:
CMake has an {{ command.format('if') }} statement, though over the years it has become rather complex. There are a series of all caps keywords you can use inside an if statement, and you can often refer to variables by either directly by name or using the `${}` syntax (the if statement historically predates variable expansion). An example if statement:
```cmake
if(variable)
@ -13,7 +13,7 @@ endif()
# If variable does not expand to one of the above, CMake will expand it then try again
```
Since this can be a little confusing if you explicitly put a variable expansion, like `${variable}`, due to the potential expansion of an expansion, a policy («policy:CMP0054») was added in CMake 3.1+ that keeps a quoted expansion from being expanded yet again. So, as long as the minimum version of CMake is 3.1+, you can do:
Since this can be a little confusing if you explicitly put a variable expansion, like `${variable}`, due to the potential expansion of an expansion, a policy ({{ policy.format('CMP0054') }}) was added in CMake 3.1+ that keeps a quoted expansion from being expanded yet again. So, as long as the minimum version of CMake is 3.1+, you can do:
```cmake
if("${variable}")
@ -29,9 +29,9 @@ There are a variety of keywords as well, such as:
- Binary: `STREQUAL`, `AND`, `OR`, `MATCHES` (regular expression), `VERSION_LESS`, `VERSION_LESS_EQUAL` (CMake 3.7+), etc.
- Parentheses can be used to group
## «cmake:generator-expressions»
## {{ cmake.format('generator-expressions') }}
«cmake:generator-expressions» are really powerful, but a bit odd and specialized. Most CMake commands happen at configure time, include the if statements seen above. But what if you need logic to occur at build time or even install time? Generator expressions were added for this purpose.[^1] They are evaluated in target properties.
{{ cmake.format('generator-expressions') }} are really powerful, but a bit odd and specialized. Most CMake commands happen at configure time, include the if statements seen above. But what if you need logic to occur at build time or even install time? Generator expressions were added for this purpose.[^1] They are evaluated in target properties.
The simplest generator expressions are informational expressions, and are of the form `$<KEYWORD>`; they evaluate to a piece of information relevant for the current configuration. The other form is `$<KEYWORD:value>`, where `KEYWORD` is a keyword that controls the evaluation, and value is the item to evaluate (an informational expression keyword is allowed here, too). If KEYWORD is a generator expression or variable that evaluates to 0 or 1, `value` is substituted
if 1 and not if 0. You can nest generator expressions, and you can use variables to make reading nested variables bearable. Some
@ -64,7 +64,7 @@ target_include_directories(
## Macros and Functions
You can define your own CMake «command:`function`» or «command:`macro`» easily. The only difference between a function and a macro is scope; macros don't have one. So, if you set a variable in a function and want it to be visible outside, you'll need `PARENT_SCOPE`. Nesting functions therefore is a bit tricky, since you'll have to explicitly set the variables you want visible to the outside world to `PARENT_SCOPE` in each function. But, functions don't "leak" all their variables like macros do. For the
You can define your own CMake {{ command.format('function') }} or {{ command.format('macro') }} easily. The only difference between a function and a macro is scope; macros don't have one. So, if you set a variable in a function and want it to be visible outside, you'll need `PARENT_SCOPE`. Nesting functions therefore is a bit tricky, since you'll have to explicitly set the variables you want visible to the outside world to `PARENT_SCOPE` in each function. But, functions don't "leak" all their variables like macros do. For the
following examples, I'll use functions.
An example of a simple function is as follows:
@ -90,7 +90,7 @@ If you want positional arguments, they are listed explicitly, and all other argu
## Arguments
CMake has a named variable system that you've already seen in most of the build in CMake functions. You can use it with the «command:`cmake_parse_arguments`» function. If you want to support a version of CMake less than 3.5, you'll want to also include the «module:CMakeParseArguments» module, which is where it used to live before becoming a built in command. Here is an example of how to use it:
CMake has a named variable system that you've already seen in most of the build in CMake functions. You can use it with the {{ command.format('cmake_parse_arguments') }} function. If you want to support a version of CMake less than 3.5, you'll want to also include the {{ module.format('CMakeParseArguments') }} module, which is where it used to live before becoming a built in command. Here is an example of how to use it:
```cmake
function(COMPLEX)
@ -108,7 +108,7 @@ complex(SINGLE ONE_VALUE value MULTI_VALUES some other values)
Inside the function after this call, you'll find:
```cmake
```
COMPLEX_PREFIX_SINGLE = TRUE
COMPLEX_PREFIX_ANOTHER = FALSE
COMPLEX_PREFIX_ONE_VALUE = "value"

View File

@ -45,8 +45,10 @@ Which is better, but still doesn't have the sort of explicit control that compil
You can find more information about the final two methods on [Craig Scott's useful blog post][crascit].
{% hint style='danger' %}
:::{danger}
Don't set manual flags yourself. You'll then become responsible for mainting correct flags for every release of every compiler, error messages for unsupported compilers won't be useful, and some IDEs might not pick up the manual flags.
{% endhint %}
:::
[crascit]: https://crascit.com/2015/03/28/enabling-cxx11-in-cmake/

View File

@ -22,13 +22,13 @@ You can control how files show up in each folder with regular expressions or exp
## Folders for files
You can also control how the folders inside targets appear. There are two ways, both using the «command:source_group» command. The traditional way is
You can also control how the folders inside targets appear. There are two ways, both using the {{ command.format('source_group') }} command. The traditional way is
```cmake
source_group("Source Files\\New Directory" REGULAR_EXPRESSION ".*\\.c[ucp]p?")
```
You can explicitly list files with `FILES`, or use a `REGULAR_EXPRESSION`. This way you have complete control over the folder structure. However, if your on-disk layout is well designed, you might just want to mimic that. In CMake 3.8+, you can do so very easily with a new version of the «command:source_group» command:
You can explicitly list files with `FILES`, or use a `REGULAR_EXPRESSION`. This way you have complete control over the folder structure. However, if your on-disk layout is well designed, you might just want to mimic that. In CMake 3.8+, you can do so very easily with a new version of the {{ command.format('source_group') }} command:
```cmake
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/base/dir" PREFIX "Header Files" FILES ${FILE_LIST})

View File

@ -1,8 +1,8 @@
# Useful Modules
There are a ton of useful modules in CMake's «cmake:modules» collection; but some of them are more useful than others. Here are a few highlights.
There are a ton of useful modules in CMake's {{ cmake.format('modules') }} collection; but some of them are more useful than others. Here are a few highlights.
## «module:CMakeDependentOption»
## {{ module.format('CMakeDependentOption') }}
This adds a command `cmake_dependent_option` that sets an option based on another set of variables being true. It looks like this:
@ -29,12 +29,12 @@ endif()
Note that `BUILD_TESTING` is a better way to check for testing being enabled if you use `include(CTest)`, since it is defined for you. This is just an example of `CMakeDependentOption`.
## «module:CMakePrintHelpers»
## {{ module.format('CMakePrintHelpers') }}
This module has a couple of handy output functions. `cmake_print_properties` lets you easily print properties.
And `cmake_print_variables` will print the names and values of any variables you give it.
## «module:CheckCXXCompilerFlag»
## {{ module.format('CheckCXXCompilerFlag') }}
This checks to see if a flag is supported. For example:
@ -48,7 +48,7 @@ Note that `OUTPUT_VARIABLE` will also appear in the configuration printout, so c
This is just one of many similar modules, such as `CheckIncludeFileCXX`, `CheckStructHasMember`, `TestBigEndian`, and `CheckTypeSize` that allow you
to check for information about the system (and you can communicate that to your source code).
## «command:`try_compile`»/«command:`try_run`»
## {{ command.format('try_compile') }}/{{ command.format('try_run') }}
This is not exactly a module, but is crucial to many of the modules listed above. You can attempt to compile (and possibly run) a bit of code at configure time. This can allow you to get information about the capabilities of your system. The basic syntax is:
@ -63,9 +63,9 @@ try_compile(
There are lots of options you can add, like `COMPILE_DEFINITIONS`. In CMake 3.8+, this will honor the CMake C/C++/CUDA standard settings. If you use `try_run` instead, it will run the resulting program and give you the output in `RUN_OUTPUT_VARIABLE`.
## «module:FeatureSummary»
## {{ module.format('FeatureSummary') }}
This is a fairly useful but rather odd module. It allows you to print out a list of packages what were searched for, as well as any options you explicitly mark. It's partially but not completely tied into «command:`find_package`». You first include the module, as always:
This is a fairly useful but rather odd module. It allows you to print out a list of packages what were searched for, as well as any options you explicitly mark. It's partially but not completely tied into {{ command.format('find_package') }}. You first include the module, as always:
```cmake
include(FeatureSummary)
@ -80,7 +80,7 @@ set_package_properties(OpenMP PROPERTIES
PURPOSE "This is what it does in my package")
```
You can also set the `TYPE` of a package to `RUNTIME`, `OPTIONAL`, `RECOMMENDED`, or `REQUIRED`; you can't, however, lower the type of a package; if you have already added a `REQUIRED` package through «command:`find_package`» based on an option, you'll see it listed as `REQUIRED`.
You can also set the `TYPE` of a package to `RUNTIME`, `OPTIONAL`, `RECOMMENDED`, or `REQUIRED`; you can't, however, lower the type of a package; if you have already added a `REQUIRED` package through {{ command.format('find_package') }} based on an option, you'll see it listed as `REQUIRED`.
And, you can mark any options as part of the feature summary. If you choose the same name as a package, the two interact with each other.

View File

@ -37,7 +37,7 @@ You can pretty easily find `Find*.cmake`'s for this and other libraries that you
## Interprocedural optimization
«prop:tgt:INTERPROCEDURAL*OPTIMIZATION», best known as \_link time optimization* and the `-flto` flag, is available on very recent versions of CMake. You can turn this on with «variable:CMAKE_INTERPROCEDURAL_OPTIMIZATION» (CMake 3.9+ only) or the «prop:tgt:INTERPROCEDURAL_OPTIMIZATION» property on targets. Support for GCC and Clang was added in CMake 3.8. If you set `cmake_minimum_required(VERSION 3.9)` or better (see «policy:CMP0069»), setting this to `ON` on a target is an error if the compiler doesn't support it. You can use check_ipo_supported(), from the built-in «module:CheckIPOSupported» module, to see if support is available before hand. An example of 3.9 style usage:
{{ prop.format('tgt', 'INTERPROCEDURAL_OPTIMIZATION') }}, best known as _link time optimization_ and the `-flto` flag, is available on very recent versions of CMake. You can turn this on with {{ variable.format('CMAKE_INTERPROCEDURAL_OPTIMIZATION') }} (CMake 3.9+ only) or the {{ prop.format('tgt', 'INTERPROCEDURAL_OPTIMIZATION') }} property on targets. Support for GCC and Clang was added in CMake 3.8. If you set `cmake_minimum_required(VERSION 3.9)` or better (see {{ policy.format('CMP0069') }}, setting this to `ON` on a target is an error if the compiler doesn't support it. You can use check_ipo_supported(), from the built-in {{ module.format('CheckIPOSupported') }} module, to see if support is available before hand. An example of 3.9 style usage:
```cmake
include(CheckIPOSupported)

View File

@ -31,7 +31,7 @@ This is the command line for running clang-tidy, as a list (remember, a semicolo
Here is a simple example of using Clang-Tidy:
```term
```bash
~/package # cmake -S . -B build-tidy -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);-fix"
~/package # cmake --build build -j 1
```
@ -62,13 +62,13 @@ This is an example for using include what you use. First, you'll need to have
the tool, such as in a docker container or with brew (macOS) with `brew install include-what-you-use`. Then, you can pass this into your build without
modifying the source:
```term
```bash
~/package # cmake -S . -B build-iwyu -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=include-what-you-use
```
Finally, you can collect the output and (optionally) apply the fixes:
```term
```bash
~/package # cmake --build build-iwyu 2> iwyu.out
~/package # fix_includes.py < iwyu.out
```
@ -85,7 +85,7 @@ Clang-format doesn't really have an integration with CMake, unfortunately. You c
The following two line would do that in a git repository in bash (assuming you have a `.clang-format` file):
```term
gitbook $ git ls-files -- '*.cpp' '*.h' | xargs clang-format -i -style=file
gitbook $ git diff --exit-code --color
```bash
$ git ls-files -- '*.cpp' '*.h' | xargs clang-format -i -style=file
$ git diff --exit-code --color
```

View File

@ -1,8 +1,10 @@
# Exporting
{% hint style='danger' %}
:::{danger}
The default behavior for exporting changed in CMake 3.15. Since changing files in a user's home directory is considered "surprising" (and it is, which is why this chapter exists), it is no longer the default behavior. If you set a minimum or maximum CMake version of 3.15 or later, this will no longer happen unless you set `CMAKE_EXPORT_PACKAGE_REGISTRY` as shown below.
{% endhint %}
:::
There are three ways to access a project from another project: subdirectory, exported build directories, and installing. To use the build directory of one project in another project, you will need to export targets. Exporting targets is needed for a proper install; allowing the build directory to be used is just two added lines. It is not generally a way to work that I would recommend, but can be useful for development and as way to prepare the installation procedure discussed later.

View File

@ -1,12 +1,14 @@
# Installing CMake
{% hint style='tip' %}
:::{tip}
Your CMake version should be newer than your compiler. It should be newer than the libraries you are using (especially Boost). New versions work better for everyone.
{% endhint %}
:::
If you have a built in copy of CMake, it isn't special or customized for your system. You can easily install a new one instead, either on the system level or the user level. Feel free to instruct your users here if they complain about a CMake requirement being set too high. Especially if they want 3.1+ support. Maybe even if they want 3.30+ support...
#### Quick list (more info on each method below)
## Quick list (more info on each method below)
Ordered by author preference:
@ -34,24 +36,24 @@ You can [download CMake from KitWare][download]. This is how you will probably g
On Linux, there are several options. Kitware provides a [Debian/Ubuntu apt repository][apt], as well as [snap packages][snap]. There are universal Linux binaries provided, but you'll need to pick an install location. If you already use `~/.local` for user-space packages, the following single line command[^1] will get CMake for you [^2]:
{% term %}
```bash
~ $ wget -qO- "https://cmake.org/files/v3.30/cmake-3.30.2-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C ~/.local
{% endterm %}
```
The names changed in 3.20; older releases had names like `cmake-3.19.7-Linux-x86_64.tar.gz`. If you just want a local folder with CMake only:
{% term %}
```bash
~ $ mkdir -p cmake-3.30 && wget -qO- "https://cmake.org/files/v3.30/cmake-3.30.2-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C cmake-3.30
~ $ export PATH=`pwd`/cmake-3.30/bin:$PATH
{% endterm %}
```
You'll obviously want to append to the PATH every time you start a new terminal, or add it to your `.bashrc` or to an [LMod][] system.
And, if you want a system install, install to `/usr/local`; this is an excellent choice in a Docker container, for example on GitLab CI. Do not try it on a non-containerized system.
{% term %}
```bash
docker $ wget -qO- "https://cmake.org/files/v3.30/cmake-3.30.2-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local
{% endterm %}
```
If you are on a system without wget, replace `wget -qO-` with `curl -s`.
@ -63,7 +65,8 @@ Here are some common build environments and the CMake version you'll find on the
### Windows
[![Winget package](https://repology.org/badge/version-for-repo/winget/cmake.svg)][winget]
[The winget package][winget] is a good way to get CMake. Other options:
[![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/cmake.svg)][chocolatey]
[![MSYS2 mingw package](https://repology.org/badge/version-for-repo/msys2_mingw/cmake.svg)][msys2]
[![MSYS2 msys2 package](https://repology.org/badge/version-for-repo/msys2_msys2/cmake.svg)][msys2]
@ -149,8 +152,8 @@ Also see [pkgs.org/download/cmake](https://pkgs.org/download/cmake).
[This][pypi] is also provided as an official package, maintained by the authors of CMake at KitWare and several PyPA members, including myself. It's now supported on special architectures, like PowerPC on Linux and Apple Silicon on macOS, and on MUSL systems like Alpine too. If you have pip (Python's package installer), you can do:
```term
gitbook $ pip install cmake
```bash
pip install cmake
```
And as long as a binary exists for your system, you'll be up-and-running almost immediately. If a binary doesn't exist, it will try to use KitWare's `scikit-build` package to build, and will require an older copy of CMake to build. So only use this system if binaries exist, which is most of the time.
@ -159,10 +162,13 @@ This has the benefit of respecting your current virtual environment, as well. It
This also, of course, works with pipx. So you can even use `pipx run cmake` to run CMake in a disposable virtual environment, without any setup - and this works out-of-the-box on GitHub Actions, since `pipx` is a supported package manager there!
{% hint style='info' %}
:::{tip}
Personally, on Linux, I put versions of CMake in folders, like `/opt/cmake312` or `~/opt/cmake312`, and then add them to [LMod][]. See [`envmodule_setup`][envmodule_setup] for help setting up an LMod system on macOS or Linux. It takes a bit to learn, but is a great way to manage package and compiler versions.
[envmodule_setup]: https://github.com/CLIUtils/envmodule_setup
{% endhint %}
:::
[^1]: I assume this is obvious, but you are downloading and running code, which exposes you to a man in the middle attack. If you are in a critical environment, you should download the file and check the checksum. (And, no, simply doing this in two steps does not make you any safer, only a checksum is safer).
[^2]: If you don't have a `.local` in your home directory, it's easy to start. Just make the folder, then add `export PATH="$HOME/.local/bin:$PATH"` to your `.bashrc` or `.bash_profile` or `.profile` file in your home directory. Now you can install any packages you build to `-DCMAKE_INSTALL_PREFIX=~/.local` instead of `/usr/local`!

View File

@ -8,11 +8,10 @@ This is an abbreviated version of the CMake changelog with just the highlights f
- `$comment` supported in preset files
- `cmake -LR <regex>` to search the cache
## [CMake 3.30][]: C++26
This release adds C++26 support and a way to get the latest supported standard
for a compiler. This release makes a lot of small changes not listed below,
for a compiler. This release makes a lot of small changes not listed below,
like better TLS support, some generator expression updates, and some schema
updates.
@ -25,7 +24,6 @@ updates.
- FindBoost removed
- Visual Studio 2008 support removed
## [CMake 3.29][]: Build before testing
Finally you can make the `test` target depend on `ALL`, meaning `cmake --build
@ -299,7 +297,7 @@ fixes were implemented, especially to newer features, such as to FindPython, Fin
- Initially released [November 26, 2019](https://blog.kitware.com/cmake-3-16-0-available-for-download/)
- Added support for Objective C and Objective C++ languages
- Support for precompiling headers, with `target_precompile_headers`
- Support for "Unity" or "Jumbo" builds (merging source files) with «variable:CMAKE_UNITY_BUILD»
- Support for "Unity" or "Jumbo" builds (merging source files) with {{ variable.format('CMAKE_UNITY_BUILD') }}
- CTest: Can now skip based on regex, expand lists
- Several new features to control RPath.
- Generator expressions work in more places, like build and install paths
@ -311,13 +309,13 @@ This release has many smaller polishing changes, include several of improvements
module. `export(PACKAGE)` has drastically changed; it now no longer touches `$HOME/.cmake` by default (if CMake Minimum version is 3.15 or higher), and requires an extra step if a user wants to use it. This is generally less surprising.
- Initially released [July 17, 2019](https://blog.kitware.com/cmake-3-15-0-available-for-download/)
- «envvar:CMAKE_GENERATOR» environment variable added to control default generator
- {{ envvar.format('CMAKE_GENERATOR') }} environment variable added to control default generator
- Multiple target support in build mode, `cmake . --build --target a b`
- Shortcut `-t` for `--target`
- Install support, `cmake . --install`, does not invoke the build system
- Support for `--loglevel` and `NOTICE`, `VERBOSE`, `DEBUG`, and `TRACE` for `message`
- The «command:list» command gained `PREPEND`, `POP_FRONT`, and `POP_BACK`
- «command:execute_process» gained `COMMAND_ECHO` option («variable:CMAKE_EXECUTE_PROCESS_COMMAND_ECHO») allows you to automatically echo commands before running them
- The {{ command.format('list') }} command gained `PREPEND`, `POP_FRONT`, and `POP_BACK`
- {{ command.format('execute_process') }} gained `COMMAND_ECHO` option ({{ variable.format('CMAKE_EXECUTE_PROCESS_COMMAND_ECHO') }}) allows you to automatically echo commands before running them
- Several Ninja improvements, include SWIFT language support
- Compiler and list improvements to generator expressions
@ -329,8 +327,8 @@ Quite a few more find packages produce targets. The new Visual Studio 16 2019 ge
- Initially released [March 14, 2019](https://blog.kitware.com/cmake-3-14-0-available-for-download/)
- The cmake `--build` command gained `-v/--verbose`, to use verbose builds if your build tool supports it
- The FILE command gained `CREATE_LINK`, `READ_SYMLINK`, and `SIZE`
- «command:get*filename_component» gained `LAST_EXT` and `NAME_WLE` to access just the \_last* extension on a file, which would get `.zip` on a file such as `version.1.2.zip` (very handy!)
- You can see if a variable is defined in the CACHE with `DEFINED CACHE{VAR}` in an «command:if» statement.
- {{ command.format('get_filename_component') }} gained `LAST_EXT` and `NAME_WLE` to access just the _last_ extension on a file, which would get `.zip` on a file such as `version.1.2.zip` (very handy!)
- You can see if a variable is defined in the CACHE with `DEFINED CACHE{VAR}` in an {{ command.format('if') }} statement.
- `BUILD_RPATH_USE_ORIGIN` and CMake version were added to improve handling of RPath in the build directory.
- The CMake server mode is now being replaced with a file API, starting in this release. Will affect IDEs in the long run.

View File

@ -8,24 +8,23 @@ Unless otherwise noted, you should always make a build directory and build from
Here's the Classic CMake Build Procedure (TM):
{% term %}
```bash
~/package $ mkdir build
~/package $ cd build
~/package/build $ cmake ..
~/package/build $ make
{% endterm %}
```
You can replace the make line with `cmake --build .` if you'd like, and it will call `make` or whatever build tool you are using. If you are using a newer version of CMake (which you usually should be, except for checking compatibility with older CMake), you can instead do this:
{% term %}
```bash
~/package $ cmake -S . -B build
~/package $ cmake --build build
{% endterm %}
```
Any _one_ of these commands will install:
{% term %}
```bash
# From the build directory (pick one)
~/package/build $ make install
@ -37,7 +36,7 @@ Any _one_ of these commands will install:
~/package $ make -C build install
~/package $ cmake --build build --target install
~/package $ cmake --install build # CMake 3.15+ only
{% endterm %}
```
So which set of methods should you use? As long as you _do not forget_ to type the build directory as the argument, staying out of the build directory is shorter, and making source changes is easier from the source directory. You should try to get used to using `--build`, as that will free you from using only `make` to build. Note that working from the build directory is historically much more common, and some tools and commands (including CTest <3.20) still require running from the build directory.
@ -49,9 +48,9 @@ If you use `cmake --build` instead of directly calling the underlying build syst
Selecting a compiler must be done on the first run in an empty directory. It's not CMake syntax per se, but you might not be familiar with it. To pick Clang:
{% term %}
```bash
~/package/build $ CC=clang CXX=clang++ cmake ..
{% endterm %}
```
That sets the environment variables in bash for CC and CXX, and CMake will respect those variables. This sets it just for that one line, but that's the only time you'll need those; afterwards CMake continues to use the paths it deduces from those values.
@ -59,9 +58,9 @@ That sets the environment variables in bash for CC and CXX, and CMake will respe
You can build with a variety of tools; `make` is usually the default. To see all the tools CMake knows about on your system, run
{% term %}
```bash
~/package/build $ cmake --help
{% endterm %}
```
And you can pick a tool with `-G"My Tool"` (quotes only needed if spaces are in the tool name). You should pick a tool on your first CMake call in a directory, just like the compiler. Feel free to have several build directories, like `build/` and `buildXcode`.
You can set the environment variable `CMAKE_GENERATOR` to control the default generator (CMake 3.15+).
@ -75,10 +74,10 @@ You set options in CMake with `-D`. You can see a list of options with `-L`, or
Although not all build tools support it, you can get verbose builds (pick one):
{% term %}
```bash
~/package $ cmake --build build --verbose # CMake 3.14+ only
~/package/build $ VERBOSE=1 make
{% endterm %}
```
You can actually write `make VERBOSE=1`, and make will also do the right thing, though that's a feature of `make` and not the command line in general.

View File

@ -77,7 +77,7 @@ In CMake 3.24, the architectures values have been extended to support user frien
Using targets should work similarly to CXX, but there's a problem. If you include a target that includes compiler options (flags), most of the time, the options will not be protected by the correct includes (and the chances of them having the correct CUDA wrapper is even smaller). Here's what a correct compiler options line should look like:
```cmake
"$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CXX>>:-fopenmp>$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CUDA>>:-Xcompiler=-fopenmp>"
set(opt "$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CXX>>:-fopenmp>$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CUDA>>:-Xcompiler=-fopenmp>")
```
However, if you using almost any find_package, and using the Modern CMake methods of targets and inheritance, everything will break. I've learned that the hard way.

View File

@ -26,8 +26,10 @@ endif()
target_link_libraries(MyTarget PUBLIC OpenMP::OpenMP_CXX)
```
{% hint style='danger' %}
Warning: CMake < 3.4 has a bug in the Threads package that requires you to have the `C` language enabled.
{% endhint %}
:::{danger}
CMake < 3.4 has a bug in the Threads package that requires you to have the `C` language enabled.
:::
[openmp]: https://cmake.org/cmake/help/latest/module/FindOpenMP.html

View File

@ -8,7 +8,11 @@ Most importantly, there are _lots of improvements_ in CMake support in more rece
ROOT 6.10+ supports config file discovery, so you can just do:
[import:'find_package', lang:'cmake'](../../examples/root-simple/CMakeLists.txt)
```{literalinclude} ../../examples/root-simple/CMakeLists.txt
:start-after: "[find_package]"
:end-before: "[find_package]"
:language: cmake
```
to attempt to find ROOT. If you don't have your paths set up, you can pass `-DROOT_DIR=$ROOTSYS/cmake` to find ROOT. (But, really, you should source `thisroot.sh`).
@ -18,7 +22,11 @@ ROOT 6.12 and earlier do not add the include directory for imported targets. ROO
To link, just pick the libraries you want to use:
[import:'add_and_link', lang:'cmake'](../../examples/root-simple/CMakeLists.txt)
```{literalinclude} ../../examples/root-simple/CMakeLists.txt
:start-after: "[add_and_link]"
:end-before: "[add_and_link]"
:language: cmake
```
If you'd like to see the default list, run `root-config --libs` on the command line. In Homebrew ROOT 6.18 this would be:
@ -48,7 +56,11 @@ ROOT [provides a utility](https://root.cern.ch/how/integrate-root-my-project-cma
Here's what it would look like:
[import:'core', lang:'cmake'](../../examples/root-usefile/CMakeLists.txt)
```{literalinclude} ../../examples/root-usefile/CMakeLists.txt
:start-after: "[core]"
:end-before: "[core]"
:language: cmake
```
## Components

View File

@ -2,8 +2,8 @@
If you want to add a Git repository on the same service (GitHub, GitLab, BitBucket, etc), the following is the correct Git command to set that up as a submodule in the `extern` directory:
```term
gitbook $ git submodule add ../../owner/repo.git extern/repo
```bash
$ git submodule add ../../owner/repo.git extern/repo
```
The relative path to the repo is important; it allows you to keep the same access method (ssh or https) as the parent repository. This works very well in most ways. When you are inside the submodule, you can treat it just like a normal repo, and when you are in the parent repository, you can "add" to change the current commit pointer.
@ -43,7 +43,7 @@ add_subdirectory(extern/repo)
Or, you can build an interface library target yourself if it is a header only project. Or, you can use `find_package` if that is supported, probably preparing the initial search directory to be the one you've added (check the docs or the file for the `Find*.cmake` file you are using). You can also include a CMake helper file directory if you append to your `CMAKE_MODULE_PATH`, for example to add `pybind11`'s improved `FindPython*.cmake` files.
### Bonus: Git version number
## Bonus: Git version number
Move this to Git section:

View File

@ -6,7 +6,7 @@ GoogleTest and GoogleMock are classic options; personally, I personally would re
To use this method, just checkout GoogleTest as a submodule:[^1]
```cmake
```bash
git submodule add --branch=release-1.8.0 ../../google/googletest.git extern/googletest
```

View File

@ -4,32 +4,44 @@ This is an example of building a module that includes a dictionary in CMake. Ins
ROOT suggested flags, we will manually add threading via `find_package`, which is the only
important flag in the list on most systems.
#### examples/root-dict/CMakeLists.txt
## examples/root-dict/CMakeLists.txt
[import:'main', lang:'cmake'](CMakeLists.txt)
```{literalinclude} CMakeLists.txt
:start-after: "[main]"
:end-before: "[main]"
:language: cmake
```
## Supporting files
This is just a simple-as-possible class definition, with one method:
#### examples/root-dict/DictExample.cxx
### examples/root-dict/DictExample.cxx
[import, lang:'c_cpp'](DictExample.cxx)
```{literalinclude} DictExample.cxx
:language: cpp
```
#### examples/root-dict/DictExample.h
### examples/root-dict/DictExample.h
[import, lang:'c_cpp'](DictExample.h)
```{literalinclude} DictExample.h
:language: cpp
```
We need a `LinkDef.h`, as well.
#### examples/root-dict/DictLinkDef.h
### examples/root-dict/DictLinkDef.h
[import, lang:'c_cpp'](DictLinkDef.h)
```{literalinclude} DictLinkDef.h
:language: cpp
```
## Testing it
This is an example of a macro that tests the correct generation from the files listed above.
#### examples/root-dict/CheckLoad.C
### examples/root-dict/CheckLoad.C
[import, lang:'c_cpp'](CheckLoad.C)
```{literalinclude} CheckLoad.C
:language: cpp
```

View File

@ -2,10 +2,16 @@
This is a minimal example of a ROOT project using the target system and without a dictionary.
#### examples/root-simple/CMakeLists.txt
## examples/root-simple/CMakeLists.txt
[import:'main', lang:'cmake'](CMakeLists.txt)
```{literalinclude} CMakeLists.txt
:start-after: "[main]"
:end-before: "[main]"
:language: cmake
```
#### examples/root-simple/SimpleExample.cxx
## examples/root-simple/SimpleExample.cxx
[import](SimpleExample.cxx)
```{literalinclude} SimpleExample.cxx
:language: cpp
```

View File

@ -2,10 +2,16 @@
This is a minimal example of a ROOT project using the UseFile system and without a dictionary.
#### examples/root-usefile/CMakeLists.txt
## examples/root-usefile/CMakeLists.txt
[import:'main', lang:'cmake'](CMakeLists.txt)
```{literalinclude} CMakeLists.txt
:start-after: "[main]"
:end-before: "[main]"
:language: cmake
```
#### examples/root-usefile/SimpleExample.cxx
## examples/root-usefile/SimpleExample.cxx
[import](SimpleExample.cxx)
```{literalinclude} SimpleExample.cxx
:language: cpp
```

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

4091
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
{
"name": "modern-cmake",
"version": "1.0.0",
"dependencies": {
"gitbook-cli": "2.2.0",
"gitbook-plugin-ace": "^0.3.2",
"gitbook-plugin-hints": "^1.0.2",
"gitbook-plugin-include-codeblock": "^3.2.2",
"gitbook-plugin-replace": "0.0.1",
"gitbook-plugin-term": "^0.5.1",
"svgexport": ">=0.4.2"
},
"scripts": {
"postinstall": "npx gitbook fetch 3.2.3 && npx gitbook install"
}
}

11656
pixi.lock generated Normal file

File diff suppressed because it is too large Load Diff

34
pixi.toml Normal file
View File

@ -0,0 +1,34 @@
[project]
authors = ["Henry Schreiner <henryschreineriii@gmail.com>"]
channels = ["conda-forge"]
description = "A book about modern CMake"
name = "modern-cmake"
platforms = ["linux-64", "osx-64", "osx-arm64"]
version = "0.1.0"
[feature.test.dependencies]
root_base = ">=6.26"
cmake = ">=3.15"
make = "*"
cxx-compiler = "*"
libboost-devel = "*"
git = "*"
[feature.test.tasks.test]
cmd = "cmake -Sexamples -Bbuild && cmake --build build -v && cmake --build build -t test"
[feature.build.dependencies]
python = "3.10"
jupyter-book = ">=1.0.2,<2"
imagemagick = "*"
make = "*"
[feature.build.tasks]
build = "jupyter-book build ."
pdf = "jupyter-book build . --builder pdflatex"
[environments]
test = ["test"]
build = ["build"]