| # Copyright 2026 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. |
| """Enable roll logic to be in-tree.""" |
| |
| from __future__ import annotations |
| |
| from collections.abc import Mapping, Sequence |
| from typing import Any |
| |
| from recipe_engine import recipe_api |
| |
| from PB.recipe_modules.pigweed.run_script_roll.run_script_entry import ( |
| RunScriptEntry, |
| ) |
| |
| from RECIPE_MODULES.fuchsia.roll_commit_message import ( |
| api as roll_commit_message_api, |
| ) |
| from RECIPE_MODULES.pigweed.checkout import api as checkout_api |
| |
| |
| class RunScriptRoll(roll_commit_message_api.BaseRoll): |
| """Represents a roll created by running an in-tree script.""" |
| |
| def __init__( |
| self, |
| name: str, |
| header: str, |
| body: str, |
| properties: Mapping[str, Any], |
| *args, |
| **kwargs, |
| ): |
| """Initializes a RunScriptRoll instance. |
| |
| Args: |
| name: Short name for the roll. |
| header: Short description of the roll. |
| body: Details of what changed in the roll. |
| *args: Additional arguments for the base class. |
| **kwargs: Additional keyword arguments for the base class. |
| """ |
| super().__init__(*args, **kwargs) |
| self.name = name |
| self.header = header |
| self.body = body |
| self.properties = properties |
| |
| def short_name(self) -> str: |
| """Returns a short name for the roll, typically the destination path.""" |
| return self.name |
| |
| def message_header(self, force_summary_version: bool = False) -> str: |
| """Generates the header for the commit message. |
| |
| Args: |
| force_summary_version: If True, a more concise header is generated. |
| |
| Returns: |
| The commit message header string. |
| """ |
| if force_summary_version: |
| return self.name |
| return f'{self.name}: {self.header}' |
| |
| def message_body( |
| self, |
| *, |
| force_summary_version: bool = False, |
| escape_tags: Sequence[str] = (), |
| filter_tags: Sequence[str] = (), |
| ) -> str: |
| """Generates the body of the commit message. |
| |
| Args: |
| force_summary_version: If True, a more concise body is generated. |
| escape_tags: A sequence of tags to escape in the message. |
| filter_tags: A sequence of tags to filter from the message. |
| |
| Returns: |
| The commit message body string. |
| """ |
| del force_summary_version, escape_tags, filter_tags # Unused. |
| return self.body |
| |
| def message_footer(self, *, send_comment: bool) -> str: |
| """Generates the footer for the commit message. |
| |
| Args: |
| send_comment: If True, indicates a comment should be sent. |
| |
| Returns: |
| An empty string, as run script rolls typically don't have footers. |
| """ |
| del send_comment # Unused. |
| return '' |
| |
| def output_property(self) -> dict[str, str]: |
| """Generates a dictionary of properties representing the roll. |
| |
| Returns: |
| A dictionary containing details of the roll. |
| """ |
| return self.properties |
| |
| |
| class RunScriptRollApi(recipe_api.RecipeApi): |
| """Allows for in-tree roll logic.""" |
| |
| RunScriptRoll = RunScriptRoll |
| |
| def run( |
| self, |
| checkout: checkout_api.CheckoutContext, |
| run_script_entry: RunScriptEntry, |
| ) -> list[RunScriptRoll]: |
| """Runs an in-tree script that modifies the checkout. |
| |
| Args: |
| checkout: The checkout context, providing repository information. |
| run_script_entry: A protobuf message defining the script to run. |
| |
| Returns: |
| A list containing a `RunScriptRoll` object if the tree was updated, |
| or an empty list if no updates were made. |
| """ |
| with self.m.step.nest(run_script_entry.script): |
| script = checkout.root / run_script_entry.script |
| |
| result = self.m.step( |
| 'run', |
| [script], |
| stdout=self.m.json.output(), |
| step_test_data=lambda: self.m.json.test_api.output_stream( |
| { |
| 'name': 'foo', |
| 'header': 'changed some files', |
| 'body': 'abc: changed\ndef: changed\nghi: changed\n', |
| 'properties': {'key': 'value'}, |
| } |
| ), |
| ).stdout |
| |
| if not result: |
| return [] |
| |
| return [ |
| RunScriptRoll( |
| name=result['name'], |
| header=result['header'], |
| body=result['body'], |
| properties=result['properties'], |
| ), |
| ] |