安装
cmake的安装特别简单,ubuntu下直接
$ sudo apt-get install cmake
下面以几个版本的hello world
为例。
版本一
mkdir -p /home/cmake_sample/sample01
cd /home/cmake_sample/sample01
分别建立 main.c
和 CMakeLists.txt
文件
//main.c
#include <stdio.h>
int main(){
printf("Hello World !\n");
return 0;
}
# CMakeLists.txt
PROJECT(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})
CMakeLists.txt
是cmake的构建定义文件,如果工程有多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt
。
(1) 命令
PROJECT
这个指令的语法是:
PROJECT(projectname [CXX] [C] [Java])
你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个cmake变量:<projectname>_BINARY_DIR
以及<projectname>_SOURCE_DIR
,这里就是HELLO_BINARY_DIR
和HELLO_SOURCE_DIR
(所以CMakeLists.txt
中两个MESSAGE
指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径/home/cmake_sample/sample01
,后面我们会讲到外部编译,两者所指代的内容会有所不同。
同时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]])
现阶段,你只需要了解SET
指令可以用来显式的定义变量即可。
MESSAGE
指令的语法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR
,产生错误,生成过程被跳过。STATUS
,输出前缀为—的信息。FATAL_ERROR
,立即终止所有cmake过程
ADD_EXECUTABLE
ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为hello
的可执行文件,相关的源文件是SRC_LIST
中
定义的源文件列表, 本例中你也可以直接写成
ADD_EXECUTABLE(hello main.c)
我们使用了
${}
来引用变量,这是cmake
的变量应用方式,但是,有一些例外,比如在IF
控制语句,变量是直接使用变量名引用,而不需要${}
。如果使用了${}
去应用变量,其实IF
会去判断名为${}
所代表的值的变量,那当然是不存在的了
(2) 使用
一般推荐新建一个文件夹,专门存储编译的文件。
$ mkdir build
$ cd build
$ cmake ..
输出
[fangjin@ubuntu build]$ cmake ..
-- The C compiler identification is GNU 3.4.5
-- The CXX compiler identification is GNU 3.4.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- This is Binary dir /home/cmake_sample/sample01/build
-- This is Source dir /home/cmake_sample/sample01
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cmake_sample/sample01/build
编译
$ make
如果你需要看到make构建的详细过程,可以使用make VERBOSE=1
或者VERBOSE=1 make
命令来进行构建。
输出
[fangjin@ubuntu build]$ make
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.c.o
Linking C executable hello
[100%] Built target hello
运行./hello
[fangjin@ubuntu build]$ ./hello
Hello World !
版本二
建立目录src存储代码,doc存储文档,添加文本COPYRIGHT,README。
mkdir -p /home/cmake_sample/sample02
cd /home/cmake_sample/sample02
将上一版本的main.c移动到src下,新建src/CMakeLists.txt
ADD_EXECUTABLE(hello main.c)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
新建CMakeLists.txt
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/lesson_02)
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/lesson_02)
(1) 命令
换个地方保存目标二进制
不论是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)
问题是,我应该把这两条指令写在工程的
CMakeLists.txt
还是src
目录下的CMakeLists.txt
,把握一个简单的原则,在哪里ADD_EXECUTABLE
或ADD_LIBRARY
,如果需要改变目标存放路径,就在哪里加入上述的定义。
ADD_SUBDIRECTORY
指令
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL
参数的含义是将这个目录从编译过程中排除,比如,工程的example,可能就需要工程构建完成后,再进入example目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)。
上面的例子定义了将src
子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin
目录。如果不进行bin
目录的指定,那么编译结果(包括中间结果)都将存放在build/src
目录(这个目录跟原有的src
目录对应),指定bin
目录后,相当于在编译时将src
重命名为bin
,所有的中间结果和目标二进制都将存放在bin
目录。
INSTALL
指令
用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
INSTALL指令包含了各种安装类型,我们需要一个个分开解释:
A. 目标文件的安装
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
参数中的TARGETS
后面跟的就是我们通过ADD_EXECUTABLE
或者ADD_LIBRARY
定义的目标文件,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种,ARCHIVE
特指静态库,LIBRARY
特指动态库,RUNTIME
特指可执行目标二进制。
DESTINATION
定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候
CMAKE_INSTALL_PREFIX
其实就无效了。如果你希望使用CMAKE_INSTALL_PREFIX
来
定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
举个简单的例子:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子会将:
- 可执行二进制
myrun
安装到${CMAKE_INSTALL_PREFIX}/bin
目录 - 动态库
libmylib
安装到${CMAKE_INSTALL_PREFIX}/lib
目录 - 静态库
libmystaticlib
安装到${CMAKE_INSTALL_PREFIX}/libstatic
目录
特别注意的是你不需要关心TARGETS
具体生成的路径,只需要写上TARGETS
名称就可以
了。
B. 普通文件的安装
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限PERMISSIONS
,安装后的权限为:
OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ
,即644权限。
C. 非目标文件的可执行程序安装
非目标文件的可执行程序安装(比如脚本之类):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
跟上面的FILES
指令使用方法一样,唯一的不同是安装后权限为:OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE
,即755权限。
d. 目录的安装
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
,如果目录名以/
结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。PATTERN
用于使用正则表达式进行过滤,PERMISSIONS
用于指定PATTERN
过滤后的文件权限。
版本三
本版本使用静态库和动态库
(1) 动态库
mkdir -p /home/cmake_sample/sample03
cd /home/cmake_sample/sample03
在sample03目录下新建CMakeLists.txt
,内容如下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在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
在lib
目录下建立CMakeLists.txt
,内容如下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
如果你要指定libhello.so
生成的位置,可以通过在主工程文件CMakeLists.txt
中修改ADD_SUBDIRECTORY(lib)
指令来指定一个编译输出位置或者在lib/CMakeLists.txt
中添加SET(LIBRARY_OUTPUT_PATH <路径>)
来指定一个新的位置。
ADD_LIBRARY
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
你不需要写全libhello.so
,只需要填写hello
即可,cmake
系统会自动为你生成libhello.X
类型有三种:
SHARED
,动态库STATIC
,静态库MODULE
,在使用dyld
的系统有效,如果不支持dyld
,则被当作SHARED
对待。
(2) 静态库
下面我们用这个指令再来添加静态库:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然后再在build
目录进行外部编译,我们会发现,静态库根本没有被构建,仍然只生成了一个动态库。因为hello
作为一个target
是不能重名的,所以,静态库构建指令无效。如果我们把上面的hello
修改为hello_static
:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就可以构建一个libhello_static.a
的静态库了。
这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库
SET_TARGET_PROPERTIES
其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和API版本。在本例中,我们需要作的是向lib/CMakeLists.txt
中添加一条:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
这样,我们就可以同时得到libhello.so/libhello.a
两个库了。
与他对应的指令是:
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})
让我们来检查一下最终的构建结果,我们发现,libhello.a
已经构建完成,位于build/lib
目录中,但是libhello.so
却消失了。这个问题的原因是:cmake
在构建一个新的target
时,会尝试清理掉其他使用这个名字的库,因为,在构建libhello.a
时,就会清理掉libhello.so
.
为了回避这个问题,比如再次使用SET_TARGET_PROPERTIES
定义CLEAN_DIRECT_OUTPUT
属性。
向lib/CMakeLists.txt
中添加:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
(3) 动态库版本号
按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是
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)
VERSION
指代动态库版本,SOVERSION
指代API版本。
将上述指令加入lib/CMakeLists.txt
中,重新构建看看结果。
在build/lib
目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
(4) 安装共享库和头文件
以上面的例子,我们需要将libhello.a
, libhello.so.x
以及hello.h
安装到系统目
录,才能真正让其他人开发使用,在本例中我们将hello的共享库安装到<prefix>/lib
目录,将hello.h
安装到<prefix>/include/hello
目录。
利用上一节了解到的INSTALL
指令,我们向lib/CMakeLists.txt
中添加如下指令:
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)