CMake 命令笔记

2018-07-26  本文已影响0人  神齐

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

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

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

前言

常用预定义变量

CMake 的预定义变量

系统信息预定义变量

开关选项

常用命令

cmake_minimum_required

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

cmake_minimum_required(VERSION 3.5)

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

project

project(<PROJECT-NAME>)

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

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

project(Lab)

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

CMake 中预定义了两个变量:PROJECT_BINARY_DIRPROJECT_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])

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

include_directories

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])

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

add_executable

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 …])

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

示例 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 …])

示例:

add_library(hello SHARED ${LIB_hello_SRC})

link_directories

link_directories(directory1 directory2 …)

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

target_link_libraries

target_link_libraries(<target> … <item>…)

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

message

message([<mode>] "message to display" …)

set_target_properties

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

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

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

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

PREFIX、SUFFIX

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> …]]
        )

非目标文件

.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…]] […])

示例:

install(DIRECTORY icons scripts/ DESTINATION share/myproj
        PATTERN "CVS" EXCLUDE
        PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_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 基本用法

数字比较表达式

字母表顺序比较

示例 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 还可以循环访问生成的数字范围。这种迭代有三种类型:

foreach(<loop_var> IN [LISTS [<list1> …]]
                      [ITEMS [<item1> …]])
  COMMAND1(<ARGS> …)
  COMMAND2(<ARGS> …)
  …
endforeach(<loop_var>)

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

上一篇 下一篇

猜你喜欢

热点阅读