1#!/usr/bin/env python 2 3# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. 4# 5# Use of this source code is governed by a BSD-style license 6# that can be found in the LICENSE file in the root of the source 7# tree. An additional intellectual property rights grant can be found 8# in the file PATENTS. All contributing project authors may 9# be found in the AUTHORS file in the root of the source tree. 10"""WebRTC iOS FAT libraries build script. 11Each architecture is compiled separately before being merged together. 12By default, the library is created in out_ios_libs/. (Change with -o.) 13""" 14 15import argparse 16import distutils.dir_util 17import logging 18import os 19import shutil 20import subprocess 21import sys 22 23os.environ['PATH'] = '/usr/libexec' + os.pathsep + os.environ['PATH'] 24 25SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 26SRC_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, '..', '..')) 27sys.path.append(os.path.join(SRC_DIR, 'build')) 28import find_depot_tools 29 30SDK_OUTPUT_DIR = os.path.join(SRC_DIR, 'out_ios_libs') 31SDK_FRAMEWORK_NAME = 'WebRTC.framework' 32 33DEFAULT_ARCHS = ENABLED_ARCHS = ['arm64', 'arm', 'x64', 'x86'] 34IOS_DEPLOYMENT_TARGET = '10.0' 35LIBVPX_BUILD_VP9 = False 36 37sys.path.append(os.path.join(SCRIPT_DIR, '..', 'libs')) 38from generate_licenses import LicenseBuilder 39 40 41def _ParseArgs(): 42 parser = argparse.ArgumentParser(description=__doc__) 43 parser.add_argument('--build_config', 44 default='release', 45 choices=['debug', 'release'], 46 help='The build config. Can be "debug" or "release". ' 47 'Defaults to "release".') 48 parser.add_argument( 49 '--arch', 50 nargs='+', 51 default=DEFAULT_ARCHS, 52 choices=ENABLED_ARCHS, 53 help='Architectures to build. Defaults to %(default)s.') 54 parser.add_argument( 55 '-c', 56 '--clean', 57 action='store_true', 58 default=False, 59 help='Removes the previously generated build output, if any.') 60 parser.add_argument( 61 '-p', 62 '--purify', 63 action='store_true', 64 default=False, 65 help='Purifies the previously generated build output by ' 66 'removing the temporary results used when (re)building.') 67 parser.add_argument( 68 '-o', 69 '--output-dir', 70 default=SDK_OUTPUT_DIR, 71 help='Specifies a directory to output the build artifacts to. ' 72 'If specified together with -c, deletes the dir.') 73 parser.add_argument( 74 '-r', 75 '--revision', 76 type=int, 77 default=0, 78 help='Specifies a revision number to embed if building the framework.') 79 parser.add_argument('-e', 80 '--bitcode', 81 action='store_true', 82 default=False, 83 help='Compile with bitcode.') 84 parser.add_argument('--verbose', 85 action='store_true', 86 default=False, 87 help='Debug logging.') 88 parser.add_argument('--use-goma', 89 action='store_true', 90 default=False, 91 help='Use goma to build.') 92 parser.add_argument( 93 '--extra-gn-args', 94 default=[], 95 nargs='*', 96 help='Additional GN args to be used during Ninja generation.') 97 98 return parser.parse_args() 99 100 101def _RunCommand(cmd): 102 logging.debug('Running: %r', cmd) 103 subprocess.check_call(cmd, cwd=SRC_DIR) 104 105 106def _CleanArtifacts(output_dir): 107 if os.path.isdir(output_dir): 108 logging.info('Deleting %s', output_dir) 109 shutil.rmtree(output_dir) 110 111 112def _CleanTemporary(output_dir, architectures): 113 if os.path.isdir(output_dir): 114 logging.info('Removing temporary build files.') 115 for arch in architectures: 116 arch_lib_path = os.path.join(output_dir, arch + '_libs') 117 if os.path.isdir(arch_lib_path): 118 shutil.rmtree(arch_lib_path) 119 120 121def BuildWebRTC(output_dir, target_arch, flavor, gn_target_name, 122 ios_deployment_target, libvpx_build_vp9, use_bitcode, use_goma, 123 extra_gn_args): 124 output_dir = os.path.join(output_dir, target_arch + '_libs') 125 gn_args = [ 126 'target_os="ios"', 'ios_enable_code_signing=false', 127 'use_xcode_clang=true', 'is_component_build=false' 128 ] 129 130 # Add flavor option. 131 if flavor == 'debug': 132 gn_args.append('is_debug=true') 133 elif flavor == 'release': 134 gn_args.append('is_debug=false') 135 else: 136 raise ValueError('Unexpected flavor type: %s' % flavor) 137 138 gn_args.append('target_cpu="%s"' % target_arch) 139 140 gn_args.append('ios_deployment_target="%s"' % ios_deployment_target) 141 142 gn_args.append('rtc_libvpx_build_vp9=' + 143 ('true' if libvpx_build_vp9 else 'false')) 144 145 gn_args.append('enable_ios_bitcode=' + 146 ('true' if use_bitcode else 'false')) 147 gn_args.append('use_goma=' + ('true' if use_goma else 'false')) 148 149 args_string = ' '.join(gn_args + extra_gn_args) 150 logging.info('Building WebRTC with args: %s', args_string) 151 152 cmd = [ 153 sys.executable, 154 os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gn.py'), 155 'gen', 156 output_dir, 157 '--args=' + args_string, 158 ] 159 _RunCommand(cmd) 160 logging.info('Building target: %s', gn_target_name) 161 162 cmd = [ 163 os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'ninja'), 164 '-C', 165 output_dir, 166 gn_target_name, 167 ] 168 if use_goma: 169 cmd.extend(['-j', '200']) 170 _RunCommand(cmd) 171 172 173def main(): 174 args = _ParseArgs() 175 176 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) 177 178 if args.clean: 179 _CleanArtifacts(args.output_dir) 180 return 0 181 182 architectures = list(args.arch) 183 gn_args = args.extra_gn_args 184 185 if args.purify: 186 _CleanTemporary(args.output_dir, architectures) 187 return 0 188 189 gn_target_name = 'framework_objc' 190 if not args.bitcode: 191 gn_args.append('enable_dsyms=true') 192 gn_args.append('enable_stripping=true') 193 194 # Build all architectures. 195 for arch in architectures: 196 BuildWebRTC(args.output_dir, arch, args.build_config, gn_target_name, 197 IOS_DEPLOYMENT_TARGET, LIBVPX_BUILD_VP9, args.bitcode, 198 args.use_goma, gn_args) 199 200 # Create FAT archive. 201 lib_paths = [ 202 os.path.join(args.output_dir, arch + '_libs') for arch in architectures 203 ] 204 205 # Combine the slices. 206 dylib_path = os.path.join(SDK_FRAMEWORK_NAME, 'WebRTC') 207 # Dylibs will be combined, all other files are the same across archs. 208 # Use distutils instead of shutil to support merging folders. 209 distutils.dir_util.copy_tree( 210 os.path.join(lib_paths[0], SDK_FRAMEWORK_NAME), 211 os.path.join(args.output_dir, SDK_FRAMEWORK_NAME)) 212 logging.info('Merging framework slices.') 213 dylib_paths = [os.path.join(path, dylib_path) for path in lib_paths] 214 out_dylib_path = os.path.join(args.output_dir, dylib_path) 215 try: 216 os.remove(out_dylib_path) 217 except OSError: 218 pass 219 cmd = ['lipo'] + dylib_paths + ['-create', '-output', out_dylib_path] 220 _RunCommand(cmd) 221 222 # Merge the dSYM slices. 223 lib_dsym_dir_path = os.path.join(lib_paths[0], 'WebRTC.dSYM') 224 if os.path.isdir(lib_dsym_dir_path): 225 distutils.dir_util.copy_tree( 226 lib_dsym_dir_path, os.path.join(args.output_dir, 'WebRTC.dSYM')) 227 logging.info('Merging dSYM slices.') 228 dsym_path = os.path.join('WebRTC.dSYM', 'Contents', 'Resources', 229 'DWARF', 'WebRTC') 230 lib_dsym_paths = [os.path.join(path, dsym_path) for path in lib_paths] 231 out_dsym_path = os.path.join(args.output_dir, dsym_path) 232 try: 233 os.remove(out_dsym_path) 234 except OSError: 235 pass 236 cmd = ['lipo'] + lib_dsym_paths + ['-create', '-output', out_dsym_path] 237 _RunCommand(cmd) 238 239 # Generate the license file. 240 ninja_dirs = [ 241 os.path.join(args.output_dir, arch + '_libs') 242 for arch in architectures 243 ] 244 gn_target_full_name = '//sdk:' + gn_target_name 245 builder = LicenseBuilder(ninja_dirs, [gn_target_full_name]) 246 builder.GenerateLicenseText( 247 os.path.join(args.output_dir, SDK_FRAMEWORK_NAME)) 248 249 # Modify the version number. 250 # Format should be <Branch cut MXX>.<Hotfix #>.<Rev #>. 251 # e.g. 55.0.14986 means branch cut 55, no hotfixes, and revision 14986. 252 infoplist_path = os.path.join(args.output_dir, SDK_FRAMEWORK_NAME, 253 'Info.plist') 254 cmd = [ 255 'PlistBuddy', '-c', 'Print :CFBundleShortVersionString', 256 infoplist_path 257 ] 258 major_minor = subprocess.check_output(cmd).strip() 259 version_number = '%s.%s' % (major_minor, args.revision) 260 logging.info('Substituting revision number: %s', version_number) 261 cmd = [ 262 'PlistBuddy', '-c', 'Set :CFBundleVersion ' + version_number, 263 infoplist_path 264 ] 265 _RunCommand(cmd) 266 _RunCommand(['plutil', '-convert', 'binary1', infoplist_path]) 267 268 logging.info('Done.') 269 return 0 270 271 272if __name__ == '__main__': 273 sys.exit(main()) 274