Cmake的使用和CMakeLists.txt的编写
PROJECT 指令的语法是: PROJECT(projectname [CXX] [C] [Java])
1
1
PROJECT
(
projectname
[
CXX
] [
C
] [
Java
])
你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,
默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量 :
<projectname> _BINARY_DIR 以及 <projectname> _SOURCE_DIR ,这里就是
HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR( 所以 CMakeLists.txt 中两个 MESSAGE
指令可以直接使用了这两个变量 ) ,因为采用的是内部编译,两个变量目前指的都是工程所
在路径 /backup/cmake/t1 ,后面我们会讲到外部编译,两者所指代的内容会有所不同。
同时 cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR
变量,他们的值分别跟 HELLO_BINARY_DIR 与 HELLO_SOURCE_DIR 一致。
为了统一起见,建议以后直接使用 PROJECT_BINARY_DIR , PROJECT_SOURCE_DIR ,即
使修改了工程名称,也不会影响这两个变量。如果使用了
<projectname> _SOURCE_DIR ,修改工程名称后,需要同时修改这些变量。
SET 指令的语法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
1
1 SET ( VAR [ VALUE ] [ CACHE
TYPE
DOCSTRING [ FORCE ]])
现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可。 比如我们用到的是 SET(SRC_LIST main.c) ,如果有多个源文件,也可以定义成: SET(SRC_LIST main.c t1.c t2.c) 。
MESSAGE 指令的语法是: MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
1
1 MESSAGE ([ SEND_ERROR
|
STATUS
|
FATAL_ERROR ] "message to display" ...)
这个指令用于向终端输出用户定义的信息,包含了三种类型 : SEND_ERROR ,产生错误,生成过程被跳过。 SATUS ,输出前缀为 — 的信息。 FATAL_ERROR ,立即终止所有 cmake 过程 . 我们在这里使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量 HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR 。
ADD_EXECUTABLE 指令的语法是: ADD_EXECUTABLE(hello ${SRC_LIST})
1
1 ADD_EXECUTABLE ( hello
$ { SRC_LIST })
定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中 定义的源文件列表, 本例中你也可以直接写成 ADD_EXECUTABLE(hello main.c) 。 在本例我们使用了 ${} 来引用变量,这是 cmake 的变量应用方式,但是,有一些例外,比 如在 IF 控制语句,变量是直接使用变量名引用,而不需要 ${} 。如果使用了 ${} 去应用变 量,其实 IF 会去判断名为 ${} 所代表的值的变量,那当然是不存在的了。
基本语法规则 前面提到过, cmake 其实仍然要使用 ”cmake 语言和语法 ” 去构建,上面的内容就是所谓的 ”cmake 语言和语法 ” ,最简单的语法规则是: 1 ,变量使用 ${} 方式取值,但是在 IF 控制语句中是直接使用变量名 2 ,指令 ( 参数 1 参数 2...) 参数使用括弧括起,参数之间使用空格或分号分开。 以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成: ADD_EXECUTABLE(hello main.c func.c) 或者 ADD_EXECUTABLE(hello main.c;func.c) 3 ,指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。 上面的 MESSAGE 指令我们已经用到了这条规则: MESSAGE(STATUS “This is BINARY dir” ${HELLO_BINARY_DIR}) 也可以写成: MESSAGE(STATUS “This is BINARY dir ${HELLO_BINARY_DIR}”) 这里需要特别解释的是作为工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的。 hello 定义了可执行文件的文件名,你完全可以写成: ADD_EXECUTABLE(t1 main.c) 编译后会生成一个 t1 可执行文件。
关于语法的疑惑 cmake 的语法还是比较灵活而且考虑到各种情况,比如 SET(SRC_LIST main.c) 也可以写成 SET(SRC_LIST “main.c”) 是没有区别的,但是假设一个源文件的文件名是 fu nc.c( 文件名中间包含了空格 ) 。 这时候就必须使用双引号,如果写成了 SET(SRC_LIST fu nc.c) ,就会出现错误,提示 你找不到 fu 文件和 nc.c 文件。这种情况,就必须写成 : SET(SRC_LIST “fu nc.c”) 此外,你可以可以忽略掉 source 列表中的源文件后缀,比如可以写成 ADD_EXECUTABLE(t1 main) , cmake 会自动的在本目录查找 main.c 或者 main.cpp 等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.c 一个 main. 同时参数也可以使用分号来进行分割。 下面的例子也是合法的: ADD_EXECUTABLE(t1 main.c t1.c) 可以写成 ADD_EXECUTABLE(t1 main.c;t1.c). 我们只需要在编写 CMakeLists.txt 时注意形成统一的风格即可。
清理工程: 跟经典的 autotools 系列工具一样,运行 : make clean
1
1 make
clean
即可对构建结果进行清理。
内部构建与 外部构建 : 上面的例子展示的是 “ 内部构建 ” ,相信看到生成的临时文件比您的代码文件还要多的时候, 估计这辈子你都不希望再使用内部构建 :-D 举个简单的例子来说明外部构建,以编译 wxGTK 动态库和静态库为例,在 Everest 中打包 方式是这样的: 解开 wxGTK 后。 在其中建立 static 和 shared 目录。 进入 static 目录,运行 ../configure –enable-static;make 会在 static 目录生 成 wxGTK 的静态库。 进入 shared 目录,运行 ../configure –enable-shared;make 就会在 shared 目录 生成动态库。 这就是外部编译的一个简单例子。 对于 cmake ,内部编译上面已经演示过了,它生成了一些无法自动删除的中间文件,所以, 引出了我们对外部编译的探讨,外部编译的过程如下: 1 ,首先,请清除 t1 目录中除 main.c CmakeLists.txt 之外的所有中间文件,最关键 的是 CMakeCache.txt 。 2 ,在 t1 目录中 建立 build 目录 ,当然你也可以在任何地方建立 build 目录,不一定必 须在工程目录中。 3 , 进入 build 目录,运行 cmake .. ( 注意 ,.. 代表父目录,因为父目录存在我们需要的 CMakeLists.txt ,如果你在其他地方建立了 build 目录,需要运行 cmake < 工程的全 路径 >) ,查看一下 build 目录,就会发现了生成了编译需要的 Makefile 以及其他的中间 文件 . 4 ,运行 make 构建工程,就会在当前目录 (build 目录 ) 中获得目标文件 hello 。 上述过程就是所谓的 out-of-source 外部编译,一个最大的好处是,对于原有的工程没 有任何影响, 所有动作全部发生在编译目录 。通过这一点,也足以说服我们全部采用外部编 译方式构建工程。 这里需要特别注意的是: 通过外部编译进行工程构建, HELLO_SOURCE_DIR 仍然指代工程路径 , 即 /backup/cmake/t1 而 HELLO_BINARY_DIR 则指代编译路径 , 即 /backup/cmake/t1/build
ADD_SUBDIRECTORY 指令 ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
1
1 ADD_SUBDIRECTORY ( source_dir [ binary_dir ] [ EXCLUDE_FROM_ALL ]) 这个指令用于向当前工程添加存放源文件的子目录, 并可以指定中间二进制和目标二进制存 放的位置 。 EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程 的 example ,可能就需要工程构建完成后,再进入 example 目录单独进行构建 ( 当然,你 也可以通过定义依赖来解决此类问题 ) 。 上面的例子定义了将 src 子目录加入工程,并指定编译输出 ( 包含编译中间结果 ) 路径为 bin 目录。如果不进行 bin 目录的指定,那么编译结果 ( 包括中间结果 ) 都将存放在 build/src 目录 ( 这个目录跟原有的 src 目录对应 ) , 指定 bin 目录后,相当于在编译时 将 src 重命名为 bin ,所有的中间结果和目标二进制都将存放在 bin 目录。
换个地方保存目标二进制 不论是 SUBDIRS 还是 ADD_SUBDIRECTORY 指令 ( 不论是否指定编译输出目录 ) ,我们都可 以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量 来指定最终的目标二进制的位置 ( 指最终生成的 hello 或者最终的共享库 ,不包含编译生成 的中间文件 ) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
2
1 SET ( EXECUTABLE_OUTPUT_PATH
$ { PROJECT_BINARY_DIR } / bin ) 2 SET ( LIBRARY_OUTPUT_PATH
$ { PROJECT_BINARY_DIR } / lib )
在第一节我们提到了 <projectname> _BINARY_DIR 和 PROJECT_BINARY_DIR 变量, 他
们指的编译发生的当前目录 ,如果是内部编译,就相当于 PROJECT_SOURCE_DIR 也就是
工程代码所在目录,如果是 外部编译 ,指的是外部编译所在目录, 也就是本例中的 build
目录。
所以,上面两个指令分别定义了:
可执行二进制的输出路径为 build/bin 和库的输出路径为 build/lib.
本节我们没有提到共享库和静态库的构建,所以,你可以不考虑第二条指令。
问题是,我应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的
CMakeLists.txt , 把握一个简单的原则,在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY ,
如果需要改变目标存放路径,就在哪里加入上述的定义。
如何安装 安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定 目录安装。 所以,即使最简单的手工编写的 Makefile ,看起来也是这个样子的: DESTDIR= install: mkdir -p $(DESTDIR)/usr/bin install -m 755 hello $(DESTDIR)/usr/bin 你可以通过 : make install 将 hello 直接安装到 /usr/bin 目录,也可以通过 make install DESTDIR=/tmp/test 将他安装在 /tmp/test/usr/bin 目录,打包时这个方式经常被使用。 稍微复杂一点的是还需要定义 PREFIX ,一般 autotools 工程,会运行这样的指令 : ./configure –prefix=/usr 或者 ./configure --prefix=/usr/local 来指定 PREFIX 比如上面的 Makefile 就可以改写成 : DESTDIR= PREFIX=/usr install: mkdir -p $(DESTDIR)/$(PREFIX)/bin install -m 755 hello $(DESTDIR)/$(PREFIX)/bin 那么我们的 HelloWorld 应该怎么进行安装呢? 这里需要引入一个新的 cmake 指令 INSTALL 和一个非常有用的变量 CMAKE_INSTALL_PREFIX 。 CMAKE_INSTALL_PREFIX 变量 类似于 configure 脚本的 –prefix ,常见的使用方法看 起来是这个样子: cmake -DCMAKE_INSTALL_PREFIX=/usr .
1
1 cmake
DCMAKE_INSTALL_PREFIX =/ usr .
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及
文件、目录、脚本等。 INSTALL 指令包含了各种安装类型,我们需要一个个分开解释:
目标文件的安装:
INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION <dir> ] [PERMISSIONS permissions...] [CONFIGURATIONS] [Debug|Release|...]] [COMPONENT <component> ] [OPTIONAL] ] [...])
9
1
INSTALL ( TARGETS
targets ... 2 [[ ARCHIVE | LIBRARY | RUNTIME ] 3
[ DESTINATION
< dir
] 4
[ PERMISSIONS
permissions ...] 5
[ CONFIGURATIONS ] 6
[ Debug | Release | ...]] 7 [ COMPONENT
< component
] 8 [ OPTIONAL ] 9 ] [...])
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的
目标文件 ,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种, ARCHIVE 特指静态库 , LIBRARY 特指动态库 , RUNTIME
特指可执行目标二进制 。
DESTINATION 定义了安装的路径 ,如果路径以 / 开头,那么指的是绝对路径,这时候
CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来
定义安装路径,就要写成相对路径,即不要以 / 开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/ <DESTINATION 定义的路径>
1
1 $ { CMAKE_INSTALL_PREFIX } /< DESTINATION
定义的路径
举个简单的例子:
INSTALL(TARGETS myrun mylib mystaticlib RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION libstatic )
5
1 INSTALL ( TARGETS
myrun
mylib
mystaticlib 2
RUNTIME
DESTINATION
bin 3
LIBRARY
DESTINATION
lib 4
ARCHIVE
DESTINATION
libstatic 5 )
上面的例子会将: 可执行二进制 myrun 安装到 ${CMAKE_INSTALL_PREFIX}/bin 目录 动态库 libmylib 安装到 ${CMAKE_INSTALL_PREFIX}/lib 目录 静态库 libmystaticlib 安装到 ${CMAKE_INSTALL_PREFIX}/libstatic 目录 特别注意的是你不需要关心 TARGETS 具体生成的路径,只需要写上 TARGETS 名称就可以 了。
普通文件的安装:
INSTALL(FILES files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component> ] [RENAME <name> ] [OPTIONAL])
5
1 INSTALL ( FILES
files ... DESTINATION
< dir
2 [ PERMISSIONS
permissions ...] 3 [ CONFIGURATIONS [ Debug | Release | ...]] 4 [ COMPONENT
< component
] 5 [ RENAME
< name
] [ OPTIONAL ]) (头文件使用该种方式安装) 可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。 如果 默认不定义权限 PERMISSIONS ,安装后的权限为: OWNER_WRITE, OWNER_READ, GROUP_READ, 和 WORLD_READ ,即 644 权限。
非目标文件的可执行程序安装 ( 比如脚本之类 ) :
INSTALL(PROGRAMS files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component> ] [RENAME <name> ] [OPTIONAL])
5
1 INSTALL ( PROGRAMS
files ... DESTINATION
< dir
2 [ PERMISSIONS
permissions ...] 3 [ CONFIGURATIONS [ Debug | Release | ...]] 4 [ COMPONENT
< component
] 5 [ RENAME
< name
] [ OPTIONAL ]) 跟上面的 FILES 指令使用方法一样, 唯一的不同是安装后权限为 : OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE ,即 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...]] [...])
8
1 INSTALL ( DIRECTORY
dirs ... DESTINATION
< dir
2 [ FILE_PERMISSIONS
permissions ...] 3 [ DIRECTORY_PERMISSIONS
permissions ...] 4 [ USE_SOURCE_PERMISSIONS ] 5 [ CONFIGURATIONS [ Debug | Release | ...]] 6 [ COMPONENT
< component
] 7 [[ PATTERN
< pattern
|
REGEX
< regex
] 8 [ EXCLUDE ] [ PERMISSIONS
permissions ...]] [...]) 这里主要介绍其中的 DIRECTORY 、 PATTERN 以及 PERMISSIONS 参数。 DIRECTORY 后面连接的是所在 Source 目录的相对路径 ,但务必注意: abc 和 abc/ 有很大的区别。 如果目录名不以 / 结尾,那么这个目录将被安装为目标路径下的 abc , 如果目录名以 / 结尾, 代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。 PATTERN 用于使用正则表达式 进行过滤, PERMISSIONS 用于 指定 PATTERN 过滤后的文件 权限。 我们来看一个例子 :
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj PATTERN "CVS" EXCLUDE PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ)
4
1 INSTALL ( DIRECTORY
icons
scripts /
DESTINATION
share / myproj
PATTERN
"CVS"
EXCLUDE 2
PATTERN
"scripts/*" 3
PERMISSIONS
OWNER_EXECUTE
OWNER_WRITE
OWNER_READ 4
GROUP_EXECUTE
GROUP_READ
)
这条指令的执行结果是:
将 icons 目录安装到 <prefix> /share/myproj ,将 scripts/ 中的内容安装到
<prefix> /share/myproj
不包含目录名为 CVS 的目录,对于 scripts/* 文件指定权限为 OWNER_EXECUTE
OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
安装时 CMAKE 脚本的执行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
1
1 INSTALL ([[ SCRIPT
< file
] [ CODE
< code
]] [...])
SCRIPT 参数用于在安装时 调用 cmake 脚本文件 (也就是 <abc> .cmake 文件)
CODE 参数用于 执行 CMAKE 指令 ,必须以双引号括起来。比如:
INSTALL(CODE "MESSAGE("Sample install message.")")
1
1 INSTALL ( CODE
"MESSAGE("Sample install message.")" )
一个疑问 如果我没有定义 CMAKE_INSTALL_PREFIX 会安装到什么地方? 你可以尝试以下, cmake ..;make;make install ,你会发现 CMAKE_INSTALL_PREFIX 的默认定义是 /usr/loca
ADD_LIBRARY
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
3
1 ADD_LIBRARY ( libname [ SHARED | STATIC | MODULE ] 2 [ EXCLUDE_FROM_ALL ] 3
source1
source2 ... sourceN ) 你不需要写全 libhello.so , 只需要填写 hello 即可 , cmake 系统会自动为你生成 libhello.X 类型有三种 : SHARED ,动态库 STATIC ,静态库 MODULE ,在使用 dyld 的系统有效,如果不支持 dyld ,则被当作 SHARED 对待。 EXCLUDE_FROM_ALL 参数的意思是这个库 不会被默认构建 ,除非有其他的组件依赖或者手 工构建。
SET_TARGET_PROPERTIES
其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...)
3
1 SET_TARGET_PROPERTIES ( target1
target2 ... 2
PROPERTIES
prop1
value1 3
prop2
value2 ...) (这条 命令 要 写在add_library后面 ) 这条指令可以用来 设置输出的名称 ,对于动态库,还可以用来指定动态库版本和 API 版本。 在本例中,我们需要作的是向 lib/CMakeLists.txt 中添加一条: SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") 这样,我们就可以同时得到 libhello.so/libhello.a 两个库了。
与他对应的指令是: GET_TARGET_PROPERTY(VAR target property)
1
1 GET_TARGET_PROPERTY ( VAR
target
property )
具体用法如下例,我们向 lib/CMakeListst.txt 中添加: GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME) MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:"${OUTPUT_VALUE})
2
1 GET_TARGET_PROPERTY ( OUTPUT_VALUE
hello_static
OUTPUT_NAME ) 2 MESSAGE ( STATUS
"This is the hello_static OUTPUT_NAME:" $ { OUTPUT_VALUE })
如果 没有 这个 属性定义 ,则 返回 NOTFOUND .
清理生成的动态库或静态库 SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1) SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
2
1 SET_TARGET_PROPERTIES ( hello
PROPERTIES
CLEAN_DIRECT_OUTPUT
1 ) 2 SET_TARGET_PROPERTIES ( hello_static
PROPERTIES
CLEAN_DIRECT_OUTPUT
1 )
动态库版本号 按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是 libhello.so.1.2 libhello.so ->libhello.so.1 libhello.so.1->libhello.so.1.2 为了实现动态库版本号,我们仍然需要使用 SET_TARGET_PROPERTIES 指令。 具体使用方法如下: SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
1 SET_TARGET_PROPERTIES ( hello
PROPERTIES
VERSION
1.2
SOVERSION
1 ) VERSION 指代动态库版本, SOVERSION 指代 API 版本 。 将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果。 在 build/lib 目录会生成: libhello.so.1.2 libhello.so.1->libhello.so.1.2 libhello.so ->libhello.so.1