diff options
Diffstat (limited to 'chromium/tools/mb/mb.py')
-rwxr-xr-x | chromium/tools/mb/mb.py | 259 |
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, '', '' |