Skip to content

Commit d71a24f

Browse files
committed
fix(mac): share Swift packaging preflight
1 parent 57513ba commit d71a24f

11 files changed

Lines changed: 117 additions & 36 deletions

scripts/check-changed.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const ANDROID_VERSION_SYNC_PATHS = new Set([
6969
"apps/android/version.json",
7070
]);
7171
const MACOS_APP_CI_PATH_RE =
72-
/^(?:apps\/(?:macos|macos-mlx-tts|shared|swabble)\/|Swabble\/|scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.sh$|scripts\/lib\/plistbuddy\.sh$|test\/scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.test\.ts$)/u;
72+
/^(?:apps\/(?:macos|macos-mlx-tts|shared|swabble)\/|Swabble\/|scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.sh$|scripts\/lib\/(?:plistbuddy|swift-toolchain)\.sh$|test\/scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.test\.ts$)/u;
7373
let corepackPnpmShimDir;
7474
let corepackPnpmShimCleanupRegistered = false;
7575

scripts/ci-changed-scope.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const NATIVE_PROTOCOL_GEN_RE = /^apps\/shared\/OpenClawKit\/Sources\/OpenClawPro
3737
const MACOS_NATIVE_RE =
3838
/^(apps\/macos\/|apps\/macos-mlx-tts\/|apps\/ios\/|apps\/shared\/|apps\/swabble\/|Swabble\/)/;
3939
const MACOS_SCRIPT_SCOPE_RE =
40-
/^(?:scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.sh|scripts\/lib\/plistbuddy\.sh|test\/scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.test\.ts)$/;
40+
/^(?:scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.sh|scripts\/lib\/(?:plistbuddy|swift-toolchain)\.sh|test\/scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.test\.ts)$/;
4141
const IOS_BUILD_RE =
4242
/^(apps\/ios\/|apps\/shared\/|apps\/swabble\/|Swabble\/|config\/(?:swiftformat|swiftlint\.yml)$|scripts\/ios-(?:configure-signing|team-id|write-version-xcconfig)\.sh$|scripts\/ios-version\.ts$|scripts\/lib\/(?:ios-version\.ts|npm-publish-plan\.mjs|version-script-args\.ts)$)/;
4343
const ANDROID_NATIVE_RE = /^(apps\/android\/|apps\/shared\/)/;

scripts/lib/swift-toolchain.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
3+
REQUIRED_SWIFT_TOOLS_MAJOR=6
4+
REQUIRED_SWIFT_TOOLS_MINOR=2
5+
6+
require_swift_toolchain() {
7+
local swift_version
8+
if ! swift_version="$(swift --version 2>&1)"; then
9+
printf '%s\n' "$swift_version" >&2
10+
echo "ERROR: OpenClaw macOS app packaging requires Swift tools ${REQUIRED_SWIFT_TOOLS_MAJOR}.${REQUIRED_SWIFT_TOOLS_MINOR}+." >&2
11+
echo " Install/select Xcode 26.x or newer before running macOS packaging scripts." >&2
12+
return 1
13+
fi
14+
15+
local major_minor
16+
major_minor="$(printf '%s\n' "$swift_version" | sed -nE 's/.*Apple Swift version ([0-9]+)\.([0-9]+).*/\1 \2/p' | head -n 1)"
17+
if [[ -z "$major_minor" ]]; then
18+
printf '%s\n' "$swift_version" >&2
19+
echo "ERROR: Could not parse selected Swift toolchain version." >&2
20+
echo " OpenClaw macOS app packaging requires Swift tools ${REQUIRED_SWIFT_TOOLS_MAJOR}.${REQUIRED_SWIFT_TOOLS_MINOR}+." >&2
21+
return 1
22+
fi
23+
24+
local major minor
25+
read -r major minor <<< "$major_minor"
26+
if (( major < REQUIRED_SWIFT_TOOLS_MAJOR )) ||
27+
(( major == REQUIRED_SWIFT_TOOLS_MAJOR && minor < REQUIRED_SWIFT_TOOLS_MINOR )); then
28+
printf '%s\n' "$swift_version" >&2
29+
echo "ERROR: OpenClaw macOS app packaging requires Swift tools ${REQUIRED_SWIFT_TOOLS_MAJOR}.${REQUIRED_SWIFT_TOOLS_MINOR}+." >&2
30+
echo " Current Swift is ${major}.${minor}; install/select Xcode 26.x or newer." >&2
31+
return 1
32+
fi
33+
}

scripts/package-mac-app.sh

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ set -euo pipefail
66

77
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
88
source "$ROOT_DIR/scripts/lib/plistbuddy.sh"
9+
source "$ROOT_DIR/scripts/lib/swift-toolchain.sh"
910
APP_ROOT="$ROOT_DIR/dist/OpenClaw.app"
1011
BUILD_ROOT="$ROOT_DIR/apps/macos/.build"
1112
PRODUCT="OpenClaw"
@@ -40,8 +41,6 @@ if [[ "$BUNDLE_ID" == *.debug ]]; then
4041
SPARKLE_FEED_URL=""
4142
AUTO_CHECKS=false
4243
fi
43-
REQUIRED_SWIFT_TOOLS_MAJOR=6
44-
REQUIRED_SWIFT_TOOLS_MINOR=2
4544

