Base implementation of the orchestrator.

This base implementation has enough functionality to orchestrate engine
builds with subbuilds using led. This will be used to validate all the
possible combinations build/test/archives that will be required for the
engine.

Change-Id: I84875ab4e328664cdc72b24a7ba9756b78f437ad
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/14861
Commit-Queue: Godofredo Contreras <godofredoc@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Kaushik Iska <kaushikiska@google.com>
diff --git a/recipe_modules/shard_util_v2/__init__.py b/recipe_modules/shard_util_v2/__init__.py
new file mode 100644
index 0000000..e534fb4
--- /dev/null
+++ b/recipe_modules/shard_util_v2/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'recipe_engine/buildbucket',
+    'recipe_engine/file',
+    'recipe_engine/json',
+    'recipe_engine/led',
+    'recipe_engine/path',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+    'recipe_engine/swarming',
+]
diff --git a/recipe_modules/shard_util_v2/api.py b/recipe_modules/shard_util_v2/api.py
new file mode 100644
index 0000000..ce052f6
--- /dev/null
+++ b/recipe_modules/shard_util_v2/api.py
@@ -0,0 +1,115 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DRONE_TIMEOUT_SECS = 3600 * 3  # 3 hours.
+
+import attr
+
+from google.protobuf import json_format
+from recipe_engine import recipe_api
+from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
+from PB.go.chromium.org.luci.buildbucket.proto import build as build_pb2
+
+# Builder names use full platform name instead of short names. We need to
+# map short names to full platform names to be able to identify the drone
+# used to run the subshards.
+PLATFORM_TO_NAME = {'win': 'Windows', 'linux': 'Linux', 'mac': 'Mac'}
+
+
+@attr.s
+class SubbuildResult(object):
+    """Subbuild result metadata."""
+
+    builder = attr.ib(type=str)
+    build_id = attr.ib(type=str)
+    url = attr.ib(type=str, default=None)
+    build_proto = attr.ib(type=build_pb2.Build, default=None)
+
+
+class ShardUtilApi(recipe_api.RecipeApi):
+  """Utilities to shard tasks."""
+
+  def schedule(self, builds):
+    """Schedules one subbuild per build."""
+    # Dependencies get here as a frozen dict we need to force them back
+    # to list of dicts.
+    results = {}
+    for build in builds:
+      task_name = build.get('name')
+      drone_properties = self.m.properties.thaw()
+      del drone_properties['builds']
+      drone_properties['build'] = build
+      # Copy parent bot dimensions.
+      drone_dimensions = build.get('drone_dimensions', [])
+      task_dimensions = {}
+      task_dimensions = []
+      for d in drone_dimensions:
+        k, v = d.split('=')
+        task_dimensions.append(common_pb2.RequestedDimension(key=k, value=v))
+      platform_name = PLATFORM_TO_NAME.get(self.m.platform.name)
+
+      # Override recipe.
+      drone_properties['recipe'] = 'engine_v2/builder'
+
+      if self.m.led.launched_by_led:
+        # If coming from led Launch sub-build using led.
+        builder_name='%s Engine Drone' % platform_name
+        parent = self.m.buildbucket.build.builder
+        led_data = self.m.led(
+            "get-builder",
+            "luci.%s.%s:%s" % (parent.project, parent.bucket, builder_name),
+        )
+        edit_args = []
+        for k, v in drone_properties.items():
+          edit_args.extend(["-p", "%s=%s" % (k, self.m.json.dumps(v))])
+        # led reduces the priority of tasks by 10 from their values in
+        # buildbucket which we do not want.
+        # TODO(crbug.com/1138533) Add an option to led to handle this.
+        led_data.result.buildbucket.bbagent_args.build.infra.swarming.priority -= 10
+        led_data = led_data.then("edit", *edit_args)
+        led_data = led_data.then("edit", "-name", task_name)
+        led_data = led_data.then("edit", "-r", 'engine_v2/builder')
+        led_data = self.m.led.inject_input_recipes(led_data)
+        launch_res = led_data.then("launch", "-modernize")
+        task_id = launch_res.launch_result.task_id
+        build_url = "https://ci.chromium.org/swarming/task/%s?server=%s" % (
+            task_id,
+            launch_res.launch_result.swarming_hostname,
+        )
+        results[task_name] = SubbuildResult(
+                builder=task_name, build_id=task_id, url=build_url
+        )
+    return results
+
+  def collect(self, task_ids, presentation):
+    """Waits for a list of builds to complete.
+
+    Args:
+      task_ids((list(str|TaskRequestMetadata)): The tasks metadata used to
+        wait for completion.
+      presentation(StepPresentation): Used to add logs and logs to UI.
+
+    Returns: A list of SubBuildResult, one per task.
+    """
+    swarming_results = self.m.swarming.collect(
+        "collect", task_ids, output_dir=self.m.path["cleanup"]
+    )
+    builds = {}
+    for result in swarming_results:
+      task_id = result.id
+      # Led launch ensures this file is present in the task root dir.
+      build_proto_path = result.output_dir.join("build.proto.json")
+      build_proto_json = self.m.file.read_text(
+          "read build.proto.json", build_proto_path
+      )
+      build_proto = build_pb2.Build()
+      log_name = '%s-build.proto.json' % result.id
+      presentation.logs[log_name] = build_proto_json.splitlines()
+      json_format.Parse(build_proto_json, build_proto)
+      builds[task_id] = SubbuildResult(
+         builder=build_proto.builder.builder,
+         build_id=task_id,
+         build_proto=build_proto,
+      )
+    return builds
diff --git a/recipe_modules/shard_util_v2/examples/full.expected/presubmit.json b/recipe_modules/shard_util_v2/examples/full.expected/presubmit.json
new file mode 100644
index 0000000..59ed22c
--- /dev/null
+++ b/recipe_modules/shard_util_v2/examples/full.expected/presubmit.json
@@ -0,0 +1,827 @@
+[
+  {
+    "cmd": [
+      "led",
+      "get-builder",
+      "luci.proj.try:Linux Engine Drone"
+    ],
+    "name": "led get-builder",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@proto.output@{@@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"bbagent_args\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"builder\": \"Linux Engine Drone\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }@@@",
+      "@@@STEP_LOG_LINE@proto.output@      }@@@",
+      "@@@STEP_LOG_LINE@proto.output@    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@}@@@",
+      "@@@STEP_LOG_END@proto.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "led",
+      "edit",
+      "-p",
+      "$recipe_engine/led={\"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", \"rbe_cas_input\": {\"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", \"digest\": {\"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", \"size_bytes\": 91}}}",
+      "-p",
+      "recipe=\"engine_v2/builder\"",
+      "-p",
+      "dependencies=[{\"dependency\": \"android_sdk\"}, {\"dependency\": \"chrome_and_driver\"}]",
+      "-p",
+      "git_url=\"http://abc\"",
+      "-p",
+      "$recipe_engine/buildbucket={\"build\": {\"builder\": {\"bucket\": \"try\", \"builder\": \"try-builder\", \"project\": \"proj\"}, \"createTime\": \"2018-05-25T23:50:17Z\", \"createdBy\": \"user:commit-bot@chromium.org\", \"id\": \"8945511751514863184\", \"infra\": {\"resultdb\": {\"invocation\": \"invocations/build:8945511751514863184\"}, \"swarming\": {\"priority\": 30}}, \"input\": {\"gerritChanges\": [{\"change\": \"123456\", \"host\": \"github.com\", \"patchset\": \"7\", \"project\": \"repo/a\"}], \"gitilesCommit\": {\"host\": \"github.com\", \"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", \"project\": \"repo/a\", \"ref\": \"refs/heads/main\"}}, \"number\": 123, \"tags\": [{\"key\": \"cq_experimental\", \"value\": \"false\"}]}}",
+      "-p",
+      "git_ref=\"refs/123/master\"",
+      "-p",
+      "build={\"drone_dimensions\": [\"dimension1=abc\"], \"gn\": [], \"name\": \"ios_debug\", \"ninja\": [\"ios_debug\"]}"
+    ],
+    "name": "led edit",
+    "stdin": "{\n\"buildbucket\": {\n\"bbagent_args\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"Linux Engine Drone\", \n\"project\": \"proj\"\n}, \n\"infra\": {\n\"swarming\": {\n\"priority\": -10\n}\n}\n}\n}\n}\n}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@proto.output@{@@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"bbagent_args\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"builder\": \"Linux Engine Drone\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"priority\": -10@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"builder\": \"try-builder\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"id\": \"8945511751514863184\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"invocation\": \"invocations/build:8945511751514863184\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"priority\": 30.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"change\": \"123456\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"project\": \"repo/a\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gitilesCommit\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"project\": \"repo/a\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"ref\": \"refs/heads/main\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"number\": 123.0, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"key\": \"cq_experimental\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"value\": \"false\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/led\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"rbe_cas_input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"digest\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"size_bytes\": 91.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"drone_dimensions\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dimension1=abc\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"gn\": [], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"name\": \"ios_debug\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"ninja\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"dependencies\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"android_sdk\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"chrome_and_driver\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_ref\": \"refs/123/master\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_url\": \"http://abc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"recipe\": \"engine_v2/builder\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }@@@",
+      "@@@STEP_LOG_LINE@proto.output@      }@@@",
+      "@@@STEP_LOG_LINE@proto.output@    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@}@@@",
+      "@@@STEP_LOG_END@proto.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "led",
+      "edit",
+      "-name",
+      "ios_debug"
+    ],
+    "name": "led edit (2)",
+    "stdin": "{\n\"buildbucket\": {\n\"bbagent_args\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"Linux Engine Drone\", \n\"project\": \"proj\"\n}, \n\"infra\": {\n\"swarming\": {\n\"priority\": -10\n}\n}, \n\"input\": {\n\"properties\": {\n\"$recipe_engine/buildbucket\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"try-builder\", \n\"project\": \"proj\"\n}, \n\"createTime\": \"2018-05-25T23:50:17Z\", \n\"createdBy\": \"user:commit-bot@chromium.org\", \n\"id\": \"8945511751514863184\", \n\"infra\": {\n\"resultdb\": {\n\"invocation\": \"invocations/build:8945511751514863184\"\n}, \n\"swarming\": {\n\"priority\": 30.0\n}\n}, \n\"input\": {\n\"gerritChanges\": [\n{\n\"change\": \"123456\", \n\"host\": \"github.com\", \n\"patchset\": \"7\", \n\"project\": \"repo/a\"\n}\n], \n\"gitilesCommit\": {\n\"host\": \"github.com\", \n\"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", \n\"project\": \"repo/a\", \n\"ref\": \"refs/heads/main\"\n}\n}, \n\"number\": 123.0, \n\"tags\": [\n{\n\"key\": \"cq_experimental\", \n\"value\": \"false\"\n}\n]\n}\n}, \n\"$recipe_engine/led\": {\n\"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", \n\"rbe_cas_input\": {\n\"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", \n\"digest\": {\n\"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", \n\"size_bytes\": 91.0\n}\n}\n}, \n\"build\": {\n\"drone_dimensions\": [\n\"dimension1=abc\"\n], \n\"gn\": [], \n\"name\": \"ios_debug\", \n\"ninja\": [\n\"ios_debug\"\n]\n}, \n\"dependencies\": [\n{\n\"dependency\": \"android_sdk\"\n}, \n{\n\"dependency\": \"chrome_and_driver\"\n}\n], \n\"git_ref\": \"refs/123/master\", \n\"git_url\": \"http://abc\", \n\"recipe\": \"engine_v2/builder\"\n}\n}\n}\n}\n}\n}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@proto.output@{@@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"bbagent_args\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"builder\": \"Linux Engine Drone\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"priority\": -10@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"builder\": \"try-builder\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"id\": \"8945511751514863184\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"invocation\": \"invocations/build:8945511751514863184\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"priority\": 30.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"change\": \"123456\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"project\": \"repo/a\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gitilesCommit\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"project\": \"repo/a\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"ref\": \"refs/heads/main\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"number\": 123.0, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"key\": \"cq_experimental\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"value\": \"false\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/led\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"rbe_cas_input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"digest\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"size_bytes\": 91.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"drone_dimensions\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dimension1=abc\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"gn\": [], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"name\": \"ios_debug\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"ninja\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"dependencies\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"android_sdk\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"chrome_and_driver\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_ref\": \"refs/123/master\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_url\": \"http://abc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"recipe\": \"engine_v2/builder\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }@@@",
+      "@@@STEP_LOG_LINE@proto.output@      }@@@",
+      "@@@STEP_LOG_LINE@proto.output@    }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"name\": \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@}@@@",
+      "@@@STEP_LOG_END@proto.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "led",
+      "edit",
+      "-r",
+      "engine_v2/builder"
+    ],
+    "name": "led edit (3)",
+    "stdin": "{\n\"buildbucket\": {\n\"bbagent_args\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"Linux Engine Drone\", \n\"project\": \"proj\"\n}, \n\"infra\": {\n\"swarming\": {\n\"priority\": -10\n}\n}, \n\"input\": {\n\"properties\": {\n\"$recipe_engine/buildbucket\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"try-builder\", \n\"project\": \"proj\"\n}, \n\"createTime\": \"2018-05-25T23:50:17Z\", \n\"createdBy\": \"user:commit-bot@chromium.org\", \n\"id\": \"8945511751514863184\", \n\"infra\": {\n\"resultdb\": {\n\"invocation\": \"invocations/build:8945511751514863184\"\n}, \n\"swarming\": {\n\"priority\": 30.0\n}\n}, \n\"input\": {\n\"gerritChanges\": [\n{\n\"change\": \"123456\", \n\"host\": \"github.com\", \n\"patchset\": \"7\", \n\"project\": \"repo/a\"\n}\n], \n\"gitilesCommit\": {\n\"host\": \"github.com\", \n\"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", \n\"project\": \"repo/a\", \n\"ref\": \"refs/heads/main\"\n}\n}, \n\"number\": 123.0, \n\"tags\": [\n{\n\"key\": \"cq_experimental\", \n\"value\": \"false\"\n}\n]\n}\n}, \n\"$recipe_engine/led\": {\n\"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", \n\"rbe_cas_input\": {\n\"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", \n\"digest\": {\n\"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", \n\"size_bytes\": 91.0\n}\n}\n}, \n\"build\": {\n\"drone_dimensions\": [\n\"dimension1=abc\"\n], \n\"gn\": [], \n\"name\": \"ios_debug\", \n\"ninja\": [\n\"ios_debug\"\n]\n}, \n\"dependencies\": [\n{\n\"dependency\": \"android_sdk\"\n}, \n{\n\"dependency\": \"chrome_and_driver\"\n}\n], \n\"git_ref\": \"refs/123/master\", \n\"git_url\": \"http://abc\", \n\"recipe\": \"engine_v2/builder\"\n}\n}\n}\n}, \n\"name\": \"ios_debug\"\n}\n}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@proto.output@{@@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"bbagent_args\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"builder\": \"Linux Engine Drone\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"priority\": -10@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"builder\": \"try-builder\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"id\": \"8945511751514863184\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"invocation\": \"invocations/build:8945511751514863184\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"priority\": 30.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"change\": \"123456\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"project\": \"repo/a\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gitilesCommit\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"project\": \"repo/a\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"ref\": \"refs/heads/main\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"number\": 123.0, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"key\": \"cq_experimental\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"value\": \"false\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/led\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"rbe_cas_input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"digest\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"size_bytes\": 91.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"drone_dimensions\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dimension1=abc\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"gn\": [], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"name\": \"ios_debug\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"ninja\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"dependencies\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"android_sdk\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"chrome_and_driver\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_ref\": \"refs/123/master\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_url\": \"http://abc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"recipe\": \"engine_v2/builder\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }@@@",
+      "@@@STEP_LOG_LINE@proto.output@      }@@@",
+      "@@@STEP_LOG_LINE@proto.output@    }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"name\": \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@}@@@",
+      "@@@STEP_LOG_END@proto.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "led",
+      "edit",
+      "-rbh",
+      "146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e/91"
+    ],
+    "name": "led edit (4)",
+    "stdin": "{\n\"buildbucket\": {\n\"bbagent_args\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"Linux Engine Drone\", \n\"project\": \"proj\"\n}, \n\"infra\": {\n\"swarming\": {\n\"priority\": -10\n}\n}, \n\"input\": {\n\"properties\": {\n\"$recipe_engine/buildbucket\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"try-builder\", \n\"project\": \"proj\"\n}, \n\"createTime\": \"2018-05-25T23:50:17Z\", \n\"createdBy\": \"user:commit-bot@chromium.org\", \n\"id\": \"8945511751514863184\", \n\"infra\": {\n\"resultdb\": {\n\"invocation\": \"invocations/build:8945511751514863184\"\n}, \n\"swarming\": {\n\"priority\": 30.0\n}\n}, \n\"input\": {\n\"gerritChanges\": [\n{\n\"change\": \"123456\", \n\"host\": \"github.com\", \n\"patchset\": \"7\", \n\"project\": \"repo/a\"\n}\n], \n\"gitilesCommit\": {\n\"host\": \"github.com\", \n\"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", \n\"project\": \"repo/a\", \n\"ref\": \"refs/heads/main\"\n}\n}, \n\"number\": 123.0, \n\"tags\": [\n{\n\"key\": \"cq_experimental\", \n\"value\": \"false\"\n}\n]\n}\n}, \n\"$recipe_engine/led\": {\n\"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", \n\"rbe_cas_input\": {\n\"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", \n\"digest\": {\n\"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", \n\"size_bytes\": 91.0\n}\n}\n}, \n\"build\": {\n\"drone_dimensions\": [\n\"dimension1=abc\"\n], \n\"gn\": [], \n\"name\": \"ios_debug\", \n\"ninja\": [\n\"ios_debug\"\n]\n}, \n\"dependencies\": [\n{\n\"dependency\": \"android_sdk\"\n}, \n{\n\"dependency\": \"chrome_and_driver\"\n}\n], \n\"git_ref\": \"refs/123/master\", \n\"git_url\": \"http://abc\", \n\"recipe\": \"engine_v2/builder\"\n}\n}\n}\n}, \n\"name\": \"ios_debug\"\n}\n}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@proto.output@{@@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"bbagent_args\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"builder\": \"Linux Engine Drone\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"priority\": -10@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@        \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@          \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/buildbucket\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"bucket\": \"try\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"builder\": \"try-builder\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"project\": \"proj\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"id\": \"8945511751514863184\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"invocation\": \"invocations/build:8945511751514863184\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"priority\": 30.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"change\": \"123456\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                      \"project\": \"repo/a\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"gitilesCommit\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"host\": \"github.com\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"project\": \"repo/a\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"ref\": \"refs/heads/main\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"number\": 123.0, @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"key\": \"cq_experimental\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                    \"value\": \"false\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@                ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"$recipe_engine/led\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"rbe_cas_input\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"digest\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@                  \"size_bytes\": 91.0@@@",
+      "@@@STEP_LOG_LINE@proto.output@                }@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"build\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"drone_dimensions\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dimension1=abc\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"gn\": [], @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"name\": \"ios_debug\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@              \"ninja\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              ]@@@",
+      "@@@STEP_LOG_LINE@proto.output@            }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"dependencies\": [@@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"android_sdk\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@              {@@@",
+      "@@@STEP_LOG_LINE@proto.output@                \"dependency\": \"chrome_and_driver\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@              }@@@",
+      "@@@STEP_LOG_LINE@proto.output@            ], @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_ref\": \"refs/123/master\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"git_url\": \"http://abc\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@            \"recipe\": \"engine_v2/builder\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@          }@@@",
+      "@@@STEP_LOG_LINE@proto.output@        }@@@",
+      "@@@STEP_LOG_LINE@proto.output@      }@@@",
+      "@@@STEP_LOG_LINE@proto.output@    }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"name\": \"ios_debug\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@  }, @@@",
+      "@@@STEP_LOG_LINE@proto.output@  \"cas_user_payload\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@    \"digest\": {@@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", @@@",
+      "@@@STEP_LOG_LINE@proto.output@      \"size_bytes\": \"91\"@@@",
+      "@@@STEP_LOG_LINE@proto.output@    }@@@",
+      "@@@STEP_LOG_LINE@proto.output@  }@@@",
+      "@@@STEP_LOG_LINE@proto.output@}@@@",
+      "@@@STEP_LOG_END@proto.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "led",
+      "launch",
+      "-modernize"
+    ],
+    "name": "led launch",
+    "stdin": "{\n\"buildbucket\": {\n\"bbagent_args\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"Linux Engine Drone\", \n\"project\": \"proj\"\n}, \n\"infra\": {\n\"swarming\": {\n\"priority\": -10\n}\n}, \n\"input\": {\n\"properties\": {\n\"$recipe_engine/buildbucket\": {\n\"build\": {\n\"builder\": {\n\"bucket\": \"try\", \n\"builder\": \"try-builder\", \n\"project\": \"proj\"\n}, \n\"createTime\": \"2018-05-25T23:50:17Z\", \n\"createdBy\": \"user:commit-bot@chromium.org\", \n\"id\": \"8945511751514863184\", \n\"infra\": {\n\"resultdb\": {\n\"invocation\": \"invocations/build:8945511751514863184\"\n}, \n\"swarming\": {\n\"priority\": 30.0\n}\n}, \n\"input\": {\n\"gerritChanges\": [\n{\n\"change\": \"123456\", \n\"host\": \"github.com\", \n\"patchset\": \"7\", \n\"project\": \"repo/a\"\n}\n], \n\"gitilesCommit\": {\n\"host\": \"github.com\", \n\"id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", \n\"project\": \"repo/a\", \n\"ref\": \"refs/heads/main\"\n}\n}, \n\"number\": 123.0, \n\"tags\": [\n{\n\"key\": \"cq_experimental\", \n\"value\": \"false\"\n}\n]\n}\n}, \n\"$recipe_engine/led\": {\n\"led_run_id\": \"flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc\", \n\"rbe_cas_input\": {\n\"cas_instance\": \"projects/chromium-swarm/instances/default_instance\", \n\"digest\": {\n\"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", \n\"size_bytes\": 91.0\n}\n}\n}, \n\"build\": {\n\"drone_dimensions\": [\n\"dimension1=abc\"\n], \n\"gn\": [], \n\"name\": \"ios_debug\", \n\"ninja\": [\n\"ios_debug\"\n]\n}, \n\"dependencies\": [\n{\n\"dependency\": \"android_sdk\"\n}, \n{\n\"dependency\": \"chrome_and_driver\"\n}\n], \n\"git_ref\": \"refs/123/master\", \n\"git_url\": \"http://abc\", \n\"recipe\": \"engine_v2/builder\"\n}\n}\n}\n}, \n\"name\": \"ios_debug\"\n}, \n\"cas_user_payload\": {\n\"digest\": {\n\"hash\": \"146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e\", \n\"size_bytes\": \"91\"\n}\n}\n}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"host_name\": \"chromium-swarm.appspot.com\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"task_id\": \"fake-task-id\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LINK@Swarming task@https://chromium-swarm.appspot.com/task?id=fake-task-id@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "collect builds",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@{@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"status\": \"SUCCESS\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      \"value\": \"false\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      \"key\": \"cq_experimental\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  ], @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"builder\": \"builder-subbuild1\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"bucket\": \"try\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"output\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      \"test_orchestration_inputs_hash\": \"abc\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"input\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@        \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@        \"host\": \"chromium-review.googlesource.com\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@        \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@        \"change\": \"123456\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    ]@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      \"priority\": 30@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@      \"invocation\": \"invocations/build:8945511751514863186\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@  \"id\": \"8945511751514863186\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-1-build.proto.json@}@@@",
+      "@@@STEP_LOG_END@fake-task-id-1-build.proto.json@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@{@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"status\": \"SUCCESS\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      \"value\": \"false\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      \"key\": \"cq_experimental\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  ], @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"builder\": \"builder-subbuild2\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"bucket\": \"try\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"output\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      \"test_orchestration_inputs_hash\": \"abc\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"input\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@        \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@        \"host\": \"chromium-review.googlesource.com\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@        \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@        \"change\": \"123456\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    ]@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      \"priority\": 30@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@      \"invocation\": \"invocations/build:8945511751514863187\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@  \"id\": \"8945511751514863187\"@@@",
+      "@@@STEP_LOG_LINE@fake-task-id-2-build.proto.json@}@@@",
+      "@@@STEP_LOG_END@fake-task-id-2-build.proto.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "collect builds.install infra/tools/luci/swarming",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/swarming/swarming_module_pin"
+    ],
+    "infra_step": true,
+    "name": "collect builds.install infra/tools/luci/swarming.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/swarming/swarming_module_pin",
+      "-ensure-file",
+      "infra/tools/luci/swarming/${platform} swarming_module_pin",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "collect builds.install infra/tools/luci/swarming.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-swarming_module_\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/swarming/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/swarming/swarming_module_pin/swarming",
+      "collect",
+      "-server",
+      "https://example.swarmingserver.appspot.com",
+      "-task-summary-json",
+      "/path/to/tmp/json",
+      "-task-output-stdout",
+      "json",
+      "-output-dir",
+      "[CLEANUP]",
+      "fake-task-id"
+    ],
+    "infra_step": true,
+    "name": "collect builds.collect",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"fake-task-id-1\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"output\": \"hello world!\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"outputs\": [], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"results\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"bot_id\": \"vm-123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"duration\": 62.35, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"exit_code\": 0, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"builder-subbuild1\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"outputs_ref\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"isolated\": \"abc123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"isolatedserver\": \"https://isolateserver.appspot.com\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"namespace\": \"default-gzip\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"state\": \"COMPLETED\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"task_id\": \"fake-task-id-1\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"fake-task-id-2\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"output\": \"hello world!\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"outputs\": [], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"results\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"bot_id\": \"vm-123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"duration\": 62.35, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"exit_code\": 0, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"builder-subbuild2\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"outputs_ref\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"isolated\": \"abc123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"isolatedserver\": \"https://isolateserver.appspot.com\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"namespace\": \"default-gzip\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"state\": \"COMPLETED\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"task_id\": \"fake-task-id-2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@task stdout+stderr: builder-subbuild1@hello world!@@@",
+      "@@@STEP_LOG_END@task stdout+stderr: builder-subbuild1@@@",
+      "@@@STEP_LOG_LINE@task stdout+stderr: builder-subbuild2@hello world!@@@",
+      "@@@STEP_LOG_END@task stdout+stderr: builder-subbuild2@@@",
+      "@@@STEP_LINK@task isolated outputs: builder-subbuild1@https://isolateserver.appspot.com/browse?namespace=default-gzip&hash=abc123@@@",
+      "@@@STEP_LINK@task isolated outputs: builder-subbuild2@https://isolateserver.appspot.com/browse?namespace=default-gzip&hash=abc123@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CLEANUP]/fake-task-id-1/build.proto.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "collect builds.read build.proto.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@{@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"status\": \"SUCCESS\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"value\": \"false\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"key\": \"cq_experimental\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  ], @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"builder\": \"builder-subbuild1\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"bucket\": \"try\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"output\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"test_orchestration_inputs_hash\": \"abc\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"input\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"host\": \"chromium-review.googlesource.com\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"change\": \"123456\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    ]@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"priority\": 30@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"invocation\": \"invocations/build:8945511751514863186\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"id\": \"8945511751514863186\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@}@@@",
+      "@@@STEP_LOG_END@build.proto.json@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CLEANUP]/fake-task-id-2/build.proto.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "collect builds.read build.proto.json (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@{@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"status\": \"SUCCESS\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"tags\": [@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"value\": \"false\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"key\": \"cq_experimental\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  ], @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"builder\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"builder\": \"builder-subbuild2\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"bucket\": \"try\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"createTime\": \"2018-05-25T23:50:17Z\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"createdBy\": \"user:commit-bot@chromium.org\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"output\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"test_orchestration_inputs_hash\": \"abc\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"input\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"gerritChanges\": [@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"project\": \"fuchsia\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"host\": \"chromium-review.googlesource.com\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"patchset\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@        \"change\": \"123456\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    ]@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"infra\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"swarming\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"priority\": 30@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    \"resultdb\": {@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@      \"invocation\": \"invocations/build:8945511751514863187\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@    }@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  }, @@@",
+      "@@@STEP_LOG_LINE@build.proto.json@  \"id\": \"8945511751514863187\"@@@",
+      "@@@STEP_LOG_LINE@build.proto.json@}@@@",
+      "@@@STEP_LOG_END@build.proto.json@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/shard_util_v2/examples/full.py b/recipe_modules/shard_util_v2/examples/full.py
new file mode 100644
index 0000000..644c43e
--- /dev/null
+++ b/recipe_modules/shard_util_v2/examples/full.py
@@ -0,0 +1,86 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import copy
+
+from PB.recipe_modules.recipe_engine.led.properties import (
+    InputProperties as LedInputProperties,
+)
+from recipe_engine.post_process import DoesNotRun, Filter, StatusFailure
+
+
+DEPS = [
+    'flutter/shard_util_v2',
+    'recipe_engine/buildbucket',
+    'recipe_engine/properties',
+    'recipe_engine/platform',
+    'recipe_engine/step',
+]
+
+
+def RunSteps(api):
+  build_configs = api.properties.get('builds', [])
+  reqs = api.shard_util_v2.schedule(build_configs)
+  with api.step.nest("collect builds") as presentation:
+    api.shard_util_v2.collect([
+        build.build_id for build in reqs.values()
+    ], presentation)
+
+
+def GenTests(api):
+  try_subbuild1 = api.shard_util_v2.try_build_message(
+      build_id=8945511751514863186,
+      builder="builder-subbuild1",
+      output_props={"test_orchestration_inputs_hash": "abc"},
+      status="SUCCESS",
+  )
+  try_subbuild2 = api.shard_util_v2.try_build_message(
+      build_id=8945511751514863187,
+      builder="builder-subbuild2",
+      output_props={"test_orchestration_inputs_hash": "abc"},
+      status="SUCCESS",
+  )
+
+  props = {
+      'builds': [{
+          "name": "ios_debug", "gn": [], "ninja": ["ios_debug"],
+          'drone_dimensions': ['dimension1=abc']
+      }],
+      'dependencies': [{"dependency": "android_sdk"},
+                       {"dependency": "chrome_and_driver"}],
+      '$recipe_engine/led': {
+          "led_run_id":
+              "flutter/led/abc_google.com/b9861e3db1034eee460599837221ab468e03bc43f9fd05684a08157fd646abfc",
+          "rbe_cas_input": {
+              "cas_instance":
+                  "projects/chromium-swarm/instances/default_instance",
+              "digest": {
+                  "hash":
+                      "146d56311043bb141309968d570e23d05a108d13ce2e20b5aeb40a9b95629b3e",
+                  "size_bytes":
+                      91
+              }
+          }
+      },
+  }
+
+  presubmit_props = copy.deepcopy(props)
+  presubmit_props['git_url'] = 'http://abc'
+  presubmit_props['git_ref'] = 'refs/123/master'
+
+  yield api.test(
+      'presubmit', api.properties(**presubmit_props),
+      api.platform.name('linux'),
+      api.buildbucket.try_build(
+          project='proj',
+          builder='try-builder',
+          git_repo='https://github.com/repo/a',
+          revision='a' * 40,
+          build_number=123
+      ),
+      api.shard_util_v2.child_led_steps(
+          builds=[try_subbuild1, try_subbuild2],
+          collect_step="collect builds",
+      )
+  )
diff --git a/recipe_modules/shard_util_v2/test_api.py b/recipe_modules/shard_util_v2/test_api.py
new file mode 100644
index 0000000..52a23d8
--- /dev/null
+++ b/recipe_modules/shard_util_v2/test_api.py
@@ -0,0 +1,79 @@
+# Copyright 2021 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+from google.protobuf import json_format
+
+from recipe_engine import recipe_test_api
+from PB.go.chromium.org.luci.buildbucket.proto import (
+    builds_service as builds_service_pb2,
+)
+from PB.go.chromium.org.luci.led.job import job as job_pb2
+
+
+class ShardUtilTestApi(recipe_test_api.RecipeTestApi):
+
+  def try_build_message(
+      self, builder, input_props=None, output_props=None, **kwargs):
+    """Generates a try Buildbucket Build message.
+
+    Args:
+      builder (str): The builder name.
+      input_props (Dict): Input properties to set on the build.
+      output_props (Dict): Output properties to set on the build.
+      kwargs: Forwarded to BuildbucketApi.try_build_message.
+
+    See BuildBucketTestApi.try_build_message for full parameter documentation.
+    """
+    project = kwargs.pop("project", "fuchsia")
+    msg = self.m.buildbucket.try_build_message(
+        builder=builder, project=project, **kwargs
+    )
+    msg.input.properties.update(input_props if input_props else {})
+    msg.output.properties.update(output_props if output_props else {})
+    return msg
+
+  def child_led_steps(self, builds, collect_step="build"):
+    """Generates step data to schedule and collect from child builds.
+
+    Args:
+      builds (list(build_pb2.Build)): The builds to schedule and collect from.
+    """
+    step_data = []
+    task_results = []
+    i = 0
+    for build in builds:
+      i += 1
+      suffix = ""
+      if i > 1:
+        suffix = " (%d)" % i
+
+      task_id = "fake-task-id-%d" % (i,)
+
+      # led launch mock will take ....infra.swarming.task_id as this
+      # build's launched swarming ID.
+      jd = job_pb2.Definition()
+      jd.buildbucket.bbagent_args.build.CopyFrom(build)
+      jd.buildbucket.bbagent_args.build.infra.swarming.task_id = task_id
+      step_data.append(
+          self.m.led.mock_get_builder(
+              jd,
+              build.builder.project,
+              build.builder.bucket,
+              build.builder.builder,
+          )
+      )
+      task_results.append(
+          self.m.swarming.task_result(id=task_id, name=build.builder.builder)
+      )
+      step_data.append(
+          self.step_data(
+              "%s.read build.proto.json%s" % (collect_step, suffix),
+              self.m.file.read_text(json_format.MessageToJson(build)),
+          )
+      )
+    ret = self.step_data(
+        "%s.collect" % collect_step, self.m.swarming.collect(task_results)
+    )
+    for s in step_data:
+      ret += s
+    return ret