未找到现代 CMake 传递依赖项

Modern CMake transitive Dependency not found

我目前正在致力于将 ROS 库实施到我们公司的软件堆栈中。因为该库是基于 ROS 的,因此使用了 catkin,我正在重写该库以专门使用 cmake 并尝试应用 modern CMake approach。该库的结构如下:

.
|-- CMakeLists.txt
|-- LICENSE
|-- README.md
|-- grid_map_core
|   |-- CHANGELOG.rst
|   |-- CMakeLists.txt
|   |-- cmake
|   |   `-- grid_map_core-extras.cmake
|   |-- grid_map_coreConfig.cmake
|   |-- include
|   |   `-- grid_map_core
|   |       `-- iterators
|   |-- src
|   |   `-- iterators
|   `-- test

如果我安装库并尝试以简单的方式将库添加到目标 test_project,我会收到一条错误消息,显示找不到 Eigen3 依赖项:

CMake Error at CMakeLists.txt:6 (find_package):
  Found package configuration file:

    /usr/local/lib/cmake/grid_map_core/grid_map_coreConfig.cmake

  but it set grid_map_core_FOUND to FALSE so package "grid_map_core" is
  considered to be NOT FOUND.  Reason given by package:

  grid_map_core could not be found because dependency Eigen3 could not be
  found.

不幸的是,我必须使用的 Eigen 版本不提供 Eigen3Config.cmake 选项,我不得不使用 cmake 提供的 FindEigen3.cmake 替代方案。 (我想手动编译更新的 Eigen3 版本是一个有效的选择,不过我试图完全理解现代 cmake 方法,它看起来非常有希望完全避免此类问题)

根据所有在线资源,我不太确定在这种情况下传递依赖性是如何处理的。 据我了解,grid_map_coreConfig.cmake 应该转发导入的 Eigen3 依赖项。 在 grid_map_core CMakeLists 中,特征值是通过命令 find_package(Eigen3 3.2 REQUIRED) 找到的,而 find_dependency 宏只是包装了这个完全相同的命令。

资源

主要CmakeLists.txt如下所示:

# Set cmake version
cmake_minimum_required(VERSION 3.0.2)

# Set project name
project(grid_map)

# Must use GNUInstallDirs to install libraries into correct
# locations on all platforms.
include(GNUInstallDirs)

add_compile_options(-std=c++11)


# Add subdirectories
add_subdirectory(grid_map_core)

grid_map_coreCMakeLists如下:

# Set cmake version
cmake_minimum_required(VERSION 3.0.2)

# Set project name
project(grid_map_core)

add_compile_options(-std=c++11)

# import Eigen3
find_package(Eigen3 3.2.2 REQUIRED)

## Define Eigen addons.
include(cmake/${PROJECT_NAME}-extras.cmake)

#########
# Build #
#########

# Add the library target
add_library(${PROJECT_NAME}
  src/BufferRegion.cpp
  src/GridMap.cpp
  src/GridMapMath.cpp
  src/Polygon.cpp
  src/SubmapGeometry.cpp
  src/iterators/CircleIterator.cpp
  src/iterators/EllipseIterator.cpp
  src/iterators/GridMapIterator.cpp
  src/iterators/LineIterator.cpp
  src/iterators/PolygonIterator.cpp
  src/iterators/SlidingWindowIterator.cpp
  src/iterators/SubmapIterator.cpp
)

# set target include directories
target_include_directories(${PROJECT_NAME}
  PUBLIC
    $<INSTALL_INTERFACE:include>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    ${EIGEN3_INCLUDE_DIR}
  PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# add an alias
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

# set target compile options
target_compile_options(${PROJECT_NAME}
  PRIVATE
    $<$<CONFIG:Debug>:-Werror>
)

###########
# Install #
###########

# 'make install' to the right locations
install(TARGETS ${PROJECT_NAME}
  EXPORT "${PROJECT_NAME}Targets"
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  INCLUDES DESTINATION include
)

# This makes the project importable from the install directory
# Put config file in per-project dir.
install(EXPORT "${PROJECT_NAME}Targets"
  FILE "${PROJECT_NAME}Targets.cmake"
  NAMESPACE "${PROJECT_NAME}::"
  DESTINATION lib/cmake/${PROJECT_NAME})

# generate config.cmake
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
  VERSION "${PROJECT_NAME}_VERSION"
  COMPATIBILITY SameMajorVersion
  )

# install config.cmake files
install(FILES "${PROJECT_NAME}Config.cmake"
  DESTINATION "lib/cmake/${PROJECT_NAME}")

###########
# Testing #
###########

和grid_map_coreConfig.cmake如下:

include(CMakeFindDependencyMacro)

find_dependency(Eigen3 REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/grid_map_coreTargets.cmake")

test_project的CMakeLists.txt:

cmake_minimum_required(VERSION 3.0)
project(test_project)

set(CMAKE_MODULE_PATH /usr/share/cmake-3.0/Modules)

add_compile_options(-std=c++11)

find_package(grid_map_core REQUIRED CONFIG)

add_executable(test_project main.cpp)

target_link_libraries(test_project
  PRIVATE
    grid_map_core::grid_map_core
  )

为了完整起见,我添加了 FindEigen3.cmake 文件:

# - Try to find Eigen3 lib
#
# This module supports requiring a minimum version, e.g. you can do
#   find_package(Eigen3 3.1.2)
# to require version 3.1.2 or newer of Eigen3.
#
# Once done this will define
#
#  EIGEN3_FOUND - system has eigen lib with correct version
#  EIGEN3_INCLUDE_DIR - the eigen include directory
#  EIGEN3_VERSION - eigen version

# Copyright (c) 2006, 2007 Montel Laurent, <montel@kde.org>
# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael@free.fr>
# Copyright (c) 2009 Benoit Jacob <jacob.benoit.1@gmail.com>
# Redistribution and use is allowed according to the terms of the 2-clause BSD license.

if(NOT Eigen3_FIND_VERSION)
  if(NOT Eigen3_FIND_VERSION_MAJOR)
    set(Eigen3_FIND_VERSION_MAJOR 2)
  endif(NOT Eigen3_FIND_VERSION_MAJOR)
  if(NOT Eigen3_FIND_VERSION_MINOR)
    set(Eigen3_FIND_VERSION_MINOR 91)
  endif(NOT Eigen3_FIND_VERSION_MINOR)
  if(NOT Eigen3_FIND_VERSION_PATCH)
    set(Eigen3_FIND_VERSION_PATCH 0)
  endif(NOT Eigen3_FIND_VERSION_PATCH)

  set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
endif(NOT Eigen3_FIND_VERSION)

macro(_eigen3_check_version)
  file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)

  string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
  set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
  string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
  set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
  string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
  set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")

  set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
  if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
    set(EIGEN3_VERSION_OK FALSE)
  else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
    set(EIGEN3_VERSION_OK TRUE)
  endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})

  if(NOT EIGEN3_VERSION_OK)

    message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
                   "but at least version ${Eigen3_FIND_VERSION} is required")
  endif(NOT EIGEN3_VERSION_OK)
endmacro(_eigen3_check_version)

if (EIGEN3_INCLUDE_DIR)

  # in cache already
  _eigen3_check_version()
  set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})

else (EIGEN3_INCLUDE_DIR)

  find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
      PATHS
      ${CMAKE_INSTALL_PREFIX}/include
      ${KDE4_INCLUDE_DIR}
      PATH_SUFFIXES eigen3 eigen
    )

  if(EIGEN3_INCLUDE_DIR)
    _eigen3_check_version()
  endif(EIGEN3_INCLUDE_DIR)

  include(FindPackageHandleStandardArgs)
  find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)

  mark_as_advanced(EIGEN3_INCLUDE_DIR)

endif(EIGEN3_INCLUDE_DIR)

您收到的错误消息是由 find_dependency 宏生成的,而不是由 find_package 命令生成的,该命令由该宏在内部调用。因为您使用 REQUIRED 关键字调用 find_dependency(并且此关键字传递给内部 find_package),所以您的错误唯一可能的情况如下:

  1. 调用 find_package(Eigen3 REQUIRED) 将 Eigen3 解释为 found
  2. 但是当 find_dependency 检查 find_package 结果时,它将 Eigen3 解释为 未找到

很奇怪,不是吗?

实际上,您的 FindEigen3.cmake 脚本是旧脚本之一,它设置 大写 变量 "FOUND" 流,表示脚本成功:

# EIGEN3_FOUND - system has eigen lib with correct version

但是正确的这个变量的名字应该是Eigen3_FOUND(包名应该和find_package(Eigen3)调用中的名字完全一样脚本 FindEigen3.cmake).

命令 find_package 检查 "FOUND" 变量的两种拼写:正确的一个和大写的一个。这样,当 FindEigen3.cmake 脚本设置 EIGEN3_FOUND 变量的意图为 "I have found the package" 时,find_package 理解该意图,将包标记为 found

但是宏 find_dependency 只检查变量的正确名称 Eigen3_FOUND。因为这个变量不是由 FindEigen3.cmake 脚本设置的,所以包被视为 未找到

作为快速修复,您可以将 FindEigen3.cmake 脚本中的 EIGEN3_FOUND 替换为 Eigen3_FOUND,一切都会正常。 (好吧,同样的替换应该在用户机器上完成。或者你应该用你的库发送更正的 FindEigen3.cmake 脚本)。