GNU开发工具——CMake进阶

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GNU开发工具——CMake进阶相关的知识,希望对你有一定的参考价值。

GNU开发工具——CMake进阶

一、CMake基础指令

1、cmake_minimum_required

cmake_minimum_required (VERSION 2.8)
cmake_minimum_required用于规定cmake程序的最低版本,可选。如果CMakeLists.txt文件中使用了高版本cmake特有的一些命令时,就需要使用cmake_minimum_required对CMake进行版本限制,提醒用户升级到相应版本后再执行cmake。

2、project

project(project_name)
project用于指定项目的名称。项目最终编译生成的可执行文件并不一定是项目名称,由另一条命令确定。
????在cmake中有两个预定义变量:projectname_BINARY_DIR以及projectname_SOURCE_DIR,同时cmake还预定义了PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变量。内部编译情况下,projectname_BINARY_DIR与PROJECT_BINARY_DIR相同;外部编译情况下,PROJECT_BINARY_DIR指向build构建目录。工程实践中,推荐使用PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变量,即使项目名称发生变化也不会影响CMakeLists.txt文件。

3、外部构建

通过执行cmake生成MakeFile文件,执行make进行编译安装,其方法如下:
方法一(内部构建):
在工程CMakeLists.txt所在目录下执行:
cmake .
make
方法二(外部构建):
在工程CMakeLists.txt所在目录下执行:
mkdir build
cd build
cmake ../
make
两种方法最大的不同在于执行cmake和make的工作路径不同。第一种方法中,cmake生成的所有中间文件和可执行文件都会存放在项目目录中;而第二种方法中,中间文件和可执行文件都将存放在build目录中,避免了源代码被污染,第二中方法的build目录可以创建在任意目录下,只需要在执行cmake时指定工程的CMakeLists.txt目录即可。由于第二种方法的生成、编译和安装是发生在不同于项目目录的其它目录中,因此第二种方法称为外部构建。
????CMake官方推荐使用外部构建方法,可以避免源码被污染。

4、add_subdirectory

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
add_subdirectory指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL参数的含义是将相应目录从编译过程中排除。如工程的example目录可能需要工程构建完成后再进入example目录单独进行构建。

5、aux_source_directory

aux_source_directory(dir variable)
aux_source_directory用于搜集在指定目录下所有的源文件的文件名(不包括头文件),将输出结果列表储存在指定的变量中。aux_source_directory不会对目录下的子目录进行递归调用。
aux_source_directory可以很方便的为一个库或可执行目标获取源文件的清单,但aux_source_directory的缺点在于,如果CMakeLists.txt中使用了aux_source_directory,那么CMake将不能生成一个可以感知新的源文件何时被加进来的构建系统(即新文件的加入并不会导致CMakeLists.txt过时,从而不能引起CMake重新运行,开发者可以手动重新运行CMake来产生一个新的构建系统)。通常,CMake生成的构建系统能够感知何时需要重新运行CMake,因为需要修改CMakeLists.txt来引入一个新的源文件。当源文件仅仅是加到了目录下,但没有修改CMakeLists.txt文件,使用者只能手动重新运行CMake来产生一个包含新文件的构建系统。

6、编译选项设置

设置编译选项可以通过add_compile_options命令,也可以通过set命令修改CMAKE_CXX_FLAGS或CMAKE_C_FLAGS。?add_compile_options命令添加的编译选项是针对所有编译器的(包括c和c++编译器),而set命令设置CMAKE_C_FLAGS或CMAKE_CXX_FLAGS变量则是分别只针对c和c++编译器的。
#判断编译器类型,如果是gcc编译器,则在编译选项中加入c++11支持

if(CMAKE_COMPILER_IS_GNUCXX)
    add_compile_options(-std=c++11)
    message(STATUS "optional:-std=c++11")   
endif(CMAKE_COMPILER_IS_GNUCXX)

设置C++标准:
set(CMAKE_CXX_STANDARD 11)
设置C编译器:
set(CMAKE_C_COMPILER "gcc")
设置C++编译器:
set(CMAKE_CXX_COMPILER "g++")
设置C编译器编译选项:
set(CMAKE_C_FLAGS“$ {CMAKE_C_FLAGS} -fPIC”)
设置C++编译器编译选项:
set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
设置可执行文件输出目录:
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

设置库文件输出目录:
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
设置Build类型:
set(CMAKE_BUILD_TYPE MATCHES "Debug")
Build类型为Debug, Release, RelWitchDebInfo,RelWitchDebInfo,MinSizeRel。

IF (CMAKE_BUILD_TYPE MATCHES "Debug"
        OR CMAKE_BUILD_TYPE MATCHES "None")
    MESSAGE(STATUS "CMAKE_BUILD_TYPE is Debug")
ELSEIF (CMAKE_BUILD_TYPE MATCHES "Release")
    MESSAGE(STATUS "CMAKE_BUILD_TYPE is Release")
ELSEIF (CMAKE_BUILD_TYPE MATCHES "RelWitchDebInfo")
    MESSAGE(STATUS "CMAKE_BUILD_TYPE is RelWitchDebInfo")
ELSEIF (CMAKE_BUILD_TYPE MATCHES "MinSizeRel")
    MESSAGE(STATUS "CMAKE_BUILD_TYPE is MinSizeRel")
ELSE ()
    MESSAGE(STATUS "unknown CMAKE_BUILD_TYPE = " ${CMAKE_BUILD_TYPE})
ENDIF ()

设置构建库的类型:
set(BUILD_SHARED_LIBS shared)
库类型为shared和static
增加编译选项:

add_definitions("-Wall?-lpthread?-g")?
if( CMAKE_BUILD_TYPE STREQUAL "Release")
  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -std=c++11 -fPIC")
else()
  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -std=c++11 -fPIC")#设置寻找外部库的cmake参数的
endif()
message("*** ${PROJECT_NAME}: Build type:" ${CMAKE_BUILD_TYPE} ${CMAKE_CXX_FLAGS} "***")

7、find_package

find_package(<package> [version] [EXACT] [QUIET]
???????? ? ??[[REQUIRED|COMPONENTS] [components...]]
?????????? ? ??[NO_POLICY_SCOPE])

? ??QUIET选项将会禁掉包没有被发现时的警告信息。REQUIRED选项表示如果报没有找到的话,cmake的过程会终止,并输出警告信息。
? ??find_package可以根据cmake内置的.cmake脚本去查找相应库的模块,调用find_package成功后,会有相应的变量生成。如调用find_package(Qt5Widgets)后,会生成变量Qt5Widgets_FOUND,Qt5Widgets_INCLUDE_DIRS,然后在CMakeLists.txt里可以使用使用上述变量。
find_package会调用预定义在CMAKE_MODULE_PATH下的 Findname.cmake模块。可以自己定义Findname模块,将其放入工程的某个目录中,通过 SET(CMAKE_MODULE_PATH dir)设置查找路径,供工程FIND_PACKAGE使用。

8、find_library

find_library (
          <VAR>
          name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
          [HINTS path1 [path2 ... ENV var]]
          [PATHS path1 [path2 ... ENV var]]
          [PATH_SUFFIXES suffix1 [suffix2 ...]]
          [DOC "cache documentation string"]
          [NO_DEFAULT_PATH]
          [NO_CMAKE_ENVIRONMENT_PATH]
          [NO_CMAKE_PATH]
          [NO_SYSTEM_ENVIRONMENT_PATH]
          [NO_CMAKE_SYSTEM_PATH]
          [CMAKE_FIND_ROOT_PATH_BOTH |
           ONLY_CMAKE_FIND_ROOT_PATH |
           NO_CMAKE_FIND_ROOT_PATH]
         )

FIND_LIBRARY(RUNTIME_LIB rt /usr/local/lib NO_DEFAULT_PATH)
cmake会在目录中查找,如果所有目录中都没有,RUNTIME_LIB就会被赋值为NO_DEFAULT_PATH。

