CMake编译系统使用

关于CMake编译系统的一些使用记录.
Views: 461
1 0
Read Time:6 Minute, 41 Second

CMake是一套开源的跨平台编译系统,可以用于编译、测试和打包代码工程。除了原生支持Linux\Windows\MacOS等系统,CMake还支持以嵌入的形式在IDE环境中使用,如Apple旗下的Xcode,以及微软出品的Microsoft Visual Studio,Google旗下的Android Studio等。

众多的开源软件以及许多大型软件开发都采用了CMake作为其编译系统。如果你想学习并使用他们,掌握其编译是第一步,因此我们学习并掌握CMake是很有必要的。

安装&配置

一般我们在Linux系统中使用CMake,此处以Ubuntu系统为例。

某些发行版可能已经自带了CMake,此时我们可以通过apt查看当前CMake的一些基本信息,如下所示:

FranzKafkaYu@coderfan:apt show cmake
Package: cmake
Version: 3.16.3-1ubuntu1.20.04.1
Priority: optional
Section: devel
Origin: Ubuntu
Maintainer: Ubuntu Developers <[email protected]>
Original-Maintainer: Debian CMake Team <[email protected]>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 19.2 MB
Depends: cmake-data (= 3.16.3-1ubuntu1.20.04.1), procps, libarchive13 (>= 3.3.3), libc6 (>= 2.17), libcurl4 (>= 7.16.2), libexpat1 (>= 2.0.1), libgcc-s1 (>= 3.0), libjsoncpp1 (>= 1.7.4), librhash0 (>= 1.2.6), libstdc++6 (>= 9), libuv1 (>= 1.11.0), zlib1g (>= 1:1.1.4)
Recommends: gcc, make
Suggests: cmake-doc, ninja-build
Homepage: https://cmake.org/
Download-Size: 3,668 kB
APT-Manual-Installed: yes
APT-Sources: http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages
Description: cross-platform, open-source make system
 CMake is used to control the software compilation process using
 simple platform and compiler independent configuration files. CMake
 generates native makefiles and workspaces that can be used in the
 compiler environment of your choice. CMake is quite sophisticated: it
 is possible to support complex environments requiring system
 configuration, pre-processor generation, code generation, and template
 instantiation.
 .
 CMake was developed by Kitware as part of the NLM Insight
 Segmentation and Registration Toolkit project. The ASCI VIEWS project
 also provided support in the context of their parallel computation
 environment. Other sponsors include the Insight, VTK, and VXL open
 source software communities.

N: There is 1 additional record. Please use the '-a' switch to see it

如果没有安装CMake,我们直接使用apt进行安装,由于CMake本身存在依赖,安装CMake时需要一并安装依赖,使用如下命令安装CMake及其依赖:

sudo apt install cmake g++ make

安装完成后,我们可以使用cmake –version查看所安装的CMake版本:

FranzKafkaYu@coderfan:$ cmake --version
cmake version 3.16.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
FranzKafkaYu@coderfan:

通过cmake –help进入帮助菜单来获取更多信息:

FranzKafkaYu@coderfan:$ cmake --help
Usage

  cmake [options] <path-to-source>
  cmake [options] <path-to-existing-build>
  cmake [options] -S <path-to-source> -B <path-to-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Options
  -S <path-to-source>          = Explicitly specify a source directory.
  -B <path-to-build>           = Explicitly specify a build directory.
  -C <initial-cache>           = Pre-load a script to populate the cache.
  -D <var>[:<type>]=<value>    = Create or update a cmake cache entry.
  -U <globbing_expr>           = Remove matching entries from CMake cache.
  -G <generator-name>          = Specify a build system generator.
  -T <toolset-name>            = Specify toolset name if supported by
                                 generator.
  -A <platform-name>           = Specify platform name if supported by
                                 generator.
  -Wdev                        = Enable developer warnings.
  -Wno-dev                     = Suppress developer warnings.
  -Werror=dev                  = Make developer warnings errors.
  -Wno-error=dev               = Make developer warnings not errors.
  -Wdeprecated                 = Enable deprecation warnings.
  -Wno-deprecated              = Suppress deprecation warnings.
  -Werror=deprecated           = Make deprecated macro and function warnings
                                 errors.
  -Wno-error=deprecated        = Make deprecated macro and function warnings
                                 not errors.
  -E                           = CMake command mode.
  -L[A][H]                     = List non-advanced cached variables.
  --build <dir>                = Build a CMake-generated project binary tree.
  --install <dir>              = Install a CMake-generated project binary
                                 tree.
  --open <dir>                 = Open generated project in the associated
                                 application.
  -N                           = View mode only.
  -P <file>                    = Process script mode.
  --find-package               = Run in pkg-config like mode.
  --graphviz=[file]            = Generate graphviz of dependencies, see
                                 CMakeGraphVizOptions.cmake for more.
  --system-information [file]  = Dump information about this system.

