From c45e62859b7d79a2aee68373ed5c2fdf30d1ccc1 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 30 Jul 2026 14:22:33 -0700 Subject: [PATCH] Fix macOS framework layout so the inner bundle can be signed The signing job failed on every python-macos-dart archive with: Python.framework: unsealed contents present in the root directory of an embedded framework macOS frameworks are VERSIONED bundles: everything at the framework root must be a symlink into Versions/Current, and only `Versions` itself may be a real directory. package-macos-for-dart.sh wrote the overlaid `Modules` and the public `Headers` as real directories at the root. That was invisible while only the outer xcframework was signed -- the outer seal does not care -- and became fatal the moment the inner framework was signed in its own right. They now go into Versions/Current with symlinks at the root, which is both what codesign requires and what CPython's own macOS framework already does for Headers and Resources. Consumers resolve Headers/ and Modules/ through the symlinks exactly as before. Also fixes the verifier, which was wrong in a way the same run exposed: it probed for `_CodeSignature` or `Versions/A/_CodeSignature`, but a versioned bundle's version directory is not always "A" -- CPython's macOS framework uses Versions/3.14 -- so a correctly signed macOS framework was reported as unsigned. It now asks `codesign -dv`, which is layout-agnostic and is the actual question. Verified with the real identity against the released 20260729 macOS archive: inner and outer both sign and verify, dart_bridge v1.7.1 (Versions/A) still verifies, and stripping the inner signature still fails the run. --- darwin/package-macos-for-dart.sh | 30 ++++++++++++++++++++++++++---- darwin/xcframework_signing.sh | 7 ++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/darwin/package-macos-for-dart.sh b/darwin/package-macos-for-dart.sh index cc608ff..502a956 100755 --- a/darwin/package-macos-for-dart.sh +++ b/darwin/package-macos-for-dart.sh @@ -34,12 +34,34 @@ mkdir -p $stdlib_dir # copy Python.xcframework rsync -av --exclude-from=$script_dir/python-darwin-framework.exclude $python_apple_support_root/support/$python_version_short/macOS/Python.xcframework $frameworks_dir -cp -r $script_dir/Modules $frameworks_dir/Python.xcframework/macos-arm64_x86_64/Python.framework -mkdir -p $frameworks_dir/Python.xcframework/macos-arm64_x86_64/Python.framework/Headers -cp -r $python_apple_support_root/support/$python_version_short/macOS/Python.xcframework/macos-arm64_x86_64/Python.framework/Versions/$python_version_short/include/python$python_version_short/* $frameworks_dir/Python.xcframework/macos-arm64_x86_64/Python.framework/Headers +# Overlay the module map and public headers. +# +# macOS frameworks are VERSIONED bundles: everything at the framework root must be +# a symlink into Versions/Current, and only `Versions` itself is a real directory. +# These used to be written as real directories at the root, which codesign rejects +# outright once the framework is signed in its own right: +# +# Python.framework: unsealed contents present in the root directory of an +# embedded framework +# +# That went unnoticed while only the outer xcframework was signed. Write them into +# Versions/Current and symlink from the root, which is both what codesign requires +# and what CPython's own macOS framework does for Headers and Resources. +macos_fw=$frameworks_dir/Python.xcframework/macos-arm64_x86_64/Python.framework +[ -d "$macos_fw/Versions/Current" ] || { + echo "expected a versioned macOS framework at $macos_fw"; exit 1; } + +cp -r $script_dir/Modules "$macos_fw/Versions/Current/" +mkdir -p "$macos_fw/Versions/Current/Headers" +cp -r $python_apple_support_root/support/$python_version_short/macOS/Python.xcframework/macos-arm64_x86_64/Python.framework/Versions/$python_version_short/include/python$python_version_short/* "$macos_fw/Versions/Current/Headers" # The built-in modulemap (if the source framework shipped one) is replaced by the # overlaid darwin/Modules/module.modulemap above; -f tolerates builds without one. -rm -f $frameworks_dir/Python.xcframework/macos-arm64_x86_64/Python.framework/Headers/module.modulemap +rm -f "$macos_fw/Versions/Current/Headers/module.modulemap" + +for _d in Headers Modules; do + rm -rf "$macos_fw/$_d" + ln -sfn "Versions/Current/$_d" "$macos_fw/$_d" +done # Last mutation of the bundle: stable, provider-owned identifier for the Python # runtime, replacing CPython's shared `org.python.python`. The macOS framework is diff --git a/darwin/xcframework_signing.sh b/darwin/xcframework_signing.sh index 414f2e4..eda83bf 100644 --- a/darwin/xcframework_signing.sh +++ b/darwin/xcframework_signing.sh @@ -285,7 +285,12 @@ xcf_verify_one() { local fw count=0 while IFS= read -r fw; do [ -n "$fw" ] || continue - if [ ! -d "$fw/_CodeSignature" ] && [ ! -d "$fw/Versions/A/_CodeSignature" ]; then + # `codesign -dv` rather than probing for a _CodeSignature directory: a + # versioned bundle keeps it at Versions//_CodeSignature, and the + # version directory is not always "A" (CPython's macOS framework uses the + # Python version, e.g. Versions/3.14). codesign exits non-zero with + # "code object is not signed at all", which is the question being asked. + if ! codesign -dv "$fw" >/dev/null 2>&1; then xcf_err "$fw: inner framework is unsigned" return 1 fi