Cmake实战指南,cmake使用方法详解

2019-11-26 16:35 来源:未知

0 综述


我觉的Cmake比较核心的一些东西就是

  • 怎么去组织一个项目的编译框架
  • 最终输出目标有哪些(可执行程序,动态库,静态库等等)
  • 怎么为指定的输出目标指定编译参数(需要哪些源文件,需要哪些编译参数)
  • 怎么为指定的输出目标指定链接参数(需要哪些外部库,需要哪些链接参数)
  • 如果存在多个独立输出目标是否有执行先后顺序(比如项目有自动配置工具,用来自动生产一些源文件,那么自动配置工具输出目标就要先于其他目标比如输出可执行程序目标)

1.安装

 $sudo apt-get install cmake


cmake 简介

    CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。

1 如何组织项目编译框架


比如我当前项目源码根目录下面有这些源码目录

├── arch
├── cfg
├── doc
├── drivers
├── fs
├── include
├── kernel
├── library
├── sample
└── utils

我会在根目录放置一个 CMakeLists.txt,之后在每个需要管理的目录下面也放置一个CMakeLists.txt,如下所示(这边没有把include包含进来是因为头文件cmake会自动去构建不需要手动添加,拿C语言来举例,所有包含在.C中的.H文件,cmake全部会帮你自动计算出来,不需要手动去添加)

├── CMakeLists.txt
├── arch
│   └── CMakeLists.txt
├── cfg
│   └── CMakeLists.txt
├── doc
│   └── CMakeLists.txt
├── drivers
│   └── CMakeLists.txt
├── fs
│   └── CMakeLists.txt
├── include
├── kernel
│   └── CMakeLists.txt
├── library
│   └── CMakeLists.txt
├── sample
│   └── CMakeLists.txt
└── utils
└── CMakeLists.txt
├── cmake
├── build

需要说明下这边我多加了两个目录cmake和build,cmake目录一般用来放置一些通用的cmake源码模块(比如通用编译环境设置,编译链接的一些选项配置等等),build用来存放项目构建过程中所有的输出文件。
根目录下面CMakeLists.txt大致就可以按下面这样撰写,就可以把整个骨架搭起来。

#cmake最低版本要求
cmake_minimum_required(VERSION 3.10.0)

#项目名字为test
project(test NONE)

#包含通用的编译环境模块到顶层目录
include(${CMAKE_SOURCE_DIR}/cmake/base.cmake)

#下一级的编译目录
add_subdirectory(arch)
add_subdirectory(cfg)
add_subdirectory(doc)
add_subdirectory(drivers)
add_subdirectory(fs)
add_subdirectory(kernel)
add_subdirectory(library)
add_subdirectory(sample)
add_subdirectory(utils)

进入build目录运行整个编译框架

cd build && cmake .. && make

用到的相关命令

命令标签格式 说明
cmake_minimum_required(VERSION major.minor[.patch[.tweak]] [FATAL_ERROR]) 设置最低版本的cmake要求,一般放在第一行
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...]) 为项目设置名字,版本,启用的编程语言,我上面没有启用任何编程语言所以用NONE
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) 增加一个子目录到编译系统
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <VAR>][NO_POLICY_SCOPE]) 从给出的file文件加载和运行cmake代码

