CMake
CMake
我们先建立几个文件:
//head.h
#pragma once
int add(int val1, int val2);
int sub(int val1, int val2);
//add.cpp
#include "head.h"
int add(int val1, int val2)
{
return val1 + val2;
}
//sub.cpp
#include "head.h"
int sub(int val1, int val2)
{
return val1 - val2;
}
//main.cpp
#include <iostream>
#include "head.h"
int main()
{
std::cout << add(10, 20) << std::endl;
std::cout << sub(20, 10) << std::endl;
return 0;
}
那么,我们怎么用cmake呢?
首先,先创建一个名叫CMakeLists.txt
的文件,再在里面添加如下内容:
cmake_minimum_required(VERSION 3.23)
project(test)
add_executable(main main.cpp add.cpp sub.cpp)
这样一个最基本的cmake文件就写好了。
下面来解释一下这3个命令分别是啥意思:
1、cmake_minimum_required(VERSION 3.23)
:这个命令就是指定使用cmake的最低版本。
2、project(test)
:定义工程名称,还可以有其他选项,这些选项是可选的。
3、add_executable(main main.cpp add.cpp sub.cpp)
:生成一个可执行程序,这个命令里面的格式为:第一个参数是生成的可执行文件名,后面跟着生成这个可执行文件所需要的文件,每个文件之间可以用空格分隔也可以使用分号分隔。
add_executable(main main.cpp add.cpp sub.cpp)
如果觉得这么写太麻烦了还有另一种方式:cmake支持变量:
set(SRC_LIST main.cpp add.cpp sub.cpp)
:这条指令相当于将这些.cpp文件赋值给名叫SRC_LIST的变量。
add_executable(main ${SRC_LIST})
:使用上面这个变量
指定C++标准
有两种方式。
一、在CMakeLists.txt文件中通过set指定标准:
set(CMAKE_CXX_STANDARD 11)
二、在cmake命令后带上参数:-DCMAKE_CXX_STANDARD=11
指定输出路径
在cmake中指定可执行程序输出的路径:
set(HOME /home/ubuntu/test-cmake)
set(EXECUTABLE_OUTPUT_PATH ${HOME})
推荐使用绝对路径,如果该路径的子目录不存在,CMake会帮我们自动生成,无需自己手动创建。
使用cmake编译项目
格式:cmake [options] <path-to-source>
cmake . # CMakeLists.txt 文件所在路径
make
如果CMakeLists.txt在/home/xxx/code/C/
目录下,而你当前所在的目录是/home/xxx/code/C/aa
,则使用cmake ..
和make
。
需要注意的是,如果CMakeLists.txt所在的路径如果存在CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
这些文件,cmake ..
命令是不会重新生成的,
搜索文件
两种方式:
1、aux_source_directory(${PROJECT_SOURCE_DIR} SRC)
其中,PROJECT_SOURCE_DIR
是一个宏,它就是cmake
命令后跟的路径,也就是CMakeLists.txt所在的路径,而SRC
是一个变量,这个函数相当于把CMakeLists.txt所在路径中所有的文件赋值给SRC
变量。
2、file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
这个函数和aux_source_directory
函数是一样的,只不过参数不同,这个函数第一个参数是是否对路径进行递归搜索,如果不递归搜索,那就GLOB
,如果选择递归就GLOB_RECURSE
,第二个参数是搜索到的文件存储到哪,这里就是存储到SRC
变量中,第三个参数就是要搜索的文件路径以及文件类型,CMAKE_CURRENT_SOURCE_DIR
是一个宏,它和PROJECT_SOURCE_DIR
是一样的,都是CMakeLists.txt所在的路径,后面是文件类型,该例子中是搜索CMakeLists.txt所在的路径中所有.cpp
为后缀的文件。
指定头文件路径
假设现在有这么一种场景,我创建了两个目录,一个include
目录,存放头文件的,另一个目录src
存放源文件的,文件还是最上面那4个文件:head.h add.cpp sub.cpp main.cpp
,而我的CMakeLists.txt文件是这样的:
cmake_minimum_required(VERSION 3.31)
project(test)
# set(SRC_LISTS main.cpp add.cpp sub.cpp)
# aux_source_directory(${PROJECT_SOURCE_DIR} SRC)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
set(CMAKE_CXX_STANDARD 11)
set(EXECUTABLE_OUTPUT_PATH /home/zqq/code/C/CMake_test/aa/bb)
add_executable(main ${SRC})
我执行cmake
命令之后,再执行make
发现报错了,说是找不到头文件,这个很简单,直接在源文件中修改包含头文件的路径就可以了,但是,如果源文件有很多呢,难道也是一个一个改吗,cmake提供了一个函数可以帮助我们指定头文件的路径:include_directories(${PROJECT_SOURCE_DIR}/include)
,这个函数里面的参数就不需要过多解释了,就是存放头文件的目录。
制作库文件
add_library(cale STATIC ${SRC})
。
首先先补充下前提知识:在windows中,静态库文件后缀是.lib
,动态库后缀是.dll
,而在Linux中,静态库文件格式为libXXX.a
,动态库文件格式为libXXX.so
。
在上面这个函数中,第一个参数是去掉前缀后缀的库文件名称,比如上面的例子中,在Linux环境下生成的库文件是libcale.a
,为什么是.a
呢,那时因为后面这个参数,第二个参数取决于你想生成的是静态库还是动态库,静态库为STATIC
,动态库则为SHARED
,第三个参数就是生成的库文件所需要的文件,这个SRC
变量就是上面指定头文件路径中CMakeLists.txt里面的SRC
变量。
指定库文件输出路径
我们可以通过set
函数设置LIBRARY_OUTPUT_PATH
为指定生成的路径,这个宏对应的就是库文件生成路径,如果是生成动态库还可以使用EXECUTABLE_OUTPUT_PATH
这个宏,因为动态库有可执行权限,但是还是不建议使用这个宏,因为不明确生成的到底是可执行程序还是动态库文件。
举例:
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) # 如果后面路径不存在则会自动创建。
如果不指定生成的路径,那它就会默认生成到当前构建目录。
链接静态库文件
link_libraries(cale)
,这个函数里面的参数可以是静态库文件的全称,即libXXX.a
,也可以是去掉前缀后缀之后的文件名。
如果这个静态库文件不在当前目录,还需要指定静态库文件所在的目录,用到的函数:link_directories(${PROJECT_SOURCE_DIR}/lib)
,里面的参数就是静态库文件所在路径。
链接动态库文件
target_link_libraries(main cale)
,第一个参数是指定要加载的库的名字,当然也不是说只能是库文件,也可以是可执行文件或者源文件,第二个参数是public
,其实在第一个参数后面跟着的参数格式是:[public/private/interface] [file]
,默认是[public]
,所以没写,后面跟着编译时要用到的库文件,我这个例子中main
在编译时需要链接libcale.so
或者libcale.a
库。
下面是关于public/private/interfac
分别代表什么意思:
target_link_libraries(a b c)
以下这个属性添加在b、c
前面
public
:代表如果有一个库d
依赖a
,那么d
能访问到b、c
。
private
:代表如果有一个库d
依赖a
,那么d
不能访问到b、c
。
interface
:b、c
不会被链接到a
中,只会导出符号。
在cmake中打印日志信息
用到的函数是message()
,里面有两个参数,第一个是日志等级,如果不写默认最高等级,还有其他等级:
STATUS
:非重要信息。
WARNING
:CMake警告,会继续执行。
AUTHOR_WARNING
:CMake警告(dev),会继续执行。
SEND_ERROR
:CMake错误,但是会继续执行,但是会跳过生成的步骤。
FATAL_ERROR
:CMake错误,终止所有处理过程。
字符串相关操作
1、追加:list(APPEND SRC hello world)
,往SRC
变量中追加hello
和world
字符串。
2、移除:list(REMOVE_ITEM SRC ${PROJECT_SOURCE_DIR}/src/add.cpp)
,在SRC
中移除${PROJECT_SOURCE_DIR}/src/add.cpp)
文件,注意,这里的移除需要绝对路径,单独一个文件名是不行的。
(其他字符串操作我就不赘述了,偷了点懒)。
自定义宏
//test.cpp
#include <iostream>
#define NUMBER 3
int main()
{
#ifdef DEBUG
std::cout << "这是一条调试信息" << std::endl;
#endif
for (int i = 0; i < NUMBER; ++i)
{
std::cout << "hello world" << std::endl;
}
return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(test)
add_executable(test test.cpp)
执行cmake
和make
命令后,执行可执行程序,得到的结果是3行hello world
,这个当然毋庸置疑,想要输出这是一条调试信息
也很简单,直接在源文件中添加宏就可以了,但是如果源文件很多呢,一个一个添加太麻烦,我们可以直接在CMakeLists.txt
中操作:
# 添加宏
add_definitions(-DDEBUG)
函数里面的参数首先前面需要加上-D
之后跟着宏,中间不需要空格。
嵌套的CMake
在一个目录中有一个CMakeLists.txt
,还有一个目录,这个目录里面又有一个CMakeLists.txt
文件,嵌套使用的函数:add_subdirectory()
,里面参数是子目录名称。