# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2016-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/>."""Helpers and definitions for lifecycle states."""importcontextlibimportloggingfrompathlibimportPathimportyamlfromcraft_parts.infosimportProjectVarfromcraft_parts.partsimportPartfromcraft_parts.stepsimportStepfrom.build_stateimportBuildStatefrom.overlay_stateimportOverlayState# , pylint: disable=W0611from.prime_stateimportPrimeStatefrom.pull_stateimportPullStatefrom.stage_stateimportStageStatefrom.step_stateimportMigrationState,StepStatelogger=logging.getLogger(__name__)
[docs]defload_step_state(part:Part,step:Step)->StepState|None:"""Retrieve the persistent state for the given part and step. :param part: The part corresponding to the state to load. :param step: The step corresponding to the state to load. :return: The step state. :raise RuntimeError: If step is invalid. """filename=get_step_state_path(part,step)ifnotfilename.is_file():returnNonelogger.debug("load state file: %s",filename)withopen(filename)asyaml_file:state_data=yaml.safe_load(yaml_file)# Fix project variables in loaded state data.## FIXME: add proper type definition for project_options so that# ProjectVar can be created by pydantic during model unmarshaling.options=state_data.get("project-options")ifoptions:pvars=options.get("project_vars")ifpvars:forkey,valinpvars.items():state_data["project-options"]["project_vars"][key]=ProjectVar(**val)state_class:type[StepState]ifstep==Step.PULL:state_class=PullStateelifstep==Step.OVERLAY:state_class=OverlayStateelifstep==Step.BUILD:state_class=BuildStateelifstep==Step.STAGE:state_class=StageStateelifstep==Step.PRIME:state_class=PrimeStateelse:raiseRuntimeError(f"invalid step {step!r}")returnstate_class.unmarshal(state_data)
[docs]defload_overlay_migration_state(state_dir:Path,step:Step)->MigrationState|None:"""Retrieve the overlay migration state for the given step. :param state_dir: The path to the directory containing migration state files. :param step: The step corresponding to the migration state to load. """filename=get_overlay_migration_state_path(state_dir,step)ifnotfilename.is_file():returnNonelogger.debug("load overlay migration state file: %s",filename)withopen(filename)asyaml_file:state_data=yaml.safe_load(yaml_file)returnMigrationState.unmarshal(state_data)
[docs]defremove(part:Part,step:Step)->None:"""Remove the persistent state file for the given part and step. :param part: The part whose state is to be removed. :param step: The step whose state is to be removed. """state_file=part.part_state_dir/step.name.lower()withcontextlib.suppress(FileNotFoundError):# no missing_ok in python 3.7state_file.unlink()
[docs]defget_step_state_path(part:Part,step:Step)->Path:"""Return the path to the state file for the given part and step."""returnpart.part_state_dir/step.name.lower()
[docs]defget_overlay_migration_state_path(state_dir:Path,step:Step)->Path:"""Return the path to the overlay migration state file for the given step."""ifstep==Step.STAGE:returnstate_dir/"stage_overlay"ifstep==Step.PRIME:returnstate_dir/"prime_overlay"raiseRuntimeError(f"no overlay migration state in step {step!r}")