# # Copyright (C) 2019 Bloomberg Finance LP # Copyright (C) 2020 Codethink Limited # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library 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 library. If not, see . # # Authors: # James Ennis # Tristan Van Berkom from typing import TYPE_CHECKING, Optional, Dict from contextlib import suppress from . import Element from . import _cachekey from ._artifact import Artifact from ._artifactproject import ArtifactProject from ._exceptions import ArtifactElementError from ._loader import LoadElement from .node import Node if TYPE_CHECKING: from ._context import Context from ._state import Task # ArtifactElement() # # Object to be used for directly processing an artifact # # Args: # context (Context): The Context object # ref (str): The artifact ref # class ArtifactElement(Element): # A hash of ArtifactElement by ref __instantiated_artifacts: Dict[str, "ArtifactElement"] = {} def __init__(self, context, ref): project_name, element_name, key = verify_artifact_ref(ref) # At this point we only know the key which was specified on the command line, # so we will pretend all keys are equal. # # If the artifact is cached, then the real keys will be loaded from the # artifact instead. # artifact = Artifact(self, context, strong_key=key, strict_key=key, weak_key=key) project = ArtifactProject(project_name, context) load_element = LoadElement(Node.from_dict({}), element_name, project.loader) # NOTE element has no .bst suffix super().__init__(context, project, load_element, None, artifact=artifact) ######################################################## # Public API # ######################################################## # new_from_artifact_name(): # # Recursively instantiate a new ArtifactElement instance, and its # dependencies from an artifact name # # Args: # artifact_name: The artifact name # context: The Context object # task: A task object to report progress to # # Returns: # (ArtifactElement): A newly created Element instance # @classmethod def new_from_artifact_name(cls, artifact_name: str, context: "Context", task: Optional["Task"] = None): # Initial lookup for already loaded artifact. with suppress(KeyError): return cls.__instantiated_artifacts[artifact_name] # Instantiate the element, this can result in having a different # artifact name, if we loaded the artifact by it's weak key then # we will have the artifact loaded via it's strong key. element = ArtifactElement(context, artifact_name) artifact_name = element.get_artifact_name() # Perform a second lookup, avoid loading the same artifact # twice, even if we've loaded it both with weak and strong keys. with suppress(KeyError): return cls.__instantiated_artifacts[artifact_name] # Now cache the loaded artifact cls.__instantiated_artifacts[artifact_name] = element # Walk the dependencies and load recursively artifact = element._get_artifact() for dep_artifact_name in artifact.get_dependency_artifact_names(): dependency = ArtifactElement.new_from_artifact_name(dep_artifact_name, context, task) element._add_build_dependency(dependency) return element # clear_artifact_name_cache() # # Clear the internal artifact refs cache # # When loading ArtifactElements from artifact refs, we cache already # instantiated ArtifactElements in order to not have to load the same # ArtifactElements twice. This clears the cache. # # It should be called whenever we are done loading all artifacts in order # to save memory. # @classmethod def clear_artifact_name_cache(cls): cls.__instantiated_artifacts = {} ######################################################## # Implement Element abstract methods # ######################################################## def configure(self, node): pass def preflight(self): pass def configure_sandbox(self, sandbox): install_root = self.get_variable("install-root") # Tell the sandbox to mount the build root and install root sandbox.mark_directory(install_root) # verify_artifact_ref() # # Verify that a ref string matches the format of an artifact # # Args: # ref (str): The artifact ref # # Returns: # project (str): The project's name # element (str): The element's name # key (str): The cache key # # Raises: # ArtifactElementError if the ref string does not match # the expected format # def verify_artifact_ref(ref): try: project, element, key = ref.split("/", 2) # This will raise a Value error if unable to split # Explicitly raise a ValueError if the key length is not as expected if not _cachekey.is_key(key): raise ValueError except ValueError: raise ArtifactElementError("Artifact: {} is not of the expected format".format(ref)) return project, element, key