From d18ba04282511e3740fb2ff6ff16cbd2b5ce12f2 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 20 Aug 2024 17:46:15 -0700 Subject: [PATCH] Clean up macOS framework creation scripts (flutter/engine#54658) This refactors `create_fat_macos_framework`, `_regenerate_symlinks` and `_set_framework_permissions` to be more generic and not hardcode "FlutterMacOS" as a framework name. Further, it reuses several utility functions from the iOS code in `sky_utils` to improve readability and eliminate duplication. This is refactoring prior to embedding dSYMs in FlutterMacOS.xcframework in a followup patch. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../sky/tools/create_macos_framework.py | 17 +++---- engine/src/flutter/sky/tools/sky_utils.py | 46 +++++++++++++------ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/engine/src/flutter/sky/tools/create_macos_framework.py b/engine/src/flutter/sky/tools/create_macos_framework.py index 8f9cd71f09..42db6c4d31 100755 --- a/engine/src/flutter/sky/tools/create_macos_framework.py +++ b/engine/src/flutter/sky/tools/create_macos_framework.py @@ -47,12 +47,12 @@ def main(): print('Cannot find macOS x64 Framework at %s' % x64_framework) return 1 - arm64_dylib = os.path.join(arm64_framework, 'FlutterMacOS') + arm64_dylib = sky_utils.get_mac_framework_dylib_path(arm64_framework) if not os.path.isfile(arm64_dylib): print('Cannot find macOS arm64 dylib at %s' % arm64_dylib) return 1 - x64_dylib = os.path.join(x64_framework, 'FlutterMacOS') + x64_dylib = sky_utils.get_mac_framework_dylib_path(x64_framework) if not os.path.isfile(x64_dylib): print('Cannot find macOS x64 dylib at %s' % x64_dylib) return 1 @@ -71,15 +71,16 @@ def main(): return 0 -def process_framework(dst, args, fat_framework): - fat_framework_binary = os.path.join(fat_framework, 'Versions', 'A', 'FlutterMacOS') +def process_framework(dst, args, framework_path): + framework_binary = sky_utils.get_mac_framework_dylib_path(framework_path) + if args.dsym: - dsym_out = os.path.splitext(fat_framework)[0] + '.dSYM' - sky_utils.extract_dsym(fat_framework_binary, dsym_out) + dsym_out = os.path.join(dst, 'FlutterMacOS.dSYM') + sky_utils.extract_dsym(framework_binary, dsym_out) if args.zip: dsym_dst = os.path.join(dst, 'FlutterMacOS.dSYM') sky_utils.create_zip(dsym_dst, 'FlutterMacOS.dSYM.zip', ['.']) - # Double zip to make it consistent with legacy artifacts. + # Create a zip of just the contents of the dSYM, then create a zip of that zip. # TODO(fujino): remove this once https://github.com/flutter/flutter/issues/125067 is resolved sky_utils.create_zip(dsym_dst, 'FlutterMacOS.dSYM_.zip', ['FlutterMacOS.dSYM.zip']) @@ -90,7 +91,7 @@ def process_framework(dst, args, fat_framework): if args.strip: unstripped_out = os.path.join(dst, 'FlutterMacOS.unstripped') - sky_utils.strip_binary(fat_framework_binary, unstripped_out) + sky_utils.strip_binary(framework_binary, unstripped_out) def zip_framework(dst): diff --git a/engine/src/flutter/sky/tools/sky_utils.py b/engine/src/flutter/sky/tools/sky_utils.py index f9441eb226..e30fd034f3 100644 --- a/engine/src/flutter/sky/tools/sky_utils.py +++ b/engine/src/flutter/sky/tools/sky_utils.py @@ -119,36 +119,43 @@ def copy_tree(source_path, destination_path, symlinks=False): def create_fat_macos_framework(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) - - fat_framework_binary = os.path.join(fat_framework, 'Versions', 'A', 'FlutterMacOS') - - # Create the arm64/x64 fat framework. - arm64_dylib = os.path.join(arm64_framework, 'FlutterMacOS') - x64_dylib = os.path.join(x64_framework, 'FlutterMacOS') - lipo([arm64_dylib, x64_dylib], fat_framework_binary) + lipo([get_mac_framework_dylib_path(arm64_framework), + get_mac_framework_dylib_path(x64_framework)], get_mac_framework_dylib_path(fat_framework)) _set_framework_permissions(fat_framework) def _regenerate_symlinks(framework_dir): - """Regenerates the symlinks structure. + """Regenerates the framework symlink structure. - Recipes V2 upload artifacts in CAS before integration and CAS follows symlinks. - This logic regenerates the symlinks in the expected 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 os.path.islink(os.path.join(framework_dir, 'FlutterMacOS')): + # 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 - os.remove(os.path.join(framework_dir, 'FlutterMacOS')) + + # 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', 'FlutterMacOS'), - os.path.join(framework_dir, 'FlutterMacOS') + 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')) @@ -158,6 +165,7 @@ def _regenerate_symlinks(framework_dir): 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]) @@ -190,6 +198,16 @@ def _dsymutil_path(): 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'