ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu 18.04上vcpkg安装报错:CMake版本过旧与工具链升级全记录

Ubuntu 18.04上vcpkg安装报错:CMake版本过旧与工具链升级全记录

用vcpkg在Ubuntu 18.04上装库,第一次碰到CMake版本报错的时候,我真的愣了一下。明明vcpkg一套流程走下来,bootstrap也过了,./vcpkg install gdal一执行,configure阶段直接挂掉,报错说CMake 3.10.2太旧。这台老服务器是Ubuntu 18.04,系统自带cmake 3.10.2,gcc 7.5.0,都是几年前的默认版本。问题不在于库本身,而在于工具链没跟上。这篇文章把我整个排查和解决过程完整记录下来,包括怎么升级CMake、怎么顺手升级GCC、怎么清理vcpkg的残留缓存,以及Ubuntu 18.04上其他几个我踩过的坑。如果你也在这类旧系统上用vcpkg,建议把这篇存下来当操作手册翻。

1. 问题背景:老服务器上的vcpkg出师不利

1.1 为什么还在用Ubuntu 18.04

你可能想问,都什么年代了还在用Ubuntu 18.04。现实是,很多线上服务器、内部构建机、科研实验室的机器,跑的就是这些老系统。系统Manager不轻易升级,项目代码又依赖老环境,动一发牵全身。与其折腾系统迁移,不如在新系统上补工具链。我这次遇到的情况就是这样:机器是Ubuntu 18.04.6 LTS,内核和系统库都挺稳定,就是开发工具链太老。这台机器上要做C++数据处理,打算用vcpkg管理第三方库,结果第一道门槛就卡在CMake上。

Ubuntu 18.04刚发布那个年代,系统仓库里的CMake是3.10.2,GCC是7.5.0。对当时的C++项目来说够用,但放在现在就明显吃力了。vcpkg里大量port已经要求CMake 3.19甚至更高,不少新版本的库也要求编译器完整支持C++17。所以在这个系统上用vcpkg,不能直接用默认环境,必须先手工升级工具链。

1.2 我为什么设置VCPKG_FORCE_SYSTEM_BINARIES

vcpkg默认会在首次使用时,从发布站点下载预编译的CMake和Ninja到downloads/tools目录,然后用自己的工具链构建库。这本是最省心的模式,但我在的这台服务器没法直接访问外网的发布站点,下载总是超时或失败。为了让vcpkg能跑起来,我选择了配置环境变量VCPKG_FORCE_SYSTEM_BINARIES=1,强制vcpkg使用系统里已有的CMake和Ninja。

这个环境变量的本意是方便离线环境或特殊网络条件的用户,让vcpkg不要下载预编译工具,转而依赖系统PATH里的cmake和ninja。如果系统里的版本够新,这套配置完全没问题。问题就在于Ubuntu 18.04的系统CMake是3.10.2,实在太老了。后来安装库时,vcpkg用这个老CMake去配置那些新库的构建脚本,直接触发版本检查报错。说实话,这个坑最坑的地方是表面看是vcpkg坏了,实际是系统工具链太旧。

1.3 直观报错是什么样

当时执行./vcpkg install gdal,日志刷到最后,卡在configure阶段,核心报错大概长这样:

CMake Error at CMakeLists.txt:30 (cmake_minimum_required): CMake "3.10.2" is below required version "3.19"

第一反应以为是vcpkg的port写错了,或者缓存出问题。后来仔细一看,报错里的CMake版本3.10.2就是系统的/usr/bin/cmake版本。也就是说vcpkg真的按VCPKG_FORCE_SYSTEM_BINARIES的设置去用了系统CMake,而系统CMake满足不了这个库的最低版本要求。

2. 报错现场与初步排查

2.1 核对工具链版本

遇到这种报错,不要急着改代码或者删缓存,先把环境摸清楚。我第一件事就是确认当前shell里的各个工具版本:

which cmake cmake --version which ninja ninja --version gcc --version g++ --version

结果很明显:

  • /usr/bin/cmake指向CMake 3.10.2
  • ninja存在,版本1.8.2
  • gcc/g++7.5.0

这三个加在一起,基本宣告了直接装新库会困难重重。老版Ninja 1.8.2倒还能凑合用,但CMake 3.10.2对现代CMake语法支持差得太多,尤其是很多库的CMakeLists里用了cmake_minimum_required(VERSION 3.19)这种做法,直接拒绝执行。

2.2 检查vcpkg工具链逻辑