2.示例:简单的文件目录

    sample |——Demo  (盛放可执行程序binary directory)

                            |——CMakeLists.txt (内容为:include_directories                                                                                                      (${HELLO_SOURCE_DIR}/Hello                                                                                                    #确认compiler能在Hello 库中找到它include的库

                                                             link_directories (${HELLO_BINARY_DIR}/Hello)                                                                          #确认一旦built时,linker能找到Hello库

                                                            add_executable (helloDemo demo.cxx demo_b.cxx)                                                                  #可执行文件叫做helloDemo,它的源码文件是"demo.cxx"                                                              #和"demo_b.cxx"

                                                           target_link_libraries (helloDemo Hello)                                                                                          #link可执行文件helloDemo 到Hello lib       )

                | ——Hello  (盛放源代码source directory)

                            |——CMakeLists.txt (内容为:add_library (Hello hello.cxx)                                                                                              #创建Hello 库,源文件为hello.cxx)                      

                |——CMakeLists.txt  (内容为:project (HELLO) #工程名

                                                                  add_subdirectory (Hello) # 子目录,路径为                                                                                   ${HELLO_SOURCE_DIR})

                                                                  add_subdirectory (Demo) #子目录 ,路径为                                                                                 ${HELLO_BINARY_DIR}   )


CMake 使用方法

    CMake的所有的语句都写在一个叫:CMakeLists.txt的文件中。当CMakeLists.txt文件确定后,可以用ccmake命令对相关 的变量值进行配置。这个命令必须指向CMakeLists.txt所在的目录。配置完成之后,应用cmake命令生成相应的makefile(在Unix like系统下)或者 project文件(指定用window下的相应编程工具编译时)。

    其基本操作流程为:

  1. $> ccmake directory

  2. $> cmake directory

  3. $> make

  其中directory为CMakeList.txt所在目录;

  • 第一条语句用于配置编译选项,如VTK_DIR目录 ,一般这一步不需要配置,直接执行第二条语句即可,但当出现错误时,这里就需要认为配置了,这一步才真正派上用场;
  • 第二条命令用于根据CMakeLists.txt生成Makefile文件;
  • 第三条命令用于执行Makefile文件,编译程序,生成可执行文件;

CMake的执行就是这么简单,其难点在于如何编写CMakeLists.txt文件,下面结合例子简单介绍CMakeLists.txt的编写,看下面这个CMakeLists.txt

  1. #project name

  2. PROJECT(test_math)

  3. #head file path

  4. INCLUDE_DIRECTORIES(

  5. include

  6. )

  7. #source directory

  8. AUX_SOURCE_DIRECTORY(src DIR_SRCS)

  9. #set environment variable

  10. SET(TEST_MATH

  11. ${DIR_SRCS}

  12. )

  13. #set extern libraries

  14. SET(LIBRARIES

  15. libm.so

  16. )

  17. #add executable file

  18. ADD_EXECUTABLE(../bin/bin ${TEST_MATH})

  19. #add link library

  20. TARGET_LINK_LIBRARIES(../bin/bin ${LIBRARIES})

  21.  

            或者用下面这个CMakeLists.txt

 

[cpp] view plain copy

 

  1. #project name  
  2. PROJECT(test_math)  
  3.   
  4. add_definitions("-Wall -lpthread -g")  
  5.   
  6. #head file path  
  7. INCLUDE_DIRECTORIES(  
  8. include  
  9. )  
  10.   
  11. #source directory  
  12. AUX_SOURCE_DIRECTORY(src DIR_SRCS)  
  13.   
  14. #set environment variable  
  15. SET(TEST_MATH  
  16. ${DIR_SRCS}  
  17. )  
  18.   
  19. #set extern libraries  
  20. SET(LIBRARIES  
  21. libm.so  
  22. )  
  23.   
  24. # set output binary path  
  25. SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)  
  26.   
  27. SET(FS_BUILD_BINARY_PREFIX "Yfs")  
  28.   
  29. #add executable file  
  30. ADD_EXECUTABLE(${FS_BUILD_BINARY_PREFIX}sqrt ${TEST_MATH})  
  31.   
  32. #add link library  
  33. TARGET_LINK_LIBRARIES(${FS_BUILD_BINARY_PREFIX}sqrt ${LIBRARIES})  

 

 这是一个测试数学函数的程序的CMakeLists.txt,"#"后面为注释的内容,CMake的命令全部为大写

第2行指定生成的工程名为test_math

第4行指定头文件目录为include

第8行指定源文件目录为src,并将其赋值给环境变量DIR_SRCS

第10行设定环境变量TEST_MATH的值为环境变量DIR_SRCS的值,此处用于显示如何用环境变量对环境变量进行赋值

第14行将数学函数库赋值给环境变量LIBRARIES,当然,可以不用这个环境变量,而在后面直接使用该库名

第18行用于指定生成文件,将环境变量TEST_MATH目录下的所有文件编译生成../bin目录下的可执行文件bin

第20行指定../bin/bin执行时的链接库为环境变量LIBRARIES的值-libm.so

下面给出源文件
/src/main.c:

  1. #include<stdio.h>

  2. #include"../include/a.h"

  3. int main()

  4. {

  5.     double b=25.0;

  6.     double a=0.0;

  7.     a=get_sqrt(b);

  8.  

  9.     printf("a is %lf, b is %lfn",a,b);

  10.     return 0;

  11. }

  12.  

/src/a.c

  1. #include"../include/a.h"

  2. double get_sqrt(double var1)

  3. {

  4.     return sqrt(var1);

  5. }

  6.  

 

/include/a.h

 #ifndef  A_FILE_HEADER_INC

  1.  
  2. #define  A_FILE_HEADER_INC

  3. #include<math.h>

  4.  

  5. double get_sqrt(double var1);

  6.  

  7. #endif

将CMakeLists.txt放在当前目录下,执行CMakeLists.txt

  1. $> cmake .

  2. $> make

即可生成可执行文件,在目录/bin下的bin文件,好了运行看其效果是否和所想一样。

====================

补充:

 

2 最终输出目标有哪些


  • 最终输出目标就是编译器编译链接之后产生的最终结果,比如linux下面的elf文件,.a静态库,.so的动态库
  • 主要通过add_executable来定义输出可执行程序目标,add_library来定义输出动态库,静态库,模块等目标。
  • 定义的输出目标是全局的,比如你在根目录通过add_executable定义了一个输出目标,可以在子目录中用target_sources命令往这个输出目标里面加源文件,cmake在输出这个目标之前会在当前project中搜集所有添加到该目标的源文件再去生成一个编译链接的规则。
#定义一个mytool 可执行程序输出目标,目前只有一个源文件  mytool.cpp
add_executable(mytool mytool.cpp)

#往mytool可执行程序输出目标添加源文件,现在mytool目标里面包含两个源文件
target_sources(mytool  PRIVATE mytool2.cpp)

#定义一个动态库archive 输出目标,文件有三个源文件
add_library(archive SHARED archive.cpp zip.cpp lzma.cpp)

#定义一个静态库archive 输出目标,也可以不指定STATIC 因为add_library默认输出目标是#静态库
add_library(archive STATIC archive.cpp zip.cpp lzma.cpp)

#从给出的源文件直接生成object文件,比如linux下C语言的.o文件
add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)

用到的相关命令

命令标签格式 说明
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...]) 定义一个名字为name可执行程序输出目标,其中 source1 [source2 ...]是源文件,比如C语言是.C的源文件,.H的源文件不需要指定,cmake会自动去搜索
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])

