CMake提供了几种指定目标源文件的方法。 一种是使用globbing(documentation),例如:
FILE (GLOB dir/*)
另一个是单独指定每个文件。
哪种方式更喜欢? Globbing似乎很容易,但我听说它有一些缺点。
答案 0 :(得分:168)
完全披露:我最初倾向于简化泛型方法,但多年来我逐渐认识到明确列出文件对于大型多开发人员项目来说不易出错。
原始答案:
通配的优点是:
可以轻松添加新文件 只列在一个地方:on 磁盘。没有全局创建 重复。
您的CMakeLists.txt文件将是 短。如果你这是一个很大的优点 有很多文件。没有全球化 导致您丢失CMake逻辑 在巨大的文件列表中。
使用硬编码文件列表的优点是:
如果我们使用,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})
唯一可以咬你的真实情况是,如果你使用git-bisect之类的东西在同一个构建目录中尝试旧版本的代码。在这种情况下,您可能需要清理和编译超过必要的内容,以确保您在列表中获得正确的文件。这是一个极端的案例,你已经在脚趾上,这不是一个问题。
答案 1 :(得分:101)
在CMake中指定源文件的最佳方法是明确列出。
CMake的创建者建议不使用globbing。
请参阅:http://www.cmake.org/cmake/help/v3.3/command/file.html?highlight=glob#file
(我们不建议使用GLOB从源树中收集源文件列表。如果在添加或删除源时没有更改CMakeLists.txt文件,则生成的构建系统无法知道何时要求CMake重新生成。 )
当然,您可能想知道缺点是什么 - 继续阅读!
globbing的一大缺点是创建/删除文件不会自动更新构建系统。
如果您是添加文件的人,这似乎是一种可接受的权衡,但是这会导致其他人构建代码出现问题,他们从版本控制更新项目,运行构建,然后与您联系,抱怨
“构建破坏”。
更糟糕的是,失败通常会产生一些链接错误,这些错误不会对问题的原因提供任何提示,并且会在排除故障时丢失时间。
在我参与的一个项目中,我们开始使用globbing,但是在添加新文件时收到了很多抱怨,这是明确列出文件而不是通配文件的充分理由。
这也打破了常见的git工作流程(git bisect
并在功能分支之间切换)。
所以我不推荐这个,它引起的问题远远超过了方便,当有人因为这个而无法构建你的软件时,他们可能会花很多时间来追查问题或者只是放弃。 / p>
另一个注意事项,只记得触摸CMakeLists.txt
并不总是足够的,使用通配的自动构建,我必须在每次构建之前运行cmake
,因为文件自上次构建*之后,可能已添加/删除。
有时候最好使用globbing:
CMakeLists.txt
文件。cmake
每次生成构建文件 以获得可靠/正确的构建(这违背了CMake的意图 - 从建筑中拆分配置的能力)。* 是的,我本可以编写一个代码来比较更新前后磁盘上的文件树,但这不是一个很好的解决方法,最好留给构建系统。
答案 2 :(得分: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)
然后进行全球化:
file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})
你仍然像以前一样利用显式依赖关系(并触发所有自动构建!),只有它在两个文件而不是一个文件中。
程序中唯一的变化是在您创建新文件之后。如果你没有全局,工作流程是从Visual Studio内部修改CMakeLists.txt并重建,如果你执行了glob,你可以显式地运行cmake - 或者只是触摸CMakeLists.txt。
答案 3 :(得分:4)
在CMake 3.12中,file(GLOB ...)
and 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})
但是,有些人仍然建议您避免对资源进行混淆。实际上,the documentation指出:
我们不建议使用GLOB从源代码树中收集源文件列表。 ...
CONFIGURE_DEPENDS
标志可能无法在所有生成器上可靠地工作,或者如果将来添加了不支持它的新生成器,则使用该生成器的项目将被卡住。即使CONFIGURE_DEPENDS
可以可靠地工作,在每次重新构建时都要进行检查。
就个人而言,我认为不必手动管理源文件列表来克服可能的弊端所带来的好处。如果您必须切换回手动列出的文件,则只需打印全局源列表并将其粘贴回即可轻松实现。
答案 4 :(得分:0)
单独指定每个文件!
我使用传统的CMakeLists.txt和python脚本来更新它。我在添加文件后手动运行python脚本。
请在此处查看我的回答: https://stackoverflow.com/a/48318388/3929196