该笔记参考这个博客
待查询名词
- Everest系统
- KDE4
CMake简介
CMake是一个跨平台的编译工具
,CMake 的核心是读取一个容易理解的文本文件CMakeLists.txt
,开发者可以往里面添加自己的源码目录,并把CMakeLists.txt
这个文件放在源代码所在的目录中。通过运行CMake
命令时,它会寻找这个文件,根据里面的内容生成标准的 Makefiles(UNIX 平台专用)或是利用命令行开关生成 XCode 项目文件(用于构建 OS X 系统上 XCode 开发工具所面向的 Mac 程序),甚至还能通过您的源代码生成 MSVC 项目。
CMake特点
- 开放源代码,使用类BSD许可发布。http://CMake.org/HTML/Copyright.html
跨平台,并可生成native编译配置文件,在Linux/Unix平台,生成makefile,在苹果平台,可以生成xcode,在Windows平台,可以生成MSVC的工程文件。
能够管理大型项目,KDE4就是最好的证明。
简化编译构建过程和编译过程。Cmake的工具链非常简单:CMake+make。
高效虑,按照KDE官方说法,CMake构建KDE4的kdelibs要比使用autotools来构建KDE3.5.6的kdelibs快40%,主要是因为 Cmake在工具链中没有libtool。
可扩展,可以为CMake编写特定功能的模块,扩充CMake功能。
大佬给的意见
如果你没有实际的项目需求,那么看到这里就可以停下来了,因为CMake的学习过程就是实践过程,没有实践,读的再多几天后也会忘记。
如果你的工程只有几个文件,直接编写Makefile是最好的选择。
如果使用的是C/C++/Java之外的语言,请不要使用CMake(至少目前是这样)
如果你使用的语言有非常完备的构建体系,比如java的ant,也不需要学习CMake,虽然有成功的例子,比如QT4.3的csharp绑定qyoto。
如果项目已经采用了非常完备的工程管理工具,并且不存在维护问题,没有必要迁移到CMake
如果仅仅使用qt编程,没有必要使用CMake,因为qmake管理Qt工程的专业性和自动化程度比CMake要高很多。
CMake入门使用
创建空目录作为工作空间1
2 mkdir -p backup/cmake/t1
cd backup/cmake/t1
创建源代码文件main.c
并编写代码1
2
3
4
5
int main(){
printf("hello cmake\n");
return 0;
}
创建CMakeLists.txt
并编写指令 注意文件名称以及大小写要一致1
2
3
4
5PROJECT(HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir" ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir" ${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
执行cmake命令1
cmake .
执行完毕你的工作目录中会出现makefile
执行make命令,执行完这个命令你会找到编译好的可执行文件,名为hello1
make
执行文件1
./hello
更好一点的工程
本小节的任务是让前面的Hello World更像一个工程,我们需要作的是:
- 为工程添加一个子目录src,用来放置工程源代码;
- 添加一个子目录doc,用来放置这个工程的文档hello.txt
- 在工程目录添加文本文件COPYRIGHT, README;
- 在工程目录添加一个runhello.sh脚本,用来调用hello二进制
- 将构建后的目标文件放入构建目录的bin子目录;
- 最终安装这些文件:将hello二进制与runhello.sh安装至/usr/bin,将doc目录的内容以及COPYRIGHT/README安装到/usr/share/doc/cmake/t2
常用指令
PROJECT指令
1 | PROJECT(projectname [CXX] [C] [Java]) |
指令定义了工程的名称,并可指定工程支持的语言,支持的语言列表是可以忽略的
使用这个指令会隐式的定义两个cmake变量:<projectname>_BINARY_DIR
以及 <projectname>_SOURCE_DIR
,例如上一节中的HELLO_BINARY_DIR与HELLO_SOURCE_DIR
<projectname>_BINARY_DIR
指向文件的编译目录,即你运行cmake命令的那个目录,<projectname>_SOURCE_DIR
指向文件的源代码目录,即/backup/cmake/t1
CMake分为内部构筑与外部构筑;内部构筑即在工程源代码的根目录进行构筑,在此模式下<projectname>_BINARY_DIR
与<projectname>_SOURCE_DIR
指向的地址是相同的,外部构筑通常我们会创建一个名为build
的目录,在此目录中进行构筑,此模式下上述的两个变量指向的地址是不相同的
同时cmake系统也帮助我们预定义了 PROJECT_BINARY_DIR
和 PROJECT_SOURCE_DIR
变量,他们的值分别与<projectname>_BINARY_DIR
与<projectname>_SOURCE_DIR
一致;为了统一起见,建议以后直接使用 PROJECT_BINARY_DIR
与PROJECT_SOURCE_DIR
,即使修改了工程名称,也不会影响这两个变量。如果使用了<projectname>_SOURCE_DIR
,修改工程名称后,需要同时修改这些变量
SET指令
1 | SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]]) |
目前只需要了解SET指令可以用来显式的定义变量即可。比如我们用到的是SET(SRC_LIST main.c)
,如果有多个源文件,也可以定义成SET(SRC_LIST main.c t1.c t2.c)
MESSAGE指令
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指令
1 | ADD_EXECUTABLE(hello ${SRC_LIST}) |
定义了这个工程会生成一个文件名为hello的可执行文件,相关的源文件是SRC_LIST
中定义的源文件列表, 上节你也可以直接写成ADD_EXECUTABLE(hello main.c)
在本例我们使用了${}来引用变量,这是CMake的变量应用方式,但是有一些例外,比如在IF控制语句,变量是直接使用变量名引用,而不需要${}。如果使用了${}去应用变量,其实IF会去判断名为${}所代表的值的变量,那当然是不存在的了。
清除工程
1 | make clean |
ADD_SUBDIRECTORY指令
在工程中每创建一个目录都需要在该目录中创建一个CMakeLists.txt
,并且为了让CMake将这些目录中的文件进行构筑,需要使用ADD_SUBDIRECTORY
指令1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 创建src目录
mkdir src
mv main.c src
现在的工程看起来是这个样子:一个子目录src,一个CMakeLists.txt
需要为任何子目录建立一个CMakeLists.txt,进入子目录src,编写CMakeLists.txt如下:
ADD_EXECUTABLE(hello main.c)
将t2工程根目录的CMakeLists.txt修改为:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
然后建立build目录,进入build目录进行外部编译。
cmake ..
make
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指令,使用方法是:SUBDIRS(dir1 dir2…),但是这个指令已经不推荐使用。它可以一次添加多个子目录,并且,即使外部编译,子目录体系仍然会被保存。如果我们在上面的例子中将ADD_SUBDIRECTORY (src bin)修改为SUBDIRS(src)。那么在build目录中将出现一个src目录,生成的目标代码hello将存放在src目录中
修改目标二进制文件输出位置
1 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) |
上面两个指令分别定义了可执行二进制的输出路径为build/bin和库的输出路径为build/lib,目前位置我们没有涉及到共享库和静态库的构建
这两条指令写在工程的CMakeLists.txt还是src目录下的CMakeLists.txt?一个简单的原则,在哪里ADD_EXECUTABLE或ADD_LIBRARY且需要改变目标存放路径,就在哪里加入上述的定义
在这个例子里,当然就是指src下的CMakeLists.txt了
INSTALL命令
这里需要引入一个CMake指令INSTALL
和一个变量DCMAKE_INSTALL_PREFIX
DCMAKE_INSTALL_PREFIX
变量类似于configure脚本的–prefix
,常见的使用方法看起来是这个样子:cmake -DCMAKE_INSTALL_PREFIX=/usr .
,DCMAKE_INSTALL_PREFIX的默认定义是/usr/local
INSTALL指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。INSTALL指令包含了各种安装类型,接下来分开解释:
安装目标文件
1 | INSTALL(TARGETS targets... |
参数中的TARGETS后面跟的就是我们通过ADD_EXECUTABLE或者ADD_LIBRARY定义的目标文件,可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,ARCHIVE特指静态库,LIBRARY特指动态库,RUNTIME特指可执行目标二进制。
DESTINATION定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX其实就无效了。如果你希望使用CMAKE_INSTALL_PREFIX来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
举个简单的例子1
2
3
4
5INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
安装普通文件
1 | INSTALL(FILES files... DESTINATION <dir> |
安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限PERMISSIONS,安装后的权限为:OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即644权限
举个简单的例子1
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
安装非目标文件的可执行程序(脚本)
1 | INSTALL(PROGRAMS files... DESTINATION <dir> |
跟上面的FILES指令使用方法一样,唯一的不同是安装后权限为:OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755权限
举个简单的例子1
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
安装目录
1 | INSTALL(DIRECTORY dirs... DESTINATION <dir> |
DIRECTORY后面连接的是所在Source目录的相对路径,但务必注意:abc和abc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的abc,如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。PATTERN用于使用正则表达式进行过滤,PERMISSIONS用于指定PATTERN过滤后的文件权限
举个简单的例子1
2
3
4
5INSTALL(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_EXECUTE OWNER_WRITE OWNER_READ ROUP_EXECUTE GROUP_READ
ADD_LIBRARY指令
前置工作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 在lib目录下建立两个源文件hello.c与hello.h
hello.c内容如下:
include "hello.h"
void HelloFunc()
{
printf("Hello World\n");
}
hello.h内容如下:
ifndef HELLO_H
define HELLO_H
include <stdio.h>
void HelloFunc();
endif
`
该指令用于制定构筑动态库或者静态库1
ADD_LIBRARY(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ... sourceN)
你不需要写全libhello.so
,只需要填写hello即可,CMake系统会自动为你生成libhello.X。类型有三种:
- SHARED,动态库(扩展名为.so)
- STATIC,静态库(扩展名为.a)
- MODULE,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待。
- EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
1
2
3
4
5SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
```
接下来创建静态库
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})1
在CMake中因为hello作为一个target是不能重名的,所以静态库的名称不能是hello了,此时构筑出来的是一个名为hello的动态库以及一个叫做hello_static的静态库,这不是我们目的,所以可以通过如下命令修改文件的输出名称
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME “hello”)1
但是在CMake在构建一个新的target时,会尝试清理掉其他使用这个名字的库,在构建libhello.a时,就会清理掉libhello.so,为了回避这个问题,比如再次使用SET_TARGET_PROPERTIES定义
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
综上完整的构筑动态库静态的代码示例为
```shell
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hellow SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hellow_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hellow_static PROPERTIES OUTPUT_NAME "hellow")
SET_TARGET_PROPERTIES(hellow PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hellow_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
INSTALL(TARGETS hellow hellow_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
在工程中引用动态库静态库
引入头文件搜索路径
如果你引用的第三方库头文件已经放入到/usr/include
中那么不需要再做本操作
1 | INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...) |
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:
- CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面
- 通过AFTER或者BEFORE参数,也可以控制是追加还是置前。现在我们在src/CMakeLists.txt中添加一个头文件搜索路径,方式很简单,加入:INCLUDE_DIRECTORIES(/usr/include/hello)
引入动态库
需要使用LINK_DIRECTORIES
和TARGET_LINK_LIBRARIES
两个指令1
LINK_DIRECTORIES(directory1 directory2 ...)
这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径
1 | TARGET_LINK_LIBRARIES(target library1<debug | optimized> library2...) |
这个指令可以用来为target添加需要链接的共享库,TARGET_LINK_LIBRARIES(main hello)
也可以写为TARGET_LINK_LIBRARIES(main libhello.so)
引入静态库
1 | TARGET_LINK_LIBRARIES(main libhello.a) |
其他指令
以下内容出现的指令用小写~
aux_source_directory
1 | aux_source_directory(<dir> <variable>) |
该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名1
2
3# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)