Add ability to build XCFramework for iOS
To build XCFramework, changed build_ios_libs.py to support
target pairs (environment, arch).
Also, changed default architecture to include the Arm64 iOS Simulator
and not the x86 iOS Simulator.
Mac Catalyst (target_environment = "catalyst") builds can also
be achieved in the same way, but at the moment, Mac Catalyst builds fail,
so I skipped them from the active arch.
Bug: webrtc:12372, webrtc:11516
Change-Id: I3f07ded81c7d0bdecc69a903b32e06c4ab63cee2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/202160
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Kári Helgason <kthelgason@webrtc.org>
Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34420}
diff --git a/tools_webrtc/ios/build_ios_libs.py b/tools_webrtc/ios/build_ios_libs.py
index 5aa40d6..c931853 100755
--- a/tools_webrtc/ios/build_ios_libs.py
+++ b/tools_webrtc/ios/build_ios_libs.py
@@ -7,7 +7,7 @@
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
-"""WebRTC iOS FAT libraries build script.
+"""WebRTC iOS XCFramework build script.
Each architecture is compiled separately before being merged together.
By default, the library is created in out_ios_libs/. (Change with -o.)
"""
@@ -29,8 +29,16 @@
SDK_OUTPUT_DIR = os.path.join(SRC_DIR, 'out_ios_libs')
SDK_FRAMEWORK_NAME = 'WebRTC.framework'
+SDK_DSYM_NAME = 'WebRTC.dSYM'
+SDK_XCFRAMEWORK_NAME = 'WebRTC.xcframework'
-DEFAULT_ARCHS = ENABLED_ARCHS = ['arm64', 'x64']
+ENABLED_ARCHS = [
+ 'device:arm64', 'simulator:arm64', 'simulator:x64',
+ 'arm64', 'x64'
+]
+DEFAULT_ARCHS = [
+ 'device:arm64', 'simulator:arm64', 'simulator:x64'
+]
IOS_DEPLOYMENT_TARGET = '12.0'
LIBVPX_BUILD_VP9 = False
@@ -114,15 +122,37 @@
if os.path.isdir(output_dir):
logging.info('Removing temporary build files.')
for arch in architectures:
- arch_lib_path = os.path.join(output_dir, arch + '_libs')
+ arch_lib_path = os.path.join(output_dir, arch)
if os.path.isdir(arch_lib_path):
shutil.rmtree(arch_lib_path)
-def BuildWebRTC(output_dir, target_arch, flavor, gn_target_name,
- ios_deployment_target, libvpx_build_vp9, use_bitcode, use_goma,
- extra_gn_args):
- output_dir = os.path.join(output_dir, target_arch + '_libs')
+def _ParseArchitecture(architectures):
+ result = dict()
+ for arch in architectures:
+ if ":" in arch:
+ target_environment, target_cpu = arch.split(":")
+ else:
+ logging.warning('The environment for build is not specified.')
+ logging.warning('It is assumed based on cpu type.')
+ logging.warning('See crbug.com/1138425 for more details.')
+ if arch == "x64":
+ target_environment = "simulator"
+ else:
+ target_environment = "device"
+ target_cpu = arch
+ archs = result.get(target_environment)
+ if archs is None:
+ result[target_environment] = {target_cpu}
+ else:
+ archs.add(target_cpu)
+
+ return result
+
+
+def BuildWebRTC(output_dir, target_environment, target_arch, flavor,
+ gn_target_name, ios_deployment_target, libvpx_build_vp9,
+ use_bitcode, use_goma, extra_gn_args):
gn_args = [
'target_os="ios"', 'ios_enable_code_signing=false',
'use_xcode_clang=true', 'is_component_build=false',
@@ -137,6 +167,8 @@
else:
raise ValueError('Unexpected flavor type: %s' % flavor)
+ gn_args.append('target_environment="%s"' % target_environment)
+
gn_args.append('target_cpu="%s"' % target_arch)
gn_args.append('ios_deployment_target="%s"' % ios_deployment_target)
@@ -182,11 +214,14 @@
_CleanArtifacts(args.output_dir)
return 0
- architectures = list(args.arch)
+ # architectures is typed as Dict[str, Set[str]],
+ # where key is for the environment (device or simulator)
+ # and value is for the cpu type.
+ architectures = _ParseArchitecture(args.arch)
gn_args = args.extra_gn_args
if args.purify:
- _CleanTemporary(args.output_dir, architectures)
+ _CleanTemporary(args.output_dir, architectures.keys())
return 0
gn_target_name = 'framework_objc'
@@ -195,78 +230,101 @@
gn_args.append('enable_stripping=true')
# Build all architectures.
- for arch in architectures:
- BuildWebRTC(args.output_dir, arch, args.build_config, gn_target_name,
- IOS_DEPLOYMENT_TARGET, LIBVPX_BUILD_VP9, args.bitcode,
- args.use_goma, gn_args)
+ framework_paths = []
+ all_lib_paths = []
+ for (environment, archs) in architectures.items():
+ framework_path = os.path.join(args.output_dir, environment)
+ framework_paths.append(framework_path)
+ lib_paths = []
+ for arch in archs:
+ lib_path = os.path.join(framework_path, arch + '_libs')
+ lib_paths.append(lib_path)
+ BuildWebRTC(lib_path, environment, arch, args.build_config,
+ gn_target_name, IOS_DEPLOYMENT_TARGET,
+ LIBVPX_BUILD_VP9, args.bitcode, args.use_goma, gn_args)
+ all_lib_paths.extend(lib_paths)
- # Create FAT archive.
- lib_paths = [
- os.path.join(args.output_dir, arch + '_libs') for arch in architectures
- ]
-
- # Combine the slices.
- dylib_path = os.path.join(SDK_FRAMEWORK_NAME, 'WebRTC')
- # Dylibs will be combined, all other files are the same across archs.
- # Use distutils instead of shutil to support merging folders.
- distutils.dir_util.copy_tree(
- os.path.join(lib_paths[0], SDK_FRAMEWORK_NAME),
- os.path.join(args.output_dir, SDK_FRAMEWORK_NAME))
- logging.info('Merging framework slices.')
- dylib_paths = [os.path.join(path, dylib_path) for path in lib_paths]
- out_dylib_path = os.path.join(args.output_dir, dylib_path)
- try:
- os.remove(out_dylib_path)
- except OSError:
- pass
- cmd = ['lipo'] + dylib_paths + ['-create', '-output', out_dylib_path]
- _RunCommand(cmd)
-
- # Merge the dSYM slices.
- lib_dsym_dir_path = os.path.join(lib_paths[0], 'WebRTC.dSYM')
- if os.path.isdir(lib_dsym_dir_path):
+ # Combine the slices.
+ dylib_path = os.path.join(SDK_FRAMEWORK_NAME, 'WebRTC')
+ # Dylibs will be combined, all other files are the same across archs.
+ # Use distutils instead of shutil to support merging folders.
distutils.dir_util.copy_tree(
- lib_dsym_dir_path, os.path.join(args.output_dir, 'WebRTC.dSYM'))
- logging.info('Merging dSYM slices.')
- dsym_path = os.path.join('WebRTC.dSYM', 'Contents', 'Resources',
- 'DWARF', 'WebRTC')
- lib_dsym_paths = [os.path.join(path, dsym_path) for path in lib_paths]
- out_dsym_path = os.path.join(args.output_dir, dsym_path)
+ os.path.join(lib_paths[0], SDK_FRAMEWORK_NAME),
+ os.path.join(framework_path, SDK_FRAMEWORK_NAME))
+ logging.info('Merging framework slices for %s.', environment)
+ dylib_paths = [os.path.join(path, dylib_path) for path in lib_paths]
+ out_dylib_path = os.path.join(framework_path, dylib_path)
try:
- os.remove(out_dsym_path)
+ os.remove(out_dylib_path)
except OSError:
pass
- cmd = ['lipo'] + lib_dsym_paths + ['-create', '-output', out_dsym_path]
+ cmd = ['lipo'] + dylib_paths + ['-create', '-output', out_dylib_path]
_RunCommand(cmd)
- # Generate the license file.
- ninja_dirs = [
- os.path.join(args.output_dir, arch + '_libs')
- for arch in architectures
- ]
- gn_target_full_name = '//sdk:' + gn_target_name
- builder = LicenseBuilder(ninja_dirs, [gn_target_full_name])
- builder.GenerateLicenseText(
- os.path.join(args.output_dir, SDK_FRAMEWORK_NAME))
+ # Merge the dSYM slices.
+ lib_dsym_dir_path = os.path.join(lib_paths[0], SDK_DSYM_NAME)
+ if os.path.isdir(lib_dsym_dir_path):
+ distutils.dir_util.copy_tree(
+ lib_dsym_dir_path, os.path.join(framework_path, SDK_DSYM_NAME))
+ logging.info('Merging dSYM slices.')
+ dsym_path = os.path.join(SDK_DSYM_NAME, 'Contents', 'Resources',
+ 'DWARF', 'WebRTC')
+ lib_dsym_paths = [
+ os.path.join(path, dsym_path) for path in lib_paths
+ ]
+ out_dsym_path = os.path.join(framework_path, dsym_path)
+ try:
+ os.remove(out_dsym_path)
+ except OSError:
+ pass
+ cmd = ['lipo'
+ ] + lib_dsym_paths + ['-create', '-output', out_dsym_path]
+ _RunCommand(cmd)
- # Modify the version number.
- # Format should be <Branch cut MXX>.<Hotfix #>.<Rev #>.
- # e.g. 55.0.14986 means branch cut 55, no hotfixes, and revision 14986.
- infoplist_path = os.path.join(args.output_dir, SDK_FRAMEWORK_NAME,
- 'Info.plist')
- cmd = [
- 'PlistBuddy', '-c', 'Print :CFBundleShortVersionString',
- infoplist_path
+ # Modify the version number.
+ # Format should be <Branch cut MXX>.<Hotfix #>.<Rev #>.
+ # e.g. 55.0.14986 means
+ # branch cut 55, no hotfixes, and revision 14986.
+ infoplist_path = os.path.join(framework_path, SDK_FRAMEWORK_NAME,
+ 'Info.plist')
+ cmd = [
+ 'PlistBuddy', '-c', 'Print :CFBundleShortVersionString',
+ infoplist_path
+ ]
+ major_minor = subprocess.check_output(cmd).strip()
+ version_number = '%s.%s' % (major_minor, args.revision)
+ logging.info('Substituting revision number: %s', version_number)
+ cmd = [
+ 'PlistBuddy', '-c', 'Set :CFBundleVersion ' + version_number,
+ infoplist_path
+ ]
+ _RunCommand(cmd)
+ _RunCommand(['plutil', '-convert', 'binary1', infoplist_path])
+
+ xcframework_dir = os.path.join(args.output_dir, SDK_XCFRAMEWORK_NAME)
+ if os.path.isdir(xcframework_dir):
+ shutil.rmtree(xcframework_dir)
+
+ logging.info('Creating xcframework.')
+ cmd = ['xcodebuild', '-create-xcframework', '-output', xcframework_dir]
+
+ # Apparently, xcodebuild needs absolute paths for input arguments
+ for framework_path in framework_paths:
+ cmd += [
+ '-framework',
+ os.path.abspath(os.path.join(framework_path, SDK_FRAMEWORK_NAME)),
+ '-debug-symbols',
+ os.path.abspath(os.path.join(framework_path, SDK_DSYM_NAME))
]
- major_minor = subprocess.check_output(cmd).strip()
- version_number = '%s.%s' % (major_minor, args.revision)
- logging.info('Substituting revision number: %s', version_number)
- cmd = [
- 'PlistBuddy', '-c', 'Set :CFBundleVersion ' + version_number,
- infoplist_path
- ]
- _RunCommand(cmd)
- _RunCommand(['plutil', '-convert', 'binary1', infoplist_path])
+
+ _RunCommand(cmd)
+
+ # Generate the license file.
+ logging.info('Generate license file.')
+ gn_target_full_name = '//sdk:' + gn_target_name
+ builder = LicenseBuilder(all_lib_paths, [gn_target_full_name])
+ builder.GenerateLicenseText(
+ os.path.join(args.output_dir, SDK_XCFRAMEWORK_NAME))
logging.info('Done.')
return 0