add_library(<name> OBJECT <src>...)
定义一个名字为name的库输出目标
target_sources(<target><INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) 往一个目标里面添加源文件,这个目标名字target是在add_executable() 或者add_library() 中定义的name

3.CMake 执行过程

      CMake在主目录执行时,会处理该目录下CMakeLists.txt文件,然后进入到子目录,处理子目录下的CMakeLists.txt.

      从字面上看,我们差不多可以理解这三个文件的涵义。第一个CMakeLists.txt文件指定包含Hello和Demo两个子目录。第二个Hello中的CMakeLists.txt文件则指定生成Hello库文件,第三个Demo中的CMakeLists.txt文件则是生成一个可执行文件helloDemo,另外两个附加语句则用来指明头文件路径以及所要链接的库。虽然要写三个CMakeLists文件,但每个文件都非常简单,总共算起来,并不比一个Makefile文件多多少。更重要的是,这其中隐含着linux哲学:分而治之。每个模块自行编写配置文件,只负责自己份内的事务,所以可扩展性好。在进行大型项目开发,就可以体现出优势了。

      CMakeLists.txt相当于定义了一套生成Makefile文件的规则,下面就可以生成Makefile文件了,命令如下:

$cmake .

表示当前目录,如果CMakeLists.txt不在当前目录,请在cmake后面指定。命令执行后,在主目录下和Demo、Hello子目录下均会生成一个Makefile文件,有了这个文件,我们就可以敲入make编译目标程序了。


一、      基本使用

 

安装:下载二进制包后可直接解压使用

从源码安装则执行命令:./bootstrap; make; make install——尝试执行bootstrap失败

使用:cmake dir_path,生成工程文件或makefile文件

3 指定编译参数和链接参数


  • 为add_executable() 或者add_library() 中定义的输出目标指定编译选项可以通过下面3个命令函数
命令标签格式 说明
target_include_directories(<target> [SYSTEM] [BEFORE]<INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) Include的头文件的查找目录,也就是Gcc的[-Idir...]选项
target_compile_definitions(<target> <INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) 通过命令行定义的宏变量,也就是gcc的[-Dmacro[=defn]...]选项
target_compile_options(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...] gcc其他的一些编译选项指定,比如-fPIC
# gcc头文件查找目录,相当于-I选项,e.g -I/foo/bar
#CMAKE_SOURCE_DIR是cmake内置变量表示当前项目根目录
target_include_directories(test_elf
    PRIVATE
    ${CMAKE_SOURCE_DIR}
    ${CMAKE_SOURCE_DIR}/common
    ${CMAKE_SOURCE_DIR}/syscalls
)

# 编译的宏定义,e.g 相当于-D选项 e.g -Dmacro=defn
set(MONITOR_OMIT_BSS_INIT      "0")
set(MONITOR_OMIT_DATA_INIT     "0")
set(MONITOR_OMIT_T_CHECKS      "0")
target_compile_definitions(test_elf
    PRIVATE
    MONITOR_OMIT_BSS_INIT=${MONITOR_OMIT_BSS_INIT}              
    MONITOR_OMIT_DATA_INIT=${MONITOR_OMIT_DATA_INIT}            
    MONITOR_TRAP_NT_IRQS=${MONITOR_TRAP_NT_IRQS}                          
)

# 其他编译选项定义,e.g -fPIC
target_compile_options(test_elf
    PRIVATE
    -std=c99 
    -Wall 
    -Wextra 
    -Werror
)
  • 为add_executable() 或者add_library() 中定义的输出目标指定链接选项可以通过target_link_libraries命令全部搞定。
命令标签格式 说明
target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <item>... [<PRIVATE|PUBLIC|INTERFACE> <item>...]...) item可以是链接到该目标的库的名字(去掉前缀lib和扩展名后缀之后的库名字),也就是gcc链接器的-llibrary选项

Item也可以是链接选项以-开始的item会被认为是链接选项(除了 -l和-framework)

Item也可以是要链接到该目标的库文件的完整路径(针对非标准路径的库时可以这样指定,当然也可以用link_directories命令来为链接器增加搜索库的搜索路径)
# 链接选项设置
target_link_libraries(test_elf
    PRIVATE
    -msoft-float 
    -static 
    -nostdlib
)

