Source code for craft_parts.state_manager.reports

# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2018-2021 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Provide a report on why a step is outdated."""

import logging
from dataclasses import dataclass
from typing import List, Optional

from craft_parts.steps import Step
from craft_parts.utils import formatting_utils

logger = logging.getLogger(__name__)


[docs]@dataclass(frozen=True) class Dependency: """The part and step that are a prerequisite to another step.""" part_name: str step: Step
[docs]class OutdatedReport: """The OutdatedReport class explains why a given step is outdated. An outdated step is defined to be a step that has run, but since doing so one of the following things have happened: - A step earlier in the lifecycle has run again. - The source on disk has been updated. """ def __init__( self, *, previous_step_modified: Optional[Step] = None, source_modified: bool = False, outdated_files: Optional[List[str]] = None, outdated_dirs: Optional[List[str]] = None, ) -> None: """Create a new OutdatedReport. :param previous_step_modified: Step earlier in the lifecycle that has changed. :param source_modified: Whether the source changed on disk. """ self.previous_step_modified = previous_step_modified self.source_modified = source_modified self.outdated_files = outdated_files self.outdated_dirs = outdated_dirs
[docs] def reason(self) -> str: """Get summarized report. :return: Short summary of why the step is outdated. """ reasons = [] if self.previous_step_modified: reasons.append(f"{self.previous_step_modified.name!r} step") if self.source_modified: reasons.append("source") if not reasons: return "" return f'{formatting_utils.humanize_list(reasons, "and", "{}")} changed'
[docs]class DirtyReport: """The DirtyReport class explains why a given step is dirty. A dirty step is defined to be a step that has run, but since doing so one of the following things have happened: - One or more properties used by the step have changed. - One of more project options have changed. - One of more of its dependencies have been re-staged. """ def __init__( self, *, dirty_properties: Optional[List[str]] = None, dirty_project_options: Optional[List[str]] = None, changed_dependencies: Optional[List[Dependency]] = None, ) -> None: """Create a new DirtyReport. :param dirty_properties: Properties that have changed. :param dirty_project_options: Project options that have changed. :param changed_dependencies: Dependencies that have changed. """ self.dirty_properties = dirty_properties self.dirty_project_options = dirty_project_options self.changed_dependencies = changed_dependencies # pylint: disable=too-many-branches
[docs] def reason(self) -> str: """Get summarized report. :return: Short summary of why the part is dirty. """ reasons = [] reasons_count = 0 if self.dirty_properties: reasons_count += 1 if self.dirty_project_options: reasons_count += 1 if self.changed_dependencies: reasons_count += 1 if self.dirty_properties: logger.debug("dirty properties: %r", self.dirty_properties) # Be specific only if this is the only reason if reasons_count > 1 or len(self.dirty_properties) > 1: reasons.append("properties") else: reasons.append(f"{self.dirty_properties[0]!r} property") if self.dirty_project_options: logger.debug("dirty project options: %r", self.dirty_project_options) # Be specific only if this is the only reason if reasons_count > 1 or len(self.dirty_project_options) > 1: reasons.append("options") else: reasons.append(f"{self.dirty_project_options[0]!r} option") if self.changed_dependencies: logger.debug("changed dependencies: %r", self.changed_dependencies) # Be specific only if this is the only reason if reasons_count > 1 or len(self.changed_dependencies) > 1: reasons.append("dependencies") else: part_name = self.changed_dependencies[0].part_name step_name = self.changed_dependencies[0].step.name.lower() reasons.append(f"{step_name} for part {part_name!r}") if not reasons: return "" return f'{formatting_utils.humanize_list(reasons, "and", "{}")} changed'
# pylint: enable=too-many-branches