fix(build): restore Python.framework symlinks before signing#1260
fix(build): restore Python.framework symlinks before signing#1260
Conversation
PyInstaller copies Python.framework using real files/directories instead of preserving the standard macOS symlink layout. This causes codesign to reject the framework with "bundle format is ambiguous", triggering a fallback path that signs individual binaries without creating a proper framework bundle signature. Apple's notarization then rejects all Python.framework binaries with "The signature of the binary is invalid." Fix: after copying watchers into the .app bundle, restore the canonical macOS framework layout using symlinks: - Versions/Current -> <version> - Python -> Versions/Current/Python - Resources -> Versions/Current/Resources This lets codesign sign the framework as a proper bundle (verified locally), producing valid bundle signatures that Apple should accept.
Greptile SummaryThis PR inserts a pre-signing step in Confidence Score: 5/5Safe to merge — the fix is logically correct and all remaining comments are P2 style suggestions. The symlink restoration logic correctly handles the described PyInstaller layout (Versions/3.9 as the real dir, Current as the copy to replace). Guards against already-correct symlinks are present. The two P2 comments are robustness suggestions that don't affect correctness in the expected build environment. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[cp -r components into .app] --> B["find Python.framework dirs"]
B --> C{Any found?}
C -- No --> G[Set executable permissions]
C -- Yes --> D["Discover version_dir\n(ls Versions/ | grep -v Current)"]
D --> E{version_dir empty?}
E -- Yes --> F[Warning: skip this framework]
F --> D2[Next framework]
E -- No --> H{"Versions/Current is\na real dir?"}
H -- Yes --> I["rm -rf Versions/Current\nln -s version_dir Versions/Current"]
H -- Already a symlink --> J
I --> J["For Python, Resources, Headers:\nif real file/dir → replace with symlink"]
J --> D2
D2 --> C
G --> K[Sign .app inside-out\nStep 1: Mach-O leaves\nStep 2: .framework bundles\nStep 3: top-level .app]
Reviews (1): Last reviewed commit: "fix(build): restore Python.framework sym..." | Re-trigger Greptile |
| while IFS= read -r fw; do | ||
| echo " Fixing: $fw" | ||
| # Find the actual version directory (e.g., "3.9"), skipping "Current" | ||
| version_dir=$(ls "$fw/Versions/" 2>/dev/null | grep -v Current | head -1) |
There was a problem hiding this comment.
ls | grep | head is fragile for version discovery
Parsing ls output in shell scripts is unreliable — locale settings can affect ordering, and grep exits non-zero via SIGPIPE when head -1 terminates the pipeline early. A glob-based approach is safer and sidesteps all of these:
| version_dir=$(ls "$fw/Versions/" 2>/dev/null | grep -v Current | head -1) | |
| version_dir="" | |
| for d in "$fw/Versions"/*/; do | |
| bname="$(basename "$d")" | |
| if [ "$bname" != "Current" ] && [ -d "$d" ]; then | |
| version_dir="$bname" | |
| break | |
| fi | |
| done |
This also implicitly verifies that the matched entry is an actual directory, guarding against unexpected files in Versions/.
| done < <(find "dist/${APP_NAME}.app" -type d -name "*.framework" \ | ||
| | grep -i python) |
There was a problem hiding this comment.
grep -i python may over-match framework paths
grep -i python applied to the full path can match any framework whose path contains "python" (e.g., a hypothetical SomePythonTool.framework). Using -iname directly in find is more precise and removes the extra pipe:
| done < <(find "dist/${APP_NAME}.app" -type d -name "*.framework" \ | |
| | grep -i python) | |
| done < <(find "dist/${APP_NAME}.app" -type d -iname "Python.framework") |
…fy find - Replace ls|grep|head with glob loop for version dir discovery (avoids SIGPIPE and locale issues) - Use find -iname "Python.framework" instead of -name "*.framework"|grep
Summary
codesignto reject it with "bundle format is ambiguous" and triggering a fallback path that never produces valid framework bundle signatures_CodeSignature/CodeResourcesseal that Apple expects for.frameworkbundlesVerified locally that restoring symlinks fixes the signing:
codesign→ "bundle format is ambiguous" (current CI behavior)codesign→ signs and verifies successfullyWhat changed
After
cp -rcopies watcher components into the.app, a new step finds allPython.frameworkdirectories and restores the canonical layout:The existing fallback code (strip-sign-sync) is left in place as a safety net but should no longer trigger for Python.framework.
Test plan
macos-14andmacos-latest)Create dev releaseworkflow should producev0.13.3b1Fixes #1216
Related: ErikBjare/bob#546