使用 GLOB 指定源文件或在 CMake 中单独指定每个文件是否更好?

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

FILE(GLOB MY_SRCS dir/*)

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

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

109900 次浏览

全面披露: 我最初更喜欢 globbing 方法的简单性,但多年来我逐渐认识到,对于大型的、多开发人员的项目,明确列出文件不太容易出错。

原答案:


全球化的好处是:

  • 很容易添加新的文件,因为它们 只在一个地方列出: 磁盘。不是球状创建 复制

  • 您的 CMakeLists.txt 文件将是 这是一个很大的加分,如果你 有很多文件,不是球状的 会导致失去 CMake 逻辑 在巨大的文件列表中

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

  • CMake 将正确地跟踪磁盘上新文件的依赖关系-如果我们使用 当你第一次运行 CMake 的时候,不会得到的文件 捡起来

  • 确保只添加您想要的文件 您不需要的文件

为了解决第一个问题,您可以通过使用 touch 命令或在不做任何更改的情况下编写文件的方式,简单地“触摸”执行 Globb 的 CMakeLists.txt。这将强制 CMake 重新运行并拾取新文件。

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

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

唯一可能出现这种情况的地方是,您正在使用类似于 Git-bisect的东西来尝试在同一个构建目录中使用较旧版本的代码。在这种情况下,您可能不得不进行更多的清理和编译,以确保在列表中获得正确的文件。这是一个如此困难的情况,一个你已经在你的脚趾,这不是一个真正的问题。

在 CMake 中指定源文件的最佳方法是使用 明确地列出它们

CMake 的创建者建议 没有使用 globbing。

见: https://cmake.org/cmake/help/latest/command/file.html?highlight=glob#glob

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

当然,你可能想知道它的缺点是什么—— 继续读!


当全球化失败时:

Globbing 的最大缺点是,创建/删除文件不会自动更新构建系统。

如果你是添加文件的人,这似乎是一个可以接受的交易,但是这会给其他人构建你的代码带来问题,他们从版本控制中更新项目,运行 build,然后联系你,抱怨 < br > < strong > “ build 已经坏了”。

更糟糕的是,故障通常会产生一些链接错误,这些错误没有给出问题原因的任何提示,并且浪费了进行故障排除的时间。

在我参与的一个项目中,我们开始使用 globbing,但是当添加新文件时收到了很多抱怨,因此我们有足够的理由显式地列出文件而不是 globbing。

这也打破了常见的 git 工作流 < br > (git bisect和特性分支之间的切换)。

所以我不建议这样做,因为它带来的问题远远大于它的便利性,当有人因此无法构建您的软件时,他们可能会失去很多时间来追踪问题,或者干脆放弃。

另一个注意事项是,仅仅记住触摸 CMakeLists.txt并不总是足够的,因为使用 globbing 的自动构建,我必须在 每一个版本之前运行 cmake,因为自上一个构建 * 以来已经添加/删除了文件 也许吧

规则的例外:

有些时候,全球化更可取:

  • 用于为不使用 CMake 的现有项目设置 CMakeLists.txt文件。< br > 这是获取所有引用的源代码的快速方法(一旦构建系统使用显式文件列表运行-替换 globbing)。
  • 当 CMake 不用作 初选构建系统时,例如,如果您正在使用一个不使用 CMake 的项目,并且您希望为此维护自己的构建系统。
  • 对于任何文件列表变化如此频繁以至于无法维护的情况。在这种情况下,它 可以是有用的,但是您必须接受运行 cmake来生成构建文件 每次都是,以获得可靠/正确的构建 (这违背了 CMake 的初衷——将配置与构建分离的能力)

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

您可以安全地获取(也许应该这样做) ,但是需要另外一个文件来保存依赖项。

在某处添加以下函数:

# 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})

您仍然在运输显式的依赖项(并触发所有自动构建!)和以前一样,只不过是两个档案而不是一个。

过程中的唯一更改是在创建新文件之后。如果你不在 Visual Studio 内部修改 CMakeLists.txt 并重新构建工作流,那么如果你在工作流中添加元素,你可以显式地运行 cmake-或者只是触摸 CMakeLists.txt。

分别指定每个文件!

我使用传统的 CMakeLists.txt 和 python 脚本来更新它。在添加文件之后,我手动运行 python 脚本。

看看我的回答: Https://stackoverflow.com/a/48318388/3929196

在 CMake 3.12中,ABC0和 file(GLOB_RECURSE ...)命令获得了一个 CONFIGURE_DEPENDS选项,该选项在 globb 的值发生变化时重新运行 CMake。 由于这是使用 globbing 处理源文件的主要缺点,现在可以这样做了:

# Whenever this glob's value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")


add_executable(my_target ${sources})

然而,一些人仍然建议避免全球化来源。实际上,文件指出:

我们不建议使用 GLOB 从源树中收集源文件列表。... CONFIGURE_DEPENDS标志可能不能在所有的发电机上可靠地工作,或者如果在未来添加一个不能支持它的新发电机,使用它的项目将被卡住。即使 CONFIGURE_DEPENDS工作可靠,在每次重建时执行检查仍然需要成本。

就个人而言,我认为不必手动管理源文件列表的好处大于可能的缺点。如果您必须切换回手动列出的文件,这可以很容易地实现,只需打印全局源列表并粘贴回来。

我不是一个球状的球迷,从来没有使用它为我的图书馆。但是最近我查看了 Robert Schumacher (vcpkg 开发人员)的 展示,他建议将所有库源作为单独的组件(例如,私有源(。(cpp)、公共标头(。H)、测试、示例——都是独立的组件) ,并对所有组件使用独立的文件夹(类似于我们对类使用 C + + 名称空间)。在这种情况下,我认为 globbing 是有意义的,因为它允许您清楚地表达这种组件方法,并激励其他开发人员遵循它。例如,您的库目录结构可以如下:

  • /包括-公开标题
  • /src-用于私有头和源
  • /测试-测试

显然,您希望其他开发人员遵循您的约定(即,将公共头文件放在/include 下,将测试放在/test 下)。File (globb)向开发人员提示,一个目录中的所有文件都具有相同的概念意义,放置到这个目录中与 regexp 匹配的任何文件也将以相同的方式处理(例如,如果我们谈到公共头文件,那么在“ make install”中安装)。