Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@
../../../flutter/sky/tools/create_xcframework.py
../../../flutter/sky/tools/dist_dart_pkg.py
../../../flutter/sky/tools/install_framework_headers.py
../../../flutter/sky/tools/sky_utils.py
../../../flutter/testing
../../../flutter/third_party/.clang-tidy
../../../flutter/third_party/.gitignore
Expand Down
147 changes: 48 additions & 99 deletions sky/tools/create_ios_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,14 @@

import argparse
import os
import platform
import shutil
import subprocess
import sys

from create_xcframework import create_xcframework # pylint: disable=import-error

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'
from sky_utils import ( # pylint: disable=import-error
Comment thread
cbracken marked this conversation as resolved.
Outdated
assert_directory, assert_file, buildroot_relative_path, copy_binary, copy_tree, create_zip,
extract_dsym, lipo, strip_binary, write_codesign_config
)

buildroot_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..'))


def main():
parser = argparse.ArgumentParser(
Expand All @@ -42,25 +36,25 @@ def main():

args = parser.parse_args()

dst = (args.dst if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst))
dst = (args.dst if os.path.isabs(args.dst) else buildroot_relative_path(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)
if os.path.isabs(args.arm64_out_dir) else buildroot_relative_path(args.arm64_out_dir)
)

x64_out_dir = None
if args.x64_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)
if os.path.isabs(args.x64_out_dir) else buildroot_relative_path(args.x64_out_dir)
)

simulator_x64_out_dir = None
if args.simulator_x64_out_dir:
simulator_x64_out_dir = (
args.simulator_x64_out_dir if os.path.isabs(args.simulator_x64_out_dir) else
os.path.join(buildroot_dir, args.simulator_x64_out_dir)
buildroot_relative_path(args.simulator_x64_out_dir)
)

framework = os.path.join(dst, 'Flutter.framework')
Expand All @@ -72,24 +66,15 @@ def main():
if args.simulator_arm64_out_dir:
simulator_arm64_out_dir = (
args.simulator_arm64_out_dir if os.path.isabs(args.simulator_arm64_out_dir) else
os.path.join(buildroot_dir, args.simulator_arm64_out_dir)
buildroot_relative_path(args.simulator_arm64_out_dir)
)

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

if not os.path.isdir(arm64_framework):
print('Cannot find iOS arm64 Framework at %s' % arm64_framework)
return 1

if not os.path.isdir(simulator_x64_framework):
print('Cannot find iOS x64 simulator Framework at %s' % simulator_framework)
return 1

if not os.path.isfile(DSYMUTIL):
print('Cannot find dsymutil at %s' % DSYMUTIL)
return 1

assert_directory(arm64_framework, 'iOS arm64 framework')
assert_directory(simulator_arm64_framework, 'iOS arm64 simulator framework')
assert_directory(simulator_x64_framework, 'iOS x64 simulator framework')
create_framework(
args, dst, framework, arm64_framework, simulator_framework, simulator_x64_framework,
simulator_arm64_framework
Expand All @@ -101,10 +86,18 @@ def main():
'%s_extension_safe' % simulator_x64_out_dir, '%s_extension_safe' % simulator_arm64_out_dir
)

generate_gen_snapshot(dst, x64_out_dir, arm64_out_dir)
# Copy gen_snapshot binary to destination directory.
if arm64_out_dir:
gen_snapshot = os.path.join(arm64_out_dir, 'gen_snapshot_arm64')
copy_binary(gen_snapshot, os.path.join(dst, 'gen_snapshot_arm64'))
if x64_out_dir:
gen_snapshot = os.path.join(x64_out_dir, 'gen_snapshot_x64')
copy_binary(gen_snapshot, os.path.join(dst, 'gen_snapshot_x64'))

zip_archive(dst)
return 0


def create_extension_safe_framework( # pylint: disable=too-many-arguments
args, dst, arm64_out_dir, simulator_x64_out_dir, simulator_arm64_out_dir
):
Expand All @@ -128,20 +121,17 @@ def create_extension_safe_framework( # pylint: disable=too-many-arguments
)
return 0


def create_framework( # pylint: disable=too-many-arguments
args, dst, framework, arm64_framework, simulator_framework,
simulator_x64_framework, simulator_arm64_framework
):
arm64_dylib = os.path.join(arm64_framework, 'Flutter')
simulator_x64_dylib = os.path.join(simulator_x64_framework, 'Flutter')
simulator_arm64_dylib = os.path.join(simulator_arm64_framework, 'Flutter')
if not os.path.isfile(arm64_dylib):
print('Cannot find iOS arm64 dylib at %s' % arm64_dylib)
return 1

