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:
committed by
WebRTC LUCI CQ
parent
00ca0044d4
commit
c41093b0be
@ -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
|
||||
|
||||
Reference in New Issue
Block a user