我当时的怀疑点是:vcpkg里是不是有一个环境变量或triplet设置让vcpkg误用了系统cmake。于是翻了下scripts/cmake/vcpkg_configure_cmake.cmake,核心逻辑其实不复杂,vcpkg在构建库时会从自己的downloads/tools目录找预编译CMake,找不到或强制系统模式时才回退到系统CMake。我设置了VCPKG_FORCE_SYSTEM_BINARIES=1,所以它直接选择了系统CMake。

我又试着临时取消这个环境变量,让vcpkg正常走默认下载逻辑,结果它一直在尝试连接外网下载工具,几次都卡住超时。这样反而更费时间。所以问题的解决方向就落在了另外一条路上:既然系统工具链版本太低,那就升级系统工具链,让vcpkg在强制系统模式下也有足够高的版本可以用。

2.3 结论:是CMake版本卡住,不是库本身的问题

排查走到这一步,结论已经比较清晰:库本身没问题,vcpkg的port也没问题,纯粹是系统CMake版本太旧。而且这个卡点还很顽固,因为vcpkg在configure阶段做版本检查,版本不满足就直接abort,不会给你临时绕过或者降级的机会。

这里我也顺带想到了编译器版本的问题。CMake过关之后,GCC 7.5对C++17特性和部分C++20特性的支持也不完整。GDAL这类库虽然保守,但新版本内部已经用了不少C++17特性,GCC 7.5可能让编译阶段报出一堆莫名其妙的模板错误。为了避免装到一半又翻车,我决定把GCC也一并升级。

3. 治本方案:源码编译升级系统CMake

3.1 安装编译CMake所需的系统依赖

CMake本身可以用源码编译安装,这个过程并不复杂,但依赖要先装齐。我用的是CMake 3.29.4这个版本,它对系统库的依赖主要是OpenSSL和cURL,以及其他基础库。执行:

sudo apt-get update sudo apt-get install -y libssl-dev libcurl4-openssl-dev zlib1g-dev

如果你还想让CMake支持Qt GUI界面,可以加装qtbase5-dev,但服务器上完全没有必要,装不带GUI的版本更省心。另外,源码编译会用到Make和GCC,这些Ubuntu 18.04默认就有,不用额外装。我踩过一个小坑:如果只装libssl-dev而不装libcurl4-openssl-dev,CMake编译时虽然不会报错,但生成出来的cmake -P脚本在处理远程下载测试时可能有问题。所以按上面几个包装齐是最稳的。

3.2 下载、编译与安装

从官网下载源码包。记住选择稳定版,不要下载rc版本,避免一些隐蔽bug:

wget https://cmake.org/files/v3.29/cmake-3.29.4.tar.gz tar -zxvf cmake-3.29.4.tar.gz cd cmake-3.29.4

接下来是关键的配置步骤。CMake源码目录下有一个bootstrap脚本,类似Linux内核的configure。指定并行数编译可以快不少:

./bootstrap --parallel=4 make -j4 sudo make install

bootstrap这步会做很多自我检测,五六分钟很正常,别以为卡死了。整个过程如果顺利,最后CMake会安装到/usr/local/bin目录下,和系统自带的/usr/bin/cmake共存。默认情况下,/usr/local/bin在/usr/bin前面,所以新版本会自动优先生效。

3.3 校验版本是否真的生效

安装完成后,立刻验证一下,同时让shell重新查找命令路径:

hash -r which cmake cmake --version

正常情况下,which cmake应该指向/usr/local/bin/cmake,版本显示3.29.4。我还专门用/usr/bin/cmake --version对比了一下,确认老的3.10.2确实还在,只是不再被优先调用。

这里有一个细节值得注意:如果你用sudo make install安装CMake后,在你自己的用户shell里执行cmake --version还是老版本,先别急着说安装失败。用hash -r清掉shell的命令哈希缓存,再退出重开一个终端,一般就能看到新版本。实在不行,检查一下PATH环境变量,确保/usr/local/bin在/usr/bin前面。

4. 未雨绸缪:顺手升级GCC编译器

4.1 Ubuntu 18.04升级GCC的正确姿势

Ubuntu 18.04自带的GCC是7.5.0,完整支持C++17其实还差一些边角。为了避免vcpkg后续编译某些新库时在模板实例化和标准库特性上报错,我决定把默认编译器升级到GCC 9。GCC 9对C++17支持已经很完整,对C++20的个别特性也有部分支持,和vcpkg里大部分port的兼容性非常好。

在Ubuntu 18.04上,用Toolchain test PPA是最省事的方式。这个PPA专门提供较新的工具链版本,相当于是为老系统补充新版编译器的官方渠道。执行:

sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y sudo apt-get update sudo apt-get install -y gcc-9 g++-9

