blob: 82960c4b2a47608cbc5673be700b68513337bb1a [file] [log] [blame]
# Copyright 2018 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module to manage local build state."""
import json
import logging
from typing import Any
from chromite.lib import constants
from chromite.utils import pformat
class BuildSummary:
"""Summarizes a build state without any external references.
This is basically a dictionary that can convert itself back and forth from
JSON, but it provides the convenience of attribute-based access instead of
carrying around dictionary key constants.
Attributes:
build_number: The build number of this build, as passed in with
--buildnumber, or 0 if --buildnumber wasn't passed.
buildbucket_id: The buildbucket id of this build, as passed in with
--buildbucket-id, or 0 if --buildbucket-id wasn't passed.
master_build_id: The CIDB id of the associated master build if there is
one, or 0 if there isn't one.
status: One of the status constants from
chromite.lib.constants.BUILDER_ALL_STATUSES
buildroot_layout: Version of the buildroot layout.
branch: Name of the branch this repository is associated with.
distfiles_ts: Float unix timestamp recording the last time the distfiles
cache was cleaned.
"""
# List of attributes that should be saved and restored to represent
# this object. We use an explicit list instead of vars() so that future
# additions can be handled explicitly.
_PERSIST_ATTRIBUTES = (
"build_number",
"buildbucket_id",
"master_build_id",
"status",
"buildroot_layout",
"branch",
"distfiles_ts",
)
def __init__(
self,
build_number=0,
buildbucket_id=0,
master_build_id=0,
status=constants.BUILDER_STATUS_MISSING,
buildroot_layout=0,
branch="",
distfiles_ts=None,
) -> None:
self.build_number = build_number
self.buildbucket_id = buildbucket_id
self.master_build_id = master_build_id
self.status = status
self.buildroot_layout = buildroot_layout
self.branch = branch
self.distfiles_ts = distfiles_ts
def __eq__(self, other: Any) -> bool:
for a in self._PERSIST_ATTRIBUTES:
if hasattr(other, a) != hasattr(self, a):
return False
if getattr(other, a) != getattr(self, a):
return False
return True
def __repr__(self) -> str:
return "BuildSummary(%s)" % self.to_json()
def from_json(self, raw_json) -> None:
"""Merge the state encoded in |raw_json| into this object.
Unknown keys will be ignored (with a warning). Values for missing keys
will remain unchanged.
Args:
raw_json: String containing valid JSON representing a BuildSummary.
Raises:
ValueError: |raw_json| is not valid JSON.
"""
new_state = json.loads(raw_json)
for key, val in new_state.items():
if key in self._PERSIST_ATTRIBUTES:
setattr(self, key, val)
else:
logging.warning('Ignoring unrecognized JSON key "%s"', key)
if self.status not in constants.BUILDER_ALL_STATUSES:
logging.warning('Ingoring unknown build status "%s"', self.status)
self.status = constants.BUILDER_STATUS_MISSING
def to_json(self):
"""Serialize this object to JSON.
Attributes that have an empty/zero value are omitted from the output.
The output of this function can be passed to from_json() to get back
another BuildSummary with the same values.
Returns:
A string containing a JSON-encoded representation of this object.
"""
state = {}
for a in self._PERSIST_ATTRIBUTES:
val = getattr(self, a)
if val:
state[a] = getattr(self, a)
return pformat.json(state, compact=True)
def is_valid(self):
"""Indicate whether this object has a valid status."""
return self.status != constants.BUILDER_STATUS_MISSING
def build_description(self):
"""Get a human-readable description of which build id is set.
If multiple fields are set, buildbucket is preferred. The result can be
used for logging or display, but should not be parsed for database
lookups.
Returns:
String describing which build id is set, or "local build" if none
are set.
"""
if self.buildbucket_id:
return "buildbucket_id=%s" % self.buildbucket_id
if self.build_number:
return "build_number=%s" % self.build_number
return "local build"