在这个问题的所有答案中,有很多关于“如何”(获得你想要的东西),但几乎没有关于“为什么”的内容(深入挖掘引发问题的原因以及提问者可能对不同类型的工具(如IDE和构建工具)之间的交互和信息共享方式以及CMake传递/需要传递给这些工具的信息的误解)。
这个问题令人困扰,因为它是由特定IDE- Code::Blocks和CMake的特定行为所驱动的,但随后提出了一个与该IDE无关的问题,而是关于Makefiles和CMake,假设他们在CMake方面做错了事情,导致了Makefiles的问题,进而导致了他们的IDE的问题。
简而言之,CMake和Makefiles有自己的跟踪头文件依赖项的方式,给定包含目录和源文件。CMake如何配置Code::Blocks IDE是完全不同的故事。
“外部”头文件在CMake中是什么?(不存在这样的东西)
我最近意识到问题似乎在于CMake认为那些头文件是项目外部的。因此,我认为CMake认为这些头文件是项目外部的,并且不会在依赖项中跟踪它们。
据我所知,当涉及到CMake时,“外部头文件”没有官方或有用的定义。我没有在文档中看到过这个短语的使用。此外,请注意,“项目”一词是一个非常重载的术语。由CMake生成的每个构建系统都包含一个顶级项目,可能包括其他外部或子目录项目。每个项目可以包含多个目标(库、可执行文件等)。CMake所称的目标有时会转换为IDE所称的项目(例如Visual Studio,可能还包括Code::Blocks)。如果你必须给出这样一个短语的含义,以下是对我来说有意义的:
如果问题是指某些IDE对“项目”一词的理解,而CMake称之为“目标”,那么外部头文件将是那些不打算通过任何目标的包含目录访问的头文件(例如从链接到所讨论的目标的目标获得的包含目录)。
在问题涉及到 CMake 对 "project" 这个词的理解时:目标(Targets)可以是项目(由调用 project()
命令定义/创建,并由生成的构建系统构建),也可以是 IMPORTED
(不由生成的构建系统构建,而是预期已存在,或者通过添加到生成的构建系统的某些自定义步骤(例如通过 ExternalProject_Add
)构建)。IMPORTED
目标的包含目录将是那些外部于所讨论的 CMake 项目的头文件,而非 IMPORTED
目标的包含目录将是那些属于项目的。
CMake 是否跟踪头文件依赖关系?(这要看情况!)
[...] CMake 认为这些头文件是项目外部的,并且不会在依赖项中跟踪它们
我对 CMake 的历史或构建工具中头文件依赖追踪并不十分了解,但是根据我对该主题所做的搜索,这是我收集到的信息。
CMake本身与实现文件/翻译单元的头文件/包含依赖关系相关的信息没有太多关联。这些信息对于CMake而言只有在需要告诉生成的构建系统这些依赖关系时才显得重要。生成的构建系统希望跟踪头文件依赖关系的变化,以避免任何不必要的重新编译。特别是对于Unix Makefiles生成器,在CMake 3.20之前,CMake会扫描头文件/包含依赖关系并将其告知Makefiles构建系统。自v3.20以来,如果编译器支持,CMake默认将该责任委托给编译器。请参见此处可用于恢复该行为的选项。
每个受支持的CMake生成器的头文件/包含依赖关系扫描方式的确切细节各不相同。例如,您可以在Ninja手册中找到一些关于Ninja能力/方法的高级描述。由于本问题仅涉及Makefiles,因此我不会尝试详细介绍其他生成器。
注意如何获取构建系统的头文件/包含文件依赖信息,您只需要给CMake提供一个目标的包含目录列表和要编译的实现源文件列表即可。您不需要提供头文件列表,因为该信息可以被扫描(无论是由CMake还是编译器)。
IDE是否通过扫描来获取有关目标头文件的信息?(他们可能会)
每个IDE都可以以任何方式显示信息。您在IDE中遇到的无法显示头文件的问题通常仅发生在项目布局的IDE显示格式与文件系统布局不同的情况下(项目头文件通常与实现文件位于同一项目目录中)。例如,Visual Studio和Code::Blocks中提供了此类非文件系统布局视图。
每个IDE都可以以任何方式获取头文件信息。据我所知(但对于Visual Studio可能我错了),Visual Studio和Code::Blocks都希望在IDE项目配置文件中明确列出项目头文件列表。还有其他可能的方法(例如头文件依赖性扫描),但似乎许多IDE选择了显式列表方法。我猜想这是因为它在实现方面很简单。
为什么扫描与目标相关的头文件对于IDE来说是一项繁琐的任务呢?(注意:这有些是推测,因为我不是任何此类工具的维护者,只使用过其中的几个)IDE可以实现文件扫描(本身就是一项复杂的任务),但要知道哪些头文件“在”目标中,它们需要从构建系统中获取关于目标的翻译单元将如何编译的信息,而且这还假设所有“不在目标中”的头文件包含路径都是用类似“system”标志指定的,这并不一定是事实。或者,它可以尝试从元构建系统(这里是CMake)中获取该信息。或者它可以尝试做CMake现在所做的事情,尝试调用所选的编译器来扫描依赖项。但无论哪种情况,它们都必须做出一些困难的决定,关于要支持哪些构建系统、元构建系统和/或编译器,然后从这些工具存储该信息的任何格式中提取该信息的艰巨工作,可能没有任何保证这些格式在未来的工具版本中将是相同的(支持新工具版本中格式的更改可能类似于必须支持完全独立的工具)。IDE可以完成所有这些工作,或者它可以要求您提供每个目标所属的头文件列表。正如您所看到的,C/C++生态系统中的工具多样性存在缺点。当然也有优点,但这超出了本问题的范围。
光明面是,CMake实际上确实有一个机制来尝试减轻您的工作量。对于那些具有非文件系统视图的IDE,它实现了一种简单的启发式方法,尝试查找与源文件相关联的头文件...
Code :: Blocks IDE生成器在CMake中如何发现头文件?
至少,在生成Code::Blocks项目时,头文件不会出现在项目中(源文件会出现)。
这里有一些有趣的事情:Code::Blocks编辑器具有作为项目一部分的源文件和头文件概念,由于CMake不希望/要求其用户告诉它关于项目中每个头文件,它只需要知道应该与目标相关联的包含目录是什么,因此尝试使用某个启发式方法来发现与实现文件相关联的头文件。该启发式方法非常基本:获取项目中每个源文件的路径,并尝试更改扩展名以使其类似于通常给出的头文件扩展名之一,并查看是否存在任何此类文件。请参见 :/Source/cmExtraCodeBlocksGenerator.cxx 中的 cmExtraCodeBlocksGenerator :: CreateNewProjectFile 成员函数。
在"Pitchfork Layout"术语中,可以说该启发式算法假定项目使用“合并头文件”而不是“分离头文件”的方式布置,其中有单独的src/
和include/
目录。因此,如果您不使用合并头文件布局或者有任何目标头文件不符合该启发式算法(例如实用程序头文件),则需要显式告诉CMake这些文件(例如使用target_sources
),以便它将该知识传递给生成的IDE配置文件。
更多阅读:
結束語
我相信有很多人比我更了解这些工具。如果您是其中之一并且发现我犯了错误,请在评论或聊天中慷慨地纠正我,或直接编辑此帖子。
请注意,虽然安装构建产物是许多项目生命周期的重要组成部分,因此已纳入大多数C/C++构建系统的设计中,但由于问题并没有明确询问配置安装部分,所以我选择将其从答案中省略,因为它本身并不是一个易于涵盖的话题(只需看看“掌握CMake”书中相关章节的长度:安装章节和导入和导出章节)。