aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pw_presubmit/docs.rst14
-rw-r--r--pw_presubmit/py/pw_presubmit/presubmit.py106
2 files changed, 96 insertions, 24 deletions
diff --git a/pw_presubmit/docs.rst b/pw_presubmit/docs.rst
index f86a13211..f6ad4d43d 100644
--- a/pw_presubmit/docs.rst
+++ b/pw_presubmit/docs.rst
@@ -132,6 +132,7 @@ The ``luci`` member is of type ``LuciContext`` and has the following members:
* ``builder``: The builder being run
* ``swarming_task_id``: The swarming task id of this build
* ``pipeline``: Information about the build pipeline, if applicable.
+* ``triggers``: Information about triggering commits, if applicable.
The ``pipeline`` member, if present, is of type ``LuciPipeline`` and has the
following members:
@@ -140,6 +141,19 @@ following members:
* ``builds_from_previous_iteration``: A list of the buildbucket ids from the
previous round, if any.
+The ``triggers`` member is a sequence of ``LuciTrigger`` objects, which have the
+following members:
+
+* ``number``: The number of the change in Gerrit.
+* ``patchset``: The number of the patchset of the change.
+* ``remote``: The full URL of the remote.
+* ``branch``: The name of the branch on which this change is being/was
+ submitted.
+* ``ref``: The ``refs/changes/..`` path that can be used to reference the
+ patch for unsubmitted changes and the hash for submitted changes.
+* ``gerrit_name``: The name of the googlesource.com Gerrit host.
+* ``submitted``: Whether the change has been submitted or is still pending.
+
Additional members can be added by subclassing ``PresubmitContext`` and
``Presubmit``. Then override ``Presubmit._create_presubmit_context()`` to
return the subclass of ``PresubmitContext``. Finally, add
diff --git a/pw_presubmit/py/pw_presubmit/presubmit.py b/pw_presubmit/py/pw_presubmit/presubmit.py
index 09282188b..24f5309da 100644
--- a/pw_presubmit/py/pw_presubmit/presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/presubmit.py
@@ -216,6 +216,24 @@ class LuciPipeline:
round: int
builds_from_previous_iteration: Sequence[int]
+ @staticmethod
+ def create(bbid: int) -> Optional['LuciPipeline']:
+ pipeline_props = (
+ get_buildbucket_info(bbid)
+ .get('input', {})
+ .get('properties', {})
+ .get('$pigweed/pipeline', {})
+ )
+ if not pipeline_props.get('inside_a_pipeline', False):
+ return None
+
+ return LuciPipeline(
+ round=int(pipeline_props['round']),
+ builds_from_previous_iteration=[
+ int(x) for x in pipeline_props['builds_from_previous_iteration']
+ ],
+ )
+
def get_buildbucket_info(bbid) -> Dict[str, Any]:
if not bbid or not shutil.which('bb'):
@@ -228,6 +246,52 @@ def get_buildbucket_info(bbid) -> Dict[str, Any]:
@dataclasses.dataclass
+class LuciTrigger:
+ """Details the pending change or submitted commit triggering the build."""
+
+ number: int
+ remote: str
+ branch: str
+ ref: str
+ gerrit_name: str
+ submitted: bool
+
+ @property
+ def gerrit_url(self):
+ if not self.number:
+ return self.gitiles_url
+ return 'https://{}-review.googlesource.com/c/{}'.format(
+ self.gerrit_name, self.number
+ )
+
+ @property
+ def gitiles_url(self):
+ return '{}/+/{}'.format(self.remote, self.ref)
+
+ @staticmethod
+ def create_from_environment(
+ env: Optional[Dict[str, str]] = None,
+ ) -> Sequence['LuciTrigger']:
+ if not env:
+ env = os.environ.copy()
+ raw_path = env.get('TRIGGERING_CHANGES_JSON')
+ if not raw_path:
+ return ()
+ path = Path(raw_path)
+ if not path.is_file():
+ return ()
+
+ result = []
+ with open(path, 'r') as ins:
+ for trigger in json.load(ins):
+ keys = set('number remote branch ref gerrit_name submitted')
+ if keys <= trigger.keys():
+ result.append(LuciTrigger(**{x: trigger[x] for x in keys}))
+
+ return tuple(result)
+
+
+@dataclasses.dataclass
class LuciContext:
"""LUCI-specific information about the environment."""
@@ -238,55 +302,49 @@ class LuciContext:
builder: str
swarming_task_id: str
pipeline: Optional[LuciPipeline]
+ triggers: Sequence[LuciTrigger] = dataclasses.field(default_factory=tuple)
@staticmethod
- def create_from_environment():
+ def create_from_environment(
+ env: Optional[Dict[str, str]] = None
+ ) -> Optional['LuciContext']:
"""Create a LuciContext from the environment."""
+
+ if not env:
+ env = os.environ.copy()
+
luci_vars = [
'BUILDBUCKET_ID',
'BUILDBUCKET_NAME',
'BUILD_NUMBER',
'SWARMING_TASK_ID',
]
- if any(x for x in luci_vars if x not in os.environ):
+ if any(x for x in luci_vars if x not in env):
return None
- project, bucket, builder = os.environ['BUILDBUCKET_NAME'].split(':')
+ project, bucket, builder = env['BUILDBUCKET_NAME'].split(':')
bbid: int = 0
pipeline: Optional[LuciPipeline] = None
try:
- bbid = int(os.environ['BUILDBUCKET_ID'])
-
- pipeline_props = (
- get_buildbucket_info(bbid)
- .get('input', {})
- .get('properties', {})
- .get('$pigweed/pipeline', {})
- )
- if pipeline_props.get('inside_a_pipeline', False):
- pipeline = LuciPipeline(
- round=int(pipeline_props['round']),
- builds_from_previous_iteration=[
- int(x)
- for x in pipeline_props[
- 'builds_from_previous_iteration'
- ]
- ],
- )
+ bbid = int(env['BUILDBUCKET_ID'])
+ pipeline = LuciPipeline.create(bbid)
except ValueError:
pass
- return LuciContext(
+ result = LuciContext(
buildbucket_id=bbid,
- build_number=int(os.environ['BUILD_NUMBER']),
+ build_number=int(env['BUILD_NUMBER']),
project=project,
bucket=bucket,
builder=builder,
- swarming_task_id=os.environ['SWARMING_TASK_ID'],
+ swarming_task_id=env['SWARMING_TASK_ID'],
pipeline=pipeline,
+ triggers=LuciTrigger.create_from_environment(env),
)
+ _LOG.debug('%r', result)
+ return result
@dataclasses.dataclass