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

Commit 7949ab3

Browse files
authored
macOS: Refactor create_macos_framework.py (#54557)
This is a refactoring with no semantic changes. This refactors the macOS framework creation code to be more readable, and extracts it to `sky_utils.py`. While I was pulling this out, also generalised the code to not hardcode `FlutterMacOS.framework` in case we one day manage to generate the iOS and macOS frameworks with the same name. This is a reland of #54546 (reverted in #54549), the original was reverted in order to revert #54543 (reverted in #54550), which was reverted because it failed to preserve symlinks while zipping the macOS framework. That patch has been relanded with a fix in #54555. This patch has been rebased to tip-of-tree for attempt two. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent f550375 commit 7949ab3

File tree

2 files changed

+87
-72
lines changed

2 files changed

+87
-72
lines changed

sky/tools/create_macos_framework.py

Lines changed: 16 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
# found in the LICENSE file.
66

77
import argparse
8-
import subprocess
98
import shutil
109
import sys
1110
import os
@@ -38,57 +37,32 @@ def main():
3837
if os.path.isabs(args.x64_out_dir) else sky_utils.buildroot_relative_path(args.x64_out_dir)
3938
)
4039

41-
fat_framework_bundle = os.path.join(dst, 'FlutterMacOS.framework')
4240
arm64_framework = os.path.join(arm64_out_dir, 'FlutterMacOS.framework')
43-
x64_framework = os.path.join(x64_out_dir, 'FlutterMacOS.framework')
44-
45-
arm64_dylib = os.path.join(arm64_framework, 'FlutterMacOS')
46-
x64_dylib = os.path.join(x64_framework, 'FlutterMacOS')
47-
4841
if not os.path.isdir(arm64_framework):
4942
print('Cannot find macOS arm64 Framework at %s' % arm64_framework)
5043
return 1
5144

45+
x64_framework = os.path.join(x64_out_dir, 'FlutterMacOS.framework')
5246
if not os.path.isdir(x64_framework):
5347
print('Cannot find macOS x64 Framework at %s' % x64_framework)
5448
return 1
5549

50+
arm64_dylib = sky_utils.get_framework_dylib_path(arm64_framework)
5651
if not os.path.isfile(arm64_dylib):
5752
print('Cannot find macOS arm64 dylib at %s' % arm64_dylib)
5853
return 1
5954

55+
x64_dylib = sky_utils.get_framework_dylib_path(x64_framework)
6056
if not os.path.isfile(x64_dylib):
6157
print('Cannot find macOS x64 dylib at %s' % x64_dylib)
6258
return 1
6359

64-
sky_utils.copy_tree(arm64_framework, fat_framework_bundle, symlinks=True)
65-
66-
regenerate_symlinks(fat_framework_bundle)
67-
68-
fat_framework_binary = os.path.join(fat_framework_bundle, 'Versions', 'A', 'FlutterMacOS')
69-
70-
# Create the arm64/x64 fat framework.
71-
sky_utils.lipo([arm64_dylib, x64_dylib], fat_framework_binary)
72-
73-
# Make the framework readable and executable: u=rwx,go=rx.
74-
subprocess.check_call(['chmod', '755', fat_framework_bundle])
75-
76-
# Add group and other readability to all files.
77-
versions_path = os.path.join(fat_framework_bundle, 'Versions')
78-
subprocess.check_call(['chmod', '-R', 'og+r', versions_path])
79-
# Find all the files below the target dir with owner execute permission
80-
find_subprocess = subprocess.Popen(['find', versions_path, '-perm', '-100', '-print0'],
81-
stdout=subprocess.PIPE)
82-
# Add execute permission for other and group for all files that had it for owner.
83-
xargs_subprocess = subprocess.Popen(['xargs', '-0', 'chmod', 'og+x'],
84-
stdin=find_subprocess.stdout)
85-
find_subprocess.wait()
86-
xargs_subprocess.wait()
87-
88-
process_framework(dst, args, fat_framework_bundle, fat_framework_binary)
60+
fat_framework = os.path.join(dst, 'FlutterMacOS.framework')
61+
sky_utils.create_fat_macos_framework(fat_framework, arm64_framework, x64_framework)
62+
process_framework(dst, args, fat_framework)
8963

9064
# Create XCFramework from the arm64 and x64 fat framework.
91-
xcframeworks = [fat_framework_bundle]
65+
xcframeworks = [fat_framework]
9266
create_xcframework(location=dst, name='FlutterMacOS', frameworks=xcframeworks)
9367

