sync with genscripts rev 343. This adds the initial py3k support and the analyse...
[gentoolkit.git] / pym / gentoolkit / analyse / 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 analyse modules"""
10
11 import sys
12
13 from gentoolkit.dbapi import PORTDB, VARDB
14 from gentoolkit import errors
15 from gentoolkit.keyword import abs_keywords
16 #from gentoolkit.package import Package
17
18 import portage
19
20
21 def get_installed_use(cpv, use="USE"):
22         """Gets the installed USE flags from the VARDB
23         
24         @type: cpv: string
25         @param cpv: cat/pkg-ver
26         @type use: string
27         @param use: 1 of ["USE", "PKGUSE"]
28         @rtype list
29         @returns [] or the list of IUSE flags
30         """
31         return VARDB.aux_get(cpv,[use])[0].split()
32
33
34 def get_iuse(cpv):
35         """Gets the current IUSE flags from the tree
36         
37         @type: cpv: string
38         @param cpv: cat/pkg-ver
39         @rtype list
40         @returns [] or the list of IUSE flags
41         """
42         try:
43                 return PORTDB.aux_get(cpv, ["IUSE"])[0].split()
44         except:
45                 return []
46
47
48 def abs_flag(flag):
49         """Absolute value function for a USE flag
50         
51         @type flag: string
52         @param flag: the use flag to absolute.
53         @rtype: string
54         @return absolute USE flag
55         """
56         if flag[0] in ["+","-"]:
57                 return flag[1:]
58         else:
59                 return flag
60
61
62 def abs_list(the_list):
63         """Absolute value function for a USE flag list
64         
65         @type the_list: list
66         @param the_list: the use flags to absolute.
67         @rtype: list
68         @return absolute USE flags
69         """
70         r=[]
71         for member in the_list:
72                 r.append(abs_flag(member))
73         return r
74
75
76 def filter_flags(use, use_expand_hidden, usemasked, useforced):
77         """Filter function to remove hidden or otherwise not normally
78         visible USE flags from a list.
79         
80         @type use: list
81         @param use: the USE flag list to be filtered.
82         @type use_expand_hidden: list
83         @param  use_expand_hidden: list of flags hidden.
84         @type usemasked: list
85         @param usemasked: list of masked USE flags.
86         @type useforced: list
87         @param useforced: the forced USE flags.
88         @rtype: list
89         @return the filtered USE flags.
90         """
91         # clean out some environment flags, since they will most probably
92         # be confusing for the user
93         for f in use_expand_hidden:
94                 f=f.lower() + "_"
95                 for x in use:
96                         if f in x:
97                                 use.remove(x)
98         # clean out any arch's
99         archlist = portage.settings["PORTAGE_ARCHLIST"].split()
100         for a in use[:]:
101                 if a in archlist:
102                         use.remove(a)
103         # dbl check if any from usemasked  or useforced are still there
104         masked = usemasked + useforced
105         for a in use[:]:
106                 if a in masked:
107                         use.remove(a)
108         return use
109
110
111 def get_all_cpv_use(cpv):
112         """Uses portage to determine final USE flags and settings for an emerge
113         
114         @type cpv: string
115         @param cpv: eg cat/pkg-ver
116         @rtype: lists
117         @return  use, use_expand_hidden, usemask, useforce
118         """
119         use = None
120         PORTDB.settings.unlock()
121         try:
122                 PORTDB.settings.setcpv(cpv, use_cache=True, mydb=portage.portdb)
123                 use = portage.settings['PORTAGE_USE'].split()
124                 use_expand_hidden = portage.settings["USE_EXPAND_HIDDEN"].split()
125                 usemask = list(PORTDB.settings.usemask)
126                 useforce =  list(PORTDB.settings.useforce)
127         except KeyError:
128                 PORTDB.settings.reset()
129                 PORTDB.settings.lock()
130                 return [], [], [], []
131         # reset cpv filter
132         PORTDB.settings.reset()
133         PORTDB.settings.lock()
134         return use, use_expand_hidden, usemask, useforce
135
136
137 def get_flags(cpv, final_setting=False):
138         """Retrieves all information needed to filter out hidded, masked, etc.
139         USE flags for a given package.
140         
141         @type cpv: string
142         @param cpv: eg. cat/pkg-ver
143         @type final_setting: boolean
144         @param final_setting: used to also determine the final
145                 enviroment USE flag settings and return them as well.
146         @rtype: list or list, list
147         @return IUSE or IUSE, final_flags
148         """
149         final_use, use_expand_hidden, usemasked, useforced = get_all_cpv_use(cpv)
150         iuse_flags = filter_flags(get_iuse(cpv), use_expand_hidden, usemasked, useforced)
151         #flags = filter_flags(use_flags, use_expand_hidden, usemasked, useforced)
152         if final_setting:
153                 final_flags = filter_flags(final_use,  use_expand_hidden, usemasked, useforced)
154                 return iuse_flags, final_flags
155         return iuse_flags
156
157 class FlagAnalyzer(object):
158         """Specialty functions for analysing an installed package's
159         USE flags.  Can be used for single or mulitple use without
160         needing to be reset unless the system USE flags are changed.
161         
162         @type system: list or set
163         @param system: the default system USE flags.
164         @type _get_flags: function
165         @param _get_flags: Normally defaulted, can be overriden for testing
166         @type _get_used: function
167         @param _get_used: Normally defaulted, can be overriden for testing
168                 """
169         def __init__(self,
170                 system,
171                 _get_flags=get_flags,
172                 _get_used=get_installed_use
173         ):
174                 self.get_flags = _get_flags
175                 self.get_used = _get_used
176                 self.reset(system)
177
178         def reset(self, system):
179                 """Resets the internal system USE flags and use_expand variables
180                 to the new setting. The use_expand variable is handled internally.
181                 
182                 @type system: list or set
183                 @param system: the default system USE flags.
184                 """
185                 self.system = set(system)
186                 self.use_expand = portage.settings['USE_EXPAND'].lower().split()
187
188         def analyse_cpv(self, cpv):
189                 """Gets all relavent USE flag info for a cpv and breaks them down
190                 into 3 sets, plus (package.use enabled), minus ( package.use disabled),
191                 unset.
192                 
193                 @param cpv: string. 'cat/pkg-ver'
194                 @rtype tuple of sets
195                 @return (plus, minus, unset) sets of USE flags
196                 """
197                 installed = set(self.get_used(cpv, "USE"))
198                 iuse =  set(abs_list(self.get_flags(cpv)))
199                 return self._analyse(installed, iuse)
200
201         def _analyse(self, installed, iuse):
202                 """Analyses the supplied info and returns the flag settings
203                 that differ from the defaults
204                 
205                 @type installed: set
206                 @param installed: the installed with use flags
207                 @type iuse: set
208                 @param iuse: the current ebuilds IUSE
209                 """
210                 defaults = self.system.intersection(iuse)
211                 usedflags = iuse.intersection(set(installed))
212                 plus = usedflags.difference(defaults)
213                 minus = defaults.difference(usedflags)
214                 unset = iuse.difference(defaults, plus, minus)
215                 cleaned = self.remove_expanding(unset)
216                 return (plus, minus, cleaned)
217
218         def analyse_pkg(self, pkg):
219                 """Gets all relevent USE flag info for a pkg and breaks them down
220                 into 3 sets, plus (package.use enabled), minus ( package.use disabled),
221                 unset.
222                 
223                 @param pkg: gentoolkit.package.Package object
224                 @rtype tuple of sets
225                 @return (plus, minus, unset) sets of USE flags
226                 """
227                 installed = set(self.pkg_used(pkg))
228                 iuse =  set(abs_list(self.pkg_flags(pkg)))
229                 return self._analyse(installed, iuse)
230
231         def pkg_used(self, pkg):
232                 return pkg.use().split()
233
234         def pkg_flags(self, pkg):
235                 final_use, use_expand_hidden, usemasked, useforced = \
236                         get_all_cpv_use(pkg.cpv)
237                 flags = pkg.environment("IUSE", prefer_vdb=False).split()
238                 return filter_flags(flags, use_expand_hidden, usemasked, useforced)
239
240         def redundant(self, cpv, iuse):
241                 """Checks for redundant settings.
242                 future function. Not yet implemented.
243                 """
244                 pass
245
246         def remove_expanding(self, flags):
247                 """Remove unwanted USE_EXPAND flags
248                 from unset IUSE sets
249                 
250                 @param flags: short list or set of USE flags
251                 @rtype set
252                 @return USE flags
253                 """
254                 _flags = set(flags)
255                 for expander in self.use_expand:
256                         for flag in flags:
257                                 if expander in flag:
258                                         _flags.remove(flag)
259                         if not _flags:
260                                 break
261                 return _flags
262
263
264 class KeywordAnalyser(object):
265         """Specialty functions for analysing the installed package db for
266         keyword useage and the packages that used them.
267         
268         Note: should be initialized with the internal set_order() before use.
269         See internal set_order() for more details.
270         This class of functions can be used for single cpv checks or
271         used repeatedly for an entire package db.
272         
273         @type  arch: string
274         @param arch: the system ARCH setting
275         @type  accept_keywords: list
276         @param accept_keywords: eg. ['x86', '~x86']
277         @type  get_aux: function, defaults to: VARDB.aux_get
278         @param vardb: vardb class of functions, needed=aux_get()
279                 to return => KEYWORDS & USE flags for a cpv
280                 = aux_get(cpv, ["KEYWORDS", "USE"])
281         """
282
283         # parsing order to determine appropriate keyword used for installation
284         normal_order = ['stable', 'testing', 'prefix', 'testing_prefix', 'missing']
285         prefix_order = ['prefix', 'testing_prefix', 'stable', 'testing', 'missing']
286         parse_range = list(range(len(normal_order)))
287
288
289         def __init__(self, arch, accept_keywords, vardb=VARDB):
290                 self.arch = arch
291                 self.accept_keywords = accept_keywords
292                 self.vardb = vardb
293                 self.prefix = ''
294                 self.parse_order = None
295                 self.check_key = {
296                         'stable': self._stable,
297                         'testing': self._testing,
298                         'prefix': self._prefix,
299                         'testing_prefix': self._testing_prefix,
300                         'missing': self._missing
301                         }
302                 self.mismatched = []
303
304         def determine_keyword(self, keywords, used, cpv):
305                 """Determine the keyword from the installed USE flags and 
306                 the KEYWORDS that was used to install a package.
307                 
308                 @param keywords: list of keywords available to install a pkg
309                 @param used: list of USE flalgs recorded for the installed pkg
310                 @rtype: string
311                 @return a keyword or null string
312                 """
313                 used = set(used)
314                 kwd = None
315                 result = ''
316                 if keywords:
317                         absolute_kwds = abs_keywords(keywords)
318                         kwd = list(used.intersection(absolute_kwds))
319                         #if keywords == ['~ppc64']:
320                                 #print "Checked keywords for kwd", keywords, used, "kwd =", kwd
321                 if not kwd:
322                         #print "Checking for kwd against portage.archlist"
323                         absolute_kwds = abs_keywords(keywords)
324                         # check for one against archlist then re-check
325                         kwd = list(absolute_kwds.intersection(portage.archlist))
326                         #print "determined keyword =", kwd
327                 if len(kwd) == 1:
328                         key = kwd[0]
329                         #print "determined keyword =", key
330                 elif not kwd:
331                         #print "kwd != 1", kwd, cpv
332                         result = self._missing(self.keyword, keywords)
333                 else: # too many, try to narrow them dowm
334                         #print "too many kwd's, trying to match against arch"
335                         _kwd = list(set(kwd).intersection(self.arch))
336                         key = ''
337                         if _kwd:
338                                 #print "found one! :)", _kwd
339                                 key = _kwd
340                         else: # try re-running the short list against archlist
341                                 #print "Checking kwd for _kwd against portage.archlist"
342                                 _kwd = list(set(kwd).intersection(portage.archlist))
343                                 if _kwd and len(_kwd) == 1:
344                                         #print "found one! :)", _kwd
345                                         key = _kwd[0]
346                                 else:
347                                         #print " :( didn't work, _kwd =", _kwd, "giving up on:", cpv
348                                         result = self._missing(self.keyword, keywords)
349                 i = 0
350                 while not result and i in self.parse_range:
351                         parsekey = self.parse_order[i]
352                         result = self.check_key[parsekey](key, keywords)
353                         i += 1
354                 return result
355
356         def _stable(self, key, keywords):
357                 """test for a normal stable keyword"""
358                 if key in keywords:
359                         return key
360                 return ''
361
362         def _testing(self, key, keywords):
363                 """test for a normal testing keyword"""
364                 if ("~" + key) in keywords:
365                         return "~" + key
366                 return ''
367
368         def _prefix(self, key, keywords):
369                 """test for a stable prefix keyword"""
370                 if not self.prefix:
371                         return ''
372                 _key = '-'.join([key, self.prefix])
373                 if _key in keywords:
374                         #print key, "is in", keywords
375                         return _key
376                 return ''
377
378         def _testing_prefix(self, key, keywords):
379                 """test for a testing prefix keyword"""
380                 if not self.prefix:
381                         return ''
382                 _key = "~" +'-'.join([key, self.prefix])
383                 if _key in keywords:
384                         #print key, "is in", keywords
385                         return _key
386                 return ''
387
388         def _missing(self, key, keywords):
389                 """generates a missing keyword to return"""
390                 if self.prefix and key != self.keyword:
391                         _key = '-'.join([key, self.prefix])
392                 else:
393                         _key = '-' + key
394                 #print "_missisng :(  _key =", _key
395                 return _key
396
397         def get_inst_keyword_cpv(self, cpv):
398                 """Determines the installed with keyword for cpv
399                 
400                 @type cpv: string
401                 @param cpv: an installed CAT/PKG-VER
402                 @rtype: string
403                 @returns a keyword determined to have been used to install cpv
404                 """
405                 keywords, used = self.vardb.aux_get(cpv, ["KEYWORDS", "USE"])
406                 keywords = keywords.split()
407                 used = used.split()
408                 return self._parse(keywords, used, cpv=cpv)
409
410         def get_inst_keyword_pkg(self, pkg):
411                 """Determines the installed with keyword for cpv
412                 
413                 @param pkg: gentoolkit.package.Package object
414                 @rtype: string
415                 @returns a keyword determined to have been used to install cpv
416                 """
417                 keywords, used = pkg.environment(["KEYWORDS", "USE"],
418                         prefer_vdb=True, fallback=False)
419                 keywords = keywords.split()
420                 used = used.split()
421                 return self._parse(keywords, used, pkg=pkg)
422
423         def _parse(self, keywords, used, pkg=None, cpv=None):
424                 if pkg:
425                         _cpv = pkg.cpv
426                 else:
427                         _cpv = cpv
428                 if not self.parse_order:
429                         self.set_order(used)
430                 keyword = self.keyword
431                 # sanity check
432                 if self.arch not in used:
433                         #print "Found a mismatch = ", cpv, self.arch, used
434                         self.mismatched.append(_cpv)
435                 if keyword in keywords:
436                         #print "keyword", keyword, "is in", keywords
437                         return keyword
438                 elif "~"+keyword in keywords:
439                         #print "~keyword", keyword, "is in", keywords
440                         return "~"+keyword
441                 else:
442                         keyword = self.determine_keyword(keywords, used, _cpv)
443                         if not keyword:
444                                 raise errors.GentoolkitUnknownKeyword(_cpv, ' '.join(keywords), used)
445                         return keyword
446
447         def set_order(self, used):
448                 """Used to set the parsing order to determine a keyword
449                 used for installation.
450                 
451                 This is needed due to the way prefix arch's and keywords
452                 work with portage.  It looks for the 'prefix' flag. A positive result
453                 sets it to the prefix order and keyword.
454                 
455                 @type used: list
456                 @param used: a list of pkg USE flags or the system USE flags"""
457                 if 'prefix' in used:
458                         #print "SET_ORDER() Setting parse order to prefix"
459                         prefix = None
460                         self.parse_order = self.prefix_order
461                         for key in self.accept_keywords:
462                                 #print "SET_ORDER()  '"+key+"'"
463                                 if '-' in key:
464                                         #print "SET_ORDER()found prefix keyword :", key
465                                         if self.arch in key:
466                                                 prefix = key.split('-')[1]
467                                                 #print "prefix =", prefix
468                                                 self.prefix = prefix
469                         self.keyword = '-'.join([self.arch, prefix])
470                 else:
471                         #print "SET_ORDER() Setting parse order to normal"
472                         self.parse_order = self.normal_order
473                         self.keyword = self.arch
474                 #print "SET_ORDER() completed: prefix =", self.prefix, ", keyword =", \
475                 #       self.keyword, "parse order =",self.parse_order
476                 #print
477