这一节中,我们通过一个最简单的例子Helloworld来演示cmake完整的构建过程。
新建一个项目文件夹,进入该文件夹并新建两个文件,分别为main.c和CMakeLists.txt:其内容分别如下:
// Project/main.c
#include
int main(){
printf("Hello World!\n");
return 0;
}
// Project/CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(HELLO)
add_executable(hello main.c)
构建只需要在命令行中依此输入以下两条命令:
cmake .
第一条命令运行后,你会发现目录下生成了CMakeFiles, CMakeCache.txt, cmake_install.cmake等文件,这些文件是中间生成文件,你可以不用去理会;除了上述文件,它自动生成了Makefile。
make
make命令运行后,我们需要的目标文件hello已经构建完成,通过输入下述命令你可以运行它:
./hello
上面的例子展示的是“内部构建”,相信看到生成的临时文件比您的代码文件还要多的时候,估计这辈子你都不希望再使用内部构建,在2.2小节中我们对上述项目进行外部构建。
外部构建会使得项目更具可读性,通过外部构建,我们得到的项目目录结构如下:
Project/
......src/ // 放置工程源代码
.........main.c
.........CMakeLists.txt
......build/ // 进行外部编译
.........bin/
............hello
......doc/ // 放置文档
.........hello.txt
......COPYRIGHT
......README
......runhello.sh
add_executable(hello main.c)
cmake_minimum_required(VERSION 2.8)
project(HELLO)
add_subdirectary(src bin)
cmake .
make
构建完成后,你会发现生成的目标文件hello位于build/bin目录中。
本节的任务:
cmake_minimum_required(VERSION 2.8)
project(HELLOLIB)
add_subdirectory(lib)
#ifndef HELLO_H
#define HELLO_H
#include
void HelloFunc();
#endif
#include "hello.h"
void HelloFunc(){
printf("Hello World\n");
}
set(LIBHELLO hello.c)
add_library(hello SHARED ${
LIBHELLO_SRC})
cmake .
make
这时,你就可以在lib目录得到一个libhello.so,这就是我们期望的共享库。
尝试一:使用add_library指令在生成动态库的基础上再为工程添加一个静态库(参数STATIC),按照习惯,静态库名字和动态库名字一致,只不过后缀是.a。但是外部构建后发现,没有生成静态库,这是因为构建生成的目标文件不能同名。
add_library(hello STATIC ${
LIBHELLO_SRC})
add_library(hello_static STATIC ${
LIBHELLO_SRC})
// 这样就可以构建一个libhello_static.a的静态库,但我们需要的是和动态库同名的静态库
尝试二:因为我们想要生成名字相同的静态库和动态库,这样的话add_library指令中target名称势必一样,因此我们借助与set_target_properties指令,设置静态库的OUTPUT_NAME属性为hello。构建之后我们发现生成了静态库libhello.a,但动态库libhello.so消失了,这是因为cmake在构建一个新的target时,会尝试清理掉其他使用这个名字的库。
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
尝试三:为了回避这个问题,我们同时修改静态库和动态库的CLEAN_DIRECT_OUTPUT属性为1。
set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(hello PROPERTIES VERSION 1.2 SOVERSION 1)
// VERSION指代动态库版本号,SOVERSION指代API版本
#include
int main(){
HelloFunc();
return 0;
}
cmake_minimum_required(VERSION 2.8)
project(NEWHELLO)
add_subdirectory(src)
add_executable(main main.c)
include_directories(/user/include/hello)
target_link_libraries(main libhello.so) // 链接到动态库
target_link_libraries(main libhello.a) // 链接到静态库
project指令的语法是:
project(projectname [CXX] [C] [Jave])
/*
作用:
1. 定义工程名称;
2. 指定工程支持的语言,支持的语言列表可以忽略,缺省情况表示支持所有语言;
3. 隐式地定义了两个cmake变量:_BINARY_DIR和_SOURCE_DIR;
补充:关于作用3,cmake系统也帮我们预定义了PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,它们的值和_BINARY_DIR和 _SOURCE_DIR相同,但我们推荐使用前者,这样即使修改了工程名称,也不影响这两个变量。
*/
set指令的语法是:
set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
/*
作用:
4. 定义变量
*/
message指令的语法是:
message([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
/*
作用:
向终端输出用户定义的信息,包含三种类型:
SEND_ERROR:产生错误,生成过程被跳过;
STATUS:输出前缀为-的信息;
FATAL_ERROR:立即终止所有cmake过程。
*/
add_executable指令的语法是:
add_exectable(hello ${
SRC_LIST})
/*
作用:
生成一个文件名为hello的可执行文件
*/
add_subdirectory指令的语法是:
add_subdirectory(source_dir [binary_dir] [EXECLUDE_FROM_ALL])
/*
作用:
向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
*/
add_library指令的语法是:
add_library(libname [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
/*
作用:
生成库文件,具体的库文件类型有三种:
SHARED:动态库
STATIC:静态库
MODULE:在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待
EXCLUDE_FROM_ALL参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
*/
set_target_properties指令的语法是:
set_target_properties(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...)
/*
作用:
1. 设置输出名称;
2. 对于动态库,还可以用来指定动态库版本和API版本
*/
get_target_property指令的语法是:
get_target_property(VAR target property)
/*
作用:
将库文件property属性赋值到变量VAR中,例如:
get_target_property(${OUTPUT_VALUE} hello OUTPUT_NAME)
*/
include_directories指令的语法是:
include_directories([AFTER | BEFORE] [SYSTEM] dir1 dir2 ...)
/*
作用:用来向工程添加多个特定的头文件搜索路径;
注意:默认的行为是追加到当前头文件搜索路径的后面,可以通过以下两种方式控制搜索路径添加的方式:
1. CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过set这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面;
2. 通过AFTER或者BEFORE参数,也可以控制是追加还是置前。
*/
link_directories指令的语法是:
link_directories(dir1 dir2 ...)
/*
作用:添加非标准的共享库搜索路径
*/
target_link_libraries指令的语法是:
target_link_libraries(target library1 <debug | optimized> library2 ...)
/*
作用:用来为target添加需要链接的共享库
*/
install指令的语法是:
1. 目标文件的安装:
install(TARGET targets ... [[ARCHIVE | LIBRARY | RUNTIME] [DESTINATION <dir>] [PERMISSION permissions ...] [CONFIGURATION [Debug | Release | ...]] [COMPONENT <component>] [OPTIONAL]] [...])
举例:
install(TARGET myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic)
该指令会将:
可执行二进制安装到${
CMAKE_INSTALL_PREFIX}/bin目录
动态库libmylib安装到${
CMAKE_INSTALL_PREFIX}/lib目录
静态库libmystaticlib安装到${
CMAKE_INSTALL_PREFIX}/libstatic目录
注意:CMAKE_INSTALL_PREFIX变量的配置需要用到指令为:
cmake -D CMAKE_INSTALL_PREFIX=/usr .
2. 普通文件的安装:
3. 非目标文件的可执行程序安装(比如脚本之类):
4. 目录的安装:
在涉及到第三方库的程序开发中,往往需要使用find_package指令来简化头文件和共享库的搜索过程。例如,假设main.c文件中include了第三方库curl,则在构建过程中需要指定头文件和共享库的地址,一般来说这有两种解决方式:
直接通过include_directories和target_link_libraries指令添加:
// src/CMakeLists.txt
include_directories(/user/include)
target_link_libraries(curltest curl)
这种方法不仅繁琐,而且不要你清楚地知道头文件的存储路径。
find_package(CURL)
if(CURL_FOUND)
include_directories(${
CURL_INCLUDE_DIR})
target_link_libraries(curltest ${
CURL_LIBRARY})
else(CURL_FOUND)
message(FATAL_ERROR "CURL library not found")
endif(CURL_FOUND)
/*
每一个模块都会定义以下几个变量
1. _FOUND
2. _INCLUDE_DIR 或 _INCLUDES
3. _LIBRARY 或 _LIBRARIES
*/