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}
This commit is contained in:
Byoungchan Lee
2021-07-06 18:52:21 +09:00
committed by WebRTC LUCI CQ
parent 00ca0044d4
commit c41093b0be

View File

@ -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 @@ import find_depot_tools
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 @@ def _CleanTemporary(output_dir, architectures):
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 @@ def BuildWebRTC(output_dir, target_arch, flavor, gn_target_name,
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 @@ def main():
_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 @@ def main():
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