4645
sparkle_canonical_build_from_version() {
4746
(cd "$ROOT_DIR" && node --import tsx "$ROOT_DIR/scripts/sparkle-build.ts" canonical-build "$1")
@@ -91,35 +90,6 @@ run_pnpm() {
9190
(cd "$ROOT_DIR" && "${PNPM_CMD[@]}" "$@")
9291
}
9392

94-
require_swift_toolchain() {
95-
local swift_version
96-
if ! swift_version="$(swift --version 2>&1)"; then
97-
printf '%s\n' "$swift_version" >&2
98-
echo "ERROR: OpenClaw macOS app packaging requires Swift tools ${REQUIRED_SWIFT_TOOLS_MAJOR}.${REQUIRED_SWIFT_TOOLS_MINOR}+." >&2
99-
echo " Install/select Xcode 26.x or newer before running scripts/package-mac-app.sh." >&2
100-
return 1
101-
fi
102-
103-
local major_minor
104-
major_minor="$(printf '%s\n' "$swift_version" | sed -nE 's/.*Apple Swift version ([0-9]+)\.([0-9]+).*/\1 \2/p' | head -n 1)"
105-
if [[ -z "$major_minor" ]]; then
106-
printf '%s\n' "$swift_version" >&2
107-
echo "ERROR: Could not parse selected Swift toolchain version." >&2
108-
echo " OpenClaw macOS app packaging requires Swift tools ${REQUIRED_SWIFT_TOOLS_MAJOR}.${REQUIRED_SWIFT_TOOLS_MINOR}+." >&2
109-
return 1
110-
fi
111-
112-
local major minor
113-
read -r major minor <<< "$major_minor"
114-
if (( major < REQUIRED_SWIFT_TOOLS_MAJOR )) ||
115-
(( major == REQUIRED_SWIFT_TOOLS_MAJOR && minor < REQUIRED_SWIFT_TOOLS_MINOR )); then
116-
printf '%s\n' "$swift_version" >&2
117-
echo "ERROR: OpenClaw macOS app packaging requires Swift tools ${REQUIRED_SWIFT_TOOLS_MAJOR}.${REQUIRED_SWIFT_TOOLS_MINOR}+." >&2
118-
echo " Current Swift is ${major}.${minor}; install/select Xcode 26.x or newer." >&2
119-
return 1
120-
fi
121-
}
122-
12393
merge_framework_machos() {
12494
local primary="$1"
12595
local dest="$2"

scripts/package-mac-dist.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ set -euo pipefail
1010

1111
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
1212
source "$ROOT_DIR/scripts/lib/plistbuddy.sh"
13+
source "$ROOT_DIR/scripts/lib/swift-toolchain.sh"
1314

1415
BUILD_ROOT="$ROOT_DIR/apps/macos/.build"
1516
PRODUCT="OpenClaw"
1617
BUILD_CONFIG="${BUILD_CONFIG:-release}"
17-
APP_VERSION_INPUT="${APP_VERSION:-$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")}"
18+
APP_VERSION_INPUT="${APP_VERSION:-}"
1819

1920
# Default to universal binary for distribution builds (supports both Apple Silicon and Intel Macs)
2021
export BUILD_ARCHS="${BUILD_ARCHS:-all}"
@@ -128,6 +129,12 @@ correction_build_from_exact_tag() {
128129

129130
# Local fallback releases must not silently fall back to a git-rev-count build number.
130131
# For correction tags, pass a higher explicit APP_BUILD than the canonical floor.
132+
require_swift_toolchain
133+
134+
if [[ -z "$APP_VERSION_INPUT" ]]; then
135+
APP_VERSION_INPUT="$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")"
136+
fi
137+
131138
if [[ -z "${APP_BUILD:-}" && "$BUILD_CONFIG" == "release" ]]; then
132139
CANONICAL_APP_BUILD="$(require_canonical_sparkle_build "$APP_VERSION_INPUT")"
133140
APP_BUILD="$(correction_build_from_exact_tag "$APP_VERSION_INPUT" "$CANONICAL_APP_BUILD")"

