cmake编译单/多文件
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cmake编译单/多文件相关的知识,希望对你有一定的参考价值。
参考技术A 在该路径下会生成一个文件夹(CMakeFiles),三个文件(Makefile, CMakeCache.txt, cmake_install.cmake)以及一个程序(addition)此时的文件目录结构为
该项目该依赖于MPI,GDAL和cereal库。MPI和GDAL库自行编译
1.配置各种编译的时候,可以使用set设置,更多详情,请自行搜索。
2.头文件的包含请使用include_directories。
3.搜索源文件请使用aux_source_directory。
4.第三方库的查找使用 find_package。例如我们想找GDAL, 那么 find_package(GDAL), 它会在 /usr/share/cmake/Modules 文件中的FindGDAL.cmake文件中去找GDAL的各种信息。前提是GDAL 是make install的, FindGDAL.cmake中才会有GDAL的各种信息。否则的话,我们需要set自行制定GDAL的相关信息。
5.第三方库的链接用target_link_libraries。
注意,也可以将该项目中的某个文件夹编译成静态库,然后在于其余源文件链接,可以参考: https://blog.csdn.net/cliukai/article/details/90670243
简单的多文件编译: https://blog.csdn.net/cliukai/article/details/90670243
有第三方库的文件编译: https://blog.csdn.net/fb_help/article/details/79593037
CMake 入门大法,学会此技能,暴击率附加100%
前言
没有章法,没有计划,想到什么写什么,所以今天准备入手CMake,也是对自己的一个查漏补缺,对于学纯C/C++的,还是有很大帮助滴!好,废话不多说,进入主题
Make 工具因遵循不同的规范和标准,执行的Makefile的格式也是不同。 主流的Make工具包括:
- GNU Make
- QT的 qmake
- 微软的MS nmake
- BSD的 pmake
每个平台都有自己的工具,则带来了很大的平台兼容性问题。CMake是一种跨平台的编译工具。
准备阶段:
- 安装cmake
- 编写 CMake 配置文件 CMakeLists.txt
基本流程:
- 执行 cmake PATH 或者 ccmake PATH 命令,将 CMakeLists.txt 文件转化为所需要的Makefile文件。 其中 PATH 为 CMakeLists.txt 所在的目录
- 执行 make 命令,编译原码生成可执行程序,或者库文件
单目录,单文件
一个简单的样例:
#CMake的最低成本要求
cmake_ mininum requtred (VERIONS 2.8)
#项目信息
project(Demo)
#指定生成目标
add_executable(Demo demo.cc)
语法规则:
- 命令、空格、注释组成
- 命令是不区分大小写的
- 符号 # 后面内容为注释
- 参数之间使用 空格 分隔
例子说明:
使用GCC编译
假设现在我们希望编写一个函数来实现安全的 int 类型加法防止数据溢出,这个源文件没有任何依赖的源码或静态库:
// safe_add.cpp
#include <iostream>
#include <memory>
#define INT_MAX 2147483647
#define ERROR_DATA_OVERFLOW 2
int SafeIntAdd(std::unique_ptr<int> &sum, int a, int b)
{
if (a > INT_MAX - b)
{
*sum = INT_MAX;
return ERROR_DATA_OVERFLOW;
}
*sum = a + b;
return EXIT_SUCCESS;
}
int main()
{
int a, b;
std::cin >> a >> b;
std::unique_ptr<int> sum(new int(1));
int res = SafeIntAdd(sum, a, b);
std::cout << *sum << std::endl;
return res;
}
我们可以直接使用一句简单的 gcc 命令 来编译这个文件并执行:
[joelzychen@DevCloud ~/cmake-tutorial]$ g++ main.cc -g -Wall -std=c++11 -o SafeIntAdd
[joelzychen@DevCloud ~/cmake-tutorial]$ ./SafeIntAdd
2100000000 2100000000
2147483647
使用 cmake 构建
如果要使用 cmake 来生成 makefile 的话我们需要首先新建一个 CMakeLists.txt 文件,cmake 的所有配置都在这个文件中完成,CMakeLists.txt 中的内容大致如下:
cmake_minimum_required(VERSION 3.10)
project(SafeIntAdd)
set(CMAKE_CXX_COMPILER "c++")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS -g -Wall)
message(STATUS "CMAKE_CXX_FLAGS: " "${CMAKE_CXX_FLAGS}")
string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
message(STATUS "CMAKE_CXX_FLAGS: " "${CMAKE_CXX_FLAGS}")
add_executable(SafeIntAdd main.cc)
其中有一些基础的 cmake 指令,它们的含义如下:
- cmake_minimum_required:cmake 的最低版本要求
- project:指定项目的名称
- set:设置普通变量,缓存变量或环境变量,上面例子中的
- add_executable:使用列出的源文件构建可执行文件
有几个需要注意的点:
-
cmake 的指令是不区分大小写的,写作 CMAKE_MINIMUM_REQUIRED 或 cmake_minimum_required,甚至是 cmAkE_mInImUm_rEquIrEd(不建议)都是可以的
-
在使用 set 指令指定 CMAKE_CXX_FLAGS 的时候通过空格来分隔多个编译选项,生成的 CMAKE_CXX_FLAGS 字符串是 “-g;-Wall”,需要用字符串替换将分号替换为空格
-
message 可以在构建的过程中向 stdout 输出一些信息,上面例子中的输出信息为:
bash -- CMAKE_CXX_FLAGS: -g;-Wall -- CMAKE_CXX_FLAGS: -g -Wall
-
类似于 bash 脚本,在 CMakeLists.txt 中输出变量时要使用 “${CMAKE_CXX_FLAGS}” 的形式,而不能直接使用 CMAKE_CXX_FLAGS
编辑好 CMakeLists.txt 之后,我们可以新建一个 build 目录,并在 build 目录下使用 cmake 来进行构建,构建成功的话再使用 make 来进行编译和链接,最终得到 SafeAdd 这个可执行文件:
[joelzychen@DevCloud ~/cmake-tutorial]$ mkdir build/
[joelzychen@DevCloud ~/cmake-tutorial]$ cd build/
[joelzychen@DevCloud ~/cmake-tutorial/build]$ cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc - works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- CMAKE_CXX_FLAGS: -g;-Wall
-- CMAKE_CXX_FLAGS: -g -Wall
-- Configuring done
-- Generating done
-- Build files have been written to: /home/joelzychen/cmake-tutorial/build
[joelzychen@DevCloud ~/cmake-tutorial/build]$ make
Scanning dependencies of target SafeIntAdd
[ 50%] Building CXX object CMakeFiles/SafeIntAdd.dir/main.cc.o
[100%] Linking CXX executable SafeIntAdd
[100%] Built target SafeIntAdd
[joelzychen@DevCloud ~/cmake-tutorial/build]$ ./SafeIntAdd
2100000000 2100000000
2147483647
单目录,多文件
在实际项目中,一般不会只有一个demo.cc 源码文件,常为一个目录下多个源文件。假设目录结构如下:
./Demo
|--main.CC
|--foo.cc
|--foo.h
此时的 CMakeLists.txt 内容可以更新为如下:
#CMake的最低版本要求
cmake_minimun_required (VERIONS 2.8)
#项目信息
project(Demo)
#指定生成目标
add_executable(Demo demo.cc foo.cc)
即只需要在 add_executable 里把依赖的 foo.cc 源文件添加进来即可。
但引入另一个问题: 新增的源文件越来越多,总不能一个个手动加进来吧?
cmake中有 aux_source_directory 命令,会查找指定目录下所有源文件,并存放到指定的变量名中:
# CMake的最低版本要求
cmake_minimun_required (VERIONS 2.8)
#项目信息
project(Demo)
#查找当前目录下所有源文件
aux_source_directory(. DIR_SRCS)
#指定生成目标
add_executable(Demo ${DIR_SRCS})
例子说明
使用 GCC 编译
假设现在我们希望将加法函数放到单独的文件中去,并在 main 函数所在的源文件中包含这个文件:
// main.cc
#include "math.h"
#include "error_code.h"
#include <iostream>
int main()
{
int a{ 0 }, b{ 0 }, c{ 0 };
std::cin >> a >> b >> c;
int sum{ 0 };
int ret_val = SafeAdd(sum, a, b, c);
std::cout << sum << std::endl;
return ret_val;
}
// util/math.h
#ifndef UTIL_MATH_H
#define UTIL_MATH_H
#include "error_code.h"
#include <limits>
template<typename ValueType>
ValueType ValueTypeMax(ValueType)
{
return std::numeric_limits<ValueType>::max();
}
template<typename ValueType>
int SafeAdd(ValueType &sum)
{
return exit_success;
}
template<typename ValueType, typename ...ValueTypes>
int SafeAdd(ValueType &sum, const ValueType &value, const ValueTypes &...other_values)
{
int ret_val = SafeAdd<ValueType>(sum, other_values...);
if (ret_val != exit_success)
{
return ret_val;
}
if (sum > ValueTypeMax(value) - value)
{
sum = ValueTypeMax(value);
return error_data_overflow;
}
sum += value;
return exit_success;
}
#endif
// definition/error_code.h
#ifndef DEFINITION_ERROR_CODE_H
#define DEFINITION_ERROR_CODE_H
constexpr int exit_success = 0;
constexpr int exit_failure = 1;
constexpr int error_data_overflow = 2;
#endif
我们可以在使用 GCC 编译的时候使用 -I 参数指定头文件所在的目录:
[joelzychen@DevCloud ~/safe_add]$ g++ -g -Wall -std=c++11 -Ilib -Idefinition -o SafeAdd main.cc
[joelzychen@DevCloud ~/safe_add]$ ./SafeAdd
20000 50000 80000
150000
使用 cmake 构建
cmake_minimum_required(VERSION 3.10)
project(SafeIntAdd)
set(CMAKE_CXX_COMPILER "c++")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS -g -Wall)
string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
include_directories(lib/ definition/)
aux_source_directory(./ SOURCE_DIR)
add_executable(SafeIntAdd ${SOURCE_DIR})
相比于构建单个文件,我们额外使用了两个指令:
- include_directories:添加多个头文件搜索路径,路径之间用空格分隔;如果将 lib 和 definition 目录都添加到到搜索路径的话,在 include 的时候就不需要使用相对路径了
- aux_source_directory:在目录中查找所有源文件,并将这些源文件存储在变量 SOURCE_DIR 中;需要注意这个指令不会递归包含子目录
接下来进入 build 目录进行构建:
[joelzychen@DevCloud ~/cmake-tutorial/build]$ rm -rf *
[joelzychen@DevCloud ~/cmake-tutorial/build]$ cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc - works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/joelzychen/cmake-tutorial/build
[joelzychen@DevCloud ~/cmake-tutorial/build]$ make
Scanning dependencies of target SafeIntAdd
[ 50%] Building CXX object CMakeFiles/SafeIntAdd.dir/main.cc.o
[100%] Linking CXX executable SafeIntAdd
[100%] Built target SafeIntAdd
[joelzychen@DevCloud ~/cmake-tutorial/build]$ ./SafeIntAdd
2000000000 1900000000
2147483647
多目录,多文件
针对一个项目中包含了多了层级目录,且每个目录下都包含一些源文件。若目录结构如下:
. /Demo
|--main. CC
|-- utils
|-- foo. cc
|-- foo.h
我们需要分别在Demo 和utils 目绿下各自编写一个CMakeLists.txt文件。
为了方便,可以先将 utils 目录里的文件编译成静态库,再由 main 函数调用。
根目录中的CMakeLists.txt:
# CMake的最低版本要求,如果不满足则报错
cmake_minimum_required (VERIONS 2.8 FATAL ERROR)
#项目信息
project(Demo)
#添加math子月录
add_subdirectory(utils)
#指定生成目标
add_executable(Demo main.cc)
#添加链接库
target_link_libraries(Demo utils)
- add_ subdirectory 表示处理子目录下的 CMakeLists .txt 和源代码
- target_Link _Libraries 表示 main 执行文件需要链接一个名为
utils的链接库
utils目录中的CMakeLists .txt
#查找当前目录下的所有源文件、并保存到DIR_LIB_SRCS变量中
aux_source_directory(. DIR_LIB_SRCS)
#生成链按库
add_library(utils ${DIR_LIB_SRCS})
add_library 会将所有源文件编译为静态链接库
其他编译选项
如下是一个项目的CMakeLists.txt:
cmake_minimun_required(VERSION 2.8)
project(Demo)
#定一个开关选项,支持cmake时通过 -DUSE_MYUTILS=OFF 指定
option(USE_MYUITLS "whether use customized math" ON)
#自定义分支逻辑
if(USE_MYUITLS)
include_directories("{PROJECT_SOURCE_DIR}/utiLs" )
add_subdirectory(utils)
set(EXTRA_LIBS ${EXTRA_LIBS} utils)
endif(USE_MYUITLS)
#查找目录下所有源文件
aux_source_directory(. DIR_SRCS)
#添加执行文件
add_executable(Demo ${DIR_SRCS})
#链按静态库
target_Link_libraries(Demo ${EXTRA_LIBS})
关于 option 的生效机制,这里详细解释下。如main.cc中的代码:
#include <stdio.h>
#include <stdlib.h>
#include "config.h" / 此头文件是cnake自动生成的
#ifdef USE_PYUTILS
#include "utils/foo. h"
#else
#include <foo.h> // 假设标准库有foo.h头文件
#endif
为了打通 CMakeLists.txt 一键便携式配置,我们需要编写一 个。
config.h.in 文件:
#cmakedefine USE_MYUTILS
若为 0FF 时,则 config.h 的内容为:
/* #undef USE_MYUTILS */
安装和测试
cmake 支持安装和测试,通过在生成 Makefile 后,使用 make install 和make test 来执行。
接上述样例,首先在 utils/CMakeLists.txt 中加上如下内容:
#指定utils库的安装路径
install(TARGETS utils DESTINATION bin)
install(FILES utils.h DESTINATION include)
在Demo/CMakeLists.txt 添加如下内容:
#指定安装路径
install(TARCETS Demo DESTINATION bin)
install(FILES "S[PROJECT_BINARY}/config.h" DESTINATION include)
原理&流程:
- cmake编译产出的 Demo 文件和库 libUtils.o 文件将会被复制到 /usr/local/bin 中
- 头文件 utils.h 和config.h 则会被赋值到/use/local/include 中
- 可以通过 CHKAE_INSTALL_PREFIX 修改默认安装的根目录/usr/local
关于测试,CMake提供一个称为CTest的测试工具,通过 add_test 命令添加: .
#启用测试
enable_testing()
#测试程序是否成功运行,arg* 为函数接收的参数
add_test(test_run Demo arg1 arg2)
add_test(test_usage Demo)
set_tests_properties(test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .....")
add_test(test_ result Demo 10 2)
#测试输出的结果是否包含字符串"is 100"
set_tests_properties(test_ result PROPERTIES PASS_ REGULAR EXPRESSION "is 100" )
支持gdb
CMake支持gdb的方式很简单,只需指定Debug 模式下开启 -g ,一个简单的样例如下
set(CMAKE_BUILD_TYPE "Debug")
# debug模式 下编译选项
se(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -00 -Wall -ggdb')
# release模式 下编译选项
set(CHAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -03 wall')
小结
-
CMake的语法主要以命令、空格、参数来组成
-
可以通过 set(<variable> <value>) 设置变量的值
-
if语法.
if( scondition>) <commands> elseif(<condition>) # optional block, can be repeated <Commands> else() # optional block <commands> endif()
-
for语法
# usage 1: foreach(<loop_ var> <items>) <commands> endforeach() # usage 2: foreach(<loop_ var> RANGE <stop>)
-
while语法
while( <condition> ) <commands> endwhile()
以上是关于cmake编译单/多文件的主要内容,如果未能解决你的问题,请参考以下文章