if not os.path.isfile(simulator_x64_dylib):
print('Cannot find iOS simulator dylib at %s' % simulator_x64_dylib)
return 1
assert_file(arm64_dylib, 'iOS arm64 dylib')
assert_file(simulator_arm64_dylib, 'iOS simulator arm64 dylib')
assert_file(simulator_x64_dylib, 'iOS simulator x64 dylib')

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

# Emit the framework for physical devices.
shutil.rmtree(framework, True)
shutil.copytree(arm64_framework, framework)
copy_tree(arm64_framework, framework)
framework_binary = os.path.join(framework, 'Flutter')
process_framework(args, dst, framework_binary, framework_dsym)

# Emit the framework for simulators.
if args.simulator_arm64_out_dir is not None:
shutil.rmtree(simulator_framework, True)
shutil.copytree(simulator_arm64_framework, simulator_framework)

copy_tree(simulator_arm64_framework, simulator_framework)
simulator_framework_binary = os.path.join(simulator_framework, 'Flutter')

# Create the arm64/x64 simulator fat framework.
subprocess.check_call([
'lipo', simulator_x64_dylib, simulator_arm64_dylib, '-create', '-output',
simulator_framework_binary
])
lipo([simulator_x64_dylib, simulator_arm64_dylib], simulator_framework_binary)
process_framework(args, dst, simulator_framework_binary, simulator_dsym)
else:
simulator_framework = simulator_x64_framework
Expand All @@ -179,74 +161,41 @@ def create_framework( # pylint: disable=too-many-arguments
dsyms = [simulator_dsym, framework_dsym] if args.dsym else None
create_xcframework(location=dst, name='Flutter', frameworks=xcframeworks, dsyms=dsyms)

# Add the x64 simulator into the fat framework.
subprocess.check_call([
'lipo', arm64_dylib, simulator_x64_dylib, '-create', '-output', framework_binary
])

lipo([arm64_dylib, simulator_x64_dylib], framework_binary)
process_framework(args, dst, framework_binary, framework_dsym)
return 0


def embed_codesign_configuration(config_path, contents):
with open(config_path, 'w') as file:
file.write('\n'.join(contents) + '\n')


def zip_archive(dst):
ios_file_with_entitlements = ['gen_snapshot_arm64']
ios_file_without_entitlements = [
'Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
'Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
'extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
'extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter'
]
embed_codesign_configuration(os.path.join(dst, 'entitlements.txt'), ios_file_with_entitlements)

embed_codesign_configuration(
os.path.join(dst, 'without_entitlements.txt'), ios_file_without_entitlements
write_codesign_config(os.path.join(dst, 'entitlements.txt'), ['gen_snapshot_arm64'])

write_codesign_config(
os.path.join(dst, 'without_entitlements.txt'), [
'Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
'Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
'extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
'extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter'
]
)

subprocess.check_call([
'zip',
'-r',
'artifacts.zip',
'gen_snapshot_arm64',
'Flutter.xcframework',
'entitlements.txt',
'without_entitlements.txt',
'extension_safe/Flutter.xcframework',
],
cwd=dst)
create_zip(
dst, 'artifacts.zip', [
'gen_snapshot_arm64',
'Flutter.xcframework',
'entitlements.txt',
'without_entitlements.txt',
'extension_safe/Flutter.xcframework',
]
)


def process_framework(args, dst, framework_binary, dsym):
if dsym:
subprocess.check_call([DSYMUTIL, '-o', dsym, framework_binary])
extract_dsym(framework_binary, dsym)

if args.strip:
# copy unstripped
unstripped_out = os.path.join(dst, 'Flutter.unstripped')
shutil.copyfile(framework_binary, unstripped_out)
subprocess.check_call(['strip', '-x', '-S', framework_binary])


def generate_gen_snapshot(dst, x64_out_dir, arm64_out_dir):
if x64_out_dir:
x64_path = os.path.join(x64_out_dir, 'gen_snapshot_x64')
_generate_gen_snapshot(x64_path, os.path.join(dst, 'gen_snapshot_x64'))

if arm64_out_dir:
arm64_path = os.path.join(arm64_out_dir, 'gen_snapshot_arm64')
_generate_gen_snapshot(arm64_path, os.path.join(dst, 'gen_snapshot_arm64'))


def _generate_gen_snapshot(gen_snapshot_path, destination):
if not os.path.isfile(gen_snapshot_path):
print('Cannot find gen_snapshot at %s' % gen_snapshot_path)
sys.exit(1)

shutil.copy2(gen_snapshot_path, destination)
strip_binary(framework_binary, unstripped_copy_path=unstripped_out)


if __name__ == '__main__':
Expand Down
89 changes: 89 additions & 0 deletions sky/tools/sky_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/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 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):
"""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)


def create_zip(cwd, zip_filename, paths):
"""Creates a zip archive in cwd, containing a set of cwd-relative files."""
subprocess.check_call(['zip', '-r', 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 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:
file.write('\n'.join(paths) + '\n')