]> git.ipfire.org Git - pakfire.git/blob - python/pakfire/packages/base.py
Rewrite the buildsystem of this package.
[pakfire.git] / python / pakfire / packages / base.py
1 #!/usr/bin/python
2 ###############################################################################
3 # #
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 import datetime
23 import logging
24 import os
25 import xml.sax.saxutils
26
27 import pakfire.util as util
28 from pakfire.i18n import _
29
30 class Package(object):
31 def __init__(self, pakfire, repo=None):
32 self.pakfire = pakfire
33 self._repo = repo
34
35 # Pointer to a package that is updated by this one.
36 self.old_package = None
37
38 def __repr__(self):
39 return "<%s %s>" % (self.__class__.__name__, self.friendly_name)
40
41 def __cmp__(self, other):
42 # if packages differ names return in alphabetical order
43 if not self.name == other.name:
44 return cmp(self.name, other.name)
45
46 ret = util.version_compare(self.pakfire.pool,
47 self.friendly_version, other.friendly_version)
48
49 # XXX this is to move packages that have been built a while ago and
50 # do not have all the meta information that they won't be evaluated
51 # as the best match.
52 if not ret:
53 if "X"*3 in (self.build_id, other.build_id):
54 if self.build_id == "X"*3 and not other.build_id == "X"*3:
55 ret = -1
56
57 elif not self.build_id == "X"*3 and other.build_id == "X"*3:
58 ret = 1
59 # XXX hack end
60
61 # Compare the build times if we have a rebuilt package.
62 if not ret and self.build_time and other.build_time:
63 ret = cmp(self.build_time, other.build_time)
64
65 #if ret == 0:
66 # logging.debug("%s is equal to %s" % (self, other))
67 #elif ret < 0:
68 # logging.debug("%s is more recent than %s" % (other, self))
69 #elif ret > 0:
70 # logging.debug("%s is more recent than %s" % (self, other))
71
72 # If no rank could be created, sort by repository priority
73 if not ret:
74 ret = cmp(self.repo, other.repo)
75
76 return ret
77
78 def __hash__(self):
79 hashstr = ["%s" % s for s in (self.name, self.epoch, self.version,
80 self.release, self.arch,)]
81
82 return hash("-".join(hashstr))
83
84 def dump(self, short=False, long=False, filelist=False):
85 if short:
86 return "%s.%s : %s" % (self.name, self.arch, self.summary)
87
88 items = [
89 (_("Name"), self.name),
90 (_("Arch"), self.arch),
91 (_("Version"), self.version),
92 (_("Release"), self.release),
93 ]
94
95 if self.size:
96 items.append((_("Size"), util.format_size(self.size)))
97
98 # filter dummy repository
99 if not self.repo == self.pakfire.repos.dummy:
100 items.append((_("Repo"), self.repo.name))
101
102 items += [
103 (_("Summary"), self.summary),
104 (_("Groups"), " ".join(self.groups)),
105 (_("URL"), self.url),
106 (_("License"), self.license),
107 ]
108
109 caption = _("Description")
110 for line in util.text_wrap(self.description):
111 items.append((caption, line))
112 caption = ""
113
114 if long:
115 if self.maintainer:
116 items.append((_("Maintainer"), self.maintainer))
117
118 items.append((_("UUID"), self.uuid))
119 items.append((_("Build ID"), self.build_id))
120 items.append((_("Build date"), self.build_date))
121 items.append((_("Build host"), self.build_host))
122
123 caption = _("Provides")
124 for prov in sorted(self.provides):
125 items.append((caption, prov))
126 caption = ""
127
128 caption = _("Pre-requires")
129 for req in sorted(self.prerequires):
130 items.append((caption, req))
131 caption = ""
132
133 caption = _("Requires")
134 for req in sorted(self.requires):
135 items.append((caption, req))
136 caption = ""
137
138 caption = _("Conflicts")
139 for req in sorted(self.conflicts):
140 items.append((caption, req))
141 caption = ""
142
143 caption = _("Obsoletes")
144 for req in sorted(self.obsoletes):
145 items.append((caption, req))
146 caption = ""
147
148 # Append filelist if requested.
149 if filelist:
150 for file in self.filelist:
151 items.append((_("File"), file))
152
153 format = "%%-%ds : %%s" % (max([len(k) for k, v in items]))
154
155 s = []
156 for caption, value in items:
157 s.append(format % (caption, value))
158
159 s.append("") # New line at the end
160
161 # XXX why do we need to decode this?
162 return "\n".join([str.decode("utf-8") for str in s])
163
164 @property
165 def info(self):
166 info = {
167 "name" : self.name,
168 "version" : self.version,
169 "release" : self.release,
170 "epoch" : self.epoch,
171 "arch" : self.arch,
172 "groups" : self.groups,
173 "summary" : self.summary,
174 "description" : self.description,
175 "maintainer" : self.maintainer,
176 "url" : self.url,
177 "license" : self.license,
178 "hash1" : self.hash1,
179 "vendor" : self.vendor,
180 "build_date" : self.build_date,
181 "build_host" : self.build_host,
182 "build_id" : self.build_id,
183 "build_time" : self.build_time,
184 "size" : self.size,
185 "inst_size" : self.inst_size,
186 }
187
188 return info
189
190 @property
191 def hash1(self):
192 return "0"*40
193
194 @property
195 def size(self):
196 """
197 Return the size of the package file.
198
199 This should be overloaded by another class and returns 0 for
200 virtual packages.
201 """
202 return 0
203
204 @property
205 def inst_size(self):
206 raise NotImplementedError, "%s" % self
207
208 @property
209 def local(self):
210 """
211 Indicates whether a package is located "local" means on disk
212 and has not be downloaded.
213 """
214 return False
215
216 ### META INFORMATION ###
217
218 @property
219 def metadata(self):
220 raise NotImplementedError
221
222 @property
223 def friendly_name(self):
224 return "%s-%s.%s" % (self.name, self.friendly_version, self.arch)
225
226 @property
227 def friendly_version(self):
228 s = "%s-%s" % (self.version, self.release)
229
230 if self.epoch:
231 s = "%d:%s" % (self.epoch, s)
232
233 return s
234
235 @property
236 def repo(self):
237 if self._repo:
238 return self._repo
239
240 # By default, every package is connected to a dummy repository
241 return self.pakfire.repos.dummy
242
243 @property
244 def name(self):
245 return self.metadata.get("PKG_NAME")
246
247 @property
248 def version(self):
249 return self.metadata.get("PKG_VER")
250
251 @property
252 def release(self):
253 ret = None
254
255 for i in ("PKG_RELEASE", "PKG_REL"):
256 ret = self.metadata.get(i, None)
257 if ret:
258 break
259
260 return ret
261
262 @property
263 def epoch(self):
264 epoch = self.metadata.get("PKG_EPOCH", 0)
265
266 return int(epoch)
267
268 @property
269 def arch(self):
270 raise NotImplementedError
271
272 @property
273 def base(self):
274 """
275 Say if a package belongs to the basic set
276 that is installed by default.
277 """
278 return "Base" in self.groups
279
280 @property
281 def critical(self):
282 """
283 Return if a package is marked "critial".
284 """
285 return "Critical" in self.groups
286
287 @property
288 def type(self):
289 return self.metadata.get("TYPE", "unknown")
290
291 @property
292 def maintainer(self):
293 return self.metadata.get("PKG_MAINTAINER")
294
295 @property
296 def license(self):
297 return self.metadata.get("PKG_LICENSE")
298
299 @property
300 def summary(self):
301 return self.metadata.get("PKG_SUMMARY")
302
303 @property
304 def description(self):
305 return self.metadata.get("PKG_DESCRIPTION")
306
307 @property
308 def groups(self):
309 return self.metadata.get("PKG_GROUPS", "").split()
310
311 @property
312 def url(self):
313 return self.metadata.get("PKG_URL")
314
315 @property
316 def triggers(self):
317 triggers = self.metadata.get("PKG_TRIGGERS", "")
318
319 return triggers.split()
320
321 @property
322 def signature(self):
323 raise NotImplementedError
324
325 @property
326 def build_date(self):
327 """
328 Automatically convert the UNIX timestamp from self.build_time to
329 a humanly readable format.
330 """
331 if self.build_time is None:
332 return _("Not set")
333
334 return "%s UTC" % datetime.datetime.utcfromtimestamp(self.build_time)
335
336 @property
337 def build_host(self):
338 return self.metadata.get("BUILD_HOST")
339
340 @property
341 def build_id(self):
342 return self.metadata.get("BUILD_ID")
343
344 @property
345 def build_time(self):
346 build_time = self.metadata.get("BUILD_TIME", 0)
347
348 return int(build_time)
349
350 @property
351 def uuid(self):
352 return self.metadata.get("PKG_UUID", None)
353
354 @property
355 def supported_arches(self):
356 return self.metadata.get("PKG_SUPPORTED_ARCHES", "all")
357
358 @property
359 def vendor(self):
360 return self.metadata.get("PKG_VENDOR", "")
361
362 @property
363 def prerequires(self):
364 requires = self.metadata.get("PKG_PREREQUIRES", "")
365
366 return requires.split()
367
368 @property
369 def requires(self):
370 ret = ""
371
372 # The default attributes, that are process for the requires.
373 attrs = ["PKG_REQUIRES", "PKG_DEPS",]
374
375 if self.arch == "src":
376 attrs += ["PKG_BUILD_DEPS",]
377
378 for i in attrs:
379 ret = self.metadata.get(i, ret)
380 if ret:
381 break
382
383 return ret.split()
384
385 @property
386 def provides(self):
387 return self.metadata.get("PKG_PROVIDES", "").split()
388
389 @property
390 def conflicts(self):
391 return self.metadata.get("PKG_CONFLICTS", "").split()
392
393 @property
394 def obsoletes(self):
395 return self.metadata.get("PKG_OBSOLETES", "").split()
396
397 @property
398 def scriptlets(self):
399 return self.metadata.get("PKG_SCRIPTLETS", "").split()
400
401 @property
402 def filelist(self):
403 raise NotImplementedError
404
405 def extract(self, path, prefix=None):
406 raise NotImplementedError, "%s" % repr(self)
407
408 def remove(self, message=None, prefix=None):
409 # Make two filelists. One contains all binary files that need to be
410 # removed, the other one contains the configuration files which are
411 # kept. files and configfiles are disjunct.
412 files = []
413 configfiles = self.configfiles
414
415 for file in self.filelist:
416 if file in configfiles:
417 continue
418
419 assert file.startswith("/")
420 files.append(file)
421
422 self._remove_files(files, message, prefix)
423
424 def _remove_files(self, files, message, prefix):
425 if prefix in ("/", None):
426 prefix = ""
427
428 # Load progressbar.
429 pb = None
430 if message:
431 message = "%-10s : %s" % (message, self.friendly_name)
432 pb = util.make_progress(message, len(files), eta=False)
433
434 # Sort files by the length of their name to remove all files in
435 # a directory first and then check, if there are any files left.
436 files.sort(cmp=lambda x,y: cmp(len(x), len(y)), reverse=True)
437
438 i = 0
439 for _file in files:
440 # Update progress.
441 if pb:
442 i += 1
443 pb.update(i)
444
445 logging.debug("Removing file: %s" % _file)
446
447 if prefix:
448 file = os.path.join(prefix, _file[1:])
449 assert file.startswith("%s/" % prefix)
450 else:
451 file = _file
452
453 # If the file was removed by the user, we can skip it.
454 if not os.path.exists(file):
455 continue
456
457 # Handle regular files and symlinks.
458 if os.path.isfile(file) or os.path.islink(file):
459 try:
460 os.remove(file)
461 except OSError:
462 logging.error("Cannot remove file: %s. Remove manually." % _file)
463
464 # Handle directories.
465 # Skip removal if the directory is a mountpoint.
466 elif os.path.isdir(file) and not os.path.ismount(file):
467 # Try to remove the directory. If it is not empty, OSError is raised,
468 # but we are okay with that.
469 try:
470 os.rmdir(file)
471 except OSError:
472 pass
473
474 # Log all unhandled types.
475 else:
476 logging.warning("Cannot remove file: %s. Filetype is unhandled." % _file)
477
478 if pb:
479 pb.finish()
480
481 # XXX Rename config files