|  | #!/usr/bin/env python | 
|  | # | 
|  | # Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file | 
|  | # for details. All rights reserved. Use of this source code is governed by a | 
|  | # BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import multiprocessing | 
|  | import optparse | 
|  | import os | 
|  | import subprocess | 
|  | import sys | 
|  | import time | 
|  | import utils | 
|  |  | 
|  | HOST_OS = utils.GuessOS() | 
|  | HOST_CPUS = utils.GuessCpus() | 
|  | SCRIPT_DIR = os.path.dirname(sys.argv[0]) | 
|  | DART_ROOT = os.path.realpath(os.path.join(SCRIPT_DIR, '..')) | 
|  | AVAILABLE_ARCHS = [ | 
|  | 'ia32', 'x64', 'simarm', 'arm', 'arm_x64', 'simarmv6', 'armv6', 'simarm64', | 
|  | 'arm64', 'simarm_x64' | 
|  | ] | 
|  |  | 
|  | usage = """\ | 
|  | usage: %%prog [options] [targets] | 
|  |  | 
|  | This script invokes ninja to build Dart. | 
|  | """ | 
|  |  | 
|  |  | 
|  | def BuildOptions(): | 
|  | result = optparse.OptionParser(usage=usage) | 
|  | result.add_option( | 
|  | "-a", | 
|  | "--arch", | 
|  | help='Target architectures (comma-separated).', | 
|  | metavar='[all,' + ','.join(AVAILABLE_ARCHS) + ']', | 
|  | default=utils.GuessArchitecture()) | 
|  | result.add_option( | 
|  | "-b", | 
|  | "--bytecode", | 
|  | help='Build with the kernel bytecode interpreter. DEPRECATED.', | 
|  | default=False, | 
|  | action='store_true') | 
|  | result.add_option( | 
|  | "-j", type=int, help='Ninja -j option for Goma builds.', default=1000) | 
|  | result.add_option( | 
|  | "-l", type=int, help='Ninja -l option for Goma builds.', default=64) | 
|  | result.add_option( | 
|  | "-m", | 
|  | "--mode", | 
|  | help='Build variants (comma-separated).', | 
|  | metavar='[all,debug,release,product]', | 
|  | default='debug') | 
|  | result.add_option( | 
|  | "--no-start-goma", | 
|  | help="Don't try to start goma", | 
|  | default=False, | 
|  | action='store_true') | 
|  | result.add_option( | 
|  | "--os", | 
|  | help='Target OSs (comma-separated).', | 
|  | metavar='[all,host,android]', | 
|  | default='host') | 
|  | result.add_option( | 
|  | "--sanitizer", | 
|  | type=str, | 
|  | help='Build variants (comma-separated).', | 
|  | metavar='[all,none,asan,lsan,msan,tsan,ubsan]', | 
|  | default='none') | 
|  | # TODO(38701): Remove this and everything that references it once the | 
|  | # forked NNBD SDK is merged back in. | 
|  | result.add_option( | 
|  | "--nnbd", | 
|  | help='Use the NNBD fork of the SDK.', | 
|  | default=False, | 
|  | action='store_true') | 
|  | result.add_option( | 
|  | "-v", | 
|  | "--verbose", | 
|  | help='Verbose output.', | 
|  | default=False, | 
|  | action="store_true") | 
|  | return result | 
|  |  | 
|  |  | 
|  | def ProcessOsOption(os_name): | 
|  | if os_name == 'host': | 
|  | return HOST_OS | 
|  | return os_name | 
|  |  | 
|  |  | 
|  | def ProcessOptions(options, args): | 
|  | if options.arch == 'all': | 
|  | options.arch = 'ia32,x64,simarm,simarm64' | 
|  | if options.mode == 'all': | 
|  | options.mode = 'debug,release,product' | 
|  | if options.os == 'all': | 
|  | options.os = 'host,android' | 
|  | if options.sanitizer == 'all': | 
|  | options.sanitizer = 'none,asan,lsan,msan,tsan,ubsan' | 
|  | options.mode = options.mode.split(',') | 
|  | options.arch = options.arch.split(',') | 
|  | options.os = options.os.split(',') | 
|  | options.sanitizer = options.sanitizer.split(',') | 
|  | for mode in options.mode: | 
|  | if not mode in ['debug', 'release', 'product']: | 
|  | print("Unknown mode %s" % mode) | 
|  | return False | 
|  | for arch in options.arch: | 
|  | if not arch in AVAILABLE_ARCHS: | 
|  | print("Unknown arch %s" % arch) | 
|  | return False | 
|  | options.os = [ProcessOsOption(os_name) for os_name in options.os] | 
|  | for os_name in options.os: | 
|  | if not os_name in ['android', 'freebsd', 'linux', 'macos', 'win32']: | 
|  | print("Unknown os %s" % os_name) | 
|  | return False | 
|  | if os_name != HOST_OS: | 
|  | if os_name != 'android': | 
|  | print("Unsupported target os %s" % os_name) | 
|  | return False | 
|  | if not HOST_OS in ['linux', 'macos']: | 
|  | print("Cross-compilation to %s is not supported on host os %s." | 
|  | % (os_name, HOST_OS)) | 
|  | return False | 
|  | if not arch in [ | 
|  | 'ia32', 'x64', 'arm', 'arm_x64', 'armv6', 'arm64' | 
|  | ]: | 
|  | print( | 
|  | "Cross-compilation to %s is not supported for architecture %s." | 
|  | % (os_name, arch)) | 
|  | return False | 
|  | # We have not yet tweaked the v8 dart build to work with the Android | 
|  | # NDK/SDK, so don't try to build it. | 
|  | if not args: | 
|  | print( | 
|  | "For android builds you must specify a target, such as 'runtime'." | 
|  | ) | 
|  | return False | 
|  | return True | 
|  |  | 
|  |  | 
|  | def NotifyBuildDone(build_config, success, start): | 
|  | if not success: | 
|  | print("BUILD FAILED") | 
|  |  | 
|  | sys.stdout.flush() | 
|  |  | 
|  | # Display a notification if build time exceeded DART_BUILD_NOTIFICATION_DELAY. | 
|  | notification_delay = float( | 
|  | os.getenv('DART_BUILD_NOTIFICATION_DELAY', sys.float_info.max)) | 
|  | if (time.time() - start) < notification_delay: | 
|  | return | 
|  |  | 
|  | if success: | 
|  | message = 'Build succeeded.' | 
|  | else: | 
|  | message = 'Build failed.' | 
|  | title = build_config | 
|  |  | 
|  | command = None | 
|  | if HOST_OS == 'macos': | 
|  | # Use AppleScript to display a UI non-modal notification. | 
|  | script = 'display notification  "%s" with title "%s" sound name "Glass"' % ( | 
|  | message, title) | 
|  | command = "osascript -e '%s' &" % script | 
|  | elif HOST_OS == 'linux': | 
|  | if success: | 
|  | icon = 'dialog-information' | 
|  | else: | 
|  | icon = 'dialog-error' | 
|  | command = "notify-send -i '%s' '%s' '%s' &" % (icon, message, title) | 
|  | elif HOST_OS == 'win32': | 
|  | if success: | 
|  | icon = 'info' | 
|  | else: | 
|  | icon = 'error' | 
|  | command = ( | 
|  | "powershell -command \"" | 
|  | "[reflection.assembly]::loadwithpartialname('System.Windows.Forms')" | 
|  | "| Out-Null;" | 
|  | "[reflection.assembly]::loadwithpartialname('System.Drawing')" | 
|  | "| Out-Null;" | 
|  | "$n = new-object system.windows.forms.notifyicon;" | 
|  | "$n.icon = [system.drawing.systemicons]::information;" | 
|  | "$n.visible = $true;" | 
|  | "$n.showballoontip(%d, '%s', '%s', " | 
|  | "[system.windows.forms.tooltipicon]::%s);\"") % ( | 
|  | 5000,  # Notification stays on for this many milliseconds | 
|  | message, | 
|  | title, | 
|  | icon) | 
|  |  | 
|  | if command: | 
|  | # Ignore return code, if this command fails, it doesn't matter. | 
|  | os.system(command) | 
|  |  | 
|  |  | 
|  | def GenerateBuildfilesIfNeeded(): | 
|  | if os.path.exists(utils.GetBuildDir(HOST_OS)): | 
|  | return True | 
|  | command = [ | 
|  | 'python', | 
|  | os.path.join(DART_ROOT, 'tools', 'generate_buildfiles.py') | 
|  | ] | 
|  | print("Running " + ' '.join(command)) | 
|  | process = subprocess.Popen(command) | 
|  | process.wait() | 
|  | if process.returncode != 0: | 
|  | print("Tried to generate missing buildfiles, but failed. " | 
|  | "Try running manually:\n\t$ " + ' '.join(command)) | 
|  | return False | 
|  | return True | 
|  |  | 
|  |  | 
|  | def RunGNIfNeeded(out_dir, target_os, mode, arch, use_nnbd): | 
|  | if os.path.isfile(os.path.join(out_dir, 'args.gn')): | 
|  | return | 
|  | gn_os = 'host' if target_os == HOST_OS else target_os | 
|  | gn_command = [ | 
|  | 'python', | 
|  | os.path.join(DART_ROOT, 'tools', 'gn.py'), | 
|  | '-m', | 
|  | mode, | 
|  | '-a', | 
|  | arch, | 
|  | '--os', | 
|  | gn_os, | 
|  | '-v', | 
|  | ] | 
|  | if use_nnbd: | 
|  | gn_command.append('--nnbd') | 
|  |  | 
|  | process = subprocess.Popen(gn_command) | 
|  | process.wait() | 
|  | if process.returncode != 0: | 
|  | print("Tried to run GN, but it failed. Try running it manually: \n\t$ " | 
|  | + ' '.join(gn_command)) | 
|  |  | 
|  |  | 
|  | def UseGoma(out_dir): | 
|  | args_gn = os.path.join(out_dir, 'args.gn') | 
|  | return 'use_goma = true' in open(args_gn, 'r').read() | 
|  |  | 
|  |  | 
|  | # Try to start goma, but don't bail out if we can't. Instead print an error | 
|  | # message, and let the build fail with its own error messages as well. | 
|  | goma_started = False | 
|  |  | 
|  |  | 
|  | def EnsureGomaStarted(out_dir): | 
|  | global goma_started | 
|  | if goma_started: | 
|  | return True | 
|  | args_gn_path = os.path.join(out_dir, 'args.gn') | 
|  | goma_dir = None | 
|  | with open(args_gn_path, 'r') as fp: | 
|  | for line in fp: | 
|  | if 'goma_dir' in line: | 
|  | words = line.split() | 
|  | goma_dir = words[2][1:-1]  # goma_dir = "/path/to/goma" | 
|  | if not goma_dir: | 
|  | print('Could not find goma for ' + out_dir) | 
|  | return False | 
|  | if not os.path.exists(goma_dir) or not os.path.isdir(goma_dir): | 
|  | print('Could not find goma at ' + goma_dir) | 
|  | return False | 
|  | goma_ctl = os.path.join(goma_dir, 'goma_ctl.py') | 
|  | goma_ctl_command = [ | 
|  | 'python', | 
|  | goma_ctl, | 
|  | 'ensure_start', | 
|  | ] | 
|  | process = subprocess.Popen(goma_ctl_command) | 
|  | process.wait() | 
|  | if process.returncode != 0: | 
|  | print( | 
|  | "Tried to run goma_ctl.py, but it failed. Try running it manually: " | 
|  | + "\n\t" + ' '.join(goma_ctl_command)) | 
|  | return False | 
|  | goma_started = True | 
|  | return True | 
|  |  | 
|  | # Returns a tuple (build_config, command to run, whether goma is used) | 
|  | def BuildOneConfig(options, targets, target_os, mode, arch, sanitizer): | 
|  | build_config = utils.GetBuildConf(mode, arch, target_os, sanitizer, | 
|  | options.nnbd) | 
|  | out_dir = utils.GetBuildRoot(HOST_OS, mode, arch, target_os, sanitizer, | 
|  | options.nnbd) | 
|  | using_goma = False | 
|  | # TODO(zra): Remove auto-run of gn, replace with prompt for user to run | 
|  | # gn.py manually. | 
|  | RunGNIfNeeded(out_dir, target_os, mode, arch, options.nnbd) | 
|  | command = ['ninja', '-C', out_dir] | 
|  | if options.verbose: | 
|  | command += ['-v'] | 
|  | if UseGoma(out_dir): | 
|  | if options.no_start_goma or EnsureGomaStarted(out_dir): | 
|  | using_goma = True | 
|  | command += [('-j%s' % str(options.j))] | 
|  | command += [('-l%s' % str(options.l))] | 
|  | else: | 
|  | # If we couldn't ensure that goma is started, let the build start, but | 
|  | # slowly so we can see any helpful error messages that pop out. | 
|  | command += ['-j1'] | 
|  | command += targets | 
|  | return (build_config, command, using_goma) | 
|  |  | 
|  |  | 
|  | def RunOneBuildCommand(build_config, args): | 
|  | start_time = time.time() | 
|  | print(' '.join(args)) | 
|  | process = subprocess.Popen(args, stdin=None) | 
|  | process.wait() | 
|  | if process.returncode != 0: | 
|  | NotifyBuildDone(build_config, success=False, start=start_time) | 
|  | return 1 | 
|  | else: | 
|  | NotifyBuildDone(build_config, success=True, start=start_time) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | def RunOneGomaBuildCommand(args): | 
|  | try: | 
|  | print(' '.join(args)) | 
|  | process = subprocess.Popen(args, stdin=None) | 
|  | process.wait() | 
|  | print(' '.join(args) + " done.") | 
|  | return process.returncode | 
|  | except KeyboardInterrupt: | 
|  | return 1 | 
|  |  | 
|  |  | 
|  | def Main(): | 
|  | starttime = time.time() | 
|  | # Parse the options. | 
|  | parser = BuildOptions() | 
|  | (options, args) = parser.parse_args() | 
|  | if not ProcessOptions(options, args): | 
|  | parser.print_help() | 
|  | return 1 | 
|  | # Determine which targets to build. By default we build the "all" target. | 
|  | if len(args) == 0: | 
|  | targets = ['all'] | 
|  | else: | 
|  | targets = args | 
|  |  | 
|  | if not GenerateBuildfilesIfNeeded(): | 
|  | return 1 | 
|  |  | 
|  | # Build all targets for each requested configuration. | 
|  | configs = [] | 
|  | for target_os in options.os: | 
|  | for mode in options.mode: | 
|  | for arch in options.arch: | 
|  | for sanitizer in options.sanitizer: | 
|  | configs.append( | 
|  | BuildOneConfig(options, targets, target_os, mode, arch, | 
|  | sanitizer)) | 
|  |  | 
|  | # Build regular configs. | 
|  | goma_builds = [] | 
|  | for (build_config, args, goma) in configs: | 
|  | if args is None: | 
|  | return 1 | 
|  | if goma: | 
|  | goma_builds.append(args) | 
|  | elif RunOneBuildCommand(build_config, args) != 0: | 
|  | return 1 | 
|  |  | 
|  | # Run goma builds in parallel. | 
|  | pool = multiprocessing.Pool(multiprocessing.cpu_count()) | 
|  | results = pool.map(RunOneGomaBuildCommand, goma_builds, chunksize=1) | 
|  | for r in results: | 
|  | if r != 0: | 
|  | return 1 | 
|  |  | 
|  | endtime = time.time() | 
|  | print("The build took %.3f seconds" % (endtime - starttime)) | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(Main()) |