当前位置: 首页 > 工具软件 > Vala_CMake > 使用案例 >

CMake 入门大法,学会此技能,暴击率附加100%

司寇高洁
2023-12-01

前言

没有章法,没有计划,想到什么写什么,所以今天准备入手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 指令,它们的含义如下:

  1. cmake_minimum_required:cmake 的最低版本要求
  2. project:指定项目的名称
  3. set:设置普通变量,缓存变量或环境变量,上面例子中的
  4. add_executable:使用列出的源文件构建可执行文件

有几个需要注意的点:

  1. cmake 的指令是不区分大小写的,写作 CMAKE_MINIMUM_REQUIRED 或 cmake_minimum_required,甚至是 cmAkE_mInImUm_rEquIrEd(不建议)都是可以的

  2. 在使用 set 指令指定 CMAKE_CXX_FLAGS 的时候通过空格来分隔多个编译选项,生成的 CMAKE_CXX_FLAGS 字符串是 “-g;-Wall”,需要用字符串替换将分号替换为空格

  3. message 可以在构建的过程中向 stdout 输出一些信息,上面例子中的输出信息为:

    bash -- CMAKE_CXX_FLAGS: -g;-Wall -- CMAKE_CXX_FLAGS: -g -Wall
    
  4. 类似于 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})

相比于构建单个文件,我们额外使用了两个指令:

  1. include_directories:添加多个头文件搜索路径,路径之间用空格分隔;如果将 lib 和 definition 目录都添加到到搜索路径的话,在 include 的时候就不需要使用相对路径了
  2. 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

我们需要分别在Demoutils 目绿下各自编写一个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.hconfig.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()
    
 类似资料: