# cmake_turorials **Repository Path**: lx_r/cmake_turorials ## Basic Information - **Project Name**: cmake_turorials - **Description**: cmake tutorials - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-12-20 - **Last Updated**: 2024-06-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cmake tutorials > 致谢:参考自[](https://github.com/gavinliu6/CMake-Practice-zh-CN) ## 1.简单例子 ```cmake PROJECT(TEST_CPP) MESSAGE(STATUS "Source Dir: " ${PROJECT_SOURCE_DIR}) MESSAGE(STATUS "Source Dir: " ${TEST_CPP_SOURCE_DIR}) MESSAGE(STATUS "Binary Dir: " ${PROJECT_BINARY_DIR}) MESSAGE(STATUS "Binary Dir: " ${TEST_CPP_BINARY_DIR}) SET(SRC_LIST main.cpp) ADD_EXECUTABLE(main ${SRC_LIST}) ``` - `PROJECT`指令,`PROJECT(projectname [CXX] [C] [Java])` - 隐式定义了两个cmake变量`_BINARY_DIR`和`_SOURCE_DIR` - `cmake`系统也预定义了`PROJECT_BINARY_DIR`和`PROJECT_SOURCE_DIR` - 建议使用`PROJECT_SOURCE_DIR`,避免修改工程名称导致错误 - `SET`指令,`SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])`,SET 指令可以用来显式的定义变量。 - `MESSAGE 指令` - 格式`MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)` - 用于向终端输出用户定义的信息,`SATUS ,输出前缀为 -- 的信息`, `FATAL_ERROR,立即终止所有 cmake 过程.` - `ADD_EXECUTABLE`的指令,格式:`ADD_EXECUTABLE(main ${SRC_LIST})` - `${}`来引用变量,这是 `cmake `的变量应用方式,但是,有一些例外,比 如在 IF 控制语句,变量是直接使用变量名引用,而不需要`${}` - **指令是大小写无关的,参数和变量是大小写相关的** ## 2.管理工程 工程目录 ```sh ├── CMakeLists.txt ├── COPYRIGHT.md ├── doc ├── README.md ├── run.sh └── src ├── CMakeLists.txt └── main.cpp ``` `src`文件中的`CMakeLists.txt` ```sh ADD_EXECUTABLE(main main.c) ``` `project`中的`CMakeLists.txt` ```sh PROJECT(HELLO) ADD_SUBDIRECTORY(src bin) ``` 构建完成后,会发现生成的目标文件 `main` 位于 build/bin 目录中 - `ADD_SUBDIRECTORY 指令` - `格式:ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])` - 该指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。 - **换个地方保存目标二进制** - `SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)` - `SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)` - 上面两个指令分别定义了可执行二进制的输出路径为 `build/bin` 和库的输出路径为 `build/lib` - 在哪里`ADD_EXECUTABLE`或`ADD_LIBRARY`,如果需要改变目标存放路径,就在哪里加入上述的定义。 - **INSTALL**命令和`CMAKE_INSTALL_PREFIX`变量 - `INSTALL`指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。 - ```cmake INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION ] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT ] [OPTIONAL] ] [...]) ``` - **`TARGETS`** 后面跟的就是我们通过 `ADD_EXECUTABLE` 或者 `ADD_LIBRARY` 定义的目标文件,可能是可执行二进制、动态库、静态库。 - 目标类型就相对应的有三种:`ARCHIVE 特指静态库`,`LIBRARY 特指动态库`,`RUNTIME特指可执行目标二进制`。 - **`DESTINATION`** 定义了安装的路径,如果路径以`/`开头,那么指的是绝对路径,这时候`CMAKE_INSTALL_PREFIX` 其实就无效了。 - **安装普通文件**:默认权限`644` ```cmake INSTALL(FILES files... DESTINATION [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT ] [RENAME ] [OPTIONAL]) ``` - **非目标文件的可执行程序安装(比如脚本之类):** 与普通文件安装的区别是,默认权限`755` ```cmake INSTALL(PROGRAMS files... DESTINATION [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT ] [RENAME ] [OPTIONAL]) ``` - **安装目录:** ```cmake INSTALL(DIRECTORY dirs... DESTINATION [FILE_PERMISSIONS permissions...] [DIRECTORY_PERMISSIONS permissions...] [USE_SOURCE_PERMISSIONS] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT ] [[PATTERN | REGEX ] [EXCLUDE] [PERMISSIONS permissions...]] [...]) ``` DIRECTORY 后面连接的是所在 Source 目录的相对路径,`abc` 和 `abc/`有很大的区别,一个是把`abc`文件夹安装到`DESTINATION`路径下,一个是将`abc/`目录下的所有文件及目录安装到`DEST``路径下。PATTERN` 用于使用正则表达式进行过滤,`PERMISSIONS` 用于指定 `PATTERN` 过滤后的文件权限。 - 安装时 CMAKE 脚本的执行: ```cmake INSTALL([[SCRIPT ] [CODE ]] [...]) ``` `SCRIPT` 参数用于在安装时调用 `cmake` 脚本文件(也就是`.cmake` 文件) `CODE` 参数用于执行 `CMAKE` 指令,必须以双引号括起来。比如: `INSTALL(CODE "MESSAGE(\"Sample install message.\")")` - **CMAKE_INSTALL_PREFIX 的默认定义是/usr/local** ## 3.生成和使用共享库 ### 3.1生成动态共享库 `ADD_LIBRARY`命令 ```cmake ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN) ``` `SHARED`动态库,`STATIC`静态库。 `EXCLUDE_FROM_ALL` 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手 工构建。 ```cmake ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}) ``` 根据以上命令,会发现静态库没有被创建,这是因为一个 `target` 是不能重名的,所以,静态库构建指令无效。 可以修改静态库的名字,但按照一般的习惯,静态库名字跟动态库名字应该是一致的,这样就需要用到另外一个指令,`SET_TARGET_PROPERTIES`,其基本语法为: ```cmake SET_TARGET_PROPERTIES(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...) ``` 添加一句 ```cmake SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") ``` 就可以同时得到 libhello.so/libhello.a 两个库了。 ```cmake # cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,为了回避这个问题,做如下配置 SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1) SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1) # 动态库版本号 # VERSION 指代动态库版本,SOVERSION 指代 API 版本。 SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1) ``` **INSTALL**共享库和头文件, ```cmake INSTALL(TARGETS hello hello_staticLIBRARY DESTINATION lib ARCHIVE DESTINATION lib) INSTALL(FILES hello.h DESTINATION include/hello) ``` 静态库要使用 `ARCHIVE` 关键字。 ## 3.2引用库文件 **设置头文件路径** ```cmake INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...) ``` 这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径 中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的 后面,你可以通过两种方式来进行控制搜索路径添加的方式: - `CMAKE_INCLUDE_DIRECTORIES_BEFORE`,通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面 - 通过 `AFTER` 或者 `BEFORE` 参数,也可以控制是追加还是置前 **设置库文件路径**,引入两个新的指令 `LINK_DIRECTORIES `和 `TARGET_LINK_LIBRARIES`. ```cmake TARGET_LINK_LIBRARIES(target library1 library2 ...) ``` 为 target 添加需要链接的共享库。 **CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH**这两个是环境变量而不是 `cmake` 变量。使用方法是要在 `bash` 中用 `export` 或者在 `csh` 中使用 `set` 命令设置或者`CMAKE_INCLUDE_PATH=/home/include cmake ..`等方式。 **FIND_PATH 用来在指定路径中搜索文件名**,比如: ```cmake FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello) IF(myHeader) INCLUDE_DIRECTORIES(${myHeader}) ENDIF(myHeader) ``` ## 4.其他 ### 4.1环境变量 - 获取环境变量: ```cmake SET(ENV_PATH $ENV{PATH}) MESSAGE(STATUS ${ENV_PATH}) ``` - 设置环境变量 ```cmake SET(ENV{变量名} 值) ``` ### 4.2系统信息 - `CMAKE_MAJOR_VERSION`,CMAKE 主版本号,比如 2.4.6 中的 2 - `CMAKE_MINOR_VERSION`,CMAKE 次版本号,比如 2.4.6 中的 4 - `CMAKE_PATCH_VERSION`,CMAKE 补丁等级,比如 2.4.6 中的 6 - `CMAKE_SYSTEM`系统名称 - `CMAKE_SYSTEM_NAME`不包含版本的系统名 - `CMAKE_SYSTEM_VERSION`系统版本 - `CMAKE_SYSTEM_PROCESSOR`处理器名称 - `UNIX`在所有的类 `UNIX` 平台为 `TRUE`,包括 `OS X` 和 `cygwin` - `WIN32`在所有的 `win32` 平台为 `TRUE`,包括 `cygwin` ```cmake MESSAGE("CMake Major Version: " ${CMAKE_MAJOR_VERSION}) MESSAGE("CMake Minor Version: " ${CMAKE_MINOR_VERSION}) MESSAGE("CMake Patch Version: " ${CMAKE_PATCH_VERSION}) MESSAGE("CMake System: " ${CMAKE_SYSTEM}) MESSAGE("CMake System Name: " ${CMAKE_SYSTEM_NAME}) MESSAGE("CMake System Version: " ${CMAKE_SYSTEM_VERSION}) MESSAGE("CMake System Processor: " ${CMAKE_SYSTEM_PROCESSOR}) MESSAGE("UNIX: " ${UNIX}) MESSAGE("WIN32: " ${WIN32}) # CMake Major Version: 3 # CMake Minor Version: 16 # CMake Patch Version: 3 # CMake System: Linux-5.15.0-56-generic # CMake System Name: Linux # CMake System Version: 5.15.0-56-generic # CMake System Processor: x86_64 # UNIX: 1 # WIN32: ``` ### 4.3 主要的开关选项 - `BUILD_SHARED_LIBS`,这个开关用来控制默认的库编译方式,如果不进行设置,使用 `ADD_LIBRARY` 并没有指定库类型的情况下,默认编译生成的库都是静态库。如果 `SET(BUILD_SHARED_LIBS ON)`后,默认生成的为动态库。 - `CMAKE_C_FLAGS`:设置 `C `编译选项,也可以通过指令 `ADD_DEFINITIONS()`添加。 - `CMAKE_CXX_FLAGS`设置 `C++`编译选项,也可以通过指令 `ADD_DEFINITIONS()`添加。 ## 5.常用指令 ### 5.1`ADD_DEFINITIONS`,向`C/C++`编译器添加`-D`定义,比如: ```cmake OPTION(ENABLE_DEBUG "Build the project using macro" OFF) IF(ENABLE_DEBUG) ADD_DEFINITIONS(-DENABLE_DEBUG -DABC) ENDIF(ENABLE_DEBUG) ``` 如果代码中定义了 ```c++ #ifdef ENABLE_DEBUG #endif ``` 这个代码块就会生效。 如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变 量设置。 ## 5.2 **ADD_DEPENDENCIES** - `ADD_DEPENDENCIES`,定义 `target` 依赖的其他 `target`,确保在编译本 `target` 之前,其他的 `target` 已经被构建。 ```cmake ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...) ``` ## 5.3 **AUX_SOURCE_DIRECTORY** ```python AUX_SOURCE_DIRECTORY(dir VARIABLE) ``` 作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来 自动构建源文件列表。 ```cmake AUX_SOURCE_DIRECTORY(. SRC_LIST) ADD_EXECUTABLE(main ${SRC_LIST}) ``` ### 5.4 `EXEC_PROGRAM` 在 `CMakeLists.txt` 处理过程中执行命令,并不会在生成的 `Makefile` 中执行。具体语法 为: ```cmake EXEC_PROGRAM(Executable [directory in which to run] [ARGS ] [OUTPUT_VARIABLE ] [RETURN_VALUE ]) ``` 用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过 `OUTPUT_VARIABLE` 和 `RETURN_VALUE` 分别定义两个变量. 这个指令可以帮助你在 `CMakeLists.txt` 处理过程中支持任何命令,比如根据系统情况去 修改代码文件等等。 **例如**:在 `src` 目录执行 `ls` 命令,并把结果和返回值存下来 ```cmake 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) ``` ### 5.5文件操作指令`FILE 指令` ```cmake FILE(WRITE filename "message to write"... ) FILE(APPEND filename "message to write"... ) FILE(READ filename variable) FILE(GLOB expressions]...) variable [RELATIVE path] [globbing]...) 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) ``` ### 5.6 **INCLUDE指令** 用来载入 `CMakeLists.txt` 文件,也用于载入预定义的 `cmake` 模块。 ```cmake INCLUDE(file1 [OPTIONAL]) INCLUDE(module [OPTIONAL]) ``` `OPTIONAL` 参数的作用是文件不存在也不会产生错误。可以指定载入一个文件,如果定义的是一个模块,那么将在 `CMAKE_MODULE_PATH` 中搜索这个模块并载入。载入的内容将在处理到 `INCLUDE` 语句是直接执行。 ### 5.7 **FIND_指令** `FIND_`系列指令主要包含一下指令: ```cmake FIND_FILE( name1 path1 path2 ...) ``` `VAR` 变量代表找到的文件全路径,包含文件名。 ```cmake FIND_LIBRARY( name1 path1 path2 ...) ``` `VAR` 变量表示找到的库全路径,包含库文件名。 ```cmake FIND_PATH( name1 path1 path2 ...) ``` `VAR` 变量代表包含这个文件的路径。 ```cmake FIND_PROGRAM( name1 path1 path2 ...) ``` `VAR` 变量代表包含这个程序的全路径。 ```cmake FIND_PACKAGE( [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]]) ``` 用来调用预定义在 `CMAKE_MODULE_PATH` 下的 `Find.cmake`模块,你也可以自己定义 `Find`模块,通过 `SET(CMAKE_MODULE_PATH dir)`将其放入工程的某个目录中供工程使用。 **例如** 在编译的时候需要使用`curl`库,需要添加 `curl` 的头文件路径和库文件: - 方法1:直接通过`INCLUDE_DIRECTORIES` 和 `TARGET_LINK_LIBRARIES `指令添加 ```cmake INCLUDE_DIRECTORIES(/usr/include) TARGET_LINK_LIBRARIES(curltest curl) ``` - 方法2: 使用 `cmake` 提供的 `FindCURL` 模块 ```cmake FIND_PACKAGE(CURL) IF(CURL_FOUND) INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY}) ELSE(CURL_FOUND) MESSAGE(FATAL_ERROR ”CURL library not found”) ENDIF(CURL_FOUND) ``` 系统预定义的 `Find.cmake` 模块,使用方法一般如上例所示,每一个模块都会定义以下几个变量 - `_FOUND`,来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译 - `_INCLUDE_DIR or _INCLUDES` - `_LIBRARY or TARGET_LINK_LIBRARIES` **自定义cmake模块** 如前面,自定义共享库,并安装到指定目录,当想在`cmake`中通过`find_package`命令找到并使用时,需要自定义`Find.cmake`模块。 自定义`cmake/FindHELLO.cmake`模块: ```cmake FIND_PATH(HELLO_INCLUDE_DIR hello.h /media/lx/data/code/test_cpp/hello/hello/include) FIND_LIBRARY(HELLO_LIBRARY hello /media/lx/data/code/test_cpp/hello/hello/lib) IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY) SET(HELLO_FOUND TRUE) ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY) IF (HELLO_FOUND) IF (NOT HELLO_FIND_QUIETLY) MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}") ENDIF (NOT HELLO_FIND_QUIETLY) ELSE (HELLO_FOUND) IF (HELLO_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find hello library") ENDIF (HELLO_FIND_REQUIRED) ENDIF (HELLO_FOUND) ``` `FIND_PATH`和`FIND_LIBRARY`后面所跟的是头文件和库文件的安装路径。 ```cmake # FIND_PACKAGE命令格式 FIND_PACKAGE( [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]]) ``` `FIND_PACKAGE`的`QUIET` 参数,对应与我们编写的 `FindHELLO` 中的 `HELLO_FIND_QUIETLY`,如果不指定这个参数,就会执行: ```cmake MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}") ``` `REQUIRED` 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于`FindHELLO.cmake` 模块中的 `HELLO_FIND_REQUIRED` 变量。 同样,在上面的模块中定义了 `HELLO_FOUND`, `HELLO_INCLUDE_DIR`,`HELLO_LIBRARY` 变量供开发者在 `FIND_PACKAGE` 指令中使用。 ### 5.8控制指令 - IF指令 ```cmake IF(var) # 如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或 _NOTFOUND 时,表达式为真。 IF(NOT var ) # 与上述条件相反 IF(var1 AND var2) # 当两个变量都为真是为真 IF(var1 OR var2) # 当两个变量其中一个为真时为真 IF(COMMAND cmd) # 当给定的 cmd 确实是命令并可以调用是为真 IF(EXISTS dir/file) # 当目录名或者文件名存在时为真 IF(file1 IS_NEWER_THAN file2)# 当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真 IF(IS_DIRECTORY dirname) # 当 dirname 是目录时,为真 # 当给定的变量或者字符串能够匹配正则表达式 `regex` 时为真 IF(variable MATCHES regex) IF(string MATCHES regex) # 数字比较表达式 IF(variable LESS number) IF(string LESS number) IF(variable GREATER number) IF(string GREATER number) IF(variable EQUAL number) IF(string EQUAL number) # 按照字母序的排列进行比较 IF(variable STRLESS string) IF(string STRLESS string) IF(variable STRGREATER string) IF(string STRGREATER string) IF(variable STREQUAL string) IF(string STREQUAL string) # 如果变量被定义,为真 IF(DEFINED variable) ``` - **WHILE 指令** ```cmake WHILE(condition) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDWHILE(condition) ``` 真假判断条件可以参考 `IF` 指令。 - **FOREACH** `FOREACH` 指令的使用方法有三种形式 **列表** ```cmake FOREACH(loop_var arg1 arg2 ...) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDFOREACH(loop_var) ``` 如: ```cmake AUX_SOURCE_DIRECTORY(. SRC_LIST) FOREACH(F ${SRC_LIST}) MESSAGE(${F}) ENDFOREACH(F) ``` **范围** ```cmake FOREACH(loop_var RANGE total) ENDFOREACH(loop_var) ``` 从 0 到 total 以1为步进 ```cmake FOREACH(VAR RANGE 10) MESSAGE(${VAR}) ENDFOREACH(VAR) ``` **范围和步进** ```cmake FOREACH(loop_var RANGE start stop [step]) ENDFOREACH(loop_var) ``` 从 `start` 开始到 `stop` 结束,以 `step` 为步进。 ```cmake FOREACH(A RANGE 5 15 3) MESSAGE(${A}) ENDFOREACH(A) ```