If you want to write software which compiles and runs on different
operating systems, you have to take care for the special properties of
the different platforms. On different operating systems there are subtle
differences, e.g. on FreeBSD you should not use malloc.h, while it is
perfectly ok to use it on Linux. These differences are typically handled
by providing a header file which contains a bunch of define-statements
according to the platform properties, usually named config.h:
The contents of config.h depend on the platform where the sources are
compiled, so there needs to be a way to generate this header file before
the actual compilation process starts. If you are using autotools-based
software, you probably know the ./configure step, which has to be
executed before starting make. The ./configure script does some
system introspection and generates from the gathered information the
config.h header file. CMake is able to do the same, and I'll show you
how to do it.
Additionally to the builtin commands, cmake offers more commands
implemented by cmake script files, called modules. These files are
located in the cmake module directory, on UNIX systems is this by
To use commands from these modules, they have to be included in the
CMakeLists.txt. CMake comes with several modules for checking the
system, they all follow the same style, as an example here
The CMake module CheckIncludeFiles offers the command
CHECK_INCLUDE_FILES(). The first argument to this command is the
header you want to check for. The second argument is the variable which
will contain the result. If the given header was found, it is set to 1,
otherwise it is empty. If another header is required to use the header
you are looking for, you have to list the header files separated by
semicolons, as you can see above. To see what CHECK_INCLUDE_FILES()
exactly does, have a look at the implementation:
/usr/local/share/CMake/Modules/CheckIncludeFiles.cmake . There you'll
see that it tries to compile a simple source file which includes the
specified header files. The results of the tests are stored in the file
CMakeCache.txt, so if you want to check later whether the test succeeded
or not look in the CMakeCache.txt file :
//Have include HAVE_MALLOC_HHAVE_MALLOC_H:INTERNAL=1
As long as the result is in the cache, the test won't be executed again.
If you want it to be executed again, either delete the file
CMakeCache.txt, then all tests will be executed again, or just remove
the entries for the variables you want to have tested again. This can
save some time. If the test failed, and you want to find out why, open
CMakeFiles/CMakeError.log and search for the header name (or function,
etc.) you were testing for. There you should see the code which failed
to compile or link, the compile command and the error message. Together
with the implementation of the test in CheckIncludeFiles.cmake you
should be able to figure out what went wrong.
Ok, now that we have tested whether e.g. malloc.h exists and have the
result in the cmake variable HAVE_MALLOC_H, we still have to create a
header config.h. To do this, we use the cmake command
CONFIGURE_FILE(), as you have seen above. This copies a source file to
a target file and edits it while doing so, see the man page for details.
So we write a source file named config.h.in, but you could give it any
name you want (with autotools it's also usually named config.h.in):
By including this header into your source files you can check these
properties using #ifdef. You can insert such checks in any of the
CMakeLists.txt in your project, not only in the top-level
CMakeLists.txt. If you have several configured headers, you shouldn't
name them all config.h, this might lead to problems with the include
path. Better give them names like config.h, config-foo.h for the
configure header in the subdirectory foo/ and config-bar.h in the
bar/ subdirectory, etc.
The other commands coming with cmake to do system checks follow this
style, so we can handle them much shorter now.
Checks whether the given symbol exists if the specified headers are
included. "Symbol" is defined as something that is either a preprocessor
macro, or whose address can be taken. Notably, this does not include
values of a C enumeration.
Module: INCLUDE (CheckLibraryExists)
Usage: CHECK_LIBRARY_EXISTS(library function location variable)
Checks whether the given library exists and contains the given function.
This is done by linking a small program which uses the function and
links to the library. In the location parameter an additional link
directory (-Ldir) can be given if required.
Checks whether the specified type exists and returns the size of the
type. In the variable the size of the type will be returned,
additionally a variable HAVE_STRUCT_UCRED will be set to true if the
type exists. Please not that you have to set
CMAKE_EXTRA_INCLUDE_FILES to the required headers for this type, and
you should reset it after calling CHECK_TYPE_SIZE. If you are not
really interested in the size of the type, but only whether it exists or
not, you can also use STRUCT_UCRED directly, if the type doesn't exist,
it will be empty and so also evaluate to FALSE.
Checks whether the code given in source will compile and link. You can
set CMAKE_REQUIRED_LIBRARIES, CMAKE_REQUIRED_FLAGS and
CMAKE_REQUIRED_INCLUDES accordingly if additional libraries or
compiler flags are required.
Sometimes, you will also want to have logic predicated on what operating system the build is being run on. This is important for things like which shell scripts to run, and where to look for dependencies. Luckily, CMake has several useful utilities for dealing with this.
WARNING: Don't use these variables to decide which headers to include or which dependencies to link to. It's very difficult to know which OSs really have which features and headers, and to predict how this will change over time. Always check for the specific headers and libraries that your program is going to use, using the checking functions in the previous section!
CMake sets certain variables to true depending on the current platform and toolchain in use. These always describe the target platform. In older versions of CMake, these were the only way of detecting the current platform.
UNIX : is TRUE on all UNIX-like OS's, including Apple OS X and
WIN32 : is TRUE on Windows. Prior to 2.8.4 this included CygWin
APPLE : is TRUE on Apple systems. Note this does not imply the
system is Mac OS X, only that APPLE is #defined in C/C++
MINGW : is TRUE when using the MinGW compiler in Windows
MSYS : is TRUE when using the MSYS developer environment in Windows
CYGWIN : is TRUE on Windows when using the CygWin version of cmake
A cleaner and more cross-compiling-compatible way to check the current platform is through the CMAKE_SYSTEM variables.
CMAKE_SYSTEM : the complete system name, e.g. "Linux-2.4.22",
"FreeBSD-5.4-RELEASE" or "Windows 5.1"
CMAKE_SYSTEM_NAME : The name of the system targeted by the build. Unfortunately, these values depend on CMake's internal platform files, and are not documented anywhere. An incomplete list is below:
Environments Where Seen
Windows (Visual Studio, MinGW GCC)
macOS/OS X (Clang, GCC)
Linux (GCC, Intel, PGI)
Android NDK (GCC, Clang)
Cray supercomputers (Cray compiler)
Windows (MSYS2 shell native GCC)
CMAKE_SYSTEM_VERSION : Version of the operating system. Generally
the kernel version.
CMAKE_SYSTEM_PROCESSOR : the processor name (e.g. "Intel(R)
Pentium(R) M processor 2.00GHz")
CMAKE_HOST_SYSTEM_NAME : The name of the system hosting the
build. Has the same possible values as CMAKE_SYSTEM_NAME.
Similarly, you may also need information about the current compiler and compiler version. This is especially useful for determining compilation flags.
CMAKE_<LANG>_COMPILER_ID: The identity of the compiler for the given language. A list of the possible ID values can be found on the doc page.
CMAKE_<LANG>_COMPILER_VERSION: The version of the compiler for the given language, e.g. "4.9.2". WARNING: This variable is not supported on all languages and compilers. Notably, CMake versions before 3.3.0 did not support detecting the Fortran compiler version.
CMAKE_<LANG>_COMPILER: The full path to the compiler in use for the given language. You should not change this after the project has been configured, or you will get errors and strange behavior. However, if you set this before enabling a language (using the project() or enable_language() commands) on the first time the buildscript is run, you can control which compiler CMake will attempt to use.
CMAKE_<LANG>_COMPILER_LOADED: Whether the given language is enabled.