#设置链接的标准路径的库
target_link_libraries(test_elf PRIVATE gcc)

#设置链接的本project自己输出的目标库libkernel.a
target_link_libraries(test_elf PRIVATE kernel)

#设置链接非标准路径中别人编译好的库
target_link_libraries(test_elf 
PRIVATE 
$ENV{HOME}/lib/libtest.a
)

备注:也可以用其他方式来设置编译链接的一些参数,比如直接设置cmake内置变量或者其他命令,我这里选择这些基于目标的命令来操作主要是因为,基于目标来管理更容易维护和管理,不会污染到其他的作用域。

  • 下面对命令用到的PRIVATE|PUBLIC|INTERFACE进行介绍一下:
    可以简单理解为向集成自己的其他目标,是否也开放本目标正在设置的这些参数。或者说一个输出目标(自己,比如库),可能会作为另一个目标(依赖者,比如可执行程序)的输入,所以就定义了这几个参数来指明对当前输出目标指定的这些编译参数是否传递给其依赖者,如果你不确定的话,最好都指定为PRIVATE。

具体的含义如下:

PRIVATE 只给自己用,不给依赖者用
PUBLIC 自己和依赖者都可以用
INTERFACE 自己不用,给依赖着用
  • 下面是cmake官网的一个例子比较简单,自己体会一下
add_library(archive archive.cpp)
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)

add_library(serialization serialization.cpp)
target_compile_definitions(serialization INTERFACE USING_SERIALIZATION_LIB)

add_library(archiveExtras extras.cpp)
target_link_libraries(archiveExtras PUBLIC archive)
target_link_libraries(archiveExtras PRIVATE serialization)
# archiveExtras is compiled with -DUSING_ARCHIVE_LIB
# and -DUSING_SERIALIZATION_LIB

add_executable(consumer consumer.cpp)
# consumer is compiled with -DUSING_ARCHIVE_LIB
target_link_libraries(consumer archiveExtras)

4.项目文件组织

      我们从一个sample入手,了解了CMake的基本用法和语法。但这个例子与实际开发还有一段距离,主要存在以下几点问题:                                                                                                        1.生成的二进制程序和源程序混在一起                                                                                           2.使用gcc进程程序编译,而不是使用交叉编译工具                                                                       3.为指定编译选项,通常会生成debug版本供调试用,release版本用于发布

      一个项目,通常包含若干子模块。比如上一篇的sample,我们可以认为它包含两个子模块,Hello为程序库,Demo为主程序。很少有项目会把目标二进制文件和源程序放在一起的,通常会建立一个bin目录,存放生成的二进制文件,发布程序则放在release。根据我在项目开发中的习惯,将目录结构修改如下:

CMakeSample

|--- release  (存放程序发布相关文件,包括程序文件、脚本、参数等。)

|--- doc(项目开发中的相关文档,如设计说明以及通过doxgen等工具从代码中生成的文档。)

|--- lib(存放项目中使用的第三方库)

|--- source(自己编写的库不放在lib,应该作为项目的一个模块放在source目录下。)

|--- include (包含整个项目中使用的公共头文件,若子模块中的头文件仅被它使用,不放)

|--- bin (bin目录存放编译后的调试版本代码。)

|--- Hello

|--- Demo

其它的子目录则为各模块的代码及头文件。

按照以上目录结构,将Hello下的hello.h移到include目录,因为这个头文件被Demo模块包含。这个sample中未使用第三方库,所以暂时为空。


二、      概念

out-of-source build,与in-source build相对,即将编译输出文件与源文件放到不同目录中;

4 对输出目标的其他处理


  • 比较常见的比如在linux下生成的elf输出目标,需要进一步进行处理从elf文件中抽出bin文件,可以使用add_custom_command命令进行处理,add_custom_command命令支持对目标编译链接之前进行处理,或者编译链接完之后进行处理
    支持该处理方式的add_custom_command命令标签格式如下:
命令标签格式 说明
add_custom_command(TARGET <target>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL])
Target是库或者可执行文件输出目标

PRE_BUILD只有Visual Studio 8或者之后的版本才支持,其他情况等同于PRE_LINK

PRE_LINK在编译之后,链接之前运行,

POST_BUILD,该目标的所有构建规则都执行完之后才运行

COMMAND ,指定要执行的命令行命令和参数
#在test_elf链接之前,先拷贝一些test_elf需要用到的文件到编译目录,在进行编译
add_custom_command(TARGET test_elf PRE_LINK
COMMAND
cp ${CMAKE_BINARY_DIR}/cfg/start.o ${CMAKE_BINARY_DIR}/. && 
cp ${CMAKE_SOURCE_DIR}/target/imx6_gcc/imx6.ld ${CMAKE_BINARY_DIR}/.
)

#把编译输出的test_elf进行进一步处理,生成最终的bin文件
add_custom_command(TARGET test_elf POST_BUILD
    COMMAND ${CUSTOM_CMD_OBJCOPY} -O binary -S test_elf test_elf.bin
)

5.CMake的内置变量

