module_deps: Support subdirectories

Bug: b/522852278
Change-Id: I561e829bd3d80233b4113eb94ceb943ac5452989
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/427772
diff --git a/recipes/module_deps.proto b/recipes/module_deps.proto
index ff4142b..175a60b 100644
--- a/recipes/module_deps.proto
+++ b/recipes/module_deps.proto
@@ -32,4 +32,6 @@
   bool run_soong = 6;
 
   repeated string cmake_flags = 7;
+
+  string subdirectory = 8;
 }
diff --git a/recipes/module_deps.py b/recipes/module_deps.py
index da10321..236af50 100644
--- a/recipes/module_deps.py
+++ b/recipes/module_deps.py
@@ -54,7 +54,10 @@
     env = api.environment.init(checkout, props.environment_options)
 
     deps = set()
-    with env(), api.context(cwd=checkout.root):
+    cwd = checkout.root
+    if props.subdirectory:
+        cwd /= props.subdirectory
+    with env(), api.context(cwd=cwd):
         if props.run_bazel:
             deps.update(handle_bazel(api))
         if props.run_gn:
@@ -174,6 +177,17 @@
 def GenTests(
     api: recipe_test_api.RecipeTestApi,
 ) -> Iterator[recipe_test_api.TestData]:
+    def step_cwd_equals(check, step_odict, step_name, expected_cwd):
+        step = step_odict.get(step_name)
+        if check(f'step {step_name} exists', step is not None):
+            actual_cwd = (
+                step.cwd if hasattr(step, 'cwd') else step.get('cwd', '')
+            )
+            check(
+                f'step {step_name} cwd is {expected_cwd} (actual: {actual_cwd})',
+                actual_cwd == expected_cwd,
+            )
+
     def properties(**kwargs: Any) -> recipe_test_api.TestData:
         props = InputProperties(**kwargs)
         props.checkout_options.CopyFrom(api.checkout.git_options())
@@ -319,3 +333,25 @@
         ),
         api.post_process(post_process.DropExpectation),
     )
+
+    yield api.test(
+        'subdirectory',
+        properties(run_cmake=True, subdirectory='my_sub_dir'),
+        api.step_data(
+            'cmake.read graphviz',
+            api.file.read_text('pw_string'),
+        ),
+        api.post_check(post_process.MustRun, 'cmake.cmake configure'),
+        api.post_check(
+            step_cwd_equals,
+            'cmake.cmake configure',
+            '[START_DIR]/co/my_sub_dir',
+        ),
+        api.post_check(post_process.MustRun, 'cmake.cmake graphviz'),
+        api.post_check(
+            step_cwd_equals,
+            'cmake.cmake graphviz',
+            '[START_DIR]/co/my_sub_dir/out',
+        ),
+        api.post_process(post_process.DropExpectation),
+    )