| #!/usr/bin/env python3 | 
 | # | 
 | # 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 argparse | 
 | import io | 
 | import json | 
 | import os | 
 | import subprocess | 
 | import sys | 
 | import time | 
 | import utils | 
 |  | 
 | import gn as gn_py | 
 |  | 
 | 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 = utils.ARCH_FAMILY.keys() | 
 |  | 
 | usage = """\ | 
 | usage: %%prog [options] [targets] | 
 |  | 
 | This script invokes ninja to build Dart. | 
 | """ | 
 |  | 
 | def BuildOptions(): | 
 |     parser = argparse.ArgumentParser( | 
 |         description='Runs GN (if necessary) followed by ninja', | 
 |         formatter_class=argparse.ArgumentDefaultsHelpFormatter) | 
 |  | 
 |     config_group = parser.add_argument_group('Configuration Related Arguments') | 
 |     gn_py.AddCommonConfigurationArgs(config_group) | 
 |  | 
 |     gn_group = parser.add_argument_group('GN Related Arguments') | 
 |     gn_py.AddCommonGnOptionArgs(gn_group) | 
 |  | 
 |     other_group = parser.add_argument_group('Other Arguments') | 
 |     gn_py.AddOtherArgs(other_group) | 
 |  | 
 |     other_group.add_argument("-j", | 
 |                              type=int, | 
 |                              help='Ninja -j option for Goma builds.', | 
 |                              default=1000) | 
 |     other_group.add_argument("-l", | 
 |                              type=int, | 
 |                              help='Ninja -l option for Goma builds.', | 
 |                              default=64) | 
 |     other_group.add_argument("--no-start-goma", | 
 |                              help="Don't try to start goma", | 
 |                              default=False, | 
 |                              action='store_true') | 
 |  | 
 |     parser.add_argument('build_targets', nargs='*') | 
 |  | 
 |     return parser | 
 |  | 
 |  | 
 | 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 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 = [ | 
 |         'python3', | 
 |         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) | 
 |     out_dir = utils.GetBuildRoot(HOST_OS, mode, arch, target_os, sanitizer) | 
 |     using_goma = False | 
 |     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, env): | 
 |     start_time = time.time() | 
 |     print(' '.join(args)) | 
 |     process = subprocess.Popen(args, env=env, 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 SanitizerEnvironmentVariables(): | 
 |     with io.open('tools/bots/test_matrix.json', encoding='utf-8') as fd: | 
 |         config = json.loads(fd.read()) | 
 |         env = dict() | 
 |         for k, v in config['sanitizer_options'].items(): | 
 |             env[str(k)] = str(v) | 
 |         symbolizer_path = config['sanitizer_symbolizer'].get(HOST_OS, None) | 
 |         if symbolizer_path: | 
 |             symbolizer_path = str(os.path.join(DART_ROOT, symbolizer_path)) | 
 |             env['ASAN_SYMBOLIZER_PATH'] = symbolizer_path | 
 |             env['LSAN_SYMBOLIZER_PATH'] = symbolizer_path | 
 |             env['MSAN_SYMBOLIZER_PATH'] = symbolizer_path | 
 |             env['TSAN_SYMBOLIZER_PATH'] = symbolizer_path | 
 |             env['UBSAN_SYMBOLIZER_PATH'] = symbolizer_path | 
 |         return env | 
 |  | 
 |  | 
 | def Main(): | 
 |     starttime = time.time() | 
 |     # Parse the options. | 
 |     parser = BuildOptions() | 
 |     options = parser.parse_args() | 
 |  | 
 |     targets = options.build_targets | 
 |     if len(targets) == 0: | 
 |         targets = ['all'] | 
 |  | 
 |     if not gn_py.ProcessOptions(options): | 
 |         parser.print_help() | 
 |         return 1 | 
 |  | 
 |     # If binaries are built with sanitizers we should use those flags. | 
 |     # If the binaries are not built with sanitizers the flag should have no | 
 |     # effect. | 
 |     env = dict(os.environ) | 
 |     env.update(SanitizerEnvironmentVariables()) | 
 |  | 
 |     # Always run GN before building. | 
 |     gn_py.RunGnOnConfiguredConfigurations(options) | 
 |  | 
 |     # 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([env, args]) | 
 |         elif RunOneBuildCommand(build_config, args, env=env) != 0: | 
 |             return 1 | 
 |  | 
 |     # Run goma builds in parallel. | 
 |     active_goma_builds = [] | 
 |     for (env, args) in goma_builds: | 
 |         print(' '.join(args)) | 
 |         process = subprocess.Popen(args, env=env) | 
 |         active_goma_builds.append([args, process]) | 
 |     while active_goma_builds: | 
 |         time.sleep(0.1) | 
 |         for goma_build in active_goma_builds: | 
 |             (args, process) = goma_build | 
 |             if process.poll() is not None: | 
 |                 print(' '.join(args) + " done.") | 
 |                 active_goma_builds.remove(goma_build) | 
 |                 if process.returncode != 0: | 
 |                     for (_, to_kill) in active_goma_builds: | 
 |                         to_kill.terminate() | 
 |                     return 1 | 
 |  | 
 |     endtime = time.time() | 
 |     print("The build took %.3f seconds" % (endtime - starttime)) | 
 |     return 0 | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     sys.exit(Main()) |