9468
if args.zip:
@@ -97,56 +71,26 @@ def main():
9771
return 0
9872

9973

100-
def regenerate_symlinks(fat_framework_bundle):
101-
"""Regenerates the symlinks structure.
102-
103-
Recipes V2 upload artifacts in CAS before integration and CAS follows symlinks.
104-
This logic regenerates the symlinks in the expected structure.
105-
"""
106-
if os.path.islink(os.path.join(fat_framework_bundle, 'FlutterMacOS')):
107-
return
108-
os.remove(os.path.join(fat_framework_bundle, 'FlutterMacOS'))
109-
shutil.rmtree(os.path.join(fat_framework_bundle, 'Headers'), True)
110-
shutil.rmtree(os.path.join(fat_framework_bundle, 'Modules'), True)
111-
shutil.rmtree(os.path.join(fat_framework_bundle, 'Resources'), True)
112-
current_version_path = os.path.join(fat_framework_bundle, 'Versions', 'Current')
113-
shutil.rmtree(current_version_path, True)
114-
os.symlink('A', current_version_path)
115-
os.symlink(
116-
os.path.join('Versions', 'Current', 'FlutterMacOS'),
117-
os.path.join(fat_framework_bundle, 'FlutterMacOS')
118-
)
119-
os.symlink(
120-
os.path.join('Versions', 'Current', 'Headers'), os.path.join(fat_framework_bundle, 'Headers')
121-
)
122-
os.symlink(
123-
os.path.join('Versions', 'Current', 'Modules'), os.path.join(fat_framework_bundle, 'Modules')
124-
)
125-
os.symlink(
126-
os.path.join('Versions', 'Current', 'Resources'),
127-
os.path.join(fat_framework_bundle, 'Resources')
128-
)
129-
74+
def process_framework(dst, args, framework_path):
75+
framework_binary = sky_utils.get_framework_dylib_path(framework_path)
13076

131-
def process_framework(dst, args, fat_framework_bundle, fat_framework_binary):
13277
if args.dsym:
133-
dsym_out = os.path.splitext(fat_framework_bundle)[0] + '.dSYM'
134-
sky_utils.extract_dsym(fat_framework_binary, dsym_out)
78+
dsym_out = os.path.join(dst, 'FlutterMacOS.dSYM')
79+
sky_utils.extract_dsym(framework_binary, dsym_out)
13580
if args.zip:
136-
dsym_dst = os.path.join(dst, 'FlutterMacOS.dSYM')
137-
sky_utils.create_zip(dsym_dst, 'FlutterMacOS.dSYM.zip', ['.'])
138-
# Double zip to make it consistent with legacy artifacts.
81+
# Create a zip of just the contents of the dSYM, then create a zip of that zip.
13982
# TODO(fujino): remove this once https://github.com/flutter/flutter/issues/125067 is resolved
140-
sky_utils.create_zip(dsym_dst, 'FlutterMacOS.dSYM_.zip', ['FlutterMacOS.dSYM.zip'])
83+
sky_utils.create_zip(dsym_out, 'FlutterMacOS.dSYM.zip', ['.'])
84+
sky_utils.create_zip(dsym_out, 'FlutterMacOS.dSYM_.zip', ['FlutterMacOS.dSYM.zip'])
14185

14286
# Overwrite the FlutterMacOS.dSYM.zip with the double-zipped archive.
143-
dsym_final_src_path = os.path.join(dsym_dst, 'FlutterMacOS.dSYM_.zip')
87+
dsym_final_src_path = os.path.join(dsym_out, 'FlutterMacOS.dSYM_.zip')
14488
dsym_final_dst_path = os.path.join(dst, 'FlutterMacOS.dSYM.zip')
14589
shutil.move(dsym_final_src_path, dsym_final_dst_path)
14690

14791
if args.strip:
14892
unstripped_out = os.path.join(dst, 'FlutterMacOS.unstripped')
149-
sky_utils.strip_binary(fat_framework_binary, unstripped_out)
93+
sky_utils.strip_binary(framework_binary, unstripped_out)
15094

15195

15296
def zip_framework(dst):

sky/tools/sky_utils.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,84 @@ def create_zip(cwd, zip_filename, paths):
5454
subprocess.check_call(['zip', '-r', '-y', zip_filename] + paths, cwd=cwd)
5555

