资讯动态

CMake find_package 双模式解析与找不到库排查

发布时间:2026/9/17 19:26:32 来源:尧图企业网站定制
装库这件事最让人上火的时刻往往不是编译报错而是配置阶段那一行find_package。你明明已经用包管理器把库装好了pkg-config也能查到版本号可 CMake 一跑就甩给你一句Could NOT find Foo。更离谱的是同一份 CMakeLists.txt在同事的机器上一切正常换到你的 Ubuntu 上就翻车——这时候你才会意识到find_package不是一个简单的查找文件函数它背后有一整套搜索约定、两套完全不同的运行模式以及一堆默认值埋着的坑。这篇内容就是围绕find_package这个指令本身展开的。它适合三类人刚接触 CMake、被find_package的各种_DIR、_ROOT、CMAKE_PREFIX_PATH搞晕的新手正在把老工程从 Makefile、Keil 工程迁移到 CMake、需要处理第三方依赖的工程师以及需要给自家库写Config文件、让别人能顺利find_package到自己的库作者。我会从指令的本质讲起一路讲到 Module 模式和 Config 模式的内部结构、版本匹配规则、跨平台差异最后给出一套可以直接照着做的排查链路。1. find_package 不是下载器它只在两个地方做选择先把最容易误解的一点说清楚find_package从来不会帮你下载、安装任何东西。它只做一件事——在磁盘上已经存在的路径里找到某个库的描述文件然后把这个库的头文件路径、库文件路径、编译选项、传递依赖打包成 CMake 能理解的形式交给你。找不到就是找不到它没有能力自己解决问题。1.1 一个几乎人人都会遇到的报错现场假设你在一台 Ubuntu 机器上用系统包管理器装了一个数学计算库 MyMath头文件在/usr/local/include/mymath.h库文件在/usr/local/lib/libmymath.so。然后你写了这样一份 CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(demo CXX) find_package(MyMath REQUIRED) add_executable(demo main.cpp) target_link_libraries(demo PRIVATE MyMath::MyMath)配置阶段直接失败报错大意是Could NOT find MyMath (missing: MyMath_DIR)。这个报错里有两个关键信息第一它没找到 MyMath 的描述文件第二它明确告诉你它缺的是MyMath_DIR而不是libmymath.so。很多人第一反应是去改CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH改完发现毫无作用——因为你把 Config 模式当成了 Module 模式来治。这两种模式的差别是所有find_package问题的分水岭。CMAKE_INCLUDE_PATH、CMAKE_LIBRARY_PATH只对 Module 模式里的find_path、find_library有帮助而 Config 模式压根不看这两个变量它要的是一个目录目录里躺着MyMathConfig.cmake这样的文件。1.2 Module 模式与 Config 模式各管什么地盘Module 模式的流程是这样的find_package(Foo)触发 CMake 去CMAKE_MODULE_PATH和 CMake 自带的Modules目录里找一个名叫FindFoo.cmake的脚本然后执行这个脚本。脚本里通常是一串find_path、find_library、find_program通过暴力搜索各种常见路径凑出一组变量比如FOO_INCLUDE_DIRS、FOO_LIBRARIES最后用find_package_handle_standard_args统一设置Foo_FOUND。Config 模式完全不同。它不执行你写的脚本而是去找库在安装时自己生成的一份文件常见命名是FooConfig.cmake或foo-config.cmake。这份文件由库的构建系统在install阶段生成里面记录了这个库的真实路径、版本号、编译开关、以及它依赖的其他库。因为信息来自库作者所以精度通常远高于通用的 Find 脚本。对比项Module 模式Config 模式查找的文件FindFoo.cmakeFooConfig.cmake、foo-config.cmake文件来源CMake 自带或你自己写库安装时自动生成搜索起点CMAKE_MODULE_PATH、CMake Modules 目录CMAKE_PREFIX_PATH、Foo_DIR、Foo_ROOT等是否看CMAKE_INCLUDE_PATH看不看典型失败提示FindFoo.cmake不存在missing: Foo_DIR信息精度依赖脚本作者的经验由库作者精确描述默认行为是先 Module 后 Config可通过CMAKE_FIND_PACKAGE_PREFER_CONFIG变量翻转CMake 3.15 起可用。也就是说find_package(MyMath)会先去找FindMyMath.cmake找不到再切到 Config 模式。这也是为什么有些库明明装了却因为 CMake 自带一个写得不太好的 Find 脚本反而拿到了错误的路径——CMake 优先用了那个脚本压根没去读库自己的 Config 文件。1.3 搜索顺序决定了你改哪个变量才有效Config 模式的搜索顺序大致是这样越靠前优先级越高命令行或缓存变量Foo_DIR直接指向包含配置文件的目录Foo_ROOTCMake 3.12 起指定一个安装根目录CMAKE_PREFIX_PATH列表中的每个前缀CMAKE_FRAMEWORK_PATH、CMAKE_APPBUNDLE_PATH环境变量PATH中的目录在 Windows 上还会去查注册表系统默认前缀例如/usr/local、/usr、/opt以及 CMake 自己的安装目录在每个前缀下面CMake 会拼出一大堆后缀去尝试比如prefix/、prefix/cmake/、prefix/lib/cmake/Foo/、prefix/share/Foo/cmake/、prefix/lib/x86_64-linux-gnu/cmake/Foo/等等。同时它还会对包名做大小写变形尝试因为有些库习惯装成fooConfig.cmake而不是FooConfig.cmake。我踩过的一个典型坑库装在了/opt/mymath下配置文件在/opt/mymath/lib/cmake/MyMath/MyMathConfig.cmake我以为把CMAKE_PREFIX_PATH设成/opt/mymath/lib就行结果失败。正确做法是设成安装根目录/opt/mymath让 CMake 自己去拼后面的lib/cmake/MyMath这一段。前缀是根不是配置文件所在目录——这个区别看起来小但很多人就是在这里反复试错。2. Module 模式FindFoo.cmake 里究竟该写什么理解了模式划分接下来聊聊自己写 Find 脚本。这件事在两种场景下必须做一是目标库没有提供 Config 文件二是你要找的东西不是标准库比如某个只有可执行文件的工具。官方Modules目录下的脚本虽然多但覆盖面有限遇到私有库只能自己动手。2.1 手写一个 FindMyMath.cmake 的完整过程一个合格的 Find 脚本核心就是用最少的假设把路径找出来并且提供现代 CMake 的导入目标。下面这份是可以直接抄的结构# FindMyMath.cmake find_path(MyMath_INCLUDE_DIR NAMES mymath.h HINTS ${MyMath_ROOT} ENV MyMath_ROOT PATH_SUFFIXES include ) find_library(MyMath_LIBRARY NAMES mymath libmymath HINTS ${MyMath_ROOT} ENV MyMath_ROOT PATH_SUFFIXES lib lib64 ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MyMath REQUIRED_VARS MyMath_LIBRARY MyMath_INCLUDE_DIR VERSION_VAR MyMath_VERSION ) if(MyMath_FOUND AND NOT TARGET MyMath::MyMath) add_library(MyMath::MyMath UNKNOWN IMPORTED) set_target_properties(MyMath::MyMath PROPERTIES IMPORTED_LOCATION ${MyMath_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES ${MyMath_INCLUDE_DIR} ) endif() mark_as_advanced(MyMath_INCLUDE_DIR MyMath_LIBRARY)这里有几个细节值得单独说。HINTS ${MyMath_ROOT} ENV MyMath_ROOT的意思是如果用户设置了MyMath_ROOT变量或同名环境变量优先从这里找但找不到也不会报错会继续走系统默认路径。这是可覆盖但不强制的设计比直接写死路径友好得多。find_package_handle_standard_args的第一个参数决定了最终变量名。如果你写MyMath它设置的是MyMath_FOUND如果写MYMATH设置的就是MYMATH_FOUND。这一点在跨脚本调用时特别容易出问题——上游脚本用if(MYMATH_FOUND)判断你这边设成了MyMath_FOUND条件永远为假然后你会看到库明明找到了链接却失败的诡异现象。2.2 导入目标与老式变量选哪个老式写法是暴露MyMath_INCLUDE_DIRS和MyMath_LIBRARIES两个变量调用方这么用find_package(MyMath REQUIRED) include_directories(${MyMath_INCLUDE_DIRS}) target_link_libraries(demo PRIVATE ${MyMath_LIBRARIES})这种写法能用但问题很多。include_directories是目录级作用域会污染当前目录下所有目标${MyMath_LIBRARIES}只能传递库文件本身传递不了编译定义、C 标准要求、以及这个库自己依赖的其他库。一旦 MyMath 依赖 pthread调用方还得自己再加一个Threads::Threads。现代写法是提供IMPORTED目标也就是上面的MyMath::MyMath。它的好处是全都自动带上包含目录、编译定义、传递依赖、链接时的顺序约束。写完target_link_libraries(demo PRIVATE MyMath::MyMath)就结束了不需要再手动include_directories。建议新写的 Find 脚本一律同时提供导入目标。变量可以保留作为兼容层但主体逻辑走导入目标后续维护成本会低很多。写导入目标时最容易出错的属性是IMPORTED_LOCATION。对于动态库和静态库它填的是.so、.a、.dll、.dylib的完整路径。如果你想区分 Debug 和 Release需要改用IMPORTED_LOCATION_DEBUG和IMPORTED_LOCATION_RELEASE否则在单配置生成器比如 Visual Studio下会拿到同一个路径调试版本链接到 Release 库行为可能和你预期不同。2.3 REQUIRED、QUIET、COMPONENTS 三个开关的真实语义这三个参数看起来简单组合起来行为差异不小。REQUIRED的含义是找不到就报错直接终止配置。它会把原本的NOTFOUND变成SEND_ERROR级别的消息。但要注意在 Module 模式下REQUIRED并不能强制find_package_handle_standard_args忽略缺失项——真正决定是否报错的是那个函数收到的REQUIRED_VARS列表。如果你的脚本里REQUIRED_VARS写漏了一个关键变量即使调用方写了REQUIRED也可能出现报错说找到了实际用的时候路径是空的。QUIET是抑制输出把找到/没找到的信息降级。它和REQUIRED可以同时使用找不到依然报错但找到时不打印任何东西。REQUIRED和QUIET同时关闭时默认行为是找到了打印一行简要信息没找到也打印但不报错。COMPONENTS指定需要的组件比如find_package(Qt6 COMPONENTS Core Widgets REQUIRED)。在 Config 模式下处理组件是配置文件自己的责任通常通过check_required_components宏实现在 Module 模式下则由脚本作者自己解析Foo_FIND_COMPONENTS变量来决定找哪些子模块。两者的行为并不统一所以看到某个库的组件检查不生效时先去翻它的配置文件或 Find 脚本而不是怀疑 CMake 本身。3. Config 模式库作者留下的接头暗号Config 模式的信息精度高但前提是你得知道它在找什么文件、文件里长什么样。理解了文件结构排查速度能快一个数量级。3.1 三种典型失败现场与对应特征第一种是missing: Foo_DIR。这是最明确的信号CMake 连一个名为FooConfig.cmake或foo-config.cmake的文件都没扫到。常见原因是库装在了非标准前缀下或者包名大小写不一致。第二种是找到了文件但报Foo_FOUND为假或者报组件缺失。这时候文件是被读到了问题出在文件内部逻辑比如它内部调用了find_dependency(SomeOtherLib)而这个依赖没找到。这类报错通常会在上面一行显示真正的缺失项。第三种最隐蔽配置完全成功链接时才报undefined reference。原因往往是导入的IMPORTED_LOCATION指向的库架构不对或者你安装的是只有头文件的部分真正的实现库没装上。报错特征大概率原因优先检查missing: Foo_DIR配置文件根本没找到CMAKE_PREFIX_PATH、Foo_DIR找到但Foo_FOUND假内部依赖缺失报错中提到的find_dependency项配置通过、链接失败库文件架构或路径不对ldd、file检查产物3.2 FooConfig.cmake 里到底写了什么库的安装导出的配置文件结构通常长这样# MyMathConfig.cmake include(CMakeFindDependencyMacro) find_dependency(Threads) include(${CMAKE_CURRENT_LIST_DIR}/MyMathTargets.cmake) set(MyMath_VERSION 1.4.2) check_required_components(MyMath)第一行的find_dependency是关键。它的作用和find_package一样但多了两个特性一是会把找不到时的处理交给调用方如果配置失败会正确传播二是会自动加上QUIET和REQUIRED的折射避免重复打印。很多配置文件找到了但依然失败的情况根因就是这一行里的依赖在你的环境里没装。MyMathTargets.cmake是真正定义IMPORTED目标的地方一般由install(EXPORT ...)自动生成里面是一大堆add_library(MyMath::MyMath SHARED IMPORTED)和对应的属性设置。这个文件不要手改改了下次重新安装就没了。版本文件MyMathConfigVersion.cmake是配套的内容大致是这样set(PACKAGE_VERSION 1.4.2) if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() set(PACKAGE_VERSION_COMPATIBLE TRUE) if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif() endif()它做的事就是回答两个问题这个版本够不够新和是不是完全相等。find_package(MyMath 1.2)时CMake 会设定PACKAGE_FIND_VERSION然后执行这个脚本根据它设置的两个布尔值决定接受还是拒绝。3.3 用 HINTS、PATHS、Foo_DIR 精准指路排查阶段最有效的办法是先用命令行变量直接怼进去验证只要指对目录就能工作然后再回头处理搜索路径的问题。# 直接指定配置文件所在目录 cmake -S . -B build -DMyMath_DIR/opt/mymath/lib/cmake/MyMath # 或者指定安装根目录 cmake -S . -B build -DCMAKE_PREFIX_PATH/opt/mymath # 多个前缀用分号隔开 cmake -S . -B build -DCMAKE_PREFIX_PATH/opt/mymath;/opt/otherFoo_DIR和CMAKE_PREFIX_PATH的区别要记住前者必须精确到包含配置文件的目录后者只需要给到安装根目录。临时调试用Foo_DIR最快长期方案应该写进工具链文件或项目文档里用CMAKE_PREFIX_PATH。在脚本内部find_package的HINTS和PATHS也可以加搜索位置区别是HINTS会被CMAKE_FIND_USE_*系列开关影响而PATHS默认是硬搜索但优先级最低在NO_DEFAULT_PATH关闭时系统路径依然会被查找。HINTS更适合写猜测性位置PATHS适合写确定的额外位置。4. 版本匹配、组件与导入目标的三个翻车点把库找到了只是第一步接下来这三处细节处理不好照样会出问题而且往往出在编译链接的后期排查成本更高。4.1 版本号比较用的是组件式规则find_package(Foo 1.4.2)中的版本号会被拆成组件逐个比较而不是当作字符串。比较规则由 Config 版本文件里的逻辑决定常见策略有以下几种策略匹配逻辑AnyNewerVersion请求版本或更新的任意版本都算兼容默认SameMajorVersion主版本必须相同次版本不低于请求值SameMinorVersion主版本和次版本都相同补丁号不低于请求值ExactVersion必须完全相等AnyNewerVersion是默认值这解释了为什么很多库你写了find_package(Foo 1.0)结果它接受了 3.x 版本——如果你的代码依赖的是 1.x 的旧 API这种太新的版本反而会带来编译错误。这种情况下必须显式写find_package(Foo 1.0 EXACT)或者让库作者把策略改成SameMajorVersion。还有一个细节是版本范围。CMake 3.19 起支持find_package(Foo 1.2...1.6)这种写法意思是接受 1.2 到 1.6 之间的版本。这在处理多环境构建时很有用比写死一个版本更灵活。4.2 COMPONENTS 的实现责任在库那边find_package(Foo COMPONENTS a b REQUIRED)的语义是我要 a 和 b 这两个组件缺一个就失败。但 CMake 本身并不检查这些组件是否存在它只是把列表放到Foo_FIND_COMPONENTS变量里由配置文件或 Find 脚本自己处理。Config 文件结尾那句check_required_components(Foo)就是干这个的。这个宏会遍历Foo_FIND_COMPONENTS检查Foo_component_FOUND变量如果有任何一个为假就把Foo_FOUND置为假并在REQUIRED的情况下报错。所以当你写COMPONENTS却发现组件缺失没有任何报错时八成是配置文件里漏了check_required_components。反之如果某个可选组件没装却导致了整体失败检查一下是不是多写了REQUIRED或者配置文件的组件判断逻辑写得太严格。提示查组件是否真的被检查可以在配置后打印Foo_FOUND和各个Foo_component_FOUND变量一眼就能看出哪个环节没生效。4.3 Debug 与 Release、动态与静态的导入差异单配置生成器Unix Makefiles、Ninja下CMAKE_BUILD_TYPE决定用哪套导入位置多配置生成器Visual Studio、Xcode下构建时可以选择配置所以需要同时提供IMPORTED_LOCATION_DEBUG和IMPORTED_LOCATION_RELEASE。如果只有IMPORTED_LOCATION在多配置环境下所有配置都会指向同一个文件。动态库和静态库的处理也不一样。静态库在链接时需要把传递依赖也一起带上这正是INTERFACE_LINK_LIBRARIES的用途。如果库作者忘了设这个属性你会看到静态链接时报一堆符号未定义然后在网上搜半天为什么 find_package 找到了还是链接失败。我自己遇到过一个特别典型的例子某个库同时装出了.a和.sofind_library默认优先选.so但我为了静态发布强行加了CMAKE_FIND_LIBRARY_SUFFIXES调整结果某些模块还是链到了动态库最终产物混着两种链接方式运行时报错。解决办法是明确用导入目标而不是自己找库文件路径让库自己决定用哪个。5. 跨平台和交叉编译场景下的路径差异同一份 CMakeLists.txt 在 Ubuntu 上跑通换到 Windows 上失败是很常见的情况。原因几乎都能归到搜索路径的默认值差异上。5.1 Ubuntu 与 Windows 上默认搜索的差别Ubuntu 上系统默认前缀包括/usr/local、/usr、/opt包管理器安装的库大多落在/usr/lib/x86_64-linux-gnu/cmake/这类位置Config 模式的默认后缀组合能覆盖到。所以用系统包管理器装完就能 find_package 到在 Linux 上成立的概率比较高。Windows 上没有这种约定。库通常被解压到C:\libs\foo-1.2.3这样的目录或者通过某个包管理器放到用户目录下。此时必须显式设置CMAKE_PREFIX_PATH或者配置环境变量。路径分隔符方面CMake 统一用正斜杠/最稳妥反斜杠在字符串里会被当转义符写C:\libs常常变成C:libs。还有一个容易忽略的点Windows 上包名大小写敏感度低但 CMake 的配置文件查找是按名字做变形尝试的。如果库导出的是fooConfig.cmake而你写的是find_package(Foo)CMake 会尝试Foo、foo、FOO等变形通常能命中但有时会因为目录名不符合预期而失败。遇到这种情况直接把Foo_DIR指过去验证一下能省很多时间。# Windows 上更稳妥的写法在 CMakeLists.txt 顶部集中管理 list(APPEND CMAKE_PREFIX_PATH C:/libs/mymath-1.4.2) list(APPEND CMAKE_PREFIX_PATH $ENV{USERPROFILE}/scoop/apps)5.2 交叉编译时要把搜索根目录锁死交叉编译的核心问题是CMake 默认会去宿主机的/usr、/usr/local找库结果找到的是给宿主机编译的版本链接时才报架构不匹配。解决办法是在工具链文件里做两件事。第一设置CMAKE_FIND_ROOT_PATH为目标系统的 sysroot并配置查找模式set(CMAKE_FIND_ROOT_PATH /opt/sysroot) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)PROGRAM设为NEVER是因为编译工具本身比如宿主机上的protoc应该从宿主机找而LIBRARY、INCLUDE、PACKAGE设为ONLY表示只在 sysroot 里找避免污染。第二把目标平台库的前缀目录加进CMAKE_PREFIX_PATH而不是靠默认路径。因为默认路径在交叉编译场景下几乎没有意义。我见过一种更隐蔽的失败CMAKE_FIND_ROOT_PATH_MODE_PACKAGE没设导致 Config 模式仍然按宿主机路径查找找到了宿主机上的同名库生成的导入目标指向宿主机文件。编译能过链接能过烧到板子上就跑不起来。这类问题只能靠严格设置查找模式来避免。5.3 和包管理器配合时的路径约定现在很多项目用包管理器来管依赖安装路径大多有规律把前缀加进CMAKE_PREFIX_PATH之后Config 模式通常能自动命中。麻烦的是同一个库存在多份的情况系统装了一份、包管理器装了一份、项目本地又解压了一份。CMake 的搜索是有顺序的但顺序和你的直觉不一定一致。一个可靠的实践是在项目根目录的 CMakeLists.txt 最前面显式把项目自带的依赖目录加到CMAKE_PREFIX_PATH的最前面并考虑用NO_CMAKE_SYSTEM_PATH之类的选项收紧搜索范围。这样构建结果不会因为某个同事机器上多装了一个库而发生变化可复现性会高不少。注意不要在多个地方重复追加CMAKE_PREFIX_PATH尤其是同时用set和list(APPEND)。set会覆盖之前的追加导致顺序和你预期的完全相反。6. 把 find_package 的排查过程走一遍前面讲的都是原理最后这部分说排查。我自己的习惯是固定按一个链路走从报错信息出发逐步缩小范围而不是想到哪个变量就改哪个。6.1 用 --debug-find 看它到底逛了哪些目录CMake 提供了一个非常实用的开关能打印出所有查找过程cmake -S . -B build --debug-find 21 | grep -A 30 find_package输出内容大致是这样find_package considered the following locations for the Config module: /opt/mymath/lib/cmake/MyMath/MyMathConfig.cmake /usr/local/lib/cmake/MyMath/MyMathConfig.cmake /usr/lib/cmake/MyMath/MyMathConfig.cmake The file was not found.这份输出的价值在于它列出了 CMake 实际尝试过的完整路径。你只要对照自己的实际安装位置就能立刻判断出是目录前缀不对还是文件名不对。如果输出里连你预期的前缀都没出现说明CMAKE_PREFIX_PATH没生效如果前缀出现了但文件名不匹配那就是包名大小写或命名约定的问题。除了--debug-find--debug-find-pkgFoo可以把输出限定到某个包日志量小很多适合依赖众多的项目。在 CMakeLists.txt 里也可以临时打开set(CMAKE_FIND_DEBUG_MODE TRUE)效果类似。6.2 CMakeCache.txt 造成的幽灵结果find_package找到的结果会缓存到CMakeCache.txt里尤其是Foo_DIR这个变量。这带来一个非常经典的问题你第一次配置时路径是错的CMake 把错误的Foo_DIR存进了缓存之后你修正了环境重新运行 cmake它依然用缓存里的旧值于是你反复怀疑自己改的东西没生效。判断方法很简单在构建目录里搜一下grep -i Foo_DIR build/CMakeCache.txt如果发现里面存的是一个已经不存在或错误的路径直接删掉构建目录重新配置是最省事的做法。更精细的方式是在 CMakeLists.txt 里加unset(Foo_DIR CACHE)或者用cmake -U Foo_DIR -S . -B build从命令行清掉这个缓存项。我一般建议遇到配置结果和预期完全不符的情况先删构建目录再试一次能排除掉一大半干扰因素。6.3 一套可以直接复用的排查清单把前面的内容收敛成一张清单遇到find_package问题按顺序执行看报错原文。是missing: Foo_DIR还是找到了但内部失败这两类问题的方向完全不同。确认库是否真的装了以及安装位置。用find / -name *Config.cmake 2/dev/null | grep -i foo这类命令扫一遍比猜快得多。用--debug-find-pkgFoo看真实搜索路径和上一步的结果对照。用-DFoo_DIR配置文件目录手动指定验证配置文件本身是完好的。如果Foo_DIR有效但CMAKE_PREFIX_PATH无效检查前缀层级是否为安装根目录。如果都无效确认是不是 Module 模式抢先命中了。此时可以写find_package(Foo CONFIG REQUIRED)强制走 Config 模式。配置成功但链接失败检查导入目标的库文件路径、架构和 Debug/Release 属性。全都对但结果诡异删掉构建目录重新配置排除缓存污染。第 6 条那个CONFIG关键字是很多人不知道的实用技巧。当你明确知道库提供了 Config 文件就不应该让 CMake 去试 Module 模式。显式写find_package(Foo CONFIG REQUIRED)或者find_package(Foo MODULE REQUIRED)能避免脚本版本和 Config 版本信息不一致引发的一系列奇怪问题。我在处理依赖多、环境复杂的项目时几乎会给每个第三方依赖都加上CONFIG虽然多打了几个字但把不确定性压到了最低。顺带说一个和版本号有关的实用操作如果你已经装了 CMake 但版本偏旧某些新参数比如Foo_ROOT、CMAKE_FIND_PACKAGE_PREFER_CONFIG用不了又不想动系统自带的版本常见的做法是单独装一份新版本放在用户目录下然后在 PATH 里把它排在前面。这种不动系统、只改个人环境的方式在多人共用的机器上尤其合适不会影响到别人的构建流程。至于卸载包管理器装的用对应包管理器卸载手动解压的目录直接删掉即可注意检查一下 PATH 里有没有残留的指向。最后补一个小技巧跟前面讲的find_dependency有关。如果你在写自家库的 Config 文件并且这个库依赖了Threads这类通常在 Module 模式下提供的包记得在find_dependency之后加一句对导入目标的引用检查比如if(NOT TARGET Threads::Threads)就提前报错。这比等到用户链接时才蹦出一堆找不到符号的报错要友好得多也省得对方来问你为什么库找到了还是编译不过。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价