mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
build(macos): add OPENCLAW_SKIP_MLX_TTS to unblock beta-toolchain builds (#123698)
The openclaw-mlx-tts voice helper pulls in the full mlx-swift Metal shader stack, which some beta Xcode toolchains (e.g. Xcode 27 / macOS 27 SDK) cannot compile: the metal compiler dies non-deterministically (a different .metal file each run, 'Could not read serialized diagnostics file'). The main app builds fine, so an unrelated dev/proof build should not be blocked by the helper. Add OPENCLAW_SKIP_MLX_TTS=1 (matching the sibling SKIP_TSC/SKIP_UI_BUILD toggles) to package the app without the voice helper, gating both the per-arch build and the bundle copy. Refuse the flag for release builds, which must ship the helper (notarization verifies it), so a skipped build can never become a silently incomplete release.
This commit is contained in:
committed by
GitHub
parent
d9211bf14d
commit
f87580ceb8
@@ -31,6 +31,12 @@ pnpm install
|
||||
Outputs `dist/OpenClaw.app`. Without an Apple Developer ID certificate, the
|
||||
script falls back to ad-hoc signing.
|
||||
|
||||
Set `OPENCLAW_SKIP_MLX_TTS=1` to package a dev/proof build without the local
|
||||
MLX voice helper. This skips the `openclaw-mlx-tts` binary and its large
|
||||
mlx-swift Metal shader stack, which some beta Xcode toolchains cannot compile.
|
||||
The resulting app has no on-device MLX voice; it is rejected for `release`
|
||||
builds, which must ship the helper.
|
||||
|
||||
For dev run modes, signing flags, and Team ID troubleshooting, see
|
||||
[apps/macos/README.md](https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md).
|
||||
Fast dev loop from repo root: `scripts/restart-mac.sh` (add `--no-sign` for
|
||||
@@ -70,6 +76,26 @@ xcrun swift --version
|
||||
|
||||
If versions don't match, update macOS/Xcode and re-run the build.
|
||||
|
||||
### Build fails: MLX voice helper Metal shaders
|
||||
|
||||
On a beta-only Xcode toolchain (for example Xcode 27 with the macOS 27 SDK),
|
||||
only the `openclaw-mlx-tts` helper may fail while the main app builds fine. The
|
||||
mlx-swift Metal compilation errors non-deterministically (a different `.metal`
|
||||
file each run, `Could not read serialized diagnostics file` then a nonzero
|
||||
`metal` exit), because the beta `metal` compiler and its separately downloaded
|
||||
Metal Toolchain are still unstable. This is an upstream toolchain issue, not an
|
||||
OpenClaw one.
|
||||
|
||||
If you do not need on-device MLX voice, skip the helper:
|
||||
|
||||
```bash
|
||||
OPENCLAW_SKIP_MLX_TTS=1 ./scripts/package-mac-app.sh
|
||||
```
|
||||
|
||||
Otherwise, install the Metal Toolchain
|
||||
(`xcodebuild -downloadComponent MetalToolchain`) and build from a stable Xcode
|
||||
release.
|
||||
|
||||
### App crashes on permission grant
|
||||
|
||||
If the app crashes when you try to allow **Speech Recognition** or
|
||||
|
||||
+31
-12
@@ -25,6 +25,17 @@ MLX_TTS_HELPER_BUILD_ROOT="$MLX_TTS_HELPER_ROOT/.build"
|
||||
BUNDLE_ID="${BUNDLE_ID:-ai.openclaw.mac.debug}"
|
||||
PKG_VERSION="$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")"
|
||||
BUILD_CONFIG="${BUILD_CONFIG:-debug}"
|
||||
# OPENCLAW_SKIP_MLX_TTS=1 packages the app without the local MLX voice helper.
|
||||
# The helper pulls in the full mlx-swift Metal shader stack, which some beta
|
||||
# Xcode toolchains cannot compile (flaky `metal` diagnostics), needlessly
|
||||
# blocking unrelated dev/proof builds. Release builds must always ship the
|
||||
# helper (notarization verifies it), so refuse the skip there instead of
|
||||
# producing a silently incomplete release bundle.
|
||||
SKIP_MLX_TTS="${OPENCLAW_SKIP_MLX_TTS:-0}"
|
||||
if [[ "$SKIP_MLX_TTS" == "1" && "$BUILD_CONFIG" == "release" ]]; then
|
||||
echo "ERROR: OPENCLAW_SKIP_MLX_TTS is not allowed for release builds; the MLX voice helper must ship in release." >&2
|
||||
exit 1
|
||||
fi
|
||||
BUILD_TS="$(openclaw_resolve_build_timestamp)"
|
||||
if [[ "$BUILD_CONFIG" == "release" ]]; then
|
||||
OPENCLAW_REQUIRE_BUILD_METADATA=1
|
||||
@@ -431,8 +442,12 @@ for arch in "${BUILD_ARCHS[@]}"; do
|
||||
echo "🔨 Building $PRODUCT ($BUILD_CONFIG) [$arch]"
|
||||
run_with_locked_swift_packages swift build -c "$BUILD_CONFIG" --product "$PRODUCT" --build-path "$BUILD_PATH" --arch "$arch" -Xlinker -rpath -Xlinker @executable_path/../Frameworks
|
||||
restore_swiftpm_resource_sources
|
||||
echo "🔨 Building $MLX_TTS_HELPER_PRODUCT ($BUILD_CONFIG) [$arch]"
|
||||
build_mlx_tts_helper "$arch"
|
||||
if [[ "$SKIP_MLX_TTS" == "1" ]]; then
|
||||
echo "🔇 Skipping $MLX_TTS_HELPER_PRODUCT (OPENCLAW_SKIP_MLX_TTS=1) — app will lack the local MLX voice helper [$arch]"
|
||||
else
|
||||
echo "🔨 Building $MLX_TTS_HELPER_PRODUCT ($BUILD_CONFIG) [$arch]"
|
||||
build_mlx_tts_helper "$arch"
|
||||
fi
|
||||
done
|
||||
|
||||
BIN_PRIMARY="$(bin_for_arch "$PRIMARY_ARCH")"
|
||||
@@ -490,17 +505,21 @@ chmod +x "$APP_ROOT/Contents/MacOS/OpenClaw"
|
||||
# SwiftPM outputs ad-hoc signed binaries; strip the signature before install_name_tool to avoid warnings.
|
||||
/usr/bin/codesign --remove-signature "$APP_ROOT/Contents/MacOS/OpenClaw" 2>/dev/null || true
|
||||
|
||||
echo "🚚 Copying MLX TTS helper"
|
||||
cp "$(helper_bin_for_arch "$PRIMARY_ARCH")" "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then
|
||||
HELPER_BIN_INPUTS=()
|
||||
for arch in "${BUILD_ARCHS[@]}"; do
|
||||
HELPER_BIN_INPUTS+=("$(helper_bin_for_arch "$arch")")
|
||||
done
|
||||
/usr/bin/lipo -create "${HELPER_BIN_INPUTS[@]}" -output "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
if [[ "$SKIP_MLX_TTS" == "1" ]]; then
|
||||
echo "🔇 Skipping MLX TTS helper copy (OPENCLAW_SKIP_MLX_TTS=1) — bundle omits Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
else
|
||||
echo "🚚 Copying MLX TTS helper"
|
||||
cp "$(helper_bin_for_arch "$PRIMARY_ARCH")" "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then
|
||||
HELPER_BIN_INPUTS=()
|
||||
for arch in "${BUILD_ARCHS[@]}"; do
|
||||
HELPER_BIN_INPUTS+=("$(helper_bin_for_arch "$arch")")
|
||||
done
|
||||
/usr/bin/lipo -create "${HELPER_BIN_INPUTS[@]}" -output "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
fi
|
||||
chmod +x "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
/usr/bin/codesign --remove-signature "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT" 2>/dev/null || true
|
||||
fi
|
||||
chmod +x "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT"
|
||||
/usr/bin/codesign --remove-signature "$APP_ROOT/Contents/MacOS/$MLX_TTS_HELPER_PRODUCT" 2>/dev/null || true
|
||||
|
||||
SPARKLE_FRAMEWORK_PRIMARY="$(sparkle_framework_for_arch "$PRIMARY_ARCH")"
|
||||
if [ -d "$SPARKLE_FRAMEWORK_PRIMARY" ]; then
|
||||
|
||||
@@ -827,6 +827,45 @@ describe("package-mac-app plist stamping", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("skips the MLX TTS helper build and copy when OPENCLAW_SKIP_MLX_TTS=1", () => {
|
||||
const script = readFileSync(scriptPath, "utf8");
|
||||
|
||||
// Both the per-arch build and the bundle copy are gated on the same flag so
|
||||
// a skipped build never tries to copy a helper binary that was not built.
|
||||
expect(script).toContain(
|
||||
'if [[ "$SKIP_MLX_TTS" == "1" ]]; then\n echo "🔇 Skipping $MLX_TTS_HELPER_PRODUCT (OPENCLAW_SKIP_MLX_TTS=1)',
|
||||
);
|
||||
expect(script).toContain(
|
||||
'if [[ "$SKIP_MLX_TTS" == "1" ]]; then\n echo "🔇 Skipping MLX TTS helper copy (OPENCLAW_SKIP_MLX_TTS=1)',
|
||||
);
|
||||
});
|
||||
|
||||
it("refuses OPENCLAW_SKIP_MLX_TTS for release builds but allows it for dev builds", () => {
|
||||
const script = readFileSync(scriptPath, "utf8");
|
||||
|
||||
// Run the real guard snippet from the script (not a copy) so the release
|
||||
// safety invariant stays coupled to source: release bundles must ship the
|
||||
// voice helper, which notarization later verifies.
|
||||
const guardStart = script.indexOf('SKIP_MLX_TTS="${OPENCLAW_SKIP_MLX_TTS:-0}"');
|
||||
const guardEnd = script.indexOf("BUILD_TS=", guardStart);
|
||||
expect(guardStart).toBeGreaterThanOrEqual(0);
|
||||
expect(guardEnd).toBeGreaterThan(guardStart);
|
||||
const guard = script.slice(guardStart, guardEnd);
|
||||
|
||||
const released = runHelper(
|
||||
`set -euo pipefail\nexport OPENCLAW_SKIP_MLX_TTS=1\nBUILD_CONFIG=release\n${guard}\necho reached-build`,
|
||||
);
|
||||
expect(released.status).toBe(1);
|
||||
expect(released.stderr).toContain("not allowed for release builds");
|
||||
expect(released.stdout).not.toContain("reached-build");
|
||||
|
||||
const dev = runHelper(
|
||||
`set -euo pipefail\nexport OPENCLAW_SKIP_MLX_TTS=1\nBUILD_CONFIG=debug\n${guard}\necho reached-build`,
|
||||
);
|
||||
expect(dev.status, dev.stderr).toBe(0);
|
||||
expect(dev.stdout).toContain("reached-build");
|
||||
});
|
||||
|
||||
it("falls back to corepack pnpm when the pnpm shim is absent", () => {
|
||||
const helperBlock = getPackageManagerHelperBlock();
|
||||
const tempRoot = tempDirs.make("openclaw-package-pnpm-root-");
|
||||
|
||||
Reference in New Issue
Block a user