Буквально сегодня открыл для себя Imported Targets. При использовании их для модулей поиска, очень упрощает и систематизирует код.
Для примера, если вам нужно подключить ZLIB к проекту, при классическом подходе пишется что-то вроде:
cmake_minimum_required (VERSION 3.1)
project(Foo)
find_package(ZLIB)
add_definitions(${ZLIB_DEFINITIONS})
include_directories(${ZLIB_INCLUDE_DIRS})
add_executable(foo_target
main.cpp)
target_link_libraries(foo_target ${ZLIB_LIBRARIES})
Какие проблемы в коде выше?
- Добавляется новая библиотека, нужно не забыть добавить переменные в три места:
1.
add_definitions()
2.include_direcrories()
3.target_link_libraries()
- Мы забыли ключевое слово
REQUIRED
вfind_package()
и узнаем, что у нас нет ZLIB или на компиляции или на линковке (так как других условий и проверок нет, предполагаю: данная библиотека необходима). - Область действия
add_definitions()
иinclude_directories()
- глобальная (на самом деле нет: все таргеты в текущем CMakeLists.txt во всех далее включённых, но не суть) и распространяется на все таргеты. Что не есть хорошо.
Возможно что-то ещё, чего я не заметил.
Последний пункт можно обойти уже написав более навороченный код:
cmake_minimum_required (VERSION 3.1)
project(Foo)
find_package(ZLIB)
add_executable(foo_target
main.cpp)
target_include_directories(foo_target ${ZLIB_INCLUDE_DIRS})
target_compile_definitions(foo_target ${ZLIB_DEFINITIONS})
target_link_libraries(foo_target ${ZLIB_LIBRARIES})
От проблемы глобальности ушли, но остальные две остались.
А вот, начиная с CMake 3.1 код выше можно переписать так:
cmake_minimum_required (VERSION 3.1)
project(Foo)
find_package(ZLIB)
add_executable(foo_target
main.cpp)
target_link_libraries(foo_target
ZLIB::ZLIB)
И теперь правильно подтянутся и параметры для компиляции, причём только для указанного таргета, и параметры линковки. А кода стало меньше. Более того, если библиотека не найдена, то цели ZLIB::ZLIB
существовать не будет и вызов cmake завершится с ошибкой. А чем раньше ошибка вылазит - тем лучше.
Как это делается? А достаточно просто, маинтейнеру модуля поиска нужно в конце добавить строчки вида:
if (ZLIB_FOUND AND NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB UNKNOWN IMPORTED)
set_target_properties(ZLIB::ZLIB PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIRS}"
INTERFACE_COMPILE_OPTIONS "${ZLIB_DEFINITIONS}"
INTERFACE_LINK_LIBRARIES "${ZLIB_LIBRARIES}")
endif()
Код для FindZLIB.cmake на самом деле несколько другой, но общую суть он отражает.
Соглашение для таких тергетов простое: PkgName::PkgComponent
где PkgName
соответствует имения пакета, которое передаётся в find_package()
(или имя модуля без префикса Find
и суффикса .cmake
), а PkgComponent
- внутренний компонент внутри модуля. Если такой компонент один, он соответствует PkgName
. Примеры:
ZLIB::ZLIB
Threads::Threads
Qt::Widgets
(см FindQt.cmake)
Кстати, вместо проверки: if (ZLIB_FOUND)
Можно использовать: if (TARGET ZLIB::ZLIB)
да, более многословно, но везде фигурирует только одно имя таргета.
Кроме того, эти таргеты можно использовать и совместно с target_include_directories()
и target_compile_definitions()
.
В общем, я крайне рекомендую в свои модули добавить несколько строчек, которые привнесут несколько большей унификации. От старых добрый переменных вас тоже никто отказываться не заставляет :)
ЗЫ ЕМНИП то такой синтаксис можно было использовать и до 3.1, с 3.1 оно появилось в некоторых модулях, которые поставляются с самим CMake.