|  | #!/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 argparse | 
|  | import platform | 
|  | import subprocess | 
|  | import shutil | 
|  | import sys | 
|  | import os | 
|  |  | 
|  | from create_xcframework import create_xcframework  # pylint: disable=import-error | 
|  |  | 
|  | buildroot_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..')) | 
|  |  | 
|  | ARCH_SUBPATH = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64' | 
|  | DSYMUTIL = os.path.join( | 
|  | os.path.dirname(__file__), '..', '..', 'buildtools', ARCH_SUBPATH, 'clang', 'bin', 'dsymutil' | 
|  | ) | 
|  |  | 
|  | out_dir = os.path.join(buildroot_dir, 'out') | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser( | 
|  | description='Creates FlutterMacOS.framework and FlutterMacOS.xcframework for macOS' | 
|  | ) | 
|  |  | 
|  | parser.add_argument('--dst', type=str, required=True) | 
|  | parser.add_argument('--arm64-out-dir', type=str, required=True) | 
|  | parser.add_argument('--x64-out-dir', type=str, required=True) | 
|  | parser.add_argument('--strip', action='store_true', default=False) | 
|  | parser.add_argument('--dsym', action='store_true', default=False) | 
|  | # TODO(godofredoc): Remove after recipes v2 have landed. | 
|  | parser.add_argument('--zip', action='store_true', default=False) | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | dst = (args.dst if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst)) | 
|  | arm64_out_dir = ( | 
|  | args.arm64_out_dir | 
|  | if os.path.isabs(args.arm64_out_dir) else os.path.join(buildroot_dir, args.arm64_out_dir) | 
|  | ) | 
|  | x64_out_dir = ( | 
|  | args.x64_out_dir | 
|  | if os.path.isabs(args.x64_out_dir) else os.path.join(buildroot_dir, args.x64_out_dir) | 
|  | ) | 
|  |  | 
|  | fat_framework = os.path.join(dst, 'FlutterMacOS.framework') | 
|  | arm64_framework = os.path.join(arm64_out_dir, 'FlutterMacOS.framework') | 
|  | x64_framework = os.path.join(x64_out_dir, 'FlutterMacOS.framework') | 
|  |  | 
|  | arm64_dylib = os.path.join(arm64_framework, 'FlutterMacOS') | 
|  | x64_dylib = os.path.join(x64_framework, 'FlutterMacOS') | 
|  |  | 
|  | if not os.path.isdir(arm64_framework): | 
|  | print('Cannot find macOS arm64 Framework at %s' % arm64_framework) | 
|  | return 1 | 
|  |  | 
|  | if not os.path.isdir(x64_framework): | 
|  | print('Cannot find macOS x64 Framework at %s' % x64_framework) | 
|  | return 1 | 
|  |  | 
|  | if not os.path.isfile(arm64_dylib): | 
|  | print('Cannot find macOS arm64 dylib at %s' % arm64_dylib) | 
|  | return 1 | 
|  |  | 
|  | if not os.path.isfile(x64_dylib): | 
|  | print('Cannot find macOS x64 dylib at %s' % x64_dylib) | 
|  | return 1 | 
|  |  | 
|  | if not os.path.isfile(DSYMUTIL): | 
|  | print('Cannot find dsymutil at %s' % DSYMUTIL) | 
|  | return 1 | 
|  |  | 
|  | shutil.rmtree(fat_framework, True) | 
|  | shutil.copytree(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. | 
|  | subprocess.check_call([ | 
|  | 'lipo', arm64_dylib, x64_dylib, '-create', '-output', fat_framework_binary | 
|  | ]) | 
|  |  | 
|  | # Add group and other readability to all files. | 
|  | versions_path = os.path.join(fat_framework, '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() | 
|  |  | 
|  | process_framework(dst, args, fat_framework, fat_framework_binary) | 
|  |  | 
|  | # Create XCFramework from the arm64 and x64 fat framework. | 
|  | xcframeworks = [fat_framework] | 
|  | create_xcframework(location=dst, name='FlutterMacOS', frameworks=xcframeworks) | 
|  |  | 
|  | zip_framework(dst, args) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | def regenerate_symlinks(fat_framework): | 
|  | """Regenerates the symlinks structure. | 
|  |  | 
|  | Recipes V2 upload artifacts in CAS before integration and CAS follows symlinks. | 
|  | This logic regenerates the symlinks in the expected structure. | 
|  | """ | 
|  | if os.path.islink(os.path.join(fat_framework, 'FlutterMacOS')): | 
|  | return | 
|  | os.remove(os.path.join(fat_framework, 'FlutterMacOS')) | 
|  | shutil.rmtree(os.path.join(fat_framework, 'Headers'), True) | 
|  | shutil.rmtree(os.path.join(fat_framework, 'Modules'), True) | 
|  | shutil.rmtree(os.path.join(fat_framework, 'Resources'), True) | 
|  | current_version_path = os.path.join(fat_framework, 'Versions', 'Current') | 
|  | shutil.rmtree(current_version_path, True) | 
|  | os.symlink('A', current_version_path) | 
|  | os.symlink( | 
|  | os.path.join('Versions', 'Current', 'FlutterMacOS'), | 
|  | os.path.join(fat_framework, 'FlutterMacOS') | 
|  | ) | 
|  | os.symlink(os.path.join('Versions', 'Current', 'Headers'), os.path.join(fat_framework, 'Headers')) | 
|  | os.symlink(os.path.join('Versions', 'Current', 'Modules'), os.path.join(fat_framework, 'Modules')) | 
|  | os.symlink( | 
|  | os.path.join('Versions', 'Current', 'Resources'), os.path.join(fat_framework, 'Resources') | 
|  | ) | 
|  |  | 
|  |  | 
|  | def embed_codesign_configuration(config_path, content): | 
|  | with open(config_path, 'w') as file: | 
|  | file.write(content) | 
|  |  | 
|  |  | 
|  | def process_framework(dst, args, fat_framework, fat_framework_binary): | 
|  | if args.dsym: | 
|  | dsym_out = os.path.splitext(fat_framework)[0] + '.dSYM' | 
|  | subprocess.check_call([DSYMUTIL, '-o', dsym_out, fat_framework_binary]) | 
|  | if args.zip: | 
|  | dsym_dst = os.path.join(dst, 'FlutterMacOS.dSYM') | 
|  | subprocess.check_call(['zip', '-r', '-y', 'FlutterMacOS.dSYM.zip', '.'], cwd=dsym_dst) | 
|  | # Double zip to make it consistent with legacy artifacts. | 
|  | # TODO(fujino): remove this once https://github.com/flutter/flutter/issues/125067 is resolved | 
|  | subprocess.check_call([ | 
|  | 'zip', | 
|  | '-y', | 
|  | 'FlutterMacOS.dSYM_.zip', | 
|  | 'FlutterMacOS.dSYM.zip', | 
|  | ], | 
|  | cwd=dsym_dst) | 
|  | # Use doubled zipped file. | 
|  | dsym_final_src_path = os.path.join(dsym_dst, 'FlutterMacOS.dSYM_.zip') | 
|  | dsym_final_dst_path = os.path.join(dst, 'FlutterMacOS.dSYM.zip') | 
|  | shutil.move(dsym_final_src_path, dsym_final_dst_path) | 
|  |  | 
|  | if args.strip: | 
|  | # copy unstripped | 
|  | unstripped_out = os.path.join(dst, 'FlutterMacOS.unstripped') | 
|  | shutil.copyfile(fat_framework_binary, unstripped_out) | 
|  |  | 
|  | subprocess.check_call(['strip', '-x', '-S', fat_framework_binary]) | 
|  |  | 
|  |  | 
|  | def zip_framework(dst, args): | 
|  | # Zip FlutterMacOS.framework. | 
|  | if args.zip: | 
|  | filepath_with_entitlements = '' | 
|  |  | 
|  | framework_dst = os.path.join(dst, 'FlutterMacOS.framework') | 
|  | # TODO(xilaizhang): Remove the zip file from the path when outer zip is removed. | 
|  | filepath_without_entitlements = 'FlutterMacOS.framework.zip/Versions/A/FlutterMacOS' | 
|  |  | 
|  | embed_codesign_configuration( | 
|  | os.path.join(framework_dst, 'entitlements.txt'), filepath_with_entitlements | 
|  | ) | 
|  |  | 
|  | embed_codesign_configuration( | 
|  | os.path.join(framework_dst, 'without_entitlements.txt'), filepath_without_entitlements | 
|  | ) | 
|  | subprocess.check_call([ | 
|  | 'zip', | 
|  | '-r', | 
|  | '-y', | 
|  | 'FlutterMacOS.framework.zip', | 
|  | '.', | 
|  | ], | 
|  | cwd=framework_dst) | 
|  | # Double zip to make it consistent with legacy artifacts. | 
|  | # TODO(fujino): remove this once https://github.com/flutter/flutter/issues/125067 is resolved | 
|  | subprocess.check_call( | 
|  | [ | 
|  | 'zip', | 
|  | '-y', | 
|  | 'FlutterMacOS.framework_.zip', | 
|  | 'FlutterMacOS.framework.zip', | 
|  | # TODO(xilaizhang): Move these files to inner zip before removing the outer zip. | 
|  | 'entitlements.txt', | 
|  | 'without_entitlements.txt', | 
|  | ], | 
|  | cwd=framework_dst | 
|  | ) | 
|  | # Use doubled zipped file. | 
|  | final_src_path = os.path.join(framework_dst, 'FlutterMacOS.framework_.zip') | 
|  | final_dst_path = os.path.join(dst, 'FlutterMacOS.framework.zip') | 
|  | shutil.move(final_src_path, final_dst_path) | 
|  |  | 
|  | zip_xcframework_archive(dst) | 
|  |  | 
|  |  | 
|  | def zip_xcframework_archive(dst): | 
|  | filepath_with_entitlements = '' | 
|  | filepath_without_entitlements = ( | 
|  | 'FlutterMacOS.xcframework/macos-arm64_x86_64/' | 
|  | 'FlutterMacOS.framework/Versions/A/FlutterMacOS' | 
|  | ) | 
|  | embed_codesign_configuration(os.path.join(dst, 'entitlements.txt'), filepath_with_entitlements) | 
|  |  | 
|  | embed_codesign_configuration( | 
|  | os.path.join(dst, 'without_entitlements.txt'), filepath_without_entitlements | 
|  | ) | 
|  |  | 
|  | subprocess.check_call([ | 
|  | 'zip', | 
|  | '-r', | 
|  | '-y', | 
|  | 'framework.zip', | 
|  | 'FlutterMacOS.xcframework', | 
|  | 'entitlements.txt', | 
|  | 'without_entitlements.txt', | 
|  | ], | 
|  | cwd=dst) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |