☰
App-Store-Connect-CLI 结构化 Xcode 版本编辑器:`asc xcode version` 从逐行扫描到跨平台结构化改写
2026/9/29 3:10:28 网站建设 项目流程

【免费下载链接】App-Store-Connect-CLI

Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more

项目地址:https://gitcode.com/gh_mirrors/ap/App-Store-Connect-CLI
点击查看免费下载

导读

本文围绕 App-Store-Connect-CLI 的设计文档 docs/design/xcode-version-structured-editor.md,深入讲解asc xcode version view/edit/bump命令组从"依赖 macOS agvtool、逐行改写 project.pbxproj"升级为"基于对象图解析 + 无损 xcconfig 扫描 + 原子写入"的结构化版本编辑器。你将掌握新增的--target/--configuration作用域、--next-build-number远程安全构建号、结构化 JSON 变更输出,以及底层实现原理与验证策略,可直接在 CI(Linux/Windows/macOS)上安全地自动管理 MARKETING_VERSION 与 CURRENT_PROJECT_VERSION。

一、设计定位:命令组保持原位,能力原地升级

本次改动不新增任何顶层命令,也不修改命令注册表(registry)。所有能力仍然收敛在已有的asc xcode version命令组之下,包含三个子命令:view(查看版本号)、edit(编辑版本号/构建号)、bump(递增版本号/构建号)。从源码看,命令组的入口定义在 internal/cli/xcode/xcode_version.go#L72-L110,通过ffcli.Command注册xcodeVersionViewCommand()、xcodeVersionEditCommand()、xcodeVersionBumpCommand()三个子命令,ShortHelp明确说明其职责是 "Read and modify Xcode project version numbers"。

1.1 旧行为与痛点

改动前的实现存在明确的平台与可靠性局限:

  • view、edit、bump均要求 macOS 环境,且依赖 Apple Generic Versioning(即agvtool)。
  • 现代项目的解析读取依赖xcodebuild -showBuildSettings。
  • 现代项目的写入方式是逐行扫描project.pbxproj,替换所有以MARKETING_VERSION =或CURRENT_PROJECT_VERSION =开头的行。