其他平台的安装或者手动安装可以参考官方网站Link,此处略过不表。

编译流程

这里我们主要讲讲CMake的常规编译流程。

一般而言使用CMake的编译流程如下:

1.mkdir build && cd build //在代码工程顶层目录下创建build文件夹,进入build文件夹
2.cmake .. //通过cmake cli根据CMakeLists.txt生成编译配置
3.make -j4 //执行编译,-j4用于指定线程数量,也可使用cmake --build build -j3 
4.make install //执行安装

第一步:创建一个单独的名为build的文件夹,这一步并非必须的。不过为了规范,我们会约定俗成地创建这样一个文件夹。因为后续的编译行为会产生一些配置文件以及中间产物,包括最最终生成的二进制产物。为了不污染源码环境,同时为了集中管理,我们单独创建了build文件夹,在之后的操作中所有的产物都会留存在该文件夹内。

第二步:通过CMake cli加载上级目录中的CMakeLists.txt。CMakeLists.txt是CMake编译系统的核心配置文件,我们一般将其放置于代码工程的顶级目录。CMake会根据CMakeLists.txt生成CMakeCacahe.txt以及Makefile,前者是CMake相关环境配置的缓存文件,我们可以通过其查看CMake编译系统的各个配置信息;后者是make编译所需要的配置文件。

第三步:执行编译过程,这里将会使用make进行编译,这一环节make cli将会根据当前目录下的Makefile执行编译。当然,这一步我们也可以通过cmake cli来执行,如使用cmake –build build -j4命令。编译完成后,我们将会看到生成产物的目录。

第四步:进行安装,这一步将会将生成的二进制产物安装到本机或者指定位置。在Linux系统中默认安装到/usr/lib、/usr/share或者/usr/bin目录。如果我们需要指定安装位置,可以使用如下命令进行指定:

make install DESTDIR= /path/to/install

以上就是常规的编译流程了,但是仅仅掌握其编译流程是不够的,尤其是当我们从头开始搭建一个项目时,我们往往都需要进行CMakeLists.txt的编写,可以说,掌握CMakeLists.txt编写才是最为核心的。

语法规则

格式要求

CMake的语法规则包含很多方面,首先是其格式上的要求。这部分其实就是规定了CMakeLists.txt在形式上应当遵守的格式,如下所示:

#首先使用project命令指定项目名称
project(main)
#其次使用cmake_minimum_required指定Cmake的最小版本
cmake_minimum_required(VERSION 3.6) 
#通过include_directories来指定头文件目录
include_directories(./include}
#通过add_subdirectory命令来指定源码目录
add_subdirectory( src )
#通过add_executable命令来指定生成的目标二进制以及对应的源码
add_executable(main ${DIR_SRCS}  )
#通过target_link_libraries来指定链接依赖的库
target_link_libraries( main pthread atomic )

以上就是大体上的格式要求了。我们在编写CMakeLists.txt时,应该尽可能地遵守该格式。

头文件&库

很多C/C++程序依赖于外部的库,在编译和链接一个工程时,我们首先需要通过CMake提供的多种特性,将外部库集成到工程中。 

与集成外部库相关的命令包括: find_library 、find_path、find_program、find_package 。对于大部分C/C++库,使用前两个命令一般足够和系统上已安装的库进行链接,这两个命令分别可以用来定位库文件、头文件所在目录,这里给出示例:

# 寻找一个库
find_library(
        TIFF_LIBRARY
        NAMES tiff tiff2  #只需要库的basename,不需要平台特定的前缀、后缀。前面的库优先
        #额外的路径,支持Windows注册表条目,例如[HKEY_CURRENT_USER\\Software\\Path;Build1]
        PATHS /usr/local/lib /usr/lib       #前面的路径优先
)
# 寻找一般性的文件,仅支持一个待查找文件,支持多个路径
find_path(
        TIFF_INCLUDES
        tiff.h
        /usr/local/include /usr/include
)
#设定头文件
include_directories(${TIFF_INCLUDES})
#指定编译产物
add_executable(mytiff mytiff.c)
target_link_libraries(mytiff ${TIFF_LIBRARY})
产物指定

CMake可以用于编译多种产物,如binary、library等,那么我们需要在CMakeLists.txt进行指定来明确我们需要编译生成的 产物类型。

指定编译生成binary:需要使用add_executable命令,其语法规则如下:

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

在这个命令中,name用于指定binary的名称,编译生成的binary即为此名;WIN32、MACOSX_BUNDLE、EXCLUDE_FROM_ALL为布尔类型的拓展,当它们值为true或者false时CMake都会针对这些拓展进行相应的设置,使编译产物能够满足使用者的需求,更多细节可参考here,需要说明的是,这些拓展的属性本身是可以省略不写的。source1、source2等即为编译产物name所需要的源码文件。这里给出一个示例:

#由mainboard.cc、module_argument.cc、module_controller.cc编译生成mainboard
add_executable(mainboard
  cyber/mainboard/mainboard.cc
  cyber/mainboard/module_argument.cc
  cyber/mainboard/module_controller.cc
)

add_executable除了用于指定编译产物外,还存在一些其他的用途。如导入外部命令行工具,如下所示:

add_executable(<name> IMPORTED [GLOBAL])

此时add_excutable通常与add_custom_command搭配使用;add_executable还可以为可执行文件设定alias别名,如下所示:

add_executable(<name> ALIAS <target>)

指定编译生成library:需要使用add_library命令,其语法规则如下:

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

在该命令中,name为编译生成的library名称,而STATIC、SHARED、MODULE这三个属性则用于指定生成的library类型,STATIC则生成静态库(Windows下的.lib,Linux/MacOs下的.a),SHARED则生成动态库(Windows下的.dll,Linux下的.so,MacOs下的.dylib),而MODULE则是将生成动态库但不会进行链接。如果我们在add_library时未显示指定其类型,则会根据变量BUILD_SHARED_LIBS值来进行判定。sources用于指定所需要的源码文件。

与add_executable类似,add_library还有一些其他的作用,此处不再赘述,可参考此Link

属性设定

上面我们已经知道了如何去指定编译产物,但是在编译的过程中我们可能还需要进行一些额外的设定,如编译选项、链接选项等,此时我们就需要进行属性设定。

首先我们需要明确知道CMake中支持对哪些属性进行设定,这里列出一些比较常用的:

属性说明
LINK_FLAGS指定(传递给)链接(器的)标记。示例:
123456set_target_properties(    cstudy PROPERTIES    INCLUDE_DIRECTORIES /home/alex/.local/include    # 使用定制的glibc库    LINK_FLAGS “-Wl,-rpath=/home/alex/.local/lib  -Wl,–dynamic-linker=/home/alex/.local/lib/ld-linux-x86-64.so.2”)
COMPILE_FLAGS指定(传递给)编译(器的)标记
INCLUDE_DIRECTORIES指定目标需要引用的头文件目录
PUBLIC_HEADER共享的库目标提供的公共头文件
VERSION
SOVERSION
对于共享库来说,VERSION、SOVERSION允许让你分别设置构建版本、API版本。通常SOVERSION更加稳定不变如果设置了NO_SONAME属性,则SOVERSION属性被自动忽略
OUTPUT_NAME目标输出文件名称

详细的属性支持列表请参考here

一般我们可以通过set_property和get_property命令来设置和获取属性,对于目标产物(Targets)而言,我们也可以通过set_target_properties 或者 get_target_properties命令,如下所示:

# 修改目录使用的头文件目录,注意,全局的include_directories会此目标忽略
set_property (TARGET jsonrpc
  PROPERTY INCLUDE_DIRECTORIES
    ${JSONCPP_INCLUDE_DIRS}
    ${Boost_INCLUDE_DIRS}
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/jsonrpc
)
 
# 同时设置多个属性
set_target_properties(jsonrpc PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR})
链接行为