需要注意,Ubuntu 18.04的Python环境和add-apt-repository可能没装。如果提示命令不存在,先执行sudo apt-get install -y software-properties-common补上。此外,添加PPA后apt-get update有时会因为网络问题报错,多试几次,或者切换到国内的镜像源,不然这个PPA一直拉不下来。

4.2 切换默认编译器版本

安装完gcc-9和g++-9后,系统不会自动把默认编译器切换过去,需要手动设置。我用了update-alternatives,这是Debian/Ubuntu系管理多版本工具链的标准方式:

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100

之后执行:

gcc --version g++ --version

都会显示9.x版本。我还保留了原来的GCC 7.5没删,万一有的老项目需要旧编译器,可以通过update-alternatives再切回去。

这里要提醒一句:update-alternatives的优先级参数很重要,我给gcc-9设置了100,比gcc-7的默认优先级高,系统才会默认选gcc-9。如果以后还要装gcc-10或gcc-12,记得调整优先级,否则切了半天还是老版本,白折腾。

4.3 验证编译器环境

升级完编译器,我确认了一下C++标准库的头文件路径和版本,避免出现头文件与libstdc++库不匹配的情况。实际项目里常见的一种坑是这样的:GCC切换了,但CMake的缓存里还记录着旧编译器路径,编译时用了gcc-9却链了老的libstdc++,导致各种诡异链接错误。

验证方法很简单,执行一个小程序或者直接echo | gcc -E -dM -看宏定义。我在实践里是直接编译了一个用了C++17特性的测试文件:

#include <filesystem> #include <iostream> int main() { std::filesystem::path p("/tmp"); std::cout << p.filename() << std::endl; return 0; }

GCC 7.5对<filesystem>的支持还不成熟,能编译通过说明环境和标准库是匹配的。测试通过后,我才继续下一步。

5. 重新安装库并接入项目

5.1 清理失败遗留的缓存和构建目录

升级完CMake和GCC后,不能直接重新跑./vcpkg install gdal,因为vcpkg里有几处残留可能干扰新构建:

  • buildtrees目录:存放上一次构建的临时文件,一些configure的产物已经按老CMake生成了,留着会污染新构建
  • packages目录:未完成安装的半成品
  • downloads目录:如果之前下载过损坏的源码包,也可能被复用

我习惯性地手动清理:

rm -rf buildtrees/gdal packages/gdal

如果更彻底一点,可以把整个buildtrees目录删掉,代价是下次所有库都要重新编译,比较费时间。我这次只删了对应库的目录,够用。还有一种做法是执行./vcpkg remove gdal --recurse先移除依赖关系,但我没采用,因为当前没有安装成功,没有完整依赖树可卸。

5.2 重新执行安装

清理之后,重新执行安装命令:

./vcpkg install gdal

这次configure阶段能顺利通过,不再报CMake版本过低。随后是漫长的编译过程,GDAL这种库体积大,依赖也不少,在4核机器上编译了大概20多分钟。期间会有很多warning,只要不是error就继续跑。编译完成时,vcpkg会输出类似这样的信息:

gdal:x64-linux is installed.

到此问题基本解决。顺便验证一下新的工具链是否全程生效:在vcpkg编译日志里,configure阶段会显示用的CMake路径和编译器版本,我特意看了一眼,确认是/usr/local/bin/cmake和gcc-9。这一步很有必要,不然你永远不知道它到底用的哪个版本。

5.3 在项目里集成安装好的库

vcpkg装完库之后,最常用的集成方式是在CMake项目里指定工具链文件:

cmake -B build -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake

CMakeLists里通过find_package找到对应库:

find_package(GDAL REQUIRED) target_link_libraries(my_app PRIVATE GDAL::GDAL)

需要注意,find_package(GDAL REQUIRED)里的组件名、target名在不同版本的库之间可能变化,具体以vcpkg安装完成后打印的提示文档为准。我这次就是调用gdal_info确认了库能正常工作,再用一个测试程序验证了链接通过。整个过程走完,vcpkg在老系统上就算真正能用了。

6. 问题速查:Ubuntu 18.04老环境还有哪些坑

6.1 GLIBCXX版本不兼容

这是Ubuntu 18.04上很经典的一个问题。有时候你升级了GCC和CMake,但运行vcpkg或某些预编译工具时,还会报类似这样的错误:

/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26' not found

原因很简单:预编译工具(比如新版的ninja)是用较新版本GCC编译出来的,运行时需要更高版本的libstdc++。而系统默认的libstdc++来自GCC 7.5,里面的GLIBCXX版本符号不够新。

