8

我对 CMake 的生成文件阶段很慢有一个问题,这类似于这个未回答的问题:

CMake 生成 makefile 的速度很慢

我的项目由一个顶级CMakeLists.txt文件组成,该文件add_subdirectory()用于为各个库和可执行组件添加各种子项目。

对于给定的组件,该CMakeLists.txt文件包含以下内容:

add_library(mylib SHARED
  sourceFile1.cpp
  sourceFile2.cpp
  ...
)

我可以使用以下方法仅构建该目录的内容:

make mylib

如果我修改CMakeLists.txt子目录中的文件(作为从纯 Makefile 迁移到 CMake 的一部分,我已经做了很多工作)然后make正确运行它重新运行 CMake 以更新配置,就像我运行make rebuild_cache.

但是,我注意到它实际上重新配置了整个项目。我真的希望 CMake 足够聪明,知道它只需要在当前目录和子目录中重新生成 Makefile。

有没有更好的方法来构建 CMake 项目来实现这一目标? 我看到有些人在每个子项目中为每个 CMakeLists.txt使用project() 。一般来说,这是个好主意吗?

或者/另外是否有一些方法可以加快 CMake 的生成步骤?(目前我有 60 多岁以上)

如果您想讨论为什么 CMake 本身应该或不应该能够并行运行(想象一下cmake -j),则可以加分。


我添加了 meson-build 标签作为适度的赏金,但仅此一项还没有引起足够的关注来保证答案。正是这种问题可能会导致人们将构建系统切换到介子构建(假设它没有类似的问题)或类似的东西。

正确的答案可能是如果不将源代码修改为 CMake 就无法完成。为了获得赏金,我需要解释 CMake 的工作原理和/或存在缺陷的地方。


澄清:在我的情况下,生成步骤很慢。配置本身足够快,但 CMake 在输出"-- 配置完成""-- 生成完成"之间挂起很长一段时间。

对于完整的缓存重建,我运行:

make -n rebuild_cache
运行 CMake 以重新生成构建系统...
使用 Makefile 生成器
-- FOOBAR_VERSION: 01.02.03
-- 配置完成
-- 生成完成
-- 构建文件已写入:/home/brucea/work/depot/emma/main/cmake
实际 74.87
用户 1.74
系统 1.02

在引擎盖下运行:

cmake -H<source dir> -B<build dir>

我认为-B是 的同义词--build。文档中没有正确描述这两个选项。-H是源目录的根目录(与--help您相信的文档不同)。

得到"Configuring done"的输出很快,但从那里开始很慢:

例如,

