OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # |
| 3 # Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 4 # for details. All rights reserved. Use of this source code is governed by a |
| 5 # BSD-style license that can be found in the LICENSE file. |
| 6 |
| 7 import multiprocessing |
| 8 import optparse |
| 9 import os |
| 10 import subprocess |
| 11 import sys |
| 12 import time |
| 13 import utils |
| 14 |
| 15 HOST_OS = utils.GuessOS() |
| 16 HOST_CPUS = utils.GuessCpus() |
| 17 SCRIPT_DIR = os.path.dirname(sys.argv[0]) |
| 18 DART_ROOT = os.path.realpath(os.path.join(SCRIPT_DIR, '..')) |
| 19 |
| 20 usage = """\ |
| 21 usage: %%prog [options] [targets] |
| 22 |
| 23 This script runs 'make' in the *current* directory. So, run it from |
| 24 the Dart repo root, |
| 25 |
| 26 %s , |
| 27 |
| 28 unless you really intend to use a non-default Makefile.""" % DART_ROOT |
| 29 |
| 30 |
| 31 def BuildOptions(): |
| 32 result = optparse.OptionParser(usage=usage) |
| 33 result.add_option("-m", "--mode", |
| 34 help='Build variants (comma-separated).', |
| 35 metavar='[all,debug,release,product]', |
| 36 default='debug') |
| 37 result.add_option("-v", "--verbose", |
| 38 help='Verbose output.', |
| 39 default=False, action="store_true") |
| 40 result.add_option("-a", "--arch", |
| 41 help='Target architectures (comma-separated).', |
| 42 metavar='[all,ia32,x64,simarm,arm,simarmv6,armv6,simarmv5te,armv5te,' |
| 43 'simmips,mips,simarm64,arm64,simdbc,armsimdbc]', |
| 44 default=utils.GuessArchitecture()) |
| 45 result.add_option("--os", |
| 46 help='Target OSs (comma-separated).', |
| 47 metavar='[all,host,android]', |
| 48 default='host') |
| 49 result.add_option("-j", |
| 50 type=int, |
| 51 help='Ninja -j option for Goma builds.', |
| 52 metavar=1000, |
| 53 default=1000) |
| 54 return result |
| 55 |
| 56 |
| 57 def ProcessOsOption(os_name): |
| 58 if os_name == 'host': |
| 59 return HOST_OS |
| 60 return os_name |
| 61 |
| 62 |
| 63 def ProcessOptions(options, args): |
| 64 if options.arch == 'all': |
| 65 options.arch = 'ia32,x64,simarm,simarm64,simmips,simdbc64' |
| 66 if options.mode == 'all': |
| 67 options.mode = 'debug,release,product' |
| 68 if options.os == 'all': |
| 69 options.os = 'host,android' |
| 70 options.mode = options.mode.split(',') |
| 71 options.arch = options.arch.split(',') |
| 72 options.os = options.os.split(',') |
| 73 for mode in options.mode: |
| 74 if not mode in ['debug', 'release', 'product']: |
| 75 print "Unknown mode %s" % mode |
| 76 return False |
| 77 for arch in options.arch: |
| 78 archs = ['ia32', 'x64', 'simarm', 'arm', 'simarmv6', 'armv6', |
| 79 'simarmv5te', 'armv5te', 'simmips', 'mips', 'simarm64', 'arm64', |
| 80 'simdbc', 'simdbc64', 'armsimdbc', 'armsimdbc64'] |
| 81 if not arch in archs: |
| 82 print "Unknown arch %s" % arch |
| 83 return False |
| 84 options.os = [ProcessOsOption(os_name) for os_name in options.os] |
| 85 for os_name in options.os: |
| 86 if not os_name in ['android', 'freebsd', 'linux', 'macos', 'win32']: |
| 87 print "Unknown os %s" % os_name |
| 88 return False |
| 89 if os_name != HOST_OS: |
| 90 if os_name != 'android': |
| 91 print "Unsupported target os %s" % os_name |
| 92 return False |
| 93 if not HOST_OS in ['linux']: |
| 94 print ("Cross-compilation to %s is not supported on host os %s." |
| 95 % (os_name, HOST_OS)) |
| 96 return False |
| 97 if not arch in ['ia32', 'x64', 'arm', 'armv6', 'armv5te', 'arm64', 'mips', |
| 98 'simdbc', 'simdbc64']: |
| 99 print ("Cross-compilation to %s is not supported for architecture %s." |
| 100 % (os_name, arch)) |
| 101 return False |
| 102 # We have not yet tweaked the v8 dart build to work with the Android |
| 103 # NDK/SDK, so don't try to build it. |
| 104 if not args: |
| 105 print "For android builds you must specify a target, such as 'runtime'." |
| 106 return False |
| 107 return True |
| 108 |
| 109 |
| 110 def NotifyBuildDone(build_config, success, start): |
| 111 if not success: |
| 112 print "BUILD FAILED" |
| 113 |
| 114 sys.stdout.flush() |
| 115 |
| 116 # Display a notification if build time exceeded DART_BUILD_NOTIFICATION_DELAY. |
| 117 notification_delay = float( |
| 118 os.getenv('DART_BUILD_NOTIFICATION_DELAY', sys.float_info.max)) |
| 119 if (time.time() - start) < notification_delay: |
| 120 return |
| 121 |
| 122 if success: |
| 123 message = 'Build succeeded.' |
| 124 else: |
| 125 message = 'Build failed.' |
| 126 title = build_config |
| 127 |
| 128 command = None |
| 129 if HOST_OS == 'macos': |
| 130 # Use AppleScript to display a UI non-modal notification. |
| 131 script = 'display notification "%s" with title "%s" sound name "Glass"' % ( |
| 132 message, title) |
| 133 command = "osascript -e '%s' &" % script |
| 134 elif HOST_OS == 'linux': |
| 135 if success: |
| 136 icon = 'dialog-information' |
| 137 else: |
| 138 icon = 'dialog-error' |
| 139 command = "notify-send -i '%s' '%s' '%s' &" % (icon, message, title) |
| 140 elif HOST_OS == 'win32': |
| 141 if success: |
| 142 icon = 'info' |
| 143 else: |
| 144 icon = 'error' |
| 145 command = ("powershell -command \"" |
| 146 "[reflection.assembly]::loadwithpartialname('System.Windows.Forms')" |
| 147 "| Out-Null;" |
| 148 "[reflection.assembly]::loadwithpartialname('System.Drawing')" |
| 149 "| Out-Null;" |
| 150 "$n = new-object system.windows.forms.notifyicon;" |
| 151 "$n.icon = [system.drawing.systemicons]::information;" |
| 152 "$n.visible = $true;" |
| 153 "$n.showballoontip(%d, '%s', '%s', " |
| 154 "[system.windows.forms.tooltipicon]::%s);\"") % ( |
| 155 5000, # Notification stays on for this many milliseconds |
| 156 message, title, icon) |
| 157 |
| 158 if command: |
| 159 # Ignore return code, if this command fails, it doesn't matter. |
| 160 os.system(command) |
| 161 |
| 162 |
| 163 def RunGN(target_os, mode, arch): |
| 164 gn_os = 'host' if target_os == HOST_OS else target_os |
| 165 gn_command = [ |
| 166 'python', |
| 167 os.path.join(DART_ROOT, 'tools', 'gn.py'), |
| 168 '-m', mode, |
| 169 '-a', arch, |
| 170 '--os', gn_os, |
| 171 '-v', |
| 172 ] |
| 173 process = subprocess.Popen(gn_command) |
| 174 process.wait() |
| 175 if process.returncode != 0: |
| 176 print ("Tried to run GN, but it failed. Try running it manually: \n\t$ " + |
| 177 ' '.join(gn_command)) |
| 178 |
| 179 |
| 180 def ShouldRunGN(out_dir): |
| 181 return (not os.path.exists(out_dir) or |
| 182 not os.path.isfile(os.path.join(out_dir, 'args.gn'))) |
| 183 |
| 184 |
| 185 def UseGoma(out_dir): |
| 186 args_gn = os.path.join(out_dir, 'args.gn') |
| 187 return 'use_goma = true' in open(args_gn, 'r').read() |
| 188 |
| 189 |
| 190 # Try to start goma, but don't bail out if we can't. Instead print an error |
| 191 # message, and let the build fail with its own error messages as well. |
| 192 goma_started = False |
| 193 def EnsureGomaStarted(out_dir): |
| 194 global goma_started |
| 195 if goma_started: |
| 196 return True |
| 197 args_gn_path = os.path.join(out_dir, 'args.gn') |
| 198 goma_dir = None |
| 199 with open(args_gn_path, 'r') as fp: |
| 200 for line in fp: |
| 201 if 'goma_dir' in line: |
| 202 words = line.split() |
| 203 goma_dir = words[2][1:-1] # goma_dir = "/path/to/goma" |
| 204 if not goma_dir: |
| 205 print 'Could not find goma for ' + out_dir |
| 206 return False |
| 207 if not os.path.exists(goma_dir) or not os.path.isdir(goma_dir): |
| 208 print 'Could not find goma at ' + goma_dir |
| 209 return False |
| 210 goma_ctl = os.path.join(goma_dir, 'goma_ctl.py') |
| 211 goma_ctl_command = [ |
| 212 'python', |
| 213 goma_ctl, |
| 214 'ensure_start', |
| 215 ] |
| 216 process = subprocess.Popen(goma_ctl_command) |
| 217 process.wait() |
| 218 if process.returncode != 0: |
| 219 print ("Tried to run goma_ctl.py, but it failed. Try running it manually: " |
| 220 + "\n\t" + ' '.join(goma_ctl_command)) |
| 221 return False |
| 222 goma_started = True |
| 223 return True |
| 224 |
| 225 |
| 226 # Returns a tuple (build_config, command to run, whether goma is used) |
| 227 def BuildOneConfig(options, targets, target_os, mode, arch): |
| 228 build_config = utils.GetBuildConf(mode, arch, target_os) |
| 229 out_dir = utils.GetBuildRoot(HOST_OS, mode, arch, target_os) |
| 230 using_goma = False |
| 231 if ShouldRunGN(out_dir): |
| 232 RunGN(target_os, mode, arch) |
| 233 command = ['ninja', '-C', out_dir] |
| 234 if options.verbose: |
| 235 command += ['-v'] |
| 236 if UseGoma(out_dir): |
| 237 if EnsureGomaStarted(out_dir): |
| 238 using_goma = True |
| 239 command += [('-j%s' % str(options.j))] |
| 240 else: |
| 241 # If we couldn't ensure that goma is started, let the build start, but |
| 242 # slowly so we can see any helpful error messages that pop out. |
| 243 command += ['-j1'] |
| 244 command += targets |
| 245 return (build_config, command, using_goma) |
| 246 |
| 247 |
| 248 def RunOneBuildCommand(build_config, args): |
| 249 start_time = time.time() |
| 250 print ' '.join(args) |
| 251 process = subprocess.Popen(args, stdin=None) |
| 252 process.wait() |
| 253 if process.returncode != 0: |
| 254 NotifyBuildDone(build_config, success=False, start=start_time) |
| 255 return 1 |
| 256 else: |
| 257 NotifyBuildDone(build_config, success=True, start=start_time) |
| 258 |
| 259 return 0 |
| 260 |
| 261 |
| 262 def RunOneGomaBuildCommand(args): |
| 263 print ' '.join(args) |
| 264 process = subprocess.Popen(args, stdin=None) |
| 265 process.wait() |
| 266 print (' '.join(args) + " done.") |
| 267 return process.returncode |
| 268 |
| 269 |
| 270 def Main(): |
| 271 starttime = time.time() |
| 272 # Parse the options. |
| 273 parser = BuildOptions() |
| 274 (options, args) = parser.parse_args() |
| 275 if not ProcessOptions(options, args): |
| 276 parser.print_help() |
| 277 return 1 |
| 278 # Determine which targets to build. By default we build the "all" target. |
| 279 if len(args) == 0: |
| 280 targets = ['all'] |
| 281 else: |
| 282 targets = args |
| 283 |
| 284 # Build all targets for each requested configuration. |
| 285 configs = [] |
| 286 for target_os in options.os: |
| 287 for mode in options.mode: |
| 288 for arch in options.arch: |
| 289 configs.append(BuildOneConfig(options, targets, target_os, mode, arch)) |
| 290 |
| 291 # Build regular configs. |
| 292 goma_builds = [] |
| 293 for (build_config, args, goma) in configs: |
| 294 if args is None: |
| 295 return 1 |
| 296 if goma: |
| 297 goma_builds.append(args) |
| 298 elif RunOneBuildCommand(build_config, args) != 0: |
| 299 return 1 |
| 300 |
| 301 # Run goma builds in parallel. |
| 302 pool = multiprocessing.Pool(multiprocessing.cpu_count()) |
| 303 results = pool.map(RunOneGomaBuildCommand, goma_builds, chunksize=1) |
| 304 for r in results: |
| 305 if r != 0: |
| 306 return 1 |
| 307 |
| 308 endtime = time.time() |
| 309 print ("The build took %.3f seconds" % (endtime - starttime)) |
| 310 return 0 |
| 311 |
| 312 |
| 313 if __name__ == '__main__': |
| 314 sys.exit(Main()) |
OLD | NEW |