如何在CMake中修复“无法找到软件包配置文件…”错误?

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

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

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

因此,在 thisthis 的帮助下,我能够构建我的本地项目。到目前为止,一切都很好。
然而,主要问题在于我必须在我的repo的CMakeLists.txt中硬编码RPLidar SDK路径。现在,每当我的团队中有其他人开始在该repo上工作时(这是相当明显的),他/她首先必须更新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>或<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文件。如何解决这个错误?

我想不出更好的标题。如果有人能想出更合适的标题,请更新它。谢谢 :) - Milan
1个回答

23

我的抱怨:

当某个库的作者没有提供foo-config.cmake文件方便你使用find_package(foo)时,你不得不使用该库foo,这真的很糟糕。当一个相对现代的项目仍然使用手写的Makefile作为其构建系统时,这是绝对令人震惊的。我自己现在被卡在一个比你的SDK更糟糕的构建环境中。


简短回答:

由于SDK作者未提供配置文件以支持您的cmake使用,如果您仍然坚持在库上调用find_package(并且您应该这样做!),则需要编写自己的Module文件来清理他们的混乱。(是的,您正在为库作者做工作)。

要真正实现跨平台使用,您应编写一个Findrplidar.cmake模块文件来查找库。

要编写合理的模块文件,您最有可能使用API find_path用于头文件和find_library用于库。您应该查看其文档并尝试使用它们,也许可以搜索一些教程。

这是我版本的Findglog.cmake,供您参考。(glog作者已更新他们的代码并支持Config模式。不幸的是,Ubuntu构建不使用它,所以我仍然需要编写自己的文件)

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文档确实令人生畏。

这就是为什么我在这里写下自己的答案,以免您通过Google搜索而受到误导。

噩梦已经结束了。等待已经结束。“cmake之救世主”()已经到来。他为2020年及以后编写的asm/C/C++/CUDA项目带来了希望。这里是真理之言

上面的链接指向编写cmake项目的唯一方法,并真正实现跨平台。请注意,对于初学者来说,这些材料并不容易跟随。当时我已经对cmake概念有所了解(但陷入了旧的罪恶之路),但我自己花了整整一周时间才完全掌握了真理之言中涵盖的内容。

所谓的“长话短说”实际上更短。它只是指向真正答案的指针。祝你阅读真理之言好运。拥抱真理之言,因为任何反对都是纯粹的异端邪说。


评论1-5的回复:

好问题。其中许多可以从Word中获取。但是当您变得更加熟悉CMake时,Word会更易于理解。让我按照与您手头问题相关性递减的顺序回答它们。

为了便于讨论,我将只使用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文件?

答案:因为在libfoo(也适用于glog)的情况下,只需要搜索一次头文件位置。就像libfoo的示例一样,其中foo/foo.hfoo/bar.h位于同一位置。因此,它们的find_path输出将相同:/home/dev/libfoo-dev/include


问:为什么我的头文件和库文件会显示“NOTFOUND”?

答:函数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_64-linux-gnu用于x86 Linux)用于find_path;相应地,lib变体用于find_library。不寻常的位置需要传入更多参数,这是不常见且很可能不必要的。

正因为如此,在libfoo的情况下,调用find_path(... foo.h ...)是不合适的。您应该使用find_path(... foo/foo.h ...)。有关详细信息,请参阅文档。您也可以自行尝试。

同样基于这个原因,在类Unix系统上将库文件以通常的bin include lib share方式组织是可取的。我不熟悉Windows。


问题: DebugRelease

答案: 有几个选项。最简单的可能是:

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

当然还有其他方法,但可能需要在您的Findrplidar.cmake脚本中特别注意(也许需要一些if语句)。


问题:为什么要使用glog::glog而不是glog

答案:这只是现代CMake实践,有一些小的好处。目前并不重要。如果您感兴趣,请参考文档。


Q: 你提到正在编写 rplidarConfig.cmake 文件。实际上,你应该将文件重命名为 Findrplidar.cmake

A: 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

答:这是教义所说的。因此,这是良好的实践。为什么教义会这样说是你必须自己找出来的。 在这个答案中,我无法解释教义的要点,也不会让你信服。我只会说以下几点:

编写混乱的CMakeLists非常容易,这几乎是不可能维护的。通过遵循教义的精神, 强制你仔细思考以下几点,可以帮助你避免这种情况:

  • 库结构:例如公共与私有头文件。这使您考虑要包含哪些头文件和公共API。
  • 构建规范:编写库所必需的内容(需要包括什么;需要链接什么)
  • 使用要求:他人使用您编写的库所必需的内容(需要包括什么;需要链接什么)
  • 依赖关系:您编写的库与其依赖项之间的关系
  • 可能还有其他方面

如果您仔细思考一下,这些方面对于编写跨平台和可维护的库非常重要。 include_directorieslink_directoriesadd_definitions都是非常糟糕的实践 (根据许多来源,包括这些API的官方文档)。不良实践往往会掩盖上述方面,并在整个集成时造成问题。例如, include_directories将为在该CMakeLists.txt目录中编写的每个目标添加-I到编译器。 反复阅读此句话,Spock会告诉你这是不合逻辑的。

不用担心。如果您不熟悉教义,现在使用它们也没关系(否则为什么会在最后一节中提到)。了解教义后,可以在有时间时重构CMakeLists。不良实践可能会在项目变得更加复杂时引起问题。(根据我的专业经验,5个非常小的团队足以引起噩梦。噩梦意味着在CMakeLists中硬编码所有内容;为每个不同的平台/设备/环境创建一个git分支;修复错误意味着在每个分支上挑选一个提交。我曾经在了解教义之前遇到过这种情况。)

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


1
非常感谢您提供如此详细的回复,我真的很感激。同时,很抱歉我回复晚了。然而,我对CMake还不是很有经验。我尝试使用您的CMake编写了rplidarConfig.cmake文件。但是,我仍然有很多疑惑:1. 在您的find_path命令中,只有一个.h文件。如果有一个子目录和多个头文件怎么办?因为在我的本地项目中运行cmake ..时,我得到了-- rplidar headers found at: rplidar_INCLUDE_DIR-NOTFOUND -- librplidar found at: rplidar_LIB-NOTFOUND的结果。 - Milan
  1. 通常在 CMake 中,我们会写 find_packageinclude_directorieslink_directoriesadd_definitionstarget_link_libraries。但您建议只写 find_packagetarget_link_libraries!为什么呢?
  2. 为什么我们要写 glog::glog 而不是只写 glog
  3. 如果我有不同的库用于 DebugRelease,该怎么办?
我还没有时间彻底研究您分享的链接。但是,如果您认为经过深入研究后可以回答我的问题,请忽略它们。
- Milan
最后一个问题:现在,作为一种解决方法,在我的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
1
@Milan 我更新了我的答案。请看底部部分。 - TerryTsao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接