用CMake和VSCodium建立一个构建系统

282 阅读4分钟

本文是关于C/C++开发的开源DevOps工具系列的一部分。如果你从一开始就把你的项目建立在一个强大的工具链上,你将从更快、更安全的开发中受益。除此之外,你将更容易让其他人参与到你的项目中。在这篇文章中,我将准备一个基于CMakeVSCodium的C/C++构建系统。像往常一样,相关的示例代码可以在GitHub上找到。

我已经测试了本文中描述的步骤。这是一个适用于所有平台的解决方案。

为什么是CMake?

CMake是一个构建系统生成器,为你的项目创建Makefile。第一眼听起来很简单,但第二眼就会觉得相当复杂。在高处,你定义了项目的各个部分(可执行文件、库)、编译选项(C/C++标准、优化、架构)、依赖关系(头文件、库)以及文件层面的项目结构。这些信息在文件CMakeLists.txt 中使用一种特殊的描述语言提供给CMake。当CMake处理这个文件时,它会自动检测你系统上安装的编译器,并创建一个工作的Makefile。

此外,CMakeLists.txt 中描述的配置可以被许多编辑器读取,如QtCreator、VSCodium/VSCode或Visual Studio。

示例程序

我们的示例程序是一个简单的命令行工具。它接受一个整数作为参数,并在从1到所提供的输入值的范围内输出随机洗牌的数字。

$ ./Producer 10
3 8 2 7 9 1 5 10 6 4 

在我们的可执行程序的main() 函数中,我们只是处理输入参数,如果没有提供一个值(或一个不能处理的值),则退出程序。

producer.cpp

int main(int argc, char** argv){
    if (argc != 2) {
        std::cerr << "Enter the number of elements as argument" << std::endl;
        return -1;
    }
    int range = 0;
    try{
        range = std::stoi(argv[1]);
    }catch (const std::invalid_argument&){
        std::cerr << "Error: Cannot parse \"" << argv[1] << "\" ";
        return -1;
    }
    catch (const std::out_of_range&) {
        std::cerr << "Error: " << argv[1] << " is out of range";
        return -1;
    }
    if (range <= 0) {
        std::cerr << "Error: Zero or negative number provided: " << argv[1];
        return -1;
    }
    std::stringstream data;
    std::cout << Generator::generate(data, range).rdbuf();
}

实际的工作是在Generator中完成的,它被编译并作为静态库连接到我们的Producer 可执行文件。

Generator.cpp

std::stringstream &Generator::generate(std::stringstream &stream, const int range) {
    std::vector data(range);
    std::iota(data.begin(), data.end(), 1);
    std::random_device rd;
    std::mt19937 g(rd());
    std::shuffle(data.begin(), data.end(), g);
    for (const auto n : data) {
        stream << std::to_string(n) << " ";
    }
    return stream;
}

函数generate 接受一个指向std::stringstream的引用和一个整数作为参数。根据整数range ,在1到n的范围内制作一个整数的向量,然后进行洗牌。然后将洗牌后的向量中的值转换成字符串并推送到stringstream 。该函数返回与作为参数传递的相同的stringstream

顶层的CMakeLists.txt

顶层的CMakeLists.txt是我们项目的入口。在子目录中可能有几个CMakeLists.txt 文件(例如,与项目相关的库或其他可执行文件)。我们开始一步一步地浏览顶层的CMakeLists.txt

第一行告诉我们CMake的版本,这是处理文件所需要的,项目名称和它的版本,以及预定的C++标准。

cmake_minimum_required(VERSION 3.14)
project(CPP_Testing_Sample VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

我们用下面这行告诉CMake去查找子目录Generator 。这个子目录包括构建Generator 库的所有信息,并包含一个自己的CMakeLists.txt 。我们很快就会讨论这个问题。

add_subdirectory(Generator)

现在我们来看看一个绝对的特殊功能。CMake模块。加载模块可以扩展CMake的功能。在我们的项目中,我们加载了FetchContent模块,它使我们能够在CMake运行时下载外部资源,在我们的例子中是GoogleTest

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/bb9216085fbbf193408653ced9e73c61e7766e80.zip
)
FetchContent_MakeAvailable(googletest)

