cmake笔记

主要内容

  • cmake简介;
  • 简单语法;
  • 几个项目示例。
    本文主要参考cmake practice,项目代码在我的仓库(因为原书内容有点过时,所以代码做出了修正),笔记中加入了自己的理解。
    另,原书未更新完,所以这里只有前六章内容,但是日常也够用了。

简单的HelloWorld(文件夹t1)

cmake以指定目录下的CMakeLists.txt为根据生成相应的中间文件(包括CMakeCache.txt,cmake_install.cmake,Makefile,CMakeFiles文件夹),之后运行make(或者运行make VERBOSE=1观察make命令构建的详细过程)。

基本语法规则

CMakeLists.txt必须在工程的每个目录下都存在一份,其最基本语法规则为:

  • 变量用${}方式取值,但在IF控制语句中直接使用变量名;
  • 指令(参数1 参数2),参数用空格或分号分开;
  • 指令大小写无关,但是变量名大小写相关,但推荐全部使用大写指令;
  • 运行make clean即可对构建结果进行清理(二进制可执行文件);make distclean在cmake中不可用(因为中间文件不可追踪,于是干脆禁用命令)。

注意
1. 工程名和生成的可执行文件名之间没有关系(因为一个项目中可以生成多个可执行文件)。
2. 推荐外部构建(out-of-source build)而不推荐内部构建(in-source-build),这样在需要重新构建时将整个构建文件夹删除即可。(最主要删除CMakeCache.txt)

更像工程的HelloWorld(文件夹t2)

一些注意:

  • 代码安装时用cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..选项;默认的CMAKE_INSTALL_PREFIX=/usr/local
  • 在INSTALL后,注意文件的权限会发生变更。

默认以后采用外部构建方式。要使项目更像工程:
1. 添加一个子目录src存放工程源代码;
- 主目录下CMakeLists.txt填写ADD_SUBDIRECTORY(src bin);
- 子目录下用ADD_EXECUTABLE();
2. 添加一个子目录doc存放工程文档;
3. 工程目录下添加文本文件COPYRIGHT,README;
4. 工程目录添加一个runhello.sh脚本用于调用hello二进制文件;
5. 安装这些文件:将hello二进制文件与runhello.sh安装至/usr/bin,将doc目录内容及COPYRIGHT/README安装到/usr/share/doc/cmake/t2。
- 方式1:make install;方式2:打包时指定目录安装。
- 对于cmake,需要使用指令INSTALL和一个很有用的变量CMAKE_INSTALL_PREFIX,下面具体说明

cmake install

cmake需要使用指令INSTALL和一个很有用的变量CMAKE_INSTALL_PREFIX
1. INSTALL指令用于定义安装规则,安装内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等,具体安装指令在下面介绍;
2. CMAKE_INSTALL_PREFIX的使用格式一般是cmake -DCMAKE_INSTALL_PREFIX=/usr .

INSTALL命令

INSTALL命令(不管是make install或者install命令),都是负责copy files and set attributes。

1.目标文件(动态静态库及可执行文件)的安装

INSTALL(TARGET targets...           #TARGETS后面跟ADD_EXECUTABLE/ADD_LIBRARY定义的目标文件,可能是可执行二进制、动态库、静态库,targets可以有多个
        [[ARCHIVE|LIBRARY|RUNTIME]  # 对应目标类型为ARCHIVE静态库、LIBRARY动态库、RUNTIME可执行目标二进制
        [DESTINATION <dir>]         # 如果路径以/开头,那么就是绝对路径,此时CMAKE_INSTALL_PREFIX无效;相对路径不要以/开头,则${CMAKE_INSTALL_PREFIX}/<DESTINATION>为安装后的路径
        [PERMISSIONS perssions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>] [OPTIONAL]]
        [...]
)

举个例子

INSTALL (TARGETS myrun  mylib mystaticlib
        RUNTIME DESTINATION bin         # 将二进制myrun安装到${CMAKE_INSTALL_PREFIX}/bin目录
        LIBRARY DESTINATION lib         # 动态库libmylib安装到${CMAKE_INSTALL_PREFIX}/lib目录
        ARCHIVE DESTINATION libstatic   # 静态库libmystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic目录
    )

上述例子中,不需要写出库文件生成的全称(lib-),而只要写出库文件的名字即可。

2.普通文件的安装

INSTALL (FILES files... DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL])

其中,文件名(FILES)是此指令所在路径下的相对路径,默认PERMISSIONS为644权限,cmake中描述为OWNER_WRITE,OWNER_READ,GROUP_READ,WORLD_READ。

