Skip to content

Commit f7fe1c0

Browse files
committed
fix
1 parent 233aae9 commit f7fe1c0

3 files changed

Lines changed: 97 additions & 22 deletions

File tree

tools/dossier_codesigningtool/dossier_codesigning_reader_test.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,16 @@
7474
'embedded_bundle_manifests': [],
7575
}
7676

77-
_IPA_WORKSPACE_PATH = 'test/starlark_tests/targets_under_test/ios/app.ipa'
78-
_IPA_W_WATCHOS_WORKSPACE_PATH = 'test/starlark_tests/targets_under_test/watchos/app_companion.ipa'
79-
_COMBINED_ZIP_W_WATCHOS_WORKSPACE_PATH = 'test/starlark_tests/targets_under_test/watchos/app_companion_dossier_with_bundle.zip'
77+
_IPA_WORKSPACE_PATH = (
78+
'test/starlark_tests/targets_under_test/ios/ipa_with_app.ipa'
79+
)
80+
_IPA_W_WATCHOS_WORKSPACE_PATH = (
81+
'test/starlark_tests/targets_under_test/watchos/ipa_app_companion.ipa'
82+
)
83+
_COMBINED_ZIP_W_WATCHOS_WORKSPACE_PATH = (
84+
'test/starlark_tests/targets_under_test/watchos/'
85+
'ipa_app_companion_dossier_with_bundle.zip'
86+
)
8087

8188
_ADDITIONAL_SIGNING_KEYCHAIN = '/tmp/Library/Keychains/ios-dev-signing.keychain'
8289

tools/imported_dynamic_framework_processor/imported_dynamic_framework_processor.py

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@
3434
import os
3535
import re
3636
import shutil
37+
import stat
3738
import sys
3839
import textwrap
3940
import time
41+
import zipfile
4042
from typing import List, Optional
4143

4244
from tools.bitcode_strip import bitcode_strip
@@ -129,6 +131,47 @@ def _update_modified_timestamps(framework_temp_path: str) -> None:
129131
os.utime(framework_temp_path, (timestamp, timestamp))
130132

131133

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+
132175
def _relpath_from_framework(framework_absolute_path):
133176
"""Returns a relative path to the root of the framework bundle."""
134177
framework_dir = None
@@ -381,23 +424,9 @@ def main() -> None:
381424
if status_code:
382425
return status_code
383426

384-
# Update modified timestamps and create archive using ditto.
427+
# Update modified timestamps and create archive.
385428
_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)
401430

402431

403432
if __name__ == "__main__":

tools/imported_dynamic_framework_processor/imported_dynamic_framework_processor_test.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
import os
1717
import shutil
18+
import stat
1819
import tempfile
1920
import unittest
21+
import zipfile
2022
from unittest import mock
2123

2224
from tools.imported_dynamic_framework_processor import imported_dynamic_framework_processor
@@ -41,6 +43,12 @@ def _scratch_file(self, path, content=""):
4143
fp.write(content)
4244
return full_path
4345

46+
def _scratch_symlink(self, path, target):
47+
full_path = os.path.join(self._scratch_dir, path)
48+
os.makedirs(os.path.dirname(full_path), exist_ok=True)
49+
os.symlink(target, full_path)
50+
return full_path
51+
4452
@mock.patch.object(execute, "execute_and_filter_output")
4553
def test_get_install_path_for_binary(self, mock_execute):
4654
with self.assertRaisesRegex(
@@ -194,7 +202,7 @@ def test_strip_or_copy_binary_skips_lipo_with_matching_archs_bin(
194202
imported_dynamic_framework_processor.codesigningtool,
195203
"find_identity_and_sign_bundle_paths")
196204
@mock.patch.object(
197-
imported_dynamic_framework_processor.execute, "execute_and_filter_output")
205+
imported_dynamic_framework_processor, "_create_framework_zip")
198206
@mock.patch.object(
199207
imported_dynamic_framework_processor, "_update_modified_timestamps")
200208
@mock.patch.object(
@@ -206,7 +214,7 @@ def test_main_reconstructs_versioned_framework_and_signs_current_version(
206214
mock_parse_args,
207215
mock_try_get_framework_version_from_structure,
208216
mock_update_modified_timestamps,
209-
mock_execute,
217+
mock_create_framework_zip,
210218
mock_sign):
211219
framework_binary = self._scratch_file(
212220
"Foo.framework/Versions/A/Foo", "framework-binary")
@@ -270,7 +278,38 @@ def test_main_reconstructs_versioned_framework_and_signs_current_version(
270278
)
271279
mock_sign.assert_called_once()
272280
mock_update_modified_timestamps.assert_called_once_with(temp_path)
273-
mock_execute.assert_called_once()
281+
mock_create_framework_zip.assert_called_once_with(temp_path, output_zip)
282+
283+
def test_create_framework_zip_preserves_versioned_framework_symlinks(self):
284+
temp_path = os.path.join(self._scratch_dir, "out", "Foo.framework")
285+
output_zip = os.path.join(self._scratch_dir, "Foo.zip")
286+
self._scratch_file("out/Foo.framework/Versions/A/Foo", "binary")
287+
self._scratch_file(
288+
"out/Foo.framework/Versions/A/Resources/Info.plist", "plist")
289+
self._scratch_symlink("out/Foo.framework/Versions/Current", "A")
290+
self._scratch_symlink(
291+
"out/Foo.framework/Foo", "Versions/Current/Foo")
292+
self._scratch_symlink(
293+
"out/Foo.framework/Resources", "Versions/Current/Resources")
294+
295+
imported_dynamic_framework_processor._create_framework_zip(
296+
temp_path, output_zip)
297+
298+
with zipfile.ZipFile(output_zip) as zip_file:
299+
self.assertNotIn("Foo.framework/Resources/", zip_file.namelist())
300+
self.assertEqual(
301+
b"Versions/Current/Resources",
302+
zip_file.read("Foo.framework/Resources"),
303+
)
304+
self.assertTrue(stat.S_ISLNK(
305+
zip_file.getinfo("Foo.framework/Resources").external_attr >> 16))
306+
self.assertEqual(
307+
b"A",
308+
zip_file.read("Foo.framework/Versions/Current"),
309+
)
310+
self.assertTrue(stat.S_ISLNK(
311+
zip_file.getinfo(
312+
"Foo.framework/Versions/Current").external_attr >> 16))
274313

275314
if __name__ == "__main__":
276315
unittest.main()

0 commit comments

Comments
 (0)