mirror of
1
0
Fork 0

Adding some content

This commit is contained in:
Henry Fredrick Schreiner 2017-10-17 15:41:41 -04:00
parent bd542bd8c3
commit f04e3cbb9d
5 changed files with 286 additions and 2 deletions

View File

@ -6,5 +6,68 @@ An introduction to Modern CMake practices
---
People love to hate build systems.
Just watch the talks from CppCon17.
This raises the question: Why?
Certainly there are no shortage of problems when building.
But I think that, in 2017, we have a very good solution to quite a few of those problems.
It's CMake. Not CMake 2.8 though; that was released before C++11 even existed!
Nor the horible examples out there for CMake (even those posted on KitWare's own tutorials list).
I'm talking about Modern CMake. CMake 3.1+, maybe even CMake 3.9!
It's clean, powerful, and elegant, so you can spend most of your time coding, not addding lines to an unreadable, unmaintainable Make (Or CMake 2) file.
This document is a work in progress (really just a place holder).
In short, here are the most likely questions in your mind if you are considering Modern CMake:
## Why do I need a good build system?
Do any of the following apply to you?
* You want to avoid hard-coding paths
* You need to build a package on more than one computer
* You want to use CI (continious integration)
* You need to support diffent OSs (maybe even just flavors of Unix)
* You want to support multiple compilers
* You want to use an IDE, but maybe not all of the time
* You want to describe how your program is structured logically, not flags and commands
* You want to use a library
* You want to use tools, like Clang-Tidy, to help you code
* You want to use a debugger
If so, you'll benifit from a CMake-like build system.
## Why must the answer be CMake?
Build systems is a hot topic. Of course there are many options. But even a really good one, or one that re-uses a familar syntax, can't come close to CMake.
Why?
Support.
Every IDE supports CMake (or CMake supports that IDE).
More packages use CMake than any other system.
So, if you use a library that is designed to be included in your code, you have a choice: Make your own build system, or use one of of the provided ones, and that will almost always include CMake.
And that will quickly be the common denominator if you include multiple projects.
And, if you need a library that's preinstalled, the chances of it having a find cmake script or config cmake script are excellent.
## Why use a Modern CMake?
Around CMake 2.6-2.8, CMake started taking over. It was in most of the package managers for Linux OS's, and was being used in lots of packages.
Then Python 3 came out.
I know, this should have nothing whatsoever to do with CMake.
But it had a 3.
And it followed 2.
And it was a hard, ugly, transition that is still ongoing in some places, even today.
I believe that CMake 3 had the bad luck to follow Python 3.
Even though every version of CMake is insanely backward compatible, the 3 series was treated as if it was something new.
And so, you'll find OS's like CentOS7 with GCC 4.8, with almost-complete C++14 support, and CMake 2.8, which came out before C++11.
You really should *at least* use a version of CMake that came out after your compiler, since it needs to know compiler flags, etc, for that version.
And, since CMake will dumb itself down to the minimium required version in your CMake file, installing a new CMake, even system wide, is pretty safe.
You should *at least* install it locally.
It's easy (1-2 lines in many cases), and you'll find that 5 minutes of work will save you hundreds of lines and hours of CMakeLists.txt writing, and will be much easier to maintain in the long run.
This book tries to solve the problem of the poor examples and best practices that you'll find proliferating the web.
This document is a work in progress.

View File

@ -1,4 +1,23 @@
# Summary
* [Introduction](README.md)
## Making a CMakeLists
* [Installing CMake](chapters/install.md)
* [Running CMake](chapters/running.md)
* [Introduction to the basics](chapters/basics.md)
* [Adding features (like C++11)](chapters/features.md)
* [How to structure your project](chapters/structure.md)
* [Including small projects](chapters/smallinc.md)
* [Including large projects](chapters/largeinc.md)
* [Running other programs](chapters/programs.md)
* [Testing](chapters/testing.md)
* [Tidy and Format](chapters/testing.md)
* [IDEs](chapters/IDEs.md)
* [Debugging](chapters/IDEs.md)
## Specific use cases
* [CUDA](specifics/CUDA.md)
* [OpenMP](specifics/OpenMP.md)
* [Boost](specifics/Boost.md)
* [MPI](specifics/MPI.md)
* [ROOT](specifics/ROOT.md)

103
chapters/basics.md Normal file
View File

@ -0,0 +1,103 @@
# Introduction to the basics
## Minimum Version
Here's the first line of every CMakeLists.txt, which is the required name of the file CMake looks for:
```cmake
cmake_minimum_required(VERSION 3.1)
```
Let's mention a bit of CMake syntax. The function name 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. See the excellent documentation [here](https://cmake.org/cmake/help/v3.9/command/cmake_minimum_required.html), and use the dropdown to switch documentation between CMake versions.
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. A list of policies and versions is [here](https://cmake.org/cmake/help/v3.9/manual/cmake-policies.7.html).
## Setting a project
Now, every top-level CMake file will have the next line:
```cmake
project(MyProject VERSION 1.0
DESCRIPTION "Very nice project"
LANGUAGES CXX)
```
Now we see even more syntax. Strings are quoted, white space doesn't matter [^3], and the name of the project is the first argument (positional). All the keyword arguments here are optional. The version sets a bunch of variables, like `MyProject_VERSION` and `PROJECT_VERSION`. The languages are C, CXX, FORTRAN, and CUDA (CMake 3.7+). `C CXX` is the default. Docs [here](https://cmake.org/cmake/help/v3.9/command/project.html).
There's really nothing special about the project name. No targets are added at this point.
## Making an executable
Although libraries are much more interesting, and we'll spend most of our time with them, let's start with a simple executable.
```cmake
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 [here](https://cmake.org/cmake/help/v3.9/manual/cmake-buildsystem.7.html).
## Making a library
[Making a library](https://cmake.org/cmake/help/v3.9/command/add_library.html?highlight=add_library) that compiles is just about as simple:
```cmake
add_LIBRARY(one STATIC two.cpp three.h)
```
You get to pick a type of library, STATIC, SHARED, or MODULE. If you leave this choice off, the value of `BUILD_SHARED_LIBS` will be used to pick between STATIC and SHARED.
As you'll see in the following sections, often you'll need to make a fictional target, that is, one where nothing needs to be compiled, for example, for a header-only library. That is called an INTERFACE library, and is another choice; the only difference is it cannot be followed by filenames.
You can also make an `ALIAS` library with an existing library, which simply gives you a new name for a target. The one benefit to this is that you can make libraries with `::` in the name (which you'll see later). [^3]
## Targets are your friend
Now we've specified a target, how do we add information about it? For example, maybe it needs an include directory:
```cmake
target_include_directories(one PUBLIC include)
```
This [command](https://cmake.org/cmake/help/v3.9/command/target_include_directories.html?highlight=include_directories#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).
We can then chain targets:
```cmake
add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
```
[This](https://cmake.org/cmake/help/v3.9/command/target_link_libraries.html#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.
Focus on using targets everywhere, and keywords everywhere, and you'll be fine.
Targets can have include directories, linked libraries (or linked targets), compile options, compile definitions, compile features (see the C++11 chapter), and more. As you'll see in the two including projects chapters, you can often get targets (and always make targets) to represent all the libraries you use. Even things that are not true libraries, like OpenMP, can be represented with targets. This is why Modern CMake is great!
## Dive in
See if you can follow the following file. It makes a simple C++11 library and a program using it. No dependencies. I'll discuss more C++ standard options later, using the CMake 3.8 system for now.
```cmake
cmake_minimum_required(VERSION 3.8)
project(Calculator LANGUAGES CXX)
add_library(calclib STATIC src/calclib.cpp include/calc/lib.hpp)
target_include_directories(calclib PUBLIC include)
target_compile_features(calclib PUBLIC cxx_std_11)
add_executable(calc apps/calc.cpp)
target_link_libraries(calc PUBLIC calclib)
```
[^1]: In this book, I'll mostly avoid showing you the wrong way to do things; you can find plenty of examples of that online. I'll mention alternatives occasionally, but these are not recommended unless they are absolutely necessary; often they are just there to help you read older CMake code.
[^2]: You will sometimes see `FATAL_ERROR` here, that was needed to support nice failures when running this in CMake <2.6, which should not be a problem anymore.
[^3]: The `::` syntax was originally intended for `INTERFACE IMPORTED` libraries, which were explicitly supposed to be libraries defined outside the current project. But, because of this, most of the `target_*` commands don't work on `IMPORTED` libraries, making them hard to set up yourself. So don't use the `IMPORTED` keyword for now, and use an `ALIAS` target instead. There's a project in the works to fix this limitation for CMake 3.11.

57
chapters/running.md Normal file
View File

@ -0,0 +1,57 @@
# Running CMake
Before writing CMake, let's make sure you know how to run it to make things. This is true for almost all CMake projects, which is almost everything.
## Building a project
Unless otherwise noted, you should always make a build directory and build from there. You can technically do an in-source build, but you'll have to be careful not to overwrite files or add them to git, so just don't.
Here's the CMake Build Procedure (TM):
```bash
mkdir build
cd build
cmake ..
make
```
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. You can follow it by `make install` or `cmake --build . --target install` if you want to install it.
## Picking a compiler
Selecting a compiler must be done on the first run in an empty directory. It's not CMake syntax per-say, but you might not be familiar with it. To pick Clang:
```bash
CC=clang CXX=clang++ cmake ..
```
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.
## Picking a generator
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
```
cmake --help
```
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`.
## Setting options
You set options in CMake with `-D`. You can see a list of options with `-L`, or a list with human-readable help with `-LH`.
## Verbose and partial builds
Again, not really CMake, but if you are using a command line build tool like `make`, you can get verbose builds:
VERBOSE=1 make
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.
You can also build just a part of a build by specifying a target, such as the name of a library or executable you've defined in CMake, and make will just build that target.
## Standard options
These are common CMake options to most packages:
* `-DCMAKE_BUILD_TYPE=` Pick from Release, RelWithDebInfo, Debug, or sometimes more.
* `-DCMAKE_INSTALL_PREFIX=` The location to install to. System install on UNIX would often be `/usr/local` (the default), user directories are often `~/.local`, or you can pick a folder.

42
chapters/structure.md Normal file
View File

@ -0,0 +1,42 @@
# How to structure your project
The following information is biased. But in a good way, I think. I'm going to tell you how to structure the directories in your project. This is based on convention, but will help you:
* Easily read other projects following the same patters,
* Avoid a pattern that causes conflicts,
* Keep from muddling and complicating your build.
First, this is what your files should look like when you start if your project is creatively called `project`, with a library called `lib`, and a executable called `app`:
```
- project
- .gitignore
- README.md
- LICENCE.md
- CMakeLists.txt
- include
- project
- lib.hpp
- src
- CMakeLists.txt
- lib.cpp
- apps
- CMakeLists.txt
- app.cpp
- tests
- testlib.cpp
- docs
- Doxyfile.in
- extern
- googletest
- scripts
- helper.py
```
The names are not absolute; you'll see contention about `test/` vs. `tests/`, and the application folder may be called something else (or not exist for a library-only project). You'll also sometime see a `python` folder for python bindings, or a `cmake` folder for helper CMake files, like `Find<library>.cmake` files. But the basics are there.
Notice a few things already apparent; the `CMakeLists.txt` files are split up over all source directories, and are not in the include directories. This is because you should be able to copy the contents of the include directory to `/usr/include` or similar directly (except for configuration headers, which I go over in another chapter), and not have any extra files or cause any conflicts. That's also why there is a directory for your project inside the include directory.
Your `extern` folder should contain git submodules almost exclusively. That way, you can control the version of the dependencies explicitly, but still upgrade easily. See the Testing chapter for an example of adding a submodule.
You should have something like `/build*` in your `.gitignore`, so that users can make build directories in the source directory and use those to build. A few packages prohibit this, but it's much better than doing a true out-of-source build and having to type something different for each package you build.