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

Cra*_*ott 15 cmake

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

问题:

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

  1. 其中一个构建目标是命令行可执行文件,我将其命名为mycomp,其源代码在a mycompdir中,并且无法对该目录的内容进行任何修改.
  2. 该项目包含文本文件(我将其称为foo.mybar.my),需要mycomp运行它们以生成一组C++源代码和标题以及一些 CMakeLists.txt定义从这些源构建的库的文件.
  3. 同一项目中的其他构建目标需要链接到由这些生成的CMakeLists.txt文件定义的库.这些其他目标也有#include一些生成标题的源.

您可以将mycomp视为类似编译器的内容,将第2步中的文本文件视为某种源文件.这提出了一个问题,因为CMake CMakeLists.txt在配置时需要文件,但mycomp在构建时间之前不可用,因此在第一次运行时不能及时创建CMakeLists.txt文件.

NON-解答:

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

Cra*_*ott 17

问题的关键是在运行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)
Run Code Online (Sandbox Code Playgroud)

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

我已经尝试了上述方法,实际上它似乎在真实世界项目中起作用,提示原始问题,至少对于Unix Makefiles,Ninja,Xcode(OS X和iOS)和Visual Studio生成器.这种方法的一部分吸引力在于它只需要将适量的代码添加到顶级CMakeLists.txt文件中.尽管如此,仍有一些观察结果:

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

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

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

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

  • 使用ccache可以最大限度地减少因使用不同设置重建某些文件而导致的任何成本.实际上,我们发现使用ccache通常会使嵌套构建非常快,因为与主构建相比,它几乎没有变化.

  • 嵌套构建可能需要在某些平台上CMAKE_BUILD_WITH_INSTALL_RPATH设置,FALSE以便可以找到mycomp所需的任何库,而无需设置环境变量等.

  • ...在重新启动几次后,我想我找到了罪魁祸首。顶级 CMakeLists.txt 需要是“管理”的并且没有设置语言。(请记住,我使用的是一个非常简单的示例。)进行该更改似乎可以将构建正确地分成“两半”(对于遇到此问题的其他人)。 (2认同)