Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: tools/mb/mb.py

Issue 1370373005: Add MB commands for generating isolates and running them. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: merge mb_isolates forward, clean up a bit Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2015 The Chromium Authors. All rights reserved. 2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """MB - the Meta-Build wrapper around GYP and GN 6 """MB - the Meta-Build wrapper around GYP and GN
7 7
8 MB is a wrapper script for GYP and GN that can be used to generate build files 8 MB is a wrapper script for GYP and GN that can be used to generate build files
9 for sets of canned configurations and analyze them. 9 for sets of canned configurations and analyze them.
10 """ 10 """
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
91 subp = subps.add_parser('gen', 91 subp = subps.add_parser('gen',
92 help='generate a new set of build files') 92 help='generate a new set of build files')
93 AddCommonOptions(subp) 93 AddCommonOptions(subp)
94 subp.add_argument('--swarming-targets-file', 94 subp.add_argument('--swarming-targets-file',
95 help='save runtime dependencies for targets listed ' 95 help='save runtime dependencies for targets listed '
96 'in file.') 96 'in file.')
97 subp.add_argument('path', nargs=1, 97 subp.add_argument('path', nargs=1,
98 help='path to generate build into') 98 help='path to generate build into')
99 subp.set_defaults(func=self.CmdGen) 99 subp.set_defaults(func=self.CmdGen)
100 100
101 subp = subps.add_parser('isolate',
102 help='generate the .isolate files for a given'
103 'binary')
104 AddCommonOptions(subp)
105 subp.add_argument('path', nargs=1,
106 help='path build was generated into')
107 subp.add_argument('target', nargs=1,
108 help='ninja target to generate the isolate for')
109 subp.set_defaults(func=self.CmdIsolate)
110
101 subp = subps.add_parser('lookup', 111 subp = subps.add_parser('lookup',
102 help='look up the command for a given config or ' 112 help='look up the command for a given config or '
103 'builder') 113 'builder')
104 AddCommonOptions(subp) 114 AddCommonOptions(subp)
105 subp.set_defaults(func=self.CmdLookup) 115 subp.set_defaults(func=self.CmdLookup)
106 116
117 subp = subps.add_parser('run',
118 help='build and run the isolated version of a '
119 'binary')
120 AddCommonOptions(subp)
121 subp.add_argument('-j', '--jobs', dest='jobs', type=int,
122 help='Number of jobs to pass to ninja')
123 subp.add_argument('--no-build', dest='build', default=True,
124 action='store_false',
125 help='Do not build, just isolate and run')
126 subp.add_argument('path', nargs=1,
127 help='path to generate build into')
128 subp.add_argument('target', nargs=1,
129 help='ninja target to build and run')
130 subp.set_defaults(func=self.CmdRun)
131
107 subp = subps.add_parser('validate', 132 subp = subps.add_parser('validate',
108 help='validate the config file') 133 help='validate the config file')
109 subp.add_argument('-f', '--config-file', metavar='PATH', 134 subp.add_argument('-f', '--config-file', metavar='PATH',
110 default=self.default_config, 135 default=self.default_config,
111 help='path to config file ' 136 help='path to config file '
112 '(default is //tools/mb/mb_config.pyl)') 137 '(default is //tools/mb/mb_config.pyl)')
113 subp.set_defaults(func=self.CmdValidate) 138 subp.set_defaults(func=self.CmdValidate)
114 139
115 subp = subps.add_parser('help', 140 subp = subps.add_parser('help',
116 help='Get help on a subcommand.') 141 help='Get help on a subcommand.')
117 subp.add_argument(nargs='?', action='store', dest='subcommand', 142 subp.add_argument(nargs='?', action='store', dest='subcommand',
118 help='The command to get help for.') 143 help='The command to get help for.')
119 subp.set_defaults(func=self.CmdHelp) 144 subp.set_defaults(func=self.CmdHelp)
120 145
121 self.args = parser.parse_args(argv) 146 self.args = parser.parse_args(argv)
122 147
123 def CmdAnalyze(self): 148 def CmdAnalyze(self):
124 vals = self.GetConfig() 149 vals = self.Lookup()
125 if vals['type'] == 'gn': 150 if vals['type'] == 'gn':
126 return self.RunGNAnalyze(vals) 151 return self.RunGNAnalyze(vals)
127 elif vals['type'] == 'gyp': 152 else:
128 return self.RunGYPAnalyze(vals) 153 return self.RunGYPAnalyze(vals)
129 else:
130 raise MBErr('Unknown meta-build type "%s"' % vals['type'])
131 154
132 def CmdGen(self): 155 def CmdGen(self):
133 vals = self.GetConfig() 156 vals = self.Lookup()
134
135 self.ClobberIfNeeded(vals) 157 self.ClobberIfNeeded(vals)
136 158
137 if vals['type'] == 'gn': 159 if vals['type'] == 'gn':
138 return self.RunGNGen(vals) 160 return self.RunGNGen(vals)
139 if vals['type'] == 'gyp': 161 else:
140 return self.RunGYPGen(vals) 162 return self.RunGYPGen(vals)
141 163
142 raise MBErr('Unknown meta-build type "%s"' % vals['type'])
143
144 def CmdLookup(self):
145 vals = self.GetConfig()
146 if vals['type'] == 'gn':
147 cmd = self.GNCmd('gen', '_path_', vals['gn_args'])
148 env = None
149 elif vals['type'] == 'gyp':
150 cmd, env = self.GYPCmd('_path_', vals)
151 else:
152 raise MBErr('Unknown meta-build type "%s"' % vals['type'])
153
154 self.PrintCmd(cmd, env)
155 return 0
156
157 def CmdHelp(self): 164 def CmdHelp(self):
158 if self.args.subcommand: 165 if self.args.subcommand:
159 self.ParseArgs([self.args.subcommand, '--help']) 166 self.ParseArgs([self.args.subcommand, '--help'])
160 else: 167 else:
161 self.ParseArgs(['--help']) 168 self.ParseArgs(['--help'])
162 169
170 def CmdIsolate(self):
171 vals = self.GetConfig()
172 if vals['type'] == 'gn':
173 return self.RunGNIsolate(vals)
174 else:
175 return self.Build('%s_run' % self.args.target[0])
176
177 def CmdLookup(self):
178 vals = self.Lookup()
179 if vals['type'] == 'gn':
180 cmd = self.GNCmd('gen', '_path_', vals['gn_args'])
181 env = None
182 else:
183 cmd, env = self.GYPCmd('_path_', vals)
184
185 self.PrintCmd(cmd, env)
186 return 0
187
188 def CmdRun(self):
189 vals = self.GetConfig()
190 build_dir = self.args.path[0]
191 target = self.args.target[0]
192
193 if vals['type'] == 'gn':
194 ret = self.RunGNIsolate(vals)
195 if ret:
196 return ret
197 if self.args.build:
198 ret = self.Build(target)
199 if ret:
200 return ret
201 else:
202 ret = self.Build('%s_run' % target)
203 if ret:
204 return ret
205
206 ret, out, err = self.Run([
207 self.executable,
208 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
209 'run',
210 '-s',
211 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))])
Ken Russell (switch to Gerrit) 2015/10/02 18:08:19 Will this '/' cause problems on Windows? I'm guess
212
213 # Always log the output of the test run, but we only log the
214 # commend when self.args.verbose=True.
215 if not self.args.verbose:
216 if out:
217 self.Print(out)
218 if err:
219 self.Print(err, file=sys.stderr)
220
221 return ret
222
163 def CmdValidate(self): 223 def CmdValidate(self):
164 errs = [] 224 errs = []
165 225
166 # Read the file to make sure it parses. 226 # Read the file to make sure it parses.
167 self.ReadConfigFile() 227 self.ReadConfigFile()
168 228
169 # Figure out the whole list of configs and ensure that no config is 229 # Figure out the whole list of configs and ensure that no config is
170 # listed in more than one category. 230 # listed in more than one category.
171 all_configs = {} 231 all_configs = {}
172 for config in self.common_dev_configs: 232 for config in self.common_dev_configs:
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 errs.append('Unreferenced mixin "%s".' % mixin) 287 errs.append('Unreferenced mixin "%s".' % mixin)
228 288
229 if errs: 289 if errs:
230 raise MBErr(('mb config file %s has problems:' % self.args.config_file) + 290 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
231 '\n ' + '\n '.join(errs)) 291 '\n ' + '\n '.join(errs))
232 292
233 self.Print('mb config file %s looks ok.' % self.args.config_file) 293 self.Print('mb config file %s looks ok.' % self.args.config_file)
234 return 0 294 return 0
235 295
236 def GetConfig(self): 296 def GetConfig(self):
297 build_dir = self.args.path[0]
298
299 vals = {}
300 if self.args.builder or self.args.master or self.args.config:
301 vals = self.Lookup()
302 if vals['type'] == 'gn':
303 # Re-run gn gen in order to ensure the config is consistent with the
304 # build dir.
305 self.RunGNGen(vals)
306 return vals
307
308 # TODO: We can only get the config for GN build dirs, not GYP build dirs.
309 # GN stores the args that were used in args.gn in the build dir,
310 # but GYP doesn't store them anywhere. We should consider modifying
311 # gyp_chromium to record the arguments it runs with in a similar
312 # manner.
313
314 mb_type_path = self.PathJoin(self.ToAbsPath(build_dir), 'mb_type')
315 if not self.Exists(mb_type_path):
316 self.Print('Must either specify a path to an existing GN build dir '
317 'or pass in a -m/-b pair or a -c flag to specify the '
318 'configuration')
319 return 1
320
321 mb_type = self.ReadFile(mb_type_path).strip()
322 if mb_type == 'gn':
323 vals = self.GNValsFromDir(build_dir)
324 else:
325 vals = {}
326 vals['type'] = mb_type
327
328 return vals
329
330 def GNValsFromDir(self, build_dir):
331 args_contents = self.ReadFile(
332 self.PathJoin(self.ToAbsPath(build_dir), 'args.gn'))
333 gn_args = []
334 for l in args_contents.splitlines():
335 fields = l.split(' ')
336 name = fields[0]
337 val = ' '.join(fields[2:])
338 gn_args.append('%s=%s' % (name, val))
339
340 return {
341 'gn_args': ' '.join(gn_args),
342 'type': 'gn',
343 }
344
345 def Lookup(self):
237 self.ReadConfigFile() 346 self.ReadConfigFile()
238 config = self.ConfigFromArgs() 347 config = self.ConfigFromArgs()
239 if not config in self.configs: 348 if not config in self.configs:
240 raise MBErr('Config "%s" not found in %s' % 349 raise MBErr('Config "%s" not found in %s' %
241 (config, self.args.config_file)) 350 (config, self.args.config_file))
242 351
243 return self.FlattenConfig(config) 352 vals = self.FlattenConfig(config)
353
354 # Do some basic sanity checking on the config so that we
355 # don't have to do this in every caller.
356 assert 'type' in vals, 'No meta-build type specified in the config'
357 assert vals['type'] in ('gn', 'gyp'), (
358 'Unknown meta-build type "%s"' % vals['gn_args'])
359
360 return vals
244 361
245 def ReadConfigFile(self): 362 def ReadConfigFile(self):
246 if not self.Exists(self.args.config_file): 363 if not self.Exists(self.args.config_file):
247 raise MBErr('config file not found at %s' % self.args.config_file) 364 raise MBErr('config file not found at %s' % self.args.config_file)
248 365
249 try: 366 try:
250 contents = ast.literal_eval(self.ReadFile(self.args.config_file)) 367 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
251 except SyntaxError as e: 368 except SyntaxError as e:
252 raise MBErr('Failed to parse config file "%s": %s' % 369 raise MBErr('Failed to parse config file "%s": %s' %
253 (self.args.config_file, e)) 370 (self.args.config_file, e))
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
346 if self.args.dryrun: 463 if self.args.dryrun:
347 return 464 return
348 465
349 if needs_clobber: 466 if needs_clobber:
350 self.RemoveDirectory(build_dir) 467 self.RemoveDirectory(build_dir)
351 468
352 self.MaybeMakeDirectory(build_dir) 469 self.MaybeMakeDirectory(build_dir)
353 self.WriteFile(mb_type_path, new_mb_type) 470 self.WriteFile(mb_type_path, new_mb_type)
354 471
355 def RunGNGen(self, vals): 472 def RunGNGen(self, vals):
356 path = self.args.path[0] 473 build_dir = self.args.path[0]
357 474
358 cmd = self.GNCmd('gen', path, vals['gn_args'], extra_args=['--check']) 475 cmd = self.GNCmd('gen', build_dir, vals['gn_args'], extra_args=['--check'])
359 476
360 swarming_targets = [] 477 swarming_targets = []
361 if self.args.swarming_targets_file: 478 if self.args.swarming_targets_file:
362 # We need GN to generate the list of runtime dependencies for 479 # We need GN to generate the list of runtime dependencies for
363 # the compile targets listed (one per line) in the file so 480 # the compile targets listed (one per line) in the file so
364 # we can run them via swarming. We use ninja_to_gn.pyl to convert 481 # we can run them via swarming. We use ninja_to_gn.pyl to convert
365 # the compile targets to the matching GN labels. 482 # the compile targets to the matching GN labels.
366 contents = self.ReadFile(self.args.swarming_targets_file) 483 contents = self.ReadFile(self.args.swarming_targets_file)
367 swarming_targets = contents.splitlines() 484 swarming_targets = contents.splitlines()
368 gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin( 485 gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin(
369 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl'))) 486 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl')))
370 gn_labels = [] 487 gn_labels = []
371 for target in swarming_targets: 488 for target in swarming_targets:
372 if not target in gn_isolate_map: 489 if not target in gn_isolate_map:
373 raise MBErr('test target "%s" not found in %s' % 490 raise MBErr('test target "%s" not found in %s' %
374 (target, '//testing/buildbot/gn_isolate_map.pyl')) 491 (target, '//testing/buildbot/gn_isolate_map.pyl'))
375 gn_labels.append(gn_isolate_map[target]['label']) 492 gn_labels.append(gn_isolate_map[target]['label'])
376 493
377 gn_runtime_deps_path = self.ToAbsPath(path, 'runtime_deps') 494 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
378 495
379 # Since GN hasn't run yet, the build directory may not even exist. 496 # Since GN hasn't run yet, the build directory may not even exist.
380 self.MaybeMakeDirectory(self.ToAbsPath(path)) 497 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
381 498
382 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n') 499 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n')
383 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path) 500 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
384 501
385 ret, _, _ = self.Run(cmd) 502 ret, _, _ = self.Run(cmd)
386 if ret: 503 if ret:
387 # If `gn gen` failed, we should exit early rather than trying to 504 # If `gn gen` failed, we should exit early rather than trying to
388 # generate isolates. Run() will have already logged any error output. 505 # generate isolates. Run() will have already logged any error output.
389 self.Print('GN gen failed: %d' % ret) 506 self.Print('GN gen failed: %d' % ret)
390 return ret 507 return ret
391 508
392 for target in swarming_targets: 509 for target in swarming_targets:
393 if gn_isolate_map[target]['type'] == 'gpu_browser_test': 510 if gn_isolate_map[target]['type'] == 'gpu_browser_test':
394 runtime_deps_target = 'browser_tests' 511 runtime_deps_target = 'browser_tests'
395 elif gn_isolate_map[target]['type'] == 'script': 512 elif gn_isolate_map[target]['type'] == 'script':
396 # For script targets, the build target is usually a group, 513 # For script targets, the build target is usually a group,
397 # for which gn generates the runtime_deps next to the stamp file 514 # for which gn generates the runtime_deps next to the stamp file
398 # for the label, which lives under the obj/ directory. 515 # for the label, which lives under the obj/ directory.
399 label = gn_isolate_map[target]['label'] 516 label = gn_isolate_map[target]['label']
400 runtime_deps_target = 'obj/%s.stamp' % label.replace(':', '/') 517 runtime_deps_target = 'obj/%s.stamp' % label.replace(':', '/')
401 else: 518 else:
402 runtime_deps_target = target 519 runtime_deps_target = target
403 if self.platform == 'win32': 520 if self.platform == 'win32':
404 deps_path = self.ToAbsPath(path, 521 deps_path = self.ToAbsPath(build_dir,
405 runtime_deps_target + '.exe.runtime_deps') 522 runtime_deps_target + '.exe.runtime_deps')
406 else: 523 else:
407 deps_path = self.ToAbsPath(path, 524 deps_path = self.ToAbsPath(build_dir,
408 runtime_deps_target + '.runtime_deps') 525 runtime_deps_target + '.runtime_deps')
409 if not self.Exists(deps_path): 526 if not self.Exists(deps_path):
410 raise MBErr('did not generate %s' % deps_path) 527 raise MBErr('did not generate %s' % deps_path)
411 528
412 command, extra_files = self.GetIsolateCommand(target, vals, 529 command, extra_files = self.GetIsolateCommand(target, vals,
413 gn_isolate_map) 530 gn_isolate_map)
414 531
415 runtime_deps = self.ReadFile(deps_path).splitlines() 532 runtime_deps = self.ReadFile(deps_path).splitlines()
416 533
417 isolate_path = self.ToAbsPath(path, target + '.isolate') 534 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
418 self.WriteFile(isolate_path, 535 extra_files)
419 pprint.pformat({
420 'variables': {
421 'command': command,
422 'files': sorted(runtime_deps + extra_files),
423 }
424 }) + '\n')
425 536
426 self.WriteJSON( 537 return 0
427 { 538
428 'args': [ 539 def RunGNIsolate(self, vals):
429 '--isolated', 540 gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin(
430 self.ToSrcRelPath('%s%s%s.isolated' % (path, self.sep, target)), 541 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl')))
431 '--isolate', 542
432 self.ToSrcRelPath('%s%s%s.isolate' % (path, self.sep, target)), 543 build_dir = self.args.path[0]
433 ], 544 target = self.args.target[0]
434 'dir': self.chromium_src_dir, 545 command, extra_files = self.GetIsolateCommand(target, vals, gn_isolate_map)
435 'version': 1, 546
436 }, 547 label = gn_isolate_map[target]['label']
437 isolate_path + 'd.gen.json', 548 ret, out, _ = self.Call(['gn', 'desc', build_dir, label, 'runtime_deps'])
438 ) 549 if ret:
550 return ret
551
552 runtime_deps = out.splitlines()
553
554 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
555 extra_files)
556
557 ret, _, _ = self.Run([
558 self.executable,
559 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
560 'check',
561 '-i',
562 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
563 '-s',
564 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))])
439 565
440 return ret 566 return ret
441 567
568 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
569 extra_files):
570 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
571 self.WriteFile(isolate_path,
572 pprint.pformat({
573 'variables': {
574 'command': command,
575 'files': sorted(runtime_deps + extra_files),
576 }
577 }) + '\n')
578
579 self.WriteJSON(
580 {
581 'args': [
582 '--isolated',
583 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
584 '--isolate',
585 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
586 ],
587 'dir': self.chromium_src_dir,
588 'version': 1,
589 },
590 isolate_path + 'd.gen.json',
591 )
592
442 def GNCmd(self, subcommand, path, gn_args='', extra_args=None): 593 def GNCmd(self, subcommand, path, gn_args='', extra_args=None):
443 if self.platform == 'linux2': 594 if self.platform == 'linux2':
444 subdir = 'linux64' 595 subdir = 'linux64'
445 elif self.platform == 'darwin': 596 elif self.platform == 'darwin':
446 subdir = 'mac' 597 subdir = 'mac'
447 else: 598 else:
448 subdir = 'win' 599 subdir = 'win'
449 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, 'gn') 600 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, 'gn')
450 601
451 cmd = [gn_path, subcommand, path] 602 cmd = [gn_path, subcommand, path]
(...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after
762 cmd = ['python'] + cmd[1:] 913 cmd = ['python'] + cmd[1:]
763 self.Print(*[shell_quoter(arg) for arg in cmd]) 914 self.Print(*[shell_quoter(arg) for arg in cmd])
764 915
765 def PrintJSON(self, obj): 916 def PrintJSON(self, obj):
766 self.Print(json.dumps(obj, indent=2, sort_keys=True)) 917 self.Print(json.dumps(obj, indent=2, sort_keys=True))
767 918
768 def Print(self, *args, **kwargs): 919 def Print(self, *args, **kwargs):
769 # This function largely exists so it can be overridden for testing. 920 # This function largely exists so it can be overridden for testing.
770 print(*args, **kwargs) 921 print(*args, **kwargs)
771 922
923 def Build(self, target):
924 build_dir = self.ToSrcRelPath(self.args.path[0])
925 ninja_cmd = ['ninja', '-C', build_dir]
926 if self.args.jobs:
927 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
928 ninja_cmd.append(target)
929 return self.Run(ninja_cmd)
930
772 def Run(self, cmd, env=None, force_verbose=True): 931 def Run(self, cmd, env=None, force_verbose=True):
773 # This function largely exists so it can be overridden for testing. 932 # This function largely exists so it can be overridden for testing.
774 if self.args.dryrun or self.args.verbose or force_verbose: 933 if self.args.dryrun or self.args.verbose or force_verbose:
775 self.PrintCmd(cmd, env) 934 self.PrintCmd(cmd, env)
776 if self.args.dryrun: 935 if self.args.dryrun:
777 return 0, '', '' 936 return 0, '', ''
778 937
779 ret, out, err = self.Call(cmd, env=env) 938 ret, out, err = self.Call(cmd, env=env)
780 if self.args.verbose or force_verbose: 939 if self.args.verbose or force_verbose:
781 if out: 940 if out:
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
877 1036
878 if __name__ == '__main__': 1037 if __name__ == '__main__':
879 try: 1038 try:
880 sys.exit(main(sys.argv[1:])) 1039 sys.exit(main(sys.argv[1:]))
881 except MBErr as e: 1040 except MBErr as e:
882 print(e) 1041 print(e)
883 sys.exit(1) 1042 sys.exit(1)
884 except KeyboardInterrupt: 1043 except KeyboardInterrupt:
885 print("interrupted, exiting", stream=sys.stderr) 1044 print("interrupted, exiting", stream=sys.stderr)
886 sys.exit(130) 1045 sys.exit(130)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698