用GLOB全局指定源文件?

CMake提供了几种方法来指定目标的源文件。 一个是使用globbing( 文档 ),例如:

FILE (GLOB dir/*) 

另一个是分别指定每个文件。

哪种方式更喜欢? 全球化似乎很容易,但我听说它有一些缺点。

全面的披露:我原本更喜欢简洁的方式,但多年来,我认识到明确列出这些文件对大型多开发人员项目而言不太容易出错。

原始答案:


globbing的好处是:

  • 添加新文件很容易,因为它们只在一个地方列出:在磁盘上。 不匹配造成重复。

  • 您的CMakeLists.txt文件将会缩短。 如果你有很多文件,这是一个很大的优势。 没有通配会导致你失去巨大的文件列表中的CMake逻辑。

使用硬编码文件列表的优点是:

  • CMake将正确地跟踪一个新文件在磁盘上的依赖关系 – 如果我们使用glob,那么当您运行CMake时,不会第一次循环播放的文件将不会被拾取

  • 你确保只有你想要的文件被添加。 全球化可能会挑选你不想要的stream浪文件。

为了解决第一个问题,你可以简单地通过触摸CMakeLists.txt来执行glob,或者使用touch命令,或者直接写入没有改变的文件。 这将迫使cmake重新运行,并拿起新的文件。

要解决第二个问题,您可以将代码小心地组织到目录中,这是您可能要做的事情。 在最坏的情况下,您可以使用列表(REMOVE_ITEM)命令来清理文件的globbed列表:

 file(GLOB to_remove file_to_remove.cpp) list(REMOVE_ITEM list ${to_remove}) 

这可以咬你的唯一的真实情况是,如果你正在使用像git-bisect的东西来尝试在同一个生成目录的旧版本的代码。 在这种情况下,您可能必须清理和编译超过必要的内容,以确保您在列表中获得正确的文件。 这是一个angular落的情况,一个你已经在你的脚趾,这不是一个真正的问题。

在CMake中指定源文件的最好方法是明确列出它们

CMake的创build者build议不要使用globbing。

请参阅: http : //www.cmake.org/cmake/help/v3.3/command/file.html?highlight=glob#file

(我们不build议使用GLOB从源代码树中收集源文件列表,如果在添加或删除源文件时没有更改CMakeLists.txt文件,则生成的生成系统无法知道何时要求CMake重新生成。

当然,你可能想知道缺点是什么 – 请继续阅读!


当Globbing失败时:

globbing的一大缺点是创build/删除文件不会自动更新构build系统。

如果你是添加文件的人,这可能是一个可以接受的平衡,但是这会导致其他人构build你的代码的问题,他们从版本控制更新项目,运行构build,然后联系你,抱怨
“build设的破碎”。

更糟糕的是,失败通常会导致一些链接错误,这不会给问题的原因提供任何提示,并且时间也不会被排除。

在我工作的一个项目中,我们开始使用globbing,但是在添加新文件的时候得到了很多抱怨,因此有足够的理由明确地列出文件而不是globbing。

这也打破了常见的git工作stream程
git bisect和function分支之间的切换)。

所以我不能推荐这个,它造成的问题远大于方便,当有人因此无法构build你的软件时,他们可能会耗费大量的时间来追踪问题或放弃。

还有一点需要注意的是,只要记住触摸CMakeLists.txt并不总是足够的,使用globbing的自动化构build,我必须在每次构build之前运行cmake ,因为自上次构build*以来可能已经添加/删除了文件。

规则的例外情况:

有好几次比较好,

  • 用于为不使用CMake的现有项目设置CMakeLists.txt文件。
    它是一个快速的方法来获取所有的引用(一旦构build系统的运行 – 用明确的文件列表replaceglobbing)。
  • 当CMake不被用作主要的构build系统时,例如,如果你正在使用一个不使用CMake的项目,而且你想维护自己的构build系统。
  • 对于文件列表经常变化以至于不能保持的情况。 在这种情况下,它可能是有用的,但是你必须接受每次运行cmake生成构build文件以获得可靠/正确的构build(这违背了CMake的意图 – 从构build拆分configuration的能力)

* 是的,我可以编写一个代码来比较更新前后磁盘上的文件树,但这不是一个很好的解决方法,更好的方法是构build系统。

您可以安全地(并且可能应该)以一个额外的文件为代价来保存依赖关系。

像这些地方添加function:

 # Compare the new contents with the existing file, if it exists and is the # same we don't want to trigger a make by changing its timestamp. function(update_file path content) set(old_content "") if(EXISTS "${path}") file(READ "${path}" old_content) endif() if(NOT old_content STREQUAL content) file(WRITE "${path}" "${content}") endif() endfunction(update_file) # Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with # the list of dependencies in it - this file should be treated as part of # CMakeLists.txt (source controlled, etc.). function(update_deps_file deps) set(deps_file "CMakeDeps.cmake") # Normalize the list so it's the same on every machine list(REMOVE_DUPLICATES deps) foreach(dep IN LISTS deps) file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep}) list(APPEND rel_deps ${rel_dep}) endforeach(dep) list(SORT rel_deps) # Update the deps file set(content "# generated by make process\nset(sources ${rel_deps})\n") update_file(${deps_file} "${content}") # Include the file so it's tracked as a generation dependency we don't # need the content. include(${deps_file}) endfunction(update_deps_file) 

然后去通过:

 file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp) update_deps_file("${sources}") add_executable(test ${sources}) 

你仍然像以前一样围绕显式的依赖关系(并触发所有的自动化构build!),只有它在两个文件,而不是一个。

过程中的唯一更改是在创build新文件之后。 如果你不工作,工作stream就是从Visual Studio里面修改CMakeLists.txt并重新编译,如果你使用glob,你可以明确地运行cmake,或者直接触摸CMakeLists.txt。