summaryrefslogtreecommitdiff
path: root/chromium/tools/mb/mb.py
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/tools/mb/mb.py')
-rwxr-xr-xchromium/tools/mb/mb.py259
1 files changed, 168 insertions, 91 deletions
diff --git a/chromium/tools/mb/mb.py b/chromium/tools/mb/mb.py
index f9db99a6e04..60520e87bad 100755
--- a/chromium/tools/mb/mb.py
+++ b/chromium/tools/mb/mb.py
@@ -93,7 +93,7 @@ class MetaBuildWrapper(object):
self.args = argparse.Namespace()
self.configs = {}
self.public_artifact_builders = None
- self.masters = {}
+ self.builder_groups = {}
self.mixins = {}
self.isolate_exe = 'isolate.exe' if self.platform.startswith(
'win') else 'isolate'
@@ -120,7 +120,8 @@ class MetaBuildWrapper(object):
def AddCommonOptions(subp):
group = subp.add_mutually_exclusive_group()
group.add_argument(
- '-m', '--master', help='master name to look up config from')
+ '-m', '--builder-group',
+ help='builder group name to look up config from')
subp.add_argument('-b', '--builder',
help='builder name to look up config from')
subp.add_argument('-c', '--config',
@@ -257,6 +258,16 @@ class MetaBuildWrapper(object):
'newline.')
subp.add_argument('--json-output',
help='Write errors to json.output')
+ # For more info about RTS, please see
+ # //docs/testing/regression-test-selection.md
+ subp.add_argument('--use-rts',
+ action='store_true',
+ default=False,
+ help='whether or not to use regression test selection')
+ subp.add_argument('--rts-target-change-recall',
+ type=float,
+ help='how much safety is needed when selecting tests. '
+ '0.0 is the lowest and 1.0 is the highest')
subp.add_argument('path',
help='path to generate build into')
subp.set_defaults(func=self.CmdGen)
@@ -435,8 +446,8 @@ class MetaBuildWrapper(object):
for f in self.ListDir(expectations_dir):
self.RemoveFile(os.path.join(expectations_dir, f))
obj = self._ToJsonish()
- for master, builder in sorted(obj.items()):
- expectation_file = os.path.join(expectations_dir, master + '.json')
+ for builder_group, builder in sorted(obj.items()):
+ expectation_file = os.path.join(expectations_dir, builder_group + '.json')
json_s = json.dumps(builder,
indent=2,
sort_keys=True,
@@ -444,7 +455,31 @@ class MetaBuildWrapper(object):
self.WriteFile(expectation_file, json_s)
return 0
+ def RtsSelect(self):
+ exe = self.PathJoin(self.chromium_src_dir, 'testing', 'rts', 'rts-chromium')
+ if self.platform == 'win32':
+ exe += '.exe'
+
+ args = [
+ exe, 'select',
+ '-model-dir', self.PathJoin(self.chromium_src_dir, 'testing', 'rts'), \
+ '-out', self.PathJoin(self.args.path, 'gen', 'rts'),
+ '-checkout', self.chromium_src_dir,
+ ]
+ if self.args.rts_target_change_recall:
+ if (self.args.rts_target_change_recall < 0
+ or self.args.rts_target_change_recall > 1):
+ self.WriteFailureAndRaise(
+ 'rts-target-change-recall must be between (0 and 1]', None)
+ args += ['-target-change-recall', str(self.args.rts_target_change_recall)]
+
+ _, _, err = self.Run(args, force_verbose=False)
+ if err:
+ self.WriteFailureAndRaise(err, None)
+
def CmdGen(self):
+ if self.args.use_rts:
+ self.RtsSelect()
vals = self.Lookup()
return self.RunGNGen(vals)
@@ -485,9 +520,7 @@ class MetaBuildWrapper(object):
else:
cmd = self.GNCmd('gen', '_path_')
self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
- env = None
-
- self.PrintCmd(cmd, env)
+ self.PrintCmd(cmd)
return 0
def CmdTry(self):
@@ -607,8 +640,7 @@ class MetaBuildWrapper(object):
self.RemoveDirectory(zip_dir)
def _RunUnderSwarming(self, build_dir, target, isolate_cmd):
- isolate_server = 'isolateserver.appspot.com'
- namespace = 'default-gzip'
+ cas_instance = 'chromium-swarm'
swarming_server = 'chromium-swarm.appspot.com'
# TODO(dpranke): Look up the information for the target in
# the //testing/buildbot.json file, if possible, so that we
@@ -618,7 +650,7 @@ class MetaBuildWrapper(object):
# TODO(dpranke): Also, add support for sharding and merging results.
dimensions = []
for k, v in self._DefaultDimensions() + self.args.dimensions:
- dimensions += ['-d', k, v]
+ dimensions += ['-d', '%s=%s' % (k, v)]
archive_json_path = self.ToSrcRelPath(
'%s/%s.archive.json' % (build_dir, target))
@@ -628,12 +660,8 @@ class MetaBuildWrapper(object):
'archive',
'-i',
self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
- '-s',
- self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
- '-I',
- isolate_server,
- '-namespace',
- namespace,
+ '-cas-instance',
+ cas_instance,
'-dump-json',
archive_json_path,
]
@@ -641,7 +669,7 @@ class MetaBuildWrapper(object):
# Talking to the isolateserver may fail because we're not logged in.
# We trap the command explicitly and rewrite the error output so that
# the error message is actually correct for a Chromium check out.
- self.PrintCmd(cmd, env=None)
+ self.PrintCmd(cmd)
ret, out, err = self.Run(cmd, force_verbose=False)
if ret:
self.Print(' -> returned %d' % ret)
@@ -664,7 +692,7 @@ class MetaBuildWrapper(object):
'Failed to read JSON file "%s"' % archive_json_path, file=sys.stderr)
return 1
try:
- isolated_hash = archive_hashes[target]
+ cas_digest = archive_hashes[target]
except Exception:
self.Print(
'Cannot find hash for "%s" in "%s", file content: %s' %
@@ -672,28 +700,52 @@ class MetaBuildWrapper(object):
file=sys.stderr)
return 1
- tags = ['--tags=%s' % tag for tag in self.args.tags]
+ tags = ['-tag=%s' % tag for tag in self.args.tags]
- cmd = [
- self.executable,
- self.PathJoin('tools', 'swarming_client', 'swarming.py'),
- 'run',
- '-s',
- isolated_hash,
- '-I',
- isolate_server,
- '--namespace',
- namespace,
- '-S',
- swarming_server,
- '--tags=purpose:user-debug-mb',
- '--relative-cwd',
- self.ToSrcRelPath(build_dir),
- ] + tags + dimensions + ['--raw-cmd', '--'] + list(isolate_cmd)
- if self.args.extra_args:
- cmd += self.args.extra_args
- self.Print('')
- ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
+ try:
+ json_dir = self.TempDir()
+ json_file = self.PathJoin(json_dir, 'task.json')
+ cmd = [
+ self.PathJoin('tools', 'luci-go', 'swarming'),
+ 'trigger',
+ '-digest',
+ cas_digest,
+ '-server',
+ swarming_server,
+ '-tag=purpose:user-debug-mb',
+ '-relative-cwd',
+ self.ToSrcRelPath(build_dir),
+ '-dump-json',
+ json_file,
+ ] + tags + dimensions + ['--'] + list(isolate_cmd)
+ if self.args.extra_args:
+ cmd += self.args.extra_args
+ self.Print('')
+ ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
+ if ret:
+ return ret
+ task_json = self.ReadFile(json_file)
+ task_id = json.loads(task_json)["tasks"][0]['task_id']
+ collect_output = self.PathJoin(json_dir, 'collect_output.json')
+ cmd = [
+ self.PathJoin('tools', 'luci-go', 'swarming'),
+ 'collect',
+ '-server',
+ swarming_server,
+ '-task-output-stdout=console',
+ '-task-summary-json',
+ collect_output,
+ task_id,
+ ]
+ ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
+ if ret != 0:
+ return ret
+ collect_json = json.loads(self.ReadFile(collect_output))
+ # The exit_code field is not included if the task was successful.
+ ret = collect_json.get(task_id, {}).get('results', {}).get('exit_code', 0)
+ finally:
+ if json_dir:
+ self.RemoveDirectory(json_dir)
return ret
def _RunLocallyIsolated(self, build_dir, target):
@@ -731,14 +783,14 @@ class MetaBuildWrapper(object):
"""Dumps the config file into a json-friendly expanded dict.
Returns:
- A dict with master -> builder -> all GN args mapping.
+ A dict with builder group -> builder -> all GN args mapping.
"""
self.ReadConfigFile(self.args.config_file)
obj = {}
- for master, builders in self.masters.items():
- obj[master] = {}
+ for builder_group, builders in self.builder_groups.items():
+ obj[builder_group] = {}
for builder in builders:
- config = self.masters[master][builder]
+ config = self.builder_groups[builder_group][builder]
if not config:
continue
if isinstance(config, dict):
@@ -757,7 +809,7 @@ class MetaBuildWrapper(object):
args = {'gn_args': gn_helpers.FromGNArgs(flattened_config['gn_args'])}
if flattened_config.get('args_file'):
args['args_file'] = flattened_config['args_file']
- obj[master][builder] = args
+ obj[builder_group][builder] = args
return obj
@@ -767,7 +819,7 @@ class MetaBuildWrapper(object):
self.ReadConfigFile(self.args.config_file)
# Build a list of all of the configs referenced by builders.
- all_configs = validation.GetAllConfigs(self.masters)
+ all_configs = validation.GetAllConfigs(self.builder_groups)
# Check that every referenced args file or config actually exists.
for config, loc in all_configs.items():
@@ -784,11 +836,11 @@ class MetaBuildWrapper(object):
self.configs, self.mixins)
if self.args.config_file == self.default_config:
- validation.EnsureNoProprietaryMixins(errs, self.masters, self.configs,
- self.mixins)
+ validation.EnsureNoProprietaryMixins(errs, self.builder_groups,
+ self.configs, self.mixins)
validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
- self.masters, FlattenConfig)
+ self.builder_groups, FlattenConfig)
if errs:
raise MBErr(('mb config file %s has problems:\n ' %
@@ -810,7 +862,7 @@ class MetaBuildWrapper(object):
build_dir = self.args.path
vals = DefaultVals()
- if self.args.builder or self.args.master or self.args.config:
+ if self.args.builder or self.args.builder_group or self.args.config:
vals = self.Lookup()
# Re-run gn gen in order to ensure the config is consistent with the
# build dir.
@@ -833,15 +885,34 @@ class MetaBuildWrapper(object):
gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
if self.Exists(gn_args_path):
args_contents = self.ReadFile(gn_args_path)
- gn_args = []
- for l in args_contents.splitlines():
- l = l.split('#', 2)[0].strip()
- if not l:
- continue
- (name, value) = l.split('=', 1)
- gn_args.append('%s=%s' % (name.strip(), value.strip()))
- return ' '.join(gn_args)
+ # Handle any .gni file imports, e.g. the ones used by CrOS. This should
+ # be automatically handled by gn_helpers.FromGNArgs (via its call to
+ # gn_helpers.GNValueParser.ReplaceImports), but that currently breaks
+ # mb_unittest since it mocks out file reads itself instead of using
+ # pyfakefs. This results in gn_helpers trying to read a non-existent file.
+ # The implementation of ReplaceImports here can be removed once the
+ # unittests use pyfakefs.
+ def ReplaceImports(input_contents):
+ output_contents = ''
+ for l in input_contents.splitlines(True):
+ if not l.strip().startswith('#') and 'import(' in l:
+ import_file = l.split('"', 2)[1]
+ import_file = self.ToAbsPath(import_file)
+ imported_contents = self.ReadFile(import_file)
+ output_contents += ReplaceImports(imported_contents) + '\n'
+ else:
+ output_contents += l
+ return output_contents
+
+ args_contents = ReplaceImports(args_contents)
+ args_dict = gn_helpers.FromGNArgs(args_contents)
+ # Re-add the quotes around strings so they show up as they would in the
+ # args.gn file.
+ for k, v in args_dict.items():
+ if isinstance(v, str):
+ args_dict[k] = '"%s"' % v
+ return ' '.join(['%s=%s' % (k, v) for (k, v) in args_dict.items()])
def Lookup(self):
self.ReadConfigFile(self.args.config_file)
@@ -883,10 +954,10 @@ class MetaBuildWrapper(object):
return vals
def ReadIOSBotConfig(self):
- if not self.args.master or not self.args.builder:
+ if not self.args.builder_group or not self.args.builder:
return {}
path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
- self.args.master, self.args.builder + '.json')
+ self.args.builder_group, self.args.builder + '.json')
if not self.Exists(path):
return {}
@@ -908,7 +979,7 @@ class MetaBuildWrapper(object):
self.configs = contents['configs']
self.mixins = contents['mixins']
- self.masters = contents.get('masters')
+ self.builder_groups = contents.get('builder_groups')
self.public_artifact_builders = contents.get('public_artifact_builders')
def ReadIsolateMap(self):
@@ -935,38 +1006,39 @@ class MetaBuildWrapper(object):
def ConfigFromArgs(self):
if self.args.config:
- if self.args.master or self.args.builder:
- raise MBErr('Can not specific both -c/--config and -m/--master or '
- '-b/--builder')
+ if self.args.builder_group or self.args.builder:
+ raise MBErr('Can not specific both -c/--config and --group '
+ 'or -b/--builder')
return self.args.config
- if not self.args.master or not self.args.builder:
+ if not self.args.builder_group or not self.args.builder:
raise MBErr('Must specify either -c/--config or '
- '(-m/--master and -b/--builder)')
+ '(--group and -b/--builder)')
- if not self.args.master in self.masters:
- raise MBErr('Master name "%s" not found in "%s"' %
- (self.args.master, self.args.config_file))
+ if not self.args.builder_group in self.builder_groups:
+ raise MBErr('Builder group name "%s" not found in "%s"' %
+ (self.args.builder_group, self.args.config_file))
- if not self.args.builder in self.masters[self.args.master]:
- raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
- (self.args.builder, self.args.master, self.args.config_file))
+ if not self.args.builder in self.builder_groups[self.args.builder_group]:
+ raise MBErr('Builder name "%s" not found under groups[%s] in "%s"' %
+ (self.args.builder, self.args.builder_group,
+ self.args.config_file))
- config = self.masters[self.args.master][self.args.builder]
+ config = self.builder_groups[self.args.builder_group][self.args.builder]
if isinstance(config, dict):
if self.args.phase is None:
raise MBErr('Must specify a build --phase for %s on %s' %
- (self.args.builder, self.args.master))
+ (self.args.builder, self.args.builder_group))
phase = str(self.args.phase)
if phase not in config:
raise MBErr('Phase %s doesn\'t exist for %s on %s' %
- (phase, self.args.builder, self.args.master))
+ (phase, self.args.builder, self.args.builder_group))
return config[phase]
if self.args.phase is not None:
raise MBErr('Must not specify a build --phase for %s on %s' %
- (self.args.builder, self.args.master))
+ (self.args.builder, self.args.builder_group))
return config
def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
@@ -1517,6 +1589,7 @@ class MetaBuildWrapper(object):
cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
+ javascript_coverage = 'use_javascript_coverage=true' in vals['gn_args']
executable = isolate_map[target].get('executable', target)
executable_suffix = isolate_map[target].get(
@@ -1564,6 +1637,9 @@ class MetaBuildWrapper(object):
'--tsan=%d' % tsan,
'--cfi-diag=%d' % cfi_diag,
]
+
+ if javascript_coverage:
+ cmdline += ['--devtools-code-coverage=${ISOLATED_OUTDIR}']
elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
cmdline += [
'../../testing/test_env.py',
@@ -1588,10 +1664,21 @@ class MetaBuildWrapper(object):
'--logs-dir=${ISOLATED_OUTDIR}',
'--',
]
- cmdline += [
- '../../testing/test_env.py',
- '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
- ]
+ if is_android:
+ extra_files.append('../../build/android/test_wrapper/logdog_wrapper.py')
+ cmdline += [
+ '../../testing/test_env.py',
+ '../../build/android/test_wrapper/logdog_wrapper.py',
+ '--script',
+ '../../' + self.ToSrcRelPath(isolate_map[target]['script']),
+ '--logdog-bin-cmd',
+ '../../.task_template_packages/logdog_butler',
+ ]
+ else:
+ cmdline += [
+ '../../testing/test_env.py',
+ '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
+ ]
elif test_type == 'additional_compile_target':
cmdline = [
'./' + str(target) + executable_suffix,
@@ -1789,22 +1876,12 @@ class MetaBuildWrapper(object):
(e, path))
- def PrintCmd(self, cmd, env):
+ def PrintCmd(self, cmd):
if self.platform == 'win32':
- env_prefix = 'set '
- env_quoter = QuoteForSet
shell_quoter = QuoteForCmd
else:
- env_prefix = ''
- env_quoter = pipes.quote
shell_quoter = pipes.quote
- def print_env(var):
- if env and var in env:
- self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
-
- print_env('LLVM_FORCE_HEAD_REVISION')
-
if cmd[0] == self.executable:
cmd = ['python'] + cmd[1:]
self.Print(*[shell_quoter(arg) for arg in cmd])
@@ -1828,7 +1905,7 @@ class MetaBuildWrapper(object):
def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
# This function largely exists so it can be overridden for testing.
if self.args.dryrun or self.args.verbose or force_verbose:
- self.PrintCmd(cmd, env)
+ self.PrintCmd(cmd)
if self.args.dryrun:
return 0, '', ''