blob: 60219e54b5dbd28b122422f75002e1acf19b0b5d [file] [log] [blame] [view]
---
title: Apache Mesos - CMake
layout: documentation
---
# Install CMake 3.7+
[cmake-download]: https://cmake.org/download/
## Linux
Install the latest version of CMake from [CMake.org][cmake-download].
A self-extracting tarball is available to make this process painless.
Currently, few of the common Linux flavors package a sufficient CMake
version. Ubuntu versions 12.04 and 14.04 package CMake 2;
Ubuntu 16.04 packages CMake 3.5. If you already installed cmake from packages,
you may remove it via: `apt-get purge cmake`.
The standard CentOS package is CMake 2, and unfortunately even the `cmake3`
package in EPEL is only CMake 3.6, you may remove them via:
`yum remove cmake cmake3`.
## Mac OS X
HomeBrew's CMake version is sufficient: `brew install cmake`.
## Windows
Download and install the MSI from [CMake.org][cmake-download].
**NOTE:** Windows needs CMake 3.8+, rather than 3.7+.
# Quick Start
The most basic way to build with CMake, with no configuration, is fairly
straightforward:
```
mkdir build
cd build
cmake ..
cmake --build .
```
The last step, `cmake --build .` can also take a `--target` command to build any
particular target (e.g. `mesos-tests`, or `tests` to build `mesos-tests`,
`libprocess-tests`, and `stout-tests`): `cmake --build . --target tests`. To
send arbitrary flags to the native build system underneath (e.g. `make`), append
the command with `-- <flags to be passed>`: `cmake --build . -- -j4`.
Also, `cmake --build` can be substituted by your build system of choice. For
instance, the default CMake generator on Linux produces GNU Makefiles, so after
configuring with `cmake ..`, you can just run `make tests` in the `build` folder
like usual. Similarly, if you configure with `-G Ninja` to use the Ninja
generator, you can then run `ninja tests` to build the `tests` target with
Ninja.
# Installable build
This example will build Mesos and install it into a custom prefix:
```
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/home/current_user/mesos
cmake --build . --target install
```
To additionally install `mesos-tests` executable and related test helpers
(this can be used to run Mesos tests against the installed binaries),
one can enable the `MESOS_INSTALL_TESTS` option.
To produce a set of binaries and libraries that will work after being
copied/moved to a different location, use `MESOS_FINAL_PREFIX`.
The example below employs both `MESOS_FINAL_PREFIX` and `MESOS_INSTALL_TESTS`.
On a build system:
```
mkdir build && cd build
cmake -DMESOS_FINAL_PREFIX=/opt/mesos -DCMAKE_INSTALL_PREFIX=/home/current_user/mesos -DMESOS_INSTALL_TESTS=ON
cmake --build . --target install
tar -czf mesos.tar.gz mesos -C /home/current_user
```
On a target system:
```
sudo tar -xf mesos.tar.gz -C /opt
# Run tests against Mesos installation
sudo /opt/mesos/bin/mesos-tests
# Start Mesos agent
sudo /opt/mesos/bin/mesos-agent --work-dir=/var/lib/mesos ...
```
# Supported options
See [configuration options](configuration/cmake.md).
# Examples
See [CMake By Example](cmake-examples.md).
# Documentation
The [CMake documentation][] is written as a reference module. The most commonly
used sections are:
* [buildsystem overview](https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html)
* [commands](https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html)
* [properties](https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html)
* [variables](https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html)
The wiki also has a set of [useful variables][].
[CMake documentation]: https://cmake.org/cmake/help/latest/
[useful variables]: https://cmake.org/Wiki/CMake_Useful_Variables
# Dependency graph
Like any build system, CMake has a dependency graph. The difference is
that targets in CMake's dependency graph are _much richer_ compared to other
build systems. CMake targets have the notion of 'interfaces', where build
properties are saved as part of the target, and these properties can be
inherited transitively within the graph.
For example, say there is a library `mylib`, and anything which links it must
include its headers, located in `mylib/include`. When building the library, some
private headers must also be included, but not when linking to it. When
compiling the executable `myprogram`, `mylib`'s public headers must be included,
but not its private headers. There is no manual step to add `mylib/include` to
`myprogram` (and any other program which links to `mylib`), it is instead
deduced from the public interface property of `mylib`. This is represented by
the following code:
```
# A new library with a single source file (headers are found automatically).
add_library(mylib mylib.cpp)
# The folder of private headers, not exposed to consumers of `mylib`.
target_include_directories(mylib PRIVATE mylib/private)
# The folder of public headers, added to the compilation of any consumer.
target_include_directories(mylib PUBLIC mylib/include)
# A new exectuable with a single source file.
add_executable(myprogram main.cpp)
# The creation of the link dependency `myprogram` -> `mylib`.
target_link_libraries(myprogram mylib)
# There is no additional step to add `mylib/include` to `myprogram`.
```
This same notion applies to practically every build property:
compile definitions via [`target_compile_definitions`][],
include directories via [`target_include_directories`][],
link libraries via [`target_link_libraries`][],
compile options via [`target_compile_options`][],
and compile features via [`target_compile_features`][].
All of these commands also take an optional argument of
`<INTERFACE|PUBLIC|PRIVATE>`, which constrains their transitivity in the graph.
That is, a `PRIVATE` include directory is recorded for the target, but not
shared transitively to anything depending on the target, `PUBLIC` is used
for both the target and dependencies on it, and `INTERFACE` is used only
for dependencies.
Notably missing from this list are link directories. CMake explicitly prefers
finding and using the absolute paths to libraries, obsoleting link directories.
[`target_compile_definitions`]: https://cmake.org/cmake/help/latest/command/target_compile_definitions.html
[`target_include_directories`]: https://cmake.org/cmake/help/latest/command/target_include_directories.html
[`target_link_libraries`]: https://cmake.org/cmake/help/latest/command/target_link_libraries.html
[`target_compile_options`]: https://cmake.org/cmake/help/latest/command/target_compile_options.html
[`target_compile_features`]: https://cmake.org/cmake/help/latest/command/target_compile_features.html
# Common mistakes
## Booleans
CMake treats `ON`, `OFF`, `TRUE`, `FALSE`, `1`, and `0` all as true/false
booleans. Furthermore, variables of the form `<target>-NOTFOUND` are also
treated as false (this is used for finding packages).
In Mesos, we prefer the boolean types `TRUE` and `FALSE`.
See [`if`](https://cmake.org/cmake/help/latest/command/if.html) for more info.
## Conditionals
For historical reasons, CMake conditionals such as `if` and `elseif`
automatically interpolate variable names. It is therefore dangerous to
interpolate them manually, because if `${FOO}` evaluates to `BAR`, and `BAR` is
another variable name, then `if (${FOO})` becomes `if (BAR)`, and `BAR` is then
evaluated again by the `if`. Stick to `if (FOO)` to check the value of `${FOO}`.
Do not use `if (${FOO})`.
Also see the CMake policies
[CMP0012](https://cmake.org/cmake/help/latest/policy/CMP0012.html) and
[CMP0054](https://cmake.org/cmake/help/latest/policy/CMP0054.html).
## Definitions
When using `add_definitions()` (which should be used rarely, as it is for
"global" compile definitions), the flags must be prefixed with `-D` to be
treated as preprocessor definitions. However, when using
`target_compile_definitions()` (which should be preferred, as it is
for specific targets), the flags do not need the prefix.
# Style
In general, wrap at 80 lines, and use a two-space indent. When wrapping
arguments, put the command on a separate line and arguments on subsequent lines:
```
target_link_libraries(
program PRIVATE
alpha
beta
gamma)
```
Otherwise keep it together:
```
target_link_libraries(program PUBLIC library)
```
Always keep the trailing parenthesis with the last argument.
Use a single space between conditionals and their open parenthesis, e.g.
`if (FOO)`, but not for commands, e.g. `add_executable(program)`.
CAPITALIZE the declaration and use of custom functions and macros (e.g.
`EXTERNAL` and `PATCH_CMD`), and do not capitalize the use of CMake built-in
(including modules) functions and macros. CAPITALIZE variables.
# CMake anti-patterns
[anti-patterns]: http://voices.canonical.com/jussi.pakkanen/2013/03/26/a-list-of-common-cmake-antipatterns/
Because CMake handles much more of the grunt work for you than other build
systems, there are unfortunately a lot of CMake [anti-patterns][] you should
look out for when writing new CMake code. These are some common problems
that should be avoided when writing new CMake code:
## Superfluous use of `add_dependencies`
When you've linked library `a` to library `b` with `target_link_libraries(a b)`,
the CMake graph is already updated with the dependency information. It is
redundant to use `add_dependencies(a b)` to (re)specify the dependency. In fact,
this command should _rarely_ be used.
The exceptions to this are:
1. Setting a dependency from an imported library to a target added via
`ExternalProject_Add`.
2. Setting a dependency on Mesos modules since no explicit linking is done.
3. Setting a dependency between executables (e.g. the `mesos-agent` requiring the
`mesos-containerizer` executable). In general, runtime dependencies need
to be setup with `add_dependency`, but never link dependencies.
## Use of `link_libraries` or `link_directories`
Neither of these commands should ever be used. The only appropriate command used
to link libraries is [`target_link_libraries`][], which records the information
in the CMake dependency graph. Furthermore, imported third-party libraries
should have correct locations recorded in their respective targets, so the use
of `link_directories` should never be necessary. The
[official documentation][link-directories] states:
> Note that this command is rarely necessary. Library locations returned by
> `find_package()` and `find_library()` are absolute paths. Pass these absolute
> library file paths directly to the `target_link_libraries()` command. CMake
> will ensure the linker finds them.
[link-directories]: https://cmake.org/cmake/help/latest/command/link_directories.html
The difference is that the former sets global (or directory level) side effects,
and the latter sets specific target information stored in the graph.
## Use of `include_directories`
This is similar to the above: the [`target_include_directories`][] should always
be preferred so that the include directory information remains localized to the
appropriate targets.
## Adding anything to `endif ()`
Old versions of CMake expected the style `if (FOO) ... endif (FOO)`, where the
`endif` contained the same expression as the `if` command. However, this is
tortuously redundant, so leave the parentheses in `endif ()` empty. This goes
for other endings too, such as `endforeach ()`, `endwhile ()`, `endmacro ()` and
`endfunction ()`.
## Specifying header files superfluously
One of the distinct advantages of using CMake for C and C++ projects is that
adding header files to the source list for a target is unnecessary. CMake is
designed to parse the source files (`.c`, `.cpp`, etc.) and determine their
required headers automatically. The exception to this is headers generated as
part of the build (such as protobuf or the JNI headers).
## Checking `CMAKE_BUILD_TYPE`
See the ["Building debug or release configurations"](cmake-examples.md#building-debug-or-release-configurations)
example for more information. In short, not all generators respect the variable
`CMAKE_BUILD_TYPE` at configuration time, and thus it must not be used in CMake
logic. A usable alternative (where supported) is a [generator expression][] such
as `$<$<CONFIG:Debug>:DEBUG_MODE>`.
[generator expression]: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#logical-expressions
# Remaining hacks
## `3RDPARTY_DEPENDENCIES`
Until Mesos on Windows is stable, we keep some dependencies in an external
repository, [3rdparty](https://github.com/mesos/3rdparty). When
all dependencies are bundled with Mesos, this extra repository will no longer be
necessary. Until then, the CMake variable `3RDPARTY_DEPENDENCIES` points by
default to this URL, but it can also point to the on-disk location of a local
clone of the repo. With this option you can avoid pulling from GitHub for every
clean build. Note that this must be an absolute path with forward slashes, e.g.
`-D3RDPARTY_DEPENDENCIES=C:/3rdparty`, otherwise it will fail on Windows.
## `EXTERNAL`
The CMake function `EXTERNAL` defines a few variables that make it easy for us
to track the directory structure of a dependency. In particular, if our
library's name is `boost`, we invoke:
```
EXTERNAL(boost ${BOOST_VERSION} ${CMAKE_CURRENT_BINARY_DIR})
```
Which will define the following variables as side-effects in the current scope:
* `BOOST_TARGET` (a target folder name to put dep in e.g., `boost-1.53.0`)
* `BOOST_CMAKE_ROOT` (where to have CMake put the uncompressed source, e.g.,
`build/3rdparty/boost-1.53.0`)
* `BOOST_ROOT` (where the code goes in various stages of build, e.g.,
`build/.../boost-1.53.0/src`, which might contain folders
`build-1.53.0-build`, `-lib`, and so on, for each build
step that dependency has)
The implementation is in `3rdparty/cmake/External.cmake`.
This is not to be confused with the CMake module [ExternalProject][], from which
we use `ExternalProject_Add` to download, extract, configure, and build our
dependencies.
[ExternalProject]: https://cmake.org/cmake/help/latest/module/ExternalProject.html
## `CMAKE_NOOP`
This is a CMake variable we define in `3rdparty/CMakeLists.txt` so that we can
cancel steps of `ExternalProject`. `ExternalProject`'s default behavior is to
attempt to configure, build, and install a project using CMake. So when one of
these steps must be skipped, we use set it to `CMAKE_NOOP` so that nothing
is run instead.
## `CMAKE_FORWARD_ARGS`
The `CMAKE_FORWARD_ARGS` variable defined in `3rdparty/CMakeLists.txt` is sent
as the `CMAKE_ARGS` argument to the `ExternalProject_Add` macro (along with any
per-project arguments), and is used when the external project is configured as a
CMake project. If either the `CONFIGURE_COMMAND` or `BUILD_COMMAND` arguments of
`ExternalProject_Add` are used, then the `CMAKE_ARGS` argument will be ignored.
This variable ensures that compilation configurations are properly propagated to
third-party dependencies, such as compiler flags.
### `CMAKE_SSL_FORWARD_ARGS`
The `CMAKE_SSL_FORWARD_ARGS` variable defined in `3rdparty/CMakeLists.txt`
is like `CMAKE_FORWARD_ARGS`, but only used for specific external projects
that find and link against OpenSSL.
## `LIBRARY_LINKAGE`
This variable is a shortcut used in `3rdparty/CMakeLists.txt`. It is set to
`SHARED` when `BUILD_SHARED_LIBS` is true, and otherwise it is set to `STATIC`.
The `SHARED` and `STATIC` keywords are used to declare how a library should be
built; however, if left out then the type is deduced automatically from
`BUILD_SHARED_LIBS`.
## `MAKE_INCLUDE_DIR`
This function works around a [CMake issue][cmake-15052] with setting include
directories of imported libraries built with `ExternalProject_Add`. We have to
call this for each `IMPORTED` third-party dependency which has set
`INTERFACE_INCLUDE_DIRECTORIES`, just to make CMake happy. An example is Glog:
```
MAKE_INCLUDE_DIR(glog)
```
[cmake-15052]: https://gitlab.kitware.com/cmake/cmake/issues/15052
## `GET_BYPRODUCTS`
This function works around a [CMake issue][cmake-060234] with the Ninja
generator where it does not understand imported libraries, and instead needs
`BUILD_BYPRODUCTS` explicitly set. This simply allows us to use
`ExternalProject_Add` and Ninja. For Glog, it looks like this:
```
GET_BYPRODUCTS(glog)
```
Also see the CMake policy [CMP0058][].
[cmake-060234]: https://cmake.org/pipermail/cmake/2015-April/060234.html
[CMP0058]: https://cmake.org/cmake/help/latest/policy/CMP0058.html
## `PATCH_CMD`
The CMake function `PATCH_CMD` generates a patch command given a patch file.
If the path is not absolute, it's resolved to the current source directory.
It stores the command in the variable name supplied. This is used to easily
patch third-party dependencies. For Glog, it looks like this:
```
PATCH_CMD(GLOG_PATCH_CMD glog-${GLOG_VERSION}.patch)
ExternalProject_Add(
${GLOG_TARGET}
...
PATCH_COMMAND ${GLOG_PATCH_CMD})
```
The implementation is in `3rdparty/cmake/PatchCommand.cmake`.
### Windows `patch.exe`
While using `patch` on Linux is straightforward, doing the same on Windows takes
a bit of work. `PATH_CMD` encapsulates this:
* Checks the cache variable `PATCHEXE_PATH` for `patch.exe`.
* Searches for `patch.exe` in its default locations.
* Copies `patch.exe` and a custom manifest to the temporary directory.
* Applies the manifest to avoid the UAC prompt.
* Uses the patched `patch.exe`.
As such, `PATCH_CMD` lets us apply patches as we do on Linux, without requiring
an administrative prompt.
Note that on Windows, the patch file must have CRLF line endings. A file with LF
line endings will cause the error: "Assertion failed, hunk, file patch.c, line
343". For this reason, it is required to checkout the Mesos repo with `git
config core.autocrlf true`.