模块化用户手册¶
modularize 是一款独立工具,用于检查一组头文件是否提供使用模块所需的一致定义。例如,它可以检测同一实体(例如,NULL 宏或 size_t 类型定义)是否在多个头文件中定义,或者头文件在不同情况下是否生成不同的定义。这些情况会导致从头文件构建的模块行为异常,应在引入模块映射之前修复。
modularize 还具有辅助模式选项,可根据提供的头文件列表生成模块映射文件。生成的模块映射文件是可用的,可以用作模块.modulemap 文件的起点。
入门¶
从源代码构建
阅读 LLVM 系统入门 和 Clang 工具文档,了解如何获取 LLVM、Clang 和 Clang Extra 工具的源代码。
LLVM 系统入门 和 使用 CMake 构建 LLVM 提供了构建说明。如果将所有源代码检出到正确的位置,则 LLVM 构建会自动构建 Clang Extra 工具及其依赖项。
如果使用 CMake,还可以使用
modularize
目标仅构建模块化工具及其依赖项。
在继续之前,请查看 模块化使用,了解如何调用模块化工具。
模块化检查内容¶
模块化工具将检查以下内容
重复的全局类型和变量定义
重复的宏定义
宏实例、'defined(macro)' 或 #if、#elif、#ifdef、#ifndef 条件在头文件中评估结果不同
'extern “C/C++” {}' 或 'namespace (name) {}' 块内的 #include 指令
模块映射头文件覆盖完整性(仅在模块映射输入的情况下)
模块化工具将进行正常的 C/C++ 解析,报告正常的错误和警告,但也会报告特殊的错误消息,例如以下内容
error: '(symbol)' defined at multiple locations:
(file):(row):(column)
(file):(row):(column)
error: header '(file)' has different contents depending on how it was included
后面可能会跟随以下类似的消息
note: '(symbol)' in (file) at (row):(column) not always provided
还会检查宏展开、defined(macro) 表达式和评估结果不一致的预处理器条件指令,并可能生成以下错误消息
(...)/SubHeader.h:11:5:
#if SYMBOL == 1
^
error: Macro instance 'SYMBOL' has different values in this header,
depending on how it was included.
'SYMBOL' expanded to: '1' with respect to these inclusion paths:
(...)/Header1.h
(...)/SubHeader.h
(...)/SubHeader.h:3:9:
#define SYMBOL 1
^
Macro defined here.
'SYMBOL' expanded to: '2' with respect to these inclusion paths:
(...)/Header2.h
(...)/SubHeader.h
(...)/SubHeader.h:7:9:
#define SYMBOL 2
^
Macro defined here.
还会检查嵌套在 'extern “C/C++” {}' 或 'namespace (name) {}' 块内的 '#include' 指令,并可能生成以下错误消息
IncludeInExtern.h:2:3:
#include "Empty.h"
^
error: Include directive within extern "C" {}.
IncludeInExtern.h:1:1:
extern "C" {
^
The "extern "C" {}" block is here.
模块映射覆盖检查¶
覆盖检查使用 Clang 库读取和解析模块映射文件。从模块映射文件目录开始,或者仅从指定的包含路径开始,它将收集所有被视为头文件的文件的名称(无扩展名、.h 或 .inc - 如果需要更多,请修改 isHeader 函数)。然后,它将这些头文件与模块映射中引用的头文件进行比较,这些头文件可能是显式命名的,也可能是通过模块映射对象解析的伞形目录或伞形文件隐式命名的。如果发现头文件未被引用或未被伞形目录或文件覆盖,则会生成警告消息,并且该程序将返回错误代码 1。如果没有发现问题,则会返回错误代码 0。
请注意,在伞形头文件的情况下,此工具会调用编译器来预处理文件,并使用回调来收集伞形头文件或其嵌套包含文件包含的头文件。如果这些编译器调用需要任何前端选项,则可以在模块映射文件参数后的命令行中包含这些选项。
警告消息具有以下形式
warning: module.modulemap does not account for file: Level3A.h
请注意,对于模块映射引用不存在的文件的情况,Clang 中的模块映射解析器将(在撰写本文时)显示错误消息。
要将 modularize 执行的检查限制为仅模块映射覆盖检查,请使用 -coverage-check-only option
。
例如
modularize -coverage-check-only module.modulemap
模块映射生成¶
如果指定了 -module-map-path=<module map file>
,modularize 将根据输入头文件列表输出模块映射。将为每个头文件创建一个模块。此外,如果头文件列表中的头文件是部分路径,则将创建一个嵌套的模块层次结构,其中将为头文件路径中的每个子目录组件创建一个模块,头文件本身由最内层的模块表示。如果其他头文件使用相同的子目录,它们也将包含在这些模块中。
例如,对于头文件列表
SomeTypes.h
SomeDecls.h
SubModule1/Header1.h
SubModule1/Header2.h
SubModule2/Header3.h
SubModule2/Header4.h
SubModule2.h
将生成以下模块映射
// Output/NoProblemsAssistant.txt
// Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt \
-root-module=Root NoProblemsAssistant.modularize
module SomeTypes {
header "SomeTypes.h"
export *
}
module SomeDecls {
header "SomeDecls.h"
export *
}
module SubModule1 {
module Header1 {
header "SubModule1/Header1.h"
export *
}
module Header2 {
header "SubModule1/Header2.h"
export *
}
}
module SubModule2 {
module Header3 {
header "SubModule2/Header3.h"
export *
}
module Header4 {
header "SubModule2/Header4.h"
export *
}
header "SubModule2.h"
export *
}
可以使用可选的 -root-module=<root-name>
选项创建一个根模块,该模块包含所有模块。
可以使用可选的 -problem-files-list=<problem-file-name>
输入要排除的文件列表,这可能是作为临时解决方案,直到可以修复问题头文件。
例如,使用上面的相同头文件列表
// Output/NoProblemsAssistant.txt
// Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt \
-root-module=Root NoProblemsAssistant.modularize
module Root {
module SomeTypes {
header "SomeTypes.h"
export *
}
module SomeDecls {
header "SomeDecls.h"
export *
}
module SubModule1 {
module Header1 {
header "SubModule1/Header1.h"
export *
}
module Header2 {
header "SubModule1/Header2.h"
export *
}
}
module SubModule2 {
module Header3 {
header "SubModule2/Header3.h"
export *
}
module Header4 {
header "SubModule2/Header4.h"
export *
}
header "SubModule2.h"
export *
}
}
请注意,具有依赖项的头文件将被忽略并发出警告,因为 Clang 模块机制不支持依赖于其他头文件先被包含的头文件。
模块映射格式定义了一些不能在模块名称中使用的关键字。如果头文件包含这些名称之一,则会在名称前面添加下划线 ('_')。例如,如果头文件名是 header.h
,因为 header
是关键字,模块名称将是 _header
。有关模块映射关键字的列表,请参阅:词法结构