scripts/test-projects.test-support.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,10 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
12331233
"test/scripts/package-mac-dist.test.ts",
12341234
],
12351235
],
1236+
[
1237+
"scripts/lib/swift-toolchain.sh",
1238+
["test/scripts/package-mac-app.test.ts", "test/scripts/package-mac-dist.test.ts"],
1239+
],
12361240
[
12371241
"scripts/lib/plugin-npm-runtime-build.mjs",
12381242
["test/scripts/plugin-npm-runtime-build-args.test.ts", "test/plugin-npm-runtime-build.test.ts"],

src/scripts/ci-changed-scope.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ describe("detectChangedScope", () => {
372372
"scripts/codesign-mac-app.sh",
373373
"scripts/create-dmg.sh",
374374
"scripts/lib/plistbuddy.sh",
375+
"scripts/lib/swift-toolchain.sh",
375376
"scripts/notarize-mac-artifact.sh",
376377
"scripts/package-mac-app.sh",
377378
"scripts/package-mac-dist.sh",

test/scripts/changed-lanes.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,7 @@ describe("scripts/changed-lanes", () => {
15811581
"scripts/codesign-mac-app.sh",
15821582
"scripts/create-dmg.sh",
15831583
"scripts/lib/plistbuddy.sh",
1584+
"scripts/lib/swift-toolchain.sh",
15841585
"scripts/notarize-mac-artifact.sh",
15851586
"scripts/package-mac-app.sh",
15861587
"scripts/package-mac-dist.sh",

test/scripts/package-mac-app.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ function getPackageManagerHelperBlock(): string {
4949
}
5050

5151
function getSwiftToolchainBlock(): string {
52-
const script = readFileSync(scriptPath, "utf8");
52+
const script = readFileSync("scripts/lib/swift-toolchain.sh", "utf8");
5353
const start = script.indexOf("REQUIRED_SWIFT_TOOLS_MAJOR=");
54-
const end = script.indexOf("merge_framework_machos()");
54+
const end = script.length;
5555

5656
expect(start).toBeGreaterThanOrEqual(0);
5757
expect(end).toBeGreaterThan(start);
@@ -301,6 +301,7 @@ describe("package-mac-app plist stamping", () => {
301301
const installIndex = script.indexOf('if [[ "${SKIP_PNPM_INSTALL:-0}" != "1" ]]');
302302
const preInstallBlock = script.slice(0, installIndex);
303303

304+
expect(script).toContain('source "$ROOT_DIR/scripts/lib/swift-toolchain.sh"');
304305
expect(preInstallBlock).toContain("\nrequire_swift_toolchain\n");
305306
});
306307

test/scripts/package-mac-dist.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,62 @@ describe("package-mac-dist plist validation", () => {
113113
expect(script).not.toContain('canonical_sparkle_build "$VERSION" 2>/dev/null || true');
114114
});
115115

116+
it("checks Swift before Sparkle metadata or dependency bootstrap work", () => {
117+
const script = readFileSync(scriptPath, "utf8");
118+
const swiftIndex = script.indexOf("\nrequire_swift_toolchain\n");
119+
const versionIndex = script.indexOf('if [[ -z "$APP_VERSION_INPUT" ]]');
120+
const appBuildIndex = script.indexOf(
121+
'if [[ -z "${APP_BUILD:-}" && "$BUILD_CONFIG" == "release" ]]',
122+
);
123+
const packageAppIndex = script.indexOf('"$ROOT_DIR/scripts/package-mac-app.sh"');
124+
const preSwiftBlock = script.slice(0, swiftIndex);
125+
126+
expect(script).toContain('source "$ROOT_DIR/scripts/lib/swift-toolchain.sh"');
127+
expect(swiftIndex).toBeGreaterThanOrEqual(0);
128+
expect(versionIndex).toBeGreaterThan(swiftIndex);
129+
expect(appBuildIndex).toBeGreaterThan(versionIndex);
130+
expect(packageAppIndex).toBeGreaterThan(appBuildIndex);
131+
expect(preSwiftBlock).not.toContain("node -p");
132+
});
133+
134+
it("fails on old Swift before reading package metadata", () => {
135+
const toolsDir = mkdtempSync(path.join(tmpdir(), "openclaw-dist-swift-tools-"));
136+
tempDirs.push(toolsDir);
137+
138+
writeFileSync(
139+
path.join(toolsDir, "swift"),
140+
[
141+
"#!/usr/bin/env bash",
142+
"echo 'swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)'",
143+
"",
144+
].join("\n"),
145+
"utf8",
146+
);
147+
chmodSync(path.join(toolsDir, "swift"), 0o755);
148+
writeFileSync(
149+
path.join(toolsDir, "node"),
150+
[
151+
"#!/usr/bin/env bash",
152+
"echo 'node should not run before Swift preflight' >&2",
153+
"exit 42",
154+
"",
155+
].join("\n"),
156+
"utf8",
157+
);
158+
chmodSync(path.join(toolsDir, "node"), 0o755);
159+
160+
const result = runHelper(`
161+
set -euo pipefail
162+
PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)}
163+
BUILD_CONFIG=release bash ${scriptPath}
164+
`);
165+
166+
expect(result.status).toBe(1);
167+
expect(result.stderr).toContain("OpenClaw macOS app packaging requires Swift tools 6.2+");
168+
expect(result.stderr).toContain("Current Swift is 6.0");
169+
expect(result.stderr).not.toContain("node should not run before Swift preflight");
170+
});
171+
116172
it("prefers repo Corepack pnpm over a global pnpm shim", () => {
117173
const helperBlock = getPackageManagerHelperBlock();
118174
const tempRoot = mkdtempSync(path.join(tmpdir(), "openclaw-dist-pnpm-root-"));

0 commit comments

Comments
 (0)