3.非目标文件的可执行程序安装(如脚本之类):

INSTALL(PROGRAMS files... DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL])

跟上述FILES指令使用方法一致,唯一不同的是安装后的权限为:OWNER_EXECUTE,GROUP_EXECUTE,WORLD_EXECUTE,即755权限。

4.目录的安装

INSTALL(DIRECTORY dirs... DESTINATION <dir>
    [FILE_PERMISSIONS permissions...]       # permissions总是需要一连串指令指明
    [DIRECTORY_PERMISSIONS permissions...]
    [USE_SOURCE_PERMISSIONS]
    [CONFIGUTATIONS [Debug|Release|...]]
    [COMPONENT <component>]
    [[PATTERN <pattern> | REGREX <regex>]
    [EXECLUDE] [PERMISSIONS permissions...]] [...])

其中

  • DIRECTORY:后面接所在Source目录的相对路径,但注意abc表示将整个目录拷贝,abc/表示将目录下的所有文件拷贝过去;
  • PATTERN:用于正则表达式过滤;
  • PERMISSIONS:用于指定对前面的正则表达式过滤出来的文件的权限。

举个例子:

INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
        PATTERN "CVS" EXECLUDE
        PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ
        # 可以看到每次出现一个关键词,将其后面的内容存储为其中内容,直到碰到下一个关键词
)

