CMake base tutorial
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。
1.安装
cmake下载地址:[https://cmake.org/download/]
本文使用的是Ubuntu Linux环境,通过apt安装cmake:
$ sudo apt update
$ sudo apt install cmake
2.构建和运行
构建如下文件:
.
├── build
└── src
├── CMakeLists.txt
└── main.c
main.c
#include <stdio.h>
int main(void)
{
printf("hello world.\n");
return 0;
}
CMakeLists.txt
# 运行此文件的cmake最低版本
cmake_minimum_required(VERSION 3.10)
# 设置项目名Tutorial
project(Tutorial)
# 增加可执行文件Tutorial,由如下文件关联:main.c
add_executable(Tutorial main.c)
注:CMakeLists.txt中的关键字大小写均可,这里默认使用小写。
有两种构建方式(二选一,推荐第二种):
- 内部构建
$ cd ./src
$ cmake .
- 外部构建
$ cd ./build
$ cmake ../src/
无论是内部构建还是外部构建,都会生成一系列文件,由于平台是Linux,所以会生成Makefile文件。
- 编译
一般使用如下方式:
$ cmake --build .
因为是在Linux环境下,所以也可以使用make编译,如下:
$ make
- 运行
本例编译后生成叫”Tutorial”的可执行文件,直接运行:
$ ./Tutorial
3.说明
- cmake命令不区分大小写,但是参数、变量区分大小写;
- 参数用空格或分号隔开;
- 使用${VAR}引用变量;
- 引号可加可不加,但如果字符串中有空格则要加引号;
4.概念
- 目标文件(target): 可执行文件(add_executable)、库文件(add_library);
- 命令(cmake-command):用来设置数据的命令;
- 变量(cmake-variable):以”CMAKE_“开头的变量名;
- 属性(cmake-properties):文件/文件夹都有各自的属性;
5.命令
5.1 cmake_minimum_required
# 设置最低cmake版本
cmake_minimum_required(VERSION <min>)
例:
cmake_minimum_required(VERSION 3.10)
5.2 project
# 设置项目名
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.minor[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
# 项目名会被存储在变量PROJECT_NAME和CMAKE_PROJECT_NAME中
# PROJECT_SOURCE_DIR等价于<PROJECT-NAME>_SOURCE_DIR
# PROJECT_BINARY_DIR等价于<PROJECT-NAME>_BINARY_DIR
# 如果定义了版本号
# 版本号被保存在PROJECT_VERSION和<PROJECT-NAME>_VERSION中
# 主版本号被保存在PROJECT_VERSION_MAJOR和<PROJECT-NAME>_VERSION_MAJOR中
# 次版本号被保存在PROJECT_VERSION_MINOR和<PROJECT-NAME>_VErSION_MINOR中
例:
project(Tutorial)
project(Tutorial C CXX)
project(Tutorial VERSION 2.3 LANGUAGES CXX)
5.3 add_executable
# 用指定的源文件为项目添加可执行文件
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[sourcel] [source2] ...)
# <name>即生成可执行文件的名字(与项目名没有关系),在一个项目中必须唯一
例:
add_executable(Tutorial tutorial.cxx)
5.4 message
# 打印信息
message([<mode>] "message text" ...)
# STATUS 前缀为"--"的信息
# SEND_ERROR产生错误,跳过生成过程
# FATAL_ERROR产生错误,终止运行
例:
message(STATUS "${PROJECT_VERSION_MAJOR}")
5.5 set
# 将变量设置为指定值
set(<variable> <value>)
例:
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REOUIRED True)
# 设置输出文件位置
# 设置运行时目标(exe、dll、so)的输出位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 设置存档目标文件(lib、a)的输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
5.6 option
# 定义一个开关
option(<variable> "<help_text>" [value])
# value的值为ON或OFF,默认为OFF
# option的值可以通过命令行改变 -D <variable>=ON/OFF
例:
option(VERSION_ENABLE "output version" ON)
5.7 configure_file
# 将输入文件进行替换并生成输出文件
configure_file(<input> <output>)
# 输入文件中形如@VAR@或${VAR}的字符串会被替换为这些变量的当前值,如果未定义则被替换为空字符串
#cmakedefine VAR ...
// 会被替换为以下两行之一,取决于VAR是否被设置
1.被设置会替换成:#define VAR ...
2.没有被设置替换成:/* #undef VAR */
例:
目录树
.
├── build
└── src
├── CMakeLists.txt
├── config.h.in
└── main.c
main.c
#include <stdio.h>
#include "config.h"
int main(void)
{
printf("hello world.\n");
printf("Version %d.%d.\n", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
return 0;
}
config.h.in
#cmakedefine PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#cmakedefine PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
CMakeLists.txt
# 设置运行此文件的最低cmake版本
cmake_minimum_required(VERSION 3.10)
# 设置项目名和版本号
# 版本1.2,其中1是Major号,2是Minor号
# Major号有效,会设置PROJECT_VERSION_MAJOR
# Minor号有效,会设置PROJECT_VERSION_MINOR
project(Tutorial VERSION 1.2)
# 将输入文件进行替换并生成输出文件
# 由于设置了版本号1.2,在输出文件config.h中
# 会定义并替换Major和Minor真实的数字
configure_file(config.h.in config.h)
# 增加可执行文件,关联main.c
add_executable(Tutorial main.c)
# 指定目标的头文件路径
# 用来指明本例生成的config.h会在目标路径中
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
运行方式
$ cd build
$ cmake ../src/
$ cmake --build .
$ ./Tutorial
hello world.
Version 1.2.
生成的config.h的内容如下:
#define PROJECT_VERSION_MAJOR 1
#define PROJECT_VERSION_MINOR 2
5.8 include_directories
# 指定所有目标的头文件路径
include_directories(dir1 [dir2 ...])
# 目录会被添加到当前文件的INCLUDE_DIRECTORIES属性中
# 当前文件的每一个目标文件的INCLUDE_DIRECTORIES属性中也会田间该目录
例:
include_directories(${PROJECT_BINARY_DIR})
5.9 target_include_directories
# 指定目标的头文件路径
target_include_directories(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2] ...])
例:
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
5.10 add_subdirectory
# 添加源文件目录
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FORM_ALL])
# binary_dir 指定编译结果存放的位置
目录树
.
├── build
└── src
├── CMakeLists.txt
├── calc
│ ├── CMakeLists.txt
│ ├── add.c
│ ├── add.h
│ ├── sub.c
│ └── sub.h
├── config.h.in
└── main.c
例(src/CMakeLists.txt):
add_subdirectory(calc)
6.库
6.1 add_library
# 用指定的原为见生成库
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FORM_ALL]
[<source>...])
# STATIC 静态库
# SHARED 动态库
# 生成的库文件名为lib<name>.xxx
6.2 target_link_libraries
# 为目标链接库
target_link_libraries(<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
# item可以是target名,绝对路径(必须保证文件存在)
6.3 区分
# 头文件目录
include_directories()
target_include_directories()
# 链接时库目录
link_directories()
target_link_directories()
# 链接库
link_libraries()
target_link_libraries()
# 默认推荐使用以"target_"开头的关键字,因为不带"target_"的关键字是全局包含、向下传递的
例:
目录树
.
├── build
└── src
├── CMakeLists.txt
├── calc
│ ├── CMakeLists.txt
│ ├── add.c
│ ├── add.h
│ ├── sub.c
│ └── sub.h
├── config.h.in
└── main.c
src/main.c
#include <stdio.h>
#include "config.h"
#include "add.h"
#include "sub.h"
int main(void)
{
printf("hello world.\n");
printf("Version %d.%d.\n", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
printf("3 + 2 = %d.\n", add(3, 2));
printf("3 - 2 = %d.\n", sub(3, 2));
return 0;
}
src/config.h.in
#cmakedefine PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#cmakedefine PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
src/CMakeLists.txt
# 运行此文件的最低cmake版本
cmake_minimum_required(VERSION 3.10)
# 可执行文件存放位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 动态库存放位置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 静态库存放位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 设置项目名
project(Tutorial VERSION 1.2)
# 替换并输出config.h
configure_file(config.h.in config.h)
# 增加子目录
add_subdirectory(calc)
# 添加可执行文件,关联main.c
add_executable(Tutorial main.c)
# 链接目标库
target_link_libraries(Tutorial PUBLIC addition)
target_link_libraries(Tutorial PUBLIC subtract)
# 添加搜索头文件路径的位置
target_include_directories(Tutorial PUBLIC
${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/calc
)
src/calc/add.h
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif /* __ADD_H__ */
src/calc/add.c
#include "add.h"
int add(int a, int b)
{
return a + b;
}
src/calc/sub.h
#ifndef __SUB_H__
#define __SUB_H__
int sub(int a, int b);
#endif /* __SUB_H__ */
src/calc/sub.c
#include "sub.h"
int sub(int a, int b)
{
return a - b;
}
src/calc/CMakeLists.txt
# 生成名为addition的静态库,关联add.c
# Linux下为libaddition.a
add_library(addition STATIC add.c)
# 生成名为subtract的动态库,关联sub.c
# Linux下为libsubtract.so
add_library(subtract SHARED sub.c)
运行测试
$ cd build
$ cmake ../src/
$ cmake --build .
$ ./bin/Tutorial
hello world.
Version 1.2.
3 + 2 = 5.
3 - 2 = 1.
7.安装
# 安装目标
install(TARGETS <target> DESTINATION <dir>)
# 安装文件
install(FILES <file> DESTINATION <dir>)
# 安装非目标文件的可执行程序,如脚本
install(PROGRAMS <非目标文件的可执行程序> DESTINATION <dir>)
# 安装目录
install(DIRECTORY <dir> DESTINATION <dir>)
例:
# CMakeLists.txt
install(TARGETS Tutorial DESTINATION lib)
install(FILES config.h DESTINATION include)
install(DIRECTORY docs/ DESTINATION doc)
安装命令
# 安装到默认目录CMAKE_INSTALL_PREFIX
$ cmake --install .
# 安装到指定目录
$ cmake --install . --prefix <dir>