尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++包管理新选择:cget轻量级源码编译与CMake集成实践

C++包管理新选择:cget轻量级源码编译与CMake集成实践 1. 项目概述为什么我们需要另一个C包管理工具如果你写过C尤其是在Linux环境下肯定对依赖管理这件事深有体会。从早期的make手动链接库到后来用apt-get、yum安装系统包再到现代的vcpkg、ConanC的包管理生态一直在进化但“开箱即用”的体验似乎总是差那么一口气。我自己在带团队做跨平台C项目时最头疼的就是新成员配置环境光是安装Boost、OpenCV这些依赖就得花上半天还得处理各种版本冲突和编译选项。直到我遇到了cget它用一种极其轻量、非侵入式的方式把这个问题简化了。简单来说cget是一个命令行工具核心功能就是从指定的源比如GitHub仓库、直接的文件URL获取C库然后调用CMake帮你编译安装到本地的一个独立目录里。它不像vcpkg那样需要一个中央仓库也不像Conan那样有复杂的依赖解析和二进制缓存机制。它的哲学是“简单直接”你给我一个能找到CMakeLists.txt的地方我就能把它变成你的项目可用的库。这对于那些依赖特定版本、或者尚未进入主流包管理器的小众库、内部私有库来说简直是救星。最近在配置一些机器学习或图形学项目时经常需要拉取GitHub上最新的研究代码库cget就成了我的首选工具。2. cget的核心设计理念与竞品对比2.1 非侵入式哲学不绑架你的构建系统这是cget最吸引我的地方。像Conan和vcpkg这样的工具功能强大但它们或多或少都需要你“皈依”它们的体系。Conan要求你在项目里写conanfile.txtvcpkg需要你通过CMAKE_TOOLCHAIN_FILE来集成。这意味着你的CMakeLists.txt文件里会留下这些工具的“烙印”项目构建变得与特定工具链绑定。cget走了另一条路。它不修改你的项目CMakeLists.txt。它的工作方式是先把依赖库下载并编译安装到一个独立的目录比如./cget然后你只需要在调用CMake时通过-DCMAKE_PREFIX_PATH参数告诉CMake去这个目录里找包。整个过程你的项目代码和构建脚本是完全干净的不知道cget的存在。这种解耦带来了巨大的灵活性你可以随时换用其他方式管理依赖或者把项目交给一个不用cget的同事他完全可以用自己的方式解决依赖问题项目照样能编译。2.2 与vcpkg、Conan的适用场景对比为了更清晰地理解cget的定位我们可以用一个表格来对比特性cgetvcpkgConan核心模式源码编译非侵入式源码编译/二进制分发需工具链文件二进制优先强依赖管理包来源Git仓库、URL、本地目录官方中央仓库微软维护中央仓库JFrog维护 私有远程 本地缓存依赖解析简单按顺序安装较复杂支持特性features和依赖冲突检测非常复杂支持版本范围、条件依赖、冲突解决二进制兼容性需自行保证同环境编译为特定 triplet如x64-windows预编译核心优势为不同配置os/arch/compiler/build_type预编译集成方式CMAKE_PREFIX_PATHCMAKE_TOOLCHAIN_FILECMakeDepsCMakeToolchain生成器最适合场景1. 依赖特定Git提交/分支2. 内部私有库3. 快速原型测试新库4. 希望构建系统保持纯净1. Windows平台开发2. 依赖大量成熟开源库如Boost, OpenSSL3. 团队统一使用微软生态1. 大型商业项目对二进制依赖和版本有严格要求2. 跨平台、多配置交付3. 需要复杂依赖图和条件编译实操心得我个人的选择策略是开发阶段、研究性质项目、依赖不稳定的库用cget。它的快速迭代和灵活性无可替代。到了产品化、需要稳定发布和团队协同时转向Conan。vcpkg则在Windows环境下特别是搭配Visual Studio时有独特的便捷性。不要把cget看成是Conan的替代品它们是解决不同层面问题的工具。3. 从零开始cget的安装与环境配置3.1 安装cget的几种方式cget本身是一个Python包所以安装它最直接的方式就是通过pip。这保证了它在所有支持Python的平台上都能一致地工作。首选方案使用pip安装pip install cget安装完成后在终端输入cget --help如果能看到帮助信息说明安装成功。我推荐使用pip的--user选项安装到用户目录避免污染系统Python环境pip install --user cget安装后可能需要将用户基础目录下的bin文件夹如~/.local/bin添加到系统的PATH环境变量中。备选方案从源码安装如果你想使用最新的开发版或者pip安装遇到问题可以从GitHub克隆源码安装git clone https://github.com/pfultz2/cget.git cd cget pip install .注意事项确保你的pip指向的是Python 3。在一些系统上pip可能默认指向Python 2。可以使用pip3 install cget来明确指定。另外cget依赖于CMake所以你需要提前在系统上安装好CMake版本最好在3.10以上并确保cmake命令在PATH中可用。3.2 初始化你的项目工作区cget不需要像git init那样的全局初始化。它的工作模式是“按目录管理”。你只需要在项目的根目录下创建一个目录来存放cget安装的包。通常这个目录就叫cget。mkdir my_cpp_project cd my_cpp_project mkdir cget这个cget目录就是你的本地包仓库。所有通过cget install安装的库都会放在这个目录下。你可以把这个目录加入.gitignore因为里面都是编译生成的二进制文件不应该纳入版本控制。4. 核心操作详解安装、使用与管理依赖4.1 安装一个库从GitHub开始假设你的项目需要用到fmt这个优秀的格式化库。用cget安装它非常简单cget install -f ./cget fmtlib/fmt让我们拆解这个命令cget install: 安装命令。-f ./cget:-f或--prefix参数指定安装目录。这里我们指定到当前目录下的cget文件夹。fmtlib/fmt: 这是GitHub仓库的简写格式用户名/仓库名。cget会默认从https://github.com/fmtlib/fmt拉取代码。执行这个命令后cget会做以下几件事克隆fmt仓库的默认分支通常是master或main到临时目录。进入源码目录执行cmake进行配置和编译。默认是Release模式并会安装到./cget目录下。安装完成后在./cget目录下你会看到熟悉的CMake安装结构include/,lib/,share/等。安装特定版本或分支如果你想安装某个特定的标签版本或分支可以使用-t参数# 安装10.1.0版本 cget install -f ./cget -t 10.1.0 fmtlib/fmt # 安装develop分支 cget install -f ./cget -t develop fmtlib/fmt4.2 在CMake项目中使用已安装的库安装好库之后如何在你的项目中使用它呢关键在于CMAKE_PREFIX_PATH这个CMake变量。它告诉CMake在哪些额外的路径下寻找包配置文件PackageNameConfig.cmake或FindPackageName.cmake。假设你的项目结构如下my_cpp_project/ ├── CMakeLists.txt ├── src/ │ └── main.cpp └── cget/ (cget安装目录) ├── include/ ├── lib/ └── ...你的CMakeLists.txt可以这样写cmake_minimum_required(VERSION 3.10) project(MyApp) # 最关键的一步将cget目录添加到CMAKE_PREFIX_PATH list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cget) # 查找包。CMake会在CMAKE_PREFIX_PATH指定的路径中寻找fmt的配置 find_package(fmt REQUIRED) add_executable(my_app src/main.cpp) # 链接库这里使用现代CMake的目标导入方式 target_link_libraries(my_app PRIVATE fmt::fmt)然后用以下命令配置和构建你的项目mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease cmake --build .你会发现CMake成功地在./cget目录下找到了fmt库并且链接无误。整个过程你的CMakeLists.txt没有一行cget相关的特殊代码保持了完全的纯净。4.3 安装来自其他源的库除了GitHubcget还支持直接从URL安装压缩包或者安装本地目录的库。从URL安装压缩包有些项目发布的是源码压缩包如.tar.gz,.zip。cget install -f ./cget https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xzcget会自动下载并解压该压缩包然后进行编译安装。安装本地目录的库这对于开发自己的私有库或者修改了第三方库源码后进行测试非常有用。# 假设你在当前目录下有一个名为mylib的库里面有CMakeLists.txt cget install -f ./cget ./mylib4.4 高级安装选项与配置cget的install命令有很多选项可以定制编译过程这让你能应对各种复杂的库。传递CMake配置选项很多库需要通过CMake选项来启用/禁用功能。使用-D参数可以多次使用来传递。# 安装Catch2测试框架并开启静态库编译 cget install -f ./cget -D BUILD_STATIC_LIBSON catchorg/Catch2指定C/C编译器标志通过-X参数可以传递额外的编译器标志。# 为所有依赖的编译添加调试符号和C17标准 cget install -f ./cget -X CMAKE_CXX_FLAGS-g -stdc17 fmtlib/fmt处理有子模块Submodule的仓库有些Git仓库包含了子模块。使用--recurse-submodules参数让cget在克隆时初始化并更新子模块。cget install -f ./cget --recurse-submodules someuser/somelib-with-submodules实操心得-D和-X参数是调试和适配库的利器。如果一个库安装失败首先去查它的CMake文档看看有没有必须设置的选项。例如安装gtest时我通常会加上-Dgtest_force_shared_crtON来在Windows上避免运行时库冲突。另外建议在项目根目录创建一个cget-requirements.txt文件记录所有安装命令和参数方便团队复用。5. 依赖管理进阶需求文件与工作流5.1 使用需求文件requirements.txt批量安装当项目依赖多个库时一条条手动执行cget install命令既容易出错也不利于重现。cget支持类似Pythonpip的需求文件。创建一个名为cget-requirements.txt名字可以自定的文件内容如下# 注释项目依赖列表 fmtlib/fmt nlohmann/jsonv3.11.2 catchorg/Catch2v3.4.0 -D CATCH_INSTALL_DOCSOFF https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.0.tar.gz每一行代表一个依赖项格式可以是用户名/仓库名用户名/仓库名标签或分支直接的URL后面可以跟-D、-X等安装参数。然后使用一条命令安装所有依赖cget install -f ./cget -r cget-requirements.txtcget会按顺序解析文件中的每一行并执行安装。这极大地简化了项目环境的搭建流程新人只需要克隆代码运行这一条命令所有依赖就绪。5.2 创建可移植的开发与构建环境结合需求文件我们可以打造一个标准化的开发工作流项目初始化开发者克隆项目仓库。一键安装依赖在项目根目录运行cget install -f ./cget -r cget-requirements.txt。构建项目进入build目录用CMAKE_PREFIX_PATH指向./cget进行构建。更新依赖如果需要升级某个库修改cget-requirements.txt中的版本然后重新运行安装命令。cget会重新编译安装该库。为了更自动化我通常会在项目根目录放一个configure.sh或configure.bat脚本#!/bin/bash # configure.sh set -e # 遇到错误退出 PREFIX_DIR./cget REQUIREMENTS_FILEcget-requirements.txt echo Installing dependencies with cget into $PREFIX_DIR... if [ -f $REQUIREMENTS_FILE ]; then cget install -f $PREFIX_DIR -r $REQUIREMENTS_FILE else echo Requirements file $REQUIREMENTS_FILE not found. exit 1 fi echo Dependencies installed. You can now configure CMake with: echo cmake -B build -DCMAKE_PREFIX_PATH\$PREFIX_DIR\ -DCMAKE_BUILD_TYPERelease这样团队成员包括CI/CD系统只需要运行./configure.sh就能获得完全一致的依赖环境。5.3 清理与卸载cget没有内置的卸载命令因为它的安装本质就是向一个目录里复制文件。所以“卸载”就是删除这个目录中对应库的文件。但是手动删除容易出错因为库文件可能分散在include、lib、share等子目录。更安全的方式是清理整个cget目录如果你确定要重装所有依赖直接删除./cget目录是最彻底的。rm -rf ./cget选择性清理如果只想删除某个特定库可以查看该库安装时生成的CMake包配置文件。通常位于./cget/lib/cmake/LibraryName/下。你可以手动删除与此库相关的头文件在include中查找对应目录和库文件在lib中查找对应的.a,.so,.lib,.dll等。但这需要你对库的文件结构比较了解容易有残留。注意事项由于cget是源码编译安装编译过程可能会产生大量的中间文件在build临时目录但安装命令结束后这些中间文件会被自动清理。主要的磁盘占用就是安装目录./cget本身。对于大型库如Boost这个目录可能会很大请注意磁盘空间。6. 实战踩坑与疑难问题排查6.1 常见编译失败问题与解决思路即使有了cget编译第三方库也并非总是一帆风顺。以下是我遇到过的典型问题及解决方法问题1CMake版本过低症状安装失败CMake报错提示需要更高版本的CMake或某个特性如CXX_STANDARD。排查cget会调用系统默认的cmake命令。用cmake --version检查版本。解决升级系统的CMake。可以去CMake官网下载最新版本或者使用包管理器升级如apt upgrade cmake,brew upgrade cmake。问题2依赖缺失症状库配置成功但编译链接时失败报错找不到某个函数或头文件。排查这个库可能本身依赖其他系统库。例如libcurl可能依赖openssl和zlib。cget只管理它命令行指定的依赖不会自动处理系统的间接依赖。解决你需要手动安装这些系统级的依赖。例如在Ubuntu上sudo apt-get install libssl-dev zlib1g-dev然后再重新运行cget install。问题3编译器不兼容或标志冲突症状编译错误提示语法错误、标准不支持或者链接错误。排查可能是库要求的C标准比你环境默认的高或者你通过-X传递的编译器标志与库内部设置冲突。解决明确指定C标准在安装时通过CMake选项传递如-D CMAKE_CXX_STANDARD17。简化编译器标志先去掉所有-X参数看是否能编译通过。如果能再逐一添加标志定位冲突源。检查编译器确保你使用的是库支持的主流编译器如GCC, Clang, MSVC。问题4跨平台问题特别是Windows症状在Linux/macOS上好好的在Windows上编译失败。排查Windows生态复杂常见问题有路径问题Windows路径使用反斜杠和空格可能导致脚本错误。确保cget安装目录路径简单无空格和中文字符。运行时库冲突Windows上静态链接时需确保所有库使用相同的运行时库/MT 或 /MD。通过-X CMAKE_CXX_FLAGS_CONFIG来指定。缺少Windows SDK编译需要Windows头文件和库。解决使用x64 Native Tools Command Prompt for VS 20xx这样的开发者命令行它已经设置好了VC环境。安装时显式指定生成器cget install -f ./cget -G Visual Studio 17 2022 -A x64 ...对于运行时库可以尝试-X CMAKE_CXX_FLAGS/MD(动态链接) 或/MT(静态链接)。6.2 依赖冲突与隔离策略cget本身不解决依赖冲突。如果库A需要fmt版本10库B需要fmt版本9你先后安装两者后安装的会覆盖先安装的可能导致其中一个库无法工作。策略1使用独立的prefix目录为不同的项目或同一个项目中的不同组件使用完全独立的cget目录。这是最彻底、最安全的隔离方式。# 项目A cget install -f ./cget_for_project_a fmtlib/fmt10.1.0 # 项目B cget install -f ./cget_for_project_b fmtlib/fmt9.1.0构建时分别指定不同的CMAKE_PREFIX_PATH即可。策略2利用CMake的find_package机制现代CMake的find_package具有版本查找能力。你可以在项目的CMakeLists.txt中指定需要的版本find_package(fmt 10.1.0 REQUIRED)如果CMAKE_PREFIX_PATH指向的目录里只有9.1.0CMake会报错这能提前发现问题。但这依赖于库的Config.cmake文件是否正确提供了版本信息。实操心得对于小型到中型项目我强烈推荐策略1。虽然多占了一些磁盘空间但换来了绝对的依赖纯净和可重现性。磁盘空间很便宜但调试依赖冲突的时间成本非常高。可以将每个项目的cget目录视为项目的一部分当然要加入.gitignore这样项目归档或迁移时依赖环境是自包含的。6.3 调试cget安装过程如果安装过程出错cget默认的输出信息可能不够详细。你可以通过增加-vverbose参数来获取更详细的日志特别是CMake的配置和编译输出。cget install -f ./cget -v fmtlib/fmt-v参数会让cget打印出它执行的每一个步骤和命令这对于定位问题发生在哪个阶段克隆、配置、编译、安装非常有帮助。通常错误信息会出现在CMake的配置输出或make/ninja的编译输出中。7. 与现有项目及CI/CD集成7.1 将cget引入已有CMake项目如果你已经有一个使用find_package的传统CMake项目集成cget非常简单几乎不需要修改项目代码。在项目根目录创建cget目录和需求文件。将依赖安装到cget目录。在构建时通过CMake参数或环境变量设置CMAKE_PREFIX_PATH。你甚至可以不修改任何构建脚本只在调用CMake时动态指定cmake -B build -DCMAKE_PREFIX_PATH$(pwd)/cget或者通过环境变量在某些CI环境中更方便export CMAKE_PREFIX_PATH/path/to/your/project/cget cmake -B build7.2 在持续集成CI中使用cgetcget的非侵入性和脚本化能力让它非常适合CI/CD环境。以下是一个GitHub Actions工作流的示例片段展示了如何在Linux环境下使用cgetjobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Install CMake and cget run: | sudo apt-get update sudo apt-get install -y cmake python3-pip pip3 install cget - name: Install dependencies with cget run: | mkdir cget cget install -f ./cget -r cget-requirements.txt - name: Configure and Build run: | cmake -B build -DCMAKE_PREFIX_PATH${{ github.workspace }}/cget -DCMAKE_BUILD_TYPERelease cmake --build build --config Release - name: Run Tests run: | cd build ctest --output-on-failure这个工作流清晰地将依赖安装cget和项目构建cmake解耦每一步都独立且可缓存。你可以缓存cget目录以加速后续的CI运行。7.3 性能考量与缓存优化源码编译的缺点是耗时。在CI中反复编译Boost这样的大型库是不可接受的。有几种优化策略利用CI缓存如GitHub Actions的actions/cache将编译好的cget目录缓存起来。关键是为缓存键key包含cget-requirements.txt的内容哈希这样只有当依赖变更时才会触发重新编译。使用预编译的二进制包对于极其庞大且稳定的库可以考虑预先在其他地方编译好然后将整个cget目录打包在CI开始时直接下载解压。这违背了cget的“源码编译”哲学但在追求极致构建速度的生产环境中是务实的选择。分层依赖管理将几乎不变的基础依赖如fmt,spdlog和频繁变动的项目专用依赖分开。为基础依赖创建一个长期有效的缓存。我在实际项目中通常会为cget目录设置一个较长的缓存过期时间比如7天并为cget-requirements.txt文件生成一个哈希值作为缓存键的一部分。这样在依赖没有变化时CI可以直接使用缓存整个构建过程能在几十秒内完成。
返回列表