• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2015, Google Inc.
2#
3# Permission to use, copy, modify, and/or distribute this software for any
4# purpose with or without fee is hereby granted, provided that the above
5# copyright notice and this permission notice appear in all copies.
6#
7# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15"""Enumerates source files for consumption by various build systems."""
16
17import optparse
18import os
19import subprocess
20import sys
21import json
22
23
24# OS_ARCH_COMBOS maps from OS and platform to the OpenSSL assembly "style" for
25# that platform and the extension used by asm files.
26OS_ARCH_COMBOS = [
27    ('ios', 'arm', 'ios32', [], 'S'),
28    ('ios', 'aarch64', 'ios64', [], 'S'),
29    ('linux', 'arm', 'linux32', [], 'S'),
30    ('linux', 'aarch64', 'linux64', [], 'S'),
31    ('linux', 'ppc64le', 'linux64le', [], 'S'),
32    ('linux', 'x86', 'elf', ['-fPIC', '-DOPENSSL_IA32_SSE2'], 'S'),
33    ('linux', 'x86_64', 'elf', [], 'S'),
34    ('mac', 'x86', 'macosx', ['-fPIC', '-DOPENSSL_IA32_SSE2'], 'S'),
35    ('mac', 'x86_64', 'macosx', [], 'S'),
36    ('win', 'x86', 'win32n', ['-DOPENSSL_IA32_SSE2'], 'asm'),
37    ('win', 'x86_64', 'nasm', [], 'asm'),
38]
39
40# NON_PERL_FILES enumerates assembly files that are not processed by the
41# perlasm system.
42NON_PERL_FILES = {
43    ('linux', 'arm'): [
44        'src/crypto/curve25519/asm/x25519-asm-arm.S',
45        'src/crypto/poly1305/poly1305_arm_asm.S',
46    ],
47    ('linux', 'x86_64'): [
48        'src/crypto/curve25519/asm/x25519-asm-x86_64.S',
49    ],
50    ('mac', 'x86_64'): [
51        'src/crypto/curve25519/asm/x25519-asm-x86_64.S',
52    ],
53}
54
55PREFIX = None
56
57
58def PathOf(x):
59  return x if not PREFIX else os.path.join(PREFIX, x)
60
61
62class Android(object):
63
64  def __init__(self):
65    self.header = \
66"""# Copyright (C) 2015 The Android Open Source Project
67#
68# Licensed under the Apache License, Version 2.0 (the "License");
69# you may not use this file except in compliance with the License.
70# You may obtain a copy of the License at
71#
72#      http://www.apache.org/licenses/LICENSE-2.0
73#
74# Unless required by applicable law or agreed to in writing, software
75# distributed under the License is distributed on an "AS IS" BASIS,
76# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
77# See the License for the specific language governing permissions and
78# limitations under the License.
79
80# This file is created by generate_build_files.py. Do not edit manually.
81
82"""
83
84  def PrintVariableSection(self, out, name, files):
85    out.write('%s := \\\n' % name)
86    for f in sorted(files):
87      out.write('  %s\\\n' % f)
88    out.write('\n')
89
90  def WriteFiles(self, files, asm_outputs):
91    # New Android.bp format
92    with open('sources.bp', 'w+') as blueprint:
93      blueprint.write(self.header.replace('#', '//'))
94
95      blueprint.write('cc_defaults {\n')
96      blueprint.write('    name: "libcrypto_sources",\n')
97      blueprint.write('    srcs: [\n')
98      for f in sorted(files['crypto']):
99        blueprint.write('        "%s",\n' % f)
100      blueprint.write('    ],\n')
101      blueprint.write('    target: {\n')
102
103      for ((osname, arch), asm_files) in asm_outputs:
104        if osname != 'linux' or arch == 'ppc64le':
105          continue
106        if arch == 'aarch64':
107          arch = 'arm64'
108
109        blueprint.write('        android_%s: {\n' % arch)
110        blueprint.write('            srcs: [\n')
111        for f in sorted(asm_files):
112          blueprint.write('                "%s",\n' % f)
113        blueprint.write('            ],\n')
114        blueprint.write('        },\n')
115
116        if arch == 'x86' or arch == 'x86_64':
117          blueprint.write('        linux_%s: {\n' % arch)
118          blueprint.write('            srcs: [\n')
119          for f in sorted(asm_files):
120            blueprint.write('                "%s",\n' % f)
121          blueprint.write('            ],\n')
122          blueprint.write('        },\n')
123
124      blueprint.write('    },\n')
125      blueprint.write('}\n\n')
126
127      blueprint.write('cc_defaults {\n')
128      blueprint.write('    name: "libssl_sources",\n')
129      blueprint.write('    srcs: [\n')
130      for f in sorted(files['ssl']):
131        blueprint.write('        "%s",\n' % f)
132      blueprint.write('    ],\n')
133      blueprint.write('}\n\n')
134
135      blueprint.write('cc_defaults {\n')
136      blueprint.write('    name: "bssl_sources",\n')
137      blueprint.write('    srcs: [\n')
138      for f in sorted(files['tool']):
139        blueprint.write('        "%s",\n' % f)
140      blueprint.write('    ],\n')
141      blueprint.write('}\n\n')
142
143      blueprint.write('cc_defaults {\n')
144      blueprint.write('    name: "boringssl_test_support_sources",\n')
145      blueprint.write('    srcs: [\n')
146      for f in sorted(files['test_support']):
147        blueprint.write('        "%s",\n' % f)
148      blueprint.write('    ],\n')
149      blueprint.write('}\n\n')
150
151      blueprint.write('cc_defaults {\n')
152      blueprint.write('    name: "boringssl_crypto_test_sources",\n')
153      blueprint.write('    srcs: [\n')
154      for f in sorted(files['crypto_test']):
155        blueprint.write('        "%s",\n' % f)
156      blueprint.write('    ],\n')
157      blueprint.write('}\n\n')
158
159      blueprint.write('cc_defaults {\n')
160      blueprint.write('    name: "boringssl_ssl_test_sources",\n')
161      blueprint.write('    srcs: [\n')
162      for f in sorted(files['ssl_test']):
163        blueprint.write('        "%s",\n' % f)
164      blueprint.write('    ],\n')
165      blueprint.write('}\n\n')
166
167    # Legacy Android.mk format, only used by Trusty in new branches
168    with open('sources.mk', 'w+') as makefile:
169      makefile.write(self.header)
170
171      self.PrintVariableSection(makefile, 'crypto_sources', files['crypto'])
172
173      for ((osname, arch), asm_files) in asm_outputs:
174        if osname != 'linux':
175          continue
176        self.PrintVariableSection(
177            makefile, '%s_%s_sources' % (osname, arch), asm_files)
178
179
180class Bazel(object):
181  """Bazel outputs files suitable for including in Bazel files."""
182
183  def __init__(self):
184    self.firstSection = True
185    self.header = \
186"""# This file is created by generate_build_files.py. Do not edit manually.
187
188"""
189
190  def PrintVariableSection(self, out, name, files):
191    if not self.firstSection:
192      out.write('\n')
193    self.firstSection = False
194
195    out.write('%s = [\n' % name)
196    for f in sorted(files):
197      out.write('    "%s",\n' % PathOf(f))
198    out.write(']\n')
199
200  def WriteFiles(self, files, asm_outputs):
201    with open('BUILD.generated.bzl', 'w+') as out:
202      out.write(self.header)
203
204      self.PrintVariableSection(out, 'ssl_headers', files['ssl_headers'])
205      self.PrintVariableSection(out, 'fips_fragments', files['fips_fragments'])
206      self.PrintVariableSection(
207          out, 'ssl_internal_headers', files['ssl_internal_headers'])
208      self.PrintVariableSection(out, 'ssl_sources', files['ssl'])
209      self.PrintVariableSection(out, 'crypto_headers', files['crypto_headers'])
210      self.PrintVariableSection(
211          out, 'crypto_internal_headers', files['crypto_internal_headers'])
212      self.PrintVariableSection(out, 'crypto_sources', files['crypto'])
213      self.PrintVariableSection(out, 'tool_sources', files['tool'])
214      self.PrintVariableSection(out, 'tool_headers', files['tool_headers'])
215
216      for ((osname, arch), asm_files) in asm_outputs:
217        self.PrintVariableSection(
218            out, 'crypto_sources_%s_%s' % (osname, arch), asm_files)
219
220    with open('BUILD.generated_tests.bzl', 'w+') as out:
221      out.write(self.header)
222
223      out.write('test_support_sources = [\n')
224      for filename in sorted(files['test_support'] +
225                             files['test_support_headers'] +
226                             files['crypto_internal_headers'] +
227                             files['ssl_internal_headers']):
228        if os.path.basename(filename) == 'malloc.cc':
229          continue
230        out.write('    "%s",\n' % PathOf(filename))
231
232      out.write(']\n\n')
233
234      self.PrintVariableSection(out, 'crypto_test_sources',
235                                files['crypto_test'])
236      self.PrintVariableSection(out, 'ssl_test_sources', files['ssl_test'])
237
238
239class GN(object):
240
241  def __init__(self):
242    self.firstSection = True
243    self.header = \
244"""# Copyright (c) 2016 The Chromium Authors. All rights reserved.
245# Use of this source code is governed by a BSD-style license that can be
246# found in the LICENSE file.
247
248# This file is created by generate_build_files.py. Do not edit manually.
249
250"""
251
252  def PrintVariableSection(self, out, name, files):
253    if not self.firstSection:
254      out.write('\n')
255    self.firstSection = False
256
257    out.write('%s = [\n' % name)
258    for f in sorted(files):
259      out.write('  "%s",\n' % f)
260    out.write(']\n')
261
262  def WriteFiles(self, files, asm_outputs):
263    with open('BUILD.generated.gni', 'w+') as out:
264      out.write(self.header)
265
266      self.PrintVariableSection(out, 'crypto_sources',
267                                files['crypto'] + files['crypto_headers'] +
268                                files['crypto_internal_headers'])
269      self.PrintVariableSection(out, 'ssl_sources',
270                                files['ssl'] + files['ssl_headers'] +
271                                files['ssl_internal_headers'])
272
273      for ((osname, arch), asm_files) in asm_outputs:
274        self.PrintVariableSection(
275            out, 'crypto_sources_%s_%s' % (osname, arch), asm_files)
276
277      fuzzers = [os.path.splitext(os.path.basename(fuzzer))[0]
278                 for fuzzer in files['fuzz']]
279      self.PrintVariableSection(out, 'fuzzers', fuzzers)
280
281    with open('BUILD.generated_tests.gni', 'w+') as out:
282      self.firstSection = True
283      out.write(self.header)
284
285      self.PrintVariableSection(out, 'test_support_sources',
286                                files['test_support'] +
287                                files['test_support_headers'])
288      self.PrintVariableSection(out, 'crypto_test_sources',
289                                files['crypto_test'])
290      self.PrintVariableSection(out, 'ssl_test_sources', files['ssl_test'])
291
292
293class GYP(object):
294
295  def __init__(self):
296    self.header = \
297"""# Copyright (c) 2016 The Chromium Authors. All rights reserved.
298# Use of this source code is governed by a BSD-style license that can be
299# found in the LICENSE file.
300
301# This file is created by generate_build_files.py. Do not edit manually.
302
303"""
304
305  def PrintVariableSection(self, out, name, files):
306    out.write('    \'%s\': [\n' % name)
307    for f in sorted(files):
308      out.write('      \'%s\',\n' % f)
309    out.write('    ],\n')
310
311  def WriteFiles(self, files, asm_outputs):
312    with open('boringssl.gypi', 'w+') as gypi:
313      gypi.write(self.header + '{\n  \'variables\': {\n')
314
315      self.PrintVariableSection(gypi, 'boringssl_ssl_sources',
316                                files['ssl'] + files['ssl_headers'] +
317                                files['ssl_internal_headers'])
318      self.PrintVariableSection(gypi, 'boringssl_crypto_sources',
319                                files['crypto'] + files['crypto_headers'] +
320                                files['crypto_internal_headers'])
321
322      for ((osname, arch), asm_files) in asm_outputs:
323        self.PrintVariableSection(gypi, 'boringssl_%s_%s_sources' %
324                                  (osname, arch), asm_files)
325
326      gypi.write('  }\n}\n')
327
328
329def FindCMakeFiles(directory):
330  """Returns list of all CMakeLists.txt files recursively in directory."""
331  cmakefiles = []
332
333  for (path, _, filenames) in os.walk(directory):
334    for filename in filenames:
335      if filename == 'CMakeLists.txt':
336        cmakefiles.append(os.path.join(path, filename))
337
338  return cmakefiles
339
340def OnlyFIPSFragments(path, dent, is_dir):
341  return is_dir or (path.startswith(
342      os.path.join('src', 'crypto', 'fipsmodule', '')) and
343      NoTests(path, dent, is_dir))
344
345def NoTestsNorFIPSFragments(path, dent, is_dir):
346  return (NoTests(path, dent, is_dir) and
347      (is_dir or not OnlyFIPSFragments(path, dent, is_dir)))
348
349def NoTests(path, dent, is_dir):
350  """Filter function that can be passed to FindCFiles in order to remove test
351  sources."""
352  if is_dir:
353    return dent != 'test'
354  return 'test.' not in dent
355
356
357def OnlyTests(path, dent, is_dir):
358  """Filter function that can be passed to FindCFiles in order to remove
359  non-test sources."""
360  if is_dir:
361    return dent != 'test'
362  return '_test.' in dent
363
364
365def AllFiles(path, dent, is_dir):
366  """Filter function that can be passed to FindCFiles in order to include all
367  sources."""
368  return True
369
370
371def NoTestRunnerFiles(path, dent, is_dir):
372  """Filter function that can be passed to FindCFiles or FindHeaderFiles in
373  order to exclude test runner files."""
374  # NOTE(martinkr): This prevents .h/.cc files in src/ssl/test/runner, which
375  # are in their own subpackage, from being included in boringssl/BUILD files.
376  return not is_dir or dent != 'runner'
377
378
379def NotGTestSupport(path, dent, is_dir):
380  return 'gtest' not in dent
381
382
383def SSLHeaderFiles(path, dent, is_dir):
384  return dent in ['ssl.h', 'tls1.h', 'ssl23.h', 'ssl3.h', 'dtls1.h']
385
386
387def FindCFiles(directory, filter_func):
388  """Recurses through directory and returns a list of paths to all the C source
389  files that pass filter_func."""
390  cfiles = []
391
392  for (path, dirnames, filenames) in os.walk(directory):
393    for filename in filenames:
394      if not filename.endswith('.c') and not filename.endswith('.cc'):
395        continue
396      if not filter_func(path, filename, False):
397        continue
398      cfiles.append(os.path.join(path, filename))
399
400    for (i, dirname) in enumerate(dirnames):
401      if not filter_func(path, dirname, True):
402        del dirnames[i]
403
404  return cfiles
405
406
407def FindHeaderFiles(directory, filter_func):
408  """Recurses through directory and returns a list of paths to all the header files that pass filter_func."""
409  hfiles = []
410
411  for (path, dirnames, filenames) in os.walk(directory):
412    for filename in filenames:
413      if not filename.endswith('.h'):
414        continue
415      if not filter_func(path, filename, False):
416        continue
417      hfiles.append(os.path.join(path, filename))
418
419      for (i, dirname) in enumerate(dirnames):
420        if not filter_func(path, dirname, True):
421          del dirnames[i]
422
423  return hfiles
424
425
426def ExtractPerlAsmFromCMakeFile(cmakefile):
427  """Parses the contents of the CMakeLists.txt file passed as an argument and
428  returns a list of all the perlasm() directives found in the file."""
429  perlasms = []
430  with open(cmakefile) as f:
431    for line in f:
432      line = line.strip()
433      if not line.startswith('perlasm('):
434        continue
435      if not line.endswith(')'):
436        raise ValueError('Bad perlasm line in %s' % cmakefile)
437      # Remove "perlasm(" from start and ")" from end
438      params = line[8:-1].split()
439      if len(params) < 2:
440        raise ValueError('Bad perlasm line in %s' % cmakefile)
441      perlasms.append({
442          'extra_args': params[2:],
443          'input': os.path.join(os.path.dirname(cmakefile), params[1]),
444          'output': os.path.join(os.path.dirname(cmakefile), params[0]),
445      })
446
447  return perlasms
448
449
450def ReadPerlAsmOperations():
451  """Returns a list of all perlasm() directives found in CMake config files in
452  src/."""
453  perlasms = []
454  cmakefiles = FindCMakeFiles('src')
455
456  for cmakefile in cmakefiles:
457    perlasms.extend(ExtractPerlAsmFromCMakeFile(cmakefile))
458
459  return perlasms
460
461
462def PerlAsm(output_filename, input_filename, perlasm_style, extra_args):
463  """Runs the a perlasm script and puts the output into output_filename."""
464  base_dir = os.path.dirname(output_filename)
465  if not os.path.isdir(base_dir):
466    os.makedirs(base_dir)
467  subprocess.check_call(
468      ['perl', input_filename, perlasm_style] + extra_args + [output_filename])
469
470
471def ArchForAsmFilename(filename):
472  """Returns the architectures that a given asm file should be compiled for
473  based on substrings in the filename."""
474
475  if 'x86_64' in filename or 'avx2' in filename:
476    return ['x86_64']
477  elif ('x86' in filename and 'x86_64' not in filename) or '586' in filename:
478    return ['x86']
479  elif 'armx' in filename:
480    return ['arm', 'aarch64']
481  elif 'armv8' in filename:
482    return ['aarch64']
483  elif 'arm' in filename:
484    return ['arm']
485  elif 'ppc' in filename:
486    return ['ppc64le']
487  else:
488    raise ValueError('Unknown arch for asm filename: ' + filename)
489
490
491def WriteAsmFiles(perlasms):
492  """Generates asm files from perlasm directives for each supported OS x
493  platform combination."""
494  asmfiles = {}
495
496  for osarch in OS_ARCH_COMBOS:
497    (osname, arch, perlasm_style, extra_args, asm_ext) = osarch
498    key = (osname, arch)
499    outDir = '%s-%s' % key
500
501    for perlasm in perlasms:
502      filename = os.path.basename(perlasm['input'])
503      output = perlasm['output']
504      if not output.startswith('src'):
505        raise ValueError('output missing src: %s' % output)
506      output = os.path.join(outDir, output[4:])
507      if output.endswith('-armx.${ASM_EXT}'):
508        output = output.replace('-armx',
509                                '-armx64' if arch == 'aarch64' else '-armx32')
510      output = output.replace('${ASM_EXT}', asm_ext)
511
512      if arch in ArchForAsmFilename(filename):
513        PerlAsm(output, perlasm['input'], perlasm_style,
514                perlasm['extra_args'] + extra_args)
515        asmfiles.setdefault(key, []).append(output)
516
517  for (key, non_perl_asm_files) in NON_PERL_FILES.iteritems():
518    asmfiles.setdefault(key, []).extend(non_perl_asm_files)
519
520  return asmfiles
521
522
523def ExtractVariablesFromCMakeFile(cmakefile):
524  """Parses the contents of the CMakeLists.txt file passed as an argument and
525  returns a dictionary of exported source lists."""
526  variables = {}
527  in_set_command = False
528  set_command = []
529  with open(cmakefile) as f:
530    for line in f:
531      if '#' in line:
532        line = line[:line.index('#')]
533      line = line.strip()
534
535      if not in_set_command:
536        if line.startswith('set('):
537          in_set_command = True
538          set_command = []
539      elif line == ')':
540        in_set_command = False
541        if not set_command:
542          raise ValueError('Empty set command')
543        variables[set_command[0]] = set_command[1:]
544      else:
545        set_command.extend([c for c in line.split(' ') if c])
546
547  if in_set_command:
548    raise ValueError('Unfinished set command')
549  return variables
550
551
552def main(platforms):
553  cmake = ExtractVariablesFromCMakeFile(os.path.join('src', 'sources.cmake'))
554  crypto_c_files = FindCFiles(os.path.join('src', 'crypto'), NoTestsNorFIPSFragments)
555  fips_fragments = FindCFiles(os.path.join('src', 'crypto', 'fipsmodule'), OnlyFIPSFragments)
556  ssl_source_files = FindCFiles(os.path.join('src', 'ssl'), NoTests)
557  tool_c_files = FindCFiles(os.path.join('src', 'tool'), NoTests)
558  tool_h_files = FindHeaderFiles(os.path.join('src', 'tool'), AllFiles)
559
560  # Generate err_data.c
561  with open('err_data.c', 'w+') as err_data:
562    subprocess.check_call(['go', 'run', 'err_data_generate.go'],
563                          cwd=os.path.join('src', 'crypto', 'err'),
564                          stdout=err_data)
565  crypto_c_files.append('err_data.c')
566
567  test_support_c_files = FindCFiles(os.path.join('src', 'crypto', 'test'),
568                                    NotGTestSupport)
569  test_support_h_files = (
570      FindHeaderFiles(os.path.join('src', 'crypto', 'test'), AllFiles) +
571      FindHeaderFiles(os.path.join('src', 'ssl', 'test'), NoTestRunnerFiles))
572
573  # Generate crypto_test_data.cc
574  with open('crypto_test_data.cc', 'w+') as out:
575    subprocess.check_call(
576        ['go', 'run', 'util/embed_test_data.go'] + cmake['CRYPTO_TEST_DATA'],
577        cwd='src',
578        stdout=out)
579
580  crypto_test_files = FindCFiles(os.path.join('src', 'crypto'), OnlyTests)
581  crypto_test_files += [
582      'crypto_test_data.cc',
583      'src/crypto/test/file_test_gtest.cc',
584      'src/crypto/test/gtest_main.cc',
585  ]
586
587  ssl_test_files = FindCFiles(os.path.join('src', 'ssl'), OnlyTests)
588  ssl_test_files.append('src/crypto/test/gtest_main.cc')
589
590  fuzz_c_files = FindCFiles(os.path.join('src', 'fuzz'), NoTests)
591
592  ssl_h_files = (
593      FindHeaderFiles(
594          os.path.join('src', 'include', 'openssl'),
595          SSLHeaderFiles))
596
597  def NotSSLHeaderFiles(path, filename, is_dir):
598    return not SSLHeaderFiles(path, filename, is_dir)
599  crypto_h_files = (
600      FindHeaderFiles(
601          os.path.join('src', 'include', 'openssl'),
602          NotSSLHeaderFiles))
603
604  ssl_internal_h_files = FindHeaderFiles(os.path.join('src', 'ssl'), NoTests)
605  crypto_internal_h_files = FindHeaderFiles(
606      os.path.join('src', 'crypto'), NoTests)
607
608  files = {
609      'crypto': crypto_c_files,
610      'crypto_headers': crypto_h_files,
611      'crypto_internal_headers': crypto_internal_h_files,
612      'crypto_test': sorted(crypto_test_files),
613      'fips_fragments': fips_fragments,
614      'fuzz': fuzz_c_files,
615      'ssl': ssl_source_files,
616      'ssl_headers': ssl_h_files,
617      'ssl_internal_headers': ssl_internal_h_files,
618      'ssl_test': sorted(ssl_test_files),
619      'tool': tool_c_files,
620      'tool_headers': tool_h_files,
621      'test_support': test_support_c_files,
622      'test_support_headers': test_support_h_files,
623  }
624
625  asm_outputs = sorted(WriteAsmFiles(ReadPerlAsmOperations()).iteritems())
626
627  for platform in platforms:
628    platform.WriteFiles(files, asm_outputs)
629
630  return 0
631
632
633if __name__ == '__main__':
634  parser = optparse.OptionParser(usage='Usage: %prog [--prefix=<path>]'
635      ' [android|bazel|gn|gyp]')
636  parser.add_option('--prefix', dest='prefix',
637      help='For Bazel, prepend argument to all source files')
638  options, args = parser.parse_args(sys.argv[1:])
639  PREFIX = options.prefix
640
641  if not args:
642    parser.print_help()
643    sys.exit(1)
644
645  platforms = []
646  for s in args:
647    if s == 'android':
648      platforms.append(Android())
649    elif s == 'bazel':
650      platforms.append(Bazel())
651    elif s == 'gn':
652      platforms.append(GN())
653    elif s == 'gyp':
654      platforms.append(GYP())
655    else:
656      parser.print_help()
657      sys.exit(1)
658
659  sys.exit(main(platforms))
660