in scripts/gha/build_ios_tvos.py [0:0]
def build_xcframeworks(frameworks_path, xcframeworks_path, template_info_plist,
targets):
"""Build xcframeworks combining libraries for different operating systems.
Combine frameworks for different operating systems (ios, tvos), architectures
(arm64, armv7, x86_64 etc) per platform variant (device, simulator).
This makes it super convenient for developers to use a single deliverable in
XCode and develop for multiple platforms/operating systems in one project.
Args:
frameworks_path (str): Absolute path to path containing frameworks.
xcframeworks_path (str): Absolute path to create xcframeworks in.
template_info_plist (str): Absolute path to a template Info.plist that
will be copied over to each xcframework and provides metadata to XCode.
targets iterable(str): List of firebase target libraries.
(eg: [firebase_auth, firebase_remote_config])
Eg: <build_dir>/frameworks <------------- <frameworks_path>
- ios <---------- <frameworks_os_path>
- device-arm64
- firebase.framework
- firebase_admob.framework
...
- simulator-i386
...
- tvos
- device-arm64
- firebase.framework
- firebase_admob.framework
...
- simulator-x86_64
...
Output: <build_dir>/xcframeworks <----------- <xcframeworks_path>
- firebase.xcframework
- Info.plist <----------- <Info.plist file>
- ios-arm64_armv7 <-- <all_libraries_for_ios_device>
- firebase.framework
- firebase <---- <library>
- Headers <---- <all_include_headers>
- ios-arm64_i386_x86_64-simulator <--- <for_ios_simulator>
- firebase.framework
- firebase
- Headers
- tvos-arm64 <- <all_libraries_for_tvos_device>
- firebase.framework
- firebase
- Headers
...
...
- firebase_auth.xcframework <-- <firebase_auth target>
- Info.plist
- ios-arm64_armv7
- firebase_auth.framework
- firebase_auth
- ios-arm64_i386_x86_64-simulator
- firebase_auth.framework
- firebase_auth
- tvos-arm64
- firebase.framework
- firebase
- Headers
...
...
...
"""
for apple_os in os.listdir(frameworks_path):
framework_os_path = os.path.join(frameworks_path, apple_os)
platform_variant_architecture_dirs = os.listdir(framework_os_path)
# Extract list of all built platform-architecture combinations into a map.
# Map looks like this,
# {'device': ['arm64', 'armv7'], 'simulator': ['x86_64']}
platform_variant_arch_map = defaultdict(list)
for variant_architecture in platform_variant_architecture_dirs:
# Skip directories not of the format platform-arch (eg: universal)
if not '-' in variant_architecture:
continue
platform_variant, architecture = variant_architecture.split('-')
platform_variant_arch_map[platform_variant].append(architecture)
reference_dir_path = os.path.join(framework_os_path,
platform_variant_architecture_dirs[0])
logging.debug('Using {0} as reference path for scanning '
'targets'.format(reference_dir_path))
# Filter only .framework directories and make sure the framework is
# in list of supported targets.
target_frameworks = [x for x in os.listdir(reference_dir_path)
if x.endswith('.framework') and
x.split('.')[0] in targets]
logging.debug('Targets found: {0}'.format(' '.join(target_frameworks)))
# For each target, we collect all libraries for a specific platform variants
# (device or simulator) and os (ios or tvos).
for target_framework in target_frameworks:
target_libraries = []
# Eg: split firebase_auth.framework -> firebase_auth, .framework
target, _ = os.path.splitext(target_framework)
xcframework_target_map = {}
for platform_variant in platform_variant_arch_map:
architectures = platform_variant_arch_map[platform_variant]
xcframework_libraries = []
for architecture in architectures:
# <build_dir>/<apple_os>/frameworks/<platform-arch>/
# <target>.framework/target
library_path = os.path.join(framework_os_path,
'{0}-{1}'.format(platform_variant,
architecture), target_framework, target)
xcframework_libraries.append(library_path)
xcframework_key_parts = [apple_os]
xcframework_key_parts.append('_'.join(sorted(architectures)))
if platform_variant != 'device':
# device is treated as default platform variant and we do not add any
# suffix at the end. For all other variants, add them as suffix.
xcframework_key_parts.append(platform_variant)
# Eg: ios-arm64_armv7, tvos-x86_64-simulator
xcframework_key = '-'.join(xcframework_key_parts)
# <build_dir>/xcframeworks/<target>.xcframework/<os>-<list_of_archs>/
# <target>.framework
library_output_dir = os.path.join(xcframeworks_path,
'{0}.xcframework'.format(target),
xcframework_key,
'{0}.framework'.format(target))
logging.debug('Ensuring all directories exist: ' + library_output_dir)
os.makedirs(library_output_dir)
cmd = ['lipo', '-create']
cmd.extend(xcframework_libraries)
cmd.append('-output')
cmd.append(os.path.join(library_output_dir, target))
logging.info('Creating xcframework at' +
os.path.join(library_output_dir, target))
utils.run_command(cmd)
# <build_dir>/xcframeworks/<target>.xcframework
target_xcframeworks_path = os.path.join(xcframeworks_path,
'{0}.xcframework'.format(target))
# Create Info.plist for xcframework
dest_path = os.path.join(target_xcframeworks_path, 'Info.plist')
logging.info('Copying template {0}'.format(template_info_plist))
shutil.copy(template_info_plist, dest_path)
contents = None
# Replace token LIBRARY_PATH with current target framework.
with open(dest_path, 'r') as info_plist_file:
contents = info_plist_file.read()
if contents:
logging.debug('Updating LIBRARY_PATH with '
'{0}.framework'.format(target))
contents = contents.replace('LIBRARY_PATH',
'{0}.framework'.format(target))
with open(dest_path, 'w') as info_plist_file:
info_plist_file.write(contents)
# Copy Headers for firebase.xcframework from firebase.framework.
# Using a random platform specific firebase.framework.
firebase_framework_headers_path = os.path.join(reference_dir_path,
'firebase.framework',
'Headers')
firebase_xcframework_path = os.path.join(xcframeworks_path,
'firebase.xcframework')
for xcframework_key in os.listdir(firebase_xcframework_path):
if os.path.isfile(os.path.join(firebase_xcframework_path,
xcframework_key)):
continue
dest_headers_path = os.path.join(firebase_xcframework_path,
xcframework_key,
'firebase.framework', 'Headers')
if os.path.exists(dest_headers_path):
continue
logging.info('Copying {0} to {1}'.format(firebase_framework_headers_path,
dest_headers_path))
shutil.copytree(firebase_framework_headers_path,
dest_headers_path)