Современная переходная зависимость CMake не найдена

В настоящее время я работаю над внедрением библиотеки ROS в программный стек нашей компании. Поскольку библиотека основана на ROS и, следовательно, использует сережку, я переписываю библиотеку для использования исключительно cmake и пытаюсь применить современный подход CMake. Библиотека устроена следующим образом:

.
|-- 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_core CMakeLists выглядит следующим образом:

# 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.cm сделать следующим образом:

include(CMakeFindDependencyMacro)

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

и CMakeLists.txt test_project:

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, <[email protected]>
# Copyright (c) 2008, 2009 Gael Guennebaud, <[email protected]>
# Copyright (c) 2009 Benoit Jacob <[email protected]>
# 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)


person Beat Scherrer    schedule 14.03.2019    source источник
comment
Похоже, find_dependency() call не может найти ваш FindEigen3.cmake скрипт. Где находится этот скрипт?   -  person Tsyvarev    schedule 14.03.2019
comment
Хм, я не могу найти понятие, что FindEigen3.cmake является частью CMake, но пусть будет так. Макрос find_dependency вызывает find_package() внутри, и последний должен вывести полезное сообщение в случае сбоя. Но я не вижу этого сообщения в вашем выводе. Если в вашем тестовом проекте используется ключевое слово QUITE при поиске grid_map, удалите это ключевое слово.   -  person Tsyvarev    schedule 14.03.2019
comment
Кстати, я помню старые FindXxx.cmake скрипты, которые задают результат через переменную XXX_FOUND вместо Xxx_FOUND. В то время как команда find_package может проверять оба написания переменной, find_dependency проверяет только Xxx_FOUND один. Мне любопытно, устанавливает ли ваш скрипт FindEigen3.cmake переменную EIGEN3_FOUND. (Более подозрительно: если find_package() вызывается с ключевым словом REQUIRED и терпит неудачу, то он должен немедленно завершить работу CMake. Но ваш вывод после find_package вызова. )   -  person Tsyvarev    schedule 14.03.2019
comment
Я могу ошибаться, что FindEigen3.cmake является частью cmake, но я считаю, что это была часть Eigen версии 3.2.2. до того, как они начали делать это «правильно» и выпускать Eigen3Config.cmake. Я не на офисном ПК, поэтому я не могу слишком много исследовать сам. Тем не менее, поскольку библиотека собирается, файл находится как минимум в одном проекте, и остается вопрос, почему его нельзя найти в тестовом проекте. Я предоставлю test_project CMakeLists.txt как можно скорее, но на самом деле просто найти библиотеку grid_map и связать ее.   -  person Beat Scherrer    schedule 14.03.2019
comment
Как я уже сказал, возможной причиной может быть: find_package(Eigen3 REQUIRED) считает, что переменная EIGEN3_FOUND имеет значение TRUE, и интерпретирует Eigen3 как найденное, поэтому сборка вашей библиотеки прошла успешно. Такой же (или похожий) вызов выполняется при поиске в вашей библиотеке, и снова find_package(Eigen3 REQUIRED) интерпретирует Eigen3 как найденный. Но после возврата find_dependency(Eigen3 REQUIRED) проверяет переменную Eigen3_FOUND (на случай!). Поскольку эта переменная не установлена, find_dependency интерпретирует Eigen3 как не найденный.   -  person Tsyvarev    schedule 14.03.2019
comment
поэтому я попытаюсь использовать find_package(Eigen3 REQUIRED) вместо find_dependency(), когда снова вернусь на рабочую станцию. Очень расстраивает то, что для cmake так мало шаблонов и ресурсов, в отличие от C++.   -  person Beat Scherrer    schedule 14.03.2019
comment
при использовании find_dependency() ни одна из переменных не устанавливается, потому что команда кажется неудачной и поэтому прерывается. При использовании find_package() возникает ошибка компоновщика, с которой можно справиться, удалив target_link_libraries(PUBLIC Eigen3), поскольку eigen является только заголовком. Таким образом, использование find_package() в grid_mapConfig.cmake работает. При использовании find_dependency() возникает та же исходная ошибка.   -  person Beat Scherrer    schedule 18.03.2019
comment
Покажите FindEigen3.cmake, который вы используете. Хотя бы ссылку на него добавь. Поскольку Eigen3 предназначен только для заголовка, почему вы когда-либо find_dependency для него? Включают ли ваши общедоступные заголовки (которые доступны для пользователя вашей библиотеки) заголовки Eigen3?   -  person Tsyvarev    schedule 18.03.2019
comment
Я просто считаю, что Даниэль Пфайфер упоминает, что ВСЕГДА включает такие зависимости: find_package(Foo 1.0) target_link_library(target PUBLIC/PRIVATE Foo::Foo), даже если библиотека является только заголовком. Но поскольку FindEigen3.cmake не поддерживает эту парадигму, мне нужно только добавить собственные заголовки в мои общедоступные заголовки, и я могу забыть о зависимости. Итак, возможно, новейшая версия Eigen поддерживает эту современную парадигму cmake, которая мне очень нравится и которую я хотел адаптировать в этом случае (где она явно не работает таким образом).   -  person Beat Scherrer    schedule 18.03.2019
comment
Библиотеки только для заголовков не являются обычным явлением, и во многих руководствах они не описываются. При использовании find_dependency требуется, чтобы библиотека существовала на компьютере пользователя. Это абсолютно необходимо для обычных (разделяемых) библиотек, потому что без них ваша библиотека вообще бы не работала. Но если ваша библиотека использует какой-то заголовок только внутренне (в своей реализации, но не в общедоступном интерфейсе), нет смысла требовать, чтобы эта библиотека только заголовка была установлена ​​на компьютере пользователя. Все заголовки из него уже скомпилированы в вашу библиотеку.   -  person Tsyvarev    schedule 18.03.2019
comment
В моем случае библиотека Eigen используется в интерфейсе GridMap, поэтому она является «зависимостью интерфейса» и должна существовать на компьютере пользователя, поэтому установка зависимости от eigen была бы естественной (даже если это только заголовок).   -  person Beat Scherrer    schedule 18.03.2019


