1#!/usr/bin/python
2# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7Builds and packages ChromeWebView.framework.
8"""
9
10import argparse
11import os
12import shutil
13import sys
14
15def target_dir_name(build_config, target_device):
16  """Returns a default output directory name string.
17
18  Args:
19    build_config: A string describing the build configuration. Ex: 'Debug'
20    target_device: A string describing the target device. Ex: 'simulator'
21  """
22  return '%s-%s' % (build_config, target_device)
23
24def build(build_config, target_device, extra_gn_options, extra_ninja_options):
25  """Generates and builds ChromeWebView.framework.
26
27  Args:
28    build_config: A string describing the build configuration. Ex: 'Debug'
29    target_device: A string describing the target device. Ex: 'simulator'
30    extra_gn_options: A list of strings of gn args (key=value items) to
31      be appended to the gn gen command.
32    extra_ninja_options: A string of gn options to be appended to the ninja
33      command.
34
35  Returns:
36    The return code of generating ninja if it is non-zero, else the return code
37      of the ninja build command.
38  """
39  gn_args = [
40      'target_os="ios"',
41      'enable_websockets=false',
42      'is_component_build=false',
43      'use_xcode_clang=false',
44      'disable_file_support=true',
45      'disable_ftp_support=true',
46      'disable_brotli_filter=true',
47      'ios_enable_code_signing=false',
48      'enable_dsyms=true',
49  ]
50
51  if target_device == 'iphoneos':
52    gn_args.extend([
53        'target_cpu="arm64"',
54        'target_environment="device"',
55    ])
56  else:
57    gn_args.extend([
58        'target_cpu="x64"',
59        'target_environment="simulator"',
60    ])
61
62  if build_config == 'Debug':
63    gn_args.append('is_debug=true')
64  else:
65    gn_args.extend([
66        'is_debug=false',
67        'enable_stripping=true',
68        'is_official_build=true',
69    ])
70
71  if extra_gn_options:
72    gn_args.extend(extra_gn_options)
73
74  build_dir = os.path.join("out", target_dir_name(build_config, target_device))
75  gn_command = 'gn gen %s --args=\'%s\'' % (build_dir, ' '.join(gn_args))
76  print gn_command
77  gn_result = os.system(gn_command)
78  if gn_result != 0:
79    return gn_result
80
81  ninja_options = '-C %s' % build_dir
82  if extra_ninja_options:
83    ninja_options += ' %s' % extra_ninja_options
84  ninja_command = ('ninja %s ios/web_view:ios_web_view_package' %
85                   ninja_options)
86  print ninja_command
87  return os.system(ninja_command)
88
89def copy_build_products(build_config, target_device, out_dir, output_name):
90  """Copies the resulting framework and symbols to out_dir.
91
92  Args:
93    build_config: A string describing the build configuration. Ex: 'Debug'
94    target_device: A string describing the target device. Ex: 'simulator'
95    out_dir: A string to the path which all build products will be copied.
96  """
97  target_dir = target_dir_name(build_config, target_device)
98  build_dir = os.path.join("out", target_dir)
99  package_dir = os.path.join(build_dir, 'ios_web_view')
100
101  # # Copy framework.
102  framework_name = '%s.framework' % output_name
103  framework_source = os.path.join(build_dir, framework_name)
104  framework_dest = os.path.join(out_dir, target_dir, framework_name)
105  print 'Copying %s to %s' % (framework_source, framework_dest)
106  shutil.copytree(framework_source, framework_dest)
107
108  # Copy symbols.
109  symbols_name = '%s.dSYM' % output_name
110  symbols_source = os.path.join(build_dir, symbols_name)
111  symbols_dest = os.path.join(out_dir, target_dir, symbols_name)
112  print 'Copying %s to %s' % (symbols_source, symbols_dest)
113  shutil.copytree(symbols_source, symbols_dest)
114
115def package_framework(build_config,
116                      target_device,
117                      out_dir,
118                      output_name,
119                      extra_gn_options,
120                      extra_ninja_options):
121  """Builds ChromeWebView.framework and copies the result to out_dir.
122
123  Args:
124    build_config: A string describing the build configuration. Ex: 'Debug'
125    target_device: A string describing the target device. Ex: 'simulator'
126    out_dir: A string to the path which all build products will be copied.
127    extra_gn_options: A list of strings of gn args (key=value items) to
128      be appended to the gn gen command.
129    extra_ninja_options: A string of gn options to be appended to the ninja
130      command.
131
132  Returns:
133    The return code of the build if it fails or 0 if the build was successful.
134  """
135  print '\nBuilding for %s (%s)' % (target_device, build_config)
136
137  build_result = build(build_config,
138                       target_device,
139                       extra_gn_options,
140                       extra_ninja_options)
141  if build_result != 0:
142    error = 'Building %s/%s failed with code: ' % (build_config, target_device)
143    print >>sys.stderr, error, build_result
144    return build_result
145  copy_build_products(build_config, target_device, out_dir, output_name)
146  return 0
147
148def package_all_frameworks(out_dir, output_name, extra_gn_options,
149                           build_configs, target_devices, extra_ninja_options):
150  """Builds ChromeWebView.framework.
151
152  Builds Release and Debug versions of ChromeWebView.framework for both
153    iOS devices and simulator and copies the resulting frameworks into out_dir.
154
155  Args:
156    out_dir: A string to the path which all build products will be copied.
157    extra_gn_options: A list of strings of gn args (key=value items) to
158      be appended to the gn gen command.
159    build_configs: A list of configs to build.
160    target_devices: A list of devices to target.
161    extra_ninja_options: A string of gn options to be appended to the ninja
162      command.
163
164  Returns:
165    0 if all builds are successful or 1 if any build fails.
166  """
167  print 'Building ChromeWebView.framework...'
168
169  # Package all builds in the output directory
170  os.makedirs(out_dir)
171
172  configs_and_devices = [(a,b) for a in build_configs for b in target_devices]
173  for build_config, target_device in configs_and_devices:
174    if package_framework(build_config,
175                         target_device,
176                         out_dir,
177                         output_name,
178                         extra_gn_options,
179                         extra_ninja_options) != 0:
180      return 1
181
182  # Copy common files from last built package to out_dir.
183  build_dir = os.path.join('out', target_dir_name('Release', 'iphoneos'))
184  package_dir = os.path.join(build_dir, 'ios_web_view')
185  shutil.copy2(os.path.join(package_dir, 'AUTHORS'), out_dir)
186  shutil.copy2(os.path.join(package_dir, 'LICENSE'), out_dir)
187  shutil.copy2(os.path.join(package_dir, 'VERSION'), out_dir)
188
189  print '\nSuccess! ChromeWebView.framework is packaged into %s' % out_dir
190
191  return 0
192
193def main():
194  description = 'Build and package //ios/web_view.'
195  parser = argparse.ArgumentParser(description=description)
196
197  parser.add_argument('out_dir', nargs='?', default='out/IOSWebViewBuild',
198                      help='path to output directory')
199  parser.add_argument('--no_goma', action='store_true',
200                      help='Prevents adding use_goma=true to the gn args.')
201  parser.add_argument('--ninja_args',
202                      help='Additional gn args to pass through to ninja.')
203  parser.add_argument('--include_cronet', action='store_true',
204                      help='Combines Cronet and ChromeWebView as 1 framework.')
205  build_configs = ['Debug', 'Release']
206  target_devices = ['iphonesimulator', 'iphoneos']
207  parser.add_argument('--build_configs', nargs='+', default=build_configs,
208                      choices=build_configs,
209                      help='Specify which configs to build.')
210  parser.add_argument('--target_devices', nargs='+', default=target_devices,
211                      choices=target_devices,
212                      help='Specify which devices to target.')
213
214  options, extra_options = parser.parse_known_args()
215  print 'Options:', options
216
217  if len(extra_options):
218    print >>sys.stderr, 'Unknown options: ', extra_options
219    return 1
220
221  out_dir = options.out_dir
222  # Make sure that the output directory does not exist
223  if os.path.exists(out_dir):
224    print >>sys.stderr, 'The output directory already exists: ' + out_dir
225    return 1
226
227  output_name = 'ChromeWebView'
228  extra_gn_options = []
229  if not options.no_goma:
230    extra_gn_options.append('use_goma=true')
231  if options.include_cronet:
232    extra_gn_options.append('ios_web_view_include_cronet=true')
233    output_name = 'CronetChromeWebView'
234  else:
235    extra_gn_options.append('ios_web_view_include_cronet=false')
236  extra_gn_options.append('ios_web_view_output_name="%s"' % output_name)
237  # This prevents Breakpad from being included in the final binary to avoid
238  # duplicate symbols with the client app.
239  extra_gn_options.append('use_crash_key_stubs=true')
240
241  return package_all_frameworks(out_dir, output_name, extra_gn_options,
242                                set(options.build_configs),
243                                set(options.target_devices),
244                                options.ninja_args)
245
246if __name__ == '__main__':
247  sys.exit(main())
248