pw_presubmit: Allow subclassing Presubmit

Allow subclassing the primary Presubmit class, and make it easy to use
a subclass of PresubmitContext.

Change-Id: Ia2e9c97209b2caf60fddd47f8ccaa6b716d3e48e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/110396
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_presubmit/docs.rst b/pw_presubmit/docs.rst
index 4083837..227839b 100644
--- a/pw_presubmit/docs.rst
+++ b/pw_presubmit/docs.rst
@@ -130,6 +130,11 @@
 * ``builder``: The builder being run
 * ``swarming_task_id``: The swarming task id of this build
 
+Additional members can be added by subclassing ``PresubmitContext`` and
+``Presubmit``. Then override ``Presubmit._create_presubmit_context()`` to
+return the subclass of ``PresubmitContext``. Finally, add
+``presubmit_class=PresubmitSubClass`` when calling ``cli.run()``.
+
 Existing Presubmit Checks
 -------------------------
 A small number of presubmit checks are made available through ``pw_presubmit``
diff --git a/pw_presubmit/py/pw_presubmit/presubmit.py b/pw_presubmit/py/pw_presubmit/presubmit.py
index 68a6f46..dad5362 100644
--- a/pw_presubmit/py/pw_presubmit/presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/presubmit.py
@@ -410,6 +410,11 @@
                 f'{total} checks on {plural(self._paths, "file")}: {summary}',
                 _format_time(time_s)))
 
+    def _create_presubmit_context(  # pylint: disable=no-self-use
+            self, **kwargs):
+        """Create a PresubmitContext. Override if needed in subclasses."""
+        return PresubmitContext(**kwargs)
+
     @contextlib.contextmanager
     def _context(self, name: str, paths: Tuple[Path, ...]):
         # There are many characters banned from filenames on Windows. To
@@ -426,7 +431,7 @@
         try:
             _LOG.addHandler(handler)
 
-            yield PresubmitContext(
+            yield self._create_presubmit_context(
                 root=self._root,
                 repos=self._repos,
                 output_dir=output_directory,
@@ -504,7 +509,8 @@
         only_list_steps: bool = False,
         override_gn_args: Sequence[Tuple[str, str]] = (),
         keep_going: bool = False,
-        continue_after_build_error: bool = False) -> bool:
+        continue_after_build_error: bool = False,
+        presubmit_class: type = Presubmit) -> bool:
     """Lists files in the current Git repo and runs a Presubmit with them.
 
     This changes the directory to the root of the Git repository after listing
@@ -531,6 +537,8 @@
         override_gn_args: additional GN args to set on steps
         keep_going: continue running presubmit steps after a step fails
         continue_after_build_error: continue building if a build step fails
+        presubmit_class: class to use to run Presubmits, should inherit from
+            Presubmit class above
 
     Returns:
         True if all presubmit checks succeeded
@@ -566,7 +574,7 @@
     if package_root is None:
         package_root = output_directory / 'packages'
 
-    presubmit = Presubmit(
+    presubmit = presubmit_class(
         root=root,
         repos=repos,
         output_directory=output_directory,