1 #!/usr/bin/env python 2 # 3 # Copyright (C) 2015 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 """Verifies that the build is sane. 18 19 Cleans old build artifacts, configures the required environment, determines 20 build goals, and invokes the build scripts. 21 """ 22 from __future__ import print_function 23 24 import argparse 25 import collections 26 import datetime 27 import inspect 28 import os 29 import shutil 30 import site 31 import subprocess 32 import sys 33 import tempfile 34 import textwrap 35 36 site.addsitedir(os.path.join(os.path.dirname(__file__), 'build/lib')) 37 38 import build_support # pylint: disable=import-error 39 40 41 ALL_MODULES = { 42 'binutils', 43 'build', 44 'clang', 45 'cpufeatures', 46 'gabi++', 47 'gcc', 48 'gcclibs', 49 'gdbserver', 50 'gnustl', 51 'gtest', 52 'host-tools', 53 'libandroid_support', 54 'libc++', 55 'libc++abi', 56 'native_app_glue', 57 'ndk_helper', 58 'platforms', 59 'python-packages', 60 'stlport', 61 'system-stl', 62 } 63 64 65 class ArgParser(argparse.ArgumentParser): 66 def __init__(self): 67 super(ArgParser, self).__init__( 68 description=inspect.getdoc(sys.modules[__name__])) 69 70 self.add_argument( 71 '--arch', 72 choices=('arm', 'arm64', 'mips', 'mips64', 'x86', 'x86_64'), 73 help='Build for the given architecture. Build all by default.') 74 75 package_group = self.add_mutually_exclusive_group() 76 package_group.add_argument( 77 '--package', action='store_true', dest='package', default=True, 78 help='Package the NDK when done building (default).') 79 package_group.add_argument( 80 '--no-package', action='store_false', dest='package', 81 help='Do not package the NDK when done building.') 82 83 test_group = self.add_mutually_exclusive_group() 84 test_group.add_argument( 85 '--test', action='store_true', dest='test', default=True, 86 help=textwrap.dedent("""\ 87 Run host tests when finished. --package is required. Not supported 88 when targeting Windows. 89 """)) 90 test_group.add_argument( 91 '--no-test', action='store_false', dest='test', 92 help='Do not run host tests when finished.') 93 94 self.add_argument( 95 '--release', 96 help='Release name. Package will be named android-ndk-RELEASE.') 97 98 self.add_argument( 99 '--system', choices=('darwin', 'linux', 'windows', 'windows64'), 100 default=build_support.get_default_host(), 101 help='Build for the given OS.') 102 103 module_group = self.add_mutually_exclusive_group() 104 105 module_group.add_argument( 106 '--module', choices=sorted(ALL_MODULES), 107 help='NDK modules to build.') 108 109 module_group.add_argument( 110 '--host-only', action='store_true', 111 help='Skip building target components.') 112 113 114 def _invoke_build(script, args): 115 if args is None: 116 args = [] 117 subprocess.check_call([build_support.android_path(script)] + args) 118 119 120 def invoke_build(script, args=None): 121 script_path = os.path.join('build/tools', script) 122 _invoke_build(build_support.ndk_path(script_path), args) 123 124 125 def invoke_external_build(script, args=None): 126 _invoke_build(build_support.android_path(script), args) 127 128 129 def package_ndk(out_dir, dist_dir, args): 130 package_args = common_build_args(out_dir, dist_dir, args) 131 package_args.append(dist_dir) 132 133 if args.release is not None: 134 package_args.append('--release={}'.format(args.release)) 135 136 if args.arch is not None: 137 package_args.append('--arch={}'.format(args.arch)) 138 139 invoke_build('package.py', package_args) 140 141 142 def test_ndk(out_dir, args): 143 release = args.release 144 if args.release is None: 145 release = datetime.date.today().strftime('%Y%m%d') 146 147 # The packaging step extracts all the modules to a known directory for 148 # packaging. This directory is not cleaned up after packaging, so we can 149 # reuse that for testing. 150 test_dir = os.path.join(out_dir, 'android-ndk-{}'.format(release)) 151 152 test_env = dict(os.environ) 153 test_env['NDK'] = test_dir 154 155 abis = build_support.ALL_ABIS 156 if args.arch is not None: 157 abis = build_support.arch_to_abis(args.arch) 158 159 results = {} 160 for abi in abis: 161 cmd = [ 162 'python', build_support.ndk_path('tests/run-all.py'), 163 '--abi', abi, '--suite', 'build' 164 ] 165 print('Running tests: {}'.format(' '.join(cmd))) 166 result = subprocess.call(cmd, env=test_env) 167 results[abi] = result == 0 168 169 print('Results:') 170 for abi, result in results.iteritems(): 171 print('{}: {}'.format(abi, 'PASS' if result else 'FAIL')) 172 return all(results.values()) 173 174 175 def common_build_args(out_dir, dist_dir, args): 176 build_args = ['--out-dir={}'.format(out_dir)] 177 build_args = ['--dist-dir={}'.format(dist_dir)] 178 build_args.append('--host={}'.format(args.system)) 179 return build_args 180 181 182 def fixup_toolchain_triple(toolchain): 183 """Maps toolchain names to their proper triple. 184 185 The x86 toolchains are named stupidly and aren't a proper triple. 186 """ 187 return { 188 'x86': 'i686-linux-android', 189 'x86_64': 'x86_64-linux-android', 190 }.get(toolchain, toolchain) 191 192 193 def get_binutils_files(triple, has_gold, is_windows): 194 files = [ 195 'ld.bfd', 196 'nm', 197 'as', 198 'objcopy', 199 'strip', 200 'objdump', 201 'ld', 202 'ar', 203 'ranlib', 204 ] 205 206 if has_gold: 207 files.append('ld.gold') 208 209 if is_windows: 210 files = [f + '.exe' for f in files] 211 212 # binutils programs get installed to two locations: 213 # 1: $INSTALL_DIR/bin/$TRIPLE-$PROGRAM 214 # 2: $INSTALL_DIR/$TRIPLE/bin/$PROGRAM 215 # 216 # We need to copy both. 217 218 prefixed_files = [] 219 for file_name in files: 220 prefixed_name = '-'.join([triple, file_name]) 221 prefixed_files.append(os.path.join('bin', prefixed_name)) 222 223 dir_prefixed_files = [] 224 for file_name in files: 225 dir_prefixed_files.append(os.path.join(triple, 'bin', file_name)) 226 227 ldscripts_dir = os.path.join(triple, 'lib/ldscripts') 228 return prefixed_files + dir_prefixed_files + [ldscripts_dir] 229 230 231 def install_file(file_name, src_dir, dst_dir): 232 src_file = os.path.join(src_dir, file_name) 233 dst_file = os.path.join(dst_dir, file_name) 234 235 print('Copying {} to {}...'.format(src_file, dst_file)) 236 if os.path.isdir(src_file): 237 _install_dir(src_file, dst_file) 238 elif os.path.islink(src_file): 239 _install_symlink(src_file, dst_file) 240 else: 241 _install_file(src_file, dst_file) 242 243 244 def _install_dir(src_dir, dst_dir): 245 parent_dir = os.path.normpath(os.path.join(dst_dir, '..')) 246 if not os.path.exists(parent_dir): 247 os.makedirs(parent_dir) 248 shutil.copytree(src_dir, dst_dir, symlinks=True) 249 250 251 def _install_symlink(src_file, dst_file): 252 dirname = os.path.dirname(dst_file) 253 if not os.path.exists(dirname): 254 os.makedirs(dirname) 255 link_target = os.readlink(src_file) 256 os.symlink(link_target, dst_file) 257 258 259 def _install_file(src_file, dst_file): 260 dirname = os.path.dirname(dst_file) 261 if not os.path.exists(dirname): 262 os.makedirs(dirname) 263 # copy2 is just copy followed by copystat (preserves file metadata). 264 shutil.copy2(src_file, dst_file) 265 266 267 def pack_binutils(arch, host_tag, out_dir, binutils_path): 268 archive_name = '-'.join(['binutils', arch, host_tag]) 269 build_support.make_package(archive_name, binutils_path, out_dir) 270 271 272 def get_prebuilt_gcc(host, arch): 273 tag = build_support.host_to_tag(host) 274 system_subdir = 'prebuilts/ndk/current/toolchains/{}'.format(tag) 275 system_path = build_support.android_path(system_subdir) 276 toolchain = build_support.arch_to_toolchain(arch) 277 toolchain_dir = toolchain + '-4.9' 278 return os.path.join(system_path, toolchain_dir) 279 280 281 def build_binutils(out_dir, dist_dir, args): 282 print('Extracting binutils package from GCC...') 283 284 arches = build_support.ALL_ARCHITECTURES 285 if args.arch is not None: 286 arches = [args.arch] 287 288 host_tag = build_support.host_to_tag(args.system) 289 290 for arch in arches: 291 toolchain = build_support.arch_to_toolchain(arch) 292 toolchain_path = get_prebuilt_gcc(args.system, arch) 293 294 triple = fixup_toolchain_triple(toolchain) 295 296 install_dir = os.path.join(out_dir, 'binutils', triple) 297 if os.path.exists(install_dir): 298 shutil.rmtree(install_dir) 299 os.makedirs(install_dir) 300 301 has_gold = True 302 if host_tag == 'windows': 303 # Note: 64-bit Windows is fine. 304 has_gold = False 305 if arch in ('mips', 'mips64'): 306 has_gold = False 307 308 is_windows = host_tag.startswith('windows') 309 for file_name in get_binutils_files(triple, has_gold, is_windows): 310 install_file(file_name, toolchain_path, install_dir) 311 312 license_path = build_support.android_path( 313 'toolchain/binutils/binutils-2.25/COPYING') 314 shutil.copy2(license_path, os.path.join(install_dir, 'NOTICE')) 315 316 pack_binutils(arch, host_tag, dist_dir, install_dir) 317 318 319 def build_clang(out_dir, dist_dir, args): 320 print('Building Clang...') 321 invoke_build('build-llvm.py', common_build_args(out_dir, dist_dir, args)) 322 323 324 def build_gcc(out_dir, dist_dir, args): 325 print('Building GCC...') 326 build_args = common_build_args(out_dir, dist_dir, args) 327 if args.arch is not None: 328 build_args.append('--arch={}'.format(args.arch)) 329 invoke_build('build-gcc.py', build_args) 330 331 332 def build_gcc_libs(out_dir, dist_dir, args): 333 print('Packaging GCC libs...') 334 335 arches = build_support.ALL_ARCHITECTURES 336 if args.arch is not None: 337 arches = [args.arch] 338 339 for arch in arches: 340 toolchain = build_support.arch_to_toolchain(arch) 341 triple = fixup_toolchain_triple(toolchain) 342 libgcc_subdir = 'lib/gcc/{}/4.9'.format(triple) 343 is64 = arch.endswith('64') 344 libatomic_subdir = '{}/lib{}'.format(triple, '64' if is64 else '') 345 346 lib_names = [ 347 (libatomic_subdir, 'libatomic.a'), 348 (libgcc_subdir, 'libgcc.a'), 349 ] 350 351 lib_dirs = [''] 352 if arch == 'arm': 353 lib_dirs += [ 354 'armv7-a', 355 'armv7-a/hard', 356 'armv7-a/thumb', 357 'armv7-a/thumb/hard', 358 'thumb', 359 ] 360 361 libs = [] 362 for lib_dir in lib_dirs: 363 for subdir, lib in lib_names: 364 libs.append((subdir, os.path.join(lib_dir, lib))) 365 366 install_dir = os.path.join(out_dir, 'gcclibs', triple) 367 if os.path.exists(install_dir): 368 shutil.rmtree(install_dir) 369 os.makedirs(install_dir) 370 371 # These are target libraries, so the OS we use here is not 372 # important. We explicitly use Linux because for whatever reason 373 # the Windows aarch64 toolchain doesn't include libatomic. 374 gcc_path = get_prebuilt_gcc('linux', arch) 375 for gcc_subdir, lib in libs: 376 src = os.path.join(gcc_path, gcc_subdir, lib) 377 dst = os.path.join(install_dir, lib) 378 dst_dir = os.path.dirname(dst) 379 if not os.path.exists(dst_dir): 380 os.makedirs(dst_dir) 381 shutil.copy2(src, dst) 382 383 shutil.copy2( 384 os.path.join(gcc_path, 'NOTICE'), 385 os.path.join(install_dir, 'NOTICE')) 386 387 archive_name = os.path.join('gcclibs-' + arch) 388 build_support.make_package(archive_name, install_dir, dist_dir) 389 390 391 def build_host_tools(out_dir, dist_dir, args): 392 build_args = common_build_args(out_dir, dist_dir, args) 393 394 print('Building ndk-stack...') 395 invoke_external_build( 396 'ndk/sources/host-tools/ndk-stack/build.py', build_args) 397 398 print('Building ndk-depends...') 399 invoke_external_build( 400 'ndk/sources/host-tools/ndk-depends/build.py', build_args) 401 402 print('Building awk...') 403 invoke_external_build( 404 'ndk/sources/host-tools/nawk-20071023/build.py', build_args) 405 406 print('Building make...') 407 invoke_external_build( 408 'ndk/sources/host-tools/make-3.81/build.py', build_args) 409 410 if args.system in ('windows', 'windows64'): 411 print('Building toolbox...') 412 invoke_external_build( 413 'ndk/sources/host-tools/toolbox/build.py', build_args) 414 415 print('Building Python...') 416 invoke_external_build('toolchain/python/build.py', build_args) 417 418 print('Building GDB...') 419 invoke_external_build('toolchain/gdb/build.py', build_args) 420 421 print('Building YASM...') 422 invoke_external_build('toolchain/yasm/build.py', build_args) 423 424 package_host_tools(out_dir, dist_dir, args.system) 425 426 427 def merge_license_files(output_path, files): 428 licenses = [] 429 for license_path in files: 430 with open(license_path) as license_file: 431 licenses.append(license_file.read()) 432 433 with open(output_path, 'w') as output_file: 434 output_file.write('\n'.join(licenses)) 435 436 437 def package_host_tools(out_dir, dist_dir, host): 438 packages = [ 439 'gdb-multiarch-7.10', 440 'ndk-awk', 441 'ndk-depends', 442 'ndk-make', 443 'ndk-python', 444 'ndk-stack', 445 'ndk-yasm', 446 ] 447 448 files = [ 449 'ndk-gdb', 450 'ndk-gdb.cmd', 451 'ndk-gdb.py', 452 ] 453 454 if host in ('windows', 'windows64'): 455 packages.append('toolbox') 456 457 host_tag = build_support.host_to_tag(host) 458 459 package_names = [p + '-' + host_tag + '.tar.bz2' for p in packages] 460 for package_name in package_names: 461 package_path = os.path.join(out_dir, package_name) 462 subprocess.check_call(['tar', 'xf', package_path, '-C', out_dir]) 463 464 for f in files: 465 shutil.copy2(f, os.path.join(out_dir, 'host-tools/bin')) 466 467 merge_license_files(os.path.join(out_dir, 'host-tools/NOTICE'), [ 468 build_support.android_path('toolchain/gdb/gdb-7.10/COPYING'), 469 build_support.ndk_path('sources/host-tools/nawk-20071023/NOTICE'), 470 build_support.ndk_path('sources/host-tools/ndk-depends/NOTICE'), 471 build_support.ndk_path('sources/host-tools/make-3.81/COPYING'), 472 build_support.android_path( 473 'toolchain/python/Python-2.7.5/LICENSE'), 474 build_support.ndk_path('sources/host-tools/ndk-stack/NOTICE'), 475 build_support.ndk_path('sources/host-tools/toolbox/NOTICE'), 476 build_support.android_path('toolchain/yasm/COPYING'), 477 build_support.android_path('toolchain/yasm/BSD.txt'), 478 build_support.android_path('toolchain/yasm/Artistic.txt'), 479 build_support.android_path('toolchain/yasm/GNU_GPL-2.0'), 480 build_support.android_path('toolchain/yasm/GNU_LGPL-2.0'), 481 ]) 482 483 package_name = 'host-tools-' + host_tag 484 path = os.path.join(out_dir, 'host-tools') 485 build_support.make_package(package_name, path, dist_dir) 486 487 488 def build_gdbserver(out_dir, dist_dir, args): 489 print('Building gdbserver...') 490 build_args = common_build_args(out_dir, dist_dir, args) 491 if args.arch is not None: 492 build_args.append('--arch={}'.format(args.arch)) 493 invoke_build('build-gdbserver.py', build_args) 494 495 496 def _build_stl(out_dir, dist_dir, args, stl): 497 build_args = common_build_args(out_dir, dist_dir, args) 498 if args.arch is not None: 499 build_args.append('--arch={}'.format(args.arch)) 500 script = 'ndk/sources/cxx-stl/{}/build.py'.format(stl) 501 invoke_external_build(script, build_args) 502 503 504 def build_gnustl(out_dir, dist_dir, args): 505 print('Building gnustl...') 506 _build_stl(out_dir, dist_dir, args, 'gnu-libstdc++') 507 508 509 def build_libcxx(out_dir, dist_dir, args): 510 print('Building libc++...') 511 _build_stl(out_dir, dist_dir, args, 'llvm-libc++') 512 513 514 def build_stlport(out_dir, dist_dir, args): 515 print('Building stlport...') 516 _build_stl(out_dir, dist_dir, args, 'stlport') 517 518 519 def build_platforms(out_dir, dist_dir, args): 520 print('Building platforms...') 521 build_args = common_build_args(out_dir, dist_dir, args) 522 invoke_build('build-platforms.py', build_args) 523 524 525 def build_cpufeatures(_, dist_dir, __): 526 path = build_support.ndk_path('sources/android/cpufeatures') 527 build_support.make_package('cpufeatures', path, dist_dir) 528 529 530 def build_native_app_glue(_, dist_dir, __): 531 path = build_support.android_path( 532 'development/ndk/sources/android/native_app_glue') 533 build_support.make_package('native_app_glue', path, dist_dir) 534 535 536 def build_ndk_helper(_, dist_dir, __): 537 path = build_support.android_path( 538 'development/ndk/sources/android/ndk_helper') 539 build_support.make_package('ndk_helper', path, dist_dir) 540 541 542 def build_gtest(_, dist_dir, __): 543 path = build_support.ndk_path('sources/third_party/googletest') 544 build_support.make_package('gtest', path, dist_dir) 545 546 547 def build_build(_, dist_dir, __): 548 path = build_support.ndk_path('build') 549 build_support.make_package('build', path, dist_dir) 550 551 552 def build_python_packages(_, dist_dir, __): 553 # Stage the files in a temporary directory to make things easier. 554 temp_dir = tempfile.mkdtemp() 555 try: 556 path = os.path.join(temp_dir, 'python-packages') 557 shutil.copytree( 558 build_support.android_path('development/python-packages'), path) 559 build_support.make_package('python-packages', path, dist_dir) 560 finally: 561 shutil.rmtree(temp_dir) 562 563 564 def build_gabixx(_out_dir, dist_dir, _args): 565 print('Building gabi++...') 566 path = build_support.ndk_path('sources/cxx-stl/gabi++') 567 build_support.make_package('gabixx', path, dist_dir) 568 569 570 def build_system_stl(_out_dir, dist_dir, _args): 571 print('Building system-stl...') 572 path = build_support.ndk_path('sources/cxx-stl/system') 573 build_support.make_package('system-stl', path, dist_dir) 574 575 576 def build_libandroid_support(_out_dir, dist_dir, _args): 577 print('Building libandroid_support...') 578 path = build_support.ndk_path('sources/android/support') 579 build_support.make_package('libandroid_support', path, dist_dir) 580 581 582 def build_libcxxabi(_out_dir, dist_dir, _args): 583 print('Building libc++abi...') 584 path = build_support.ndk_path('sources/cxx-stl/llvm-libc++abi') 585 build_support.make_package('libcxxabi', path, dist_dir) 586 587 588 def main(): 589 parser = ArgParser() 590 args = parser.parse_args() 591 592 if args.module is None: 593 modules = ALL_MODULES 594 else: 595 modules = {args.module} 596 597 if args.host_only: 598 modules = { 599 'clang', 600 'gcc', 601 'host-tools', 602 } 603 604 required_package_modules = ALL_MODULES 605 if args.package and required_package_modules <= modules: 606 do_package = True 607 else: 608 do_package = False 609 610 # TODO(danalbert): wine? 611 # We're building the Windows packages from Linux, so we can't actually run 612 # any of the tests from here. 613 if args.system.startswith('windows') or not do_package: 614 args.test = False 615 616 # Disable buffering on stdout so the build output doesn't hide all of our 617 # "Building..." messages. 618 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 619 620 os.chdir(os.path.dirname(os.path.realpath(__file__))) 621 622 # Set ANDROID_BUILD_TOP. 623 if 'ANDROID_BUILD_TOP' not in os.environ: 624 os.environ['ANDROID_BUILD_TOP'] = os.path.realpath('..') 625 626 out_dir = build_support.get_out_dir() 627 dist_dir = build_support.get_dist_dir(out_dir) 628 629 print('Cleaning up...') 630 invoke_build('dev-cleanup.sh') 631 632 module_builds = collections.OrderedDict([ 633 ('binutils', build_binutils), 634 ('build', build_build), 635 ('clang', build_clang), 636 ('cpufeatures', build_cpufeatures), 637 ('gabi++', build_gabixx), 638 ('gcc', build_gcc), 639 ('gcclibs', build_gcc_libs), 640 ('gdbserver', build_gdbserver), 641 ('gnustl', build_gnustl), 642 ('gtest', build_gtest), 643 ('host-tools', build_host_tools), 644 ('libandroid_support', build_libandroid_support), 645 ('libc++', build_libcxx), 646 ('libc++abi', build_libcxxabi), 647 ('native_app_glue', build_native_app_glue), 648 ('ndk_helper', build_ndk_helper), 649 ('platforms', build_platforms), 650 ('python-packages', build_python_packages), 651 ('stlport', build_stlport), 652 ('system-stl', build_system_stl), 653 ]) 654 655 print('Building modules: {}'.format(' '.join(modules))) 656 for module in modules: 657 module_builds[module](out_dir, dist_dir, args) 658 659 if do_package: 660 package_ndk(out_dir, dist_dir, args) 661 662 if args.test: 663 result = test_ndk(out_dir, args) 664 sys.exit(0 if result else 1) 665 666 667 if __name__ == '__main__': 668 main() 669