假设我们已经完成了产物指定并设置好了我们需要的属性,接下来我们还需要指定链接行为。因为CMake自身是无法知道你的编译产物具体的依赖的,所以我们需要手动来指定,此时我们需要通过target_link_libraries命令来完成指定。其语法如下:

target_link_libraries(<target>
                      <PRIVATE|PUBLIC|INTERFACE> <item>...
                     [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

这里直接通过例子来进行说明:

add_library(foo foo.cpp)
#foo库依赖于bar库,将libbar链接到libfoo中
target_link_libraries(foo bar)
 
add_executable(foobar foobar.cpp)
#foobar显式依赖foo,隐式依赖bar,后两者都会被链接到foobar中
target_link_libraries(foobar foo)
变量使用

在我们编写CMakeLists.txt过程中,可能会涉及到一些中间变量,CMakCMakeLists支持使用变量;其变量的使用和普通编程语言中的变量是类似的。变量的值要么是单个值,要么是列表。CMake自动定义一系列重要的变量。如下是CMake中一些经常使用的变量:

变量说明
CMAKE_C_FLAGSC编译标记,示例: set(CMAKE_C_FLAGS “-std=c11 -pthread”)
CMAKE_CXX_FLAGSC++编译标记
CMAKE_C_FLAGS_DEBUG用于Debug配置的C编译标记,示例: set(CMAKE_C_FLAGS_DEBUG “-g -O0”)
CMAKE_CXX_FLAGS_DEBUG用于Debug配置的C++编译标记
CMAKE_C_FLAGS_RELEASE用于Release配置的C编译标记
CMAKE_CXX_FLAGS_RELEASE用于Release配置的C++编译标记

我们也可以自定义变量进行使用,此时我们需要使用set命令,其语法规则如下:

set(<variable> <value>... [PARENT_SCOPE])

这里给出一些示例:

#设定变量名fruit,其值为一个列表,包含apple、peach、strawberry
set(fruit apple peach strawberry )
#设定变量名student,其值为Alex
set (student Alex)

我们可以通过unset命令取消变量的值设定。

如何引用变量呢,可以通过${VARIABLE_NAME}的形式进行引用。

流程控制

CMakeLists.txt 中支持流程控制,通过流程控制我们可以编写出更高效的编译配置。

流程控制一般涉及到条件选择循环控制两类。

条件选择

#if-else-endif
if (FOO)
else()
endif()

#if-elseif-endif
if(MSVC80)
  #...
elseif(MSVC90)
  #...
elseif(APPLE)
  #...
endif()

循环控制

#foreach循环
foreach (item  list)
    # do something with item
endforeach (item)

#while循环
while(${COUNT} LESS 2000)
    set(TASK_COUNT, ${COUNT})
endwhile()

在循环控制中我们可以使用break跳出循环体,可以return直接返回。

Others

这部分主要是CMakeList.txt中一些比较常用行为

注释:在CMakeList.txt中我们可以通过#来进行单行注释,通过#[[ ]]来进行多行注释,如下所示:

#这是单行注释
message("First Argument\n" # This is a line comment :)
        "Second Argument") # This is a line comment.

#[[这是多行注释.
这是多行注释]]
message("First Argument\n" #[[Bracket Comment]] "Second Argument")

日志打印:在 CMakeList.txt中 中,我们可以通过message命令来进行日志打印。

以上就是CMake编译系统的一些基本使用了,CMake一直在发展,其功能也越来越丰富,当前我所掌握到的还只是其中很小的一部分,以后有机会将会逐步进行完善~

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

Articles: 85

Leave a Reply

Your email address will not be published. Required fields are marked *

en_USEN