CMake
请先看完上一篇博客:CMake入门(一)
一、说明
CMake
的命令是不区分大小写的;但是定义的变量、参数区分大小写;- 参数是用 空格 或者 分号
;
隔开的; - 使用
${var}
引用变量,其中var
是要引用变量的名字; - 引号可加可不加,如果字符串中 有空格 就必须加引号。比如
MinGW Makefiles
就必须加引号,"MinGW Makefiles"
;
二、概念
- 目标文件(
target
)分为:可执行文件(add_executable
) 和 库文件(add_library
); - 命令(
cmake-command
):也就是后面要讲的命令; - 变量(
cmake-variable
):是 CMake 内置的以CMAKE_
开头的变量; - 属性(
cmake-properties
):文件/文件夹都有各自的属性
三、常用命令
cmake_minimum_required
该命令的作用是设置 CMake 的最低版本,低于这个版本的就不行。
格式:
cmake_minimum_required(VERSION <min>)
例子:
# 这里设置 CMake 的最低版本为 3.10
cmake_minimum_required(VERSION 3.10)
project
该命令的作用是设置 项目名。
格式:
# <PROJECT-NAME> 是设置项目名 ; <language-name> 是指定项目所使用的语言,比如C++ 就是 CXX
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 中
例子:
# 设置项目名为 demo
project(demo)
#设置项目名为 demo ; 指定项目的语言为 C C++
project(demo C CXX)
#设置项目名为 demo ; 设置项目的版本号为 2.3 ; 指定项目的语言为 C++
project(demo VERSION 2.3 LANGUAGES CXX)
add_executable
用指定的源文件为项目添加可执行文件。
格式:
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
# <name>即生成可执行文件的名字(与项目名没有关系),在一个项目中必须唯一
# 如windows系统会生成<name>.exe文件
例子:
# 把 main.cpp 添加为可执行文件
add_executable(demo main.cpp)
message
该命令的作用是打印指定信息。
格式:
message([<mode>] "message text" ...)
# STATUS 前缀为--的信息
# SEND_ERROR 产生错误,跳过生成过程
# FATAL_ERROR 产生错误,终止运行
例子:
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(demo VERSION 1.1)
# 打印 src 文件夹路径
message(${demo_SOURCE_DIR})
# 打印 build 文件夹路径
message(${demo_BINARY_DIR})
#打印项目版本号
message(${PROJECT_VERSION})
#打印主版本号
message(${PROJECT_VERSION_MAJOR})
#打印次版本号
message(${PROJECT_VERSION_MINOR})
add_executable(demo main.cpp)
在 demo/build
文件夹下执行命令:
cmake -G "MinGW Makefiles" ../src
结果:
set
该命令是将一个变量设置为指定值。
格式:
set(<variable> <value>)
例子:
cmake_minimum_required(VERSION 3.10)
project(demo VERSION 1.1)
#设置C++ 11 标准
set(CMAKE_CXX_STANDARD 11)
#设置输出文件位置
# 设置运行时目标文件(exe、dll)的输出位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 设置存档目标文件(lib、a)的输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
#设置变量
set(Genshin "感觉不如原神")
set(msg "Hello,World!!!")
set(str "hahahhhaha")
# 分别打印这些变量
message(${Genshin})
message(${msg})
#用 STATUS 模式打印
message(STATUS ${str})
add_executable(demo main.cpp)
在 demo/build
文件夹下执行命令:
cmake -G "MinGW Makefiles" ../src
结果:
option
该命令是定义一个开关,默认是 OFF
模式。
格式:
option(<variable> "<help_text>" [value])
# value的值为 ON 或 OFF ,默认为 OFF
# 命令行 -D<variable>=ON/OFF
例子:
# 定义一个开关 btn
option(btn "click" ON)
configure_file
该命令是 将输入文件指定内容替换之后生成输出文件。
格式:
configure_file(<input> <output>)
# 输入文件中形如 @VAR@ 或 ${VAR} 的字符串会被替换为这些变量的当前值,如果未定义则被替换为空字符串
# 其他规则见下
configure_file(<input> <output>)
#cmakedefine VAR ...
#会被替换为以下两行之一,取决于VAR是否被设置
#define VAR ...
/* #undef VAR */
例子:
在 demo/src
目录下,新建一个文件 foo.h.in
:
foo.h.in
#cmakedefine FOO_ENABLE
#cmakedefine FOO_STRING "@FOO_STRING@"
在 CMakeLists.txt
中加上以下这部分代码:
#设置一个名为 FOO_ENABLE 的开关
option(FOO_ENABLE "Enable Foo" ON)
#如果开关是打开的 就设置 FOO_STRING 的值为 "foo"
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
# 输入是foo.h.in文件,输出是 foo.h
configure_file(foo.h.in foo.h)
在 demo/build
文件夹下执行命令:
cmake -G "MinGW Makefiles" ../src
结果:
build
目录下生成了 foo.h
文件,并且它的内容如图所示:
将 FOO_ENABLE
的模式设置为 OFF
,再执行一次刚才的命令:
#设置一个名为 FOO_ENABLE 的开关
#模式设置为 OFF
option(FOO_ENABLE "Enable Foo" OFF)
#如果开关是打开的 就设置 FOO_STRING 的值为 "foo"
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
# 输入是foo.h.in文件,输出是 foo.h
configure_file(foo.h.in foo.h)
结果为:
所以得出结论:
// 如果FOO_ENABLE为ON,则生成以下内容的.h文件
#define FOO_ENABLE
#define FOO_STRING "foo"
// 如果FOO_ENABLE为OFF,则生成以下内容的.h文件
/* #undef FOO_ENABLE */
/* #undef FOO_STRING */
include_directories
该命令会指定所有目标的头文件路径。
格式:
include_directories(dir1 [dir2 ...])
# 目录会被添加到当前文件的 INCLUDE_DIRECTORIES 属性中
# 当前文件的每一个目标文件的 INCLUDE_DIRECTORIES 属性也会添加该目录
target_include_directories
该命令会指定目标的头文件路径。
格式:
target_include_directories(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
# 目标文件有 INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES 两个属性
# INCLUDE_DIRECTORIES 对内头文件目录
# INTERFACE_INCLUDE_DIRECTORIES 对外头文件目录
INCLUDE_DIRECTORIES | INTERFACE_INCLUDE_DIRECTORIES | |
---|---|---|
PRIVATE | ✔ | |
INTERFACE | ✔ | |
PUBLIC | ✔ | ✔ |
详细请阅读这篇文章:cmake:target_** 中的 PUBLIC,PRIVATE,INTERFACE
此时我们在 main.cpp
中添加如下代码,尝试打印 foo.h
中的宏 FOO_STRING
:
main.cpp
#include<iostream>
#include "foo.h"
using namespace std;
int main(){
cout<<"Hello World!!!"<<endl;
//打印 foo.h 中定义的这个宏
cout<<"FOO_STRING : "<<FOO_STRING<<endl;
return 0;
}
当我们执行 cmake --build .
进行编译时,发生了如下报错:
意思是找不到 foo.h
这个文件。
因为这个 foo.h
文件是存在于 build/
目录下的,所以我们需要在 CMakeLists.txt
加入以下这句命令,把 build
目录加入到其中:
target_include_directories(demo
PUBLIC ${PROJECT_BINARY_DIR})
再重新 编译、运行。
这次就成功了!!
add_subdirectory
该命令是添加源文件目录的。
格式:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# binary_dir 指定编译结果存放的位置
add_library
该命令是用用指定的源文件生成库。
格式:
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
# STATIC 静态库
# SHARED 动态库
# 生成的库文件名为 lib<name>.xxx
target_link_libraries
该命令是为目标链接库。
格式:
target_link_libraries(<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
# item 可以是target名、绝对路径(必须保证文件存在)
区别
# 头文件目录
include_directories()
target_include_directories()
# 链接时库目录
link_directories()
target_link_directories()
# 链接库
link_libraries()
target_link_libraries()
# 都推荐使用以target_开头的函数
install
该命令会把指定的文件移动到对应的文件夹下面。
格式:
install(TARGETS <target> DESTINATION <dir>)
install(FILES <file> DESTINATION <dir>)
install(PROGRAMS <非目标文件的可执行程序> DESTINATION <dir>) # 如脚本
install(DIRECTORY <dir> DESTINATION <dir>) # 安装目录
例子:
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
install(DIRECTORY doc/ DESTINATION doc)
四、库的生成和链接
1.使用自己编写的库
编写静态 addition
库
(1) 在 src
文件夹下,创建一个 calc
文件夹。再在这个 src/calc/
文件夹下面创建四个文件,addition.h
、addition.cpp
、substract.h
、substract.cpp
。
addition.h
:
int add(int,int);
addition.cpp
:
int add(int a,int b){
return a + b;
}
substract.h
:
int sub(int,int);
substract.cpp
:
int sub(int a,int b){
return a - b;
}
(2) 在 src/CMakeLists.txt
添加如下命令:
# 添加子目录 calc
add_subdirectory(calc)
(3) 在 src/calc/
文件夹下面,创建它的 CMakeLists.txt
。
src/calc/CMakeLists.txt
:
# 生成加法静态库
add_library(addition STATIC addition.cpp)
(4) 在 src/CMakeLists.txt
链接生成的静态库addition
:
src/CMakeLists.txt
:
cmake_minimum_required(VERSION 3.10)
project(demo VERSION 1.1)
#设置C++ 11 标准
set(CMAKE_CXX_STANDARD 11)
#设置输出文件位置
# 设置运行时目标文件(exe、dll)的输出位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 设置存档目标文件(lib、a)的输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
#设置一个名为 FOO_ENABLE 的开关
option(FOO_ENABLE "Enable Foo" ON)
#如果开关是打开的 就设置 FOO_STRING 的值为 "foo"
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
# 输入是foo.h.in文件,输出是 foo.h
configure_file(foo.h.in foo.h)
add_subdirectory(calc)
add_executable(demo main.cpp)
# 链接生成的加法静态库 addition
target_link_libraries(addition)
# 把 build 目录加到
target_include_directories(demo
PUBLIC "${PROJECT_BINARY_DIR}")
(5) 编译运行。
在 main.cpp
调用这个加法库中的 add
函数:
#include<iostream>
#include "foo.h"
#include "addition.h"
using namespace std;
int main(){
cout<<"Hello World!!!"<<endl;
//打印 foo.h 中定义的这个宏
cout<<"FOO_STRING : "<<FOO_STRING<<endl;
//调用 addition 库中的 add 函数
cout<<"10 + 20 = "<<add(10,20)<<endl;
return 0;
}
接着再构建项目,编译。
又显示 addition.h
找不到了。
所以我们还要把 src/calc
目录加到扫描头文件的那个命令中:
# 把 build 目录 和 src/calc 加到
target_include_directories(demo
PUBLIC "${PROJECT_BINARY_DIR}"
PUBLIC "${PROJECT_SOURCE_DIR}/calc")
接着再编译就能成功了。
编写动态 substract
库
(1) 首先在 src/calc/CMakeLists.txt
中添加生成 动态substract
库的命令。
# 生成加法静态库
add_library(addition STATIC addition.cpp)
# 生成减法动态库
add_library(substract SHARED substract.cpp)
(2) 在 src/CMakeLists.txt
中添加 链接 动态substract
库的命令。
cmake_minimum_required(VERSION 3.10)
project(demo VERSION 1.1)
#设置C++ 11 标准
set(CMAKE_CXX_STANDARD 11)
#设置输出文件位置
# 设置运行时目标文件(exe、dll)的输出位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 设置存档目标文件(lib、a)的输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
#设置一个名为 FOO_ENABLE 的开关
option(FOO_ENABLE "Enable Foo" ON)
#如果开关是打开的 就设置 FOO_STRING 的值为 "foo"
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
# 输入是foo.h.in文件,输出是 foo.h
configure_file(foo.h.in foo.h)
add_subdirectory(calc)
add_executable(demo main.cpp)
#链接加法静态库
target_link_libraries(demo PUBLIC addition)
#链接减法动态库
target_link_libraries(demo PUBLIC substract)
# 把 build 目录加到
target_include_directories(demo
PUBLIC "${PROJECT_BINARY_DIR}"
PUBLIC "${PROJECT_SOURCE_DIR}/calc")
(3) 在 main.cpp
中调用 sub
方法。
#include<iostream>
#include "foo.h"
#include "addition.h"
#include "substract.h"
using namespace std;
int main(){
cout<<"Hello World!!!"<<endl;
//打印 foo.h 中定义的这个宏
cout<<"FOO_STRING : "<<FOO_STRING<<endl;
cout<<"10 + 20 = "<<add(10,20)<<endl;
cout<<"10 - 20 = "<<sub(10,20)<<endl;
return 0;
}
(4) 开始编译、运行。
运行成功!!!
2.使用外部的库
编写乘法静态库
(1) 我们打开 CLion ,生成一个乘法的静态库 multipy.a
。
选择创建一个 C++ 14 的静态库。
把 library.cpp , library.cpp
改为 multiply.h , multiply.cpp
。
(2) 把 multiply.h , multiply.cpp
的内容改为如下:
multiply.h
#ifndef LIBRARY_MULTIPLY_H
#define LIBRARY_MULTIPLY_H
int mul(int,int);
#endif //LIBRARY_MULTIPLY_H
multiply.cpp
#include "multiply.h"
int mul(int a,int b){
return a * b;
}
(3) 把 CMakeLists.txt
的内容改为如下:
cmake_minimum_required(VERSION 3.10)
project(library)
set(CMAKE_CXX_STANDARD 14)
# 生成 multiply 静态库
add_library(multiply STATIC multiply.cpp)
(4) build
(5) 在 demo
文件下面创建一个 other_lib
文件夹,把生成的静态库 multiply.a
和头文件 multiply.h
都放入到这个 other_lib
文件夹下。
(6) 把 other_lib
添加到扫描的路径中,在 demo/src/CMakeLists.txt
添加如下命令。
# 把 build 目录加到
#因为 other_lib 是和 src 同级的
target_include_directories(demo
PUBLIC "${PROJECT_BINARY_DIR}"
PUBLIC "${PROJECT_SOURCE_DIR}/calc"
PUBLIC "${PROJECT_SOURCE_DIR}/../other_lib")
(7) 链接静态库 multiply.a
,在 demo/src/CMakeLists.txt
添加如下命令。
#链接外部 乘法静态库
#因为外部库我们可能不知道名字,所以这里直接使用绝对路径
target_link_libraries(demo PUBLIC "G:/demo/other_lib/libmultiply.a")
(8) 在 main.cpp
中调用 mul
函数。
#include<iostream>
#include "foo.h"
#include "addition.h"
#include "subtract.h"
#include "multiply.h"
using namespace std;
int main(){
cout<<"Hello World!!!"<<endl;
//打印 foo.h 中定义的这个宏
cout<<"FOO_STRING : "<<FOO_STRING<<endl;
cout<<"10 + 20 = "<<add(10,20)<<endl;
cout<<"10 - 20 = "<<sub(10,20)<<endl;
cout<<"10 * 20 = "<<mul(10,20)<<endl;
return 0;
}
(9) 编译、运行
运行成功!!!
编写除法动态库
(1) 在刚才 CLion 打开的项目中,创建 division.cpp , division.h
两个文件。
(2) 在 division.cpp , division.h
两个文件中定义和实现 divide(int,int)
方法。
division.h
#ifndef LIBRARY_DIVISION_H
#define LIBRARY_DIVISION_H
int divide(int,int);
#endif //LIBRARY_DIVISION_H
division.cpp
int divide(int a,int b){
return a / b;
}
(3) 在 CMakeLists.txt
添加命令,生成除法动态库 libdivision.dll
。
# 生成 division 动态库
add_library(division SHARED division.cpp)
(4) build
(5) 把动态库文件 libdivision.dll
和头文件 division.h
移动到 other_lib
文件夹下面。
(6) 在 demo/src/CMakeLists.txt
添加命令,链接 libdivison.dll
动态库。
target_link_libraries(demo PUBLIC G:/demo/other_lib/libdivision.dll)
(7) 在 main.cpp
中调用 divide(int,int)
函数。
#include<iostream>
#include "foo.h"
#include "addition.h"
#include "subtract.h"
#include "multiply.h"
#include "division.h"
using namespace std;
int main(){
cout<<"Hello World!!!"<<endl;
//打印 foo.h 中定义的这个宏
cout<<"FOO_STRING : "<<FOO_STRING<<endl;
cout<<"10 + 20 = "<<add(10,20)<<endl;
cout<<"10 - 20 = "<<sub(10,20)<<endl;
cout<<"10 * 20 = "<<mul(10,20)<<endl;
cout<<"20 / 10 = "<<divide(20,10)<<endl;
return 0;
}
(8) 编译、运行。
这时报错了,显示找不到 libdivision.dll
文件。
解决方法:把 libdivision.dll
复制到 demo.exe
同文件夹下面。
此时再编译运行就可以了。
五、安装
把可执行文件、头文件、动态静态库安装到指定位置。
如果我们不做任何设置,默认安装,就会安装到 "C:/Program Files (x86)/demo"
这个位置下。
在 build/cmake_install.cmake
文件下可以看到默认位置。
(1) 安装在 src/calc
目录下生成的库文件 和 头文件。
src/calc/CMakeLists.txt
:
#安装库文件
install(TARGETS addition DESTINATION lib)
install(TARGETS subtract DESTINATION bin)
#安装头文件
install(FILES addition.h DESTINATION include)
install(FILES subtract.h DESTINATION include)
(2) 安装在 src
目录下生成的库文件 和 头文件。
src/CMakeLists.txt
:
#安装
#安装可执行文件,可执行文件安装到 bin 目录下
install(TARGETS demo DESTINATION bin)
#安装动态静态库;动态库安装到 bin 目录下,静态库安装到 lib 目录下
install(FILES "G:/demo/other_lib/libmultiply.a" DESTINATION lib)
install(FILES "G:/demo/other_lib/libdivision.dll" DESTINATION bin)
#安装头文件,头文件都安装到 include 目录下
install(FILES "G:/demo/other_lib/multiply.h" DESTINATION include)
install(FILES "G:/demo/other_lib/division.h" DESTINATION include)
(3) 安装到 build/install
目录下,install
文件夹会被创建出来。
先把 build
这个文件夹的内容删除,再重新构建。
编译、安装
安装的命令:
#安装到 demo/install 目录下 , intall目录会自动创建
cmake --install . --prefix ..\install
(4) 验证是否安装成功
进入到 demo/intall/bin
目录下。
打开 cmd
,执行 demo.exe
。
执行成功,说明安装成功了!!!