fix: preserve macOS framework symlinks in portable ZIP archives

This commit is contained in:
2026-09-09 22:42:06 +00:00
parent 233b4989a8
commit b1e2e18efa
2 changed files with 61 additions and 1 deletions
+53
View File
@@ -0,0 +1,53 @@
"""Package an existing macOS app with Unix symlinks intact (stdlib only)."""
import argparse
import os
from pathlib import Path
import stat
import time
import zipfile
def package(app, output):
app = Path(app).resolve()
output = Path(output).resolve()
assert app.is_dir() and app.suffix == '.app'
with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
def add(path):
st = path.lstat()
name = path.relative_to(app.parent).as_posix()
is_dir = stat.S_ISDIR(st.st_mode)
info = zipfile.ZipInfo(name + ('/' if is_dir else ''), time.localtime(st.st_mtime)[:6])
info.create_system = 3
info.external_attr = st.st_mode << 16
if is_dir:
info.external_attr |= 0x10
info.compress_type = zipfile.ZIP_STORED if is_dir else zipfile.ZIP_DEFLATED
if stat.S_ISLNK(st.st_mode):
archive.writestr(info, os.readlink(path).encode('utf-8'))
elif is_dir:
archive.writestr(info, b'')
for child in sorted(path.iterdir()):
add(child)
elif stat.S_ISREG(st.st_mode):
with path.open('rb') as src, archive.open(info, 'w') as dst:
while chunk := src.read(1024 * 1024):
dst.write(chunk)
else:
raise ValueError(f'Unsupported file type: {path}')
add(app)
with zipfile.ZipFile(output) as archive:
assert archive.testzip() is None
for item in archive.infolist():
source = app.parent / item.filename.rstrip('/')
assert (item.external_attr >> 16) == source.lstat().st_mode
if source.is_symlink():
assert archive.read(item).decode('utf-8') == os.readlink(source)
print(output)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('app')
parser.add_argument('output')
args = parser.parse_args()
package(args.app, args.output)