5656

57+
def create_fat_macos_framework(fat_framework, arm64_framework, x64_framework):
58+
"""Creates a fat framework from two arm64 and x64 frameworks."""
59+
# Clone the arm64 framework bundle as a starting point.
60+
copy_tree(arm64_framework, fat_framework, symlinks=True)
61+
_regenerate_symlinks(fat_framework)
62+
lipo([get_framework_dylib_path(arm64_framework),
63+
get_framework_dylib_path(x64_framework)], get_framework_dylib_path(fat_framework))
64+
_set_framework_permissions(fat_framework)
65+
66+
67+
def _regenerate_symlinks(framework_path):
68+
"""Regenerates the framework symlink structure.
69+
70+
When building on the bots, the framework is produced in one shard, uploaded
71+
to LUCI's content-addressable storage cache (CAS), then pulled down in
72+
another shard. When that happens, symlinks are dereferenced, resulting a
73+
corrupted framework. This regenerates the expected symlink farm.
74+
"""
75+
# If the dylib is symlinked, assume symlinks are all fine and bail out.
76+
# The shutil.rmtree calls below only work on directories, and fail on symlinks.
77+
framework_name = get_framework_name(framework_path)
78+
framework_binary = get_framework_dylib_path(framework_path)
79+
if os.path.islink(os.path.join(framework_path, framework_name)):
80+
return
81+
82+
# Delete any existing files/directories.
83+
os.remove(framework_binary)
84+
shutil.rmtree(os.path.join(framework_path, 'Headers'), True)
85+
shutil.rmtree(os.path.join(framework_path, 'Modules'), True)
86+
shutil.rmtree(os.path.join(framework_path, 'Resources'), True)
87+
current_version_path = os.path.join(framework_path, 'Versions', 'Current')
88+
shutil.rmtree(current_version_path, True)
89+
90+
# Recreate the expected framework symlinks.
91+
os.symlink('A', current_version_path)
92+
os.symlink(os.path.join(current_version_path, framework_name), framework_binary)
93+
os.symlink(os.path.join(current_version_path, 'Headers'), os.path.join(framework_path, 'Headers'))
94+
os.symlink(os.path.join(current_version_path, 'Modules'), os.path.join(framework_path, 'Modules'))
95+
os.symlink(
96+
os.path.join(current_version_path, 'Resources'), os.path.join(framework_path, 'Resources')
97+
)
98+
99+
100+
def _set_framework_permissions(framework_dir):
101+
"""Sets framework contents to be world readable, and world executable if user-executable."""
102+
# Make the framework readable and executable: u=rwx,go=rx.
103+
subprocess.check_call(['chmod', '755', framework_dir])
104+
105+
# Add group and other readability to all files.
106+
subprocess.check_call(['chmod', '-R', 'og+r', framework_dir])
107+
108+
# Find all the files below the target dir with owner execute permission and
109+
# set og+x where it had the execute permission set for the owner.
110+
find_subprocess = subprocess.Popen(['find', framework_dir, '-perm', '-100', '-print0'],
111+
stdout=subprocess.PIPE)
112+
xargs_subprocess = subprocess.Popen(['xargs', '-0', 'chmod', 'og+x'],
113+
stdin=find_subprocess.stdout)
114+
find_subprocess.wait()
115+
xargs_subprocess.wait()
116+
117+
57118
def _dsymutil_path():
58119
"""Returns the path to dsymutil within Flutter's clang toolchain."""
59120
arch_subpath = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'
60121
dsymutil_path = os.path.join('flutter', 'buildtools', arch_subpath, 'clang', 'bin', 'dsymutil')
61122
return buildroot_relative_path(dsymutil_path)
62123

63124

125+
def get_framework_name(framework_dir):
126+
"""Returns Foo given /path/to/Foo.framework."""
127+
return os.path.splitext(os.path.basename(framework_dir))[0]
128+
129+
130+
def get_framework_dylib_path(framework_dir):
131+
"""Returns /path/to/Foo.framework/Versions/A/Foo given /path/to/Foo.framework."""
132+
return os.path.join(framework_dir, 'Versions', 'A', get_framework_name(framework_dir))
133+
134+
64135
def extract_dsym(binary_path, dsym_out_path):
65136
"""Extracts a dSYM bundle from the specified Mach-O binary."""
66137
arch_dir = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64'

0 commit comments

Comments
 (0)