在下一部分中,我们做了通常在普通Makefile中会做的事情。指定要构建的二进制文件、它们相关的源文件、应该被链接的库以及编译器可以找到头文件的目录。

add_executable(Producer Producer.cpp)
target_link_libraries(Producer PUBLIC Generator)
target_include_directories(Producer PUBLIC "${PROJECT_BINARY_DIR}")

通过下面的语句,我们让CMake在构建文件夹中创建一个名为compile_commands.json 的文件。这个文件暴露了项目中每一个文件的编译选项。在VSCodium中加载,这个文件告诉IntelliSense功能在哪里找到头文件(见文档)。

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

最后一部分定义了我们项目的测试。该项目使用之前加载的GoogleTest框架。整个单元测试的主题将是另一篇文章的一部分。

enable_testing()
add_executable(unit_test unit_test.cpp)
target_link_libraries(unit_test gtest_main)
include(GoogleTest)
gtest_discover_tests(unit_test)

库级CMakeLists.txt

现在我们看一下包含同名库的子目录Generator 中的CMakeLists.txt文件。这个CMakeLists.txt 要短得多,除了单元测试相关的命令外,它只包含两条语句。

add_library(Generator STATIC Generator.cpp Generator.h)
target_include_directories(Generator INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

通过add_library(...) 我们定义了一个新的构建目标。静态Generator 库。通过语句target_include_directories(...) ,我们将当前子目录添加到其他构建目标的头文件搜索路径中。我们还指定这个属性的范围为:INTERFACE 。这意味着该属性将只影响针对该库进行链接的构建目标,而不是该库本身。

开始使用VSCodium

有了CMakeLists.txt 中的信息,像VSCodium这样的IDE就可以对构建系统进行相应的配置。如果你还没有使用VSCodium或VS Code的经验,这个例子项目是一个很好的起点。首先,去他们的网站,为你的系统下载最新的安装包。打开VSCodium并导航到扩展标签。

为了正确地构建、调试和测试该项目,搜索以下扩展并安装它们。

Searching extensions

如果还没有做,点击开始页面的Clone Git Repository来克隆仓库。

Clone Git repository

或者通过手动输入。

git clone https://github.com/hANSIc99/cpp_testing_sample.git

之后,通过键入查看标签devops_1

git checkout tags/devops_1

或者点击分支按钮(红框),从下拉菜单(黄框)中选择该标签。

Select devops_1 tag

一旦你在VSCodium内打开版本库的根文件夹,CMake Tools 扩展程序就会检测到CMakeLists.txt 文件,并立即扫描你的系统,寻找合适的编译器。现在你可以点击屏幕底部的Build按钮(红色方框),开始构建过程。你也可以通过点击底部的区域(黄框)标记来改变编译器,它显示了当前活动的编译器。

Build compiler

要开始调试Producer 可执行文件,点击调试器符号(黄框),从下拉菜单中选择调试制作人(绿框)。

Starting the debugger

如前所述,Producer 可执行程序希望将元素的数量作为一个命令行参数。该命令行参数可以在文件中指定.vscode/launch.json.

Command-line arguments

好了,你现在可以建立和调试项目了。

总结

感谢CMake,无论你运行的是什么操作系统,上述步骤都应该是有效的。特别是有了CMake相关的扩展,VSCodium成为一个强大的IDE。我没有提到VSCodium的Git集成,因为你已经可以在网上找到很多资源。我希望你能看到,提供一个合适的CMake配置,可以让别人更容易构建、使用和贡献你的项目。在未来的文章中,我将介绍单元测试和CMake的测试工具ctest