我有一个有趣的先有鸡还是先有蛋的问题以及一个潜在的解决方案(请参阅我发布的答案),但该解决方案以一种不寻常的方式使用 CMake。欢迎更好的替代方案或评论。
问题:
该问题的简单版本可以描述为具有以下特征的单个 CMake 项目:
- 构建目标之一是一个命令行可执行文件,我将其称为mycomp,其来源是在
mycompdir
并且无法对该目录的内容进行任何修改。
- 该项目包含文本文件(我将其称为
foo.my
and bar.my
)哪个需要mycomp在它们上运行以生成一组 C++ 源代码和标头以及一些CMakeLists.txt
定义从这些源构建的库的文件。
- 同一项目中的其他构建目标需要链接到生成的库定义的库
CMakeLists.txt
文件。这些其他目标也有来源#include
一些生成的标头。
你可以想到mycomp作为编译器之类的东西,而步骤 2 中的文本文件作为某种源文件。这提出了一个问题,因为 CMake 需要CMakeLists.txt
配置时的文件,但是mycomp直到构建时才可用,因此在第一次运行时不可用以创建CMakeLists.txt
尽早归档。
无答案:
通常,基于ExternalProject的超级构建安排将是一个潜在的解决方案,但上面是我正在处理的实际项目的相当大的简化,我没有自由将构建分割成不同的部分或执行其他大型任务规模重组工作。
问题的关键是需要mycomp当 CMake 运行时可用,以便生成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 Makefiles、Ninja、Xcode(OS X 和 iOS)和 Visual Studio 生成器来说是这样。这种方法的部分吸引力在于它只需要在顶层添加适量的代码CMakeLists.txt
文件。尽管如此,还是应该提出一些意见:
- 如果编译器或链接器命令为
mycomp
并且其来源在嵌套构建和主构建之间有任何不同,mycomp
目标最终在 (d) 处第二次重建。如果没有差异,mycomp
不交叉编译时只构建一次,这正是我们想要的。
- 我认为没有简单的方法可以将与传递到顶级 CMake 运行的参数完全相同的参数传递给 (b) 处的 CMake 嵌套调用(基本上是所描述的问题)here https://stackoverflow.com/questions/10205986/how-to-capture-cmake-command-line-arguments)。阅读
CMakeCache.txt
不是一个选项,因为它在第一次调用时不存在,并且无论如何也不会为您提供当前运行中的任何新的或更改的参数。我能做的最好的事情就是设置那些我认为可能会被使用的 CMake 变量,这可能会影响编译器和链接器命令mycomp
。当我遇到我发现需要的变量时,可以通过添加越来越多的变量来解决这个问题,但这并不理想。
- 当重新使用相同的二进制目录时,我们依赖 CMake,直到生成阶段才开始将其任何文件写入二进制目录(好吧,至少直到 (c) 处的构建完成之后)。对于测试的发电机来说,看起来我们没问题,但我不知道是否all发电机开启all平台也遵循这种行为(我无法测试每个组合来找出答案!)。这是我最关心的部分。如果任何人都可以通过推理和/或证据确认这对于所有生成器和平台都是安全的,那将是有价值的(如果您想将其作为单独的答案来解决,则值得投票)。
UPDATE:在对具有不同 CMake 熟悉程度的人员的多个实际项目使用上述策略后,可以得出一些结论。
让嵌套构建重复使用与主构建相同的构建目录有时会导致问题。具体来说,如果用户在嵌套构建完成之后但在主构建完成之前终止 CMake 运行,则CMakeCache.txt
文件留下EARLY_BUILD
set to ON
。这使得所有后续的 CMake 运行都像嵌套构建一样,因此主构建基本上会丢失,直到CMakeCache.txt
文件被手动删除。项目之一的某个地方可能出现错误CMakeLists.txt
文件也可能会导致类似的情况(未经证实)。在其自己单独的构建目录中执行嵌套构建效果非常好,但没有出现此类问题。
嵌套构建可能应该是Release而不是Debug。如果不重新使用与主构建相同的构建目录(现在我建议这样做),我们不再关心尝试避免编译相同的文件两次,所以不妨使mycomp尽可能快。
使用ccache https://crascit.com/2016/04/09/using-ccache-with-cmake/以便最大限度地减少因使用不同设置两次重建某些文件而产生的成本。实际上,我们发现使用 ccache 通常会使嵌套构建非常快,因为与主构建相比它很少发生变化。
嵌套构建可能需要有CMAKE_BUILD_WITH_INSTALL_RPATH
set to FALSE
在某些平台上,以便任何库mycomp无需设置环境变量等即可找到需求
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)