【问题标题】:CMake configuration to build a project from root or from subdirectoriesCMake 配置以从根目录或子目录构建项目
【发布时间】:2017-04-20 20:43:42
【问题描述】:

我最近开始使用 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 过程,构建结果(库或 bin)必须始终放在 BaseDir/{lib|bin}/{Debug/Release/etc} 下,永远不要放在库或应用程序子目录下.例如,这意味着 Library_B(依赖于 Library_A)或 Application_1(依赖于 Library_A 和 B)的链接必须查看 BaseDir/lib/{Debug/Release}。

我的目标是在 BaseDir 下拥有许多应用程序,因此如果不是真的需要,我希望避免每次都构建它们,只需要我想要的单个应用程序。

我查看了CMakeLists.txt files for multiple libraries and executablesCMake and finding other projects and their dependencies,但这与我在这里尝试实现的情况并不完全相同。

我尝试过类似以下的方法:

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 错误:此项目中使用了以下变量,但它们设置为 NOTFOUND。 请设置它们或确保它们在 CMake 文件中正确设置和测试: LIBRARY_B

我猜 "find_library()" 调用不足以满足我的要求,它基本上是加载库项目中包含的所有设置,不仅包括库本身,还包括该库添加的包含目录。 find_library() 的具体用法是什么,将其指向创建库的项目的位置,或实际生成的库(“.a”或“.so”)?

所以,我的主要疑问是:这种布局可行吗?如果是这样,我错过了什么?我应该使用 find_package() 还是 find_path() 之类的东西?如何从另一个“相同级别”CMakeLists.txt 触发解析 CMakeLists.txt 配置文件?

PS:我真的需要考虑在任何时候使用 "add_dependencies()" 吗?如果不是,该命令有什么意义?

【问题讨论】:

  • find_library 搜索 实际 库文件。与find_path 类似。 “对于我想要的,这基本上是加载库项目中包含的所有设置,不仅包括库本身,还包括该库添加的包含目录。” - CMake 能够导出 项目的二进制或安装树作为一个包。请参阅documentation guide 了解更多信息。
  • 谢谢。所以我想 "find_package()" 是我要走的路。

标签: cmake dependencies subdirectory


【解决方案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 运行的一部分再次生成的。

您可以通过添加imported target 并设置其接口属性来手动执行此操作,也可以使用CMake 的packaging mechanism 以自动方式完成此操作。请注意,这些都不是微不足道的,因此您可能希望从简单的情况开始,即一切都发生在一次 CMake 运行中,然后在您更习惯使用 CMake 后添加对分离构建的支持。

【讨论】:

  • 抱歉,我没听懂:“在 CMake 中,您总是希望尝试将构建目录与源目录严格分开”。我确实已经将源与构建分开了,还是您的意思是中间 obj?关于你刚刚做target_link_libraries(B A) 的评论,这实际上是我的基本实现所做的(我开始做的那个,它能够直接从根目录构建,find_library() + target_link_library() 是我修改的,看看它是否适用第二种情况(从子目录构建。
  • 顺便说一句,我尝试bold在测试之间的原始 CMakeLists.txt 中修改的行,但我不知道如何在代码块中执行此操作:- (我尝试使用
     标签,但我想我错过了一些东西。如果有人可以给我一个指针,我将编辑原始问题以清楚起见
  • @A.Palma 我说的是find_library(LIBRARY_A A PATHS ../../Library_A)。您不应假设二进制文件位于相对于源树的位置。源树和二叉树应该是不同的和不相关的。并且构建永远不应该修改源代码树。
  • 哦,感谢您注意到这一点。确实(并不是因为我想在战斗结束后看起来像个将军:-))这是一个错字,这行真的看起来像:find_library(LIBRARY_A A PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ ../../Library_A)
  • @A.Palma 这也不是更好,因为它仍在相对于源路径的位置寻找二进制文件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 1970-01-01
  • 2018-05-27
相关资源
最近更新 更多