由此带来的问题在文档中被逐一列出,并可在旧代码路径中得到印证(internal/xcode/version.go#L223-L278 的getVersionLegacy仍保留 agvtool 调用):

旧行为问题后果
写入是 project-wide(全项目生效)无法只针对某个 target 或 configuration 精确修改
依赖文本格式缩进、换行风格变化会导致误匹配或漏改
无法处理 xcconfig 承载的构建设置xcconfig 中的版本号不会被更新
以0600权限重写文件破坏原有文件权限位,影响团队协作与源码管理

二、公开命令形态:新增作用域与远程构建号参数

2.1 作用域参数--target/--configuration

原有全部调用方式保持有效,新增两个可选参数:

asc xcode version view \ [--target NAME] [--configuration NAME] asc xcode version edit \ [--version VER] [--build-number NUM] \ [--target NAME] [--configuration NAME] asc xcode version bump --type major|minor|patch|build \ [--target NAME] [--configuration NAME]

作用域组合的语义非常明确,三条规则即可完整覆盖:

  • 两个都不给:保留原有的 project-wide(全项目)编辑行为;
  • 只给--target:更新该 target 下的全部 configuration;
  • 只给--configuration:更新该项目及所有 target 中的该 configuration。

在命令实现中,--target与--configuration被定义为fs.String参数并传入GetVersionOptions/SetVersionOptions/BumpVersionOptions(见 internal/cli/xcode/xcode_version.go#L141-L202)。同时还有两个项目定位参数:--project-dir(默认.,指向包含.xcodeproj的目录)与--project(当目录中存在多个.xcodeproj时指定具体项目路径),selectedProjectInput函数在 internal/cli/xcode/xcode_version.go#L112-L120 中完成二选一逻辑;底层findXcodeproj(internal/xcode/version.go#L744-L787)在发现零个或多个.xcodeproj时会分别报错,并提示使用--project消歧。

2.2 远程安全构建号:--next-build-number

这是本次设计中最具 CI 价值的特性:不再需要本地拼装 shell 管道去查询 App Store Connect 拿到下一个构建号。新增的无管道调用形态:

asc xcode version edit --next-build-number --app APP \ [--version VER] [--platform PLATFORM] [--initial-build-number N] asc xcode version bump --type build --next-build-number --app APP \ [--platform PLATFORM] [--initial-build-number N]

约束规则(文档原话 + 源码双重印证):

  • --next-build-number与--build-number互斥(internal/cli/xcode/xcode_version.go#L272-L274 直接返回 usage error);
  • 必须提供--app或环境变量ASC_APP_ID;
  • bump仅在接受--type build时允许携带该参数(internal/cli/xcode/xcode_version.go#L398-L400);
  • 未显式传入--version时,本地的 marketing version 将作为远程版本过滤条件;此时所有被选中的 configuration 必须解析出同一个marketing version,否则命令会在发起 App Store Connect 查询之前就失败(防止把不一致的版本号写进远程筛选)。

远程筛选相关参数与asc builds规范命令完全同义、同校验:

参数含义默认值
--appApp Store Connect App ID、Bundle ID 或精确 App 名称(也可用ASC_APP_ID)空
--platform远程构建平台过滤:IOS、MAC_OS、TV_OS、VISION_OS空(不过滤)
--processing-state远程处理状态过滤空
--exclude-expired从远程筛选中排除过期构建false
--initial-build-number远程尚无构建时的初始构建号1

参数绑定实现在 internal/cli/xcode/xcode_version.go#L44-L53 的bindXcodeRemoteBuildNumberFlags,且--initial-build-number在validateXcodeRemoteBuildNumberOptions中强制>= 1。解析流程复用asc builds next-build-number同源的 processed-build 与 in-flight-upload 逻辑:resolveXcodeNextBuildNumber先经shared.NormalizeLatestBuildSelectionOptions归一化筛选条件,再调用shared.ResolveNextBuildNumber(定义于 internal/cli/shared/build_numbers.go#L84),返回asc.BuildsNextBuildNumberResult中的nextBuildNumber(见 internal/asc/output_builds.go#L57-L62)。也就是说,远程构建号的选择算法(processed 构建优先、排除 in-flight 上传等)与既有 builds 命令完全一致,CI 脚本无需再自建幂等逻辑。

2.3 交互与退出码契约

设计文档明确了交互边界:没有任何命令提示符(no prompts);数据始终输出到 stdout,错误始终输出到 stderr;用法错误返回退出码 2。这与仓库 cmd/exit_codes.go 中定义的 usage error 退出码约定保持一致,便于脚本化调用方精确区分"参数写错"与"业务失败"。

三、结构化输出:JSON 向后兼容 + 变更明细

JSON 输出保持向后兼容:原有顶层 version/build 字段不变,VersionInfo、SetVersionResult、BumpVersionResult、VersionChange等结构定义在 internal/xcode/version.go#L119-L202。

变更(mutation)结果新增字段:

  • target与configuration:仅在使用了作用域参数时出现(omitempty);
  • changedFiles:稳定排序(stable sorted)的路径列表;
  • changes:稳定顺序的变更明细数组,每个元素包含setting(设置名)、oldValue、newValue、target、configuration、path以及source(取值pbxproj或xcconfig)。

对应 Go 结构为 internal/xcode/version.go#L153-L173 的VersionChange:

{ "setting": "MARKETING_VERSION", "oldValue": "1.2.3", "newValue": "1.3.0", "target": "MyApp", "configuration": "Release", "path": "MyApp/Config/Release.xcconfig", "source": "xcconfig" }

view 结果在选中的值上附加来源路径。VersionInfo增加了target、configuration、versionSource、buildNumberSource与modern字段(modern为 true 表示项目使用MARKETING_VERSION构建设置,见 internal/xcode/version.go#L119-L129)。Table 与 Markdown 输出保持原有 version/build 展示方式,仅当作用域/来源信息存在时附加展示——命令实现中printVersionScopeAndSources在纯文本与 Markdown 两种渲染模式下分别以Target:/**Target:**形式打印作用域与来源(internal/cli/xcode/xcode_version.go#L205-L220)。

四、实现原理:三层纵深

4.1 pbxproj:对象图解析而非行扫描

底层解析依赖github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj。设计文档写的是v1.3.3(MIT 许可),而当前仓库go.mod中实际锁定版本为v1.3.4(go.mod#L17),版本略有前进,但选型结论一致:这是一个已在生产级 Go 工具链中广泛使用的结构化解析器。

编辑器不再扫描文本行,而是遍历 project 与 target 的 configuration list(对象图)。已存在的设置原位修改(mutated in place),不会在无关层级凭空捏造设置。对应实现是 internal/xcode/version_project.go#L32-L50 中的structuredVersionProject,它持有xcodeproj.XcodeProj、pbxprojPath、配置列表configurations以及父子关系索引parentByChild,并通过serialized.Object读取构建设置对象图。两个核心设置常量定义在 internal/xcode/version_project.go#L22-L25:

const ( marketingVersionSetting = "MARKETING_VERSION" currentProjectSetting = "CURRENT_PROJECT_VERSION" )

4.2 xcconfig:无损扫描器

仓库自研了一个无损(lossless)xcconfig 扫描器,能力清单与文档逐项对应:

  • 处理赋值(assignments)、行注释(//)与块注释(/* */);
  • 兼容 CRLF 与 LF 行尾;
  • 支持条件键(conditional keys);
  • 支持#include与#include?(可选包含);
  • include 相对包含文件解析、环检测(cycle-safe),且可被多个 configuration 共享;
  • 无关字节保持原样不动;
  • 保留原有引号定界符;
  • 编辑版本设置时+=与?=被归一化为=,确保写入的 effective value 与请求值一致;
  • 选中的 include 图内所有匹配的MARKETING_VERSION/CURRENT_PROJECT_VERSION变体都会被更新。

实现证据在 internal/xcode/version_xcconfig.go:parseXCConfig(L95-L164)逐行解析并维护块注释状态机,xcconfigIncludePattern正则识别#include(?)?(L15),xcconfigAssignmentPattern正则识别+=、?=、=三种赋值符(L14);maskXCConfigCommentsState(L287-L339)处理引号内注释豁免、//行注释与/* */块注释的遮蔽;resolveXCConfigInclude(L348-L360)解析 include 相对路径、自动补.xcconfig后缀并拒绝含未解析构建变量的 include。收集器collectXCConfigFiles系列(L362 起)以含文件预算(maxFiles)的图遍历方式收集源文件,并在 Windows 大小写敏感目录等边界场景下保留正确的遍历身份语义。

4.3 容错语义:失败边界明确

文档给出两条关键容错原则:

  1. 不可读的 xcconfig 图:当所选作用域依赖它时才失败;若坏图属于无关 configuration,则不影响直接的 pbxproj view/edit。但当该坏图导致共享文件消费者(shared-file consumer)发现不确定时,mutation 会保守地拒绝执行。
  2. 读取解析:本地解析直接 pbxproj 设置、已注册的 xcconfig 值以及简单的构建设置引用(build-setting references),包括跨下一层 target xcconfig 或 project 层的$(inherited)。target 的 xcconfig 继承以匹配的 project configuration 为种子,与 Xcode 的层序一致。无法解析的构建系统变量产生显式错误而非臆造值;调用方在需要 SDK 特定解析时应使用 Xcode。

条件键(conditional-only)值可以编辑,但不能作为 view 或 bump 的基线(baseline),因为缺少无条件值作为参照。仅定义了两种结构化设置中一种的项目(以及仅把版本存在 Info.plist 的旧项目)保留 macOS/agvtool 回退路径;无作用域的远程构建号 bump 在该回退下仍可用——通过把解析出的数字传给agvtool new-version -all;而有作用域的 legacy bump 会在任何 project-wide 写入之前被拒绝。Legacy 回退要求存在可发现、可解析、且缺少结构化设置的 Xcode 项目;缺失、不可读、歧义的项目路径一律是 discovery error。

4.4 原子写入与回滚

这是保证多文件事务安全的核心:

  • 每个输出文件在变更前完成 prepare 与 validate;
  • 写入采用同目录临时文件,保留原文件 mode(解决旧实现 0600 权限问题),fsync后 rename;
  • 若后续某个文件失败,已写入的文件会从其捕获的原始字节恢复;
  • 提交前 staged pbxproj 会被重新解析(reparse);
  • 变更值若含注释语法或构建设置表达式,在 staging 之前即被拒绝(validateVersionMutationValue),防止"报告值"与"解析值"分叉;
  • 每个选中的叶 configuration 要么解析出请求值,要么被安排一次实际 mutation;未解析的受保护设置不能返回假成功。

SetVersion/BumpVersion的入口(internal/xcode/version.go#L281-L415)先做validateVersionMutationValue校验,再经openStructuredVersionProject打开对象图,通过hasStructuredSettingsForMutation判定走结构化路径还是 legacy 回退;ValidateSetVersion/ValidateBumpVersion(internal/xcode/version.go#L308-L466)提供零写入的预检能力——bump --next-build-number在发起远程查询前正是先调用runValidateSetVersion/runValidateBumpVersion验证本地变更合法性(见 internal/cli/xcode/xcode_version.go#L290-L299 与 L413-L425),确保不会"先改坏本地文件、再发现远程失败"。

4.5 xcodebuild 回退策略:--xcodebuild-settings-lookup

文档聚焦结构化解析,仓库实现还额外提供了一个可控的隐藏回退开关:--xcodebuild-settings-lookup auto|never(默认auto)。当结构化解析无法解析版本设置时,auto策略会在 stderr 输出警告后运行xcodebuild -showBuildSettings兜底,并把结果缓存在命令级BuildSettingsLookupSession中(internal/xcode/version.go#L641-L715);never则直接失败、不启动 xcodebuild(对强制纯净环境的 CI 很有用)。多 target 的-showBuildSettings输出会触发"请使用--target"的明确错误。

五、兼容性与生命周期

  • 属于稳定命令的实现改进 + 附加 flag/JSON 字段,不涉及破坏性变更;
  • 既有的 project-wide 调用方式保持 project-wide 行为不变;
  • 既有的人类可读输出保持可识别(version/build 展示不变,作用域/来源信息按需附加);
  • 无需任何 deprecation 流程;
  • 现代 pbxproj/xcconfig 操作变为跨平台(Linux/Windows 也能改版本号);仍需要 Xcode 构建系统解析或 legacy agvtool 行为的操作,会以明确的 macOS/Xcode 要求报错(requireMacOS/requireAgvtool,见 internal/xcode/version.go#L596-L615)。

六、RED-GREEN 验证策略

设计文档给出了完整的测试矩阵,覆盖行为特征(characterization)与回归(regression)两层:

行为特征覆盖:

  • 无作用域 flag 时的既有 project-wide 行为;
  • target-only、configuration-only、target+configuration 三种写入;
  • project 级与 target 级设置;
  • 单个或多个递归 xcconfig include 的继承;
  • 可选/缺失 include、include 环、共享 include、注释、引号值、赋值符、条件键、继承值、CRLF、无结尾换行、未变化字节区域。

回归与错误路径覆盖:

  • 畸形 pbxproj/xcconfig、缺失设置、歧义 target/configuration、多 application target、conditional-only 基线、部分迁移项目、不安全值、符号链接、权限、写失败、回滚、原子替换;
  • 跨平台 view/edit/bump(现代项目无需 agvtool);
  • 远程 next-build-number 校验、API 错误、in-flight 上传、JSON 输出;
  • Xcode 26 与 Xcode 27 项目副本:变更后用xcodebuild -list/-showBuildSettings重新解析校验;
  • 全量门禁:聚焦单元/CLI 测试 → 构建/tmp/asc黑盒检查 → format/docs/lint/test 全量流水线。

仓库中的测试印证:internal/cli/cmdtest/xcode_test.go断言edit与bump命令必须暴露--next-build-numberflag(internal/cli/cmdtest/xcode_test.go#L94-L118);internal/xcode/version_xcconfig_test.go、version_xcconfig_containment_test.go、version_xcconfig_bench_test.go覆盖扫描器行为与性能;version_structured_test.go覆盖对象图解析路径。

七、为什么不用其他方案

设计文档对候选方案做了取舍分析,这是理解架构决策的关键:

候选方案被否原因
保留行扫描编辑器实现更小,但无法安全作用域化,也无法理解 xcconfig 继承
移植rork-xcode模型更丰富,但会引入新的解析器维护负担
调用 Fastlane 或 Ruby 的xcodeproj违反单二进制、无运行时依赖(single-binary / no-runtime-dependency)的项目契约
Bitrisego-xcode解析器已在生产 Go 工具链中验证,是"最小的可信结构化底座";仓库自有的 xcconfig 扫描与原子写入层恰好补足它缺失的行为

八、实战速查

查看当前版本与来源:

# 项目根目录内,直接查看 asc xcode version view # 指定 target + configuration,并显示来源 asc xcode version view --target MyApp --configuration Release # 多项目目录中明确指定 .xcodeproj asc xcode version view --project ./MyApp/App.xcodeproj

编辑版本/构建号(结构化、跨平台):

# 全项目编辑 marketing version asc xcode version edit --version "1.3.0" # 仅编辑构建号,不触碰版本号 asc xcode version edit --build-number "42" # 同时写版本与构建号 asc xcode version edit --version "1.3.0" --build-number "42" # 精确作用域:只改 Widget target 的 Release configuration asc xcode version edit --target Widget --configuration Release --build-number "42" # 远程安全构建号:本地版本作为筛选条件 asc xcode version edit --next-build-number --app "com.example.app"

递增版本(bump 类型映射):

major 1.2.3 → 2.0.0 minor 1.2.3 → 1.3.0 patch 1.2.3 → 1.2.4 build Increment CFBundleVersion (build number)
asc xcode version bump --type patch asc xcode version bump --type minor --project-dir ./MyApp asc xcode version bump --type build --next-build-number --app "com.example.app"

典型 CI 用法(伪代码示意):bump --type build --next-build-number --app $ASC_APP_ID一次性完成"查询远程安全构建号 + 原子写入本地项目",无需在 pipeline 中手写curl + jq + plutil组合;若某一步失败(远程 API 错误、本地校验失败、写入回滚),命令以非零退出码终止且不留下半成品文件。

结语

asc xcode version的结构化升级,本质上是把"依赖 macOS 工具链的文本 hack"替换为"跨平台、可作用域、可回滚的结构化事务"。对使用 App-Store-Connect-CLI 的发布自动化而言,它让版本号管理具备了与asc builds等远程命令同等的可编程性:参数即契约、JSON 即事实、原子写入即安全。想深入源码的读者可以从 internal/cli/xcode/xcode_version.go(命令层)、internal/xcode/version.go(核心模型与 legacy 回退)、internal/xcode/version_project.go(pbxproj 对象图)与 internal/xcode/version_xcconfig.go(无损扫描器)四个文件入手。

【免费下载链接】App-Store-Connect-CLI

Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more

项目地址:https://gitcode.com/gh_mirrors/ap/App-Store-Connect-CLI
点击查看免费下载
上一篇:终极视觉革命:Photon光影包让Minecraft焕发电影级画面
下一篇:厦门大学论文LaTeX模板终极指南:从零到一的学术排版自动化方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询