blob: 237c48677cdcb146b5b288492a82791e38bb4a53 [file] [log] [blame]
#!/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', 'simarmv6', 'armv6',
'simarmv5te', 'armv5te', 'simarm64', 'arm64',
'simdbc', 'simdbc64', 'armsimdbc', 'armsimdbc64']
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("-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,simdbc64'
if options.mode == 'all':
options.mode = 'debug,release,product'
if options.os == 'all':
options.os = 'host,android'
options.mode = options.mode.split(',')
options.arch = options.arch.split(',')
options.os = options.os.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', 'armv6', 'armv5te', 'arm64',
'simdbc', 'simdbc64']:
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):
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',
]
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):
build_config = utils.GetBuildConf(mode, arch, target_os)
out_dir = utils.GetBuildRoot(HOST_OS, mode, arch, target_os)
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)
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:
configs.append(BuildOneConfig(options, targets, target_os, mode, arch))
# 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())