1# -*- coding: utf-8 -*-
2#
3# Copyright 2013 Ingo Weinhold
4# Copyright 2014 Oliver Tappe
5# Distributed under the terms of the MIT License.
6
7# -- Modules ------------------------------------------------------------------
8
9from . import BuildPlatform
10from .Configuration import Configuration
11from .Options import getOption
12from .PackageInfo import PackageInfo, Resolvable
13from .Utils import versionCompare
14
15# -- ProvidesInfo class -------------------------------------------------------
16
17class ProvidesInfo(Resolvable):
18	def __init__(self, packageInfo, providesString):
19		super(ProvidesInfo, self).__init__(providesString)
20		self.packageInfo = packageInfo
21
22	@property
23	def packageID(self):
24		return self.packageInfo.versionedName
25
26	@property
27	def path(self):
28		return self.packageInfo.path
29
30# -- ProvidesManager class ----------------------------------------------------
31
32class ProvidesManager(object):
33	def __init__(self):
34		self._providesMap = {}
35		self._providesSourceMap = {}
36		self.architectures = [BuildPlatform.buildPlatform.targetArchitecture,
37			'any', 'source']
38
39	def addProvidesFromPackage(self, package):
40		for providesString in package.recipeKeys['PROVIDES']:
41			self._addPackageProvidesInfo(package.revisionedName, providesString)
42
43	def addProvidesFromPackageInfo(self, packageInfo):
44		if (packageInfo.architecture not in self.architectures
45			and not (Configuration.isCrossBuildRepository()
46				and '_cross_' in packageInfo.path)):
47			return
48
49		for provides in packageInfo.provides:
50			self._addPackageProvidesInfo(packageInfo, str(provides))
51
52	def getMatchingProvides(self, resolvableExpression, anyHpkg=False,
53		ignoreBase=False):
54		name = resolvableExpression.name
55		operator = resolvableExpression.operator
56		version = resolvableExpression.version
57		base = resolvableExpression.base
58
59		if name not in self._providesMap:
60			return None
61
62		updateDependencies = getOption('updateDependencies')
63		missingDependencies = getOption('missingDependencies')
64
65		providesList = self._providesMap[name]
66
67		found = None
68		foundIsHpkg = False
69		for provides in providesList:
70			provideIsHpkg = (provides.packageInfo.path.endswith('.hpkg')
71				if isinstance(provides.packageInfo, PackageInfo) else False)
72			if not ignoreBase and base and provideIsHpkg:
73				continue
74			if not operator:
75				if not updateDependencies and not missingDependencies:
76					return provides
77				if (found is None or
78					(anyHpkg and provideIsHpkg) or
79					(provideIsHpkg and not foundIsHpkg and
80						(missingDependencies or found.version is None
81							or versionCompare(provides.version, found.version) >= 0))):
82					found = provides
83					foundIsHpkg = provideIsHpkg
84				continue
85			if not provides.version:
86				continue
87			matches = {
88				'<':	lambda compareResult: compareResult < 0,
89				'<=':	lambda compareResult: compareResult <= 0,
90				'==':	lambda compareResult: compareResult == 0,
91				'!=':	lambda compareResult: compareResult != 0,
92				'>=':	lambda compareResult: compareResult >= 0,
93				'>':	lambda compareResult: compareResult > 0,
94			}[operator](versionCompare(provides.version, version))
95			if not matches:
96				continue
97			if (provides.compatibleVersion
98				and versionCompare(provides.compatibleVersion, version) > 0):
99				continue
100			if not updateDependencies and not missingDependencies:
101				return provides
102			if (found is None or
103				(anyHpkg and provideIsHpkg) or
104				(provideIsHpkg and not foundIsHpkg and
105					(missingDependencies or found.version is None
106						or versionCompare(provides.version, found.version) >= 0))):
107				found = provides
108				foundIsHpkg = provideIsHpkg
109		return found
110
111	@staticmethod
112	def _providesSource(packageInfo):
113		return packageInfo.path if isinstance(packageInfo, PackageInfo) \
114			else packageInfo
115
116
117	def _addPackageProvidesInfo(self, packageInfo, providesString):
118		provides = ProvidesInfo(packageInfo, providesString.strip())
119
120		source = self._providesSource(packageInfo)
121		if source in self._providesSourceMap:
122			self._providesSourceMap[source].append(provides)
123		else:
124			self._providesSourceMap[source] = [provides]
125
126		if provides.name in self._providesMap:
127			self._providesMap[provides.name].append(provides)
128		else:
129			self._providesMap[provides.name] = [provides]
130
131	def removeProvidesOfPackageInfo(self, packageInfo):
132		source = self._providesSource(packageInfo)
133		providesList = self._providesSourceMap.pop(source)
134		for provides in providesList:
135			self._providesMap[provides.name].remove(provides)
136