fix: preserve macOS framework symlinks in portable ZIP archives
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user