【问题标题】:How to fix "Could not find a package configuration file ..." error in CMake?如何修复 CMake 中的“找不到包配置文件...”错误?
【发布时间】:2021-03-10 16:44:33
【问题描述】:

我一直在做一个使用rplidar_sdk 的项目,一开始我遇到了this 的问题:

如何在我的 C++ 项目中链接本地安装的 SDK 的静态库?

基本上,SDK 在其本地目录中生成库,在其Makefile 中,它没有install 规则。我的意思是我可以运行make,但在那之后,如果我运行sudo make install,那么它会给出make: *** No rule to make target 'install'. Stop. 错误。

因此,在thisthis 答案的帮助下,我能够构建我的本地项目。到目前为止一切顺利。

但是,主要问题是我必须在我的仓库的CMakeLists.txt 中硬编码 RPLidar SDK 路径。现在,每当我团队中的其他人开始处理该回购(这很明显)时,他/她必须首先更新CMakeLists.txt。这不是一个好主意/做法!

为了解决这个问题,我更新了 RPLidar SDK 的Makefile 如下:

.
.
.

RPLIDAR_RELEASE_LIB := $(HOME_TREE)/output/Linux/Release/librplidar_sdk.a

install: $(RPLIDAR_RELEASE_LIB)
    install -d $(DESTDIR)/usr/local/lib/rplidar/Release/
    install -m 644 $(RPLIDAR_RELEASE_LIB) $(DESTDIR)/usr/local/lib/rplidar/Release/


RPLIDAR_DEBUG_LIB := $(HOME_TREE)/output/Linux/Debug/librplidar_sdk.a

install: $(RPLIDAR_DEBUG_LIB)
    install -d $(DESTDIR)/usr/local/lib/rplidar/Debug/
    install -m 644 $(RPLIDAR_DEBUG_LIB) $(DESTDIR)/usr/local/lib/rplidar/Debug/


RPLIDAR_HEADERS := $(HOME_TREE)/sdk/include

