如何在iOS构建过程中动态生成Settings.bundle并解决构建异常
当前实现的核心问题
- 嵌套调用
xcodebuild引发构建进程冲突:在主Target的Run Script阶段二次启动xcodebuild编译子Target,相当于在一个已运行的Xcode构建会话内并行启动了第二个独立构建进程,两个进程会争抢DeriveData目录下的编译缓存、中间产物文件锁,这是构建阶段偶发崩溃的根本原因。你看到的「Building targets in manual order is deprecated」警告,本质也是因为这种写法绕开了Xcode原生的依赖调度机制,强行手动控制子Target构建顺序触发的。 - 构建产物路径硬编码不可靠:脚本里写死的
build/${CONFIGURATION}/Settings Builder是相对路径,会随Xcode构建配置变化(是否用workspace、是否自定义DeriveData路径、是否执行归档构建、新/旧构建系统切换)失效,很多场景下会找不到可执行文件直接导致构建失败。 - 共享源文件引发竞态:将同一份源文件、资源文件同时分配给主Target和命令行工具Target编译,Xcode默认开启并行编译时,两个编译任务会同时读写同一份源文件的中间产物,进一步放大文件锁冲突的概率。
构建阶段自动生成依赖源文件资源的最佳实践
- 放弃脚本内嵌套调用xcodebuild的写法,使用Xcode原生Target依赖机制:在主Target的
Build Phases > Target Dependencies中添加「Settings Builder」Target,Xcode会自动调度构建顺序,保证命令行工具编译完成后再启动主Target的后续构建步骤,从根源消除手动顺序构建的警告,也避免了双构建进程的锁冲突问题。 - 用Xcode内置环境变量引用构建产物,不要硬编码路径:配置完原生依赖后,Xcode会将依赖Target的产物路径统一放到
BUILT_PRODUCTS_DIR环境变量下,Run Script中直接调用"${BUILT_PRODUCTS_DIR}/Settings Builder"即可,覆盖所有构建场景(Debug/Release、真机/模拟器、归档、CI构建)的路径正确性。
注意:Xcode 14及之后版本默认启用新构建系统,需要给「Settings Builder」Target勾选Install选项,并将其安装目录配置为$(BUILT_PRODUCTS_DIR),避免产物被输出到系统临时目录找不到。 - 明确脚本的输入输出,适配增量构建:将生成Settings.bundle的Run Script阶段移动到
Copy Bundle Resources阶段之前,脚本中把生成的Root.plist输出到${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Settings.bundle/Root.plist路径,同时在Run Script配置的Output Files列表中添加该路径。Xcode会自动跟踪文件变化,只有输入内容变更时才重新执行脚本,大幅提升构建速度,也避免资源拷贝时机不对导致文件缺失。 - 轻量场景优先用内嵌Swift脚本,减少额外Target维护成本:如果Settings文件的生成逻辑不复杂,不需要维护单独的命令行工具Target,直接在Run Script中写Swift代码即可,省去独立Target的编译、依赖配置成本。最小可运行示例如下:
#!/usr/bin/env swift import Foundation // 此处编写自定义配置生成逻辑 let rootPlist: [String: Any] = [ "StringsTable": "Root", "PreferenceSpecifiers": [ // 自定义设置项 ] ] let env = ProcessInfo.processInfo.environment let bundlePath = env["TARGET_BUILD_DIR"]! + "/" + env["UNLOCALIZED_RESOURCES_FOLDER_PATH"]! + "/Settings.bundle" let outputPath = bundlePath + "/Root.plist" // 自动创建目录、写入文件 try FileManager.default.createDirectory(atPath: bundlePath, withIntermediateDirectories: true) try (rootPlist as NSDictionary).write(to: URL(fileURLWithPath: outputPath))
- 抽离共享代码避免编译竞态:如果命令行工具和主App确实需要复用逻辑,不要直接把同一份源文件同时勾选给两个Target编译,把共享代码抽为静态库、Swift Package或者独立Framework,两个Target统一依赖该公共模块即可,彻底避免并行编译时的文件读写冲突。
内容的提问来源于stack exchange,提问作者orakull
相关产品推荐
相关产品推荐

