模块化用户手册

modularize 是一款独立工具,用于检查一组头文件是否提供使用模块所需的一致定义。例如,它可以检测同一实体(例如,NULL 宏或 size_t 类型定义)是否在多个头文件中定义,或者头文件在不同情况下是否生成不同的定义。这些情况会导致从头文件构建的模块行为异常,应在引入模块映射之前修复。

modularize 还具有辅助模式选项,可根据提供的头文件列表生成模块映射文件。生成的模块映射文件是可用的,可以用作模块.modulemap 文件的起点。

入门

从源代码构建

  1. 阅读 LLVM 系统入门Clang 工具文档,了解如何获取 LLVM、Clang 和 Clang Extra 工具的源代码。

  2. 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。有关模块映射关键字的列表,请参阅:词法结构