从上文中我们知道,通过set语句可以自定义变量,然而,CMake还包含大量的内置变量,这些变量和自定义变量的用法没有区别,下面就列出一些常用的变量:

CMAKE_C_COMPILER

指定C编译器,通常,CMake运行时能够自动检测C语言编译器。进行嵌入式系统开发时,通常需要设置此变量,指定交叉编译器。

CMAKE_CXX_COMPILER

指定C++编译器

CMAKE_C_FLAGS

指定编译C文件时编译选项,比如-g指定产生调试信息。也可以通过add_definitions命令添加编译选项。

EXECUTABLE_OUTPUT_PATH

指定可执行文件存放的路径

LIBRARY_OUTPUT_PATH

指定库文件放置的路径

CMAKE_BUILD_TYPE     build 类型(Debug, Release),-DCMAKE_BUILD_TYPE=Debug

BUILD_SHARED_LIBS     Switch between shared and static libraries

内置变量的使用:

>> 在CMakeLists.txt中指定,使用set

>> cmake命令中使用,如cmake -DBUILD_SHARED_LIBS=OFF


三、      基本结构

1,依赖CMakeLists.txt文件,项目主目标一个,主目录中可指定包含的子目录;

2,在项目CMakeLists.txt中使用project指定项目名称,add_subdirectory添加子目录

3,子目录CMakeLists.txt将从父目录CMakeLists.txt继承设置(TBD,待检验)

5 自动生成源码规则制定


  • 如果项目中用到了一些第三方的工具来自动生成一些源码,要怎么制定规则让cmake在编译源码之前先去调用第三方工具先去生成需要编译的源码,主要用到add_custom_target,add_custom_command,dependencies这几个命令
#先为这个第三方工具设置一个自定义目标,这个目标并没有实际输出文件,
#只是一个目标的名字比如这边的autoconfig 
add_custom_target(autoconfig ALL)
#利用这个目标去运行这个第三方工具,这边是cfg,后面是这个工具需要的参数
add_custom_command(TARGET autoconfig PRE_BUILD
COMMAND 
cfg --pass ${pass} ${CFG_ELF_INCLUDE} ${rom_image} ${symbol_table} ${T_file} ${CFG_TABLES}
)   

#autoconfig和test_elf这两目标,没有直接的关系(像前面介绍的可执行程序,可能依赖其#他库输出目标,这里没有这样的关系),需要指定目标输出顺序,先对autoconfig进行输
#出(自动生成源码),在进行test_elf输出(编译和链接)
add_dependencies(test_elf autoconfig)

#把自动生成的源文件cfg_out.c加到test_elf目标去编译
target_sources(test_elf  PRIVATE cfg_out.c)
#指明cfg_out.c是自动生成的文件,否则会认为找不到文件而出错
set_source_files_properties(cfg_out.c PROPERTIES GENERATED 1)
命令标签格式 说明
add_custom_target(Name [ALL] [command1 [args1...]] [COMMAND command2[args2...] ...] [DEPENDS depend depend depend ... ] [BYPRODUCTS [files...]] [WORKING_DIRECTORY dir] [COMMENT comment] [VERBATIM] [USES_TERMINAL] [COMMAND_EXPAND_LISTS] [SOURCES src1 [src2...]])[items2...] ...]) 增加一个名字为Name的目标,该目标没有输出,所以它总是被构建。
ALL选项表明该目标被添加到默认的构建目标,所以它每次都会运行
add_dependencies(<target> [<target-dependency>]...) 在目标target在构建之前,后面的其他target-dependency依赖目标必须先被构建完成Target或者target-dependency目标是由add_executable(),add_library(), add_custom_target()命令输出的目标
set_source_files_properties([file1 [file2 [...]]] PROPERTIES prop1 value1 [prop2 value2 [...]]) 通过键/值对( key/value)来设置源文件的一些属性

6.CMake的常用命令

除了内置变量,我们还可以通过命令来修改编译选项,现将一些常用的命令列出来:

include_directories

指定头文件的搜索路径,相当于指定gcc编译器的-I参数

link_directories

动态链接库或静态链接库的搜索路径,相当于指定gcc的-L参数

add_subdirectory

包含子目录,当工程包含多个子目录时,此命令有用

add_definitions

添加编译参数,比如add_definitions(-DDEBUG)将在gcc命令行添加DEBUG宏定义

add_executable

编译可执行程序

target_link_libraries

指定链接库,相同于指定-l参数

四、      语法

1.       #注释

2.       变量:使用set命令显式定义及赋值,在非if语句中,使用${}引用,if中直接使用变量名引用;后续的set命令会清理变量原来的值;

3.       command (args ...)  #命令不分大小写,参数使用空格分隔,使用双引号引起参数中空格

4.       set(var a;b;c) <=> set(var a b c)  #定义变量var并赋值为a;b;c这样一个string list

5.       Add_executable(${var}) <=> Add_executable(a b c)   #变量使用${xxx}引用

6.       条件语句:

