Probing compilation, linking, and execution

Questions

  • How can you add custom steps to your build system with CMake?

Objectives

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.

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.

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.py Python script.

  • After building, we want to check the size of the static allocations in the binary, by invoking the size command. We use the static-size.py Python script.

The scaffold code is in content/code/day-1/18_pre_post-f.

  1. Add CMake commands to build the example executable from the Fortran sources. Find the text file with the link line under the build folder. Hint: have a look in CMakeFiles and keep in mind the name you gave to the target.

  2. Call add_custom_command with PRE_LINK to invoke the echo-file.py Python script.

  3. Call add_custom_command with POST_BUILD to invoke the static-size.py Python 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>CompilerFlag providing the check_<LANG>_compiler_flag function, to check whether a compiler flag is valid for the compiler in use.

  • Check<LANG>SourceCompiles providing the check_<LANG>_source_compiles. Which check whether a given source file compiles with the compiler in use.

  • Check<LANG>SourceRuns providing the check_<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.

  1. Implement a CMakeLists.txt to build an executable from the asan-example.cpp source file.

  2. 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
    
  3. 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.

  1. Create an executable target from the source file use-uuid.cpp.

  2. 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_runs requires the test source code to be passed in as a string. Find the command signature with:

    $ cmake --help-module CheckCSourceRuns
    
  3. If the test is successful, link executable target against the UUID library: use the PkgConfig::UUID target as argument to target_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.