美文网首页
CMake 命令笔记

CMake 命令笔记

作者: 神齐 | 来源:发表于2018-07-26 18:39 被阅读0次

    CMake 全称“cross platform make”,是开源、跨平台的自动化构建系统。CMake 由 Kitware 开发与维护,来自使用者的贡献使得 CMake 快速成长。

    CMake 并不直接建构出最终的软件,而是依照平台、编译器产生标准的建构档(如 Unix Makefile 或 Visual Studio 的 projects/workspaces),然后再依一般的生成方式使用。和标准的 GNU 开发工具相比,CMake 的角色比 Make 更高阶,比较接近 Autotools,而且支持多种不同的平台与编译器。

    虽然跨平台是 CMake 的重要特色,但由于 CMake 的简单与弹性,在单一平台上使用也很便利。

    前言

    • 每一个需要进行 CMake 操作的目录下面,都必须存在文件 CMakeLists.txt
    • CMake 命令不区分大小写。习惯上,CMake 命令全小写,预定义变量全大写。
    • 变量使用 ${} 方式取值,但是在 if 控制语句中是直接使用变量名。
    • command(parameter1 parameter2 …),参数使用括号括起,参数之间使用空格或分号分开。

    常用预定义变量

    CMake 的预定义变量

    • PROJECT_SOURCE_DIR:工程根目录;
    • PROJECT_BINARY_DIR:运行 CMake 命令的目录。建议定义为 ${PROJECT_SOURCE_DIR}/build 下;
    • CMAKE_INCLUDE_PATH:环境变量,非 CMake 变量;
    • CMAKE_LIBRARY_PATH:环境变量;
    • CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 文件所在路径;
    • CMAKE_CURRENT_BINARY_DIR:target 编译目录;
      • 使用 add_subdirectory 命令可以更改该变量的值;
      • set(EXECUTABLE_OUTPUT_PATH <dir>) 命令不会对该变量有影响,但改变了最终目标文件的存储路径。
    • CMAKE_CURRENT_LIST_FILE:输出调用该变量的 CMakeLists.txt 的完整路径;
    • CMAKE_CURRENT_LIST_LINE:输出该变量所在的行;
    • CMAKE_MODULE_PATH:定义自己的 CMake 模块所在路径;
    • EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置;
    • LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置;
    • PROJECT_NAME:返回由 project 命令定义的项目名称;
    • CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS:用来控制 if…else… 语句的书写方式。

    系统信息预定义变量

    • CMAKE_MAJOR_VERSION:CMake 主版本号,如 3.12.0 中的 3;
    • CMAKE_MINOR_VERSION:CMake 次版本号,如 3.12.0 中的 12;
    • CMAKE_PATCH_VERSION:CMake 补丁等级,如 3.12.0 中的 0;
    • CMAKE_SYSTEM:系统名称,例如 Windows-10.0.17134;
    • CMAKE_SYSTEM_NAME:不包含版本号的系统名,如 Windows;
    • CMAKE_SYSTEM_VERSION:系统版本号,如 10.0.17134;
    • CMAKE_SYSTEM_PROCESSOR:处理器架构,如 AMD64;
    • UNIX:在所有的类 UNIX 平台为 true,包括 macOS 和 Cygwin;
    • WIN32:在所有的 Win32 平台为 true,包括 Cygwin。

    开关选项

    • BUILD_SHARED_LIBS:控制默认的库编译方式;
      • 如果未进行设置,使用 add_library 时又没有指定库类型,默认编译生成的库都是静态库。
    • CMAKE_C_FLAGS:设置 C 编译选项;
    • CMAKE_CXX_FLAGS:设置 C++ 编译选项。

    常用命令

    cmake_minimum_required

    该语句一般放置在 CMakeLists.txt 的开头,用于说明 CMake 最低版本要求。

    cmake_minimum_required(VERSION 3.5)
    

    上述示例指 CMake 的版本号最低为 3.5。

    project

    project(<PROJECT-NAME>)
    
    • <PROJECT-NAME> 指工程名称。

    该命令一般紧跟 cmake_minimum_required 命令之后,定义了工程的名称。但项目最终编译生成的可执行文件并不一定是这个项目名称,而是由另一条命令确定的,后面会介绍。

    执行了该命令之后,将会自动创建两个变量:

    • <PROJECT-NAME>_BINARY_DIR:二进制文件保存路径;
    • <PROJECT-NAME>_SOURCE_DIR:源代码路径。
    project(Lab)
    

    执行了上一条指令,即定义了一个项目名称 Lab,相应的会生成两个变量:Lab_BINARY_DIRLab_SOURCE_DIR

    CMake 中预定义了两个变量:PROJECT_BINARY_DIRPROJECT_SOURCE_DIR

    在这个例子中:

    • PROJECT_BINARY_DIR 等价于 Lab_BINARY_DIR
    • PROJECT_SOURCE_DIR 等价于 Lab_SOURCE_DIR

    建议直接使用 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR,这样即使项目名称发生了变化,也不会影响 CMakeLists.txt 文件。

    关于 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR 这两个变量是否相同的问题,涉及到编译方法是内部编译还是外部编译。如果是内部编译,则这两个变量相同;如果是外部编译,则两个变量不同。此处对内部编译与外部编译做出介绍:

    外部构建与内部构建

    假设此时已经完成了 CMakeLists.txt 的编写,在 CMakeLists.txt 所在目录下,有两种执行 CMake 的方法:

    cmake .\\
    make
    

    以及

    mkdir build
    cd .\\build
    cmake ..\\
    make
    

    第一种方法是内部构建,第二种方法是外部构建。上述两种方法中,最大不同在于 CMake 与 Make 的工作路径不同。

    内部构建方法中,CMake 生成的中间文件和可执行文件都会存放在项目目录中;外部构建方法中,中间文件与可执行文件都存放在 build 目录中。

    建议使用外部构建方法。优点显而易见:最大限度地保持了代码目录的整洁,生成、编译与安装是不同于项目目录的其他目录中,在外部构建方法下,PROJECT_SOURCE_DIR 指向目录与内部构建相同,为 CMakeLists.txt 所在根目录;而 PROJECT_BINARY_DIR 不同,它指向 CMakeLists.txt 所在根目录下的 build 目录。

    set

    set(<variable> <value>… CACHE <type> <docstring> [FORCE])
    

    示例:

    set(CMAKE_INSTALL_PREFIX C:\\Program Files\\${PROJECT_NAME})
    

    该示例显式地将 CMAKE_INSTALL_PREFIX 的值定义为 C:\\Program Files\\${PROJECT_NAME}。如此,在外部构建情况下执行 make install 命令时,Make 会将生成的可执行文件拷贝到 C:\\Program Files\\${PROJECT_NAME}\\bin 目录下。

    当然,可执行文件的安装路径 CMAKE_INSTALL_PREFIX 也可以在执行 cmake 命令的时候指定,cmake 参数如下:

    cmake -D CMAKE_INSTALL_PREFIX="C:\\Program Files\\…"
    

    如果 cmake 参数和 CMakeLists.txt 文件中都不指定该值的话,则该值为默认值(Windows 下为 C:\\Program Files\\${PROJECT_NAME},UNIX 下为 /usr/local)。

    add_subdirectory

    add_subdirectory(source_dir [binary_dir]
                     [EXCLUDE_FROM_ALL])
    
    • source_dir:源文件路径;
    • [binary_dir]:中间二进制与目标二进制文件存放路径;
    • [EXECLUDE_FROM_ALL]:将这个目录从编译过程中排除。

    这个命令用于向当前工程添加存放源文件的子目录。

    include_directories

    include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
    
    • [AFTER|BEFORE]:追加标志,指定控制追加或预先添加;
    • [SYSTEM]:指定该目录为系统包含目录;
    • dir1, …, dir n:添加的一系列头文件搜索路径。

    向工程添加多个特定的头文件搜索路径,路径之间用空格分隔。类似于 GCC 中的编译参数 -l,即指定编译过程中编译器搜索头文件的路径。当项目需要的头文件不在系统默认的搜索路径时,则指定该路径。

    add_executable

    add_executable(<name> [WIN32] [MACOSX_BUNDLE]
                   [EXCLUDE_FROM_ALL]
                   [source1] [source2 …])
    
    • name:可执行文件名;
    • source1, …, source n:生成该可执行文件的源文件。

    该命令给出源文件,并指出需要编译出的可执行文件名。

    示例 1:

    add_executable(hello main.cpp)
    

    该示例中,利用源文件 main.cpp,编译出名为 hello 的可执行文件。

    示例 2:

    set(SRC_LIST main.cc
                 rpc/CRNode.cpp
                 rpc/Schd_types.cpp
                 task/TaskExecutor.cpp
                 task/TaskMoniter.cpp
                 util/Const.cpp
                 util/Globals.cc
       )
    
    add_executable(CRNode ${SRC_LIST})
    

    该示例中,定义了该工程会生成一个名为 CRNode 的可执行文件,所依赖的源文件是变量 SRC_LIST 定义的源文件列表。

    如果前面 project() 命令中定义的项目名称也是 CRNode,没有什么问题,两者之间没有任何关系。

    add_library

    add_library(<name> [STATIC|SHARED|MODULE]
                [EXCLUDE_FROM_ALL]
                [source1] [source2 …])
    
    • name:库文件名称;
    • [STATIC|SHARED|MODULE]:生成的库的文件类型(静态库/共享库);
    • [EXCLUDE_FROM_ALL]:表示该库不会被默认构建;
    • source1, …, sourceN:生成库所依赖的源文件。

    示例:

    add_library(hello SHARED ${LIB_hello_SRC})
    

    link_directories

    link_directories(directory1 directory2 …)
    

    该命令用于添加外部库的搜索路径。

    target_link_libraries

    target_link_libraries(<target> … <item>…)
    
    • target:目标文件;
    • item:链接外部库文件。

    指定链接目标文件时需要链接的外部库,效果类似于 GCC 编译参数 -L,解决外部库依赖的问题。

    message

    message([<mode>] "message to display" …)
    
    • mode:确定消息的类型:

      模式 描述
      (none) 重要信息
      STATUS 附带信息
      WARNING CMake 警告,继续处理
      AUTHOR_WARNING CMake 警告(dev),继续处理
      SEND_ERROR CMake 错误,继续处理,但跳过生成过程
      FATAL_ERROR CMake 错误,停止处理和生成过程
      DEPRECATION 如果分别启用 CMAKE_ERROR_DEPRECATEDCMAKE_WARN_DEPRECATED,则弃用 CMake 错误或警告,否则无消息
    • "message to display":需要显示的文字消息。

    set_target_properties

    set_target_properties(target1 target2 …
                          PROPERTIES prop1 value1
                          prop2 value2 …)
    

    设置目标的某些属性,改变它们构建的方式。

    该指令为一个目标设置属性,语法是列出所有用户想要变更的文件,然后提供想要设置的值。用户可以使用任何想用的属性与对应的值,并在随后的代码中调用 GET_TARGET_PROPERTY 命令取出属性的值。

    影响目标输出文件的属性PROPERTIES详述如下:

    PREFIX、SUFFIX

    • PREFIX 覆盖了默认的目标名前缀(如 lib);
    • SUFFIX 覆盖了默认的目标名后缀(如 .so)。

    IMPORT_PREFIX、IMPORT_SUFFIX

    PREFIXSUFFIX 是等价的属性,但针对的是 DLL 导入库(即共享库目标)。

    OUTPUT_NAME

    构建目标时,OUTPUT_NAME 用来设置目标的真实名称。

    LINK_FLAGS

    为一个目标的链接阶段添加额外标志。

    LINK_FLAGS_<CONFIG> 将为配置 <CONFIG> 添加链接标志,如 DebugReleaseRelWithDebInfoMinSizeRel

    COMPILE_FLAGS

    设置附加的编译器标志,在构建目标内的源文件时用到。

    LINKER_LANGUAGE

    改变链接可执行文件或共享库的工具。默认值是设置与库中文件相匹配的语言。

    CXX 与 C 是该属性的公共值。

    VERSION、SOVERSION

    VERSION 指定构建的版本号,SOVERSION 指定构建的 API 版本号。

    构建或安装时,如果平台支持符号链接,且链接器支持 so 名称,那么将会创建恰当的符号链接。

    如果只指定两者中的一个,缺失的另一个假定为具有相同版本号。

    示例 1:

    set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
    

    示例 2:

    set_target_properties(hello PROPERTEIES VERSION 1.2 SOVERSION 1)
    

    该命令用于控制版本,VERSION 指代动态库版本,SOVERSION 指代 API 版本。

    aux_source_directory

    查找某个路径下的所有源文件,并将源文件列表存储到一个变量中。

    aux_source_directory(<dir> <variable>)
    

    示例:

    aux_source_directory(. SRC_LIST)
    

    该指令将当前目录下的文件列表全部存入变量 SRC_LIST 中。

    install

    install 命令可以按照对象的不同分为多种类型:目标文件、非目标文件、目录。

    目标文件

    install(TARGETS targets… [EXPORT <export-name>]
            [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
              PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
             [DESTINATION <dir>]
             [PERMISSIONS permissions…]
             [CONFIGURATIONS [Debug|Release|…]]
             [COMPONENT <component>]
             [NAMELINK_COMPONENT <component>]
             [OPTIONAL] [EXCLUDE_FROM_ALL]
             [NAMELINK_ONLY|NAMELINK_SKIP]
            ] […]
            [INCLUDES DESTINATION [<dir> …]]
            )
    
    • TARGETS targetstargets 即通过 add_executableadd_library 定义的目标文件,可能是可执行二进制文件、动态库、静态库;
    • DESTINATION <dir>dir 即定义的安装路径。安装路径可以是绝对/相对路径。在绝对路径的情况下,CMAKE_INSTALL_PREFIX 就无效了。
      • 如果希望使用 CMAKE_INSTALL_PREFIX 定义安装路径,就需要使用相对路径,这时候安装后的路径就是 ${CMAKE_INSTALL_PREFIX}\\<dir>

    非目标文件

    .sh 脚本文件,即为典型的非目标文件的可执行程序。

    install(<FILES|PROGRAMS> files… DESTINATION <dir>
            [PERMISSIONS permissions…]
            [CONFIGURATIONS [Debug|Release|…]]
            [COMPONENT <component>]
            [RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])
    

    使用方法和上述目标文件指令的 install 基本相同。唯一的区别是,安装非目标文件之后的权限还包括 OWNER_EXECUTEGOUP_EXECUTEWORLD_EXECUTE,即 755 权限目录的安装。

    目录

    install(DIRECTORY dirs… DESTINATION <dir>
            [FILE_PERMISSIONS permissions…]
            [DIRECTORY_PERMISSIONS permissions…]
            [USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
            [CONFIGURATIONS [Debug|Release|…]]
            [COMPONENT <component>] [EXCLUDE_FROM_ALL]
            [FILES_MATCHING]
            [[PATTERN <pattern> | REGEX <regex>]
             [EXCLUDE] [PERMISSIONS permissions…]] […])
    
    • DIRECTORY dirsdirs 是所在源文件目录的相对路径。但必须注意,abcabc/ 有很大区别:
      • abc:该目录将被安装为目标路径的 abc
      • abc/:将该目录内容安装到目标路径,但不包括该目录本身。

    示例:

    install(DIRECTORY icons scripts/ DESTINATION share/myproj
            PATTERN "CVS" EXCLUDE
            PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ)
    

    该指令的执行结果是:

    • 将 icons 目录安装到 <prefix>/share/myproj
    • 将 scripts/ 中的内容安装到 <prefix>/share/myproj
    • 不包含目录名为 CVS 的目录;
    • 对于 scripts/* 文件指定权限为 OWNER_EXECUTEOWNER_WRITEOWNER_READGROUP_EXECUTGROUP_READ

    基本控制语法

    if

    if…else… 语法格式有些类似于 Visual Basic .NET:

    if(<expression>)
      # then section.
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      #…
    elseif(<expression2>)
      # elseif section.
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      #…
    else(<expression>)
      # else section.
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      #…
    endif(<expression>)
    

    其中,一定要有 endif()if() 对应。

    if 基本用法

    • if(<expression>)expression 不为 0OFFNOFALSENIGNORENOTFOUND、空字符串,或者不含后缀 -NOTFOUND 时,为真;

    • if(NOT <expression>:与上一条相反;

    • if(<expr1> AND <expr2>)

    • if(<expr1> OR <expr2>

    • if(COMMAND command-name):如果 command-name 是命令、宏或函数并可调用,为真;

    • if(EXISTS path-to-file-or-directory):如果给定路径的文件或目录存在,为真;

    • if(file1 IS_NEWER_THAN file2):当 file1 比 file2 新,或 file1/file2 中有一个不存在时为真,文件名需使用全路径;

    • if(IS_DIRECTORY path-to-directory):当给定路径是目录时,为真。注意使用全路径;

    • if(DEFINED <variable>):如果变量已被定义,为真;

    • if(<variable|string> MATCHES regex):当给定变量或字符串能匹配正则表达式 regex 时,为真。此处的 variable 直接使用变量名,而非 ${variable}

      • 示例:

        if("hello" MATCHES "ell") 
        message("true") 
        endif("hello" MATCHES "ell")
        

    数字比较表达式

    • if(<var> LESS <number>)
    • if(<var> GREATER <number>)
    • if(<var> EQUAL <number>)

    字母表顺序比较

    • if(<var1> STRLESS <var2>)
    • if(<var1> STRGREATER <var2>)
    • if(<var1> STREQUAL <var2>)

    示例 1:

    判断平台差异。

    if(WIN32)
        message(STATUS "This is windows.")
    else(WIN32)
        message(STATUS "This is not windows.")
    endif(WIN32)
    

    上述代码可以控制不同平台进行不同控制。

    也许 else(WIN32) 之类的语句阅读起来很不舒服,这时候可以加上语句:

    set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
    

    这时候上述结构就可以写成:

    if(WIN32)
        message(STATUS "This is windows.")
    else()
        message(STATUS "This is not windows.")
    endif()
    

    示例 2:

    if(WIN32)
        #do something related to WIN32
    elseif(UNIX)
        #do something related to UNIX
    elseif(APPLE)
        #do something related to APPLE
    endif(WIN32)
    

    while

    while(<condition>)
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      …
    endwhile(<condition>)
    

    类似于 endifwhile() 也需要 endwhile() 匹配。

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

    foreach

    foreach 有多种使用形式的语法,且每个 foreach() 都需要一个 endforeach() 与之匹配。

    列表语法

    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)
    

    该示例中,先将当前路径下的所有源文件列表赋值给变量 SRC_LIST,然后遍历 SRC_LIST 中的文件,并持续输出信息,信息内容是当前路径下所有源文件的名称。

    范围语法

    foreach(<loop_var> RANGE <total>)
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      …
    endforeach(<loop_var>)
    

    示例:

    foreach(v RANGE 10)
        message(${v})
    endforeach(v)
    

    该示例从 0 到 total(此处为 10),以 1 为步进。此处输出为:012345678910

    范围步进语法

    foreach(<loop_var> RANGE <start> <stop> [<step>])
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      …
    endforeach(<loop_var>)
    

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

    示例:

    foreach(a RANGE 5 15 3)
        message(${a})
    endforeach(a)
    

    此处输出为 581114

    迭代语法

    foreach 还可以循环访问生成的数字范围。这种迭代有三种类型:

    • 指定单个数字时,范围将包含元素 [0, …, total](包括 total);
    • 指定两个数字时,范围将包含从第一个数字到第二个数字(包括)的元素;
    • 第三个可选数字是用于从第一个数字迭代到第二个数字(包括)的增量。
    foreach(<loop_var> IN [LISTS [<list1> …]]
                          [ITEMS [<item1> …]])
      COMMAND1(<ARGS> …)
      COMMAND2(<ARGS> …)
      …
    endforeach(<loop_var>)
    

    foreach 循环访问一个精确的项列表:

    • LISTS:给要遍历的列表值变量命名,包括空元素(空字符串是零长度列表)。注意宏参数不是变量。
    • ITEMS:结束参数解析并包含迭代中它后面的所有参数。

    相关文章

      网友评论

          本文标题:CMake 命令笔记

          本文链接:https://www.haomeiwen.com/subject/jefimftx.html