if(var) #var 非empty 0 N No OFF FALSE... #非运算使用NOT

       …

else()/elseif() … endif(var)

7.       循环语句

Set(VAR a b c)

Foreach(f ${VAR})       …Endforeach(f)

8.       循环语句

WHILE() … ENDWHILE()

6 如何编译汇编源文件


  • 我开发的项目是嵌入式系统有部分源码是用汇编编写的,所以有这个需求。Cmake wiki有介绍了如何编译汇编的介绍,那个可能已经过时了,比较麻烦反正我没试过:)
    我使用下面的方式进行测试,可以正常编译(cmake 版本3.10.0)
#支持的编程语言配置
enable_language(ASM)

#设置汇编源文件的编译器,我这边配置成和C语言的编译器一样
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})

#把汇编源码和C源码一起放置在待编译的源文件列表里面就行
set(SRC_KERNEL_LIB
    ${CMAKE_SOURCE_DIR}/target/imx6_gcc/target_support.S
    ${CMAKE_SOURCE_DIR}/arch/arm_gcc/mpcore/chip_support.S
    ${CMAKE_SOURCE_DIR}/arch/arm_gcc/common/core_support.S
    ${CMAKE_SOURCE_DIR}/drivers/mmu_table.c
)
命令标签格式 说明
enable_language(<lang> [OPTIONAL] ) 启用某种编程语言

7.CMake语法介绍

CMake语法非常简单,包含注释、命令和空格。以#开头的行为注释行,命令则由命令名、括号及以空格进行分隔的参数组成。命令可以是诸如add_library这样的内置命令,也可以是子定义的宏或者函数。CMake的输入是主目录下的CMakeLists.txt文件,该文件可以使用include或者add_directory命令添加其它的输入文件。

命令的形式如下:

command (args ...)

其中command为命令名,args为空格分隔的参数列表,如果参数中包含空格,使用双引号引起来。命令不区分大小写。

lists and strings. CMake的基本数据类型为字符串,字符串又可以组成list类型,有两种方式:一种通过分号分隔,一种通过空格分隔。比如以下例子给VAR赋了同样的值:

set(VAR a;b;c)    set(VAR a b c)

字符串列表主要用于foreach进行迭代,有些命令也用于对list进行处理。

CMake支持字符串和list类型的简单变量,变量以${VAR}形式引用。多个参数可以用set命令组成一个list,命令将展开list,例如:

set(Foo a b c)

command(${Foo})

等价于

command(a b c)

如果你希望将list当作一个参数传递给命令,就应该用双引号把list引起来,如command("${Foo}")等价于command("a b c")

流程控制

写CMakeLists.txt文件就象写一个简单的程序,CMake提供了三种流程控制结构:

条件语句if

# some_command will be called if the variable's value is not:

# empty, 0, N, NO, OFF, FALSE, NOTFOUND, or -NOTFOUND.

if(var)

some_command(...)

endif(var)

循环结构

set(VAR a b c)

# loop over a, b,c with the variable f

foreach(f ${VAR})

some_command(${f})

endforeach(f)

宏和函数,函数在2.6及以上版本才支持,函数和宏的区别在于函数中可定义局部变量,而宏定义的变量都是全局变量。

# define a macro hello

macro(hello MESSAGE)

message(${MESSAGE})

endmacro(hello)

# call the macro with the string "hello world"

hello("hello world")

# define a function hello

function(hello MESSAGE)

message(${MESSAGE})

endfunction(hello)

project(HELLO)  #指定项目名称,生成的VC项目的名称;

>>使用${HELLO_SOURCE_DIR}表示项目根目录

include_directories:指定头文件的搜索路径,相当于指定gcc的-I参数

>> include_directories (${HELLO_SOURCE_DIR}/Hello)  #增加Hello为include目录

link_directories:动态链接库或静态链接库的搜索路径,相当于gcc的-L参数

>> link_directories (${HELLO_BINARY_DIR}/Hello)     #增加Hello为link目录

add_subdirectory:包含子目录

>> add_subdirectory (Hello)

add_executable:编译可执行程序,指定编译,好像也可以添加.o文件

>> add_executable (helloDemo demo.cxx demo_b.cxx)   #将cxx编译成可执行文件——

add_definitions:添加编译参数

>> add_definitions(-DDEBUG)将在gcc命令行添加DEBUG宏定义;

>> add_definitions( “-Wall -ansi –pedantic –g”)

target_link_libraries:添加链接库,相同于指定-l参数

>> target_link_libraries(demo Hello) #将可执行文件与Hello连接成最终文件demo

add_library:

>> add_library(Hello hello.cxx)  #将hello.cxx编译成静态库如libHello.a

add_custom_target:

message( status|fatal_error, “message”):

set_target_properties( ... ): lots of properties... OUTPUT_NAME, VERSION, ....

link_libraries( lib1 lib2 ...): All targets link with the same set of libs

五、      内部变量

CMAKE_C_COMPILER:指定C编译器

CMAKE_CXX_COMPILER

CMAKE_C_FLAGS:编译C文件时的选项,如-g;也可以通过add_definitions添加编译选项

