Конфигурация 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.

Другие вопросы по тегам