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