15:44:14 execve("/usr/local/bin/cmake",
>grep 生成 cmake_strace.log
>grep “配置” cmake_strace.log
15:44:15 write(1, "-- 配置完成\n", 20-- 配置完成
15:45:01 write(1, "-- 生成完成\n", 19-- 生成完成
>grep “构建文件” cmake_strace.log
15:45:22 write(1, "-- 构建文件已被写入"..., 77-- 构建文件已写入:

如果在子目录中编辑单个 CMakeLists.txt 文件,然后运行make -n,它将运行:

cd /home/project/cmake && /usr/local/bin/cmake -H/home/project/cmake -B/home/project/cmake --check-build-system CMakeFiles/Makefile.cmake 0

--check-build-system 是另一个未记录的选项。

效果是一样的——重新生成整个构建系统,而不仅仅是当前子树。源内构建和源外构建之间的行为没有区别。

如果我运行跟踪,例如:

strace -r cmake --trace -H/home/project/cmake -B/home/project/cmake 2>&1 | tee cmake_rebuild_cache.log
sort -r cmake_rebuild_cache.log | uniq

花费的大部分时间似乎都花在(或之间)打开、访问和取消链接调用上。

每个任务的长度变化很大,但数量庞大。我不知道 Labels.json 和 Labels.txt 文件是关于什么的(CMake 内部的东西)。

一次运行:

    49.363537 打开(“/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2bar.testViewingSource1.dir/build.make”,O_RDONLY)= 5
     1.324777 访问(“/home/projectbar/main/test/performance/CMakeFiles/performancetest.chvcthulhu.testChvcthulhuPerformance2.dir”,R_OK)= 0
     0.907807 访问(“/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2bar.testPeripheralConnection2.dir”,R_OK)= 0
     0.670272 unlink("/home/projectbar/main/src/foo2bar/Foo2Bar/CMakeFiles/foo2bar_lib.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.600272 访问(“/home/projectbar/main/test/foo2bar/testFilesModel2.ok”,R_OK)= 0
     0.599010 访问(“/home/projectbar/main/test/hve2snafu/testInvalidByte2c.ok”,R_OK)= 0
     0.582466 读取(5,“openjdk 版本 \”1.8.0_71\“\nOpenJ”...,1024)= 130
     0.570540 writev(3, [{"# CMAKE 生成的文件: DO NOT E"..., 8190}, {"M", 1}], 2) = 8191
     0.553576 关闭(4)= 0
     0.448811 unlink("/home/projectbar/main/test/snafu2hve/CMakeFiles/test2.snafu2hve.testNoProbes2.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.431559 访问(“/home/projectbar/main/src/foo2bar/Foo2Bar/CMakeFiles/foo2bar_lib.dir”,R_OK)= 0
     0.408003 unlink("/home/projectbar/main/test/lachesis/CMakeFiles/test2.lachesis.testBadSequenceNumber1.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.407120 write(4, "# 语言集"..., 566) = 566
     0.406674 write(3, "# CMAKE 生成的文件:不要 E"..., 675) = 675
     0.383892 读取(3,“ewingPeriod.cpp.o -c /home/bruce”...,8191)= 8191
     0.358490 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.chvdiff.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)

再次运行相同的命令:

     2.009451 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.lachesis.dir/Labels.json") = -1 ENOENT (没有这样的文件或目录)
) = 20
) = 19
     1.300387 访问(“/home/projectbar/main/test/chvedit/CMakeFiles/test2.chvedit.tefooultiMatchFactoringEdit2.dir”,R_OK)= 0
     1.067957 访问(“/home/projectbar/main/test/chvedit/CMakeFiles/test2.chvedit.tefooultiMatchFactoringEdit2.dir”,R_OK)= 0
) = 1
     0.885854 unlink("/home/projectbar/main/src/gorkyorks2bar/CMakeFiles/doxygen.correct.gorkyorks2bar.dir/Labels.json") = -1 ENOENT(没有这样的文件或目录)
     0.854539 访问(“/home/projectbar/main/test/reportImpressions/ReportImpressions/CMakeFiles/testsuite1_reportImpressions.dir”,R_OK)= 0
     0.791741 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.bar_models.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.659506 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.647838 unlink("/home/projectbar/main/test/libyar/YarModels/CMakeFiles/testsuite1_yarmodels.dir/Labels.txt") = -1 ENOENT (没有这样的文件或目录)
     0.620511 unlink("/home/projectbar/main/test/libyar/YarModels/CMakeFiles/testsuite1_yarmodels.dir/Labels.json") = -1 ENOENT (没有这样的文件或目录)
     0.601942 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.lachesis.dir/Labels.txt") = -1 ENOENT (没有这样的文件或目录)
     0.591871 访问(“/home/projectbar/main/src/runbardemo/simple_demo/CMakeFiles”,R_OK)= 0
     0.582448 写入(3,“CMAKE_PROGRESS_1 = \n\n”,21)= 21
     0.536947 写(3,“CMAKE_PROGRESS_1 = \n\n”,21)= 21
     0.499758 unlink("/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2bar.testInputDirectory1.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.458120 unlink("/home/projectbar/main/test/yak2dcs/CMakeFiles/test2.yak2dcs.testsuite2.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.448104 unlink("/home/projectbar/main/test/reportImpressions/CMakeFiles/test2.reportImpressions.dir/progress.make.tmp") = -1 ENOENT(没有这样的文件或目录)
     0.444344 访问(“/home/projectbar/main/src/bananas/CMakeFiles/bin.bananas.dir”,R_OK)= 0
     0.442685 unlink("/home/projectbar/main/test/rvedit/CMakeFiles/test2.rvedit.tefooissingOptionValue.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.425604 unlink("/home/projectbar/main/test/listdcs/CMakeFiles/test2.listdcs.testListCalls5.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)
     0.391163 访问(“/home/projectbar/main/src/siedit/CMakeFiles/siedit.dir”,R_OK)= 0
     0.362171 访问(“/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2emma.testHowResults6.dir”,R_OK)= 0

请注意,忍者生成器要快得多(尽管仍然不够出色)。例如,

/usr/bin/time -p ninja rebuild_cache
忍者:警告:多个规则生成 ../src/ams2yar/ams2yar。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
忍者:警告:多个规则生成 ../src/vox/vox。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/bananas/bananas。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
忍者:警告:多个规则生成 ../src/fidlertypes2fidlerinfo/fidlertypes2fidlerinfo。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/mkrundir/mkrundir。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/runyar/runyar。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/runyardemo/runyardemo。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
[1/1] 运行 CMake 重新生成构建系统...
发电机=忍者
-- FOO_VERSION: 01.02.03
-- 配置完成
-- 生成完成
-- 构建文件已写入:/home/project/cmake/build
真正的 12.67
用户 1.01
系统 0.31

请注意,该项目还没有为 Ninja 做好准备,因为存在如下错误:

ninja: warning: multiple rules generate ../src/runfoobardemo/runfoobardemo. builds involving this target will not be correct; continuing anyway [-w dupbuild=warn]

ninja: error: dependency cycle: ../src/foobar -> ../src/foobar/CMakeFiles/foobar -> ../src/ams2emma/foobar

待解决。这个问题实际上是关于为什么Makefile生成器很慢。我不确定 Ninja 显示的问题是这里有用的提示还是红鲱鱼。


构建具有更多优化的 CMake 并没有帮助。

根据我的跟踪输出它和时间的输出,它不太可能会。用户时间以及因此在 CMake 代码本身中花费的时间非常少。(参见例如“真实”、“用户”和“系统”在 time(1) 的输出中是什么意思?)。

这是我为完整性所做的尝试:

export CXX_FLAGS="-O3 -ftree-vectorise -msse2"
cmake -DCMAKE_BUILD_TYPE=RELEASE

实际上使用更优化的 CMake确实使配置部分更快,但在我的情况下,生成部分很慢。从时间上看,这一步似乎是受 I/O 限制的。


我决定调查 Florian 的想法,即使用文件流的内存 insteam 存储临时文件可能会有所作为。

我决定尝试简单的方法并破解 CMake 将 .tmp 文件写入 RAM 磁盘。

然后我全力以赴,尝试在 RAM 磁盘上生成构建系统:

sudo mkdir /mnt/ramdisk
sudo mount -t tmpfs -o size=512m tmpfs /mnt/ramdisk
/usr/bin/time -p cmake -H/<source> -B/mnt/ramdisk/build

我很惊讶地发现这对挂钟时间没有任何影响:

real 59.61
user 1.55
sys 0.62
>du -sh /mnt/ramdisk/build/
4.4M    /mnt/ramdisk/build/

与 ramfs 类似:

real 51.09
user 1.58
sys 0.50

这里会发生什么?我在猜测子流程,但我无法确定哪些子流程正在消耗挂钟时间(如果有的话)。他们看起来很短暂。


为了完整起见,以下是 perf 的一些输出(使用 构建的 CMake -fno-omit-frame-pointer):

perf record -g --call-graph dwarf cmake -H<source> -B<build>
perf report -g graph
样本:17K 的事件“周期”,事件计数(大约):14363392067
  儿童自我命令共享对象符号
+ 65.23% 0.00% cmake cmake [.] do_cmake
+ 65.02% 0.00% cmake cmake [.] cmake::Run
+ 60.32% 0.00% cmake cmake [.] main
+ 59.82% 0.00% cmake libc-2.17.so [.] __libc_start_main
+ 57.78% 0.00% cmake cmake [.] _start
+ 55.04% 0.00% cmake cmake [.] cmGlobalUnixMakefileGenerator3::Generate
+ 54.56% 0.00% cmake cmake [.] cmake::Generate
+ 49.90% 0.00% cmake cmake [.] cmGlobalGenerator::Generate
+ 38.87% 0.02% cmake cmake [.] cmLocalUnixMakefileGenerator3::Generate
+ 18.65% 0.01% cmake cmake [.] cmMakefileTargetGenerator::WriteTargetBuildRules
+ 17.05% 0.02% cmake cmake [.] cmMakefile::ExecuteCommand
+ 16.99% 0.01% cmake cmake [.] cmMakefile::ReadListFile
+ 16.84% 0.01% cmake cmake [.] cmCommand::InvokeInitialPass
+ 16.79% 0.00% cmake cmake [.] cmMakefile::Configure
+ 14.71% 0.00% cmake cmake [.] cmMakefile::ConfigureSubDirectory
+ 14.67% 0.05% cmake cmake [.] cmMacroHelperCommand::InvokeInitialPass
+ 14.27% 0.02% cmake cmake [.] cmMakefileUtilityTargetGenerator::WriteRuleFiles
+ 13.91% 0.00% cmake cmake [.] cmGlobalGenerator::Configure
+ 13.50% 0.05% cmake cmake [.] cmOutputConverter::Convert
+ 13.48% 0.00% cmake cmake [.] cmAddSubDirectoryCommand::InitialPass
+ 13.46% 0.00% cmake cmake [.] cmMakefile::AddSubDirectory
+ 12.91% 0.00% cmake cmake [.] cmGlobalUnixMakefileGenerator3::Configure
+ 12.82% 0.00% cmake cmake [.] cmake::ActualConfigure
+ 10.90% 0.00% cmake cmake [.] cmake::Configure
+ 10.55% 0.02% cmake cmake [.] cmMakefileTargetGenerator::WriteObjectRuleFiles
+ 10.35% 0.09% cmake cmake [.] cmLocalUnixMakefileGenerator3::WriteMakeRule
+ 9.76% 0.03% cmake cmake [.] cmMakefileTargetGenerator::WriteObjectBuildFile
+ 7.97% 0.00% cmake cmake [.] cmMakefileLibraryTargetGenerator::WriteRuleFiles
+ 7.93% 0.00% cmake cmake [.] cmMakefileExecutableTargetGenerator::WriteRuleFiles
+ 7.88% 0.00% cmake cmake [.] cmLocalUnixMakefileGenerator3::WriteLocalMakefile
+ 7.68% 0.02% cmake [kernel.kallsyms] [k] sysret_audit
+ 7.60% 0.05% cmake [kernel.kallsyms] [k] __audit_syscall_exit
+ 7.40% 0.08% cmake cmake [.] cmsys::SystemTools::CollapseFullPath

perf report -g graph -no-children

+ 2.86% cmake libc-2.17.so [.] _int_malloc
+ 2.15% cmake libc-2.17.so [.] __memcpy_ssse3_back
+ 2.11% cmake [kernel.kallsyms] [k] find_next_bit
+ 1.84% cmake libc-2.17.so [.] __memcmp_sse4_1
+ 1.83% cmake libc-2.17.so [.] _int_free
+ 1.71% cmake libstdc++.so.6.0.20 [.] std::__ostream_insert >
+ 1.18% cmake libstdc++.so.6.0.20 [.] std::basic_string, std::allocator >::~basic_string
+ 1.13% cmake libc-2.17.so [.] malloc
+ 1.12% cmake cmake [.] cmOutputConverter::Shell__ArgumentNeedsQuotes
+ 1.11% cmake libstdc++.so.6.0.20 [.] std::string::compare
+ 1.08% cmake libc-2.17.so [.] __strlen_sse2_pminub
+ 1.05% cmake cmake [.] std::string::_S_construct
+ 1.04% cmake cmake [.] cmsys::SystemTools::ConvertToUnixSlashes
+ 0.97% cmake cmake [.] yy_get_previous_state
+ 0.87% cmake cmake [.] cmOutputConverter::Shell__GetArgument
+ 0.76% cmake libstdc++.so.6.0.20 [.] std::basic_filebuf >::xsputn
+ 0.75% cmake libstdc++.so.6.0.20 [.] std::string::size
+ 0.75% cmake cmake [.] cmOutputConverter::Shell__SkipMakeVariables
+ 0.74% cmake cmake [.] cmOutputConverter::Shell__CharNeedsQuotesOnUnix
+ 0.73% cmake [kernel.kallsyms] [k] mls_sid_to_context
+ 0.72% cmake libstdc++.so.6.0.20 [.] std::basic_string, std::allocator >::basic_string
+ 0.71% cmake cmake [.] cmOutputConverter::Shell__GetArgumentSize
+ 0.65% cmake libc-2.17.so [.] malloc_consolidate
+ 0.65% cmake [kernel.kallsyms] [k] mls_compute_context_len
+ 0.65% cmake cmake [.] cmOutputConverter::Shell__CharNeedsQuotes
+ 0.64% cmake cmake [.] cmSourceFileLocation::Matches
+ 0.58% cmake cmake [.] cmMakefile::ExpandVariablesInStringNew
+ 0.57% cmake cmake [.] std::__deque_buf_size
+ 0.56% cmake cmake [.] cmCommandArgument_yylex
+ 0.55% cmake cmake [.] std::vector >::size
+ 0.54% cmake cmake [.] cmsys::SystemTools::SplitPath
+ 0.51% cmake libstdc++.so.6.0.20 [.] std::basic_streambuf >::xsputn
4

1 回答 1

6

有很多方面定义了 CMake 的配置和生成步骤持续时间(除了您在 CMakeLists.txt 文件中实际执行的操作;例如您的主机系统、您的工具链以及您正在使用的 CMake 版本/发行版)。

所以我试着专注于你的具体问题。

仅为子目录重建/重写makefile ?

首先:Usingadd_subdirectory()有利于构建 CMake 代码。但是您必须记住,您始终可以更改子目录中的全局 CMake 属性,并且这些子目录中的目标可以具有交叉依赖关系。

那么 CMake 做了什么(考虑到CMakeLists.txt这里讨论的“我已经触及了子目录中的一个文件”的情况):

  • 如果CMakeLists.txt文件被更改,它会再次遍历CMakeLists.txt文件的完整层次结构并在内存中再次重建构建环境。
  • 现在它临时重新创建所有必要的构建/制作文件并检查它们是否与现有文件不同(请参阅 参考资料cmGeneratedFileStreamBase::Close())。
  • 如果文件已更改,它将用新文件替换现有文件。

此行为是必要的,因为即使只有子目录的CMakeLists.txt文件已更改,任何 makefile 都可能更改,并且已对其进行了优化,以防止在实际make步骤期间进行不必要的重建(来自接触的 makefile)。

有什么方法可以加快 CMake 的生成步骤吗?

所以是的,它确实暂时重写了所有的makefile(这可能很慢),不,你不能add_subdirectory()在只使用更改的子目录时最小化它。

在 CMake 自己的代码中,未来一种可能的性能优化可能是对临时文件使用内存流而不是文件流。

@BruceAdams 通过对生成的 makefile 环境使用 RAM 磁盘进行了测试,但没有任何效果。

是的,CMake 生成cmake_check_build_system的规则与规则几乎相同,rebuild_cache是的 used-B和options 是 CMake 内部命令行选项,因此未记录(即使在 Stack Overflow 上经常提到,例如在我的答案之一中-H--check-build-system

帮助我加快配置/生成速度的原因是重建 CMake 本身,它提供了比正常发行版更多的优化选项,并使用 64 位工具链而不是目前仍在发行的 32 位版本。

以下是我的 Windows PC 上的一些测试结果(使用下面找到的带有 100 个子目录/库的 CMake 测试脚本),始终使用相同的 MSYS 环境,但相同 CMake 源代码的不同 CMake 编译:

  1. 官方 CMake 3.2.2 版本:

    $ time -p cmake -G "MSYS Makefiles" ..
    [...]
    real 43.93
    user 0.00
    sys 0.03
    
  2. 使用mingw32GNU 4.8.1重建 CMake 3.2.2

    cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3" -G "MSYS Makefiles" ..
    

    并得到

    $ time -p /c/temp/cmake-3.2.2/MSYS32/bin/cmake.exe -G "MSYS Makefiles" ..
    [...]
    real 41.37
    user 0.01
    sys 0.04
    

    我的防病毒软件关闭时也是如此:

    $ time -p /c/temp/cmake-3.2.2/MSYS32/bin/cmake.exe -G "MSYS Makefiles" ..
    [...]
    real 20.98
    user 0.00
    sys 0.04
    
  3. 使用mingw-w64GNU 5.3.0重建 CMake 3.2.2

    $ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-march=native -m64 -Ofast  -flto" -G "MSYS Makefiles" ..
    

    并得到

    $ time -p /c/temp/cmake-3.2.2/MSYS64/bin/cmake.exe -G "MSYS Makefiles" ..
    [...]
    real 25.59
    user 0.00
    sys 0.04
    

    我的防病毒软件关闭时也是如此:

    $ time -p /c/temp/cmake-3.2.2/MSYS64/bin/cmake.exe -G "MSYS Makefiles" ..
    [...]
    real 6.95
    user 0.00
    sys 0.03
    

总而言之,我看到两个主要影响:

第一个:配置步骤可以通过选择 64 位版本来加快速度,并针对您的处理器平台进行优化(您当然必须找到一个通用基础-march=...-mtune=...所有项目的构建 PC)。

第二:生成步骤主要可以通过在 CMake 之外搜索可能的文件 I/O 瓶颈来加快。在我的情况下,告诉防病毒软件在我每次读/写时不要检查工具链并构建目录确实加快了速度。

备注:我确认了@BruceAdams 测试结果,编译器的自动矢量化(默认为-O3or -Ofast)无法对 CMake 源代码在多个进程/多个内核上运行的能力做很多事情。

有没有更好的方法来构建 CMake 项目来实现这一目标?

是的,如果你知道你的 CMake 脚本代码的某个子树只生成一个库并且没有依赖项,你可以使用ExternalProject_Add(). 是的,对大型 CMake 项目也有类似的担忧,这被视为一种很好的“现代 CMake”实践(另请参见下面的参考资料)。

参考

我用来重现您的问题的方法

只是为了完整性,如果有人想对照他/她自己的数字检查这些数字,这是我的测试代码:

cmake_minimum_required(VERSION 3.0)

project(CMakeTest CXX)

#set_property(GLOBAL PROPERTY GLOBAL_DEPENDS_DEBUG_MODE 1)

set(_idx 1)

while (_idx LESS 100)
    math(EXPR _next_idx "${_idx} + 1")
    if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib${_idx}")
        file(MAKE_DIRECTORY "lib${_idx}")
        file(
            WRITE "lib${_idx}/lib${_idx}.h"
                "int lib${_idx}_func();"
        )
        file(
            WRITE "lib${_idx}/lib${_idx}.cc"
                "#include \"lib${_next_idx}.h\"\n"
                "int lib${_idx}_func() { return lib${_next_idx}_func(); }"
        )
        file(
            WRITE "lib${_idx}/CMakeLists.txt"
                "add_library(lib${_idx} \"lib${_idx}.cc\")\n"
                "target_link_libraries(lib${_idx} lib${_next_idx})\n"
                "target_include_directories(lib${_idx} PUBLIC \".\")"
        )
    endif()
    add_subdirectory("lib${_idx}")
    set(_idx "${_next_idx}")
endwhile()

if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib${_idx}")
    file(MAKE_DIRECTORY "lib${_idx}")
    file(
        WRITE "lib${_idx}/lib${_idx}.h"
            "int lib${_idx}_func();"
    )
    file(
        WRITE "lib${_idx}/lib${_idx}.cc"
            "int lib${_idx}_func() { return 0; }"
    )
    file(
        WRITE "lib${_idx}/CMakeLists.txt"
            "add_library(lib${_idx} \"lib${_idx}.cc\")\n"
            "target_include_directories(lib${_idx} PUBLIC \".\")"
    )
endif()
add_subdirectory("lib${_idx}")

if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/main.cc")
    file(
        WRITE "main.cc"
            "#include \"lib1.h\"\n"
            "int main() { return lib1_func(); }"
    )
endif()

add_executable(${PROJECT_NAME} "main.cc")
target_link_libraries(${PROJECT_NAME} lib1)

然后 - 在第一个cmake ..make电话之后 - 做:

$ touch ../lib100/CMakeLists.txt
$ time -p cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: [your path here]
real 28.89
user 0.01
sys 0.04
于 2016-03-06T12:09:17.413 回答