Ответы (1)


Полученное вами сообщение об ошибке генерируется макросом find_dependency, а не командой find_package, которая вызывается внутри этого макроса. Поскольку вы вызываете find_dependency с ключевым словом REQUIRED (и это ключевое слово передается внутреннему find_package), единственный возможный сценарий вашей ошибки следующий:

  1. Вызов find_package(Eigen3 REQUIRED) интерпретирует Eigen3 как найденный.
  2. Но когда find_dependency проверяет результаты find_package, он интерпретирует Eigen3 как не найденный.

Довольно странно, не правда ли?

На самом деле, ваш FindEigen3.cmake скрипт - один из старых, который задает верхний регистр поток переменной "FOUND", что означает успех скрипта:

# EIGEN3_FOUND - в системе есть собственная библиотека правильной версии

но правильное имя такой переменной должно быть Eigen3_FOUND (имя пакета должно быть точно таким же, как в вызове find_package(Eigen3) и в имени скрипта FindEigen3.cmake).

Команда find_package проверяет оба написания переменной "FOUND": правильное и заглавное. Таким образом, когда скрипт FindEigen3.cmake устанавливает переменную EIGEN3_FOUND с намерением "Я нашел пакет", find_package понимает это намерение, помечая пакет как найденный.

Но макрос find_dependency проверяет только правильное имя переменной, Eigen3_FOUND. Поскольку эта переменная не задана сценарием FindEigen3.cmake, пакет считается не найденным.

В качестве быстрого исправления можно заменить в скрипте FindEigen3.cmake EIGEN3_FOUND на Eigen3_FOUND и все должно заработать. (Ну, такую ​​же замену надо сделать на машине пользователя. Либо поставить исправленный FindEigen3.cmake скрипт со своей библиотекой).

person Tsyvarev    schedule 18.03.2019
comment
Хороший ответ. Кроме того, мне пришлось удалить target_link_libraries(${PROJECT_NAME} PUBLIC Eigen3), так как компоновщик не найдет библиотеку только для заголовков. Эта семантика должна быть правильной в более новых версиях, где предоставляется Eigen3Config.cmake, но мне придется проверить. Спасибо за ваши ответы! - person Beat Scherrer; 18.03.2019
comment
Эта семантика должна быть правильной в более новых версиях, где предоставляется Eigen3Config.cmake — Eigen3 устанавливает свою цель с помощью install (TARGETS eigen EXPORT Eigen3Targets) и экспортирует ее с помощью install (EXPORT Eigen3Targets NAMESPACE Eigen3:: ...), поэтому правильное имя цели для связи с Eigen3 будет Eigen3::eigen. Обратите внимание, что при использовании целевого имени в пространстве имен CMake требует существования целевого объекта. Это противоположно цели без пространства имен Eigen3: если цель не существует, CMake молча передает ее компоновщику. - person Tsyvarev; 18.03.2019
comment
Да, я забыл пространство имен в этом случае. Уже взволнован, чтобы попробовать это, как только наша система будет обновлена ​​​​до Ubuntu. - person Beat Scherrer; 18.03.2019