| # |
| # Copyright (C) 2018 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 <http://www.gnu.org/licenses/>. |
| # |
| # Authors: |
| # Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> |
| |
| from .._exceptions import LoadError |
| from ..exceptions import LoadErrorReason |
| from ..node cimport MappingNode, Node, ProvenanceInformation, ScalarNode, SequenceNode |
| |
| |
| # Symbol(): |
| # |
| # A simple object to denote the symbols we load with from YAML |
| # |
| class Symbol(): |
| FILENAME = "filename" |
| KIND = "kind" |
| DEPENDS = "depends" |
| BUILD_DEPENDS = "build-depends" |
| RUNTIME_DEPENDS = "runtime-depends" |
| SOURCES = "sources" |
| CONFIG = "config" |
| VARIABLES = "variables" |
| ENVIRONMENT = "environment" |
| ENV_NOCACHE = "environment-nocache" |
| PUBLIC = "public" |
| TYPE = "type" |
| BUILD = "build" |
| RUNTIME = "runtime" |
| ALL = "all" |
| DIRECTORY = "directory" |
| JUNCTION = "junction" |
| SANDBOX = "sandbox" |
| STRICT = "strict" |
| |
| |
| # Dependency() |
| # |
| # A simple object describing a dependency |
| # |
| # Args: |
| # name (str or Node): The element name |
| # dep_type (str): The type of dependency, can be |
| # Symbol.ALL, Symbol.BUILD, or Symbol.RUNTIME |
| # junction (str): The element name of the junction, or None |
| # provenance (ProvenanceInformation): The YAML node provenance of where this |
| # dependency was declared |
| # |
| cdef class Dependency: |
| cdef public ProvenanceInformation provenance |
| cdef public str name |
| cdef public str dep_type |
| cdef public str junction |
| cdef public bint strict |
| |
| def __init__(self, |
| Node dep, |
| str default_dep_type=None): |
| cdef str dep_type |
| |
| self.provenance = dep.get_provenance() |
| |
| if type(dep) is ScalarNode: |
| self.name = dep.as_str() |
| self.dep_type = default_dep_type |
| self.junction = None |
| self.strict = False |
| |
| elif type(dep) is MappingNode: |
| if default_dep_type: |
| (<MappingNode> dep).validate_keys(['filename', 'junction', 'strict']) |
| dep_type = default_dep_type |
| else: |
| (<MappingNode> dep).validate_keys(['filename', 'type', 'junction', 'strict']) |
| |
| # Make type optional, for this we set it to None |
| dep_type = (<MappingNode> dep).get_str(<str> Symbol.TYPE, None) |
| if dep_type is None or dep_type == <str> Symbol.ALL: |
| dep_type = None |
| elif dep_type not in [Symbol.BUILD, Symbol.RUNTIME]: |
| provenance = dep.get_scalar(Symbol.TYPE).get_provenance() |
| raise LoadError("{}: Dependency type '{}' is not 'build', 'runtime' or 'all'" |
| .format(provenance, dep_type), LoadErrorReason.INVALID_DATA) |
| |
| self.name = (<MappingNode> dep).get_str(<str> Symbol.FILENAME) |
| self.dep_type = dep_type |
| self.junction = (<MappingNode> dep).get_str(<str> Symbol.JUNCTION, None) |
| self.strict = (<MappingNode> dep).get_bool(<str> Symbol.STRICT, False) |
| |
| # Here we disallow explicitly setting 'strict' to False. |
| # |
| # This is in order to keep the door open to allowing the project.conf |
| # set the default of dependency 'strict'-ness which might be useful |
| # for projects which use mostly static linking and the like, in which |
| # case we can later interpret explicitly non-strict dependencies |
| # as an override of the project default. |
| # |
| if self.strict == False and Symbol.STRICT in dep: |
| provenance = dep.get_scalar(Symbol.STRICT).get_provenance() |
| raise LoadError("{}: Setting 'strict' to False is unsupported" |
| .format(provenance), LoadErrorReason.INVALID_DATA) |
| |
| else: |
| raise LoadError("{}: Dependency is not specified as a string or a dictionary".format(self.provenance), |
| LoadErrorReason.INVALID_DATA) |
| |
| # Only build dependencies are allowed to be strict |
| # |
| if self.strict and self.dep_type == Symbol.RUNTIME: |
| raise LoadError("{}: Runtime dependency {} specified as `strict`.".format(self.provenance, self.name), |
| LoadErrorReason.INVALID_DATA, |
| detail="Only dependencies required at build time may be declared `strict`.") |
| |
| # `:` characters are not allowed in filename if a junction was |
| # explicitly specified |
| if self.junction and ':' in self.name: |
| raise LoadError("{}: Dependency {} contains `:` in its name. " |
| "`:` characters are not allowed in filename when " |
| "junction attribute is specified.".format(self.provenance, self.name), |
| LoadErrorReason.INVALID_DATA) |
| |
| # Name of the element should never contain more than one `:` characters |
| if self.name.count(':') > 1: |
| raise LoadError("{}: Dependency {} contains multiple `:` in its name. " |
| "Recursive lookups for cross-junction elements is not " |
| "allowed.".format(self.provenance, self.name), LoadErrorReason.INVALID_DATA) |
| |
| # Attempt to split name if no junction was specified explicitly |
| if not self.junction and self.name.count(':') == 1: |
| self.junction, self.name = self.name.split(':') |
| |
| |
| # _extract_depends_from_node(): |
| # |
| # Helper for extract_depends_from_node to get dependencies of a particular type |
| # |
| # Adds to an array of Dependency objects from a given dict node 'node', |
| # allows both strings and dicts for expressing the dependency. |
| # |
| # After extracting depends, the symbol is deleted from the node |
| # |
| # Args: |
| # node (Node): A YAML loaded dictionary |
| # key (str): the key on the Node corresponding to the dependency type |
| # default_dep_type (str): type to give to the dependency |
| # acc (list): a list in which to add the loaded dependencies |
| # rundeps (dict): a dictionary mapping dependency (junction, name) to dependency for runtime deps |
| # builddeps (dict): a dictionary mapping dependency (junction, name) to dependency for build deps |
| # |
| cdef void _extract_depends_from_node(Node node, str key, str default_dep_type, list acc, dict rundeps, dict builddeps) except *: |
| cdef SequenceNode depends = node.get_sequence(key, []) |
| cdef Node dep_node |
| cdef tuple deptup |
| |
| for dep_node in depends: |
| dependency = Dependency(dep_node, default_dep_type=default_dep_type) |
| deptup = (dependency.junction, dependency.name) |
| if dependency.dep_type in [Symbol.BUILD, None]: |
| if deptup in builddeps: |
| raise LoadError("{}: Duplicate build dependency found at {}." |
| .format(dependency.provenance, builddeps[deptup].provenance), |
| LoadErrorReason.DUPLICATE_DEPENDENCY) |
| else: |
| builddeps[deptup] = dependency |
| if dependency.dep_type in [Symbol.RUNTIME, None]: |
| if deptup in rundeps: |
| raise LoadError("{}: Duplicate runtime dependency found at {}." |
| .format(dependency.provenance, rundeps[deptup].provenance), |
| LoadErrorReason.DUPLICATE_DEPENDENCY) |
| else: |
| rundeps[deptup] = dependency |
| acc.append(dependency) |
| |
| # Now delete the field, we dont want it anymore |
| node.safe_del(key) |
| |
| |
| # extract_depends_from_node(): |
| # |
| # Creates an array of Dependency objects from a given dict node 'node', |
| # allows both strings and dicts for expressing the dependency and |
| # throws a comprehensive LoadError in the case that the node is malformed. |
| # |
| # After extracting depends, the symbol is deleted from the node |
| # |
| # Args: |
| # node (Node): A YAML loaded dictionary |
| # |
| # Returns: |
| # (list): a list of Dependency objects |
| # |
| def extract_depends_from_node(Node node): |
| cdef list acc = [] |
| cdef dict rundeps = {} |
| cdef dict builddeps = {} |
| _extract_depends_from_node(node, <str> Symbol.BUILD_DEPENDS, <str> Symbol.BUILD, acc, rundeps, builddeps) |
| _extract_depends_from_node(node, <str> Symbol.RUNTIME_DEPENDS, <str> Symbol.RUNTIME, acc, rundeps, builddeps) |
| _extract_depends_from_node(node, <str> Symbol.DEPENDS, None, acc, rundeps, builddeps) |
| return acc |