mirror of
https://github.com/dart-lang/sdk
synced 2024-11-05 18:22:09 +00:00
26f50368d2
Not checking Windows, where the set of linker outputs is not constant. Change-Id: I1241aa4108f7feebc2638ca762743464fcb48a52 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201165 Reviewed-by: Daco Harkes <dacoharkes@google.com> Reviewed-by: Tess Strickland <sstrickl@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
308 lines
10 KiB
Python
Executable file
308 lines
10 KiB
Python
Executable file
#!/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')
|
|
other_group.add_argument(
|
|
"--check-clean",
|
|
help="Check that a second invocation of Ninja has nothing to do",
|
|
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 CheckCleanBuild(build_config, args, env):
|
|
args = args + ['-n', '-d', 'explain']
|
|
print(' '.join(args))
|
|
process = subprocess.Popen(args,
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
stdin=None)
|
|
out, err = process.communicate()
|
|
process.wait()
|
|
if process.returncode != 0:
|
|
return 1
|
|
if 'ninja: no work to do' not in out.decode('utf-8'):
|
|
print(err.decode('utf-8'))
|
|
return 1
|
|
|
|
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
|
|
|
|
if options.check_clean:
|
|
for (build_config, args, goma) in configs:
|
|
if CheckCleanBuild(build_config, args, env=env) != 0:
|
|
return 1
|
|
|
|
endtime = time.time()
|
|
print("The build took %.3f seconds" % (endtime - starttime))
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(Main())
|