如何修复 CMake 中的“找不到包配置文件...”错误?

Mil*_*lan 10 c++ linux makefile cmake

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

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

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

因此,在这个这个答案的帮助下,我能够构建我的本地项目。到目前为止,一切都很好。

然而,主要问题是我必须对CMakeLists.txt我的存储库中的 RPLidar SDK 路径进行硬编码。现在,每当我团队中的其他人开始处理该存储库(这是非常明显的)时,他/她就必须更新第CMakeLists.txt一个。这不是一个好主意/做法!

为了解决这个问题,我更新了MakefileRPLidar SDK,如下所示:

.
.
.

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/
Run Code Online (Sandbox Code Playgroud)

由于这次更新,现在我可以运行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})
Run Code Online (Sandbox Code Playgroud)

但是,在运行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!

Run Code Online (Sandbox Code Playgroud)

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

我该如何修复这个错误?

Ter*_*sao 24

发自内心的咆哮:

foo当作者未能提供一个foo-config.cmake供您通过调用轻松使用的库时,您必须使用任何库,这很糟糕find_package(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()
Run Code Online (Sandbox Code Playgroud)

你可以这样使用它:

find_package(glog)
target_link_libraries(main PRIVATE glog::glog)
Run Code Online (Sandbox Code Playgroud)

长答案:

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

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

噩梦不再存在。等待结束了。cmake(来源)的“弥赛亚”来了。他为 2020 年及以后编写的 asm/C/C++/CUDA 项目带来了希望。这是圣言

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

所谓“长答案”其实更短。它只是指向真正答案的指针。祝你阅读圣言好运。拥抱神的道,因为任何反对它的行为都是纯粹的异端。


评论1-5的回复:

好问题。其中很多都可以从神的话语中获得。但是,当您更加熟悉 CMake 时,就会更好地理解这个词。让我以降低与您手头问题的相关性来回答它们。

为了便于讨论,我仅使用 libfoo 作为示例。

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

find_package(foo)
target_link_libraries(your_exe ... foo::foo)
Run Code Online (Sandbox Code Playgroud)

假装 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.
Run Code Online (Sandbox Code Playgroud)

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

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


问:为什么我要获取NOTFOUND标头和库?

答:文档中指定的功能find_path和仅搜索位置。find_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 命令不同findfind_path它只会搜索内部的特定路径/home/dev/libfoo-dev,而不是一路向下:( include通常也像x86 Linux 中的include/{arch}{arch} 一样)分别为 的变体。不寻常的位置需要传递更多参数,这是不常见的,而且很可能是不必要的。x86_64-linux-gnufind_pathlibfind_library

正是由于这个原因,对于libfoo,调用find_path(... foo.h ...)是不受欢迎的。有人会想要find_path(... foo/foo.h ...)。请参阅文档了解更多详细信息。你也可以自己尝试一下。

也出于这个原因,最好将库组织bin include lib share在类 Unix 系统上的常用四元组中。我对 Windows 不熟悉。


问:Debug&Release

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

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

肯定还有其他方法,但可能需要在Findrplidar.cmake脚本中特别小心(也许是一些if语句)。


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

答:这只是现代的 cmake 实践,好处不大。现在不重要。如果您有兴趣,请参阅《圣经》。


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

A:CMake 的理念是这样的:

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

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

  • 请注意图书馆用户是如何Findfoo.cmake通过猜测来编写的。
  • 疯了吧!用户不应该这样做。这是作者的错,让他们的用户陷入这种不舒服的境地。
  • foo-config.cmake对于 libfoo 的作者来说,如果他们严格遵循单词,那么编写文件是非常容易的。
  • 对于作者来说非常容易,因为:cmake 可以处理一切。它将自动生成脚本供作者在他们的文件中使用foo-config.cmake
  • 感谢 cmake,保证跨平台且易于用户使用。
  • 然而,现实却很糟糕。现在你必须写Findfoo.cmake

问:为什么只有find_package& target_link_libraries

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

编写意大利面 CMakeList 非常容易,而且几乎无法维护。圣经的精神会迫使你仔细思考以下问题,从而帮助你避免这种情况:

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

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

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

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