pw_presubmit: Read from output_properties.json

Bug: b/245788264
Change-Id: If00b180c482478dac766acd16e5580d6d4382233
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/122954
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/recipe_modules/pw_presubmit/api.py b/recipe_modules/pw_presubmit/api.py
index 56d585f..2f27d9e 100644
--- a/recipe_modules/pw_presubmit/api.py
+++ b/recipe_modules/pw_presubmit/api.py
@@ -21,6 +21,7 @@
 METADATA = {
     'binary_sizes': (('target', 12345), ('target.budget', 12346)),
     'test_runtimes': (('target', 200), ('target.max', 250)),
+    'output_properties': (),
 }
 
 
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json b/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json
index 13580a6..82dcb09 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/bad-json-steps.json
@@ -745,6 +745,44 @@
   },
   {
     "cmd": [],
+    "name": "program_0.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/program_0/export/output_properties.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": "program_0.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "program_1",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1397,6 +1435,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "program_1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/program_1/export/output_properties.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": "program_1.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json
index ca77c4d..9d789dd 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-disallowed-host.json
@@ -729,6 +729,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1383,6 +1421,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json
index e0043a8..f898340 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-always-no-cl.json
@@ -729,6 +729,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.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": "step1.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1383,6 +1421,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.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": "step2.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json
index 095df1f..d68912b 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-always.json
@@ -892,6 +892,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1589,6 +1627,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json b/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json
index fd82401..cdcaceb 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/comment-on-failure.json
@@ -729,6 +729,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1383,6 +1421,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/long.json b/recipe_modules/pw_presubmit/tests/full.expected/long.json
index 6fdb738..3489e65 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/long.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/long.json
@@ -729,6 +729,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1383,6 +1421,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/medium.json b/recipe_modules/pw_presubmit/tests/full.expected/medium.json
index 6ae390a..b8a23ad 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/medium.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/medium.json
@@ -729,6 +729,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1383,6 +1421,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json b/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
index 306905b..943f8d6 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/pigweed.json
@@ -727,6 +727,44 @@
   },
   {
     "cmd": [],
+    "name": "full_0.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/full_0/export/output_properties.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": "full_0.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "full_1",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1381,6 +1419,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "full_1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/full_1/export/output_properties.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": "full_1.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "foo",
       "--directory",
diff --git a/recipe_modules/pw_presubmit/tests/full.expected/step.json b/recipe_modules/pw_presubmit/tests/full.expected/step.json
index 017b3b3..9742f09 100644
--- a/recipe_modules/pw_presubmit/tests/full.expected/step.json
+++ b/recipe_modules/pw_presubmit/tests/full.expected/step.json
@@ -729,6 +729,44 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step1/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -1383,6 +1421,44 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/p/step2/export/output_properties.json",
+      "/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.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "python",
       "-m",
diff --git a/recipes/pw_presubmit.expected/one_step.json b/recipes/pw_presubmit.expected/one_step.json
index 04d9e0d..acaeb26 100644
--- a/recipes/pw_presubmit.expected/one_step.json
+++ b/recipes/pw_presubmit.expected/one_step.json
@@ -2500,9 +2500,51 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/co/p/step1/export/output_properties.json",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "PW_TEST_VAR": "test_value"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "metadata",
     "~followup_annotations": [
       "@@@SET_BUILD_PROPERTY@binary_sizes@{\"step1.target\": 12345, \"step1.target.budget\": 12346, \"target\": 12345, \"target.budget\": 12346}@@@",
+      "@@@SET_BUILD_PROPERTY@output_properties@{}@@@",
       "@@@SET_BUILD_PROPERTY@test_runtimes@{\"step1.target\": 200, \"step1.target.max\": 250, \"target\": 200, \"target.max\": 250}@@@"
     ]
   },
diff --git a/recipes/pw_presubmit.expected/sign.json b/recipes/pw_presubmit.expected/sign.json
index 422213d..ab482f0 100644
--- a/recipes/pw_presubmit.expected/sign.json
+++ b/recipes/pw_presubmit.expected/sign.json
@@ -2235,9 +2235,51 @@
   },
   {
     "cmd": [],
+    "name": "release.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/co/p/release/export/output_properties.json",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "PW_TEST_VAR": "test_value"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "release.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "metadata",
     "~followup_annotations": [
       "@@@SET_BUILD_PROPERTY@binary_sizes@{\"target\": 12345, \"target.budget\": 12346}@@@",
+      "@@@SET_BUILD_PROPERTY@output_properties@{}@@@",
       "@@@SET_BUILD_PROPERTY@test_runtimes@{\"target\": 200, \"target.max\": 250}@@@"
     ]
   },
diff --git a/recipes/pw_presubmit.expected/two_steps.json b/recipes/pw_presubmit.expected/two_steps.json
index 8fd1141..eba63b4 100644
--- a/recipes/pw_presubmit.expected/two_steps.json
+++ b/recipes/pw_presubmit.expected/two_steps.json
@@ -2442,6 +2442,47 @@
   },
   {
     "cmd": [],
+    "name": "step1.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/co/p/step1/export/output_properties.json",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "PW_TEST_VAR": "test_value"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step1.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "step2",
     "~followup_annotations": [
       "@@@STEP_FAILURE@@@"
@@ -3114,9 +3155,51 @@
   },
   {
     "cmd": [],
+    "name": "step2.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/co/p/step2/export/output_properties.json",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "PW_TEST_VAR": "test_value"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "step2.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "metadata",
     "~followup_annotations": [
       "@@@SET_BUILD_PROPERTY@binary_sizes@{\"step1.target\": 12345, \"step1.target.budget\": 12346, \"step2.target\": 12345, \"step2.target.budget\": 12346}@@@",
+      "@@@SET_BUILD_PROPERTY@output_properties@{}@@@",
       "@@@SET_BUILD_PROPERTY@test_runtimes@{\"step1.target\": 200, \"step1.target.max\": 250, \"step2.target\": 200, \"step2.target.max\": 250}@@@"
     ]
   },
diff --git a/recipes/target_to_cipd.expected/pw-presubmit.json b/recipes/target_to_cipd.expected/pw-presubmit.json
index 2220d2f..156ae0e 100644
--- a/recipes/target_to_cipd.expected/pw-presubmit.json
+++ b/recipes/target_to_cipd.expected/pw-presubmit.json
@@ -1548,6 +1548,35 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "step.output_properties.json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/co/p/step/export/output_properties.json",
+      "/path/to/tmp/"
+    ],
+    "env": {
+      "PW_TEST_VAR": "test_value"
+    },
+    "infra_step": true,
+    "name": "step.output_properties.json.read",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@output_properties.json@{}@@@",
+      "@@@STEP_LOG_END@output_properties.json@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",