install: $(RPLIDAR_HEADERS)
    install -d $(DESTDIR)/usr/local/include/rplidar/
    cp -r $(RPLIDAR_HEADERS)/* $(DESTDIR)/usr/local/include/rplidar/

RPLIDAR_HEADERS_HAL := $(HOME_TREE)/sdk/src/hal

install: $(RPLIDAR_HEADERS_HAL)
    install -d $(DESTDIR)/usr/local/include/rplidar/
    cp -r $(RPLIDAR_HEADERS_HAL) $(DESTDIR)/usr/local/include/rplidar/

由于这次更新,现在我可以运行sudo make install,它基本上将RPLidar SDK的头文件从本地目录复制到/usr/local/rplidar/目录。它还将lib文件复制到/usr/local/lib/rplidar/<Debug> or <Release>/目录。

现在,在我的本地项目中,我将CMakeLists.txt 更新为如下:

cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)

project(<project_name>)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
SET(CMAKE_CXX_FLAGS -pthread)

include_directories(include)
add_executable(${PROJECT_NAME} src/main.cpp src/another_src_file.cpp)

find_package(rplidar REQUIRED)
include_directories(${rplidar_INCLUDE_DIRS})
link_directories(${rplidar_LIBRARY_DIRS})
target_link_libraries(${PROJECT_NAME} ${rplidar_LIBRARY})

但是,在运行 cmake .. 命令时,我收到此错误:

.
.
.
CMake Error at CMakeLists.txt:12 (find_package):
  By not providing "Findrplidar.cmake" in CMAKE_MODULE_PATH this project has
  asked CMake to find a package configuration file provided by "rplidar", but
  CMake did not find one.

  Could not find a package configuration file provided by "rplidar" with any
  of the following names:

    rplidarConfig.cmake
    rplidar-config.cmake

  Add the installation prefix of "rplidar" to CMAKE_PREFIX_PATH or set
  "rplidar_DIR" to a directory containing one of the above files.  If
  "rplidar" provides a separate development package or SDK, be sure it has
  been installed.


-- Configuring incomplete, errors occurred!

据我所知,RPLidar SDK 没有rplidarConfig.cmakerplidar-config.cmake 文件。

我该如何解决这个错误?

【问题讨论】:

  • 我想不出更好的标题。如果有人能想出更合适的标题,请更新它。谢谢你:)

标签: c++ linux makefile cmake


【解决方案1】:

我的灵魂咆哮:

当您必须使用任何库 foo 而作者未能提供 foo-config.cmake 供您通过调用 find_package(foo) 轻松使用时,这很糟糕。当一个相当现代的项目仍然使用手写的 Makefile 作为其构建系统时,这绝对是令人发指的。我自己现在被构建的 SDK 比你的差得多。


简答:

由于 SDK 的作者未能提供配置文件来支持您的 cmake 使用,如果您仍然坚持在库上调用 find_package(您应该!),您需要编写自己的 Module文件来清理他们的烂摊子。 (是的,您正在为图书馆作者工作)。

要真正实现跨平台使用,你应该写一个Findrplidar.cmake模块文件来为你找到库。

要编写一个合理的模块文件,您很可能将 API find_path 用于头文件,将 find_library 用于库。你应该查看它的文档并尝试使用它们,也许谷歌一些教程。

这是我的Findglog.cmake 版本供您参考。 (glog作者更新了their code,支持Config模式,可惜Ubuntu build不使用,所以还是要自己写文件)

find_path(glog_INCLUDE_DIR glog/logging.h)
message(STATUS "glog header found at: ${glog_INCLUDE_DIR}")

find_library(glog_LIB glog)
message(STATUS "libglog found at: ${glog_LIB}")

mark_as_advanced(glog_INCLUDE_DIR glog_LIB)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(glog REQUIRED_VARS
  glog_INCLUDE_DIR
  glog_LIB
  )

if(glog_FOUND AND NOT TARGET glog::glog)
  add_library(glog::glog SHARED IMPORTED)
  set_target_properties(glog::glog PROPERTIES
    IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
    IMPORTED_LOCATION "${glog_LIB}"
    INTERFACE_INCLUDE_DIRECTORIES
      "${glog_INCLUDE_DIR}"
    )
endif()

你可以这样使用它:

find_package(glog)
target_link_libraries(main PRIVATE glog::glog)

长答案:

开发人员使用 cmake 的历史简直就是一场噩梦。互联网上充斥着如何在项目中不使用 cmake 的不良做法/示例,包括旧的官方 cmake 教程(可能仍然存在)。主要是因为没有人真正给出一个****(如果我可以构建我的项目,谁在乎它是否是跨平台的)。另一个正当的理由是 cmake 文档对初学者来说确实令人生畏。

这就是为什么我在这里写下我自己的答案,以免你被其他地方的谷歌搜索误导。

噩梦已不复存在。等待已经结束。 cmake (source) 的“弥赛亚”来了。他为 2020 年及以后编写的 asm/C/C++/CUDA 项目带来了希望。这里是The Word

上面的链接指向了如何编写 cmake 项目并真正实现一劳永逸的跨平台的唯一方法。请注意,对于初学者来说,这些材料一点也不容易理解。我自己花了整整一周的时间来完全掌握 The Word 所涵盖的内容,当时我对 cmake 的概念有了一些了解(但迷失在我以前的罪恶方式中)。

所谓的“长答案”其实更短。这只是指向真正答案的指针。祝你读到这个词好运。拥抱这个词,因为任何反对它的东西都是纯粹的异端。


评论 1-5 的回应:

好问题。其中很多可以从Word中获得。但是当您对 CMake 更加熟悉时,这个词会更好地消化。让我以与您手头问题的相关性降低的方式回答它们。

为了便于讨论,我只以 libfoo 为例。

假设你总是想像这样使用 libfoo:

find_package(foo)
target_link_libraries(your_exe ... foo::foo)

假装 foo 安装在以下位置:

- /home/dev/libfoo-dev/
  - include
    - foo
      - foo.h
      - bar.h
      - ...
  - lib
    - libfoo.so
  - share
    - foo/foo-config.cmake   # This may or may not exist. See discussion.

问:只有一个.h 文件。为什么?

A:因为在libfoo 的情况下(glog 也是如此),只需要搜索一次标题位置。就像libfoo的例子一样, 其中foo/foo.hfoo/bar.h 位于同一位置。所以find_path 的输出将是相同的:/home/dev/libfoo-dev/include


问:为什么我的头文件和库会收到 NOTFOUND

A:函数find_pathfind_library 仅搜索文档中指定的位置。默认情况下,他们搜索系统位置, 分别像/usr/include/usr/lib。有关系统位置的详细信息,请参阅官方文档。但是,在libfoo 的情况下, 他们居住在/home/dev/libfoo-dev。所以你必须在 cmake 变量CMAKE_PREFIX_PATH 中指定这些位置。这是一个; 分隔的字符串。 可以在命令行上执行cmake -D CMAKE_PREFIX_PATH="/home/dev/libfoo-dev;/more/path/for/other/libs/;...;/even/more/path" ....

一个非常重要的注意事项:与 Unix 命令 find 不同,find_path 只会搜索 /home/dev/libfoo-dev 内的特定路径,而不是一路向下: include(通常也是include/{arch},其中 {arch} 类似于 x86 Linux 的 x86_64-linux-gnu)对于 find_pathlib 变体为find_library, 分别。不寻常的位置需要传递更多的参数,这是不常见的,而且很可能是不必要的。

出于这个原因,对于libfoo,不希望调用find_path(... foo.h ...)。有人会想要find_path(... foo/foo.h ...)。参考文档 更多细节。你也可以自己试试。

同样出于这个原因,在类 Unix 系统上,最好在通常的 bin include lib share 四边形中组织库。我对 Windows 不熟悉。


问:Debug & Release

答:有几种选择。最简单的可能是:

  • 在两个不同的文件夹中准备 rplidar 调试和发布版本,例如 /path/to/debug/path/to/release
  • 分别传递给Debug & Release build (cmake -D CMAKE_PREFIX_PATH="/path/to/debugORrelease" ....)

肯定还有其他方法,但可能需要特别注意您的 Findrplidar.cmake 脚本(可能是一些 if 语句)。


问:为什么是glog::glog 而不是glog

A:这只是现代的 cmake 实践,好处很小。目前不重要。如果您有兴趣,请参阅 Word。


问:你提到你正在写rplidarConfig.cmake。相反,您应该将文件重命名为 Findrplidar.cmake

答:CMake 的理念是这样的:

  • 图书馆作者应该写foo-config.cmakefooConfig.cmake
  • 如果他们没有提供一个,那就糟透了。根据弥赛亚的说法,它应该被报告为错误。
  • 在这种情况下,作为库用户,您应该通过猜测如何描述 libfoo 的依赖关系来编写Findfoo.cmake。对于简单的库,这还不错。对于复杂的,比如 Boost,这很糟糕!

关于这个话题的一些旁注:

  • 请注意Findfoo.cmake 是图书馆用户如何根据猜测编写的。
  • 这太疯狂了!用户不应该这样做。这是作者的错,让他们的用户处于这种不舒服的境地。
  • foo-config.cmake 文件对于 libfoo 的作者来说非常容易编写,前提是他们完全遵循 Word。
  • 对于作者来说非常容易,因为 cmake 可以处理所有事情。它将自动生成脚本供作者在其foo-config.cmake 文件中使用。
  • 感谢 cmake,保证跨平台且易于用户使用。
  • 然而,现实很糟糕。现在你必须写Findfoo.cmake

问:为什么只有find_packagetarget_link_libraries

答:这就是圣经所说的。因此,这是一个很好的做法。为什么圣经这么说是你必须自己找出来的。 我不可能在这个答案中解释这个词的要旨,也不能让你信服。我只想说:

编写几乎无法维护的意大利面条式 CMakeList 非常容易。圣言的精神通过以下方式帮助你避免这种情况 迫使你仔细考虑:

  • 库结构:例如,公共与私有标头。这让您考虑在标头和公共 API 中包含哪些内容。
  • 构建规范:构建您编写的库需要什么(包括什么;链接什么)
  • 使用要求:其他人使用您编写的库需要什么(包括什么;链接什么)
  • 依赖关系:你写的库和它的依赖关系是什么关系
  • 也许更多

如果您考虑一下,这些方面对于编写跨平台且可维护的库至关重要。 include_directorieslink_directoriesadd_definitions 都是非常糟糕的做法 (根据很多来源,包括这些 API 的官方文档)。不良做法往往会掩盖上述方面, 并在以后将所有内容整合为一个整体时引起问题。例如。 include_directories 将为每个编译器添加 -I 目标写在那个CMakeLists.txt的目录中。把这句话读几遍,Spock 会告诉你这不合逻辑。

别担心。当您不熟悉 Word 时,现在可以使用它们(否则为什么会在最后一节中)。一旦你知道了这个词,当你有时间的时候重构你的 CMakeLists。当您的项目变得更加复杂时,不良做法可能会在以后引起问题。 (根据我的专业经验,5 个非常小的群体足以引发一场噩梦。我所说的噩梦是指在 CMakeLists 中对所有内容进行硬编码;为每个不同的平台/设备/环境创建一个 git 分支;修复一个对樱桃有意义的错误 -为每个分支选择一个提交。我在了解 Word 之前就去过那里。)

Word的实践很好地利用了现代CMake的哲学,将构建规范和使用要求封装在里面 CMake 目标。因此,当target_link_libraries 被调用时,这些属性会被正确传播。

【讨论】:

  • 非常感谢您提供如此详细的回复。我真的很感激。另外,很抱歉我的回复晚了。但是,我对 CMake 的经验还不是很丰富。我试着在你的CMake 的帮助下写rplidarConfig.cmake。但是,我有很多困惑:1.在您的find_path 命令中,只有一个.h 文件。如果子目录有多个头文件怎么办?因为在我的本地项目中运行 cmake .. 时,我得到了 -- rplidar headers found at: rplidar_INCLUDE_DIR-NOTFOUND -- librplidar found at: rplidar_LIB-NOTFOUND
  • 2.在CMake中,一般我们写find_packageinclude_directorieslink_directoriesadd_definitionstarget_link_libraries。但你建议只写find_package & targe_link_libraries!为什么? 3. 为什么我们要做glog::glog 而不仅仅是glog 4. 如果我有不同的库用于DebugRelease 怎么办?我没有机会彻底浏览链接,你分享了。但是,当然,我会做到的。因此,如果您认为在彻底阅读之后可以回答我的问题,那么请忽略它们。
  • 5.最后一个问题:现在,作为一种解决方法,在我的CMakeLists 中,我刚刚添加了以下三行:include_directories(/usr/local/include/rplidar/) target_link_libraries(${PROJECT_NAME} debug /usr/local/lib/rplidar/Debug/librplidar_sdk.a) target_link_libraries(${PROJECT_NAME} optimized /usr/local/lib/rplidar/Release/librplidar_sdk.a),它可以正常工作,没有任何错误或警告。但这是一个坏习惯吗?因为,我不能使用find_package 命令以及${rplidar_INCLUDE_DIR}${rplidar_LIB}!提前非常感谢您!
  • @Milan 更新了我的答案。见底部。
猜你喜欢
  • 2015-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-17
  • 2020-11-10
  • 1970-01-01
  • 2013-05-29
  • 2014-10-19
相关资源
最近更新 更多