SwiftGen 持续集成检查:用 CI 自动拦截“忘记重新生成代码常量“的提交

发布时间:2026/9/25 2:20:24
SwiftGen 持续集成检查:用 CI 自动拦截“忘记重新生成代码常量“的提交
开发工具代码生成【免费下载链接】SwiftGenThe Swift code generator for your assets, storyboards, Localizable.strings, … — Get rid of all String-based APIs!项目地址https://gitcode.com/gh_mirrors/sw/SwiftGen点击查看免费下载本篇技术指南聚焦 SwiftGen 官方推荐的另一种工程实践——在持续集成CI中自动校验开发者是否在修改资源后忘记重新生成代码常量。文章以官方文档 Making-CI-check-SwiftGen-changes.md 为骨架结合 Xcode-Integration.md、ConfigFile.md 以及 SwiftGen 的源码实现OutputDestination.swift、ConfigRun.swift进行纵深剖析。读完本文你将掌握在 CI 中重跑 SwiftGen 并对比 git 状态这一可靠校验方案能直接落地到 CircleCI、GitHub Actions 等任意 CI 平台。为什么需要让 CI 检查 SwiftGen 的输出SwiftGen 的推荐用法是将它作为 Xcode 项目中的一个 Script Build Phase这样每次构建时如果资源发生了变化生成的常量文件会自动更新开发者无需手动干预。但并非所有人都喜欢这种做法。官方文档明确指出了两个顾虑不希望为每次构建增加编译时间——即便 SwiftGen 本身做了优化、执行速度很快并且在内容没有变化时不重写生成文件更习惯在终端手动运行 SwiftGen——只在修改资源之后、提交之前自己手动执行一次生成命令。如果你属于第二种情况那么就会面临一个经典问题开发者修改了Images.xcassets却忘记重新运行 SwiftGen 更新ImageCatalog.swift然后就把代码提交了上去。生成文件与资源文件不同步轻则 CI 编译报错重则把过时的常量合入主干。解决方案就是让 CI 在每次构建时替开发者重跑一遍 SwiftGen再检查 git 工作区里生成文件是否发生了变化。如果有变化说明开发者漏掉了手动生成这一步——此时 CI 直接返回失败要求开发者补跑并重新提交。核心思路重跑 swiftgen然后对比 git 状态这一校验方案的本质是利用 SwiftGen 的幂等性做差分检测CI 上执行与开发者相同的swiftgen命令如果开发者的提交是资源与生成文件同步的那么 CI 重跑后生成文件的内容不会改变git status显示干净构建通过如果开发者忘记重新生成CI 重跑后生成文件的内容会发生变化git status检测到改动构建失败。官方文档以xcassets资源目录功能为例给出了一个完整的四步工作流开发者向Images.xcassets中新增或移除一张图片开发者运行swiftgen重新生成ImageCatalog.swift文件前提swiftgen.yml配置文件中已包含xcassets条目开发者提交改动并推送到远程服务器CI 工具运行同样的swiftgen命令。如果生成文件发生了变化就说明开发者漏掉了第 2 步——CI 返回失败信息警告你漏掉了这一步强制你补跑生成命令并重新提交。这个流程的价值在于把人的记忆转化为机器的强制检查。它不依赖任何开发者的主观纪律只要 CI 通过就能保证生成文件与资源文件始终同步。CircleCI 示例命令逐行拆解官方文档给出了一个 CircleCI 的配置示例circleci.yml中可以这样写steps: - run: name: Check if we forgot to run SwiftGen command: swiftgen exit $(git status --porcelain DerivedSources/ImageCatalog.swift | wc -l)这条命令值得逐段拆解片段作用swiftgen不带任何子命令运行 SwiftGen。根据 ConfigFile.md 的说明这等价于swiftgen config run即读取仓库根目录下的swiftgen.yml并执行其中配置的全部解析与生成任务逻辑与短路。只有swiftgen成功退出退出码为 0后才执行后半段。这意味着如果 SwiftGen 本身解析失败、配置错误或模板出错CI 同样会失败不会静默通过git status --porcelain DerivedSources/ImageCatalog.swift以机器可读的瓷器porcelain格式查看该文件的工作区状态。如果文件被修改输出形如M DerivedSources/ImageCatalog.swift如果文件未纳入版本控制untracked输出形如?? DerivedSources/ImageCatalog.swift如果没有变化则不输出任何内容wc -l统计上述输出的行数。无变化时为0有任何改动时为≥ 1exit $(...)用统计结果作为 shell 的退出码。exit 0表示构建通过exit 1或更大的非零值表示构建失败运行逻辑总结如果开发者忘记运行 SwiftGenCI 重跑后ImageCatalog.swift内容发生变化 →git status输出 1 行 →wc -l结果为 1 →exit 1→ 构建被中止并标记为失败。需要补充两点实现细节退出码即改动行数如果生成文件恰好有多个改动条目例如同时改了多个生成文件并都传入该命令wc -l返回的计数可能大于 1exit会以该数值作为退出码。shell 的退出码通常限制在 0–255 之间超过 255 会被取模。单文件场景1 行输出 → exit 1完全够用若想更严谨可以用test -z $(git status --porcelain ...)这种布尔判断替代。未跟踪文件也会被发现如果开发者新建了生成文件却忘了git add/git commitgit status --porcelain会输出??前缀的行wc -l同样返回 ≥ 1构建照样失败——这顺带强制了生成文件必须入库的纪律。为什么这个方案可靠SwiftGen 的内容不变不写盘机制该校验方案成立的前提是SwiftGen 重跑后不会无谓地改写文件。如果每次运行都无条件覆写文件那么即使开发者已经正确生成并提交CI 重跑也会让文件的 mtime 变化虽然 git 对比的是内容而非时间戳更重要的是会让 CI 的差分失去意义——开发者同步提交了也会被误报。SwiftGen 的源码从底层保证了这一点。在 OutputDestination.swift 中write(content:onlyIfChanged:logger:)的实现如下case .file(let path): if try onlyIfChanged path.exists path.read(.utf8) content { logMessage(.info, Not writing the file as content is unchanged) return } try path.write(content) logMessage(.info, File written: \(path))可以看到当开启onlyIfChanged且目标文件已存在、内容与本次渲染结果完全一致时SwiftGen 会跳过写盘并打印 Not writing the file as content is unchanged。而这一开关正是配置文件运行路径上的默认行为。在 ConfigRun.swift 中每一个outputs条目渲染完成后都会以onlyIfChanged: true写入let rendered try template.render(enriched) let output OutputDestination.file(entryOutput.output) try output.write(content: rendered, onlyIfChanged: true, logger: logger)即使不走配置文件、直接用 CLI 子命令如swiftgen xcassets ...Run.swift 也统一采用了output.destination.write(content: rendered, onlyIfChanged: true)。结论只要开发者已经正确运行过 SwiftGen 并提交CI 上重跑会得到内容一致 → 不写盘 → git 无变化 → exit 0的结果只有开发者漏跑时渲染结果才会与已提交文件产生差异从而被 git 检出。这个机制让 CI 检查既准确又零误报。顺带一提Xcode-Integration.md 还强调了一个相关的工程细节在 Xcode 构建阶段中应使用--output Constants/AssetsGenerated.swift而非 output.swift重定向因为重定向会无条件覆写文件可能引发 Xcode 构建取消或IBDesignable触发的循环构建问题。CI 检查场景同样建议依赖配置文件中output:键的内容不变不写盘行为。前提项目必须使用 swiftgen.yml 配置文件上述 CI 方案中命令只写了swiftgen一个词它之所以能完成全部生成工作依赖的是仓库根目录下的swiftgen.yml配置文件。按 ConfigFile.md 的约定该文件用来声明解析哪些文件inputs、使用哪个模板templateName/templatePath、输出到哪里output。针对本文的 xcassets 示例一个最小可用的配置如下xcassets: inputs: Resources/Images.xcassets outputs: templateName: swift5 output: DerivedSources/ImageCatalog.swift需要注意的配置规则详见 ConfigFile.md所有相对路径都相对于配置文件自身所在目录解析包括input_dir、output_dir、inputs、templatePath、output官方建议避免使用以/开头的绝对路径以免配置依赖项目在本机的克隆位置影响 CI 与本地的一致性每个 parser 键colors、coredata、files、fonts、ib、json、plist、strings、xcassets、yaml既可以是单个字典也可以是字典数组用于同一 parser 多次调用、输出多个文件支持在配置中使用环境变量如${PROJECT_DIR}/${TARGET_NAME}/Resources/这在 Xcode Build Phase 场景尤其有用。同时可以借助以下命令提升配置的可靠性# 生成一份带注释的示例配置 swiftgen config init # 校验配置YAML 合法性、必填键是否齐全、键类型是否正确 swiftgen config lintswiftgen config lint会逐条检查每个 parser 的inputs路径是否存在、filter正则是否合法、outputs的模板能否解析、输出文件父目录是否存在等并打印配置的完整解读非常适合在 CI 检查脚本前先跑一遍。实用变体与进阶1. 检查所有生成文件官方示例只检查单个DerivedSources/ImageCatalog.swift。如果你的项目有多个 parser、多个输出文件可以把检查范围扩大到整个生成目录swiftgen test -z $(git status --porcelain DerivedSources)此变体在DerivedSources目录下有任何改动包括新增、删除、修改、未跟踪时都会失败。注意它也会捕获该目录下与 SwiftGen 无关的意外改动因此建议为生成文件划定专用目录让目录脏 忘记生成的语义保持纯粹。2. 用 git diff 替代 wc -l如果觉得exit $(... | wc -l)对退出码的依赖不够直观可改用 git 自身的退出码即是否有差异语义swiftgen ! git diff --quiet -- DerivedSources/ImageCatalog.swiftgit diff --quiet在存在差异时返回非零!取反后即为失败无差异时返回 0! 0为 1不会误报逻辑更清晰也规避了wc -l计数超过 255 的边界问题。3. 用 --verbose 调试 CI 中的生成行为如果 CI 检查失败需要确认 SwiftGen 到底执行了哪些等价命令可以使用swiftgen config run --verbose根据 ConfigRun.swift 的实现verbose 模式会为配置中每个条目打印对应的完整命令行如$ swiftgen xcassets --templateName swift5 --output ...帮助你在本地精确复现 CI 上的生成过程。测试用例 ConfigRunTests.swift 也展示了这类等价命令的日志形态。4. 与 Script Build Phase 的取舍两种方案的定位不同官方文档也给出了清晰的取舍Script Build PhaseXcode-Integration.md每次本地构建自动生成开发者零负担适合宁可多花一点构建时间也要保证实时同步的团队CI 检查本地手动生成 提交CI 兜底校验适合不想给每次构建增加成本、愿意接受手动流程的团队。两者也可以组合本地用 Script Build Phase 自动生成CI 仍保留检查步骤作为最后防线。无论选哪种官方都强烈建议以swiftgen.yml配置文件驱动而不是在脚本里逐个调用 parser 子命令——配置文件方式既快多个 parser 可并行见 ArrayParallel.swift又能保证 CI 与本地行为完全一致。注意事项与边界落地该方案时有几个容易踩坑的点生成文件必须被 git 跟踪如果DerivedSources/ImageCatalog.swift被写进.gitignoregit status永远看不到它检查将完全失效。生成文件应当提交入库这正是该方案能工作的前提CI 环境必须安装 SwiftGenCI 上需要与本地一致的 SwiftGen 版本。可以通过 Homebrew 全局安装brew install swiftgen或参照 Xcode-Integration.md 中的做法在脚本里做存在性检查if which swiftgen /dev/null; then ...以防 CI 环境未安装时报出误导性错误若通过 CocoaPods 集成则使用$PODS_ROOT/SwiftGen/bin/swiftgen的路径形式检查只针对已提交的内容CI 检出的是远端最新提交检查的是该提交中生成文件是否与资源同步。本地未推送的改动不在检查范围内模板与配置的漂移也会被捕获如果开发者改了swiftgen.yml或自定义.stencil模板却忘了提交对应的生成文件CI 重跑同样会发现差异——这是一个额外的、正向的副作用能保证配置/模板变更与生成结果变更一起提交。小结让 CI 检查 SwiftGen 输出本质上是把手动生成这一不可靠环节变成可机器验证的工程纪律CI 重跑一遍swiftgen再用git status --porcelain对比生成文件是否有变化有变化即失败。该方案之所以零误报得益于 SwiftGen 在源码层面的内容不变不写盘保证OutputDestination.swift开发者正确生成后CI 重跑不会产生任何 git 差异。配合swiftgen.yml配置文件与swiftgen config lint的校验你可以在任意 CI 平台上用短短几行配置把忘记运行 SwiftGen这类人为失误彻底挡在合入流程之外。赞分享开发工具代码生成【免费下载链接】SwiftGenThe Swift code generator for your assets, storyboards, Localizable.strings, … — Get rid of all String-based APIs!项目地址https://gitcode.com/gh_mirrors/sw/SwiftGen点击查看免费下载相关推荐SwiftGen与持续集成的深度集成生成代码的自动化测试SwiftGen与持续集成的深度集成生成代码的自动化测试 在iOS开发中手动管理资源引用和字符串键常常导致运行时错误和维护难题。SwiftGen通过将资源自开发工具代码生成SwiftGen与GitLab CI持续集成资源生成SwiftGen与GitLab CI持续集成资源生成 在iOS开发中资源管理往往依赖字符串硬编码导致编译时无法捕获错误。SwiftGen通过将资源如图片开发工具代码生成3个关键步骤如何用Python离线翻译库彻底告别网络依赖3个关键步骤如何用Python离线翻译库彻底告别网络依赖 还在为跨国协作的语言障碍烦恼吗还在担心敏感文档的翻译隐私问题吗Argos Translate是人工智能NLP本地部署创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考