Revert "pw_presubmit: Move most logic to a module"

This reverts commit 618497de3d75d46f0eb52aac984cca10c41c3349.

Reason for revert: not running steps in pw_presubmit recipes

Original change's description:
> pw_presubmit: Move most logic to a module
>
> Move most logic from the pw_presubmit recipe to a module. For now, leave
> the signing and upload logic in the recipe.
>
> Bug: 523
> Change-Id: I413cb9e757c98d09a9896d4466012f825e80c0f1
> Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/67229
> Commit-Queue: Rob Mohr <mohrr@google.com>
> Reviewed-by: Oliver Newman <olivernewman@google.com>

TBR=mohrr@google.com,olivernewman@google.com,pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com,tpudlik@google.com

Change-Id: I0ff688a995bb3c6afcf0a87f943df7eca0b680f8
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 523
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/67801
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: Rob Mohr <mohrr@google.com>
diff --git a/recipe_modules/pw_presubmit/__init__.py b/recipe_modules/pw_presubmit/__init__.py
deleted file mode 100644
index ed7bf1a..0000000
--- a/recipe_modules/pw_presubmit/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-#     https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-# pylint: disable=missing-docstring
-
-from PB.recipe_modules.pigweed.pw_presubmit import properties
-
-DEPS = [
-    'fuchsia/utils',
-    'pigweed/build',
-    'recipe_engine/buildbucket',
-    'recipe_engine/file',
-    'recipe_engine/path',
-    'recipe_engine/raw_io',
-    'recipe_engine/step',
-    'recipe_engine/time',
-]
-
-PROPERTIES = properties.InputProperties
-
-PYTHON_VERSION_COMPATIBILITY = "PY3"
diff --git a/recipe_modules/pw_presubmit/api.py b/recipe_modules/pw_presubmit/api.py
deleted file mode 100644
index 3be7c0e..0000000
--- a/recipe_modules/pw_presubmit/api.py
+++ /dev/null
@@ -1,166 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-#     https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-"""Wrapper for 'pw presubmit' in the project source tree."""
-
-import collections
-
-import attr
-from recipe_engine import config_types, recipe_api
-from RECIPE_MODULES.fuchsia.utils import memoize
-
-
-@attr.s
-class Step(object):
-    _api = attr.ib()
-    name = attr.ib()
-    dir = attr.ib()
-    _export_dir_name = attr.ib(default=None)
-
-    @property
-    def export_dir(self):
-        if not self._export_dir_name:
-            return None  # pragma: no cover
-        return self.dir.join(self._export_dir_name)
-
-
-class PwPresubmitApi(recipe_api.RecipeApi):
-    """Calls to checkout code."""
-
-    def __init__(self, props, *args, **kwargs):
-        super(PwPresubmitApi, self).__init__(*args, **kwargs)
-        self._command_name = props.command_name or 'python -m pw_cli'
-        self._input_steps = list(props.step)
-        self._input_programs = list(props.program)
-        self._only_on_changed_files = props.only_on_changed_files
-        self._export_dir_name = props.export_dir_name
-        self._root = None
-        self._checkout_root = None
-        self._step_objects = None
-
-    @property
-    def command_name(self):
-        return self._command_name
-
-    @property
-    def root(self):
-        return self._root
-
-    @property
-    def export_dir_name(self):
-        return self._export_dir_name
-
-    def _step(self, name):
-        return Step(
-            self.m,
-            name,
-            self.root.join(name),
-            export_dir_name=self.export_dir_name,
-        )
-
-    def init(self, checkout_root):
-        self._root = self.m.path['start_dir'].join('presubmit')
-        self._checkout_root = checkout_root
-
-        self._step_objects = collections.OrderedDict()
-
-        for step_name in self._input_steps:
-            self._step_objects[step_name] = self._step(step_name)
-
-        if self._input_programs:
-            with self.m.step.nest('get steps from programs'):
-                for program in self._input_programs:
-                    # To get step_test_data line to pass pylint.
-                    raw_io_stream_output = (
-                        self.m.raw_io.test_api.stream_output_text
-                    )
-
-                    program_steps = (
-                        self._run(
-                            ['--program', program, '--only-list-steps'],
-                            name=program,
-                            stdout=self.m.raw_io.output_text(),
-                            step_test_data=lambda: raw_io_stream_output(
-                                '{0}_0\n{0}_1\n'.format(program),
-                            ),
-                        )
-                        .stdout.strip()
-                        .splitlines()
-                    )
-
-                    for step_name in program_steps:
-                        self._step_objects[step_name] = self._step(step_name)
-
-    def steps(self):
-        return self._step_objects.values()
-
-    def _step_timeout(self):
-        # Amount of time elapsed in the run.
-        elapsed_time = (
-            self.m.time.time() - self.m.buildbucket.build.start_time.seconds
-        )
-
-        # Amount of time before build times out.
-        time_remaining = (
-            self.m.buildbucket.build.execution_timeout.seconds - elapsed_time
-        )
-
-        # Give a buffer before build times out and kill this step then. This
-        # should give enough time to read any logfiles and maybe upload to
-        # logdog/GCS before the build times out.
-        step_timeout = time_remaining - 60
-
-        # If the timeout would be negative or very small set it to 30 seconds.
-        # We likely won't have enough information to debug these steps, but in
-        # case they're fast there's no reason to kill them much before the
-        # build is terminated.
-        if step_timeout < 30:
-            step_timeout = 30
-
-        return step_timeout
-
-    def _run(self, args, name='run', **kwargs):
-        cmd = self._command_name.split()
-        cmd += [
-            '--directory',
-            self._checkout_root,
-            '--loglevel',
-            'debug',
-            'presubmit',
-            '--package-root',
-            self.m.path['cache'],
-            '--output-directory',
-            self._root,
-        ]
-
-        cmd.extend(args)
-
-        return self.m.step(name, cmd, timeout=self._step_timeout(), **kwargs)
-
-    def run(self, step):
-        with self.m.step.nest(step.name) as pres:
-            args = []
-
-            if self._only_on_changed_files:
-                args.extend(('--base', 'HEAD~1'))
-
-            args.extend(('--step', step.name))
-
-            self._run(args, name=step.name)
-
-            if step.export_dir:
-                self.m.file.ensure_directory(
-                    'mkdir {}'.format(self.export_dir_name), step.export_dir,
-                )
-
-            self.m.build.save_logs(step.dir, step.export_dir)
diff --git a/recipe_modules/pw_presubmit/properties.proto b/recipe_modules/pw_presubmit/properties.proto
deleted file mode 100644
index 6b5a526..0000000
--- a/recipe_modules/pw_presubmit/properties.proto
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-syntax = "proto3";
-
-// This message is used for both the pw_presubmit and pw_presubmit_container
-// recipes.
-package recipe_modules.pigweed.pw_presubmit;
-
-message InputProperties {
-  // Command name in case it's changed by a dependent project. Default if unset
-  // is "pw".
-  string command_name = 1;
-
-  // Step to run from 'pw presubmit'. See 'pw presubmit --help' for options.
-  // Default is to run all steps.
-  repeated string step = 2;
-
-  // Program to run from 'pw presubmit'. See 'pw presubmit --help' for options.
-  // Default is to let 'pw presubmit' run its default program.
-  repeated string program = 3;
-
-  // Only run this check against changed files.
-  bool only_on_changed_files = 4;
-
-  // Subdirectory of the build directory to upload to GCS. Required to upload
-  // build artifacts.
-  string export_dir_name = 5;
-}
diff --git a/recipe_modules/pw_presubmit/test_api.py b/recipe_modules/pw_presubmit/test_api.py
deleted file mode 100644
index d6ee7a4..0000000
--- a/recipe_modules/pw_presubmit/test_api.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-#     https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-"""Test API for pw_presubmit."""
-
-import re
-
-from PB.recipe_modules.pigweed.pw_presubmit import properties
-from recipe_engine import recipe_test_api
-
-
-class PwPresubmitTestApi(recipe_test_api.RecipeTestApi):
-    """Test API for pw_presubmit."""
-
-    def properties(self, **kwargs):
-        props = {
-            'command_name': None,
-            'program': [],
-            'step': [],
-            'only_on_changed_files': False,
-            'export_dir_name': 'export',
-        }
-
-        props.update(**kwargs)
-
-        return {'$pigweed/pw_presubmit': properties.InputProperties(**props)}
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/exit-early.json b/recipe_modules/pw_presubmit/tests/full.expected/exit-early.json
deleted file mode 100644
index b6042b6..0000000
--- a/recipe_modules/pw_presubmit/tests/full.expected/exit-early.json
+++ /dev/null
@@ -1,5 +0,0 @@
-[
-  {
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json b/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
deleted file mode 100644
index 4f49c10..0000000
--- a/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
+++ /dev/null
@@ -1,385 +0,0 @@
-[
-  {
-    "cmd": [],
-    "name": "get steps from programs"
-  },
-  {
-    "cmd": [
-      "foo",
-      "--directory",
-      "[START_DIR]/checkout",
-      "--loglevel",
-      "debug",
-      "presubmit",
-      "--package-root",
-      "[CACHE]",
-      "--output-directory",
-      "[START_DIR]/presubmit",
-      "--program",
-      "full",
-      "--only-list-steps"
-    ],
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "get steps from programs.full",
-    "timeout": 30,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_0"
-  },
-  {
-    "cmd": [
-      "foo",
-      "--directory",
-      "[START_DIR]/checkout",
-      "--loglevel",
-      "debug",
-      "presubmit",
-      "--package-root",
-      "[CACHE]",
-      "--output-directory",
-      "[START_DIR]/presubmit",
-      "--base",
-      "HEAD~1",
-      "--step",
-      "full_0"
-    ],
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_0.full_0",
-    "timeout": 30,
-    "~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",
-      "[START_DIR]/presubmit/full_0/export"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_0.mkdir export",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/full_0/ninja.log",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_0.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
-      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
-      "@@@STEP_LOG_END@ninja.log@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_0.longest build steps",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_0.longest build steps.long",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_0.longest build steps.medium",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_0.longest build steps.short",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_0.copy",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/full_0/ninja.log",
-      "[START_DIR]/presubmit/full_0/export/ninja.log"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_0.copy.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_1"
-  },
-  {
-    "cmd": [
-      "foo",
-      "--directory",
-      "[START_DIR]/checkout",
-      "--loglevel",
-      "debug",
-      "presubmit",
-      "--package-root",
-      "[CACHE]",
-      "--output-directory",
-      "[START_DIR]/presubmit",
-      "--base",
-      "HEAD~1",
-      "--step",
-      "full_1"
-    ],
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_1.full_1",
-    "timeout": 30,
-    "~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",
-      "[START_DIR]/presubmit/full_1/export"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_1.mkdir export",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/full_1/ninja.log",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_1.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
-      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
-      "@@@STEP_LOG_END@ninja.log@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_1.longest build steps",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_1.longest build steps.long",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_1.longest build steps.medium",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_1.longest build steps.short",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "full_1.copy",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/full_1/ninja.log",
-      "[START_DIR]/presubmit/full_1/export/ninja.log"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "full_1.copy.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/step.json b/recipe_modules/pw_presubmit/tests/full.expected/step.json
deleted file mode 100644
index fef6a49..0000000
--- a/recipe_modules/pw_presubmit/tests/full.expected/step.json
+++ /dev/null
@@ -1,347 +0,0 @@
-[
-  {
-    "cmd": [],
-    "name": "step1"
-  },
-  {
-    "cmd": [
-      "python",
-      "-m",
-      "pw_cli",
-      "--directory",
-      "[START_DIR]/checkout",
-      "--loglevel",
-      "debug",
-      "presubmit",
-      "--package-root",
-      "[CACHE]",
-      "--output-directory",
-      "[START_DIR]/presubmit",
-      "--step",
-      "step1"
-    ],
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step1.step1",
-    "timeout": 40.0,
-    "~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",
-      "[START_DIR]/presubmit/step1/export"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step1.mkdir export",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/step1/ninja.log",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step1.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
-      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
-      "@@@STEP_LOG_END@ninja.log@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step1.longest build steps",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step1.longest build steps.long",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step1.longest build steps.medium",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step1.longest build steps.short",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step1.copy",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/step1/ninja.log",
-      "[START_DIR]/presubmit/step1/export/ninja.log"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step1.copy.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step2"
-  },
-  {
-    "cmd": [
-      "python",
-      "-m",
-      "pw_cli",
-      "--directory",
-      "[START_DIR]/checkout",
-      "--loglevel",
-      "debug",
-      "presubmit",
-      "--package-root",
-      "[CACHE]",
-      "--output-directory",
-      "[START_DIR]/presubmit",
-      "--step",
-      "step2"
-    ],
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step2.step2",
-    "timeout": 30,
-    "~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",
-      "[START_DIR]/presubmit/step2/export"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step2.mkdir export",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/step2/ninja.log",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step2.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
-      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
-      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
-      "@@@STEP_LOG_END@ninja.log@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step2.longest build steps",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step2.longest build steps.long",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step2.longest build steps.medium",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step2.longest build steps.short",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "step2.copy",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/presubmit/step2/ninja.log",
-      "[START_DIR]/presubmit/step2/export/ninja.log"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:try"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "step2.copy.ninja.log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/recipe_modules/pw_presubmit/tests/full.py b/recipe_modules/pw_presubmit/tests/full.py
deleted file mode 100644
index 08431d3..0000000
--- a/recipe_modules/pw_presubmit/tests/full.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-#     https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-"""Full test of pw_presubmit module."""
-
-import datetime
-
-from recipe_engine.recipe_api import Property
-
-DEPS = [
-    'fuchsia/status_check',
-    'pigweed/checkout',
-    'pigweed/pw_presubmit',
-    'recipe_engine/path',
-    'recipe_engine/properties',
-    'recipe_engine/time',
-]
-
-PYTHON_VERSION_COMPATIBILITY = "PY3"
-
-
-def RunSteps(api):  # pylint: disable=invalid-name
-    api.pw_presubmit.init(api.path['start_dir'].join('checkout'))
-
-    for step in api.pw_presubmit.steps():
-        api.pw_presubmit.run(step)
-
-    # For coverage.
-    _ = api.pw_presubmit.command_name
-    _ = api.pw_presubmit.root
-
-
-def GenTests(api):  # pylint: disable=invalid-name
-    """Create tests."""
-
-    def properties(**kwargs):
-        new_kwargs = api.checkout.git_properties()
-        new_kwargs.update(api.pw_presubmit.properties(**kwargs))
-        return api.properties(**new_kwargs)
-
-    yield (
-        api.status_check.test('exit-early')
-        + properties()
-        + api.checkout.ci_test_data()
-    )
-
-    yield (
-        api.status_check.test('pigweed')
-        + properties(
-            command_name='foo', program=['full'], only_on_changed_files=True
-        )
-        + api.checkout.ci_test_data()
-    )
-
-    yield (
-        api.status_check.test('step')
-        + properties(step=['step1', 'step2'])
-        + api.checkout.try_test_data(
-            start_time=datetime.datetime.utcfromtimestamp(1600000000),
-            execution_timeout=120,
-        )
-        + api.time.seed(1600000000)
-        + api.time.step(20.0)
-    )
diff --git a/recipes/pw_presubmit.expected/exit-early.json b/recipes/pw_presubmit.expected/exit-early.json
new file mode 100644
index 0000000..8291233
--- /dev/null
+++ b/recipes/pw_presubmit.expected/exit-early.json
@@ -0,0 +1,1127 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed",
+    "~followup_annotations": [
+      "@@@STEP_LINK@applied pigweed:1234@https://pigweed-review.googlesource.com/c/1234@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/tool_manifest.json",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@{@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"path\": \"path/to/gerrit\",@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"version\": \"version:pinned-version\"@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@}@@@",
+      "@@@STEP_LOG_END@tool_manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/path/to/gerrit/version%3Apinned-version"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/path/to/gerrit/version%3Apinned-version",
+      "-ensure-file",
+      "path/to/gerrit version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@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-version:pinned-v\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"path/to/gerrit\"@@@",
+      "@@@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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.number",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"_number\": \"1234\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234, remote='https://pigweed.googlesource.com/pigweed/pigweed', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, branch='main', gerrit_name='pigweed', submitted=True, base=None, base_type=None)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@.GUARD_FILE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git rev-parse (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://pigweed-review.googlesource.com/c/1234@@@",
+      "@@@STEP_LINK@gitiles@https://pigweed.googlesource.com/pigweed/pigweed/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://pigweed.googlesource.com/pigweed/pigweed",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
+      "--no-recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git fetch patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--force",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git submodule status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@applied [_Change(number=1234, remote='https://pigweed.googlesource.com/pigweed/pigweed', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, branch='main', gerrit_name='pigweed', submitted=True, base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash')]\nnot applied []@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.base",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\"@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision_type@\"submitted_commit_hash\"@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "submodule status filler text",
+      "[START_DIR]/snapshot/submodules.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write submodule snapshot",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@submodules.log@submodule status filler text@@@",
+      "@@@STEP_LOG_END@submodules.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[START_DIR]/snapshot/git.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "nothing to do, exiting"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/pw_presubmit.expected/pigweed.json b/recipes/pw_presubmit.expected/pigweed.json
new file mode 100644
index 0000000..302c7e3
--- /dev/null
+++ b/recipes/pw_presubmit.expected/pigweed.json
@@ -0,0 +1,1702 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed",
+    "~followup_annotations": [
+      "@@@STEP_LINK@applied pigweed:1234@https://pigweed-review.googlesource.com/c/1234@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/tool_manifest.json",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@{@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"path\": \"path/to/gerrit\",@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"version\": \"version:pinned-version\"@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@}@@@",
+      "@@@STEP_LOG_END@tool_manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/path/to/gerrit/version%3Apinned-version"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/path/to/gerrit/version%3Apinned-version",
+      "-ensure-file",
+      "path/to/gerrit version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@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-version:pinned-v\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"path/to/gerrit\"@@@",
+      "@@@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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.number",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"_number\": \"1234\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234, remote='https://pigweed.googlesource.com/pigweed/pigweed', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, branch='main', gerrit_name='pigweed', submitted=True, base=None, base_type=None)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_END@.GUARD_FILE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git rev-parse (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://pigweed-review.googlesource.com/c/1234@@@",
+      "@@@STEP_LINK@gitiles@https://pigweed.googlesource.com/pigweed/pigweed/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://pigweed.googlesource.com/pigweed/pigweed",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
+      "--no-recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git fetch patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--force",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git submodule status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@applied [_Change(number=1234, remote='https://pigweed.googlesource.com/pigweed/pigweed', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, branch='main', gerrit_name='pigweed', submitted=True, base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash')]\nnot applied []@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.base",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\"@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision_type@\"submitted_commit_hash\"@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "submodule status filler text",
+      "[START_DIR]/snapshot/submodules.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write submodule snapshot",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@submodules.log@submodule status filler text@@@",
+      "@@@STEP_LOG_END@submodules.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[START_DIR]/snapshot/git.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "environment"
+  },
+  {
+    "cmd": [],
+    "name": "get steps from programs"
+  },
+  {
+    "cmd": [
+      "foo",
+      "--directory",
+      "[START_DIR]/checkout",
+      "--loglevel",
+      "debug",
+      "presubmit",
+      "--package-root",
+      "[CACHE]",
+      "--output-directory",
+      "[START_DIR]/presubmit",
+      "--program",
+      "full",
+      "--only-list-steps"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get steps from programs.full",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_0"
+  },
+  {
+    "cmd": [
+      "foo",
+      "--directory",
+      "[START_DIR]/checkout",
+      "--loglevel",
+      "debug",
+      "presubmit",
+      "--package-root",
+      "[CACHE]",
+      "--output-directory",
+      "[START_DIR]/presubmit",
+      "--step",
+      "full_0"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_0.run",
+    "timeout": 30,
+    "~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",
+      "[START_DIR]/presubmit/full_0/export"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_0.mkdir export",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/full_0/ninja.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_0.ninja.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
+      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
+      "@@@STEP_LOG_END@ninja.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_0.longest build steps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_0.longest build steps.long",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_0.longest build steps.medium",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_0.longest build steps.short",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_0.copy",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/full_0/ninja.log",
+      "[START_DIR]/presubmit/full_0/export/ninja.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_0.copy.ninja.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_1"
+  },
+  {
+    "cmd": [
+      "foo",
+      "--directory",
+      "[START_DIR]/checkout",
+      "--loglevel",
+      "debug",
+      "presubmit",
+      "--package-root",
+      "[CACHE]",
+      "--output-directory",
+      "[START_DIR]/presubmit",
+      "--step",
+      "full_1"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_1.run",
+    "timeout": 30,
+    "~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",
+      "[START_DIR]/presubmit/full_1/export"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_1.mkdir export",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/full_1/ninja.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_1.ninja.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
+      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
+      "@@@STEP_LOG_END@ninja.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_1.longest build steps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_1.longest build steps.long",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_1.longest build steps.medium",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_1.longest build steps.short",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "full_1.copy",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/full_1/ninja.log",
+      "[START_DIR]/presubmit/full_1/export/ninja.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "full_1.copy.ninja.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "binary sizes full_0"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/full_0/export/binary_sizes.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "binary sizes full_0.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@{@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@  \"target\": 12345,@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@  \"target.budget\": 12346@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@}@@@",
+      "@@@STEP_LOG_END@binary_sizes.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "binary sizes full_1"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/full_1/export/binary_sizes.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "binary sizes full_1.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@{@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@  \"target\": 12345,@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@  \"target.budget\": 12346@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@}@@@",
+      "@@@STEP_LOG_END@binary_sizes.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "binary sizes",
+    "~followup_annotations": [
+      "@@@SET_BUILD_PROPERTY@binary_sizes@{\"full_0.target\": 12345, \"full_0.target.budget\": 12346, \"full_1.target\": 12345, \"full_1.target.budget\": 12346}@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/pw_presubmit.expected/repo.json b/recipes/pw_presubmit.expected/repo.json
new file mode 100644
index 0000000..96c7a5d
--- /dev/null
+++ b/recipes/pw_presubmit.expected/repo.json
@@ -0,0 +1,1270 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed",
+    "~followup_annotations": [
+      "@@@STEP_LINK@applied pigweed:1234@https://pigweed-review.googlesource.com/c/1234@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/tool_manifest.json",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@{@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"path\": \"path/to/gerrit\",@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"version\": \"version:pinned-version\"@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@}@@@",
+      "@@@STEP_LOG_END@tool_manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/path/to/gerrit/version%3Apinned-version"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/path/to/gerrit/version%3Apinned-version",
+      "-ensure-file",
+      "path/to/gerrit version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.ensure gerrit.install path/to/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@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-version:pinned-v\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"path/to/gerrit\"@@@",
+      "@@@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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.change data.process gitiles commit.number",
+    "timeout": 30,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"_number\": \"1234\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234, remote='https://pigweed.googlesource.com/pigweed/manifest', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, branch='main', gerrit_name='pigweed', submitted=True, base=None, base_type=None)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.mkdir checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "RECIPE_MODULE[pigweed::repo]/resources/repo",
+      "init",
+      "--manifest-url",
+      "https://pigweed.googlesource.com/pigweed/manifest",
+      "--groups",
+      "all",
+      "--manifest-branch",
+      "main",
+      "--manifest-name",
+      "default.xml"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.repo init",
+    "timeout": 20,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "find",
+      ".repo/",
+      "-type",
+      "f",
+      "-name",
+      "*.lock",
+      "-print",
+      "-delete"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.clear repo locks",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "RECIPE_MODULE[pigweed::repo]/resources/repo",
+      "forall",
+      "--ignore-missing",
+      "-j",
+      "32",
+      "-c",
+      "find",
+      ".git/",
+      "-type",
+      "f",
+      "-name",
+      "*.lock",
+      "-print",
+      "-delete"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.clear git locks",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://pigweed-review.googlesource.com/c/1234@@@",
+      "@@@STEP_LINK@gitiles@https://pigweed.googlesource.com/pigweed/manifest/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://pigweed.googlesource.com/pigweed/manifest",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
+      "--no-recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout/.repo/manifests",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git fetch patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--force",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/.repo/manifests",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/.repo/manifests",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout/.repo/manifests",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "branch",
+      "--set-upstream-to=origin/main"
+    ],
+    "cwd": "[START_DIR]/checkout/.repo/manifests",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.apply pigweed:1234.git branch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw@<?xml version=\"1.0\" encoding=\"UTF-8\"?>@@@",
+      "@@@STEP_LOG_LINE@raw@<manifest>@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"sso://default\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"..\"@@@",
+      "@@@STEP_LOG_LINE@raw@    review=\"https://pigweed.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"https://pigweed-internal.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    review=\"https://pigweed-internal.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"prefixed\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"https://foo.googlesource.com/prefix\"@@@",
+      "@@@STEP_LOG_LINE@raw@    review=\"https://foo.googlesource.com/prefix\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <default@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"default_name\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"default_path\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed_name\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"pigweed_path\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed-internal_name\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"pigweed-internal_path\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"0123456789012345678901234567890123456789\"@@@",
+      "@@@STEP_LOG_LINE@raw@    upstream=\"main\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"suffix\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"prefix/suffix\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"prefixed\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@</manifest>@@@",
+      "@@@STEP_LOG_END@raw@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/.repo/manifests/default.xml",
+      "/path/to/tmp/"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.read manifest.read file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@default.xml@<?xml version=\"1.0\" encoding=\"UTF-8\"?>@@@",
+      "@@@STEP_LOG_LINE@default.xml@<manifest>@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"sso://default\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"..\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    review=\"https://pigweed.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"https://pigweed-internal.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    review=\"https://pigweed-internal.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"prefixed\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"https://foo.googlesource.com/prefix\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    review=\"https://foo.googlesource.com/prefix\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <default@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"default_name\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"default_path\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed_name\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"pigweed_path\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"main\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed-internal_name\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"pigweed-internal_path\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"0123456789012345678901234567890123456789\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    upstream=\"main\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"suffix\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"prefix/suffix\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"prefixed\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@</manifest>@@@",
+      "@@@STEP_LOG_END@default.xml@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "{\"projects\": [{\"name\": \"default_name\", \"path\": \"default_path\", \"remote\": \"default_remote\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://default.googlesource.com/default_name\"}, {\"name\": \"pigweed_name\", \"path\": \"pigweed_path\", \"remote\": \"pigweed_remote\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://pigweed.googlesource.com/pigweed_name\"}, {\"name\": \"pigweed-internal_name\", \"path\": \"pigweed-internal_path\", \"remote\": \"pigweed-internal_remote\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://pigweed-internal.googlesource.com/pigweed-internal_name\"}, {\"name\": \"pinned\", \"path\": \"pinned\", \"remote\": \"pigweed_remote\", \"revision\": \"0123456789012345678901234567890123456789\", \"upstream\": \"main\", \"url\": \"https://pigweed.googlesource.com/pinned\"}, {\"name\": \"suffix\", \"path\": \"prefix/suffix\", \"remote\": \"prefixed\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://foo.googlesource.com/prefix/suffix\"}], \"remotes\": {\"default_remote\": {\"alias\": null, \"fetch\": {\"https\": \"https://default.googlesource.com\", \"url\": \"sso://default\"}, \"name\": \"default_remote\", \"review\": null, \"revision\": null}, \"pigweed-internal_remote\": {\"alias\": null, \"fetch\": {\"https\": \"https://pigweed-internal.googlesource.com\", \"url\": \"https://pigweed-internal.googlesource.com\"}, \"name\": \"pigweed-internal_remote\", \"review\": \"https://pigweed-internal.googlesource.com\", \"revision\": \"main\"}, \"pigweed_remote\": {\"alias\": null, \"fetch\": {\"https\": \"https://pigweed.googlesource.com\", \"url\": \"https://pigweed.googlesource.com\"}, \"name\": \"pigweed_remote\", \"review\": \"https://pigweed.googlesource.com\", \"revision\": \"main\"}, \"prefixed\": {\"alias\": null, \"fetch\": {\"https\": \"https://foo.googlesource.com/prefix\", \"url\": \"https://foo.googlesource.com/prefix\"}, \"name\": \"prefixed\", \"review\": \"https://foo.googlesource.com/prefix\", \"revision\": null}}}",
+      "[START_DIR]/manifest.json"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.read manifest.manifest json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@manifest.json@{\"projects\": [{\"name\": \"default_name\", \"path\": \"default_path\", \"remote\": \"default_remote\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://default.googlesource.com/default_name\"}, {\"name\": \"pigweed_name\", \"path\": \"pigweed_path\", \"remote\": \"pigweed_remote\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://pigweed.googlesource.com/pigweed_name\"}, {\"name\": \"pigweed-internal_name\", \"path\": \"pigweed-internal_path\", \"remote\": \"pigweed-internal_remote\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://pigweed-internal.googlesource.com/pigweed-internal_name\"}, {\"name\": \"pinned\", \"path\": \"pinned\", \"remote\": \"pigweed_remote\", \"revision\": \"0123456789012345678901234567890123456789\", \"upstream\": \"main\", \"url\": \"https://pigweed.googlesource.com/pinned\"}, {\"name\": \"suffix\", \"path\": \"prefix/suffix\", \"remote\": \"prefixed\", \"revision\": \"main\", \"upstream\": \"main\", \"url\": \"https://foo.googlesource.com/prefix/suffix\"}], \"remotes\": {\"default_remote\": {\"alias\": null, \"fetch\": {\"https\": \"https://default.googlesource.com\", \"url\": \"sso://default\"}, \"name\": \"default_remote\", \"review\": null, \"revision\": null}, \"pigweed-internal_remote\": {\"alias\": null, \"fetch\": {\"https\": \"https://pigweed-internal.googlesource.com\", \"url\": \"https://pigweed-internal.googlesource.com\"}, \"name\": \"pigweed-internal_remote\", \"review\": \"https://pigweed-internal.googlesource.com\", \"revision\": \"main\"}, \"pigweed_remote\": {\"alias\": null, \"fetch\": {\"https\": \"https://pigweed.googlesource.com\", \"url\": \"https://pigweed.googlesource.com\"}, \"name\": \"pigweed_remote\", \"review\": \"https://pigweed.googlesource.com\", \"revision\": \"main\"}, \"prefixed\": {\"alias\": null, \"fetch\": {\"https\": \"https://foo.googlesource.com/prefix\", \"url\": \"https://foo.googlesource.com/prefix\"}, \"name\": \"prefixed\", \"review\": \"https://foo.googlesource.com/prefix\", \"revision\": null}}}@@@",
+      "@@@STEP_LOG_END@manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--global",
+      "--add",
+      "url.https://default.googlesource.com/a.insteadof",
+      "sso://default"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git insteadof sso://default",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "RECIPE_MODULE[pigweed::repo]/resources/repo",
+      "sync",
+      "--force-sync",
+      "--current-branch",
+      "--jobs",
+      "2"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.repo sync",
+    "timeout": 120,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "RECIPE_MODULE[pigweed::repo]/resources/repo",
+      "start",
+      "base",
+      "--all"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.repo start",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@applied [_Change(number=1234, remote='https://pigweed.googlesource.com/pigweed/manifest', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, branch='main', gerrit_name='pigweed', submitted=True, base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash')]\nnot applied []@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.root",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@root=[START_DIR]/checkout\nself._root=[START_DIR]/checkout\n@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.ls",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "RECIPE_MODULE[pigweed::repo]/resources/repo",
+      "manifest",
+      "-r"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.repo manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output_text@<manifest></manifest>@@@",
+      "@@@STEP_LOG_END@raw_io.output_text@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "<manifest></manifest>",
+      "[START_DIR]/snapshot/manifest.xml"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write manifest.xml",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@manifest.xml@<manifest></manifest>@@@",
+      "@@@STEP_LOG_END@manifest.xml@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "submodule status filler text",
+      "[START_DIR]/snapshot/submodules.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write submodule snapshot",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@submodules.log@submodule status filler text@@@",
+      "@@@STEP_LOG_END@submodules.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[START_DIR]/snapshot/git.log"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "environment"
+  },
+  {
+    "cmd": [],
+    "name": "step"
+  },
+  {
+    "cmd": [
+      "python",
+      "-m",
+      "pw_cli",
+      "--directory",
+      "[START_DIR]/checkout",
+      "--loglevel",
+      "debug",
+      "presubmit",
+      "--package-root",
+      "[CACHE]",
+      "--output-directory",
+      "[START_DIR]/presubmit",
+      "--base",
+      "HEAD~1",
+      "--step",
+      "step"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step.run",
+    "timeout": 30,
+    "~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",
+      "[START_DIR]/presubmit/step/export"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step.mkdir export",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/step/ninja.log",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step.ninja.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@ninja.log@2000 5000 0 medium 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@3000 8000 0 long 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@malformed line@@@",
+      "@@@STEP_LOG_LINE@ninja.log@4000 5000 0 short 0@@@",
+      "@@@STEP_LOG_LINE@ninja.log@5000 x 0 malformed-end-time 0@@@",
+      "@@@STEP_LOG_END@ninja.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "step.longest build steps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "step.longest build steps.long",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@5.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "step.longest build steps.medium",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@3.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "step.longest build steps.short",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@1.0s@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "step.copy",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/step/ninja.log",
+      "[START_DIR]/presubmit/step/export/ninja.log"
+    ],
+    "env": {
+      "BUILDBUCKET_ID": "8945511751514863184",
+      "BUILDBUCKET_NAME": "project:ci:builder",
+      "BUILD_NUMBER": "0",
+      "GOCACHE": "[CACHE]/go",
+      "PIP_CACHE_DIR": "[CACHE]/pip",
+      "PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED": "1",
+      "PW_ENVSETUP_DISABLE_SPINNER": "1",
+      "PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE": "1",
+      "PW_PROJECT_ROOT": "[START_DIR]/checkout",
+      "PW_ROOT": "[START_DIR]/checkout",
+      "TEST_TMPDIR": "[CACHE]/bazel"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step.copy.ninja.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "binary sizes step"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/presubmit/step/export/binary_sizes.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "binary sizes step.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@{@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@  \"target\": 12345,@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@  \"target.budget\": 12346@@@",
+      "@@@STEP_LOG_LINE@binary_sizes.json@}@@@",
+      "@@@STEP_LOG_END@binary_sizes.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "binary sizes",
+    "~followup_annotations": [
+      "@@@SET_BUILD_PROPERTY@binary_sizes@{\"target\": 12345, \"target.budget\": 12346}@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/pw_presubmit.expected/sign-nobuildid.json b/recipes/pw_presubmit.expected/sign-nobuildid.json
index 9076be8..1580b1d 100644
--- a/recipes/pw_presubmit.expected/sign-nobuildid.json
+++ b/recipes/pw_presubmit.expected/sign-nobuildid.json
@@ -1167,7 +1167,7 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "release.release",
+    "name": "release.run",
     "timeout": 30,
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
@@ -1560,6 +1560,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "listdir",
+      "[START_DIR]/presubmit"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "upload.ls presubmit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/presubmit/release@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
       "[START_DIR]/presubmit/release/export",
       "--recursive"
     ],
diff --git a/recipes/pw_presubmit.expected/sign.json b/recipes/pw_presubmit.expected/sign.json
index 1bc1f1b..7c86575 100644
--- a/recipes/pw_presubmit.expected/sign.json
+++ b/recipes/pw_presubmit.expected/sign.json
@@ -1167,7 +1167,7 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "release.release",
+    "name": "release.run",
     "timeout": 30,
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
@@ -1560,6 +1560,36 @@
       "--json-output",
       "/path/to/tmp/json",
       "listdir",
+      "[START_DIR]/presubmit"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "upload.ls presubmit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/presubmit/release@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
       "[START_DIR]/presubmit/release/export",
       "--recursive"
     ],
diff --git a/recipes/pw_presubmit.expected/step.json b/recipes/pw_presubmit.expected/step.json
index bbd65b5..c260f34 100644
--- a/recipes/pw_presubmit.expected/step.json
+++ b/recipes/pw_presubmit.expected/step.json
@@ -1491,7 +1491,7 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "step1.step1",
+    "name": "step1.run",
     "timeout": 40.0,
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
@@ -1714,7 +1714,7 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "step2.step2",
+    "name": "step2.run",
     "timeout": 30,
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
@@ -2145,6 +2145,37 @@
       "--json-output",
       "/path/to/tmp/json",
       "listdir",
+      "[START_DIR]/presubmit"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "upload.ls presubmit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/presubmit/step1@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/presubmit/step2@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
       "[START_DIR]/presubmit/step1/export",
       "--recursive"
     ],
diff --git a/recipes/pw_presubmit.proto b/recipes/pw_presubmit.proto
index 15b16b8..7165534 100644
--- a/recipes/pw_presubmit.proto
+++ b/recipes/pw_presubmit.proto
@@ -19,9 +19,28 @@
 package recipes.pigweed.pw_presubmit;
 
 message InputProperties {
+  // Command name in case it's changed by a dependent project. Default if unset
+  // is "pw".
+  string command_name = 1;
+
+  // Step to run from 'pw presubmit'. See 'pw presubmit --help' for options.
+  // Default is to run all steps.
+  repeated string step = 2;
+
   // Bucket to upload build to. Default is to not upload.
-  string gcs_bucket = 1;
+  string gcs_bucket = 3;
 
   // File extensions to sign. (E.g., ".out", ".sign-me", ...)
-  repeated string extensions_to_sign = 2;
+  repeated string extensions_to_sign = 4;
+
+  // Program to run from 'pw presubmit'. See 'pw presubmit --help' for options.
+  // Default is to let 'pw presubmit' run its default program.
+  repeated string program = 5;
+
+  // Only run this check against changed files.
+  bool only_on_changed_files = 6;
+
+  // Subdirectory of the build directory to upload to GCS. Required to upload
+  // build artifacts.
+  string export_dir_name = 7;
 }
diff --git a/recipes/pw_presubmit.py b/recipes/pw_presubmit.py
index 33be891..9e86ee6 100644
--- a/recipes/pw_presubmit.py
+++ b/recipes/pw_presubmit.py
@@ -22,10 +22,11 @@
 DEPS = [
     'fuchsia/gsutil',
     'fuchsia/status_check',
+    'pigweed/build',
     'pigweed/checkout',
     'pigweed/environment',
-    'pigweed/pw_presubmit',
     'pigweed/util',
+    'recipe_engine/buildbucket',
     'recipe_engine/file',
     'recipe_engine/futures',
     'recipe_engine/json',
@@ -49,6 +50,30 @@
 RELEASE_PUBKEY_FILENAME = 'publickey.pem'
 
 
+def _step_timeout(api):
+    # Amount of time elapsed in the run.
+    elapsed_time = api.time.time() - api.buildbucket.build.start_time.seconds
+
+    # Amount of time before build times out.
+    time_remaining = (
+        api.buildbucket.build.execution_timeout.seconds - elapsed_time
+    )
+
+    # Give a buffer before build times out and kill this step then. This should
+    # give enough time to read any logfiles and maybe upload to logdog/GCS
+    # before the build times out.
+    step_timeout = time_remaining - 60
+
+    # If the timeout would be negative or very small set it to 30 seconds. We
+    # likely won't have enough information to debug these steps, but in case
+    # they're fast there's no reason to kill them much before the build is
+    # terminated.
+    if step_timeout < 30:
+        step_timeout = 30
+
+    return step_timeout
+
+
 def _try_sign_archive(api, archive_path, name):
     args = [
         '--archive-file',
@@ -66,44 +91,108 @@
 
 def RunSteps(api, props):
     """Run Pigweed presubmit checks."""
+    # TODO(mohrr) Transition to passing a list in through properties.
+    command_name = props.command_name or 'python -m pw_cli'
     gcs_bucket = props.gcs_bucket
 
     api.checkout()
     root = api.checkout.root
 
+    if not props.step and not props.program:
+        with api.step.nest('nothing to do, exiting'):
+            return
+
     api.environment.init(root)
-    api.pw_presubmit.init(root)
+
+    presubmit_dir = api.path['start_dir'].join('presubmit')
+
+    prefix = command_name.split()
+    prefix += [
+        '--directory',
+        root,
+        '--loglevel',
+        'debug',
+        'presubmit',
+        '--package-root',
+        api.path['cache'],
+        '--output-directory',
+        presubmit_dir,
+    ]
+
+    if props.only_on_changed_files:
+        prefix.extend(('--base', 'HEAD~1'))
 
     with api.environment():
+        steps = []
+        steps.extend(props.step)
+
+        if props.program:
+            with api.step.nest('get steps from programs'):
+                for program in props.program:
+                    # To get step_test_data line to pass pylint.
+                    raw_io_stream_output = (
+                        api.raw_io.test_api.stream_output_text
+                    )
+
+                    program_steps = (
+                        api.step(
+                            program,
+                            prefix
+                            + ['--program', program, '--only-list-steps'],
+                            stdout=api.raw_io.output_text(),
+                            step_test_data=lambda: raw_io_stream_output(
+                                '{0}_0\n{0}_1\n'.format(program),
+                            ),
+                        )
+                        .stdout.strip()
+                        .splitlines()
+                    )
+                    steps.extend(x for x in program_steps if x not in steps)
+
         with api.step.defer_results():
-            for step in api.pw_presubmit.steps():
-                api.pw_presubmit.run(step)
+            for step in steps:
+                with api.step.nest(step) as pres:
+                    api.step(
+                        'run',
+                        prefix + ['--step', step],
+                        timeout=_step_timeout(api),
+                    )
 
-    binary_size_data = {}
-    for step in api.pw_presubmit.steps():
-        if not step.export_dir:
-            continue  # pragma: no cover
+                    build_dir = presubmit_dir.join(step)
+                    export_dir = None
+                    if props.export_dir_name:
+                        export_dir = build_dir.join(props.export_dir_name)
+                        api.file.ensure_directory(
+                            'mkdir {}'.format(props.export_dir_name),
+                            export_dir,
+                        )
 
-        binary_sizes_json = step.export_dir.join('binary_sizes.json')
+                    api.build.save_logs(build_dir, export_dir)
 
-        api.path.mock_add_file(binary_sizes_json)
-        if api.path.isfile(binary_sizes_json):
-            with api.step.nest('binary sizes {}'.format(step.name)):
-                binary_size_data[step.name] = api.file.read_json(
-                    'read',
-                    binary_sizes_json,
-                    test_data={'target': 12345, 'target.budget': 12346},
-                )
+    if props.export_dir_name:
+        binary_size_data = {}
+        for step in steps:
+            binary_sizes_json = presubmit_dir.join(
+                step, props.export_dir_name, 'binary_sizes.json'
+            )
 
-    binary_sizes = {}
-    if len(binary_size_data) == 1:
-        _, binary_sizes = binary_size_data.popitem()
-    elif len(binary_size_data) > 1:
-        for step_name, values in binary_size_data.items():
-            for name, size in values.items():
-                binary_sizes['{}.{}'.format(step_name, name)] = size
+            api.path.mock_add_file(binary_sizes_json)
+            if api.path.isfile(binary_sizes_json):
+                with api.step.nest('binary sizes {}'.format(step)):
+                    binary_size_data[step] = api.file.read_json(
+                        'read',
+                        binary_sizes_json,
+                        test_data={'target': 12345, 'target.budget': 12346},
+                    )
 
-    if binary_sizes:
+        binary_sizes = {}
+        if len(binary_size_data) == 1:
+            _, binary_sizes = binary_size_data.popitem()
+        elif len(binary_size_data) > 1:
+            for step, values in binary_size_data.items():
+                for name, size in values.items():
+                    binary_sizes['{}.{}'.format(step, name)] = size
+
         with api.step.nest('binary sizes') as pres:
             pres.properties['binary_sizes'] = binary_sizes
 
@@ -112,7 +201,7 @@
 
         with api.step.nest('upload') as pres:
             with api.environment():
-                command = api.pw_presubmit.command_name.split()
+                command = command_name.split()
                 command.extend(['--directory', root, 'build-id'])
                 step_data = api.step(
                     'get build id',
@@ -152,62 +241,60 @@
                 )
             )
 
-            for step in api.pw_presubmit.steps():
-                if not api.pw_presubmit.export_dir_name:
-                    continue  # pragma: no cover
-
-                step_dir = api.pw_presubmit.root.join(step.name)
-                export_dir = step_dir.join(api.pw_presubmit.export_dir_name)
-
-                # In testing this will never be true because of the
-                # mock_add_file() call for binary_sizes.json.
-                if not api.path.exists(export_dir):
-                    continue  # pragma: no cover
-
-                for entry in api.file.listdir(
-                    'ls {}/{}'.format(
-                        step.name, api.pw_presubmit.export_dir_name,
-                    ),
-                    export_dir,
-                    recursive=True,
+            if props.export_dir_name:
+                for step_dir in api.file.listdir(
+                    'ls presubmit', presubmit_dir, test_data=props.step,
                 ):
-                    metadata = None
+                    step_name = api.path.basename(step_dir)
+                    export_dir = step_dir.join(props.export_dir_name)
+                    # In testing this will never be true because of the
+                    # mock_add_file() call for binary_sizes.json.
+                    if not api.path.exists(export_dir):
+                        continue  # pragma: no cover
 
-                    ext = api.path.splitext(entry)[1]
-                    if ext in props.extensions_to_sign:
-                        signature = _try_sign_archive(
-                            api,
-                            entry,
-                            name=api.path.relpath(entry, api.pw_presubmit.root),
-                        )
-                        if signature:
-                            metadata = {
-                                "x-goog-meta-signature": signature,
-                            }
-                            if not uploaded_public_key:
-                                futures.append(
-                                    api.futures.spawn(
-                                        api.gsutil.upload_namespaced_file,
-                                        source=RELEASE_PUBKEY_PATH,
-                                        bucket=gcs_bucket,
-                                        subpath=RELEASE_PUBKEY_FILENAME,
-                                        namespace=namespace,
+                    for entry in api.file.listdir(
+                        'ls {}/{}'.format(step_name, props.export_dir_name),
+                        export_dir,
+                        recursive=True,
+                    ):
+                        metadata = None
+
+                        ext = api.path.splitext(entry)[1]
+                        if ext in props.extensions_to_sign:
+                            signature = _try_sign_archive(
+                                api,
+                                entry,
+                                name=api.path.relpath(entry, presubmit_dir),
+                            )
+                            if signature:
+                                metadata = {
+                                    "x-goog-meta-signature": signature,
+                                }
+                                if not uploaded_public_key:
+                                    futures.append(
+                                        api.futures.spawn(
+                                            api.gsutil.upload_namespaced_file,
+                                            source=RELEASE_PUBKEY_PATH,
+                                            bucket=gcs_bucket,
+                                            subpath=RELEASE_PUBKEY_FILENAME,
+                                            namespace=namespace,
+                                        )
                                     )
-                                )
-                                uploaded_public_key = True
+                                    uploaded_public_key = True
 
-                    futures.append(
-                        api.futures.spawn(
-                            api.gsutil.upload_namespaced_file,
-                            source=entry,
-                            bucket=gcs_bucket,
-                            subpath='{}/{}'.format(
-                                step.name, api.path.relpath(entry, export_dir),
-                            ),
-                            namespace=namespace,
-                            metadata=metadata,
+                        futures.append(
+                            api.futures.spawn(
+                                api.gsutil.upload_namespaced_file,
+                                source=entry,
+                                bucket=gcs_bucket,
+                                subpath='{}/{}'.format(
+                                    step_name,
+                                    api.path.relpath(entry, export_dir),
+                                ),
+                                namespace=namespace,
+                                metadata=metadata,
+                            )
                         )
-                    )
 
             # Need to wait for results but don't care about their values.
             _ = [f.result() for f in futures]
@@ -248,15 +335,23 @@
 
     def properties(**kwargs):
         new_kwargs = api.checkout.git_properties()
-        new_kwargs['$pigweed/pw_presubmit'] = {'export_dir_name': 'export'}
-        if 'gcs_bucket' in kwargs:
-            new_kwargs['gcs_bucket'] = kwargs.pop('gcs_bucket')
-        if 'extensions_to_sign' in kwargs:
-            new_kwargs['extensions_to_sign'] = kwargs.pop('extensions_to_sign')
-        new_kwargs['$pigweed/pw_presubmit'].update(kwargs)
+        new_kwargs['export_dir_name'] = 'export'
+        new_kwargs.update(kwargs)
         return api.properties(**new_kwargs)
 
     yield (
+        api.status_check.test('exit-early')
+        + properties()
+        + api.checkout.ci_test_data()
+    )
+
+    yield (
+        api.status_check.test('pigweed')
+        + properties(command_name='foo', program=['full'])
+        + api.checkout.ci_test_data()
+    )
+
+    yield (
         api.status_check.test('step')
         + properties(step=['step1', 'step2'], gcs_bucket='bucket')
         + api.checkout.try_test_data(
@@ -269,6 +364,18 @@
         + api.time.step(20.0)
     )
 
+    manifest = 'https://pigweed.googlesource.com/pigweed/manifest'
+    yield (
+        api.status_check.test('repo')
+        + properties(
+            step=['step'],
+            only_on_changed_files=True,
+            **api.checkout.repo_properties(remote=manifest)
+        )
+        + api.checkout.ci_test_data(manifest)
+        + api.checkout.manifest_test_data(name='pigweed')
+    )
+
     yield (
         api.status_check.test('sign')
         + properties(