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

Mar*_*enz 145 cmake

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

FILE(GLOB MY_SRCS dir/*)
Run Code Online (Sandbox Code Playgroud)

另一个是单独指定每个文件.

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

ric*_*chq 175

完全披露:我最初倾向于简化泛型方法,但多年来我已经认识到明确列出文件对于大型多开发人员项目来说不易出错.

原始答案:


通配的优点是:

  • 添加新文件很容易,因为它们只列在一个地方:磁盘上.不是globbing会创建重复.

  • 您的CMakeLists.txt文件将更短.如果你有很多文件,这是一个很大的优势.不是globbing会导致您丢失巨大的文件列表中的CMake逻辑.

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

  • CMake将正确跟踪磁盘上新文件的依赖关系 - 如果我们使用glob,那么当你运行时,第一次没有全局文件的文件将无法获取

  • 您确保只添加所需的文件.Globbing可能会拾取您不想要的杂散文件.

为了解决第一个问题,您可以通过使用touch命令或通过编写没有更改的文件来"触摸"执行glob的CMakeLists.txt.这将迫使cmake重新运行并获取新文件.

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

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})
Run Code Online (Sandbox Code Playgroud)

唯一可以咬你的真实情况是,如果你使用像git-bisect这样的东西来在同一个构建目录中尝试旧版本的代码.在这种情况下,您可能需要清理和编译超过必要的内容,以确保您在列表中获得正确的文件.这是一个极端的案例,而且你已经在你的脚趾上,这不是一个真正的问题.

  • 你知道吗?自从6年前**写这个答案以来,我已经改变了主意,现在更愿意明确列出文件.唯一真正的缺点是"添加文件需要更多工作",但它可以为您节省各种麻烦.在很多方面,明确比隐含更好. (34认同)
  • 我认为这个答案掩盖了cmake缺少新文件的缺点,"如果你是开发人员,简单地"触摸"CMakeLists.txt`就行了,但对于其他构建软件的人来说,构建失败真的是一个痛点.更新后,负担是**他们**调查原因. (9认同)
  • 正如安东尼奥所说,投票是为了倡导"全球化"的方法.改变答案的性质是为那些选民做的诱饵和转换.作为妥协,我添加了一个编辑,以反映我的观点.我向互联网道歉,因为在茶杯中造成了这样的风暴:-P (6认同)
  • 通配符还有一个缺点。这些文件最终作为绝对路径,因此如果您想使用文件名(带有前导相对路径)在其他地方重现文件夹结构作为构建的一部分,突然这些前导路径是绝对的。 (3认同)
  • @richq [这个 git hook](http://stackoverflow.com/a/17838951/2436175) 会让你重新考虑你当前的立场吗?:) (2认同)
  • 我发现最好的折衷办法是使用一个很小的外部脚本(例如 python)来更新 CMakeLists.txt 文件的源代码。它结合了自动为您添加文件的好处(因此可以防止文件路径中的潜在拼写错误),但会生成一个明确的清单,可以将其签入源代码管理(因此增量很明显/跟踪并且 git-bisect 将起作用),允许检查在包含虚假或意外文件时调试问题,并正确触发 cmake 以了解何时应重新运行。 (2认同)

ide*_*n42 108

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

CMake的创建者建议不要使用globbing.

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

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

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


当Globbing失败时:

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

如果你是添加文件的人,这似乎是一个可以接受的权衡,但这会导致其他人构建代码出现问题,他们从版本控制更新项目,运行构建,然后联系你,抱怨
"构建的"破碎".

更糟糕的是,失败通常会产生一些链接错误,这些错误不会给问题的原因提供任何提示,并且会在排除故障时丢失时间.

在我工作的一个项目中,我们开始使用globbing但是在添加新文件时抱怨很多,这是明确列出文件而不是globbing的充分理由.

这也打破了常见的git工作流程
(git bisect以及功能分支之间的切换).

所以我不能推荐这个,它造成的问题远远超过了方便,当有人因为这个而无法构建你的软件时,他们可能会花费大量时间来追查问题或者只是放弃.

还有另一个注意事项,只记得触摸CMakeLists.txt并不总是足够的,使用globbing的自动构建,我必须cmake在每次构建之前运行,因为自上次构建以来文件可能已被添加/删除*.

规则的例外情况:

有时候最好使用globbing:

  • 用于CMakeLists.txt为不使用CMake的现有项目设置文件.
    它是一种快速获取所有源代码的方法(一旦构建系统运行 - 用显式文件列表替换globbing).
  • 当CMake没有用作主要构建系统时,例如,如果您正在使用不使用CMake的项目,并且您希望为其维护自己的构建系统.
  • 对于文件列表经常更改的任何情况,维护都变得不切实际.在这种情况下它可能很有用,但是你必须接受运行cmake以每次生成构建文件以获得可靠/正确的构建(这违背了CMake的意图 - 从构建中拆分配置的能力).

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

  • @Dan,****你的**系统 - 当然,如果你只是单独开发这很好,但是那些建立你项目的人呢?你打算给他们发电子邮件去手动触摸CMake文件吗?每次添加或删除文件? - 在CMake中存储文件列表可确保构建始终使用vcs所知的相同文件.相信我 - 这不只是一些微妙的细节 - 当你的构建失败了许多开发者 - 他们邮件列表并在IRC上询问代码是否被破坏.注意:*(即使在您自己的系统上,您可能会回到git历史记录中,例如,而不是考虑进入并触摸CMake文件)* (15认同)
  • "globbing的最大缺点是创建新文件不会自动更新构建系统." 但是,如果你不使用glob,你仍然需要手动更新CMakeLists.txt,这意味着cmake仍然不会自动更新构建系统吗?看起来你必须记住手动执行某些操作才能构建新文件.触摸CMakeLists.txt似乎比打开它并编辑它以添加新文件更容易. (8认同)
  • 啊我没想到那个案子。这是我听到的反对通配的最好理由。我希望 cmake 文档能够扩展为什么他们建议人们避免使用通配符。 (2认同)
  • @tim-mb,“但是如果 CMake 创建了一个您可以检入的 filetree_updated 文件,它会在每次文件团更新时自动更改。” - 你刚刚准确地描述了我的答案。 (2认同)

Jus*_*tin 9

在CMake 3.12中,file(GLOB ...)和file(GLOB_RECURSE ...)命令获得了一个CONFIGURE_DEPENDS选项,如果glob的值更改,该选项将重新运行cmake。因为这是遍历源文件的主要缺点,所以现在可以这样做:

# 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})
Run Code Online (Sandbox Code Playgroud)

但是,有些人仍然建议避免使用源代码。实际上,文档指出:

我们不建议使用GLOB从源代码树中收集源文件列表。...该CONFIGURE_DEPENDS标志可能无法在所有生成器上可靠地工作,或者如果将来添加了不支持该标志的新生成器,则使用该标志的项目将被卡住。即使CONFIGURE_DEPENDS工作可靠,仍然需要对每次重建执行检查。

就我个人而言,我认为不必手动管理源文件列表来克服可能的弊端所带来的好处。如果必须切换回手动列出的文件,则只需打印全局源列表并将其粘贴回即可轻松实现。

  • 真正的、经过深思熟虑的答案,总是在页面底部附近。那些宁愿继续更新文件的人没有注意到,实际的效率损失并不是手动更新文件时的秒级,也不是执行检查时的纳秒级,而是可能是程序员损失时累积的_天或周_。他们在处理文件时会中断流程,或者完全推迟他们的工作,只是因为他们不想更新文件。感谢您的回答,真正为人类服务...并感谢 CMake 人员最终修补了它!:) (2认同)

Gle*_*les 8

您可以安全地(也可能应该)以额外文件为代价来保存依赖项.

在某处添加这些功能:

# 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)
Run Code Online (Sandbox Code Playgroud)

然后去全球化:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})
Run Code Online (Sandbox Code Playgroud)

你仍然像以前一样利用显式依赖关系(并触发所有自动构建!),只有它在两个文件而不是一个文件中.

程序中唯一的变化是在您创建新文件之后.如果你没有全局,工作流程是从Visual Studio内部修改CMakeLists.txt并重建,如果你做了glob,你可以显式地运行cmake - 或者只是触摸CMakeLists.txt.