(十一) 编译工具cmake


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


致谢:参考自<Cmake 实践 Cmake Practice – Cjacker.pdf>

1.简单例子

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变量<projectname>_BINARY_DIR<projectname>_SOURCE_DIR
    • cmake系统也预定义了PROJECT_BINARY_DIRPROJECT_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.管理工程

工程目录

├── CMakeLists.txt
├── COPYRIGHT.md
├── doc
├── README.md
├── run.sh
└── src
    ├── CMakeLists.txt
    └── main.cpp

src文件中的CMakeLists.txt

ADD_EXECUTABLE(main main.c)

project中的CMakeLists.txt

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_EXECUTABLEADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义。
  • INSTALL命令和CMAKE_INSTALL_PREFIX变量

    • INSTALL指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。

    • INSTALL(TARGETS targets...
          [[ARCHIVE|LIBRARY|RUNTIME]
              [DESTINATION <dir>]
              [PERMISSIONS permissions...]
              [CONFIGURATIONS
              [Debug|Release|...]]
              [COMPONENT <component>]
              [OPTIONAL]
          ] [...])
      
    • TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。

    • 目标类型就相对应的有三种:ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME特指可执行目标二进制

    • DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX 其实就无效了。

    • 安装普通文件:默认权限644

      INSTALL(FILES files... DESTINATION <dir>
          [PERMISSIONS permissions...]
          [CONFIGURATIONS [Debug|Release|...]]
          [COMPONENT <component>]
          [RENAME <name>] [OPTIONAL])
      
    • 非目标文件的可执行程序安装(比如脚本之类): 与普通文件安装的区别是,默认权限755

      INSTALL(PROGRAMS files... DESTINATION <dir>
          [PERMISSIONS permissions...]
          [CONFIGURATIONS [Debug|Release|...]]
          [COMPONENT <component>]
          [RENAME <name>] [OPTIONAL])
      
    • 安装目录:

      INSTALL(DIRECTORY dirs... DESTINATION <dir>
          [FILE_PERMISSIONS permissions...]
          [DIRECTORY_PERMISSIONS permissions...]
          [USE_SOURCE_PERMISSIONS]
          [CONFIGURATIONS [Debug|Release|...]]
          [COMPONENT <component>]
          [[PATTERN <pattern> | REGEX <regex>]
          [EXCLUDE] [PERMISSIONS permissions...]] [...])
      

      DIRECTORY 后面连接的是所在 Source 目录的相对路径,abcabc/有很大的区别,一个是把abc文件夹安装到DESTINATION路径下,一个是将abc/目录下的所有文件及目录安装到DEST``路径下。PATTERN 用于使用正则表达式进行过滤,PERMISSIONS 用于指定 PATTERN 过滤后的文件权限。

    • 安装时 CMAKE 脚本的执行:

      INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
      

      SCRIPT 参数用于在安装时调用 cmake 脚本文件(也就是<abc>.cmake 文件)
      CODE 参数用于执行 CMAKE 指令,必须以双引号括起来。比如:
      INSTALL(CODE "MESSAGE(\"Sample install message.\")")

    • CMAKE_INSTALL_PREFIX 的默认定义是/usr/local

3.生成和使用共享库

3.1生成动态共享库

ADD_LIBRARY命令

ADD_LIBRARY(libname
    [SHARED|STATIC|MODULE]
    [EXCLUDE_FROM_ALL]
    source1 source2 ... sourceN)

SHARED动态库,STATIC静态库。

EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手
工构建。

ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

根据以上命令,会发现静态库没有被创建,这是因为一个 target 是不能重名的,所以,静态库构建指令无效。

可以修改静态库的名字,但按照一般的习惯,静态库名字跟动态库名字应该是一致的,这样就需要用到另外一个指令,SET_TARGET_PROPERTIES,其基本语法为:

SET_TARGET_PROPERTIES(target1 target2 ...
    PROPERTIES prop1 value1 prop2 value2 ...)

添加一句

SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

就可以同时得到 libhello.so/libhello.a 两个库了。

# 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共享库和头文件,

INSTALL(TARGETS hello hello_staticLIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

INSTALL(FILES hello.h DESTINATION include/hello)

静态库要使用 ARCHIVE 关键字。

3.2引用库文件

设置头文件路径

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径
中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
后面,你可以通过两种方式来进行控制搜索路径添加的方式:

  • CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面
  • 通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前

设置库文件路径,引入两个新的指令
LINK_DIRECTORIES TARGET_LINK_LIBRARIES.

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)

为 target 添加需要链接的共享库。

CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH这两个是环境变量而不是 cmake 变量。使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。

FIND_PATH 用来在指定路径中搜索文件名,比如:

FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)

4.其他