上述执行结果会是:icons目录安装到/share/myproj,将scripts/文件夹中所有内容安装到/share/myproj;对含有”CVS”的目录不包含,对符合srcipts/*模式的文件。
如果要换个地方存放目标二进制,可以通过SET命令重新定义EXECUTABLE_OUTPUT_PATH/LIBRARY_OUTPUT_PATH变量来指定最终的目标二进制的位置(指最终生成的二进制可执行文件或最终共享库,不包含编译生成的中间文件。记住:在哪里ADD_EXECUTABLE()/ADD_LIBRARY(),就在哪里加入上述修改目标文件夹的定义。

静态库与动态库构建(t3文件夹)

任务

  1. 建立一个静态库和动态库,提供HelloFunc函数供其他程序编程使用,HelloFunc向终端输出Hello World字符串。
  2. 安装头文件及共享库。

相关指令

  • ADD_LIBRARY添加构建动态静态库
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)

其中,

  • libname不需要写全libhello.so,只需要写hello就可以,cmake会自动生成libhello.X;
  • 库的三种类型
    • SHARED动态库(.so后缀)
    • STATIC静态库(.a后缀)
    • MODULE在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待
  • EXCLUDE_FROM_ALL表示该库不会被默认构建,除非有其他组建依赖或者手工构建
    问题:要构建的TARGET(可执行二进制文件/动态静态链接库等)是不能重名的,但是输出库的名字可以被修改,以使输出的动态静态库可以同名。

  • SET_TARGET_PROPERTIES设置输出的名称,对于动态库还可以用来指定动态库版本和API版本

SET_TARGET_PROPERTIES(target1 target2 ...
        PROPERTIES prop1 value1
        prop2 value2...)

在/t3/build中进行cmake,就可以同时输出libhello.solibhello.a两个库。
举几个例子

SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")#以下命令用来表示不清除文件中的同名目标,但在3.5.1中已经可以生成同名的目标文件而不会被清除
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUPUT 1)

问题:动态库一般是包含一个版本号的,如果我们想生成以下链接,我们应该怎么实现呢?

libhello.so.1.2
libhello.so->libhello.so.1
libhell.so.1->libhello.so.1.2
  • 采用以下指令添加动态库版本:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1) #SOVERSION表示API版本,VERSION表示动态库版本

最终我们需要将动态库和头文件案安装到系统目录,才能被人开发使用,按照如下命令将hello共享库安装到/lib目录,将hello.h安装到/include/hello目录下。为下一节准备,将文件安装在/usr/lib/usr/include/hello下。

使用外部共享库和头文件(t4目录)

按照构建可执行二进制文件的方式构建文件夹,此时#include<hello.h>需要用<>符号(因为已经在系统文件夹中,但是不在系统标准的头文件路径/usr/include中),因此直接在cmakemake之后会出现错误,改用make VERBOSE=1来构建。
注意:其实只要写成#include<hello/hello.h>会在/usr/include下查找相应文件。

此时我们要引入头文件搜索所路径,用指令INCLUDE_DIRECTORIES或者是CMAKE_INCLUDE_PATH选项

  • INCLUDE_DIRECTORIES
    • 用于向工程添加多个特定的头文件搜索路径,多个路径之间用空格分割,如果路径中包含空格,可以用双括号括起来,默认的行为是追加到当前的头文件搜索路径后面;
    • 可以通过两种方式来进行控制搜索路径添加的方式:
      1. CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面;
      2. 通过AFTER或者BEFORE参数,也可以控制是追加或者是置前。
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

问题:上述步骤完成后,会出现一个新的错误undefined reference to 'HelloFunc',因为还没有link到共享库libhello上(gcc -lhello)。

  • TARGET_LINK_LIBRARIES用于为target(库/可执行二进制文件)添加需要链接的共享库。
TARGET_LINK_LIBRARIES(target library1
                    <debug | optimized> library2
                    ...)

其中library可以写成hello(会优先链接到动态库.so)或者库全称libhello.so/libhello.a
以下还有另外一个命令:

  • LINK_DIRECTORIES添加非标准的共享库搜索路径。
    • 如在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。
LINK_DIRECTORIES(directory1 directory2 ...)
  • 特殊的环境变量CMAKE_INCLUDE_PATHCMAKE_LIBRARY_PATH
    • 两个环境变量可以设置到.bashrc中并且export生效,或者在CMakeLists.txt中用SET设置相应变量值;
    • CMAKE_INCLUDE_PATH配合FIND_PATH(用来在指定路径中搜索文件名)使用;
    • 当头文件没有存在常规路径/usr/include,usr/local/include中时,可以通过这些变量弥补。
    • CMAKE_LIBRARY_PATH配合FIND_LIBRARY(在指定路径中搜索库名)使用。
      则更自动化的INCLUDE_DIRECTORIES()如下:
SET(CMAKE_INCLUDE_PATH /usr/include/hello)
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)

模块的使用和自定义模块

对于系统与定义的Find.cmake模块,使用方法如下:
每一个模块都会定义以下几个变量(系统预定义):

  • _FOUND:判断模块是否找到
  • _INCLUDE_DIR or _INCLUDES:存储include路径,当FOUND为真,INCLUDE_DIRECTORIES();
  • _LIBRARY or _LIBRARIES:存储library路径,当FOUND为真时,TARGET_LINK_LIBRARIES()。

以下看一个复杂的例子,用_FOUND控制工程特性:

SET(mySources viewer.c)
SET(optionalSources)
SET(optionalLibs)


FIND_PACKAGE(JPEG)# 当输入选项-DENABLE_JPEG_SUPPORT以下代码生效
IF(JPEG_FOUND)
    SET(optionalSources ${optionalSources} jpegview.c)
    INCLUDE_DIRECTORIES(${JPEG_INCLUDE_DIR})
    SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES})
    ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)


FIND_PACKGAE(PNG)# 当输入选项-DENABLE_PNG_SUPPORT以下代码生效
IF(PNG_FOUND)
    SET(optionalSources ${optionalSources} pngview.c)
    INCLUDE_DIRECTORIES(${PNG_INCLUDE_DIR})
    SET(optionalLibs ${optionalLibs} ${PNG_LIBRATIES})
    ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)

ADD_EXECUTABLE(viewer ${mySources} ${optionalSources})
TARGET_LINK_LIBRARIES(viewer ${optionalLibs})

编写自己的FindHello模块(t6目录)

在CMakeLists.txt中调用的是.cmake模块中的变量,而cmake模块中的变量可以系统定义,也可以用户自己定义。

  • FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [REQUIRED|COMPONENTS] [components...]])
    • 如果指定QUIET参数,就对应编写的FindHELLO.cmake中的HELLO_FIND_QUIETLY;
    • 指定REQUIRED参数,则找不到该链接库时不能编译,对应FindHELLO.cmake中的HELLO_FIND_REQUIRED变量。

基本指令汇总

项目中用到的指令如下:

  • PROJECT(projectname [CXX] [C] [JAVA])
    • 语言列表可以忽略,默认支持所有语言;
    • 两个隐式的cmake变量,<projectname>_BINARY_DIR<projectname>_SOURCE_DIR,或者就直接用PROJECT_BINARY_dirPROJECT_SOURCE_DIR;
  • SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
    • 如给SRC_LIST设置变量时main.cpp/"main.cpp"均可;
  • MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
    • SEND_ERROR,产生错误,生成过程被跳过;
    • STATUS,输出前缀为--的信息;
    • FATAL_ERROR,立即终止所有cmake过程。
  • ADD_EXECUTABLE(hello ${SRC_LIST})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值