replace all use of dbapi's PORTDB, VARDB, BINDB assigned variables with the actuall...
[gentoolkit.git] / pym / gentoolkit / enalyze / lib.py
1 #!/usr/bin/python
2 #
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
6 #
7
8
9 """Provides support functions to enalyze modules"""
10
11 import sys
12
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
18
19 import portage
20
21
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.
26
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
33                 """
34         def __init__(self,
35                 system,
36                 filter_defaults=False,
37                 target="USE",
38                 _get_flags=get_flags,
39                 _get_used=get_installed_use
40         ):
41                 self.get_flags = _get_flags
42                 self.get_used = _get_used
43                 self.filter_defaults = filter_defaults
44                 self.target = target
45                 self.reset(system)
46
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.
50
51                 @type system: list or set
52                 @param system: the default system USE flags.
53                 """
54                 self.system = set(system)
55                 self.use_expand = portage.settings['USE_EXPAND'].lower().split()
56
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),
60                 unset.
61
62                 @param cpv: string. 'cat/pkg-ver'
63                 @rtype tuple of sets
64                 @return (plus, minus, unset) sets of USE flags
65                 """
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)
71
72         def _analyse(self, installed, iuse, iuse_defaults):
73                 """Analyzes the supplied info and returns the flag settings
74                 that differ from the defaults
75
76                 @type installed: set
77                 @param installed: the installed with use flags
78                 @type iuse: set
79                 @param iuse: the current ebuilds IUSE
80                 """
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)
88                 else:
89                         plus = usedflags
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)
94
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),
98                 unset.
99
100                 @param pkg: gentoolkit.package.Package object
101                 @rtype tuple of sets
102                 @return (plus, minus, unset) sets of USE flags
103                 """
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)
111
112         def pkg_used(self, pkg):
113                 if self.target == "USE":
114                         return pkg.use().split()
115                 return pkg.environment(self.target).split()
116
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)
122
123         def redundant(self, cpv, iuse):
124                 """Checks for redundant settings.
125                 future function. Not yet implemented.
126                 """
127                 pass
128
129         def remove_expanding(self, flags):
130                 """Remove unwanted USE_EXPAND flags
131                 from unset IUSE sets
132
133                 @param flags: short list or set of USE flags
134                 @rtype set
135                 @return USE flags
136                 """
137                 _flags = set(flags)
138                 for expander in self.use_expand:
139                         for flag in flags:
140                                 if expander in flag:
141                                         _flags.remove(flag)
142                         if not _flags:
143                                 break
144                 return _flags
145
146
147 class KeywordAnalyser(object):
148         """Specialty functions for analysing the installed package db for
149         keyword useage and the packages that used them.
150
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.
155
156         @type  arch: string
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"])
164         """
165
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)))
170
171
172         def __init__(self, arch, accept_keywords, vardb=portage.db[portage.root]["vartree"].dbapi):
173                 self.arch = arch
174                 self.accept_keywords = accept_keywords
175                 self.vardb = vardb
176                 self.prefix = ''
177                 self.parse_order = None
178                 self.check_key = {
179                         'stable': self._stable,
180                         'testing': self._testing,
181                         'prefix': self._prefix,
182                         'testing_prefix': self._testing_prefix,
183                         'missing': self._missing
184                         }
185                 self.mismatched = []
186
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.
190
191                 @param keywords: list of keywords available to install a pkg
192                 @param used: list of USE flalgs recorded for the installed pkg
193                 @rtype: string
194                 @return a keyword or null string
195                 """
196                 used = set(used)
197                 kwd = None
198                 result = ''
199                 if keywords:
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
204                 if not 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
210                 if len(kwd) == 1:
211                         key = kwd[0]
212                         #print "determined keyword =", key
213                 elif not kwd:
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))
219                         key = ''
220                         if _kwd:
221                                 #print "found one! :)", _kwd
222                                 key = _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
228                                         key = _kwd[0]
229                                 else:
230                                         #print " :( didn't work, _kwd =", _kwd, "giving up on:", cpv
231                                         result = self._missing(self.keyword, keywords)
232                 i = 0
233                 while not result and i in self.parse_range:
234                         parsekey = self.parse_order[i]
235                         result = self.check_key[parsekey](key, keywords)
236                         i += 1
237                 return result
238
239         def _stable(self, key, keywords):
240                 """test for a normal stable keyword"""
241                 if key in keywords:
242                         return key
243                 return ''
244
245         def _testing(self, key, keywords):
246                 """test for a normal testing keyword"""
247                 if ("~" + key) in keywords:
248                         return "~" + key
249                 return ''
250
251         def _prefix(self, key, keywords):
252                 """test for a stable prefix keyword"""
253                 if not self.prefix:
254                         return ''
255                 _key = '-'.join([key, self.prefix])
256                 if _key in keywords:
257                         #print key, "is in", keywords
258                         return _key
259                 return ''
260
261         def _testing_prefix(self, key, keywords):
262                 """test for a testing prefix keyword"""
263                 if not self.prefix:
264                         return ''
265                 _key = "~" +'-'.join([key, self.prefix])
266                 if _key in keywords:
267                         #print key, "is in", keywords
268                         return _key
269                 return ''
270
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])
275                 else:
276                         _key = '-' + key
277                 #print "_missisng :(  _key =", _key
278                 return _key
279
280         def get_inst_keyword_cpv(self, cpv):
281                 """Determines the installed with keyword for cpv
282
283                 @type cpv: string
284                 @param cpv: an installed CAT/PKG-VER
285                 @rtype: string
286                 @returns a keyword determined to have been used to install cpv
287                 """
288                 keywords, used = self.vardb.aux_get(cpv, ["KEYWORDS", "USE"])
289                 keywords = keywords.split()
290                 used = used.split()
291                 return self._parse(keywords, used, cpv=cpv)
292
293         def get_inst_keyword_pkg(self, pkg):
294                 """Determines the installed with keyword for cpv
295
296                 @param pkg: gentoolkit.package.Package object
297                 @rtype: string
298                 @returns a keyword determined to have been used to install cpv
299                 """
300                 keywords, used = pkg.environment(["KEYWORDS", "USE"],
301                         prefer_vdb=True, fallback=False)
302                 keywords = keywords.split()
303                 used = used.split()
304                 return self._parse(keywords, used, pkg=pkg)
305
306         def _parse(self, keywords, used, pkg=None, cpv=None):
307                 if pkg:
308                         _cpv = pkg.cpv
309                 else:
310                         _cpv = cpv
311                 if not self.parse_order:
312                         self.set_order(used)
313                 keyword = self.keyword
314                 # sanity check
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
320                         return keyword
321                 elif "~"+keyword in keywords:
322                         #print "~keyword", keyword, "is in", keywords
323                         return "~"+keyword
324                 else:
325                         keyword = self.determine_keyword(keywords, used, _cpv)
326                         if not keyword:
327                                 raise errors.GentoolkitUnknownKeyword(_cpv, ' '.join(keywords), used)
328                         return keyword
329
330         def set_order(self, used):
331                 """Used to set the parsing order to determine a keyword
332                 used for installation.
333
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.
337
338                 @type used: list
339                 @param used: a list of pkg USE flags or the system USE flags"""
340                 if 'prefix' in used:
341                         #print "SET_ORDER() Setting parse order to prefix"
342                         prefix = None
343                         self.parse_order = self.prefix_order
344                         for key in self.accept_keywords:
345                                 #print "SET_ORDER()  '"+key+"'"
346                                 if '-' in key:
347                                         #print "SET_ORDER()found prefix keyword :", key
348                                         if self.arch in key:
349                                                 prefix = key.split('-')[1]
350                                                 #print "prefix =", prefix
351                                                 self.prefix = prefix
352                         self.keyword = '-'.join([self.arch, prefix])
353                 else:
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
359                 #print
360