4.1环境变量

  • 获取环境变量:
SET(ENV_PATH $ENV{PATH})
MESSAGE(STATUS ${ENV_PATH})
  • 设置环境变量
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 Xcygwin
  • WIN32在所有的 win32 平台为 TRUE,包括 cygwin
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.1ADD_DEFINITIONS

C/C++编译器添加-D定义,比如:

OPTION(ENABLE_DEBUG "Build the project using macro" OFF)

IF(ENABLE_DEBUG)
    ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)
ENDIF(ENABLE_DEBUG)

如果代码中定义了

#ifdef ENABLE_DEBUG 
#endif

这个代码块就会生效。
如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变
量设置。

5.2 ADD_DEPENDENCIES

  • ADD_DEPENDENCIES,定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)

5.3 AUX_SOURCE_DIRECTORY

AUX_SOURCE_DIRECTORY(dir VARIABLE)

作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来
自动构建源文件列表。

AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})

5.4 EXEC_PROGRAM

CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行。具体语法
为:

EXEC_PROGRAM(Executable [directory in which to run]
    [ARGS <arguments to executable>]
    [OUTPUT_VARIABLE <var>]
    [RETURN_VALUE <var>])

用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过
OUTPUT_VARIABLERETURN_VALUE 分别定义两个变量.
这个指令可以帮助你在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去
修改代码文件等等。

例如:在 src 目录执行 ls 命令,并把结果和返回值存下来

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 指令

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 模块。

INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])

OPTIONAL 参数的作用是文件不存在也不会产生错误。可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH 中搜索这个模块并载入。载入的内容将在处理到 INCLUDE 语句是直接执行。

5.7 FIND_指令

FIND_系列指令主要包含一下指令:

FIND_FILE(<VAR> name1 path1 path2 ...)

VAR 变量代表找到的文件全路径,包含文件名。

FIND_LIBRARY(<VAR> name1 path1 path2 ...)

VAR 变量表示找到的库全路径,包含库文件名。

FIND_PATH(<VAR> name1 path1 path2 ...)

VAR 变量代表包含这个文件的路径。

FIND_PROGRAM(<VAR> name1 path1 path2 ...)

VAR 变量代表包含这个程序的全路径。

FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])

用来调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake模块,你也可以自己定义 Find<name>模块,通过 SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用。

例如

在编译的时候需要使用curl库,需要添加 curl 的头文件路径和库文件:

  • 方法1:直接通过INCLUDE_DIRECTORIESTARGET_LINK_LIBRARIES 指令添加
INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)
  • 方法2: 使用 cmake 提供的 FindCURL 模块
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<name>.cmake 模块,使用方法一般如上例所示,每一个模块都会定义以下几个变量

  • <name>_FOUND,来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译
  • <name>_INCLUDE_DIR or <name>_INCLUDES
  • <name>_LIBRARY or TARGET_LINK_LIBRARIES

自定义cmake模块

如前面,自定义共享库,并安装到指定目录,当想在cmake中通过find_package命令找到并使用时,需要自定义Find<NAME>.cmake模块。

自定义cmake/FindHELLO.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_PATHFIND_LIBRARY后面所跟的是头文件和库文件的安装路径。

# FIND_PACKAGE命令格式
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])

FIND_PACKAGEQUIET 参数,对应与我们编写的 FindHELLO 中的 HELLO_FIND_QUIETLY,如果不指定这个参数,就会执行:

MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")

REQUIRED 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于FindHELLO.cmake 模块中的 HELLO_FIND_REQUIRED 变量。

同样,在上面的模块中定义了 HELLO_FOUND,
HELLO_INCLUDE_DIR,HELLO_LIBRARY 变量供开发者在 FIND_PACKAGE 指令中使用。

5.8控制指令

  • IF指令
IF(var) # 如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或 <var>_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 指令

    WHILE(condition)
        COMMAND1(ARGS ...)
        COMMAND2(ARGS ...)
        ...
    ENDWHILE(condition)
    

    真假判断条件可以参考 IF 指令。

  • FOREACH

FOREACH 指令的使用方法有三种形式

列表

FOREACH(loop_var arg1 arg2 ...)
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
ENDFOREACH(loop_var)

如:

AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
    MESSAGE(${F})
ENDFOREACH(F)

范围

FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)

从 0 到 total 以1为步进

FOREACH(VAR RANGE 10)
    MESSAGE(${VAR})
ENDFOREACH(VAR)

范围和步进

FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)

start 开始到 stop 结束,以 step 为步进。

FOREACH(A RANGE 5 15 3)
    MESSAGE(${A})
ENDFOREACH(A)

示例工程

以上代码汇总示例工程见:https://gitee.com/lx_r/cmake_turorials

参考资料

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值