我有一个有趣的鸡蛋问题和一个潜在的解决方案(请参阅我发布的答案),但该解决方案以不寻常的方式使用CMake。欢迎更好的替代方案或评论。
问题:
问题的简单版本可以描述为具有以下特征的单个CMake项目:
mycompdir
并对该目录的内容进行任何修改是不可能的。foo.my
和bar.my
),以生成一组C ++源代码和标题以及一些CMakeLists.txt
文件定义从这些源构建的库。CMakeLists.txt
文件定义的库。这些其他目标也包含#include
部分生成标题的来源。您可以将 mycomp 视为编译器,将步骤2中的文本文件视为某种源文件。这会出现问题,因为CMake在配置时需要CMakeLists.txt
个文件,但 mycomp 在构建时间之前不可用,因此在第一次运行时无法创建{{1文件足够早。
NON-解答:
通常情况下,基于外部项目的超级建筑安排可能是一个潜在的解决方案,但上面是我正在处理的实际项目的一个相当大的简化,我没有自由将构建拆分成不同的部分或进行其他大规模的重组工作。
答案 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
只会在不进行交叉编译时生成一次,这正是我们想要的。CMakeCache.txt
不是一个选项,因为它在第一次调用时不会存在,并且无论如何它都不会从当前运行中提供任何新的或更改的参数。我能做的最好的事情是设置我认为可能会被使用的CMake变量,这些变量可能会影响mycomp
的编译器和链接器命令。这可以通过添加越来越多的变量来解决,因为我遇到了我发现需要的变量,但这并不理想。更新:在对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 需求等等。