文章目录
前言背景知识Action 详解LaunchAction 关键配置LaunchAction 其它主要配置BuildAction 的关键配置BuildAction 的其它重要配置project.pbxproj 的 `clang-target` 配置
优化原理优化步骤
总结
前言
本文中的编译根据上下文有不同的含义,请注意区分。
编译原始概念是指:将 a.m 编译为 a.out
本文中,也可以用来表示根据项目产出构建产物(可选的附带执行部分脚本、文件复制等操作)
笔者每次进行 clang 工程编译时,都会被编译耗时困扰。 clang 每次编译都在5分钟左右。
首先,先提供一份效果对比图。
图1,如下所示,笔者在只改动 1 行代码时,编译速度耗时 300.1 秒。
图2,经过简单的处理,编译速度被优化到 28.6 秒。
背景知识
当我们执行点击 运行 按钮时,Xcode 会执行以下步骤:
执行 BuildAction ,为后续的 LaunchAction 做准备
parallelizeBuildables = "YES" buildImplicitDependencies = "YES"> buildForTesting = "NO" buildForRunning = "YES" buildForProfiling = "YES" buildForArchiving = "YES" buildForAnalyzing = "YES"> BuildableIdentifier = "primary" BlueprintIdentifier = "A79DD8637E8944CF96F0A620" BuildableName = "clang" BlueprintName = "clang" ReferencedContainer = "container:../../build/Xcode-DebugAssert/llvm-macosx-x86_64/LLVM.xcodeproj">
执行 LaunchAction,运行程序
buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> runnableDebuggingMode = "0"> BuildableIdentifier = "primary" BlueprintIdentifier = "A79DD8637E8944CF96F0A620" BuildableName = "clang" BlueprintName = "clang" ReferencedContainer = "container:../../build/Xcode-DebugAssert/llvm-macosx-x86_64/LLVM.xcodeproj">
Action 详解 下面会重点讲解两个 Action 的各种配置参数。
阅读本文可以只看LaunchAction 关键配置 和 BuildAction 关键配置。LaunchAction 其它主要配置 和 BuildAction 其它主要配置可以当做扩展阅读。
LaunchAction 关键配置
buildConfiguration ,代表执行 LaunchAction 使用的配置组合名。默认是 Debug
通过 Xcode 新建的项目会有 Debug 和 Release 两种配置组合。
Build Settings 中可以增加新的配置组合。如下,LLVM 共计有 4 种配置组合。
通过配置组合,开发者可以快速调整编译选项。如下,clang 在不同的配置组合下,开启了不同的编译优化级别 BuildableProductRunnable 编译配置。
ReferencedContainer 代表依赖的文件位置BlueprintIdentifier 代表 ReferencedContainer 的节点ID。A79DD8637E8944CF96F0A620 的内容会在下一节重点讲解(后面会用 clang-target 代替) runnableDebuggingMode = "0"> BuildableIdentifier = "primary" BlueprintIdentifier = "A79DD8637E8944CF96F0A620" BuildableName = "clang" BlueprintName = "clang" ReferencedContainer = "container:../../build/Xcode-DebugAssert/llvm-macosx-x86_64/LLVM.xcodeproj">
LaunchAction 其它主要配置
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 代表使用的调试器。如果不希望调试程序,可以通过置为空,避免启动调试器。
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 代表使用的启动器。如果不希望调试程序,可以改为 Spawn 模式。 selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
debugAsWhichUser 代表是否使用 root 用户执行程序。
allowLocationSimulation 是否模拟自己的所在位置。通常用于开发 APP 进行位置模拟时使用。
enableAddressSanitizer = "YES" 是否启用地址杀毒剂。可以用于调试 EXC_BAD_ACCESS 等问题
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
launchStyle = "1" 启动类型,1 代表需要程序员手动执行程序,0 代表自动执行程序
CommandLineArguments 启动APP时传递的参数
EnvironmentVariables 环境变量,比如,可以通过以下命令控制动态库的动态加载 (看过 调试 iOS 的 objc 运行时,你可能还需要掌握这些知识 的同学,可以研究一下这个环境变量)
key = "DYLD_INSERT_LIBRARIES" value = "A_DYLD_INSERT_LIBRARIES" isEnabled = "YES">
BuildAction 的关键配置
parallelizeBuildables = "YES" 执行编译任务时,是否多任务并行处理
buildImplicitDependencies = "NO" 是否查找隐式依赖
BuildAction 的其它重要配置
BuildActionEntry 执行其它action时,是否触发 BuildAction。
buildForTesting = "NO" 代表,执行单测任务时,不需要触发编译buildForRunning = "YES" 代表,执行 运行任务时,需要触发编译任务
project.pbxproj 的 clang-target 配置
在 LaunchAction 中,我们曾经提到 clang-target (A79DD8637E8944CF96F0A620)。
因这部分的配置是在 project.pbxproj 中维护,所以单独用一个小节讲解一下。
如下所示,project.pbxproj 项目中关于 clang-target )的配置(因为篇幅原因,dependencies 被省略了一部分)
A79DD8637E8944CF96F0A620 /* clang */ = {
isa = PBXNativeTarget;
buildConfigurationList = A7BAD0EC9F7549319D37DAB4 /* Build configuration list for PBXNativeTarget "clang" */;
buildPhases = (
8720B378A9BF485883000F4C /* Sources */,
05FD7D0438324244B5AB2823 /* CMake PostBuild Rules */,
);
buildRules = (
);
dependencies = (
5D887B4D650A443FAD81013A /* PBXTargetDependency */,
........
........
........
);
name = clang;
productName = clang;
productReference = 66B22582EC2946B9B97BCED6 /* clang */;
productType = "com.apple.product-type.tool";
};
isa 就像 OC 中的类有自己的 isa 属性一样,这里同样有isa 标明这是一个 target PBXNativeTarget
对 target 概念比较陌生的同学可以参考 Xcode中的Workspace、Project、Target和Scheme
buildConfigurationList 指向一个列表,该列表负责维护 clang-target 有几种配置组合
A7BAD0EC9F7549319D37DAB4 /* Build configuration list for PBXNativeTarget "clang" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF7FFFB5E8D4AFAB08BDF78 /* Debug */,
895C7D31C2AE46899583672D /* Release */,
85E6509B95FE4402A01A4657 /* MinSizeRel */,
1BAC7DB5A2ED41A0A31AB96E /* RelWithDebInfo */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
buildPhases 负责维护一个编译 phase 列表
PBXSourcesBuildPhase 源码编译:指向 clang-target 的编译代码 注意:这里只有 cpp c m 等文件,没有h 等头文件
8720B378A9BF485883000F4C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F91A7D5B43464D99B6C2B745 /* cc1_main.cpp in Sources */,
77711346965C4D53B41045FE /* cc1as_main.cpp in Sources */,
FCA5E4060CCE4B9FBE948241 /* cc1gen_reproducer_main.cpp in Sources */,
98295C12ADE0429190E290C0 /* driver.cpp in Sources */,
5C039FA62D1A42F2946FBDC0 /* dummy.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
PBXShellScriptBuildPhase 脚本执行。(执行效果是通过创建链接的方式,将 clang++ clang-cl clang-cpp 指向 clang-target 的构建产物 clang)
05FD7D0438324244B5AB2823 /* CMake PostBuild Rules */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "CMake PostBuild Rules";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "make -C ~/swift-source/build/Xcode-DebugAssert/llvm-macosx-x86_64/tools/clang/tools/driver -f ~/swift-source/build/Xcode-DebugAssert/llvm-macosx-x86_64/tools/clang/tools/driver/CMakeScripts/clang_postBuildPhase.make$CONFIGURATION OBJDIR=$(basename \"$OBJECT_FILE_DIR_normal\") all";
showEnvVarsInLog = 0;
};
dependencies 负责维护的依赖列表(可以近似理解为对 静态库 或 动态库 的依赖)。clang-target 共计有 108 个依赖。
如下所示,这个依赖表明只有LLVMDemangle完成构建后, clang-target 才能执行完毕
5D887B4D650A443FAD81013A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4F1F4485C1C44E48A8AF56BB /* LLVMDemangle */;
targetProxy = 2A241593F41943B38ACDA422 /* PBXContainerItemProxy */;
};
name 代表target的名字。
productName 代表该 target 产物的名字。
如下,左侧是 name ,右侧是 productName productReference 代表产物的ID
productType 代表产物的类型。字段的含义可以参考 /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX Product Types.xcspec
clang的产物类型是 com.apple.product-type.tool,代表 普通 Unix 命令行可执行文件
// Tool (normal Unix command-line executable)
{ Type = ProductType;
Identifier = com.apple.product-type.tool;
Class = PBXToolProductType;
Name = "Command-line Tool";
Description = "Standalone command-line tool";
IconNamePrefix = "TargetExecutable";
DefaultTargetName = "Command-line Tool";
DefaultBuildProperties = {
FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)";
MACH_O_TYPE = "mh_execute";
EXECUTABLE_PREFIX = "";
EXECUTABLE_SUFFIX = "";
REZ_EXECUTABLE = YES;
INSTALL_PATH = "/usr/local/bin";
FRAMEWORK_FLAG_PREFIX = "-framework";
LIBRARY_FLAG_PREFIX = "-l";
LIBRARY_FLAG_NOSPACE = YES;
GCC_DYNAMIC_NO_PIC = NO;
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_INLINES_ARE_PRIVATE_EXTERN = YES;
STRIP_STYLE = "all";
CODE_SIGNING_ALLOWED = YES;
};
PackageTypes = (
com.apple.package-type.mach-o-executable // default
);
WantsSigningEditing = YES;
WantsBundleIdentifierEditing = YES;
},
优化原理
当我们执行点击 运行 按钮时,Xcode 会执行以下步骤:
读取各类配置信息
根据 LaunchAction确定配置组合,本例是 Debug根据 LaunchAction确定编译 target,本例是 clang-target根据 BuildAction确定是否查找隐式依赖根据 BuildAction确定是否并行编译根据 编译 target 确定前置依赖的 dependencies,递归执行本步骤根据 依赖图dependencies确定编译任务 并发或者顺序执行编译步骤
预处理编译链接对产物签名执行脚本
实际上,上面构建 依赖图的根本原因是:编译 clang 需要其它 target 的 些构建产物才能完成。
重点:在增量编译场景下,大部分的产物实际上是保持不变的,只有少数几个静态库会发生变化。
优化步骤
下面,我们看一下如何针对增量编译场景特殊处理:
通过快捷键或者鼠标,复制一份新的 clang target,我们可以称之为 clang-mini
重点:复制的原因是,我们可以在某些场景下(比如不小心删除了某个文件),通过 clang 快速编译所有的依赖产物
更改 Product Name 重点:不是所有的优化都需要此步骤,clang 执行此步骤的原因是之前提到过,编译完成后,会有脚本创建clang++ 等快捷方式,所以,这里特定调整一下
保留会有源文件变更的依赖
比如,我保留 LLVMDemangle 依赖的原因是,我需要加一下日志进行调试。
关闭隐式依赖的查找 重点:对于clang,这步的意义不大,但是,某些项目可能存在大量的隐式依赖,关掉这个开关,并且将依赖放到上一步的Dependencies中,可以节省大量的编译消耗
总结
本文通过分析 Xcode 运行构建工作的原理,提供了一种新的思路,可以快速降低大型项目的增量编译的时间消耗。
首发地址:https://ai-chan.top/%e4%bb%8e-5%e5%88%86%e9%92%9f%e5%88%b0-30-%e7%a7%92%ef%bc%8c%e5%a6%82%e4%bd%95%e4%bc%98%e5%8c%96clang-%e5%b7%a5%e7%a8%8b%e7%9a%84%e5%a2%9e%e9%87%8f%e7%bc%96%e8%af%91%e8%80%97%e6%97%b6/