EXECUTABLE_OUTPUT_PATH:可执行文件的存放路径

LIBRARY_OUTPUT_PATH:库文件路径

CMAKE_BUILD_TYPE::build 类型(Debug, Release, ...),CMAKE_BUILD_TYPE=Debug

BUILD_SHARED_LIBS:Switch between shared and static libraries

内置变量的使用:

>> 在CMakeLists.txt中指定,使用set

>> cmake命令中使用,如cmake -DBUILD_SHARED_LIBS=OFF

7 如何配置cmake的交叉编译环境


  • 这一步按官网交叉编译环境配置说明进行配置就好了,其实就是重写一些变量的值,我项目里面的主要配置如下:
#配置交叉编译变量
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_CROSSCOMPILING TRUE)
#不使用动态链接 -rdyamic
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") 

set(CMAKE_C_COMPILER "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-gcc")
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_AR "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-ar")
set(CMAKE_RANLIB "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-ranlib")
set(CUSTOM_CMD_OBJCOPY "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-objcopy")
set(CUSTOM_CMD_NM "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-nm") 

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH $ENV{CROSS_COMPILE_ROOT_PATH})

8. FAQ

1)  怎样获得一个目录下的所有源文件

>> aux_source_directory( )

>> 将dir中所有源文件(不包括头文件)保存到变量variable中,然后可以add_executable (ss7gw ${variable})这样使用。

2)  怎样指定项目编译目标

>>  project命令指定

3)  怎样添加动态库和静态库

>> target_link_libraries命令添加即可

4)  怎样在执行CMAKE时打印消息

>> message([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)

>> 注意大小写

5)  怎样指定头文件与库文件路径

>> include_directories与link_directories

>>可以多次调用以设置多个路径

>> link_directories仅对其后面的targets起作用

6)  怎样区分debug、release版本

>>建立debug/release两目录,分别在其中执行cmake -DCMAKE_BUILD_TYPE=Debug(或Release),需要编译不同版本时进入不同目录执行make即可;

Debug版会使用参数-g;Release版使用-O3 –DNDEBUG

>> 另一种设置方法——例如DEBUG版设置编译参数DDEBUG

IF(DEBUG_mode)

add_definitions(-DDEBUG)

ENDIF()

在执行cmake时增加参数即可,例如cmake -D DEBUG_mode=ON

7)  怎样设置条件编译

例如debug版设置编译选项DEBUG,并且更改不应改变CMakelist.txt

>> 使用option command,eg:

option(DEBUG_mode "ON for debug or OFF for release" ON)

IF(DEBUG_mode)

add_definitions(-DDEBUG)

ENDIF()

>> 使其生效的方法:首先cmake生成makefile,然后make edit_cache编辑编译选项;Linux下会打开一个文本框,可以更改,该完后再make生成目标文件——emacs不支持make edit_cache;

>> 局限:这种方法不能直接设置生成的makefile,而是必须使用命令在make前设置参数;对于debug、release版本,相当于需要两个目录,分别先cmake一次,然后分别make edit_cache一次;

>> 期望的效果:在执行cmake时直接通过参数指定一个开关项,生成相应的makefile——可以这样做,例如cmake –DDEBUGVERSION=ON

8)  怎样添加编译宏定义

>> 使用add_definitions命令,见命令部分说明

9)  怎样添加编译依赖项

用于确保编译目标项目前依赖项必须先构建好

>>add_dependencies

10)        怎样指定目标文件目录

>> 建立一个新的目录,在该目录中执行cmake生成Makefile文件,这样编译结果会保存在该目录——类似

>> SET_TARGET_PROPERTIES(ss7gw PROPERTIES

RUNTIME_OUTPUT_DIRECTORY "${BIN_DIR}")

11)        很多文件夹,难道需要把每个文件夹编译成一个库文件?

>> 可以不在子目录中使用CMakeList.txt,直接在上层目录中指定子目录

12)        怎样设定依赖的cmake版本

>>cmake_minimum_required(VERSION 2.6)

13)        相对路径怎么指定

>> ${projectname_SOURCE_DIR}表示根源文件目录,${ projectname _BINARY_DIR}表示根二进制文件目录?

14)        怎样设置编译中间文件的目录

>> TBD

15)        怎样在IF语句中使用字串或数字比较

>>数字比较LESS、GREATER、EQUAL,字串比STRLESS、STRGREATER、STREQUAL,

>> Eg:

set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)

set(AAA abc)

IF(AAA STREQUAL abc)

message(STATUS "true")   #应该打印true

ENDIF()

16)        更改h文件时是否只编译必须的cpp文件

>> 是

17)        机器上安装了VC7和VC8,CMAKE会自动搜索编译器,但是怎样指定某个版本?

>> TBD

18)        怎样根据OS指定编译选项

>> IF( APPLE ); IF( UNIX ); IF( WIN32 )

19)        能否自动执行某些编译前、后命令?

>> 可以,TBD

20)        怎样打印make的输出

make VERBOSE=1

