workflows: Add extra_bazel_arguments property

Bug: b/515196087
Change-Id: Ide4c49fd06191b6471579d361fc69e4a82213832
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/412835
diff --git a/recipes/workflows.proto b/recipes/workflows.proto
index 0a30830..4561dfa 100644
--- a/recipes/workflows.proto
+++ b/recipes/workflows.proto
@@ -41,4 +41,7 @@
   // Version of Bazel to use. If empty, bazelisk uses the .bazelversion file.
   // Cannot be set if the pigweed.latest_bazel experiment is active.
   string bazel_version = 7;
+
+  // Extra Bazel arguments.
+  repeated string extra_bazel_arguments = 8;
 }
diff --git a/recipes/workflows.py b/recipes/workflows.py
index e0c3154..2ddf9ad 100644
--- a/recipes/workflows.py
+++ b/recipes/workflows.py
@@ -18,7 +18,7 @@
 import contextlib
 import re
 import shlex
-from collections.abc import Iterator, Sequence
+from collections.abc import Iterator, Mapping, Sequence
 from typing import Any
 
 from recipe_engine import (
@@ -140,10 +140,16 @@
 
         workflow_output_base = api.path.cleanup_dir / 'workflows_launcher'
 
+        extra_bazel_arguments = _process_extra_bazel_arguments(
+            props.extra_bazel_arguments,
+            {'CHECKOUT_ROOT': checkout.root},
+        )
+
         bazel_args = [
             *api.bazel.rbe_arguments(remote=False, remote_cache=True),
             *api.bazel.override_arguments(checkout),
             *api.bazel.airlock_arguments(),
+            *extra_bazel_arguments,
         ]
         wrapped_bazel_args = [f'--extra-arg=bazel={x}' for x in bazel_args]
 
@@ -310,6 +316,24 @@
     return gcs_raw_result
 
 
+def _process_extra_bazel_arguments(
+    extra_bazel_arguments: Sequence[str],
+    variables: Mapping[str, str],
+):
+    result: list[str] = []
+    for arg in extra_bazel_arguments:
+
+        def repl(match: re.Match) -> str:
+            return str(variables.get(match.group(1), match.group(0)))
+
+        final_arg = arg
+        final_arg = re.sub(r'\$([\w]+)', repl, final_arg)
+        final_arg = re.sub(r'\$\{([\w]+)\}', repl, final_arg)
+        result.append(final_arg)
+
+    return result
+
+
 def _bazel_cache_context(api: recipe_api.RecipeScriptApi):
     # See b/441999854. Don't allow builds that have github/RBE disabled and
     # builds that don't to share Bazel caches. For now, do this by disabling
@@ -464,12 +488,33 @@
 
     yield api.test(
         'mac',
-        properties(),
+        properties(
+            extra_bazel_arguments=[
+                '--extra-arg=${CHECKOUT_ROOT}/with',
+                '--extra-arg=$CHECKOUT_ROOT/without',
+            ],
+        ),
         api.buildbucket.try_build(
             experiments=['pigweed.disable_rbe', 'pigweed.latest_bazel'],
             git_repo='https://pigweed.googlesource.com/pigweed/pigweed',
         ),
         api.platform.name('mac'),
+        api.post_check(  # One check that './pw' is invoked with the arg.
+            post_process.StepCommandContains,
+            'build test_build.run',
+            [
+                '--extra-arg=[START_DIR]/co/with',
+                '--extra-arg=[START_DIR]/co/without',
+            ],
+        ),
+        api.post_check(  # One check that the build is invoked with the arg.
+            post_process.StepCommandContains,
+            'build test_build.run',
+            [
+                '--extra-arg=bazel=--extra-arg=[START_DIR]/co/with',
+                '--extra-arg=bazel=--extra-arg=[START_DIR]/co/without',
+            ],
+        ),
         api.post_process(post_process.DropExpectation),
     )