1. Introduction
CMake is much simpler than autotools. Next, let’s take a closer look at the common commands provided by CMake. In previous articles, we have discussed many command usages such as PROJECT, ADD_EXECUTABLE, INSTALL, ADD_SUBDIRECTORY, SUBDIRS, INCLUDE_DIRECTORIES, LINK_DIRECTORIES, TARGET_LINK_LIBRARIES, and SET.
This section introduces more CMake commands, arranged in the order of the CMake man page for convenience. Commands that are no longer recommended will not be discussed here. The INSTALL series commands have been explained in great detail in the installation section and will not be mentioned again.
2. Basic Commands
2.1 ADD_DEFINITIONS
Adds -D definitions to the C/C++ compiler, for example:
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)
Parameters are separated by spaces.
If your code defines
#ifdef ENABLE_DEBUG
// ...
#endif
this code block will be activated.
If you want to add other compiler switches, you can set them through CMAKE_C_FLAGS and CMAKE_CXX_FLAGS variables.
2.2 ADD_DEPENDENCIES
Defines other targets that the target depends on, ensuring that other targets are built before compiling this target.
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
2.3 ADD_TEST and ENABLE_TESTING Commands
The ENABLE_TESTING command controls whether the Makefile builds the test target and applies to all directories in the project. The syntax is simple, with no parameters:
ENABLE_TESTING()
Typically, this command is placed in the main CMakeLists.txt of the project.
The syntax for the ADD_TEST command is:
ADD_TEST(testname Exename arg1 arg2 ...)
testname is the custom test name, Exename can be the built target file or an external script, etc. The following connects parameters passed to the executable. If ENABLE_TESTING() is not activated in the same CMakeLists.txt, any ADD_TEST will be ineffective.
For example:
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING()
After generating the Makefile, you can run make test to execute the tests.
2.4 AUX_SOURCE_DIRECTORY
The basic syntax is:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
This command discovers all source code files in a directory and stores the list in a variable. This command is temporarily used to automatically build the source file list, as CMake currently cannot automatically discover newly added source files.
For example:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
You can also process this list using the FOREACH command mentioned later.
2.5 CMAKE_MINIMUM_REQUIRED
The syntax is:
CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
For example:
CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
If the CMake version is less than 2.5, a fatal error occurs and the process is halted.
2.6 EXEC_PROGRAM
Executes commands during the processing of CMakeLists.txt, but will not execute in the generated Makefile. The specific syntax is:
EXEC_PROGRAM(Executable [directory in which to run]
[ARGS <arguments executable="" to="">]
[OUTPUT_VARIABLE <var>]
[RETURN_VALUE <var>])</var></var></arguments>
This is used to run a program in a specified directory, adding parameters through ARGS. If you want to obtain output and return values, you can define two variables through OUTPUT_VARIABLE and RETURN_VALUE.
This command can help you support any command during the CMakeLists.txt processing, such as modifying code files based on system conditions.
For a simple example, if we want to execute the ls command in the src directory and store the result and return value, you can directly add in src/CMakeLists.txt:
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)
During the CMake Makefile generation process, the ls command will be executed. If the return is 0, it indicates successful execution, and the result of ls *.c will be output. Regarding the IF statement, the subsequent control commands will be mentioned.
2.7 FILE Command
This command is used for file operations, with basic syntax as follows:
FILE(WRITE filename "message to write"...)
FILE(APPEND filename "message to write"...)
FILE(READ filename variable)
FILE(GLOB variable [RELATIVE path] [globbing expressions]...)
FILE(GLOB_RECURSE variable [RELATIVE path] [globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)
The syntax here is quite simple, and will not be elaborated further.
2.8 INCLUDE Command
This command is used to load CMakeLists.txt files and also to load predefined CMake modules:
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
The OPTIONAL parameter means that if the file does not exist, it will not produce an error.
You can specify a file to be loaded. If a module is defined, it will search for this module in CMAKE_MODULE_PATH and load it. The loaded content will be executed immediately when the INCLUDE statement is processed.
2.9 Other Commands
ADD_EXECUTABLE, ADD_LIBRARY, and ADD_SUBDIRECTORY have been introduced earlier, so there is no need to repeat them here. The INSTALL series commands have been explained in detail in previous articles, and will not be elaborated upon here. Please refer to earlier articles.
3. FIND_ Series Commands
The FIND_ series commands mainly include the following commands:
FIND_FILE(<var> name1 path1 path2 ...)</var>
The VAR variable represents the full path of the found file, including the file name.
FIND_LIBRARY(<var> name1 path1 path2 ...)</var>
The VAR variable indicates the full path of the found library, including the library file name.
FIND_PATH(<var> name1 path1 path2 ...)</var>
The VAR variable represents the path containing this file.
FIND_PROGRAM(<var> name1 path1 path2 ...)</var>
The VAR variable represents the full path of the program found.
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [components...]])</name>
This is used to call predefined modules in CMAKE_MODULE_PATH defined as Find
Example of FIND_LIBRARY:
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR "libX not found")
ENDIF(NOT libX)
4. Control Commands
4.1 IF Command
The basic syntax is:
IF(expression)
# THEN section. COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ELSE(expression)
# ELSE section. COMMAND1(ARGS ...) COMMAND2(ARGS ...)
...
ENDIF(expression)
Another command is ELSEIF. The general principle is that wherever there is an IF, there must be a corresponding ENDIF, and where ELSEIF appears, ENDIF is optional.
The usage of expressions is as follows:
-
IF(var), if the variable is not: empty, 0, N, NO, OFF, FALSE, NOTFOUND or
<var>_NOTFOUND</var>
, the expression is true. -
IF(NOT var), the opposite of the above condition.
-
IF(var1 AND var2), true if both variables are true.
-
IF(var1 OR var2), true if either of the two variables is true.
-
IF(COMMAND cmd), true if the given cmd is indeed a command and can be called.
-
IF(EXISTS dir) or IF(EXISTS file), true if the directory name or file name exists.
-
IF(file1 IS_NEWER_THAN file2), true if file1 is newer than file2, or
file1/file2
one of them does not exist, and the file names should use full paths. -
IF(IS_DIRECTORY dirname), true if dirname is a directory.
-
IF(variable MATCHES regex).
-
IF(string MATCHES regex).
When the given variable or string can match the regular expression regex, it is true. For example:
IF("hello" MATCHES "ell")
MESSAGE("true")
ENDIF("hello" MATCHES "ell")
IF(variable LESS number)
IF(string LESS number)
IF(variable GREATER number)
IF(string GREATER number)
IF(variable EQUAL number)
IF(string EQUAL number)
Numeric comparison expressions:
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)
These perform comparisons based on lexicographical order:
IF(DEFINED variable), true if the variable is defined.
A small example to judge platform differences:
IF(WIN32)
MESSAGE(STATUS "This is windows.")
# Perform some Windows related operations
ELSE(WIN32)
MESSAGE(STATUS "This is not windows")
# Perform some non-Windows related operations
ENDIF(WIN32)
The above code is used to control different operations on different platforms, but it’s not very readable; statements like ELSE(WIN32) can easily lead to ambiguity.
This is where the CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS switch mentioned in the “Common Variables” section comes into play.
You can set SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
Then it can be written as:
IF(WIN32)
ELSE()
ENDIF()
If combined with ELSEIF, the possible writing is like this:
IF(WIN32)
#do something related to WIN32
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)
4.2 WHILE
The syntax for the WHILE command is:
WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)
The truth condition can refer to the IF command.
4.3 FOREACH
The usage of the FOREACH command has three forms:
(1) List:
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
Like the example we used earlier with AUX_SOURCE_DIRECTORY:
AUX_SOURCE_DIRECTORY(. SRC_LIST) FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
(2) Range:
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
From 0 to total with a step of 1. Example:
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)
The final output is:
0
1
2
3
4
5
6
7
8
9
10
(3) Range and step:
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
From start to stop with a step.
Example:
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
The final result is:
5
8
11
14
This command requires attention: the entire statement block will only be executed when the ENDFOREACH command is encountered.
Conclusion
This covers the commonly used CMake commands, including basic commands, find commands, install commands, and control statements. It is especially important to note that when using variables in control statements, you should not use ${}
to reference them; instead, apply the variable name directly.
By mastering the various control commands above, you should be able to manage complex programs through CMake.
Feel free to follow my public account
Daily article updates