Skip to content

darwin/macos: keep bundled C extensions linker-signed (no explicit codesign)#22

Merged
FeodorFitsner merged 1 commit into
mainfrom
spm-friendly-macos-extension-signing
Jun 21, 2026
Merged

darwin/macos: keep bundled C extensions linker-signed (no explicit codesign)#22
FeodorFitsner merged 1 commit into
mainfrom
spm-friendly-macos-extension-signing

Conversation

@FeodorFitsner

Copy link
Copy Markdown
Contributor

Problem

macOS lib-dynload/*.so (and the bundled extensions generally) ship with an
explicit adhoc signature (CodeDirectory flags=0x2). When a downstream app
(Flutter/Xcode, via serious_python — SPM or CocoaPods) bundles them as
resources, Xcode's Release "strip" phase refuses to strip a code-signed Mach-O
(stripping invalidates the signature) and emits one warning per extension:

warning: not stripping binary because it is signed: .../lib-dynload/_ssl.cpython-3xx-darwin.so

…and the extensions stay unstripped, bloating the app.

Root cause

strip_framework() already strips every .so/.dylib, and on arm64 they keep
the linker-signed adhoc signature (flags=0x20002) that install_name_tool
/ strip re-apply — a replaceable signature. codesign_framework() then
re-signs each .so with codesign -s -, replacing that with an explicit
adhoc signature (0x2), which the toolchain treats as final and won't strip or
re-sign.

A pip wheel's .so (e.g. numpy) keeps its linker-signed signature and sails
through Xcode's strip + re-sign with no warnings — the only difference was this
explicit re-sign.

Fix

codesign_framework() now signs the framework binary and the bundled OpenSSL
.dylib, but not the C-extension .so — leaving them stripped +
linker-signed (replaceable), exactly like a wheel. They're extracted into the
stdlib resource tree, so they aren't covered by the framework's own seal.

Verification (this branch's CI artifacts, run 27911450856)

  • lib-dynload/*.so: 60/60 now flags=0x20002 (adhoc, linker-signed) (was 0x2), and smaller (_ssl 420000 → 387080).
  • dyld loads them — a standalone dlopen fails only on undefined Python symbols, not on the signature.
  • codesign --verify reports "not signed at all" — identical to a numpy wheel .so (the normal linker-signed quirk; the consuming app re-signs them).
  • Framework Python binary + OpenSSL libssl.dylib unchanged (still explicit 0x2).

Net for any consuming app (SPM or CocoaPods): the macOS extensions strip + re-sign cleanly — no more per-extension "not stripping" warnings, smaller bundles.

…er-signed)

strip_framework() already strips every .so/.dylib, and on arm64 they keep the
*linker-signed* adhoc signature install_name_tool/strip apply (CodeDirectory
flags 0x20002). codesign_framework() was then re-signing the .so with
`codesign -s -`, replacing that with an *explicit* adhoc signature (flags 0x2).

An explicit adhoc signature is treated as final: a downstream Xcode app build
refuses to strip it ("not stripping binary because it is signed", once per
extension) and won't re-sign it. A linker-signed signature is replaceable, so
the app build strips + re-signs the extension cleanly — exactly like a pip
wheel's .so.

Sign only the framework binary and the bundled OpenSSL dylibs; leave the
lib-dynload .so stripped + linker-signed. They're extracted into the stdlib
resource tree, so they aren't covered by the framework's own seal anyway. Result
for any consuming app (SPM or CocoaPods): no per-extension strip warnings and a
smaller app, since Xcode can finally strip + re-sign them.
@FeodorFitsner FeodorFitsner merged commit af77660 into main Jun 21, 2026
15 of 27 checks passed
@FeodorFitsner FeodorFitsner deleted the spm-friendly-macos-extension-signing branch June 21, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant