aboutsummaryrefslogtreecommitdiff
blob: 33198606fb733ac734c0128437d8a92f81b75ac2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#! /usr/bin/python2
#
# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
# Copyright(c) 2004, Gentoo Foundation
#
# Licensed under the GNU General Public License, v2
#
# $Header$

from errors import FatalError
import portage
from gentoolkit import *

class Package:
	"""Package descriptor. Contains convenience functions for querying the
	state of a package, its contents, name manipulation, ebuild info and
	similar."""

	def __init__(self,cpv):
		self._cpv = cpv
		self._scpv = portage.catpkgsplit(self._cpv)
		
		if not self._scpv:
			raise FatalError("invalid cpv: %s" % cpv)
		self._db = None
		self._settings = settings
		self._settingslock = settingslock

	def get_name(self):
		"""Returns base name of package, no category nor version"""
		return self._scpv[1]

	def get_version(self):
		"""Returns version of package, with revision number"""
		v = self._scpv[2]
		if self._scpv[3] != "r0":
			v += "-" + self._scpv[3]
		return v

	def get_category(self):
		"""Returns category of package"""
		return self._scpv[0]

	def get_settings(self, key):
		"""Returns the value of the given key for this package (useful 
		for package.* files."""
		self._settingslock.acquire()
		self._settings.setcpv(self._cpv)
		v = self._settings[key]
		self._settingslock.release()
		return v

	def get_cpv(self):
		"""Returns full Category/Package-Version string"""
		return self._cpv

	def get_provide(self):
		"""Return a list of provides, if any"""
		if not self.is_installed():
			try:
				x = [self.get_env_var('PROVIDE')]
			except KeyError:
				x = []
			return x
		else:
			return vartree.get_provide(self._cpv)

	def get_dependants(self):
		"""Retrieves a list of CPVs for all packages depending on this one"""
		raise NotImplementedError("Not implemented yet!")

	def get_runtime_deps(self):
		"""Returns a linearised list of first-level run time dependencies for this package, on
		the form [(comparator, [use flags], cpv), ...]"""
		# Try to use the portage tree first, since emerge only uses the tree when calculating dependencies
		try:
			cd = self.get_env_var("RDEPEND", porttree).split()
		except KeyError:
			cd = self.get_env_var("RDEPEND", vartree).split()
		r,i = self._parse_deps(cd)
		return r

	def get_compiletime_deps(self):
		"""Returns a linearised list of first-level compile time dependencies for this package, on
		the form [(comparator, [use flags], cpv), ...]"""
		# Try to use the portage tree first, since emerge only uses the tree when calculating dependencies
		try:
			rd = self.get_env_var("DEPEND", porttree).split()
		except KeyError:
			rd = self.get_env_var("DEPEND", vartree).split()
		r,i = self._parse_deps(rd)
		return r

	def get_postmerge_deps(self):
		"""Returns a linearised list of first-level post merge dependencies for this package, on
		the form [(comparator, [use flags], cpv), ...]"""
		# Try to use the portage tree first, since emerge only uses the tree when calculating dependencies
		try:
			pd = self.get_env_var("PDEPEND", porttree).split()
		except KeyError:
			pd = self.get_env_var("PDEPEND", vartree).split()
		r,i = self._parse_deps(pd)
		return r

	def _parse_deps(self,deps,curuse=[],level=0):
		# store (comparator, [use predicates], cpv)
		r = []
		comparators = ["~","<",">","=","<=",">="]
		end = len(deps)
		i = 0
		while i < end:
			tok = deps[i]
			if tok == ')':
				return r,i
			if tok[-1] == "?":
				tok = tok.replace("?","")
				sr,l = self._parse_deps(deps[i+2:],curuse=curuse+[tok],level=level+1)
				r += sr
				i += l + 3
				continue
			if tok == "||":
				sr,l = self._parse_deps(deps[i+2:],curuse,level=level+1)
				r += sr
				i += l + 3
				continue
			# conjonction, like in "|| ( ( foo bar ) baz )" => recurse
			if tok == "(":
				sr,l = self._parse_deps(deps[i+1:],curuse,level=level+1)
				r += sr
				i += l + 2
				continue
			# pkg block "!foo/bar" => ignore it
			if tok[0] == "!":
				i += 1
				continue
			# pick out comparator, if any
			cmp = ""
			for c in comparators:
				if tok.find(c) == 0:
					cmp = c
			tok = tok[len(cmp):]
			r.append((cmp,curuse,tok))
			i += 1
		return r,i

	def is_installed(self):
		"""Returns true if this package is installed (merged)"""
		self._initdb()
		return os.path.exists(self._db.getpath())

	def is_overlay(self):
		"""Returns true if the package is in an overlay."""
		dir,ovl = portage.portdb.findname2(self._cpv)
		return ovl != settings["PORTDIR"]

	def is_masked(self):
		"""Returns true if this package is masked against installation. Note: We blindly assume that
		the package actually exists on disk somewhere."""
		unmasked = portage.portdb.xmatch("match-visible", "=" + self._cpv)
		return self._cpv not in unmasked

	def get_ebuild_path(self,in_vartree=0):
		"""Returns the complete path to the .ebuild file"""
		if in_vartree:
			return vartree.getebuildpath(self._cpv)
		else:
			return portage.portdb.findname(self._cpv)

	def get_package_path(self):
		"""Returns the path to where the ChangeLog, Manifest, .ebuild files reside"""
		p = self.get_ebuild_path()
		sp = p.split("/")
		if len(sp):
			return "/".join(sp[:-1])

	def get_env_var(self, var, tree=""):
		"""Returns one of the predefined env vars DEPEND, RDEPEND, SRC_URI,...."""
		if tree == "":
			mytree = vartree
			if not self.is_installed():
				mytree = porttree
		else:
			mytree = tree
		r = mytree.dbapi.aux_get(self._cpv,[var])
		if not r:
			raise FatalError("Could not find the package tree")
		if len(r) != 1:
			raise FatalError("Should only get one element!")
		return r[0]

	def get_use_flags(self):
		"""Returns the USE flags active at time of installation"""
		self._initdb()
		if self.is_installed():
			return self._db.getfile("USE")
		return ""

	def get_contents(self):
		"""Returns the full contents, as a dictionary, on the form
		[ '/bin/foo' : [ 'obj', '1052505381', '45ca8b8975d5094cd75bdc61e9933691' ], ... ]"""
		self._initdb()
		if self.is_installed():
			return self._db.getcontents()
		return {}		

	def compare_version(self,other):
		"""Compares this package's version to another's CPV; returns -1, 0, 1"""
		v1 = self._scpv
		v2 = portage.catpkgsplit(other.get_cpv())
		# if category is different
		if v1[0] != v2[0]:
			return cmp(v1[0],v2[0])
		# if name is different
		elif v1[1] != v2[1]:
			return cmp(v1[1],v2[1])
		# Compare versions
		else:
			return portage.pkgcmp(v1[1:],v2[1:])

	def size(self):
		"""Estimates the installed size of the contents of this package, if possible.
		Returns [size, number of files in total, number of uncounted files]"""
		contents = self.get_contents()
		size = 0
		uncounted = 0
		files = 0
		for x in contents:
			try:
				size += os.lstat(x).st_size
				files += 1
			except OSError:
				uncounted += 1
		return [size, files, uncounted]

	def _initdb(self):
		"""Internal helper function; loads package information from disk,
		when necessary"""
		if not self._db:
			cat = self.get_category()
			pnv = self.get_name()+"-"+self.get_version()
			self._db = portage.dblink(cat,pnv,settings["ROOT"],settings)