解决办法是升级系统的libstdc++,仍然可以用Toolchain test PPA提供的gcc-9来更新:

sudo apt-get install -y libstdc++-9-dev

装完后用下面的命令检查是否包含所需版本的符号:

strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX

如果你看到GLIBCXX_3.4.26或更高版本,说明问题解决。这个坑很容易被忽略,因为它不像CMake版本那么直观。我建议在Ubuntu 18.04上只要升级GCC,就顺手把libstdc++-9-dev装上,免得后续装一个库就报一次错。

6.2 系统依赖和pkg-config问题

很多库在configure阶段会依赖系统里的第三方库,比如zlib、openssl、libpng、pkg-config等。Ubuntu 18.04系统自带的这些库版本偏老,但大部分情况下能兼容vcpkg编译出来的新库。我遇到的比较典型的问题是缺少pkg-config,一些port在查找系统库时会调用pkg-config来获取编译参数,没有这个命令直接报错。

尽早装上:

sudo apt-get install -y pkg-config

另外,如果你安装的库依赖新版OpenSSL,这里要特别提醒:Ubuntu 18.04自带的OpenSSL版本还算新,但vcpkg很多时候不会用系统的OpenSSL,而是自己编译一份。这样做的好处是可控,坏处是编译时间变长,磁盘占用变大。如果不想让vcpkg编译OpenSSL,可以尝试设置VCPKG_USE_SYSTEM_OPENSSL=1,但效果因库而异,我通常不折腾这个配置,直接让vcpkg自己来。

6.3 要不要用容器作为Plan B

如果老服务器上的问题实在太多,或者有些二进制依赖版本要求非常新,短期又不想继续在系统层面折腾,可以考虑用Docker跑一个较新版本的Ubuntu开发环境。比如用Ubuntu 20.04或22.04的容器,直接在容器里安装vcpkg,工具链版本都正常,能省掉大量编译和排查时间。

但容器方案也有代价:如果你的vcpkg缓存和编译产物要长期保留,Docker容器一旦删除就全没了,需要额外挂载数据卷。另外,有些库需要访问宿主机设备或内核模块,容器里可能没法正常工作。对我这次的场景来说,升级系统工具链更直接,因为服务器后续还要承担生产任务,容器只是临时方案。如果你只是做一次性编译,容器会更快;如果是长期维护项目,建议一步到位升级系统工具链。

6.4 VCPKG_FORCE_SYSTEM_BINARIES的副作用

最后专门说下VCPKG_FORCE_SYSTEM_BINARIES=1这个变量。它确实能让vcpkg在离线或受限网络环境下工作,但副作用也明显:vcpkg不再保证使用自己的工具链版本,完全依赖系统环境。一旦系统的CMake或Ninja版本太旧,就可能出现我前面遇到的问题。

如果你在使用这个变量,建议把系统工具链里的CMake、Ninja、GCC都升级到vcpkg的可接受范围内再继续。另外,这个变量只是影响工具的来源,不影响vcpkg安装的库的版本。不要在配置里把这个变量永久写进~/.profile或CMake工具链文件里,只在需要离线构建时临时设置,更容易控制环境变量作用域。我这次就是因为之前把变量写进了shell配置文件,导致排查时一度忘记是它引起的,绕了些弯路。

7. 一点实际操作中的体会

这次在Ubuntu 18.04上折腾vcpkg,最终解决问题的不是某个神奇参数,而是老老实实把工具链版本补齐。CMake升级到3.29,GCC升级到9,libstdc++同步更新,vcpkg安装库就顺畅多了。现在回想起来,最值得记录的经验有三条:

第一,遇到vcpkg安装报错,先别急着删缓存、改配置,先确认当前shell里cmake、ninja、gcc这些实际生效的版本,做到心里有数。很多诡异问题根源就是工具链新旧混搭。

第二,升级工具链要同步到位,不要只升CMake不升GCC。vcpkg里不少库的configure和编译阶段对编译器特性都有要求,CMake过了,GCC也可能成为下一道坎。一次性把GCC和libstdc++都升好,能避免反复折腾。

第三,老系统的工具链升级路径其实很有规律:CMake走源码编译,GCC走Toolchain test PPA。这两条路都成熟可靠,按步骤操作不会出大问题。不要把时间花在寻找所谓的“一键脚本”或“兼容包”上,那才是真的浪费时间。

如果你也有一台Ubuntu 18.04服务器要跑vcpkg,希望这篇记录能帮你少走几步弯路。工具链版本这种基础问题,看起来不起眼,但卡住的时候是真的让人头大。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进