Index: tools/ninja.py |
diff --git a/tools/ninja.py b/tools/ninja.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..bf89dfc55a6177eecb6d22cc7c93962cde086e30 |
--- /dev/null |
+++ b/tools/ninja.py |
@@ -0,0 +1,314 @@ |
+#!/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, '..')) |
+ |
+usage = """\ |
+usage: %%prog [options] [targets] |
+ |
+This script runs 'make' in the *current* directory. So, run it from |
+the Dart repo root, |
+ |
+ %s , |
+ |
+unless you really intend to use a non-default Makefile.""" % DART_ROOT |
+ |
+ |
+def BuildOptions(): |
+ result = optparse.OptionParser(usage=usage) |
+ result.add_option("-m", "--mode", |
+ help='Build variants (comma-separated).', |
+ metavar='[all,debug,release,product]', |
+ default='debug') |
+ result.add_option("-v", "--verbose", |
+ help='Verbose output.', |
+ default=False, action="store_true") |
+ result.add_option("-a", "--arch", |
+ help='Target architectures (comma-separated).', |
+ metavar='[all,ia32,x64,simarm,arm,simarmv6,armv6,simarmv5te,armv5te,' |
+ 'simmips,mips,simarm64,arm64,simdbc,armsimdbc]', |
+ default=utils.GuessArchitecture()) |
+ result.add_option("--os", |
+ help='Target OSs (comma-separated).', |
+ metavar='[all,host,android]', |
+ default='host') |
+ result.add_option("-j", |
+ type=int, |
+ help='Ninja -j option for Goma builds.', |
+ metavar=1000, |
+ default=1000) |
+ 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,simmips,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: |
+ archs = ['ia32', 'x64', 'simarm', 'arm', 'simarmv6', 'armv6', |
+ 'simarmv5te', 'armv5te', 'simmips', 'mips', 'simarm64', 'arm64', |
+ 'simdbc', 'simdbc64', 'armsimdbc', 'armsimdbc64'] |
+ if not arch in 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']: |
+ 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', 'mips', |
+ '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 RunGN(target_os, mode, arch): |
+ 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 ShouldRunGN(out_dir): |
+ return (not os.path.exists(out_dir) or |
+ not os.path.isfile(os.path.join(out_dir, 'args.gn'))) |
+ |
+ |
+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 |
+ if ShouldRunGN(out_dir): |
+ RunGN(target_os, mode, arch) |
+ command = ['ninja', '-C', out_dir] |
+ if options.verbose: |
+ command += ['-v'] |
+ if UseGoma(out_dir): |
+ if EnsureGomaStarted(out_dir): |
+ using_goma = True |
+ command += [('-j%s' % str(options.j))] |
+ 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): |
+ print ' '.join(args) |
+ process = subprocess.Popen(args, stdin=None) |
+ process.wait() |
+ print (' '.join(args) + " done.") |
+ return process.returncode |
+ |
+ |
+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 |
+ |
+ # 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()) |