立即构建工具,以便稍后在相同的CMake运行中使用

时间:2016-03-18 12:46:39

标签: cmake

我有一个有趣的鸡蛋问题和一个潜在的解决方案(请参阅我发布的答案),但该解决方案以不寻常的方式使用CMake。欢迎更好的替代方案或评论。

问题:

问题的简单版本可以描述为具有以下特征的单个CMake项目:

  1. 其中一个构建目标是一个命令行可执行文件,我将其命名为 mycomp ,其源代码位于mycompdir并对该目录的内容进行任何修改是不可能的。
  2. 该项目包含需要 mycomp 运行的文本文件(我称之为foo.mybar.my),以生成一组C ++源代码和标题以及一些CMakeLists.txt文件定义从这些源构建的库。
  3. 同一项目中的其他构建目标需要链接到那些生成的CMakeLists.txt文件定义的库。这些其他目标也包含#include部分生成标题的来源。
  4. 您可以将 mycomp 视为编译器,将步骤2中的文本文件视为某种源文件。这会出现问题,因为CMake在配置时需要CMakeLists.txt个文件,但 mycomp 在构建时间之前不可用,因此在第一次运行时无法创建{{1文件足够早。

    NON-解答:

    通常情况下,基于外部项目的超级建筑安排可能是一个潜在的解决方案,但上面是我正在处理的实际项目的一个相当大的简化,我没有自由将构建拆分成不同的部分或进行其他大规模的重组工作。

1 个答案:

答案 0 :(得分:14)

问题的关键是在运行CMake时 mycomp 是否可用,以便可以创建生成的CMakeLists.txt文件,然后使用add_subdirectory()将其拉入。实现此目的的一种可能方法是使用execute_process()从主构建中运行嵌套的cmake-and-build。嵌套的cmake-and-build将使用与顶级CMake运行完全相同的源和二进制目录(除非交叉编译)。主顶级CMakeLists.txt的一般结构如下:

# Usual CMakeLists.txt setup stuff goes here...

if(EARLY_BUILD)
    # This is the nested build and we will only be asked to
    # build the mycomp target (see (c) below)
    add_subdirectory(mycompdir)

    # End immediately, we don't want anything else in the nested build
    return()
endif()

# This is the main build, setup and execute the nested build
# to ensure the mycomp executable exists before continuing

# (a) When cross compiling, we cannot re-use the same binary dir
#     because the host and target are different architectures
if(CMAKE_CROSSCOMPILING)
    set(workdir "${CMAKE_BINARY_DIR}/host")
    execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${workdir}")
else()
    set(workdir "${CMAKE_BINARY_DIR}")
endif()

# (b) Nested CMake run. May need more -D... options than shown here.
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}"
                        -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
                        -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}
                        -DEARLY_BUILD=ON
                        ${CMAKE_SOURCE_DIR}
               WORKING_DIRECTORY "${workdir}")

# (c) Build just mycomp in the nested build. Don't specify a --config
#     because we cannot know what config the developer will be using
#     at this point. For non-multi-config generators, we've already
#     specified CMAKE_BUILD_TYPE above in (b).
execute_process(COMMAND ${CMAKE_COMMAND} --build . --target mycomp
                WORKING_DIRECTORY "${workdir}")

# (d) We want everything from mycompdir in our main build,
#     not just the mycomp target
add_subdirectory(mycompdir)

# (e) Run mycomp on the sources to generate a CMakeLists.txt in the
#     ${CMAKE_BINARY_DIR}/foobar directory. Note that because we want
#     to support cross compiling, working out the location of the
#     executable is a bit more tricky. We cannot know whether the user
#     wants debug or release build types for multi-config generators
#     so we have to choose one. We cannot query the target properties
#     because they are only known at generate time, which is after here.
#     Best we can do is hardcode some basic logic.
if(MSVC)
    set(mycompsuffix "Debug/mycomp.exe")
elseif(CMAKE_GENERATOR STREQUAL "Xcode")
    set(mycompsuffix "Debug/mycomp")
else()
    set(mycompsuffix "mycomp")