六、      命令

project (HELLO)   #指定项目名称,生成的VC项目的名称;

>>使用${HELLO_SOURCE_DIR}表示项目根目录

include_directories:指定头文件的搜索路径,相当于指定gcc的-I参数

>> include_directories (${HELLO_SOURCE_DIR}/Hello)  #增加Hello为include目录

link_directories:动态链接库或静态链接库的搜索路径,相当于gcc的-L参数

       >> link_directories (${HELLO_BINARY_DIR}/Hello)     #增加Hello为link目录

add_subdirectory:包含子目录

       >> add_subdirectory (Hello)

add_executable:编译可执行程序,指定编译,好像也可以添加.o文件

       >> add_executable (helloDemo demo.cxx demo_b.cxx)   #将cxx编译成可执行文件——

add_definitions:添加编译参数

>> add_definitions(-DDEBUG)将在gcc命令行添加DEBUG宏定义;

>> add_definitions( “-Wall -ansi –pedantic –g”)

target_link_libraries:添加链接库,相同于指定-l参数

>> target_link_libraries(demo Hello) #将可执行文件与Hello连接成最终文件demo

add_library:

>> add_library(Hello hello.cxx)  #将hello.cxx编译成静态库如libHello.a

add_custom_target:

message( status|fatal_error, “message”):

set_target_properties( ... ): lots of properties... OUTPUT_NAME, VERSION, ....

link_libraries( lib1 lib2 ...): All targets link with the same set of libs

8 如何自定义编译器/链接器/归档器


  • 一般不建议去重定义cmake默认的这些配置变量,如果确实默认的一些参数你不需要或者你想扩展出一些自己的定义的话可以考虑。参考模板在
    /usr/local/share/cmake-3.10/Modules/CMakeCInformation.cmake
    (如果你cmake安装是默认路径安装的话,我这边以C语言为例)

CMakeCInformation.cmake里面的编译器/链接器/归档器的变量定义如下

#编译C源文件
if(NOT CMAKE_C_COMPILE_OBJECT)
  set(CMAKE_C_COMPILE_OBJECT
    "<CMAKE_C_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT>   -c <SOURCE>")
endif()

#链接C源文件
if(NOT CMAKE_C_LINK_EXECUTABLE)
  set(CMAKE_C_LINK_EXECUTABLE
    "<CMAKE_C_COMPILER> <FLAGS> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS>  -o <TARGET> <LINK_LIBRARIES>")
endif()

#静态库的链接过程
if(NOT DEFINED CMAKE_C_ARCHIVE_CREATE)
  set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qc <TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
if(NOT DEFINED CMAKE_C_ARCHIVE_APPEND)
  set(CMAKE_C_ARCHIVE_APPEND "<CMAKE_AR> q  <TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
if(NOT DEFINED CMAKE_C_ARCHIVE_FINISH)
  set(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> <TARGET>")
endif()
  • 如果你要自定义编译器,链接器,归档器,只需要参考模板,重定义相关的变量就好了。我项目里面编译器和链接器的参数使用cmake默认的配置,但是静态库归档的一些参数cmake默认是写死的,我在我的cmake源码里面进行了重定义。
#自定义的ar生成规则(cmake默认的ar打包参数和我项目源码定义参数不一样,所以自己重新定义)
set(CMAKE_STATIC_LINKER_FLAGS "-rcs")
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> <LINK_FLAGS> <TARGET> <OBJECTS>")

七、      说明

1,CMAKE生成的makefile能够处理好.h文件更改时只编译需要的cpp文件;

9 后记


  • 本文用到的大部分示例代码片段,基本上是我项目中在用的一些cmake代码或者cmake官网的示例代码在进行微调。
  • 用到的cmake相关内置命令主要是对一些关键的参数进行介绍,如果想要更详细的介绍可以参考cmake官网的手册。
  • 我也是第一次把cmake引入到具体项目中,本文介绍的一些使用方式,基于我项目需要而去实践的,比如我需要分层管理,独立输出,交叉编译,多种语言混编等等,可能你引入cmake也会有自己的需求和设计。对我来说cmake只是一个工具而已,最本质的那些东西永远是不变的,你要理解你的需求,你要怎么样去设计整个编译框架来满足你的需求,你要用cmake的那些东西来迎合你的设计,你只拿你需要的部分,剩下的那些杂枝细节可以通通扔掉。而不是反过来因为cmake有这个功能,有那个功能,所以设计应该是这样的或者那样的。
  • 我上面介绍的那些实现方式并不是唯一的,我只选择适合我现在项目的。

八、      FAQ

参考文献

[1] https://cmake.org/cmake/help/v3.10/#

1)  怎样获得一个目录下的所有源文件

>> aux_source_directory(<dir> <variable>)

>> 将dir中所有源文件(不包括头文件)保存到变量variable中,然后可以add_executable (ss7gw ${variable})这样使用。

TAG标签:
版权声明:本文由金沙澳门官网4166发布于文物考古,转载请注明出处:Cmake实战指南,cmake使用方法详解