blob: 1f264789a09bb56040e116bfdf3b10427e790090 [file] [log] [blame]
#
# 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