endif()
set(mycomp_EXECUTABLE "${workdir}/mycompdir/${mycompsuffix}")
execute_process(COMMAND "${mycomp_EXECUTABLE}" -outdir foobar ${CMAKE_SOURCE_DIR}/foo.my ${CMAKE_SOURCE_DIR}/bar.my)

# (f) Now pull that generated CMakeLists.txt into the main build.
#     It will create a CMake library target called foobar.
add_subdirectory(${CMAKE_BINARY_DIR}/foobar ${CMAKE_BINARY_DIR}/foobar-build)

# (g) Another target which links to the foobar library
#     and includes headers from there
add_executable(gumby gumby.cpp)
target_link_libraries(gumby PUBLIC foobar)
target_include_directories(gumby PUBLIC foobar)

如果我们不再使用(b)和(c)中用于主构建的相同二进制目录,我们最终会构建mycomp两次,我们显然希望避免。对于交叉编译,我们无法避免这种情况,因此在这种情况下,我们会在单独的二进制目录中构建mycomp工具。

我已经尝试过上述方法,实际上它似乎可以在真实世界项目中工作,这个项目提出了原始问题,至少对于Unix Makefile,Ninja,Xcode(OS X和iOS)和Visual Studio生成器。这种方法的一部分吸引力在于它只需要将适量的代码添加到顶级CMakeLists.txt文件中。然而,有一些观察应该做出:

  • 如果mycomp及其源代码的编译器或链接器命令在嵌套构建版本和主构建版本之间有任何不同,则mycomp目标最终会在(d)处第二次重建。如果没有差异,mycomp只会在不进行交叉编译时生成一次,这正是我们想要的。
  • 我认为没有简单的方法可以将完全相同的参数传递给(b)的CMake嵌套调用,因为传递给顶级CMake运行(基本上是here所描述的问题)。阅读CMakeCache.txt不是一个选项,因为它在第一次调用时不会存在,并且无论如何它都不会从当前运行中提供任何新的或更改的参数。我能做的最好的事情是设置我认为可能会被使用的CMake变量,这些变量可能会影响mycomp的编译器和链接器命令。这可以通过添加越来越多的变量来解决,因为我遇到了我发现需要的变量,但这并不理想。
  • 当重新使用相同的二进制目录时,我们依赖于CMake没有开始将其任何文件写入二进制目录,直到生成阶段(好吧,至少直到(c)的构建完成之后)。对于测试的生成器,似乎我们没问题,但我不知道所有平台上的所有生成器是否也遵循这种行为(我可以' t测试每一个组合找出来!)。这是让我最关心的部分。如果任何人都可以通过推理和/或证据确认这对所有发电机和平台都是安全的,那么这将是有价值的(如果你想作为一个单独的答案解决这个问题,那么值得投票)。

更新:在对CMake熟悉程度不同的工作人员的一些实际项目中使用上述策略后,可以进行一些观察。

  • 使嵌套构建重用与主构建相同的构建目录有时会导致问题。具体来说,如果用户在嵌套构建完成之后但在主构建之前杀死了CMake运行,则CMakeCache.txt文件将保留EARLY_BUILD设置为ON。这使得所有后续CMake运行都像嵌套构建一样,因此在手动删除CMakeCache.txt文件之前,主构建基本上会丢失。项目的CMakeLists.txt文件中的某个地方的错误可能也会导致类似的情况(未经证实)。在它自己的单独构建目录中执行嵌套构建虽然没有这样的问题,但效果很好。

  • 嵌套版本应该是 Release 而不是 Debug 。如果没有重新使用与主构建相同的构建目录(现在我推荐的那样),我们不再关心尝试避免两次编译同一个文件,那么也可以使 mycomp 尽快。

  • Use ccache因此,使用不同设置重建某些文件两次所产生的任何费用都会降至最低。实际上,我们发现使用ccache通常会使嵌套构建非常快,因为与主构建相比它很少发生变化。

  • 嵌套构建可能需要在某些平台上将CMAKE_BUILD_WITH_INSTALL_RPATH设置为FALSE,以便无需设置环境变量即可找到任何库 mycomp 需求等等。