9、include_directories

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
include_directories用于指定编译过程中编译器搜索头文件的路径。 默认情况下,将指定目录追加到目录列表最后。通过使用BEFORE或AFTER可以指定目录追加在目录列表的前面或后面。如果设定SYSTEM选项,编译器认定为系统包含目录。
当项目需要的头文件不在系统默认的搜索路径时,需要指定相应头文件路径。

10、link_directories

link_directories(directory1 directory2 ...)
link_directories用于指定要链接的库文件的路径,可选。find_package和find_library指令可以得到库文件的绝对路径。如果自己写的动态库文件放在自己新建的目录下时,可以用link_directories指令指定该目录的路径以便工程能够找到。

11、link_libraires

link_libraries(library1 debug | optimized library2 ...)
库名称可以是绝对路径,库名,支持多个。

12、add_executable

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

ADD_EXECUTABLE(ExcutableName SRC)
ADD_EXECUTABLE定义了工程生成的可执行文件名称,相关的源文件是 SRC中定义的源文件列表。
可以通过SET指令重新定义EXECUTABLE_OUTPUT_PATH和 LIBRARY_OUTPUT_PATH变量来指定最终的目标二进制的位置(指最终生成的可执行文件或者最终的共享库,而不包含编译生成的中间文件)。

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

13、add_library

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2] [...])

add_library用于将指定的源文件生成库文件,然后添加到工程中。
name表示库文件的名字,库文件会根据命令里列出的源文件来创建。而STATIC、SHARED和MODULE的作用是指定生成的库文件的类型。STATIC库是目标文件的归档文件,在链接其它目标的时候使用。SHARED库会被动态链接(动态链接库),在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但可能会在运行时使用dlopen-系列的函数。

14、target_link_libraries

target_link_libraries(<target> [item1] [item2] [...]
                      [[debug|optimized|general] <item>] ...)

target_link_libraries用于指定在链接目标文件的时候需要链接的外部库,可以解决外部库的依赖问题。
target是指通过add_executable()和add_library()指令生成已经创建的目标文件。而[item]表示库文件没有后缀的名字。默认情况下,库依赖项是传递的。当target目标链接到另一个目标时,链接到target目标的库也会出现在另一个目标的链接线上。

15、设置安装目录

SET(CMAKE_INSTALL_PREFIX /usr/local)
显式将CMAKE_INSTALL_PREFIX的值定义为/usr/local,在外部构建情况下执行make install命令时,make会将生成的可执行文件拷贝到/usr/local/bin目录下。
??? 可执行文件的安装路径CMAKE_INSTALL_PREFIX也可以在执行cmake命令的时候指定,cmake参数如下:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
如果cmake参数和CMakeLists.txt文件中都不指定CMAKE_INSTALL_PREFIX值的话,则默认为/usr/local。

16、安装选项

执行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定义的目标文件,可能是可执行二进制、动态库、静态库。
????DESTINATION定义安装的路径,如果路径以/开头,那么是绝对路径,此时CMAKE_INSTALL_PREFIX将无效。如果希望使用CMAKE_INSTALL_PREFIX来定义安装路径,需要写成相对路径,即不要以/开头,安装路径就是${CMAKE_INSTALL_PREFIX} /destination定义的路径
????TARGETS指定的目标文件不需要指定路径,只需要写上TARGETS名称就。
????非目标文件的可执行程序安装(如脚本):

INSTALL(PROGRAMS files... DESTINATION < dir >
    [PERMISSIONS permissions...]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT < component >]
    [RENAME < name >] [OPTIONAL])

