Probing compilation, linking, and execution
Questions
How can you add custom steps to your build system with CMake?
Objectives
Learn how and when to use
execute_processLearn how to use
add_custom_commandwith targets.Learn how to test compilation, linking, and execution.
CMake lets you run arbitrary commands at any stage in the project lifecycle. This is yet another mechanism for fine-grained customization and we will discuss some of the options in this episode.
Running custom commands at configure-time
The most straightforward method is to explicitly run one (or more) child
process(es) when invoking the cmake command. This is achieved with the
execute_process command.
execute_process(COMMAND <cmd1> [args1...]]
[COMMAND <cmd2> [args2...] [...]]
[WORKING_DIRECTORY <directory>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <variable>]
[RESULTS_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>]
[ERROR_VARIABLE <variable>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[ENCODING <name>])
Executes one or more child processes. The standard output and standard error
streams are recorded into OUTPUT_VARIABLE and ERROR_VARIABLE,
respectively. The result of the last child process is saved into
RESULT_VARIABLE.
It is important to note that any command invoked through execute_process
will only be run at configure-time, i.e. when running the cmake
command. You should not rely on execute_process to update any artifacts at
build-time.
Exercise 17: Find a Python module
In this exercise, we’ll use execute_process to check whether the cffi Python module is
installed in your environment. On the command line, you would do:
$ python -c "import cffi; print(cffi.__version__)"
Your goal is to replicate the same in CMake.
The scaffold code is in content/code/day-1/17_find_cffi.
You will have to modify the call to execute_process to run the command above.
A working example is in the solution subfolder.
Note the use of find_package(Python REQUIRED) to obtain the python
executable. CMake comes with many modules dedicated to the detection of
dependencies, such as Python. These are conventionally called
Find<dependency>.cmake and you can inspect their documentation with:
$ cmake --help-module FindPython | more
We will revisit uses of find_package later on in Finding and using dependencies.
Custom commands for your targets
As mentioned, the main problem of execute_process is that it will run a
command at configure-time, when the cmake command is first invoked.
It is thus not a viable alternative if we intend to perform some specific
actions depending on targets or make the result of the custom commands a
dependency for other targets.
Both cases have real-world examples, such as when using automatically generated
code. The CMake command add_custom_command can be used in some of this
instances.
add_custom_command(TARGET <target>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM] [USES_TERMINAL])
Add one or more custom commands to a target, such as a library or an
executable. The commands can be executed before linking (with PRE_BUILD
and PRE_LINK) or after (with POST_BUILD)
Exercise 18: Before and after build
We want to perform some action before and after building a target, in this case a Fortran executable:
Before building, we want to read the link line, as produced by CMake, and echo it to standard output. We use the
echo-file.pyPython script.After building, we want to check the size of the static allocations in the binary, by invoking the
sizecommand. We use thestatic-size.pyPython script.
The scaffold code is in content/code/day-1/18_pre_post-f.
Add CMake commands to build the
exampleexecutable from the Fortran sources. Find the text file with the link line under the build folder. Hint: have a look inCMakeFilesand keep in mind the name you gave to the target.Call
add_custom_commandwithPRE_LINKto invoke theecho-file.pyPython script.Call
add_custom_commandwithPOST_BUILDto invoke thestatic-size.pyPython script.
A working example is in the solution subfolder.
Testing compilation, linking, and execution
We also want to be able to run checks on our compilers and linkers. Or check whether a certain library can be used correctly before attempting to build our own artifacts. CMake provides modules and commands for these purposes:
Check<LANG>CompilerFlagproviding thecheck_<LANG>_compiler_flagfunction, to check whether a compiler flag is valid for the compiler in use.Check<LANG>SourceCompilesproviding thecheck_<LANG>_source_compiles. Which check whether a given source file compiles with the compiler in use.Check<LANG>SourceRunsproviding thecheck_<LANG>_source_runs, to make sure that a given source snippet compiles, links, and runs.
In all cases, <LANG> can be one of CXX, C or Fortran.
Exercise 19: Check that a compiler accepts a compiler flag
Compilers evolve: they add and/or remove flags and sometimes you will face the need to test whether some flags are available before using them in your build.
The scaffold code is in content/code/day-1/19_check_compiler_flag.
Implement a
CMakeLists.txtto build an executable from theasan-example.cppsource file.Check that the address sanitizer flags are available with
check_cxx_compiler_flag. The flags to check are-fsanitize=address -fno-omit-frame-pointer. Find the command signature with:$ cmake --help-module CMakeCXXCompilerFlag
If the flags do work, add them to the those used to compile the executable target with
target_compile_options.
A working example is in the solution subfolder.
Exercise 20: Testing runtime capabilities
Testing that some features will work properly for your code requires not only compiling an object files, but also linking an executable and running it successfully.
The scaffold code is in content/code/day-1/20_check_source_runs.
Create an executable target from the source file
use-uuid.cpp.Add a check that linking against the library produces working executables. Use the following C code as test:
#include <uuid/uuid.h> int main(int argc, char * argv[]) { uuid_t uuid; uuid_generate(uuid); return 0; }
check_c_source_runsrequires the test source code to be passed in as a string. Find the command signature with:$ cmake --help-module CheckCSourceRuns
If the test is successful, link executable target against the UUID library: use the
PkgConfig::UUIDtarget as argument totarget_link_libraries.
A working example is in the solution subfolder.
Keypoints
You can customize the build system by executing custom commands.
CMake offers commands to probe compilation, linking, and execution.