This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
Expand file tree
/
Copy pathsky_utils.py
More file actions
268 lines (209 loc) · 10.5 KB
/
sky_utils.py
File metadata and controls
268 lines (209 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/env python3
#
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import platform
import shutil
import subprocess
import sys
def assert_directory(path, what):
"""Logs an error and exits with EX_NOINPUT if the specified directory doesn't exist."""
if not os.path.isdir(path):
log_error('Cannot find %s at %s' % (what, path))
sys.exit(os.EX_NOINPUT)
def assert_file(path, what):
"""Logs an error and exits with EX_NOINPUT if the specified file doesn't exist."""
if not os.path.isfile(path):
log_error('Cannot find %s at %s' % (what, path))
sys.exit(os.EX_NOINPUT)
def assert_valid_codesign_config(
framework_dir, zip_contents, entitlements, without_entitlements, unsigned_binaries
):
"""Exits with exit code 1 if the codesign configuration contents are incorrect.
All Mach-O binaries found within zip_contents exactly must be listed in
either entitlements or without_entitlements."""
if _contains_duplicates(entitlements):
log_error('ERROR: duplicate value(s) found in entitlements.txt')
sys.exit(os.EX_DATAERR)
if _contains_duplicates(without_entitlements):
log_error('ERROR: duplicate value(s) found in without_entitlements.txt')
sys.exit(os.EX_DATAERR)
if _contains_duplicates(unsigned_binaries):
log_error('ERROR: duplicate value(s) found in unsigned_binaries.txt')
sys.exit(os.EX_DATAERR)
if _contains_duplicates(entitlements + without_entitlements + unsigned_binaries):
log_error('ERROR: value(s) found in both entitlements and without_entitlements.txt')
sys.exit(os.EX_DATAERR)
binaries = set()
for zip_content_path in zip_contents:
# If file, check if Mach-O binary.
if _is_macho_binary(os.path.join(framework_dir, zip_content_path)):
binaries.add(zip_content_path)
# If directory, check transitive closure of files for Mach-O binaries.
for root, _, files in os.walk(os.path.join(framework_dir, zip_content_path)):
for file in [os.path.join(root, f) for f in files]:
if _is_macho_binary(file):
binaries.add(os.path.relpath(file, framework_dir))
# Verify that all Mach-O binaries are listed in either entitlements,
# without_entitlements, or unsigned_binaries.
listed_binaries = set(entitlements + without_entitlements + unsigned_binaries)
if listed_binaries != binaries:
log_error(
'ERROR: binaries listed in entitlements.txt, without_entitlements.txt, and'
'unsigned_binaries.txt do not match the set of binaries in the files to be zipped'
)
log_error('Binaries found in files to be zipped:')
for file in sorted(binaries):
log_error(' ' + file)
not_listed = sorted(binaries - listed_binaries)
if not_listed:
log_error(
'Binaries NOT LISTED in entitlements.txt, without_entitlements.txt, '
'unsigned_binaries.txt:'
)
for file in not_listed:
log_error(' ' + file)
not_found = sorted(listed_binaries - binaries)
if not_found:
log_error(
'Binaries listed in entitlements.txt, without_entitlements.txt, '
'unsigned_binaries.txt but NOT FOUND:'
)
for file in not_found:
log_error(' ' + file)
sys.exit(os.EX_NOINPUT)
def _contains_duplicates(strings):
"""Returns true if the list of strings contains a duplicate value."""
return len(strings) != len(set(strings))
def _is_macho_binary(filename):
"""Returns True if the specified path is file and a Mach-O binary."""
if os.path.islink(filename) or not os.path.isfile(filename):
return False
with open(filename, 'rb') as file:
chunk = file.read(4)
return chunk in (
b'\xca\xfe\xba\xbe', # Mach-O Universal Big Endian
b'\xce\xfa\xed\xfe', # Mach-O Little Endian (32-bit)
b'\xcf\xfa\xed\xfe', # Mach-O Little Endian (64-bit)
b'\xfe\xed\xfa\xce', # Mach-O Big Endian (32-bit)
b'\xfe\xed\xfa\xcf', # Mach-O Big Endian (64-bit)
)
def buildroot_relative_path(path):
"""Returns the absolute path to the specified buildroot-relative path."""
buildroot_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..'))
return os.path.join(buildroot_dir, path)
def copy_binary(source_path, destination_path):
"""Copies a binary, preserving POSIX permissions."""
assert_file(source_path, 'file to copy')
shutil.copy2(source_path, destination_path)
def copy_tree(source_path, destination_path, symlinks=False):
"""Performs a recursive copy of a directory. If the destination path is
present, it is deleted first."""
assert_directory(source_path, 'directory to copy')
shutil.rmtree(destination_path, True)
shutil.copytree(source_path, destination_path, symlinks=symlinks)
def create_fat_macos_framework(args, dst, fat_framework, arm64_framework, x64_framework):
"""Creates a fat framework from two arm64 and x64 frameworks."""
# Clone the arm64 framework bundle as a starting point.
copy_tree(arm64_framework, fat_framework, symlinks=True)
_regenerate_symlinks(fat_framework)
framework_dylib = get_mac_framework_dylib_path(fat_framework)
lipo([get_mac_framework_dylib_path(arm64_framework),
get_mac_framework_dylib_path(x64_framework)], framework_dylib)
_set_framework_permissions(fat_framework)
framework_dsym = fat_framework + '.dSYM' if args.dsym else None
_process_macos_framework(args, dst, framework_dylib, framework_dsym)
def _regenerate_symlinks(framework_dir):
"""Regenerates the framework symlink structure.
When building on the bots, the framework is produced in one shard, uploaded
to LUCI's content-addressable storage cache (CAS), then pulled down in
another shard. When that happens, symlinks are dereferenced, resulting a
corrupted framework. This regenerates the expected symlink farm.
"""
# If the dylib is symlinked, assume symlinks are all fine and bail out.
# The shutil.rmtree calls below only work on directories, and fail on symlinks.
framework_name = get_framework_name(framework_dir)
if os.path.islink(os.path.join(framework_dir, framework_name)):
return
# Delete any existing files/directories.
os.remove(os.path.join(framework_dir, framework_name))
shutil.rmtree(os.path.join(framework_dir, 'Headers'), True)
shutil.rmtree(os.path.join(framework_dir, 'Modules'), True)
shutil.rmtree(os.path.join(framework_dir, 'Resources'), True)
current_version_path = os.path.join(framework_dir, 'Versions', 'Current')
shutil.rmtree(current_version_path, True)
# Recreate the expected framework symlinks.
os.symlink('A', current_version_path)
os.symlink(
os.path.join('Versions', 'Current', framework_name),
os.path.join(framework_dir, framework_name)
)
os.symlink(os.path.join('Versions', 'Current', 'Headers'), os.path.join(framework_dir, 'Headers'))
os.symlink(os.path.join('Versions', 'Current', 'Modules'), os.path.join(framework_dir, 'Modules'))
os.symlink(
os.path.join('Versions', 'Current', 'Resources'), os.path.join(framework_dir, 'Resources')
)
def _set_framework_permissions(framework_dir):
"""Sets framework contents to be world readable, and world executable if user-executable."""
# Make the framework readable and executable: u=rwx,go=rx.
subprocess.check_call(['chmod', '755', framework_dir])
# Add group and other readability to all files.
versions_path = os.path.join(framework_dir, 'Versions')
subprocess.check_call(['chmod', '-R', 'og+r', versions_path])
# Find all the files below the target dir with owner execute permission
find_subprocess = subprocess.Popen(['find', versions_path, '-perm', '-100', '-print0'],
stdout=subprocess.PIPE)
# Add execute permission for other and group for all files that had it for owner.
xargs_subprocess = subprocess.Popen(['xargs', '-0', 'chmod', 'og+x'],
stdin=find_subprocess.stdout)
find_subprocess.wait()
xargs_subprocess.wait()
def _process_macos_framework(args, dst, framework_dylib, dsym):
if dsym:
extract_dsym(framework_dylib, dsym)
if args.strip:
unstripped_out = os.path.join(dst, 'FlutterMacOS.unstripped')
strip_binary(framework_dylib, unstripped_out)
def create_zip(cwd, zip_filename, paths):
"""Creates a zip archive in cwd, containing a set of cwd-relative files.
In order to preserve the correct internal structure of macOS frameworks,
symlinks are preserved (-y). In order to generate reproducible builds,
owner/group and unix file timestamps are not included in the archive (-X).
"""
subprocess.check_call(['zip', '-r', '-X', '-y', zip_filename] + paths, cwd=cwd)
def _dsymutil_path():
"""Returns the path to dsymutil within Flutter's clang toolchain."""
arch_subpath = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'
dsymutil_path = os.path.join('flutter', 'buildtools', arch_subpath, 'clang', 'bin', 'dsymutil')
return buildroot_relative_path(dsymutil_path)
def get_framework_name(framework_dir):
"""Returns Foo given /path/to/Foo.framework."""
return os.path.splitext(os.path.basename(framework_dir))[0]
def get_mac_framework_dylib_path(framework_dir):
"""Returns /path/to/Foo.framework/Versions/A/Foo given /path/to/Foo.framework."""
return os.path.join(framework_dir, 'Versions', 'A', get_framework_name(framework_dir))
def extract_dsym(binary_path, dsym_out_path):
"""Extracts a dSYM bundle from the specified Mach-O binary."""
arch_dir = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'
dsymutil = buildroot_relative_path(
os.path.join('flutter', 'buildtools', arch_dir, 'clang', 'bin', 'dsymutil')
)
subprocess.check_call([dsymutil, '-o', dsym_out_path, binary_path])
def lipo(input_binaries, output_binary):
"""Uses lipo to create a fat binary from a set of input binaries."""
subprocess.check_call(['lipo'] + input_binaries + ['-create', '-output', output_binary])
def log_error(message):
"""Writes the message to stderr, followed by a newline."""
print(message, file=sys.stderr)
def strip_binary(binary_path, unstripped_copy_path):
"""Makes a copy of an unstripped binary, then strips symbols from the binary."""
assert_file(binary_path, 'binary to strip')
shutil.copyfile(binary_path, unstripped_copy_path)
subprocess.check_call(['strip', '-x', '-S', binary_path])
def write_codesign_config(output_path, paths):
"""Writes an Apple codesign configuration file containing the specified paths."""
with open(output_path, mode='w', encoding='utf-8') as file:
if paths:
file.write('\n'.join(paths) + '\n')