Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 68109b5

Browse files
committed
iOS,macOS: Clean up create_ios_framework.py
Over time, this script and others in sky/tools have accumulated a lot of additional and sometimes duplicate code. This is a first pass cleanup of create_ios_framework.py to extract common utility code to utils.py and refactor for better readability.
1 parent 5d8ee52 commit 68109b5

File tree

3 files changed

+138
-99
lines changed

3 files changed

+138
-99
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@
439439
../../../flutter/sky/tools/create_xcframework.py
440440
../../../flutter/sky/tools/dist_dart_pkg.py
441441
../../../flutter/sky/tools/install_framework_headers.py
442+
../../../flutter/sky/tools/utils.py
442443
../../../flutter/testing
443444
../../../flutter/third_party/.clang-tidy
444445
../../../flutter/third_party/.gitignore

sky/tools/create_ios_framework.py

Lines changed: 48 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,14 @@
99

1010
import argparse
1111
import os
12-
import platform
13-
import shutil
14-
import subprocess
1512
import sys
1613

1714
from create_xcframework import create_xcframework # pylint: disable=import-error
18-
19-
ARCH_SUBPATH = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'
20-
DSYMUTIL = os.path.join(
21-
os.path.dirname(__file__), '..', '..', 'buildtools', ARCH_SUBPATH, 'clang', 'bin', 'dsymutil'
15+
from utils import ( # pylint: disable=import-error
16+
assert_directory, assert_file, buildroot_relative_path, copy_binary, copy_tree, create_zip,
17+
extract_dsym, lipo, strip_binary, write_codesign_config
2218
)
2319

24-
buildroot_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..'))
25-
2620

2721
def main():
2822
parser = argparse.ArgumentParser(
@@ -42,25 +36,25 @@ def main():
4236

4337
args = parser.parse_args()
4438

45-
dst = (args.dst if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst))
39+
dst = (args.dst if os.path.isabs(args.dst) else buildroot_relative_path(args.dst))
4640

4741
arm64_out_dir = (
4842
args.arm64_out_dir
49-
if os.path.isabs(args.arm64_out_dir) else os.path.join(buildroot_dir, args.arm64_out_dir)
43+
if os.path.isabs(args.arm64_out_dir) else buildroot_relative_path(args.arm64_out_dir)
5044
)
5145

5246
x64_out_dir = None
5347
if args.x64_out_dir:
5448
x64_out_dir = (
5549
args.x64_out_dir
56-
if os.path.isabs(args.x64_out_dir) else os.path.join(buildroot_dir, args.x64_out_dir)
50+
if os.path.isabs(args.x64_out_dir) else buildroot_relative_path(args.x64_out_dir)
5751
)
5852

5953
simulator_x64_out_dir = None
6054
if args.simulator_x64_out_dir:
6155
simulator_x64_out_dir = (
6256
args.simulator_x64_out_dir if os.path.isabs(args.simulator_x64_out_dir) else
63-
os.path.join(buildroot_dir, args.simulator_x64_out_dir)
57+
buildroot_relative_path(args.simulator_x64_out_dir)
6458
)
6559

6660
framework = os.path.join(dst, 'Flutter.framework')
@@ -72,24 +66,15 @@ def main():
7266
if args.simulator_arm64_out_dir:
7367
simulator_arm64_out_dir = (
7468
args.simulator_arm64_out_dir if os.path.isabs(args.simulator_arm64_out_dir) else
75-
os.path.join(buildroot_dir, args.simulator_arm64_out_dir)
69+
buildroot_relative_path(args.simulator_arm64_out_dir)
7670
)
7771

7872
if args.simulator_arm64_out_dir is not None:
7973
simulator_arm64_framework = os.path.join(simulator_arm64_out_dir, 'Flutter.framework')
8074

81-
if not os.path.isdir(arm64_framework):
82-
print('Cannot find iOS arm64 Framework at %s' % arm64_framework)
83-
return 1
84-
85-
if not os.path.isdir(simulator_x64_framework):
86-
print('Cannot find iOS x64 simulator Framework at %s' % simulator_framework)
87-
return 1
88-
89-
if not os.path.isfile(DSYMUTIL):
90-
print('Cannot find dsymutil at %s' % DSYMUTIL)
91-
return 1
92-
75+
assert_directory(arm64_framework, 'iOS arm64 framework')
76+
assert_directory(simulator_arm64_framework, 'iOS arm64 simulator framework')
77+
assert_directory(simulator_x64_framework, 'iOS x64 simulator framework')
9378
create_framework(
9479
args, dst, framework, arm64_framework, simulator_framework, simulator_x64_framework,
9580
simulator_arm64_framework
@@ -101,10 +86,18 @@ def main():
10186
'%s_extension_safe' % simulator_x64_out_dir, '%s_extension_safe' % simulator_arm64_out_dir
10287
)
10388

104-
generate_gen_snapshot(dst, x64_out_dir, arm64_out_dir)
89+
# Copy gen_snapshot binary to destination directory.
90+
if arm64_out_dir:
91+
gen_snapshot = os.path.join(arm64_out_dir, 'gen_snapshot_arm64')
92+
copy_binary(gen_snapshot, os.path.join(dst, 'gen_snapshot_arm64'))
93+
if x64_out_dir:
94+
gen_snapshot = os.path.join(x64_out_dir, 'gen_snapshot_x64')
95+
copy_binary(gen_snapshot, os.path.join(dst, 'gen_snapshot_x64'))
96+
10597
zip_archive(dst)
10698
return 0
10799

100+
108101
def create_extension_safe_framework( # pylint: disable=too-many-arguments
109102
args, dst, arm64_out_dir, simulator_x64_out_dir, simulator_arm64_out_dir
110103
):
@@ -128,20 +121,17 @@ def create_extension_safe_framework( # pylint: disable=too-many-arguments
128121
)
129122
return 0
130123

124+
131125
def create_framework( # pylint: disable=too-many-arguments
132126
args, dst, framework, arm64_framework, simulator_framework,
133127
simulator_x64_framework, simulator_arm64_framework
134128
):
135129
arm64_dylib = os.path.join(arm64_framework, 'Flutter')
136130
simulator_x64_dylib = os.path.join(simulator_x64_framework, 'Flutter')
137131
simulator_arm64_dylib = os.path.join(simulator_arm64_framework, 'Flutter')
138-
if not os.path.isfile(arm64_dylib):
139-
print('Cannot find iOS arm64 dylib at %s' % arm64_dylib)
140-
return 1
141-
142-
if not os.path.isfile(simulator_x64_dylib):
143-
print('Cannot find iOS simulator dylib at %s' % simulator_x64_dylib)
144-
return 1
132+
assert_file(arm64_dylib, 'iOS arm64 dylib')
133+
assert_file(simulator_arm64_dylib, 'iOS simulator arm64 dylib')
134+
assert_file(simulator_x64_dylib, 'iOS simulator x64 dylib')
145135

146136
# Compute dsym output paths, if enabled.
147137
framework_dsym = None
@@ -151,23 +141,15 @@ def create_framework( # pylint: disable=too-many-arguments
151141
simulator_dsym = simulator_framework + '.dSYM'
152142

153143
# Emit the framework for physical devices.
154-
shutil.rmtree(framework, True)
155-
shutil.copytree(arm64_framework, framework)
144+
copy_tree(arm64_framework, framework)
156145
framework_binary = os.path.join(framework, 'Flutter')
157146
process_framework(args, dst, framework_binary, framework_dsym)
158147

159148
# Emit the framework for simulators.
160149
if args.simulator_arm64_out_dir is not None:
161-
shutil.rmtree(simulator_framework, True)
162-
shutil.copytree(simulator_arm64_framework, simulator_framework)
163-
150+
copy_tree(simulator_arm64_framework, simulator_framework)
164151
simulator_framework_binary = os.path.join(simulator_framework, 'Flutter')
165-
166-
# Create the arm64/x64 simulator fat framework.
167-
subprocess.check_call([
168-
'lipo', simulator_x64_dylib, simulator_arm64_dylib, '-create', '-output',
169-
simulator_framework_binary
170-
])
152+
lipo([simulator_x64_dylib, simulator_arm64_dylib], simulator_framework_binary)
171153
process_framework(args, dst, simulator_framework_binary, simulator_dsym)
172154
else:
173155
simulator_framework = simulator_x64_framework
@@ -179,74 +161,41 @@ def create_framework( # pylint: disable=too-many-arguments
179161
dsyms = [simulator_dsym, framework_dsym] if args.dsym else None
180162
create_xcframework(location=dst, name='Flutter', frameworks=xcframeworks, dsyms=dsyms)
181163

182-
# Add the x64 simulator into the fat framework.
183-
subprocess.check_call([
184-
'lipo', arm64_dylib, simulator_x64_dylib, '-create', '-output', framework_binary
185-
])
186-
164+
lipo([arm64_dylib, simulator_x64_dylib], framework_binary)
187165
process_framework(args, dst, framework_binary, framework_dsym)
188166
return 0
189167

190168

191-
def embed_codesign_configuration(config_path, contents):
192-
with open(config_path, 'w') as file:
193-
file.write('\n'.join(contents) + '\n')
194-
195-
196169
def zip_archive(dst):
197-
ios_file_with_entitlements = ['gen_snapshot_arm64']
198-
ios_file_without_entitlements = [
199-
'Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
200-
'Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
201-
'extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
202-
'extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter'
203-
]
204-
embed_codesign_configuration(os.path.join(dst, 'entitlements.txt'), ios_file_with_entitlements)
205-
206-
embed_codesign_configuration(
207-
os.path.join(dst, 'without_entitlements.txt'), ios_file_without_entitlements
170+
write_codesign_config(os.path.join(dst, 'entitlements.txt'), ['gen_snapshot_arm64'])
171+
172+
write_codesign_config(
173+
os.path.join(dst, 'without_entitlements.txt'), [
174+
'Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
175+
'Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
176+
'extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
177+
'extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter'
178+
]
208179
)
209180

210-
subprocess.check_call([
211-
'zip',
212-
'-r',
213-
'artifacts.zip',
214-
'gen_snapshot_arm64',
215-
'Flutter.xcframework',
216-
'entitlements.txt',
217-
'without_entitlements.txt',
218-
'extension_safe/Flutter.xcframework',
219-
],
220-
cwd=dst)
181+
create_zip(
182+
dst, 'artifacts.zip', [
183+
'gen_snapshot_arm64',
184+
'Flutter.xcframework',
185+
'entitlements.txt',
186+
'without_entitlements.txt',
187+
'extension_safe/Flutter.xcframework',
188+
]
189+
)
221190

222191

223192
def process_framework(args, dst, framework_binary, dsym):
224193
if dsym:
225-
subprocess.check_call([DSYMUTIL, '-o', dsym, framework_binary])
194+
extract_dsym(framework_binary, dsym)
226195

227196
if args.strip:
228-
# copy unstripped
229197
unstripped_out = os.path.join(dst, 'Flutter.unstripped')
230-
shutil.copyfile(framework_binary, unstripped_out)
231-
subprocess.check_call(['strip', '-x', '-S', framework_binary])
232-
233-
234-
def generate_gen_snapshot(dst, x64_out_dir, arm64_out_dir):
235-
if x64_out_dir:
236-
x64_path = os.path.join(x64_out_dir, 'gen_snapshot_x64')
237-
_generate_gen_snapshot(x64_path, os.path.join(dst, 'gen_snapshot_x64'))
238-
239-
if arm64_out_dir:
240-
arm64_path = os.path.join(arm64_out_dir, 'gen_snapshot_arm64')
241-
_generate_gen_snapshot(arm64_path, os.path.join(dst, 'gen_snapshot_arm64'))
242-
243-
244-
def _generate_gen_snapshot(gen_snapshot_path, destination):
245-
if not os.path.isfile(gen_snapshot_path):
246-
print('Cannot find gen_snapshot at %s' % gen_snapshot_path)
247-
sys.exit(1)
248-
249-
shutil.copy2(gen_snapshot_path, destination)
198+
strip_binary(framework_binary, unstripped_copy_path=unstripped_out)
250199

251200

252201
if __name__ == '__main__':

sky/tools/utils.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2013 The Flutter Authors. All rights reserved.
4+
# Use of this source code is governed by a BSD-style license that can be
5+
# found in the LICENSE file.
6+
7+
import os
8+
import platform
9+
import shutil
10+
import subprocess
11+
import sys
12+
13+
14+
def assert_directory(path, what):
15+
"""Logs an error and exits with EX_NOINPUT if the specified directory doesn't exist."""
16+
if not os.path.isdir(path):
17+
log_error('Cannot find %s at %s' % (what, path))
18+
sys.exit(os.EX_NOINPUT)
19+
20+
21+
def assert_file(path, what):
22+
"""Logs an error and exits with EX_NOINPUT if the specified file doesn't exist."""
23+
if not os.path.isfile(path):
24+
log_error('Cannot find %s at %s' % (what, path))
25+
sys.exit(os.EX_NOINPUT)
26+
27+
28+
def buildroot_relative_path(path):
29+
"""Returns the absolute path to the specified buildroot-relative path."""
30+
buildroot_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..'))
31+
return os.path.join(buildroot_dir, path)
32+
33+
34+
def copy_binary(source_path, destination_path):
35+
"""Copies a binary, preserving POSIX permissions."""
36+
assert_file(source_path, 'file to copy')
37+
shutil.copy2(source_path, destination_path)
38+
39+
40+
def copy_tree(source_path, destination_path):
41+
"""Performs a recursive copy of a directory.
42+
If the destination path is present, it is deleted first."""
43+
assert_directory(source_path, 'directory to copy')
44+
shutil.rmtree(destination_path, True)
45+
shutil.copytree(source_path, destination_path)
46+
47+
48+
def create_zip(cwd, zip_filename, paths):
49+
"""Creates a zip archive in cwd, containing a set of cwd-relative files."""
50+
subprocess.check_call(['zip', '-r', zip_filename] + paths, cwd=cwd)
51+
52+
53+
def _dsymutil_path():
54+
"""Returns the path to dsymutil within Flutter's clang toolchain."""
55+
arch_subpath = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'
56+
dsymutil_path = os.path.join('flutter', 'buildtools', arch_subpath, 'clang', 'bin', 'dsymutil')
57+
return buildroot_relative_path(dsymutil_path)
58+
59+
60+
def extract_dsym(binary_path, dsym_out_path):
61+
"""Extracts a dSYM bundle from the specified Mach-O binary."""
62+
arch_dir = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'
63+
dsymutil = buildroot_relative_path(
64+
os.path.join('flutter', 'buildtools', arch_dir, 'clang', 'bin', 'dsymutil')
65+
)
66+
subprocess.check_call([dsymutil, '-o', dsym_out_path, binary_path])
67+
68+
69+
def lipo(input_binaries, output_binary):
70+
"""Uses lipo to create a fat binary from a set of input binaries."""
71+
subprocess.check_call(['lipo'] + input_binaries + ['-create', '-output', output_binary])
72+
73+
74+
def log_error(message):
75+
"""Writes the message to stderr, followed by a newline."""
76+
print(message, file=sys.stderr)
77+
78+
79+
def strip_binary(binary_path, unstripped_copy_path):
80+
"""Makes a copy of an unstripped binary, then strips symbols from the binary."""
81+
assert_file(binary_path, 'binary to strip')
82+
shutil.copyfile(binary_path, unstripped_copy_path)
83+
subprocess.check_call(['strip', '-x', '-S', binary_path])
84+
85+
86+
def write_codesign_config(output_path, paths):
87+
"""Writes an Apple codesign configuration file containing the specified paths."""
88+
with open(output_path, mode='w', encoding='utf-8') as file:
89+
file.write('\n'.join(paths) + '\n')

0 commit comments

Comments
 (0)