3 # Copyright 2010 Brian Dolbec <brian.dolbec@gmail.com>
4 # Copyright(c) 2010, Gentoo Foundation
5 # Distributed under the terms of the GNU General Public License v2
9 """Provides support functions to enalyze modules"""
13 from gentoolkit import errors
14 from gentoolkit.keyword import reduce_keywords
15 from gentoolkit.flag import (reduce_flags, get_flags, get_all_cpv_use,
16 filter_flags, get_installed_use, get_iuse, defaulted_flags)
17 #from gentoolkit.package import Package
22 class FlagAnalyzer(object):
23 """Specialty functions for analysing an installed package's
24 USE flags. Can be used for single or mulitple use without
25 needing to be reset unless the system USE flags are changed.
27 @type system: list or set
28 @param system: the default system USE flags.
29 @type _get_flags: function
30 @param _get_flags: Normally defaulted, can be overriden for testing
31 @type _get_used: function
32 @param _get_used: Normally defaulted, can be overriden for testing
36 filter_defaults=False,
39 _get_used=get_installed_use
41 self.get_flags = _get_flags
42 self.get_used = _get_used
43 self.filter_defaults = filter_defaults
47 def reset(self, system):
48 """Resets the internal system USE flags and use_expand variables
49 to the new setting. The use_expand variable is handled internally.
51 @type system: list or set
52 @param system: the default system USE flags.
54 self.system = set(system)
55 self.use_expand = portage.settings['USE_EXPAND'].lower().split()
57 def analyse_cpv(self, cpv):
58 """Gets all relavent USE flag info for a cpv and breaks them down
59 into 3 sets, plus (package.use enabled), minus ( package.use disabled),
62 @param cpv: string. 'cat/pkg-ver'
64 @return (plus, minus, unset) sets of USE flags
66 installed = set(self.get_used(cpv, self.target))
67 _iuse = self.get_flags(cpv)
68 iuse = set(reduce_flags(_iuse))
69 iuse_defaults = defaulted_flags(_iuse)
70 return self._analyse(installed, iuse, iuse_defaults)
72 def _analyse(self, installed, iuse, iuse_defaults):
73 """Analyzes the supplied info and returns the flag settings
74 that differ from the defaults
77 @param installed: the installed with use flags
79 @param iuse: the current ebuilds IUSE
81 defaults = self.system.intersection(iuse)
82 # update defaults with iuse_defaults
83 defaults.update(iuse_defaults['+'])
84 defaults = defaults.difference(iuse_defaults['-'])
85 usedflags = iuse.intersection(set(installed))
86 if self.filter_defaults:
87 plus = usedflags.difference(defaults)
90 minus = defaults.difference(usedflags)
91 unset = iuse.difference(defaults, plus, minus)
92 cleaned_unset = self.remove_expanding(unset)
93 return (plus, minus, cleaned_unset)
95 def analyse_pkg(self, pkg):
96 """Gets all relevent USE flag info for a pkg and breaks them down
97 into 3 sets, plus (package.use enabled), minus ( package.use disabled),
100 @param pkg: gentoolkit.package.Package object
102 @return (plus, minus, unset) sets of USE flags
104 installed = set(self.pkg_used(pkg))
105 #print("installed =", installed)
106 _iuse = self.pkg_flags(pkg)
107 iuse = set(reduce_flags(_iuse))
108 iuse_defaults = defaulted_flags(_iuse)
109 #print("iuse =", iuse)
110 return self._analyse(installed, iuse, iuse_defaults)
112 def pkg_used(self, pkg):
113 if self.target == "USE":
114 return pkg.use().split()
115 return pkg.environment(self.target).split()
117 def pkg_flags(self, pkg):
118 final_use, use_expand_hidden, usemasked, useforced = \
119 get_all_cpv_use(pkg.cpv)
120 flags = pkg.environment("IUSE", prefer_vdb=False).split()
121 return filter_flags(flags, use_expand_hidden, usemasked, useforced)
123 def redundant(self, cpv, iuse):
124 """Checks for redundant settings.
125 future function. Not yet implemented.
129 def remove_expanding(self, flags):
130 """Remove unwanted USE_EXPAND flags
133 @param flags: short list or set of USE flags
138 for expander in self.use_expand:
147 class KeywordAnalyser(object):
148 """Specialty functions for analysing the installed package db for
149 keyword useage and the packages that used them.
151 Note: should be initialized with the internal set_order() before use.
152 See internal set_order() for more details.
153 This class of functions can be used for single cpv checks or
154 used repeatedly for an entire package db.
157 @param arch: the system ARCH setting
158 @type accept_keywords: list
159 @param accept_keywords: eg. ['x86', '~x86']
160 @type get_aux: function, defaults to: portage.db[portage.root]["vartree"].dbapi.aux_get
161 @param vardb: vardb class of functions, needed=aux_get()
162 to return => KEYWORDS & USE flags for a cpv
163 = aux_get(cpv, ["KEYWORDS", "USE"])
166 # parsing order to determine appropriate keyword used for installation
167 normal_order = ['stable', 'testing', 'prefix', 'testing_prefix', 'missing']
168 prefix_order = ['prefix', 'testing_prefix', 'stable', 'testing', 'missing']
169 parse_range = list(range(len(normal_order)))
172 def __init__(self, arch, accept_keywords, vardb=portage.db[portage.root]["vartree"].dbapi):
174 self.accept_keywords = accept_keywords
177 self.parse_order = None
179 'stable': self._stable,
180 'testing': self._testing,
181 'prefix': self._prefix,
182 'testing_prefix': self._testing_prefix,
183 'missing': self._missing
187 def determine_keyword(self, keywords, used, cpv):
188 """Determine the keyword from the installed USE flags and
189 the KEYWORDS that was used to install a package.
191 @param keywords: list of keywords available to install a pkg
192 @param used: list of USE flalgs recorded for the installed pkg
194 @return a keyword or null string
200 absolute_kwds = reduce_keywords(keywords)
201 kwd = list(used.intersection(absolute_kwds))
202 #if keywords == ['~ppc64']:
203 #print "Checked keywords for kwd", keywords, used, "kwd =", kwd
205 #print "Checking for kwd against portage.archlist"
206 absolute_kwds = reduce_keywords(keywords)
207 # check for one against archlist then re-check
208 kwd = list(absolute_kwds.intersection(portage.archlist))
209 #print "determined keyword =", kwd
212 #print "determined keyword =", key
214 #print "kwd != 1", kwd, cpv
215 result = self._missing(self.keyword, keywords)
216 else: # too many, try to narrow them dowm
217 #print "too many kwd's, trying to match against arch"
218 _kwd = list(set(kwd).intersection(self.arch))
221 #print "found one! :)", _kwd
223 else: # try re-running the short list against archlist
224 #print "Checking kwd for _kwd against portage.archlist"
225 _kwd = list(set(kwd).intersection(portage.archlist))
226 if _kwd and len(_kwd) == 1:
227 #print "found one! :)", _kwd
230 #print " :( didn't work, _kwd =", _kwd, "giving up on:", cpv
231 result = self._missing(self.keyword, keywords)
233 while not result and i in self.parse_range:
234 parsekey = self.parse_order[i]
235 result = self.check_key[parsekey](key, keywords)
239 def _stable(self, key, keywords):
240 """test for a normal stable keyword"""
245 def _testing(self, key, keywords):
246 """test for a normal testing keyword"""
247 if ("~" + key) in keywords:
251 def _prefix(self, key, keywords):
252 """test for a stable prefix keyword"""
255 _key = '-'.join([key, self.prefix])
257 #print key, "is in", keywords
261 def _testing_prefix(self, key, keywords):
262 """test for a testing prefix keyword"""
265 _key = "~" +'-'.join([key, self.prefix])
267 #print key, "is in", keywords
271 def _missing(self, key, keywords):
272 """generates a missing keyword to return"""
273 if self.prefix and key != self.keyword:
274 _key = '-'.join([key, self.prefix])
277 #print "_missisng :( _key =", _key
280 def get_inst_keyword_cpv(self, cpv):
281 """Determines the installed with keyword for cpv
284 @param cpv: an installed CAT/PKG-VER
286 @returns a keyword determined to have been used to install cpv
288 keywords, used = self.vardb.aux_get(cpv, ["KEYWORDS", "USE"])
289 keywords = keywords.split()
291 return self._parse(keywords, used, cpv=cpv)
293 def get_inst_keyword_pkg(self, pkg):
294 """Determines the installed with keyword for cpv
296 @param pkg: gentoolkit.package.Package object
298 @returns a keyword determined to have been used to install cpv
300 keywords, used = pkg.environment(["KEYWORDS", "USE"],
301 prefer_vdb=True, fallback=False)
302 keywords = keywords.split()
304 return self._parse(keywords, used, pkg=pkg)
306 def _parse(self, keywords, used, pkg=None, cpv=None):
311 if not self.parse_order:
313 keyword = self.keyword
315 if self.arch not in used:
316 #print "Found a mismatch = ", cpv, self.arch, used
317 self.mismatched.append(_cpv)
318 if keyword in keywords:
319 #print "keyword", keyword, "is in", keywords
321 elif "~"+keyword in keywords:
322 #print "~keyword", keyword, "is in", keywords
325 keyword = self.determine_keyword(keywords, used, _cpv)
327 raise errors.GentoolkitUnknownKeyword(_cpv, ' '.join(keywords), used)
330 def set_order(self, used):
331 """Used to set the parsing order to determine a keyword
332 used for installation.
334 This is needed due to the way prefix arch's and keywords
335 work with portage. It looks for the 'prefix' flag. A positive result
336 sets it to the prefix order and keyword.
339 @param used: a list of pkg USE flags or the system USE flags"""
341 #print "SET_ORDER() Setting parse order to prefix"
343 self.parse_order = self.prefix_order
344 for key in self.accept_keywords:
345 #print "SET_ORDER() '"+key+"'"
347 #print "SET_ORDER()found prefix keyword :", key
349 prefix = key.split('-')[1]
350 #print "prefix =", prefix
352 self.keyword = '-'.join([self.arch, prefix])
354 #print "SET_ORDER() Setting parse order to normal"
355 self.parse_order = self.normal_order
356 self.keyword = self.arch
357 #print "SET_ORDER() completed: prefix =", self.prefix, ", keyword =", \
358 # self.keyword, "parse order =",self.parse_order