Fix package.py to account for PORTDIR being a symbolic link when checking if a packag...
[gentoolkit.git] / trunk / src / gentoolkit / package.py
1 #! /usr/bin/python2
2 #
3 # Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
4 # Copyright(c) 2004, Gentoo Foundation
5 #
6 # Licensed under the GNU General Public License, v2
7 #
8 # $Header$
9
10 import os
11 from errors import FatalError
12 import portage
13 from gentoolkit import *
14
15 class Package:
16         """Package descriptor. Contains convenience functions for querying the
17         state of a package, its contents, name manipulation, ebuild info and
18         similar."""
19
20         def __init__(self,cpv):
21                 self._cpv = cpv
22                 self._scpv = portage.catpkgsplit(self._cpv)
23                 
24                 if not self._scpv:
25                         raise FatalError("invalid cpv: %s" % cpv)
26                 self._db = None
27                 self._settings = settings
28                 self._settingslock = settingslock
29                 self._portdir_path = settings["PORTDIR"]
30                 if os.path.islink(self._portdir_path):
31                         self._portdir_path = os.path.join(os.path.dirname(self._portdir_path), os.readlink(self._portdir_path))
32
33         def get_name(self):
34                 """Returns base name of package, no category nor version"""
35                 return self._scpv[1]
36
37         def get_version(self):
38                 """Returns version of package, with revision number"""
39                 v = self._scpv[2]
40                 if self._scpv[3] != "r0":
41                         v += "-" + self._scpv[3]
42                 return v
43
44         def get_category(self):
45                 """Returns category of package"""
46                 return self._scpv[0]
47
48         def get_settings(self, key):
49                 """Returns the value of the given key for this package (useful 
50                 for package.* files."""
51                 self._settingslock.acquire()
52                 self._settings.setcpv(self._cpv)
53                 v = self._settings[key]
54                 self._settingslock.release()
55                 return v
56
57         def get_cpv(self):
58                 """Returns full Category/Package-Version string"""
59                 return self._cpv
60
61         def get_provide(self):
62                 """Return a list of provides, if any"""
63                 if not self.is_installed():
64                         try:
65                                 x = [self.get_env_var('PROVIDE')]
66                         except KeyError:
67                                 x = []
68                         return x
69                 else:
70                         return vartree.get_provide(self._cpv)
71
72         def get_dependants(self):
73                 """Retrieves a list of CPVs for all packages depending on this one"""
74                 raise NotImplementedError("Not implemented yet!")
75
76         def get_runtime_deps(self):
77                 """Returns a linearised list of first-level run time dependencies for this package, on
78                 the form [(comparator, [use flags], cpv), ...]"""
79                 # Try to use the portage tree first, since emerge only uses the tree when calculating dependencies
80                 try:
81                         cd = self.get_env_var("RDEPEND", porttree).split()
82                 except KeyError:
83                         cd = self.get_env_var("RDEPEND", vartree).split()
84                 r,i = self._parse_deps(cd)
85                 return r
86
87         def get_compiletime_deps(self):
88                 """Returns a linearised list of first-level compile time dependencies for this package, on
89                 the form [(comparator, [use flags], cpv), ...]"""
90                 # Try to use the portage tree first, since emerge only uses the tree when calculating dependencies
91                 try:
92                         rd = self.get_env_var("DEPEND", porttree).split()
93                 except KeyError:
94                         rd = self.get_env_var("DEPEND", vartree).split()
95                 r,i = self._parse_deps(rd)
96                 return r
97
98         def get_postmerge_deps(self):
99                 """Returns a linearised list of first-level post merge dependencies for this package, on
100                 the form [(comparator, [use flags], cpv), ...]"""
101                 # Try to use the portage tree first, since emerge only uses the tree when calculating dependencies
102                 try:
103                         pd = self.get_env_var("PDEPEND", porttree).split()
104                 except KeyError:
105                         pd = self.get_env_var("PDEPEND", vartree).split()
106                 r,i = self._parse_deps(pd)
107                 return r
108
109         def _parse_deps(self,deps,curuse=[],level=0):
110                 # store (comparator, [use predicates], cpv)
111                 r = []
112                 comparators = ["~","<",">","=","<=",">="]
113                 end = len(deps)
114                 i = 0
115                 while i < end:
116                         tok = deps[i]
117                         if tok == ')':
118                                 return r,i
119                         if tok[-1] == "?":
120                                 tok = tok.replace("?","")
121                                 sr,l = self._parse_deps(deps[i+2:],curuse=curuse+[tok],level=level+1)
122                                 r += sr
123                                 i += l + 3
124                                 continue
125                         if tok == "||":
126                                 sr,l = self._parse_deps(deps[i+2:],curuse,level=level+1)
127                                 r += sr
128                                 i += l + 3
129                                 continue
130                         # conjonction, like in "|| ( ( foo bar ) baz )" => recurse
131                         if tok == "(":
132                                 sr,l = self._parse_deps(deps[i+1:],curuse,level=level+1)
133                                 r += sr
134                                 i += l + 2
135                                 continue
136                         # pkg block "!foo/bar" => ignore it
137                         if tok[0] == "!":
138                                 i += 1
139                                 continue
140                         # pick out comparator, if any
141                         cmp = ""
142                         for c in comparators:
143                                 if tok.find(c) == 0:
144                                         cmp = c
145                         tok = tok[len(cmp):]
146                         r.append((cmp,curuse,tok))
147                         i += 1
148                 return r,i
149
150         def is_installed(self):
151                 """Returns true if this package is installed (merged)"""
152                 self._initdb()
153                 return os.path.exists(self._db.getpath())
154
155         def is_overlay(self):
156                 """Returns true if the package is in an overlay."""
157                 dir,ovl = portage.portdb.findname2(self._cpv)
158                 return ovl != self._portdir_path
159
160         def is_masked(self):
161                 """Returns true if this package is masked against installation. Note: We blindly assume that
162                 the package actually exists on disk somewhere."""
163                 unmasked = portage.portdb.xmatch("match-visible", "=" + self._cpv)
164                 return self._cpv not in unmasked
165
166         def get_ebuild_path(self,in_vartree=0):
167                 """Returns the complete path to the .ebuild file"""
168                 if in_vartree:
169                         return vartree.getebuildpath(self._cpv)
170                 else:
171                         return portage.portdb.findname(self._cpv)
172
173         def get_package_path(self):
174                 """Returns the path to where the ChangeLog, Manifest, .ebuild files reside"""
175                 p = self.get_ebuild_path()
176                 sp = p.split("/")
177                 if len(sp):
178                         return "/".join(sp[:-1])
179
180         def get_env_var(self, var, tree=""):
181                 """Returns one of the predefined env vars DEPEND, RDEPEND, SRC_URI,...."""
182                 if tree == "":
183                         mytree = vartree
184                         if not self.is_installed():
185                                 mytree = porttree
186                 else:
187                         mytree = tree
188                 r = mytree.dbapi.aux_get(self._cpv,[var])
189                 if not r:
190                         raise FatalError("Could not find the package tree")
191                 if len(r) != 1:
192                         raise FatalError("Should only get one element!")
193                 return r[0]
194
195         def get_use_flags(self):
196                 """Returns the USE flags active at time of installation"""
197                 self._initdb()
198                 if self.is_installed():
199                         return self._db.getfile("USE")
200                 return ""
201
202         def get_contents(self):
203                 """Returns the full contents, as a dictionary, on the form
204                 [ '/bin/foo' : [ 'obj', '1052505381', '45ca8b8975d5094cd75bdc61e9933691' ], ... ]"""
205                 self._initdb()
206                 if self.is_installed():
207                         return self._db.getcontents()
208                 return {}               
209
210         def compare_version(self,other):
211                 """Compares this package's version to another's CPV; returns -1, 0, 1"""
212                 v1 = self._scpv
213                 v2 = portage.catpkgsplit(other.get_cpv())
214                 # if category is different
215                 if v1[0] != v2[0]:
216                         return cmp(v1[0],v2[0])
217                 # if name is different
218                 elif v1[1] != v2[1]:
219                         return cmp(v1[1],v2[1])
220                 # Compare versions
221                 else:
222                         return portage.pkgcmp(v1[1:],v2[1:])
223
224         def size(self):
225                 """Estimates the installed size of the contents of this package, if possible.
226                 Returns [size, number of files in total, number of uncounted files]"""
227                 contents = self.get_contents()
228                 size = 0
229                 uncounted = 0
230                 files = 0
231                 for x in contents:
232                         try:
233                                 size += os.lstat(x).st_size
234                                 files += 1
235                         except OSError:
236                                 uncounted += 1
237                 return [size, files, uncounted]
238
239         def _initdb(self):
240                 """Internal helper function; loads package information from disk,
241                 when necessary"""
242                 if not self._db:
243                         cat = self.get_category()
244                         pnv = self.get_name()+"-"+self.get_version()
245                         self._db = portage.dblink(cat,pnv,settings["ROOT"],settings)