没有章法,没有计划,想到什么写什么,所以今天准备入手CMake,也是对自己的一个查漏补缺,对于学纯C/C++的,还是有很大帮助滴!好,废话不多说,进入主题
Make 工具因遵循不同的规范和标准,执行的Makefile的格式也是不同。 主流的Make工具包括:
每个平台都有自己的工具,则带来了很大的平台兼容性问题。CMake是一种跨平台的编译工具。
一个简单的样例:
#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 的指令是不区分大小写的,写作 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})
相比于构建单个文件,我们额外使用了两个指令:
接下来进入 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)
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提供一个称为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" )
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()