Skip to content

Commit a3e9e3f

Browse files
authored
fix: copy SPM resource bundles to Contents/MacOS/ to prevent startup crash (#118)
* fix: copy SPM resource bundles to Contents/MacOS/ to prevent crash SPM's auto-generated Bundle.module accessor looks for resource bundles at Bundle.main.bundleURL/<name>.bundle. For an SPM executable wrapped in a .app bundle, this resolves to Contents/MacOS/ at runtime. The accessor also hardcodes the developer's build-time path as a fallback, which only works on the machine that built it. Previously, the build script only copied resource bundles to Contents/Resources/. This meant the app worked on the developer's machine (via the hardcoded fallback) but crashed with a fatalError on end-user machines where neither path resolved — specifically during tokenizer loading via swift-transformers' Hub module (Bundle.module). Fix: copy resource bundles into both Contents/Resources/ (standard macOS convention) and Contents/MacOS/ (where SPM's accessor actually looks). Also clean up any stale bundles at the .app root from previous builds. Fixes crash: EXC_BREAKPOINT in NSBundle.module during app startup. * fix: sign resource bundles in Contents/MacOS/ before signing app The previous commit copied resource bundles to Contents/MacOS/ but didn't sign them there. codesign rejects unsigned nested bundles, causing the CI build to fail with 'bundle format unrecognized, invalid, or unsuitable'. * fix: add Info.plist to SPM resource bundles for codesign compatibility SPM-generated resource bundles (e.g. swift-transformers_Hub.bundle) may lack an Info.plist, causing codesign to reject them with 'bundle format unrecognized, invalid, or unsuitable'. This adds a minimal Info.plist to any resource bundle that lacks one before signing. * fix: restructure SPM resource bundles for codesign compatibility SPM generates flat resource bundles (files at bundle root) that codesign rejects in Contents/MacOS/ with 'bundle format unrecognized'. This restructures them into proper macOS bundle layout (Contents/Resources/ + Contents/Info.plist) when copying to Contents/MacOS/, so they pass codesign validation on both Developer ID and ad-hoc signed builds. Also adds VocaMac.app.bak/ to .gitignore.
1 parent a49aebe commit a3e9e3f

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ bitbucket-pipelines.yml
4444
# Tool artifacts
4545
.desloppify/
4646
.claude/
47+
VocaMac.app.bak/

scripts/build.sh

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,51 @@ fi
9797
cp -f "$BINARY" "${APP_DIR}/Contents/MacOS/${APP_NAME}"
9898

9999
# Update resource bundles
100+
# SPM's auto-generated Bundle.module accessor looks for resource bundles at
101+
# Bundle.main.bundleURL/<name>.bundle. For an SPM executable in a .app wrapper,
102+
# Bundle.main.bundleURL resolves to Contents/MacOS/ at runtime. The accessor
103+
# also hardcodes the developer's build-time path, which only works on the
104+
# machine that built it. On end-user machines neither path resolves →
105+
# fatalError → crash.
106+
#
107+
# Fix: copy bundles to both Contents/Resources/ (standard macOS convention) and
108+
# Contents/MacOS/ (where SPM's accessor looks at runtime). Bundles in MacOS/
109+
# must have a proper Info.plist so codesign accepts them as nested bundles.
110+
#
111+
# Clean up any stale bundles / symlinks at the app root from previous builds.
112+
find "${APP_DIR}" -maxdepth 1 -name "*.bundle" ! -name "Contents" -exec rm -rf {} + 2>/dev/null || true
113+
100114
find ".build/arm64-apple-macosx/${CONFIG}" -maxdepth 1 -name "*.bundle" | while read -r bundle; do
115+
bundle_name="$(basename "$bundle")"
101116
cp -rf "$bundle" "${APP_DIR}/Contents/Resources/"
117+
118+
# Copy to Contents/MacOS/ with a valid bundle structure for codesign.
119+
# codesign requires bundles to have Contents/Info.plist and resources in
120+
# Contents/Resources/. SPM builds flat bundles, so we restructure them.
121+
DEST="${APP_DIR}/Contents/MacOS/${bundle_name}"
122+
rm -rf "$DEST"
123+
mkdir -p "${DEST}/Contents/Resources"
124+
# Copy all original files into Contents/Resources/.
125+
cp -rf "$bundle"/* "${DEST}/Contents/Resources/" 2>/dev/null || true
126+
cp -rf "$bundle"/.[!.]* "${DEST}/Contents/Resources/" 2>/dev/null || true
127+
# Add a minimal Info.plist if one doesn't already exist.
128+
if [ ! -f "${DEST}/Contents/Info.plist" ]; then
129+
bundle_id="com.vocamac.resource.$(echo "${bundle_name%.bundle}" | tr '_ ' '-')"
130+
cat > "${DEST}/Contents/Info.plist" << BPLIST
131+
<?xml version="1.0" encoding="UTF-8"?>
132+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
133+
<plist version="1.0">
134+
<dict>
135+
<key>CFBundleIdentifier</key>
136+
<string>${bundle_id}</string>
137+
<key>CFBundlePackageType</key>
138+
<string>BNDL</string>
139+
<key>CFBundleVersion</key>
140+
<string>1</string>
141+
</dict>
142+
</plist>
143+
BPLIST
144+
fi
102145
done
103146

104147
# Copy app icon and compile Asset Catalog
@@ -192,8 +235,8 @@ if [ "$CODE_SIGN_IDENTITY" != "-" ]; then
192235
CODESIGN_OPTIONS="--options runtime"
193236
fi
194237

195-
# Sign nested bundles first
196-
find "${APP_DIR}/Contents/Resources" -name "*.bundle" -exec \
238+
# Sign nested bundles first (in both Resources/ and MacOS/)
239+
find "${APP_DIR}/Contents/Resources" "${APP_DIR}/Contents/MacOS" -name "*.bundle" -exec \
197240
codesign --force --sign "$CODE_SIGN_IDENTITY" $CODESIGN_OPTIONS {} \; 2>/dev/null || true
198241

199242
# Sign the main app

0 commit comments

Comments
 (0)