Unlocking Clang Modules: Debugging Swift Build Issues and Best Practices
This article explores the challenges of enabling Swift support with Clang modules in Xcode, explains key build settings, debugging options, and the underlying implementation of Clang modules, providing practical guidance and code examples for developers.
Background
When DingTalk started supporting Swift, various compilation problems appeared while adapting Clang modules. To uncover the real causes of these failures and identify best practices, we examined the Clang module implementation code.
Analysis
Compilation Parameters
Xcode’s Build Settings include a dedicated group for Clang Modules, as shown below.
The following settings are explained:
Enable Modules (C and Objective‑C)
Enables the Clang Module feature by adding the -fmodules flag when set to YES; disabling it removes the flag and related options.
Enable Clang Module Debugging
Generates debugging information for external Clang modules or precompiled headers by adding -gmodules. When enabled, the module cache (default ModuleCache.noindex) stores Mach‑O files containing sections such as __CLANG,__clangast and __DWARF,__debug_info. The cache files are identified by names like Foundation-3DFYNEBRQSXST.pcm. Disabling this option produces plain CPCH files without debug info.
Enabling this option incurs a performance penalty because the compiler must load the entire Mach‑O file to read the __clangast section.
Clang’s AST files are loaded “lazily” from disk. When an AST file is initially loaded, Clang reads only a small amount of data from the AST file to establish where certain important data structures are stored. The amount of data read in this initial load is independent of the size of the AST file, such that a larger AST file does not lead to longer AST load times.
Disable Private Modules Warnings
Explanation omitted for brevity.
Allow Non-modular Includes In Framework Modules
When set to NO, the compiler adds -Wnon-modular-include-in-framework-module. Including a non‑module header (e.g., #import "XXX.h") will cause an error.
Link Frameworks Automatically
Enabling Clang Modules makes the linker automatically add framework link flags. For projects using CocoaPods, it is recommended to disable this option and declare dependencies explicitly in the podspec, which adds the -fno-autolink flag.
Defines Module
When enabled, a modulemap file is generated, and the compiler sets the module name with -fmodule-name=ClangModuleTest and creates a virtual overlay using -ivfsoverlay …/all-product-headers.yaml.
Source Code
The LLVM project (https://github.com/llvm/llvm-project) version 14.0.6 was used. A new Xcode framework project named ClangModuleTest was created, and a Test class was added to analyze the compilation flow.
Build parameters can be extracted from Xcode’s build log. Some Xcode‑specific flags ( -index-unit-output-path and -index-store-path) are not recognized by the open‑source Clang and should be removed.
Modern Clang options are defined in Options.td and generated via clang-tblgen.
Preprocessing
The class clang::Preprocessor handles directives such as #import. It creates a Lexer in EnterSourceFile, then dispatches to HandleDirective for each directive.
For the test code, the first line #import <Foundation/Foundation.h> triggers the creation of a clang::Module object after parsing the corresponding module.modulemap file.
The CompilerInstance checks the cache; if the module cache is missing, it spawns a new compiler instance to compile the Foundation module, generating an AST file (PCM) that is stored in the cache. This compilation runs in a separate thread using the GenerateModuleFromModuleMapAction frontend action.
Summary
By reading the Clang Module implementation, we learned that importing a module header causes Clang to load its PCM cache (an AST file). If the cache is absent, Clang compiles the module in a separate process, writes the AST to a PCM file, and reuses it across compilation units, reducing overall build time.
References
Clang Module Spec: https://clang.llvm.org/docs/Modules.html
Precompiled Header and Modules Internals: https://clang.llvm.org/docs/PCHInternals.html
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
