编译参数
Xcode的Build Settings针对Clang Module有专门的设置分组,如下图:
针对这几个设置参数,下面分别解释一下其作用。
Enable Modules (C and Objective-C)
是否开启Clang Module特性。
当设置为YES的时候,会设置编译器参数-fmodules,开启clang module特性。当设置为NO的时候,其它4个选项也会随之失效,不会设置编译器参数-fmodules。
Enable Clang Module Debugging
对引用的外部clang module或者预编译头文件生成调试信息
当设置为YES的时候,会设置编译器参数-gmodules
。
举例说明一下这个参数,我们自己模块的Objective-C
源代码中如果有#import <Foundation/Foundation.h>
,那Foundation模块就属于被引用的外部clang module。当开启Clang Module特性的时候,会根据Foundation模块提供的modulemap生成clang module编译缓存,其缓存的目录是通过编译器参数-fmodules-cache-path
来设定的。
默认Xcode会设定编译缓存目录为的ModuleCache.noindex
-fmodules-cache-path=/Users/baozhifei/Library/Developer/Xcode/DerivedData/ModuleCache.noindex
如下图红框中所示,ModuleCache.noindex
为clang module缓存目录,Foundation-3DFYNEBRQSXST.pcm
为Foundation的缓存文件
当Enable Clang Module Debugging为YES的时候,这个缓存文件为Mach-O格式的文件,其中__CLANG,__clangast
节为缓存内容,这个文件还携带__DWARF,__debug_info
等一些调试信息。
其中缓存内容的头4个字节签名是CPCH,应该是Compiled PCH的缩写。
当Enable Clang Module Debugging为NO的时候,缓存文件直接就是CPCH文件,不会生成Mach-O格式且携带调试信息。
建议正常开发的时候关闭这个设置,当出现clang module编译问题的时候,可以开启这个调试选项,有了DWARF的调试信息,可以精确定位的错误代码的行号和列号。
开启这个选项后,编译时会有性能损失,因为缓存变成了Mach-O格式,需要完整加载整个文件,读取__clangast
节,才能获取真正的缓存内容。
Apple的官方文档《Precompiled Header and Modules Internals》中原文描述如下:
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.
从下面代码中,就可以看出,CPCH文件内容其实就是AST的bitcode,所以,clang module的实现机制是和预编译头文件一致的,clang module可以认为是更通用的预编译头文件。
Disable Private Modules Warnings
对于Private Module概念还不了解,后面再展开
Allow Non-modular Includes In Framework Modules
允许framework模块中有非clang module的include
当设置为NO的时候,会设置编译器参数-Wnon-modular-include-in-framework-module
。如果在引用的模块中,遇到非clang module的头文件,例如 #import "XXX.h"
这样格式的import指令,就会报错。
Link Frameworks Automatically
对于开启clang module后,import clang module会自动对链接器ld64增加链接参数,如下图红框所示:
因为我们都是使用CocoaPod来管理依赖,所以,最好关闭此选项,统一在podspec中声明依赖的frameworks和weak_frameworks
关闭后,会增加编译器参数-fno-autolink
。
Defines Module
-fmodule-name=ClangModuleTest
一个是形成一个虚拟的clang module层,让我们当前源码编译的模块也可以伪装成clang module格式
-ivfsoverlay /Users/baozhifei/Library/Developer/Xcode/DerivedData/ClangModuleTest-frvkjzzwryjshkeuimnrjtpuowxm/Build/Intermediates.noindex/ClangModuleTest.build/Debug-iphonesimulator/ClangModuleTest.build/all-product-headers.yaml
这个文件格式有点问题,说是yaml格式,实际上内容是json
源代码
llvm项目地址:https://github.com/llvm/llvm-project
我们编译调试的版本是14.0.6,是目前最新的tag
调试的代码,用Xcode创建一个新的framework工程叫ClangModuleTest,新增Test类,我们分析Test.m的编译流程。
Xcode内置的clang版本应该是有一些功能没有开源,我们开源的clang不认识-index-unit-output-path
和-index-store-path
,调试的时候这两个参数删除即可。
最新版本的clang的编译参数,都统一定义在Options.td文件中,通过clang-tblgen
来统一生成,这样生成出来的rst文档和Options.inc是一致的,在Options.td中没有找到上述两个参数。
我这里调试的环境是Qt Creator,设置运行参数如下图
如果只调试不写代码,可以使用Xcode来调试,如果还要编译代码,最好使用vscode,clion,qt creator这种支持cmake的IDE,可以使用ninja来构建,编译会非常快。Qt Creator界面丑一点,但是稳定,cmake自带一个GUI配置界面,很方便。
如果是vscode,要配置settings.json和launch.json文件,内容大致如下:
预处理
clang::Preprocessor
是负责预处理的类,预处理主要是处理编译单元中的一些#号开头的预处理指令,比如,#import
导入头文件预编译指令,这些指令定义在TokenKinds.def文件中。
clang会开启另外一个线程来编译Foundation模块,并把编译结果写入到pcm文件中。这个CompilerInstance执行的FrontendAction就是GenerateModuleFromModuleMapAction,我们之前编译.o文件实际上是EmitObjAction。