cmake 使用小结 (一)

安装

cmake的安装特别简单,ubuntu下直接

$ sudo apt-get install cmake

下面以几个版本的hello world为例。

版本一

mkdir -p /home/cmake_sample/sample01
cd /home/cmake_sample/sample01

分别建立 main.cCMakeLists.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_DIRHELLO_SOURCE_DIR(所以CMakeLists.txt中两个MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径/home/cmake_sample/sample01,后面我们会讲到外部编译,两者所指代的内容会有所不同。
同时cmake系统也帮助我们预定义了PROJECT_BINARY_DIRPROJECT_SOURCE_DIR变量,他们的值分别跟HELLO_BINARY_DIRHELLO_SOURCE_DIR一致。
为了统一起见,建议以后直接使用PROJECT_BINARY_DIRPROJECT_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_PATHLIBRARY_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_EXECUTABLEADD_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目录的相对路径,但务必注意:abcabc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的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.chello.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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值