Add mb try command
This command will be used by developers to debug their CL quickly. It
allows them to specify only certain test suites to run.
Bug: 1015682
Change-Id: I22e30c1cc29e31d4e484808d5a8302813cc4a386
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1856822
Reviewed-by: Dirk Pranke <[email protected]>
Commit-Queue: Stephen Martinis <[email protected]>
Cr-Commit-Position: refs/heads/master@{#707422}
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 6d2784a..f6428ce 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -64,7 +64,6 @@
mbw = MetaBuildWrapper()
return mbw.Main(args)
-
class MetaBuildWrapper(object):
def __init__(self):
self.chromium_src_dir = CHROMIUM_SRC_DIR
@@ -214,6 +213,13 @@
'implies --quiet')
subp.set_defaults(func=self.CmdLookup)
+ subp = subps.add_parser('try',
+ description='Try your change on a remote builder')
+ AddCommonOptions(subp)
+ subp.add_argument('target',
+ help='ninja target to build and run')
+ subp.set_defaults(func=self.CmdTry)
+
subp = subps.add_parser(
'run', formatter_class=argparse.RawDescriptionHelpFormatter)
subp.description = (
@@ -377,6 +383,61 @@
self.PrintCmd(cmd, env)
return 0
+ def CmdTry(self):
+ target = self.args.target
+ if not target.startswith('//'):
+ self.Print("Expected a GN target like //:foo, got %s" % target)
+ return 1
+
+ recipe_name = None
+ isolate_map = self.ReadIsolateMap()
+ for name, config in isolate_map.iteritems():
+ if 'label' in config and config['label'] == target:
+ recipe_name = name
+ break
+ if not recipe_name:
+ self.Print("Unable to find a recipe entry for %s." % target)
+
+ json_path = self.PathJoin(self.chromium_src_dir, 'out.json')
+ try:
+ ret, out, err = self.Run(
+ ['git', 'cl', 'issue', '--json=out.json'], force_verbose=False)
+ if ret != 0:
+ self.Print(
+ "Unable to fetch current issue. Output and error:\n%s\n%s" % (
+ out, err
+ ))
+ return ret
+ with open(json_path) as f:
+ issue_data = json.load(f)
+ finally:
+ if self.Exists(json_path):
+ os.unlink(json_path)
+
+ if not issue_data['issue']:
+ self.Print("Missing issue data. Upload your CL to Gerrit and try again.")
+ return 1
+
+ def run_cmd(previous_res, cmd):
+ res, out, err = self.Run(cmd, force_verbose=False, stdin=previous_res)
+ if res != 0:
+ self.Print("Err while running", cmd)
+ self.Print("Output", out)
+ raise Exception(err)
+ return out
+
+ result = LedResult(None, run_cmd).then(
+ # TODO(martiniss): maybe don't always assume the bucket?
+ 'led', 'get-builder', 'luci.chromium.try:%s' % self.args.builder).then(
+ 'led', 'edit', '-r', 'chromium_trybot_experimental',
+ '-p', 'tests=["%s"]' % recipe_name).then(
+ 'led', 'edit-cr-cl', issue_data['issue_url']).then(
+ 'led', 'launch').result
+
+ swarming_data = json.loads(result)['swarming']
+ self.Print("Launched task at https://%s/task?id=%s" % (
+ swarming_data['host_name'], swarming_data['task_id']))
+
def CmdRun(self):
vals = self.GetConfig()
if not vals:
@@ -1658,14 +1719,16 @@
ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
return ret
- def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
+ def Run(self, cmd, env=None, force_verbose=True, buffer_output=True,
+ stdin=None):
# 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)
if self.args.dryrun:
return 0, '', ''
- ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
+ ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output,
+ stdin=stdin)
if self.args.verbose or force_verbose:
if ret:
self.Print(' -> returned %d' % ret)
@@ -1676,12 +1739,12 @@
self.Print(err, end='', file=sys.stderr)
return ret, out, err
- def Call(self, cmd, env=None, buffer_output=True):
+ def Call(self, cmd, env=None, buffer_output=True, stdin=None):
if buffer_output:
p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- env=env)
- out, err = p.communicate()
+ env=env, stdin=subprocess.PIPE)
+ out, err = p.communicate(input=stdin)
else:
p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
env=env)
@@ -1761,6 +1824,28 @@
return fp.write(contents)
+class LedResult(object):
+ """Holds the result of a led operation. Can be chained using |then|."""
+
+ def __init__(self, result, run_cmd):
+ self._result = result
+ self._run_cmd = run_cmd
+
+ @property
+ def result(self):
+ """The mutable result data of the previous led call as decoded JSON."""
+ return self._result
+
+ def then(self, *cmd):
+ """Invoke led, passing it the current `result` data as input.
+
+ Returns another LedResult object with the output of the command.
+ """
+ return self.__class__(
+ self._run_cmd(self._result, cmd), self._run_cmd)
+
+
+
class MBErr(Exception):
pass