fix(ios): stop xcodegen from archiving the previous App Store version
Cloud #50 exported 1.74.0 because project.yml hardcoded MARKETING_VERSION and ci_post_clone regenerated the Xcode project from that YAML. Keep the version in Version.xcconfig and stamp it into the pbxproj before archive.
This commit is contained in:
1 parent
5880699347
commit
fabfede7e0
9 files changed
+68
-20
No files matched your search
@@ -311,7 +311,9 @@ jobs:
|
||||
log_file="$RUNNER_TEMP/xcode-cloud-control.log"
|
||||
# Manual Xcode Cloud workflows reject tags that are not in the start
|
||||
# condition. Start the Archive workflow on its configured default
|
||||
# (main) and require the iOS runtime tree to match this Release.
|
||||
# (main) and require that source's marketing version matches this
|
||||
# Release. The Git tag itself may still contain a stale project.yml
|
||||
# that xcodegen would archive as the previous live version.
|
||||
python3 .edgeever-ci/scripts/xcode-cloud-control.py start \
|
||||
--wait \
|
||||
--wait-valid |
|
||||
@@ -322,10 +324,14 @@ jobs:
|
||||
source_sha="$(grep -E '^source_sha=' "$log_file" | tail -1 | cut -d= -f2)"
|
||||
test -n "$source_sha"
|
||||
git fetch --no-tags origin "$source_sha"
|
||||
changed="$(git diff --name-only "$source_sha" HEAD -- apps/ios packages/shared)"
|
||||
if [[ -n "$changed" ]]; then
|
||||
echo "Xcode Cloud source $source_sha differs from Release $(git rev-parse HEAD) in iOS runtime files:" >&2
|
||||
echo "$changed" >&2
|
||||
cloud_version="$(
|
||||
git show "$source_sha:apps/ios/Config/Version.xcconfig" |
|
||||
sed -nE 's/^MARKETING_VERSION[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' |
|
||||
head -1
|
||||
)"
|
||||
release_version="${RELEASE_TAG#v}"
|
||||
if [[ "$cloud_version" != "$release_version" ]]; then
|
||||
echo "Xcode Cloud source $source_sha MARKETING_VERSION=$cloud_version does not match $RELEASE_TAG." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -795,7 +795,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MARKETING_VERSION = 1.74.0;
|
||||
MARKETING_VERSION = 1.79.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -885,7 +885,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MARKETING_VERSION = 1.74.0;
|
||||
MARKETING_VERSION = 1.79.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
||||
@@ -16,6 +16,12 @@ cd "$IOS_ROOT"
|
||||
|
||||
# Cloud stamps CFBundleVersion from App Store Connect "next build number".
|
||||
# Mirror that into Version.xcconfig so the archive and any tooling agree.
|
||||
# Also copy MARKETING_VERSION into the generated pbxproj so xcodegen cannot
|
||||
# keep a stale CFBundleShortVersionString from project.yml.
|
||||
MARKETING_VERSION=""
|
||||
if [ -f Config/Version.xcconfig ]; then
|
||||
MARKETING_VERSION="$(sed -nE 's/^MARKETING_VERSION[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' Config/Version.xcconfig | head -1)"
|
||||
fi
|
||||
if [ -n "${CI_BUILD_NUMBER:-}" ]; then
|
||||
echo "CI_BUILD_NUMBER=$CI_BUILD_NUMBER"
|
||||
if [ -f Config/Version.xcconfig ]; then
|
||||
@@ -24,16 +30,17 @@ if [ -n "${CI_BUILD_NUMBER:-}" ]; then
|
||||
echo "Stamped CURRENT_PROJECT_VERSION=${CI_BUILD_NUMBER} into Config/Version.xcconfig"
|
||||
grep -E '^(MARKETING_VERSION|CURRENT_PROJECT_VERSION)' Config/Version.xcconfig || true
|
||||
fi
|
||||
# Also inject into the generated project settings if present
|
||||
if [ -f EdgeEver.xcodeproj/project.pbxproj ]; then
|
||||
/usr/libexec/PlistBuddy -c "Print" EdgeEver.xcodeproj/project.pbxproj >/dev/null 2>&1 || true
|
||||
# pbxproj is not a plist; use sed for CURRENT_PROJECT_VERSION assignments
|
||||
sed -i '' -E "s/(CURRENT_PROJECT_VERSION = )[0-9]+;/\\1${CI_BUILD_NUMBER};/g" EdgeEver.xcodeproj/project.pbxproj
|
||||
echo "Stamped CURRENT_PROJECT_VERSION in project.pbxproj"
|
||||
fi
|
||||
else
|
||||
echo "CI_BUILD_NUMBER unset (local dry-run); leaving Version.xcconfig unchanged"
|
||||
fi
|
||||
if [ -n "$MARKETING_VERSION" ] && [ -f EdgeEver.xcodeproj/project.pbxproj ]; then
|
||||
sed -i '' -E "s/(MARKETING_VERSION = )[^;]+;/\\1${MARKETING_VERSION};/g" EdgeEver.xcodeproj/project.pbxproj
|
||||
echo "Stamped MARKETING_VERSION=${MARKETING_VERSION} in project.pbxproj"
|
||||
fi
|
||||
|
||||
# Defensive: ensure Automatic signing so Cloud can create Dev + Distribution profiles.
|
||||
# (project.yml already prefers Automatic; this repairs accidental Manual settings.)
|
||||
|
||||
@@ -20,8 +20,9 @@ settings:
|
||||
TARGETED_DEVICE_FAMILY: "1"
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: false
|
||||
IPHONEOS_DEPLOYMENT_TARGET: "17.0"
|
||||
CURRENT_PROJECT_VERSION: 49
|
||||
MARKETING_VERSION: 1.74.0
|
||||
# MARKETING_VERSION and CURRENT_PROJECT_VERSION live only in
|
||||
# Config/Version.xcconfig. Hardcoding them here makes xcodegen overwrite
|
||||
# the Release tag during ci_post_clone.
|
||||
# Automatic signing: required for Xcode Cloud (managed certs/profiles for
|
||||
# App Store + Development exports). Local ExportOptions.plist still pins
|
||||
# App Store profiles for Scripts/archive-app-store.sh export.
|
||||
|
||||
+11
-4
@@ -63,7 +63,12 @@ Xcode Cloud **overwrites** `CFBundleVersion` with its managed counter:
|
||||
Rules:
|
||||
|
||||
1. Next Cloud number must be **greater than** every build already on ASC for this app.
|
||||
2. Keep `apps/ios/Config/Version.xcconfig` → `CURRENT_PROJECT_VERSION` in the same ballpark (the repo stamps Cloud’s `CI_BUILD_NUMBER` into the archive via `ci_pre_xcodebuild.sh`).
|
||||
2. Keep `apps/ios/Config/Version.xcconfig` as the only marketing-version source.
|
||||
Do not hardcode `MARKETING_VERSION` in `project.yml`; Cloud runs `xcodegen`
|
||||
in `ci_post_clone`, and a stale YAML value would archive the previous App
|
||||
Store version. `ci_pre_xcodebuild.sh` copies the xcconfig version into the
|
||||
generated pbxproj and stamps Cloud’s `CI_BUILD_NUMBER` into
|
||||
`CURRENT_PROJECT_VERSION`.
|
||||
3. Before a store run, from a machine with API credentials:
|
||||
|
||||
```sh
|
||||
@@ -92,10 +97,11 @@ Rules:
|
||||
|
||||
Formal Releases do this automatically when `apps/ios` or the shared editor
|
||||
runtime changed: `bun run release` bumps `MARKETING_VERSION`, starts the Manual
|
||||
Xcode Cloud Archive workflow, requires the Cloud source to match the Release
|
||||
tag's iOS runtime tree, waits until the build is **Valid**, and submits App
|
||||
Xcode Cloud Archive workflow, requires the Cloud source's marketing version
|
||||
to match the Release tag, waits until the build is **Valid**, and submits App
|
||||
Review. The Cloud workflow stays Manual, so Apple rejects starting it on a
|
||||
tag that is not in the start condition.
|
||||
tag that is not in the start condition. `project.yml` must not hardcode
|
||||
`MARKETING_VERSION`; Cloud regenerates the Xcode project during `ci_post_clone`.
|
||||
|
||||
To retry or ship iOS without a new GitHub Release:
|
||||
|
||||
@@ -132,6 +138,7 @@ On **macOS beta**, do **not** upload that IPA; use Xcode Cloud instead. `archive
|
||||
| Cloud fails in post-clone on bun/xcodegen | Network / Homebrew on Cloud | Re-run; inspect `ci_post_clone` log |
|
||||
| Editor blank in app | EditorBundle not built | Confirm `ci_post_clone` log shows bundle build |
|
||||
| Hours running out | Workflow not Manual | Disable push/PR start conditions |
|
||||
| PrepareBuildForAppStoreConnect fails with the previous live version (for example 1.74.0) | `project.yml` hardcoded `MARKETING_VERSION` and `xcodegen` overwrote `Version.xcconfig` | Keep marketing version only in `Config/Version.xcconfig`; `ci_pre_xcodebuild.sh` must stamp it into the pbxproj |
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ On macOS beta hosts, archives must go through **Xcode Cloud** so
|
||||
`BuildMachineOSBuild` is a release OS image — see
|
||||
[iOS Xcode Cloud](ios-xcode-cloud.md). The store-delivery workflow starts that
|
||||
Manual Archive workflow on its configured default branch, requires the Cloud
|
||||
source to match the Release tag's iOS runtime tree, waits until App Store Connect marks the
|
||||
source's `MARKETING_VERSION` to match the Release tag, waits until App Store Connect marks the
|
||||
build Valid, then Fastlane (`apps/ios` `submit_review`) submits App Review and
|
||||
configures automatic release after approval. Pass `--ios-build-number` only to
|
||||
reuse an already uploaded build. Missing metadata, agreements, review
|
||||
|
||||
@@ -125,7 +125,7 @@ Release。默认命令直接使用 Production;只有明确要求测试交付
|
||||
在 macOS beta 本机上,Archive 必须通过 **Xcode Cloud**,保证
|
||||
`BuildMachineOSBuild` 来自正式系统镜像——见
|
||||
[iOS Xcode Cloud](ios-xcode-cloud.md)。商店交付工作流会按 Archive 工作流配置的
|
||||
默认分支启动这次手动构建,并要求 Cloud 源码与 Release tag 的 iOS 运行时一致,等到 App Store
|
||||
默认分支启动这次手动构建,并要求 Cloud 源码的 MARKETING_VERSION 与 Release tag 一致,等到 App Store
|
||||
Connect 将构建标为 Valid,再用 Fastlane(`apps/ios` 的 `submit_review`)提交
|
||||
App Review,并设置为审核通过后自动发布。只有复用已经上传的构建时才传入
|
||||
`--ios-build-number`。元数据、协议、审核信息或凭据不完整时
|
||||
|
||||
@@ -78,7 +78,8 @@ describe("store delivery command", () => {
|
||||
expect(workflow).toContain("--wait-valid");
|
||||
expect(workflow).not.toContain("--require-sha");
|
||||
expect(workflow).not.toContain("--git-ref");
|
||||
expect(workflow).toContain("git diff --name-only \"$source_sha\" HEAD -- apps/ios packages/shared");
|
||||
expect(workflow).toContain("git show \"$source_sha:apps/ios/Config/Version.xcconfig\"");
|
||||
expect(workflow).toContain("cloud_version");
|
||||
expect(workflow).toContain("grep -E '^(build_number|app_store_build_id|build_run_id|source_sha|processing_state)='");
|
||||
expect(workflow).toContain('refs/tags/${RELEASE_TAG}');
|
||||
expect(workflow).toContain(
|
||||
|
||||
@@ -29,11 +29,37 @@ describe("Xcode Cloud control", () => {
|
||||
|
||||
expect(workflow).toContain(".edgeever-ci/scripts/xcode-cloud-control.py");
|
||||
expect(workflow).toContain("--wait-valid");
|
||||
expect(workflow).toContain("git diff --name-only");
|
||||
expect(workflow).toContain("git show \"$source_sha:apps/ios/Config/Version.xcconfig\"");
|
||||
|
||||
expect(cloudWorkflow).toContain("scripts/xcode-cloud-control.py");
|
||||
expect(cloudWorkflow).toContain("describe");
|
||||
expect(cloudWorkflow).not.toContain("submit-review");
|
||||
expect(cloudWorkflow).not.toContain('APP_STORE_BUILD_NUMBER: "49"');
|
||||
});
|
||||
|
||||
test("keeps iOS marketing version in Version.xcconfig instead of project.yml", () => {
|
||||
const projectYml = readFileSync(
|
||||
new URL("../apps/ios/project.yml", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const preXcodebuild = readFileSync(
|
||||
new URL("../apps/ios/ci_scripts/ci_pre_xcodebuild.sh", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const pbxproj = readFileSync(
|
||||
new URL("../apps/ios/EdgeEver.xcodeproj/project.pbxproj", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const xcconfig = readFileSync(
|
||||
new URL("../apps/ios/Config/Version.xcconfig", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const marketing = xcconfig.match(/^MARKETING_VERSION\s*=\s*(\S+)/m)?.[1];
|
||||
|
||||
expect(projectYml).not.toMatch(/MARKETING_VERSION\s*:/);
|
||||
expect(preXcodebuild).toContain("Stamped MARKETING_VERSION=");
|
||||
expect(marketing).toBeTruthy();
|
||||
expect(pbxproj).not.toContain("MARKETING_VERSION = 1.74.0;");
|
||||
expect(pbxproj).toContain(`MARKETING_VERSION = ${marketing};`);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user