安装后权限为755。
???? 安装一个目录的命令如下:

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目录的相对路径,但务必注意: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/*文件指定权限为755。<br/>????INSTALL(DIRECTORY doc/ DESTINATION share/doc/crnode)`

17、message

message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
(无)   = 重要消息     
STATUS = 非重要消息     
WARNING = CMake警告,会继续执行   
AUTHOR_WARNING = CMake警告(dev),会继续执行    
SEND_ERROR = CMake错误,继续执行,但会跳过生成步骤      FATAL_ERROR = CMake错误,终止所有处理过程

二、CMake高级指令

1、操作符

操作符是大小写敏感的。
一元操作符有:EXISTS,COMMAND,DEFINED
二元操作符有:EQUAL,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL,STREQUAL,STRLESS,STRLESS_EQUAL,,STRGREATER,STRGREATER_EQUAL,VERSION_EQUAL,VERSION_LESS,VERSION_LESS_EQUAL,VERSION_GREATER,VERSION_GREATER_EQUAL,MATCHES
逻辑操作符有:NOT,AND,OR

2、布尔常量

布尔常量值是大小写敏感的。
true:1,ON,YES,TRUE,Y,非0的值。
false:0,OFF,NO,FALSE,N,IGNORE,NOTFOUND,空字符串””,以-NOTFOUND结尾的字符串。

3、if...else

if(表达式)
  # 要执行的命令块
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
elseif(表达式2)
  # 要执行的命令块
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
else(表达式)
  # 要执行的命令块
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
endif(表达式)

elseif和else是可选的,可以有多条elseif ,缩进和空格对语句解析没有影响。
IF (COMMAND cmd)
如果cmd确实是命令并可调用,为真
IF (EXISTS dir)
IF (EXISTS file)
如果目录或文件存在,为真
IF (file1 IS_NEWER_THAN file2)
当file1比file2新,或file1/file2中有一个不存在时为真,文件名需使用全路径
IF (IS_DIRECTORY dir)
当dir是目录时,为真
IF (DEFINED var)
如果变量被定义,为真
IF (var MATCHES regex)
var可以用var名,也可以用${var}
IF (string MATCHES regex)
当给定的变量或者字符串能够匹配正则表达式regex时为真
IF (variable LESS number)
variable小于number时为真
IF (string LESS number)
string小于number时为真
IF (variable GREATER number)
variable大于number时为真
IF (string GREATER number)
string大于number时为真
IF (variable EQUAL number)
variable等于number时为真
IF (string EQUAL number)
string等于number时为真
IF (variable STRLESS string)
variable小于字符串string
IF (string STRLESS string)
字符串string小于字符串string
IF (variable STRGREATER string)
variable大于字符串string
IF (string STRGREATER string)
字符串string大于字符串string
IF (variable STREQUAL string)
Variable等于字符串string
IF (string STREQUAL string)
字符串string等于字符串string
不同操作系统平台的判断代码如下:

IF (WIN32)
    #do something related to WIN32
ELSEIF (UNIX)
    #do something related to UNIX
ELSEIF(APPLE)
    #do something related to APPLE
ENDIF (WIN32)

4、while

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

5、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范围语法:

FOREACH(loop_var RANGE total)
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
ENDFOREACH(loop_var)

实例如下:

#从0到total以1为步进
FOREACH(VAR RANGE 10)
   MESSAGE(${VAR})
ENDFOREACH(VAR)

foreach范围和步进语法:

FOREACH(loop_var RANGE start stop [step])
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
ENDFOREACH(loop_var)

从start开始到stop结束,以step为步进,直到遇到ENDFOREACH指令,整个语句块才会得到真正的执行。

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

6、共享变量

通常,使用set命令定义的变量能从父目录传递到子目录,但不能在同级目录间传递,因此用set定义的变量无法共享,需要用set(variable value CACHE INTERNAL docstring )定义变量,把变量加入到CMakeCache.txt,然后各级目录共享会访问到变量。variable为变量名称,value为变量的值,docstring为变量描述,不能为空。
set_property提供了实现共享变量的方法,但set_property不会将变量写入CMakeCache.txt,而是写入内存中。
当用set_property定义的property时,第一个指定作用域(scope)的参数设为GLOBAL,这个property在cmake运行期间作用域就是全局的。然后其他目录下的CMakeLists.txt可以用get_property来读取这个property
在opencl目录的CMakeLists.txt中定义一个名为INCLUDE_OPENCL_1_2?的global property:
set_property(GLOBAL PROPERTY INCLUDE_OPENCL_1_2 "${CMAKE_CURRENT_LIST_DIR}/include/1.2" )
在其它模块的CMakeLists.txt中读取property:
get_property(INCLUDE_OPENCL GLOBAL PROPERTY "INCLUDE_OPENCL_1_2" )

7、set_target_properties

set_target_properties(target1 target2 ...PROPERTIES prop1 value1 prop2 value2 ...)
set_target_properties指令可以用来设置输出的名称。对于动态库,还可以用来指定动态库版本和API版本。为了实现动态库版本号,使用 set_target_properties指令方法如下:
set_target_properties (hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代动态库版本,SOVERSION指代API版本。
Hello库包含Hello.cpp、Hello.h两个文件。
Hello.h文件如下:

#include <iostream>

class Hello
{
public:
    void Print();
};

Hello.cpp文件如下:

#include "Hello.h"

void Hello::Print()
{
    std::cout << "Hello world." << std::endl;
}

CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 2.8.9)
SET (LIBHELLO_SRC Hello.cpp)
# 添加动态库,关键词为shared,不需要写全libhello.so,
ADD_LIBRARY (hello SHARED ${LIBHELLO_SRC})
# 添加静态库,关键词为static,不需要写全libhello_static.a
# target不能重名,因此静态库的target不同与动态库的target重名,修改为hello_static
ADD_LIBRARY (hello_static STATIC ${LIBHELLO_SRC})
# 通常,静态库名字跟动态库名字是一致的,只是扩展名不同;
# 即:静态库名为 libhello.a; 动态库名为libhello.so;
# 因此,希望"hello_static"在输出时,以"hello"的名字显示
SET_TARGET_PROPERTIES (hello_static PROPERTIES OUTPUT_NAME "hello")
GET_TARGET_PROPERTY (OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE (STATUS "This is the hello_static OUTPUT_NAME: " ${OUTPUT_VALUE})
# cmake在构建一个新的target时,会尝试清理掉其它使用target名字的库,
# 因此,在构建libhello.a时,就会清理掉libhello.so.
# 为了避免清理问题,比如再次使用SET_TARGET_PROPERTIES定义 CLEAN_DIRECT_OUTPUT属性。
SET_TARGET_PROPERTIES (hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES (hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)

#通常,动态库包含一个版本号,VERSION指代动态库版本,SOVERSION指代API版本。
SET_TARGET_PROPERTIES (hello PROPERTIES VERSION 1.2 SOVERSION 1)

#构建完成后需要将libhello.a, libhello.so.x以及hello.h安装到系统目录,
# 将hello的共享库安装到<prefix>/lib目录;
# 将hello.h安装<prefix>/include/hello目录。
INSTALL (TARGETS hello hello_static LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL (FILES hello.h DESTINATION include/hello)

创建build目录,进入build目录。
cmake ../
make
生成的目标文件如下:
技术图片
执行安装:
sudo make install
相应的库文件和头文件分别被安装到/usr/local/lib和/usr/local/include/hello目录下。

8、get_target_property

get_target_property(OUTPUT_VALUE hello_static OUTPUT_NAME)
message(STATUS "This is the hello_static OUTPUT_NAME: "${OUTPUT_VALUE})

如果没有定义hello_static变量的OUTPUT_NAME属性,则OUTPUT_VALUE被赋值NOTFOUND。

9、set_property

set_property(<GLOBAL??????????????????????????? |
??????????????? DIRECTORY [dir]?????????????????? |
??????????????? TARGET??? [target1 [target2 ...]] |
??????????????? SOURCE??? [src1 [src2 ...]]?????? |
??????????????? TEST????? [test1 [test2 ...]]???? |
??????????????? CACHE???? [entry1 [entry2 ...]]>
?????????????? [APPEND][APPEND_STRING]
?????????? ????PROPERTY <name>[value1 [value2 ...]])

在某个域中对零个或多个对象设置一个属性。第一个参数决定该属性设置所在的域,必须为下面中的其中之一:
GLOBAL域是唯一的,并且不接特殊的任何名字。
DIRECTORY域默认为当前目录,但可以用全路径或相对路径指定其它目录(指定目录必须已经被CMake处理)。
TARGET域可命名零或多个已经存在的目标。
SOURCE域可命名零或多个源文件,源文件属性只对在相同目录下的目标是可见的(CMakeLists.txt)。
TEST域可命名零或多个已存在的测试。
CACHE域必须命名零或多个已存在条目的cache。
必选项PROPERTY后为要设置的属性的名字。其它参数用于构建以分号隔开的列表形式的属性值。如果指定了APPEND选项,则指定的列表将会追加到任何已存在的属性值当中。如果指定了APPEND_STRING选项,则会将值作为字符串追加到任何已存在的属性值。

10、get_property

get_property(<variable>
???????????? ??<GLOBAL???????????? |
??????????????? DIRECTORY [dir]??? |
??????????????? TARGET??? <target> |
??????????????? SOURCE??? <source> |
??????????????? TEST????? <test>?? |
??????????????? CACHE???? <entry>? |
??????????????? VARIABLE>
?????????????? PROPERTY <name>
?????????????? [SET | DEFINED |BRIEF_DOCS | FULL_DOCS])

必选项PROPERTY后面紧跟着要获取的属性的名字。如果指定了SET选项,则变量会被设置为一个布尔值,表明该属性是否已设置。如果指定了DEFINED选项,则变量也会被设置为一个布尔值,表明该属性是否已定义(如通过define_property)。如果定义了BRIEF_DOCS或FULL_DOCS选项,则该变量被设置为一个字符串,包含了对请求的属性的文档。如果该属性没有相关文件,则会返回NOTFOUND。

get_target_property(OUTPUT_VALUE all STATUS)
if(${OUTPUT_VALUE} STREQUAL OUTPUT_VALUE-NOTFOUND)
    #Target all 不存在
else()
    #Target all 存在
endif()

11、list

定义列表
set(列表名 值1 值2 ... 值N)
set(列表名 “值1;值2; ...;值N”)

set(list_var 1 2 3 4) # list_var = 1;2;3;4
set(list_foo "5;6;7;8") # list_foo = 5;6;7;8
message(${list_var})#输出: 1234
message(${list_foo})#输出:5678
message("${list_var}")#输出:1;2;3;4
message("${list_foo}")#输出:5;6;7;8

不加引号的引用cmake将自动在分号处进行切分成多个列表元素,并将其作为多个独立的参数传给命令。加引号的引用cmake不会进行切分并保持分号不动,把整个引号内的内容当作一个参数传给命令。
常用列表操作如下:
list(LENGTH list output variable)
LENGTH返回列表的长度

list(GET <list> <elementindex> [<element index> ...]
?????? <output variable>)

GET返回列表中指定下标的元素

list(APPEND <list><element> [<element> ...])

APPEND添加新元素到列表中

list(FIND <list> <value><output variable>)

FIND查找list列表中为value的值

list(INSERT <list><element_index> <element> [<element> ...])

INSERT 将新元素插入到列表中指定的位置

list(REMOVE_ITEM <list> <value>[<value> ...])

REMOVE_ITEM从列表中删除某个元素

list(REMOVE_AT <list><index> [<index> ...])

REMOVE_AT从列表中删除指定下标的元素

list(REMOVE_DUPLICATES <list>)

REMOVE_DUPLICATES从列表中删除重复的元素

list(REVERSE <list>)

REVERSE将列表的内容实地反转,改变的是列表本身,而不是其副本

list(SORT <list>)

SORT将列表按字母顺序实地排序,改变的是列表本身,而不是其副本
列表的操作方法会在当前的CMake变量域创建一些新值,即使列表本身是在父域中定义的,LIST命令也只会在当前域创建新的变量值,为了将操作结果向上传递,需要通过SET PARENT_SCOPE, SET CACHE INTERNAL或其他值域扩展的方法。
当指定索引值时,element index为大于或等于0的值,从列表的开始处索引,0代表列表的第一个元素。如果element index为小于或等于-1的值,从列表的结尾处索引,-1代表列表的最后一个元素。

12、file

file(WRITE filename "message towrite"... )
WRITE将一则信息写入文件’filename’中,如果文件存在,会覆盖,如果不存在,会创建文件。

file(APPEND filename "message to write"... )

APPEND将信息内容追加到文件末尾。
file(READ filename variable [LIMIT numBytes] [OFFSEToffset] [HEX])
READ会读取文件的内容并将其存入到变量中。会在给定的偏移量处开始读取最多numBytes个字节。如果指定了HEX参数,二进制数据将会被转换成十进制表示形式并存储到变量中。
file(&lt;MD5|SHA1|SHA224|SHA256|SHA384|SHA512&gt; filenamevariable)
MD5, SHA1, SHA224, SHA256, SHA384, SHA512会计算出文件内容对应的加密散列。

file(STRINGS filename variable [LIMIT_COUNT num]
???? [LIMIT_INPUT numBytes] [LIMIT_OUTPUTnumBytes]
???? [LENGTH_MINIMUM numBytes] [LENGTH_MAXIMUMnumBytes]
???? [NEWLINE_CONSUME] [REGEX regex]
??? ?[NO_HEX_CONVERSION])

STRINGS从文件中解析出ASCII字符串列表并存储在变量中。文件中的二进制数据将被忽略,回车符(CR)也会被忽略。可以解析Intel Hex和Motorola S-record文件,两种文件在读取时会自动转换为二进制格式,可以使用参数NO_HEX_CONVERSION禁用自动转换。LIMIT_COUNT设置可返回的最大数量的字符串。LIMIT_INPUT 设置从输入文件中可读取的最大字节数。LIMIT_OUTPUT设置了存储在输出变量中最大的字节数。 LENGTH_MINIMUM设置了返回的字符串的最小长度。小于这个长度的字符串将被忽略。 LENGTH_MAXIMUM 设置返回的字符串的最大长度。大于这个长度的字符串将被切分为长度不大于于最大长度值的子字符串。NEWLINE_CONSUME 允许换行符包含进字符串中而不是截断它们。REGEX 指定了返回的字符串必须匹配的正则表达式的模式。典型用法
file(STRINGS file.txt myfile)
将输入文件的每行内容存储在变量"myfile"中。
file(GLOB variable [RELATIVE path] [globbingexpressions]...)
GLOB 会产生一个由所有匹配globbing表达式的文件组成的列表,并将其保存到变量中。Globbing表达式与正则表达式类似,但更简单。如果指定了RELATIVE 标记,返回的结果将是与指定的路径相对的路径构成的列表。 (通常不推荐使用GLOB命令来从源码树中收集源文件列表。
globbing 表达式包括:

*.cxx?????- match all files with extension cxx
*.vt?????? - match all files with extension vta,...,vtz
f[3-5].txt - match files f3.txt,f4.txt, f5.txt
file(GLOB_RECURSE variable [RELATIVE path]?
???? [FOLLOW_SYMLINKS] [globbingexpressions]...)

GLOB_RECURSE会遍历匹配目录的所有文件以及子目录下面的文件。对于属于符号链接的子目录,只有FOLLOW_SYMLINKS指定1或者cmake策略CMP0009没有设置为NEW时,才会遍历链接目录。?

file(RENAME <oldname> <newname>)

RENAME 将文件系统中的文件或目录移动到目标位置,并自动替换目标位置处的文件或目录。
file(REMOVE [file1 ...])
REMOVE 会删除指定的文件以及子目录下的文件。?
file(REMOVE_RECURSE [file1 ...])
REMOVE_RECURSE 会删除指定的文件及子目录,包括非空目录。
file(MAKE_DIRECTORY [directory1 directory2 ...])
MAKE_DIRECTORY在指定目录处创建子目录,如果父目录不存在,会创建父目录。
file(RELATIVE_PATH variable directory file)
RELATIVE_PAT推断出指定文件相对于特定目录的路径。
file(TO_CMAKE_PATH path result)
TO_CMAKE_PATH会将路径转换成cmake风格的路径表达形式。
file(TO_NATIVE_PATH path result)
TO_NATIVE_PATH与TO_CMAKE_PATH类似,但执行反向操作,将cmake风格的路径转换为操作系统特定风格的路径表式形式。

file(DOWNLOAD url file [INACTIVITY_TIMEOUT timeout]
???? [TIMEOUT timeout] [STATUS status] [LOGlog] [SHOW_PROGRESS]
???? [EXPECTED_MD5 sum])

DOWNLOAD下载指定URL的资源到指定的文件上。如果指定了LOG 参数,将会把下载的日志保存到相应的变量中。如果指定了STATUS变量,操作的状态信息就会保存在相应的变量中。返回的状态是一个长度为2的列表。第一个元素是操作的返回值。0表示操作过程中无错误发生。如果指定了TIMEOUT,单位于秒,且必须为整数,那么在指定的时间后,操作将会超时,INACTIVITY_TIMEOUT指定了操作在处于活动状态超过指定的秒数后,应该停止。如果指定了EXPECTED_MD5,如果操作会检验下载后的文件的实际md5校验和是否与预期的匹配,如果不匹配,操作将会失败,并返回相应的错误码。如果指定了 SHOW_PROGRESS,那么进度的信息将会被打印成状态信息直到操作完成。

file(UPLOADfilename url [INACTIVITY_TIMEOUT timeout]
???? [TIMEOUT timeout] [STATUS status][LOG log] [SHOW_PROGRESS])

UPLOAD执行的是一个上传操作。参数含义与DOWNLOAD 一致。

file(<COPY|INSTALL> files... DESTINATION<dir>
???? [FILE_PERMISSIONS permissions...]
???? [DIRECTORY_PERMISSIONSpermissions...]
???? [NO_SOURCE_PERMISSIONS][USE_SOURCE_PERMISSIONS]
???? [FILES_MATCHING]
???? [[PATTERN <pattern> | REGEX<regex>]
???? [EXCLUDE] [PERMISSIONSpermissions...]] [...])

COPY表示复制文件,目录以及符号链接到一个目标文件夹中。输入路径将视为相对于当前源码目录的路径。目标路径则是相对于当前的构建目录。复制保留输入文件的一些权限属性。

13、exec_program

EXEC_PROGRAM(Executable [dir where to run] [ARGS <args>][OUTPUT_VARIABLE <var>] [RETURN_VALUE <value>])

用于在指定目录运行某个程序(默认为当前CMakeLists.txt所在目录),通过ARGS添加参数,通过OUTPUT_VARIABLE和RETURN_VALUE获取输出和返回值

# 在src中运行ls命令,在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)

14、ENV

$ENV{VAR}是对环境变量VAR的引用,cmake支持变量嵌套引用,解引用的顺序从内到外。

message("PATH = $ENV{PATH}")

15、function

自定义函数命令格式如下:

function(<name> [arg1 [arg2 [arg3 ...]]])
# 自定义命令块
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
endfunction(<name>)

函数名为name,参数为arg1, arg2, arg3, ...的函数命令。参数之间用空格进行分隔,如果某一参数里面包含空格最好用双引号把该参数包起来(引号内的所有字符串只当作一个参数),比如”arg1”。如果不指定参数列表,则函数可以接受任意的参数,ARGC内置变量表明传人参数的个数,ARGV0, ARGV1, ARGV2, ...内置变量可以获得对应传入的参数,ARGV内置变量可以获得整个参数列表。

# print函数定义
function(print x y z)
  message("Calling function ‘print‘:")
  message("  x = ${x}")
  message("  y = ${y}")
  message("  z = ${z}")
  message("ARGC = ${ARGC} arg1 = ${ARGV0} arg2 = ${ARGV1} arg3 = ${ARGV2} all args = ${ARGV}")
# endfunction(print)
# 函数调用
print(1 2 3)

16、macro

自定义宏命令如下:

macro(<name> [arg1 [arg2 [arg3 ...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
endmacro(<name>)

以上是关于GNU开发工具——CMake进阶的主要内容,如果未能解决你的问题,请参考以下文章

GNU开发工具——CMake快速入门

GNU开发工具——CMake工程实践

GNU开发工具——CMake模块

GNU开发工具——CMake快速入门

Windows下使用CMake进阶

CMAKE_GNUtoMS:将GNU格式库转换为MSVC格式