Конфигурация CMake для сборки проекта из корня или из подкаталогов
Я недавно начал использовать CMake (2.8.12.2, включенный в CentOS 6.8), и я думаю, что он достаточно мощный, чтобы помочь выполнить то, что я хочу, но я не смог понять, как:-), поэтому я призови свою мудрость, чтобы помочь мне найти пропущенную точку.
У меня есть макет проекта, как это:
BaseDir
|
+-->bin (generated by the process)
| |
| +-->Debug
| +-->Release
|
+-->lib (generated by the process)
| |
| +-->Debug
| +-->Release
|
+-->CMakeLists.txt
|
+-->Library_A
| |
| +-->CMakeLists.txt
| +-->include
| +-->src
| | |
| | +-->CMakeLists.txt
| |
| +-->test # Small binary to test solely the library functions
| |
| +-->CMakeLists.txt
|
+-->Library_B (depends on Library_A)
| |
| +-->CMakeLists.txt
| +-->include
| +-->src
| | |
| | +-->CMakeLists.txt
| |
| +-->test # Small binary to test solely the library functions
| |
| +-->CMakeLists.txt
|
+-->Application_1 (depends on Library_B, hence transitivitely depends on Library_A)
| |
| +-->CMakeLists.txt
| +-->include
| +-->src
| |
| +-->CMakeLists.txt
|
+-->Application_2 (depends on Library_A)
|
+-->CMakeLists.txt
+-->include
+-->src
|
+-->CMakeLists.txt
Это работает как шарм, когда я помещаю себя под BaseDir и запускаю "cmake". Application_1, Application_2, Library_A и Libray_B построены в правильном порядке, связаны и т. Д.
Тем не менее, моя идея состоит в том, чтобы иметь возможность создавать, находясь под любым из подкаталогов (Application_ , Library_), и в этом случае создавать только соответствующий ему код (то есть сам по себе, его тесты и его зависимости). Например, когда он находится внутри Library_A, создается только эта папка, в то время как из Library_B также создается библиотека Library_A, и эквивалент, который происходит, когда он находится под Application_1 или Application_2. Кроме того, независимо от того, где я нахожусь для запуска процесса cmake, результаты сборки (библиотеки или библиотеки) всегда должны помещаться в BaseDir/{lib|bin}/{Debug/Release/etc}, а не в подкаталогах библиотек или приложений., Это подразумевает, например, что связывание Library_B (которое зависит от Library_A) или Application_1 (которое зависит от Library_A и B) должно искать BaseDir/lib/{Debug/Release}.
Моя цель состоит в том, чтобы многие приложения находились под BaseDir, поэтому я хочу избежать необходимости создавать их все каждый раз, если в этом нет особой необходимости, только одно приложение, которое я хочу.
Я просмотрел файлы CMakeLists.txt для нескольких библиотек и исполняемых файлов, а также CMake и нашел другие проекты и их зависимости, но на самом деле это не та ситуация, которой я пытаюсь достичь здесь.
Я пробовал что-то вроде следующего:
BaseDir/CMakeLists.txt
|
| cmake_minimum_required (VERSION 2.8)
| project (BASE_DIR)
|
| add_subdirectory (Library_A) # No local dependencies
| add_subdirectory (Library_B) # Depends on A
| add_subdirectory (Application_1) # Depends on A and B
| add_subdirectory (Application_2) # Depends on A
|
| # I want all binary outputs (executables and libraries) placed under BaseDir/{lib|bin}/{Debug|Release}
| set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/${CMAKE_BUILD_TYPE}") # For the executables
| set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/${CMAKE_BUILD_TYPE}") # For the static libraries
| set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/${CMAKE_BUILD_TYPE}") # For the dynamic libraries
|
+-->BaseDir/Library_A/CMakeLists.txt (again, no dependencies)
| |
| | cmake_minimum_required (VERSION 2.8)
| | project (LIB_A)
| |
| | # In case CMake is run from within BaseDir/Library_A and not from BaseDir, I still want the outputs being placed under BaseDir/{lib|bin}/{Debug|Release}
| | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin/${CMAKE_BUILD_TYPE}") # For the test executables
| | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../lib/${CMAKE_BUILD_TYPE}") # For the static libraries
| | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../lib/${CMAKE_BUILD_TYPE}") # For the dynamic libraries
| |
| | include_directories(include)
| | add_subdirectory (src)
| | add_subdirectory (test)
| | set(${PROJECT_NAME}_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/include CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
| |
| +-->BaseDir/Library_A/src/CMakeLists.txt
| |
| | cmake_minimum_required (VERSION 2.8)
| |
| | # add all files in the current directory
| | file(GLOB LIB_A_SRCS "*.h" "*.cpp")
| |
| | # Create a library called libA.a
| | add_library(A ${LIB_A_SRCS})
| |
| +-->BaseDir/Library_A/test/CMakeLists.txt
|
| cmake_minimum_required (VERSION 2.8)
|
| # add all files in the current directory
| file(GLOB TEST_A_SRCS "*.h" "*.cpp")
|
| # Create an executable file from sources
| add_executable(TEST_A ${TEST_A_SRCS})
|
| # Link this executable to the library it's testing
| target_link_libraries(TEST_A A)
|
+-->BaseDir/Library_B/CMakeLists.txt (dependency on A)
| |
| | cmake_minimum_required (VERSION 2.8)
| | project (LIB_B)
| |
| | # In case CMake is run from within BaseDir/Library_B and not from BaseDir, I still want the outputs being placed under BaseDir/{lib|bin}/{Debug|Release}
| | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin/${CMAKE_BUILD_TYPE}") # For the test executables
| | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../lib/${CMAKE_BUILD_TYPE}") # For the static libraries
| | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../lib/${CMAKE_BUILD_TYPE}") # For the dynamic libraries
| |
| | include_directories(include)
| | add_subdirectory (src)
| | add_subdirectory (test)
| | set(${PROJECT_NAME}_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/include CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
| |
| +-->BaseDir/Library_B/src/CMakeLists.txt
| |
| | cmake_minimum_required (VERSION 2.8)
| |
| | # add all files in the current directory
| | file(GLOB LIB_B_SRCS "*.h" "*.c")
| |
| | # Create a library called libB.a
| | add_library(B ${LIB_B_SRCS})
| |
| | # Add a dependency to Library_A
| | find_library(LIBRARY_A A PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../../Library_A)
| | include_directories("$LIB_A_INCLUDE_DIRECTORIES")
| | target_link_libraries(B ${LIBRARY_A})
| |
| +-->BaseDir/Library_B/test/CMakeLists.txt
|
| cmake_minimum_required (VERSION 2.8)
|
| # add all files in the current directory
| file(GLOB TEST_B_SRCS "*.h" "*.cpp")
|
| # Create an executable file from sources, for both versions of the library
| add_executable(TEST_B ${TEST_B_SRCS})
|
| # Link this executable to the library it's testing
| target_link_libraries(TEST_B B)
|
+-->BaseDir/Application_1/CMakeLists.txt
| |
| | cmake_minimum_required (VERSION 2.8)
| | project (APP_1)
| |
| | # In case CMake is run from within BaseDir/Application_1 and not from BaseDir, I still want the outputs being placed under BaseDir/{lib|bin}/{Debug|Release}
| | # In this case, only executables are generated.
| | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin/${CMAKE_BUILD_TYPE}")
| |
| | include_directories(include)
| | add_subdirectory (src)
| |
| +-->BaseDir/Application_1/src/CMakeLists.txt
|
| cmake_minimum_required (VERSION 2.8)
|
| # add all files in the current directory
| file(GLOB APP_1_SRCS "*.cpp")
|
| # Create an executable file from sources
| add_executable(EXE_1 ${APP_1_SRCS})
|
| # This should automatically bring Library_A
| find_library(LIBRARY_B B PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../../Library_B)
| include_directories(${LIB_B_INCLUDE_DIRECTORIES})
| target_link_libraries(EXE_1 ${LIBRARY_B})
|
+-->BaseDir/Application_2/CMakeLists/CMakeLists.txt
|
| cmake_minimum_required (VERSION 2.8)
| project (APP_2)
|
| # In case CMake is run from within BaseDir/Application_2 and not from BaseDir, I still want the outputs being placed under BaseDir/{lib|bin}/{Debug|Release}
| # In this case, only executables are generated.
| set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin/${CMAKE_BUILD_TYPE}")
|
| include_directories(include)
| add_subdirectory (src)
|
+-->BaseDir/Application_2/src/CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
# add all files in the current directory
file(GLOB APP_2_SRCS "*.cpp")
# Create an executable file from sources
add_executable(EXE_2 ${APP_2_SRCS})
# Link this executable to the library it needs
find_library(LIBRARY_A A PATHS ${CMAKE_CURRENT_SOURCE_DIR}../../Library_A)
include_directories(${LIB_A_INCLUDE_DIRECTORIES})
target_link_libraries(EXE_2 ${LIBRARY_A})
Но безуспешно, потому что когда я запускаю из подкаталогов (например, BaseDir/Application_1), я просто получаю:
CMake Error: в этом проекте используются следующие переменные, но для них установлено значение NOTFOUND. Пожалуйста, установите их или убедитесь, что они установлены и проверены правильно в файлах CMake: LIBRARY_B
Я полагаю, что вызова "find_library()" недостаточно для того, что я хочу, то есть для загрузки всех настроек, содержащихся в проекте библиотек, включая не только саму библиотеку, но и каталоги include, добавленные этой библиотекой. Как именно используется метод find_library(), чтобы указать его местоположение проекта, создающего библиотеку, или фактическую результирующую библиотеку (" .a" или ".so")?
Итак, моё главное сомнение: это выполнимо? Если так, что я пропускаю? Должен ли я использовать что-то вроде find_package() или find_path ()? Как запустить синтаксический анализ файла конфигурации CMakeLists.txt из другого CMakeLists.txt того же уровня?
PS: мне действительно нужно рассмотреть возможность использования add_dependencies() в любой момент? Какой смысл этой команды, если нет?
1 ответ
Сначала общий совет: в CMake вы всегда будете пытаться строго отделить ваши каталоги сборки от ваших исходных каталогов. В частности, вы никогда не должны записывать файлы обратно в исходный каталог как часть процесса сборки. Напишите ваши сценарии, чтобы они также работали с исходным каталогом, монтируемым в файловой системе только для чтения. Хотя поначалу это может показаться произвольным ограничением, на самом деле это поможет вам избежать многих головных болей в долгосрочной перспективе.
Что касается вашей актуальной проблемы: find_library
это очень низкоуровневый инструмент для решения проблемы зависимости. Идея здесь заключается в том, что у вас есть двоичные файлы для сторонней библиотеки (которая сама ничего не знает о CMake), установленной где-то в системе, и вы знаете, что нужно найти их, просто проверив содержимое файловой системы. Здесь это не так, поскольку вы строите зависимости как часть одного и того же запуска конфигурирования CMake.
Вместо этого вы хотите напрямую зависеть от целей зависимости. Так что вместо этого:
find_library(LIBRARY_A A)
target_link_libraries(B ${LIBRARY_A})
Вы бы прямо написать
target_link_libraries(B A)
Это, конечно, работает только если A
является известной целью на данный момент, и это проблема, на которой вы должны сосредоточиться.
Пока вы продолжаете собирать библиотеки вместе в рамках одного и того же запуска CMake, это довольно просто: если цель добавляется родительским или одноуровневым каталогом, вы сможете сразу использовать ее. Что усложняет ситуацию в вашей ситуации, так это то, что вы также хотите иметь возможность создавать разные библиотеки изолированно. Это означает, что вам нужен механизм, который импортирует цель в вашу сборку, чтобы она выглядела так, как будто эта цель была снова произведена как часть того же самого запуска CMake.
Вы можете сделать это вручную, добавив импортированную цель и задав ее свойства интерфейса, или вы можете использовать механизм упаковки CMake, чтобы сделать это автоматически. Обратите внимание, что ни одно из этих действий не является тривиальным, поэтому вы можете начать с простого случая, когда все происходит за один прогон CMake, а затем добавить поддержку для отдельной сборки позже, когда вам станет удобнее использовать CMake.