C/C++编译工具:cmake
基本使用
CMake 是一个跨平台的开源构建工具,用于管理软件构建流程。它使用一个名为 CMakeLists.txt 的文本文件来描述构建过程。以下是一个简单的 CMakeLists.txt 文件的示例,用于构建一个简单的 C++ 程序:
# 指定 CMake 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 指定项目名称
project(MyProject)
# 指定生成可执行文件
add_executable(my_program main.cpp)
复制代码
在这个示例中,我们指定了 CMake 的最低版本要求,并且指定了项目的名称。然后,我们使用 add_executable
命令来告诉 CMake 创建一个名为 my_program
的可执行文件,该可执行文件由 main.cpp
文件构建而成。
为了使用 CMake 构建项目,我们需要执行以下步骤:
创建一个 CMakeLists.txt 文件来描述构建过程。
在项目根目录下创建一个 build 文件夹,并进入该文件夹。
运行 cmake
命令来生成构建系统所需的文件。例如:cmake ..
。
运行生成的构建系统,例如使用 make
命令进行构建。
通过这些步骤,CMake 将会根据 CMakeLists.txt 文件生成相应的构建系统文件,并且帮助我们完成项目的构建过程。
基本语法
command(arg1 arg2 ...) # 运行命令
set(var_name var_value) # 定义变量,或者给已经存在的变量赋值
command(arg1 ${var_name}) # 使用变量
# 控制语句
IF(expression) COMMAND1(ARGS)
ELSE(expression) COMMAND2(ARGS)
ENDIF(expression)
# expression
IF(var) # 不是空, 0, N, NO, OFF, FALSE, NOTFOUND 或 _NOTFOUND时,为真
IF(NOT var) # 与上述条件相反。
IF(var1 AND var2) # 当两个变量都为真是为真。
IF(var1 OR var2) # 当两个变量其中一个为真时为真。
IF(COMMAND cmd) # 当给定的cmd确实是命令并可以调用是为真
IF(EXISTS dir) # 目录名存在
IF(EXISTS file) # 文件名存在
IF(IS_DIRECTORY dirname) # 当dirname是目录
IF(file1 IS_NEWER_THAN file2) # 当file1比file2新,为真
IF(variable MATCHES regex) # 符合正则
# 循环
WHILE(condition) COMMAND1(ARGS) // ...
ENDWHILE(condition)
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(one_dir ${SRC_LIST}) MESSAGE(${one_dir})
ENDFOREACH(onedir)
复制代码
基本操作
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
add_library(mod1 SHARED mod1.c mod1_func.c) # 生成动态库 libmod1.so
add_library(mod2 STATIC mod2.c) # 生成静态库 libmod2.a
复制代码
add_subdirectory: 在子文件夹添加了 library 或者 executable 之后,在上层目录添加 subdirectory, 也可以在同一个 CMakeList.txt 中使用
target_link_libraries: 设置要链接的库文件的名称 具体的动态链接库文件.so, 在生成的可执行文件或动态库中连接进去其他的库
target_link_libraries(<target> [item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)
target_link_libraries(main mod1)
复制代码
include_directories: 添加头文件目录 g++选项中的-I 参数的作用,也相当于环境变量中增加路径到 CPLUS_INCLUDE_PATH 变量的作用
link_directories 添加需要链接的库文件目录。 它相当于 g++命令的-L 选项的作用,也相当于环境变量中增加 LD_LIBRARY_PATH 的路径的作用
link_libraries 添加需要链接的库文件路径
下面是一个使用 CMake 构建的示例,其中包括了 target_link_libraries,link_directories,include_directories,add_executable 和 add_library 等关键部分,以生成一个可执行文件和一个动态库。
cmake_minimum_required(VERSION 3.5)
project(MyProject)
# 设置编译选项
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Ofast -Wfatal-errors -pthread -fPIC -O3 -mavx")
# 添加动态库的源文件
set(SOURCE_FILES_LIB mylib.cpp)
add_library(mylib SHARED ${SOURCE_FILES_LIB})
# 添加可执行文件的源文件
set(SOURCE_FILES main.cpp)
# 添加包含目录
include_directories(include)
# 添加链接目录
link_directories(${CMAKE_SOURCE_DIR}/lib)
# 添加可执行文件
add_executable(myapp ${SOURCE_FILES})
# 链接动态库
target_link_libraries(myapp mylib)
复制代码
假设项目的目录结构如下所示:
MyProject/
├── CMakeLists.txt
├── include/
│ └── mylib.h
├── src/
│ ├── main.cpp
│ └── mylib.cpp
└── lib/
└── libmylib.so
复制代码
在项目根目录中执行以下命令来生成构建文件和编译项目:
mkdir build
cd build
cmake ..
make
复制代码
这将生成一个名为 myapp 的可执行文件和一个名为 libmylib.so 的动态库。
指定安装目录
我们进行 cmake 命令,会执行 make 命令进行编译,最后如果有需要的话会执行 make install 将编译好的库或者头文件安装到指定的未知。
在 CMake 中,可以使用install
命令来设置安装相关信息,包括安装目标、安装路径等。以下是一个详细的例子,展示了如何在 CMake 中设置安装相关信息:
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 添加可执行文件
add_executable(MyExecutable main.cpp)
# 设置安装路径
set(CMAKE_INSTALL_PREFIX /usr/local) # 安装到/usr/local目录下
# 安装可执行文件
install(TARGETS MyExecutable
DESTINATION bin) # 将可执行文件安装到bin目录下
# 安装头文件
file(GLOB HEADER_FILES "*.h")
install(FILES ${HEADER_FILES}
DESTINATION include/myproject) # 将头文件安装到include/myproject目录下
# 安装额外文件
install(FILES README.md
DESTINATION share/myproject) # 将README.md文件安装到share/myproject目录下
复制代码
在上面的例子中,我们首先使用add_executable
命令添加了一个可执行文件MyExecutable
。然后使用set
命令设置了安装路径为/usr/local
。接下来使用install
命令分别安装了可执行文件、头文件和额外文件到指定的安装路径下。
需要注意的是,安装路径可以根据实际需要进行调整。在使用 CMake 时,可以根据具体的项目需求来设置不同的安装路径和安装目标。
区分开发版与发布版
在 CMake 中,可以通过MAKE_BUILD_TYPE
在确定不通类型的棒棒,常用的构建类型包括 Debug、Release、MinSizeRel 和 RelWithDebInfo。
举例来说,假设我们有一个 CMakeLists.txt 文件,其中需要根据不同的构建类型来设置编译选项。我们可以使用MAKE_BUILD_TYPE
来指定构建类型,然后根据不同的构建类型来设置不同的编译选项。
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 根据构建类型设置编译选项
if (MAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
elseif (MAKE_BUILD_TYPE STREQUAL "Release")
add_definitions(-DNDEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
# 添加源文件
add_executable(MyExecutable main.cpp)
复制代码
在上面的例子中,根据不同的构建类型,我们设置了不同的预处理宏和编译选项。当构建类型为 Debug 时,会定义DEBUG
宏并启用调试信息;当构建类型为 Release 时,会定义NDEBUG
宏并开启优化。
这样我们就可以 main.cpp 中,通过条件编译的方式,确定是哪些 debug 模型的需要编译的,而在 release 发布版本不需要编译,形如:
#ifndef NDEBUG
printf("author: %s, release_date: %s\n", AUTHOR, RELEASE_DATE ); // 只在开发版本编译
#endif
复制代码
cmake -DMAKE_BUILD_TYPE=Debug ..
cmake -DMAKE_BUILD_TYPE=Release ..
复制代码
大杀器 find_package
find_package 是 CMake 中用于查找和加载第三方库的命令。它用于在系统中查找指定的软件包,并将其路径或库的相关信息导入到 CMake 中,以便在项目中使用。
要配置 find_package,你需要在 CMakeLists.txt 文件中使用 find_package 命令,并提供要查找的软件包的名称。通常情况下,你还需要指定软件包的版本号。
下面是一个使用 find_package 的简单例子:
假设你想在 CMake 项目中使用 OpenCV,你需要在 CMakeLists.txt 文件中添加以下内容:
# 查找OpenCV包
find_package(OpenCV 4.0 REQUIRED)
# 如果找到OpenCV包,将其包含路径和链接库添加到项目中
if(OpenCV_FOUND)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(your_project_name ${OpenCV_LIBS})
endif()
复制代码
find_package 语法为:FIND_PACKAGE( <name> [version] [EXACT] [QUIET] [NO_MODULE] [ [ REQUIRED | COMPONENTS ] [ componets... ] ] )
在这个例子中,find_package 命令用于查找 OpenCV 4.0,并将其导入到项目中。如果找到了 OpenCV 包,将其包含路径添加到项目的 include 路径中,并将其链接库添加到项目的链接库中。
请注意,在实际项目中,你可能还需要根据你的项目结构和依赖项的不同做一些适当的调整。
那 find_package 怎么知道从哪里去查找相关依赖库呢?查找的目录路径:
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH
其中,PATH中的路径如果以bin或sbin结尾,则自动回退到上一级目录。
找到根目录后,cmake会检查这些目录下的
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)
cmake找到这些目录后,会开始依次找<package>Config.cmake或Find<package>.cmake文件。找到后即可执行该文件并生成相关链接信息。
最重要的一个是PATH。由于/usr/bin/在PATH中,cmake会自动去/usr/(lib/<arch>|lib|share)/cmake/<name>*/寻找模块
另外一个比较重要的是<package>_DIR。我们可以在调用cmake时将这个目录传给cmake。由于其优先级最高,因此cmake会优先从该目录中寻找,这样我们就可以随心所欲的配置cmake使其找到我们希望它要找到的包。而且除上述指定路径外,cmake还会直接进入<package>_DIR下寻找。如我在3rd_parties目录下编译了一个OpenCV,那么执行cmake时可以使用
OpenCV_DIR=../../3rd-party/opencv-3.3.4/build/ cmake ..
复制代码
多个 CMakeLists.txt
在一个复杂的项目中,通常会以模块化的方式来组织项目的框架,统一一个如 main.cpp 做为入口程序(如 ffmpeg)。那么如果所有的程序的编译信息都写在一个 CMakeLists.txt 会很难维护。cmake 里运行多个 CMakeLists.txt,每个模块有自己的 CMakeList 负责编译。如下面的结构
.
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── lib
│ │ ├── libmod1.so
│ │ └── mo2_lib
│ │ ├── libmod2.a
│ │ └── Makefile
│ └── Makefile
├── CMakeLists.txt
├── main.c
└── mod1
├── CMakeLists.txt
├── mod1.c
├── mod1_func.c
├── mod1_func.h
├── mod1.h
└── mod2
├── CMakeLists.txt
├── mod2.c
└── mod2.h
复制代码
具体的,当使用 CMake 构建一个项目时,add_subdirectory
命令可以用来包含子目录中的 CMakeLists.txt 文件,从而将子目录中的源代码文件添加到主项目中。下面是一个使用add_subdirectory
的简单示例:
假设我们有以下项目目录结构:
project/
CMakeLists.txt
main.cpp
subdirectory/
CMakeLists.txt
helper.cpp
helper.h
复制代码
在主项目的 CMakeLists.txt
文件中,可以包含子目录的 CMakeLists.txt 文件使用 add_subdirectory
命令。示例如下:
# 主项目的 CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(MyProject)
add_executable(my_app main.cpp)
# 包含subdirectory中的CMakeLists.txt
add_subdirectory(subdirectory)
# 将子目录中的源文件添加到主项目中
target_sources(my_app PRIVATE subdirectory/helper.cpp)
复制代码
在子目录的 CMakeLists.txt
文件中,可以定义子目录中的源文件,示例如下:
# 子目录的 CMakeLists.txt
# 将子目录中的源文件添加到一个库中
add_library(helper_lib helper.cpp helper.h)
复制代码
在这个例子中,add_subdirectory
命令被用来引入子目录中的 CMakeLists.txt 文件,并将子目录中的源文件添加到主项目中。
打印信息
cmake 可以通过 message 来打印一些调试信息
cmake_minimum_required(VERSION 2.8)
project(find_package_learning)
find_package(OpenCV 3 REQUIRED)
message(STATUS "OpenCV_DIR = ${OpenCV_DIR}")
message(STATUS "OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}")
message(STATUS "OpenCV_LIBS = ${OpenCV_LIBS}")
include_directories(${OPENCV_INCLUDE_DIRS})
add_executable(opencv_test opencv_test.cpp)
target_link_libraries(opencv_test ${OpenCV_LIBS})
复制代码
评论