|
34 | 34 | import os |
35 | 35 | import re |
36 | 36 | import shutil |
| 37 | +import stat |
37 | 38 | import sys |
38 | 39 | import textwrap |
39 | 40 | import time |
| 41 | +import zipfile |
40 | 42 | from typing import List, Optional |
41 | 43 |
|
42 | 44 | from tools.bitcode_strip import bitcode_strip |
@@ -129,6 +131,47 @@ def _update_modified_timestamps(framework_temp_path: str) -> None: |
129 | 131 | os.utime(framework_temp_path, (timestamp, timestamp)) |
130 | 132 |
|
131 | 133 |
|
| 134 | +def _create_framework_zip(framework_temp_path: str, output_zip: str) -> None: |
| 135 | + """Creates a zip archive for a processed framework bundle. |
| 136 | +
|
| 137 | + The macOS `ditto` zip writer has varied in how it records symlinked |
| 138 | + directories. Write entries directly so versioned framework symlinks stay |
| 139 | + symlinks across host OS versions. |
| 140 | + """ |
| 141 | + framework_parent = os.path.dirname(framework_temp_path) |
| 142 | + fixed_date_time = (2000, 1, 1, 0, 0, 0) |
| 143 | + |
| 144 | + with zipfile.ZipFile(output_zip, "w", allowZip64=True) as out_zip: |
| 145 | + for root, dirs, files in os.walk(framework_temp_path, followlinks=False): |
| 146 | + dirs.sort() |
| 147 | + files.sort() |
| 148 | + entries = dirs + files |
| 149 | + for entry in entries: |
| 150 | + path = os.path.join(root, entry) |
| 151 | + zip_path = os.path.relpath(path, framework_parent) |
| 152 | + |
| 153 | + if os.path.islink(path): |
| 154 | + zip_info = zipfile.ZipInfo(zip_path, fixed_date_time) |
| 155 | + zip_info.create_system = 3 |
| 156 | + zip_info.compress_type = zipfile.ZIP_STORED |
| 157 | + zip_info.external_attr = (stat.S_IFLNK | 0o755) << 16 |
| 158 | + out_zip.writestr(zip_info, os.readlink(path).encode("utf-8")) |
| 159 | + elif os.path.isdir(path): |
| 160 | + zip_info = zipfile.ZipInfo(zip_path + "/", fixed_date_time) |
| 161 | + zip_info.create_system = 3 |
| 162 | + zip_info.compress_type = zipfile.ZIP_STORED |
| 163 | + zip_info.external_attr = (stat.S_IFDIR | 0o755) << 16 | 0x10 |
| 164 | + out_zip.writestr(zip_info, b"") |
| 165 | + elif os.path.isfile(path): |
| 166 | + mode = stat.S_IMODE(os.stat(path).st_mode) |
| 167 | + zip_info = zipfile.ZipInfo(zip_path, fixed_date_time) |
| 168 | + zip_info.create_system = 3 |
| 169 | + zip_info.compress_type = zipfile.ZIP_DEFLATED |
| 170 | + zip_info.external_attr = (stat.S_IFREG | mode) << 16 |
| 171 | + with open(path, "rb") as file: |
| 172 | + out_zip.writestr(zip_info, file.read()) |
| 173 | + |
| 174 | + |
132 | 175 | def _relpath_from_framework(framework_absolute_path): |
133 | 176 | """Returns a relative path to the root of the framework bundle.""" |
134 | 177 | framework_dir = None |
@@ -381,23 +424,9 @@ def main() -> None: |
381 | 424 | if status_code: |
382 | 425 | return status_code |
383 | 426 |
|
384 | | - # Update modified timestamps and create archive using ditto. |
| 427 | + # Update modified timestamps and create archive. |
385 | 428 | _update_modified_timestamps(args.temp_path) |
386 | | - |
387 | | - # Previous implementation of creating the processed framework archive |
388 | | - # using shutil/zip already stripped the extended attributes of the bundle. |
389 | | - execute.execute_and_filter_output( |
390 | | - cmd_args=[ |
391 | | - "/usr/bin/ditto", |
392 | | - "-c", |
393 | | - "-k", # use PKZip format for bundletool compatibility. |
394 | | - "--keepParent", # preserves the .framework directory. |
395 | | - "--norsrc", # strip resource forks and HFS metadata. |
396 | | - "--noextattr", # strip extended attributes. |
397 | | - args.temp_path, |
398 | | - args.output_zip |
399 | | - ], |
400 | | - raise_on_failure=True) |
| 429 | + _create_framework_zip(args.temp_path, args.output_zip) |
401 | 430 |
|
402 | 431 |
|
403 | 432 | if __name__ == "__main__": |
|
0 commit comments