0%

现代cmake学习

Introduction

CMake是一个编译系统生成工具,而非编译系统。CMake能够生成编译系统的输入文件如Makefile,CMake本身支持Make/Ninja/Visual Studio/XCode等。

CMake是跨平台的,支持Linux、Windows、OSX等,同时也支持跨平台构建(编译器要支持跨平台才可以哦)。

CMake开始于1999/2000年,现代CMake开始于2014年的3.0版本,现代CMake有一个非常重要的概念,Everything is a (self-contained) target

Everything that is needed to (successfully) build that target.

Everything that is needed to (successfully) use that target.

Let’s Go

时刻牢记以下三句话。

  • Declare a target
  • Declare target’s traits
  • It’s all about targets

Minimum Version

1
cmake_minimum_required(VERSION 3.5)

Setting a Project

1
2
3
4
5
6
7
8
9
project(hello-world VERSION 1.0
DESCRIPTION ""
LANGUAGES CXX) ## C/CXX/ASM/CUDA/FORTAN/SWIFT

add_subdirectory(src)

install(FILES COPYRIGHT README.md DESTINATION share/doc/cmake/hello-world)
install(PROGREAMS **.sh DESTINATION bin)
install(DIRECTORY doc DESTINATION share/doc/cmake/hello-world)

Making an Executable

1
add_executable(one two.cpp three.h)

one既是生成的可执行程序也是Target,后续为源文件列表和头文件列表,大多数情况下,头文件将会被忽略,只是为了让他们显示在IDE中。

Making an Library

1
2
3
4
5
6
7
8
## BUILD_SHARED_LIBS
add_library(one two.cpp three.h)
## 静态库
add_library(one STATIC two.cpp three.h)
## 动态库
add_library(one SHARED two.cpp three.h)
## 模块
add_library(one MODULE two.cpp three.h)

Target

1
2
3
target_include_directories(ont PUBLIC include)
target_include_directories(ont PRIVATE include)
target_include_directories(ont INTERFACE include)

PUBLIC意味着所有链接到此目标的目标都需要包含include目录,PRIVATE表示只有当前target需要,依赖项不需要,INTERFACE表示只有依赖项许需要。

1
2
add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
  • target_include_directories指定了target包含的头文件路径。

  • target_link_libraries指定了target链接的库。

  • target_compile_options指定了taget的编译选项。

target由add_library()add_executable()生成。

我们以如下工程目录介绍PUBLIC/PRIVATE/INTERFACE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake-test/                 工程主目录,main.c 调用 libhello-world.so
├── CMakeLists.txt
├── hello-world 生成 libhello-world.so,调用 libhello.so 和 libworld.so
│ ├── CMakeLists.txt
│ ├── hello 生成 libhello.so
│ │ ├── CMakeLists.txt
│ │ ├── hello.c
│ │ └── hello.h libhello.so 对外的头文件
│ ├── hello_world.c
│ ├── hello_world.h libhello-world.so 对外的头文件
│ └── world 生成 libworld.so
│ ├── CMakeLists.txt
│ ├── world.c
│ └── world.h libworld.so 对外的头文件
└── main.c

其调用关系如下所示

1
2
3
                                 ├────libhello.so
可执行文件────libhello-world.so
├────libworld.so

PRIVATE:生成libhello-world.so时,只在hello_world.c中包含了hello.hlibhello-world.so对外的头文件hello_world.h不包含hello.h,并且main.c不调用hello.c中的函数,那么应当用PRIVATE

1
2
target_link_libraries(hello-world PRIVATE hello)
target_include_directories(hello-world PRIVATE hello)

INTERFACE:生成libhello-world.so时,只在libhello-world.so对外的头文件hello_world.h包含hello.hhello_world.c不包含hello.hlibhello-world.so不使用libhello.so提供的功能,只需要hello.h中定义的结构体/类等类型信息,但main.c需要调用hello.c中的函数即libhello.so中的函数,那么应当用INTERFACE

1
2
target_link_libraries(hello-world INTERFACE hello)
target_include_directories(hello-world INTERFACE hello)

PUBLIC:生成libhello-world.so时,在libhello-world.so对外的头文件hello_world.h包含hello.hhello_world.c也包含hello.hlibhello-world.so使用libhello.so提供的功能,并且main.c需要调用hello.c中的函数即libhello.so中的函数,那么应当用PUBLIC

1
2
target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)

着重理解依赖传递的概念,main.c依赖于libhello-world.solibhello-world.so依赖于libhello.solibworld.so,若main.c不调用libhello.so中的功能,则hello-worldhello之间采用PRIVATE。若main.c调用libhello.so中的函数,但libhello-world.so不调用,则用INTERFACE。若main.clibhello-world.so都调用libhello.so的函数,则使用PUBLIC关键字。

可以参考C++继承中PRIVATE/PROTECTED/PUBLIC的概念1

Variables

Local Variables

1
2
3
set(VAR1 "local variable")

message("VAR1 is : " ${MY_VARIABLE})

Cache Variables

1
2
3
set(VAR2
belebele1 CACHE STRING "cache")
message("VAR2 is : " ${VAR2})

命令行调用之后,会将该变量写入CMakeCache.txt,之后调用若不从命令行重新赋值,则会一直采用Cache中的值。

1
2
3
mkdir build && cd build
cmake .. -DMY_CACHE_VARIABLE(:STRING)=belebele
cmake -L .. ## 列出当前cache变量

bool类型的变量常用OPTION表示,OPTION也可以看作cache变量的一种,所以会写进CMakeCache.txt

1
OPTION(VAR3 "description" OFF/ON)

cmake的一些常见变量见官网2

Environment Variables

1
2
set(ENV{variable_name} value)
$ENV{variable_name}

Properties

1
2
3
4
5
set_property(TARGET TargetName PROPERTY CXX_STANDARD 11)

set_target_properties(TargetName PROPERTY CXX_STANDARD 11)

get_property(Result TARGET TargetName PROPERTY CXX_STANDARD)

Control Flow

1
2
3
4
5
if(${variable})
xxx
else()
zzz
endif()

Function

1
2
3
function(SIMPLE_FUNC)
message("simple function")
endfunction()

其他控制逻辑有NOT/TARGET/EXISTS/DEFINED/AND/OR/STREQUAL/MATCHES/VERSION_LESS/VERSION_LESS_EQUAL等。

参考文献