]>
git.ipfire.org Git - people/ms/pakfire.git/blob - src/pakfire/packages/base.py
2 ###############################################################################
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
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. #
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. #
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/>. #
20 ###############################################################################
25 import xml
.sax
.saxutils
28 log
= logging
.getLogger("pakfire")
30 import pakfire
.util
as util
32 from pakfire
.constants
import *
33 from pakfire
.i18n
import _
35 class Package(object):
36 def __init__(self
, pakfire
, repo
=None):
37 self
.pakfire
= pakfire
40 # Pointer to a package that is updated by this one.
41 self
.old_package
= None
44 return "<%s %s>" % (self
.__class
__.__name
__, self
.friendly_name
)
46 def __cmp__(self
, other
):
47 # if packages differ names return in alphabetical order
48 if not self
.name
== other
.name
:
49 return cmp(self
.name
, other
.name
)
51 # If UUIDs match, the packages are absolutely equal.
52 if self
.uuid
== other
.uuid
:
53 #log.debug("%s is equal to %s by UUID" % (self, other))
56 ret
= util
.version_compare(self
.pakfire
.pool
,
57 self
.friendly_version
, other
.friendly_version
)
59 # XXX this is to move packages that have been built a while ago and
60 # do not have all the meta information that they won't be evaluated
63 # if "X"*3 in (self.build_id, other.build_id):
64 # if self.build_id == "X"*3 and not other.build_id == "X"*3:
67 # elif not self.build_id == "X"*3 and other.build_id == "X"*3:
71 # Compare the build times if we have a rebuilt package.
72 if not ret
and self
.build_time
and other
.build_time
:
73 ret
= cmp(self
.build_time
, other
.build_time
)
76 # log.debug("%s is equal to %s" % (self, other))
78 # log.debug("%s is more recent than %s" % (other, self))
80 # log.debug("%s is more recent than %s" % (self, other))
82 # If no rank could be created, sort by repository priority
84 ret
= cmp(self
.repo
, other
.repo
)
89 hashstr
= ["%s" % s
for s
in (self
.name
, self
.epoch
, self
.version
,
90 self
.release
, self
.arch
,)]
92 return hash("-".join(hashstr
))
94 def dump(self
, short
=False, long=False, filelist
=False):
96 return "%s.%s : %s" % (self
.name
, self
.arch
, self
.summary
)
99 (_("Name"), self
.name
),
102 # Show supported arches if available.
103 if hasattr(self
, "supported_arches") and not self
.supported_arches
== "all":
104 arch
= "%s (%s)" % (self
.arch
, self
.supported_arches
)
107 items
.append((_("Arch"), arch
))
110 (_("Version"), self
.version
),
111 (_("Release"), self
.release
),
115 items
.append((_("Size"), util
.format_size(self
.size
)))
119 (_("Installed size"),
120 util
.format_size(self
.inst_size
))
123 # filter dummy repository
124 if not self
.repo
== self
.pakfire
.repos
.dummy
:
125 items
.append((_("Repo"), self
.repo
.name
))
128 (_("Summary"), self
.summary
),
129 (_("Groups"), " ".join(self
.groups
)),
130 (_("URL"), self
.url
),
131 (_("License"), self
.license
),
134 caption
= _("Description")
135 for line
in util
.text_wrap(self
.description
):
136 items
.append((caption
, line
))
141 items
.append((_("Maintainer"), self
.maintainer
))
143 items
.append((_("Vendor"), self
.vendor
))
145 items
.append((_("UUID"), self
.uuid
))
146 items
.append((_("Build ID"), self
.build_id
))
147 items
.append((_("Build date"), self
.build_date
))
148 items
.append((_("Build host"), self
.build_host
))
150 caption
= _("Signatures")
151 for sig
in self
.signatures
:
152 items
.append((caption
, sig
))
155 caption
= _("Provides")
156 for prov
in sorted(self
.provides
):
157 items
.append((caption
, prov
))
160 caption
= _("Pre-requires")
161 for req
in sorted(self
.prerequires
):
162 items
.append((caption
, req
))
165 caption
= _("Requires")
166 for req
in sorted(self
.requires
):
167 items
.append((caption
, req
))
170 caption
= _("Conflicts")
171 for req
in sorted(self
.conflicts
):
172 items
.append((caption
, req
))
175 caption
= _("Obsoletes")
176 for req
in sorted(self
.obsoletes
):
177 items
.append((caption
, req
))
180 caption
= _("Recommends")
181 for req
in sorted(self
.recommends
):
182 items
.append((caption
, req
))
185 caption
= _("Suggests")
186 for req
in sorted(self
.suggests
):
187 items
.append((caption
, req
))
190 # Append filelist if requested.
192 for file in self
.filelist
:
193 items
.append((_("File"), file))
195 format
= "%%-%ds : %%s" % (max([len(k
) for k
, v
in items
]))
198 for caption
, value
in items
:
199 s
.append(format
% (caption
, value
))
201 s
.append("") # New line at the end
203 # XXX why do we need to decode this?
204 return "\n".join([str.decode("utf-8") for str in s
])
210 "version" : self
.version
,
211 "release" : self
.release
,
212 "epoch" : self
.epoch
,
214 "supported_arches" : self
.supported_arches
,
215 "groups" : self
.groups
,
216 "summary" : self
.summary
,
217 "description" : self
.description
,
218 "maintainer" : self
.maintainer
,
220 "license" : self
.license
,
221 "hash1" : self
.hash1
,
222 "vendor" : self
.vendor
,
223 "build_date" : self
.build_date
,
224 "build_host" : self
.build_host
,
225 "build_id" : self
.build_id
,
226 "build_time" : self
.build_time
,
228 "inst_size" : self
.inst_size
,
240 Return the size of the package file.
242 This should be overloaded by another class and returns 0 for
250 The used disk space when the package is installed.
252 Returns None if inst_size is unknown.
259 Indicates whether a package is located "local" means on disk
260 and has not be downloaded.
264 ### META INFORMATION ###
268 raise NotImplementedError, self
271 def friendly_name(self
):
272 return "%s-%s.%s" % (self
.name
, self
.friendly_version
, self
.arch
)
275 def friendly_version(self
):
276 s
= "%s-%s" % (self
.version
, self
.release
)
279 s
= "%d:%s" % (self
.epoch
, s
)
288 # By default, every package is connected to a dummy repository
289 return self
.pakfire
.repos
.dummy
293 return self
.metadata
.get("PKG_NAME")
297 return self
.metadata
.get("PKG_VER")
303 for i
in ("PKG_RELEASE", "PKG_REL"):
304 ret
= self
.metadata
.get(i
, None)
312 epoch
= self
.metadata
.get("PKG_EPOCH", 0)
318 raise NotImplementedError
323 Say if a package belongs to the basic set
324 that is installed by default.
326 return "Base" in self
.groups
331 Return if a package is marked "critial".
333 return "Critical" in self
.groups
335 def is_installed(self
):
336 return self
.repo
.name
== "@system"
340 return self
.metadata
.get("TYPE", "unknown")
343 def maintainer(self
):
344 return self
.metadata
.get("PKG_MAINTAINER")
348 return self
.metadata
.get("PKG_LICENSE")
352 return self
.metadata
.get("PKG_SUMMARY")
355 def description(self
):
356 return self
.metadata
.get("PKG_DESCRIPTION")
360 return self
.metadata
.get("PKG_GROUPS", "").split()
364 return self
.metadata
.get("PKG_URL")
368 triggers
= self
.metadata
.get("PKG_TRIGGERS", "")
370 return triggers
.split()
373 def signatures(self
):
377 def build_date(self
):
379 Automatically convert the UNIX timestamp from self.build_time to
380 a humanly readable format.
382 if self
.build_time
is None:
385 return "%s UTC" % datetime
.datetime
.utcfromtimestamp(self
.build_time
)
388 def build_host(self
):
389 return self
.metadata
.get("BUILD_HOST")
393 return self
.metadata
.get("BUILD_ID")
396 def build_time(self
):
397 build_time
= self
.metadata
.get("BUILD_TIME", 0)
399 return int(build_time
)
403 return self
.metadata
.get("PKG_UUID", None)
406 def supported_arches(self
):
407 return self
.metadata
.get("PKG_SUPPORTED_ARCHES", "all")
411 return self
.metadata
.get("PKG_VENDOR", "")
414 def filter_deps(deps
):
416 Filter out invalid dependencies.
418 This is just for reading packages and skipping comments
424 # Remove any leading or trailing spaces.
427 # Skip empty strings.
431 # Skip comment lines.
432 if dep
.startswith("#"):
440 def prerequires(self
):
441 requires
= self
.metadata
.get("PKG_PREREQUIRES", "")
443 return requires
.split()
449 # The default attributes, that are process for the requires.
450 attrs
= ["PKG_REQUIRES", "PKG_DEPS",]
452 if self
.arch
== "src":
453 attrs
+= ["PKG_BUILD_DEPS",]
456 ret
= self
.metadata
.get(i
, ret
)
460 return ret
.splitlines()
464 return self
.metadata
.get("PKG_PROVIDES", "").splitlines()
468 return self
.metadata
.get("PKG_CONFLICTS", "").splitlines()
472 return self
.metadata
.get("PKG_OBSOLETES", "").splitlines()
475 def recommends(self
):
483 def scriptlets(self
):
484 return self
.metadata
.get("PKG_SCRIPTLETS", "").splitlines()
486 def get_scriptlet(self
, action
):
487 raise NotImplementedError
491 raise NotImplementedError
494 def configfiles(self
):
501 def extract(self
, path
, prefix
=None):
502 raise NotImplementedError, "%s" % repr(self
)
504 def remove(self
, message
=None, prefix
=None):
505 # Make two filelists. One contains all binary files that need to be
506 # removed, the other one contains the configuration files which are
507 # kept. files and configfiles are disjunct.
509 configfiles
= self
.configfiles
510 datafiles
= self
.datafiles
512 for file in self
.filelist
:
513 if file in configfiles
:
516 if file in datafiles
:
519 assert file.startswith("/")
522 self
._remove
_files
(files
, message
, prefix
)
524 def _remove_files(self
, files
, message
, prefix
):
525 if prefix
in ("/", None):
531 message
= "%-10s : %s" % (message
, self
.friendly_name
)
532 pb
= util
.make_progress(message
, len(files
), eta
=False)
534 # Sort files by the length of their name to remove all files in
535 # a directory first and then check, if there are any files left.
536 files
.sort(cmp=lambda x
,y
: cmp(len(x
.name
), len(y
.name
)), reverse
=True)
538 # Messages to the user.
548 log
.debug("Removing file: %s" % _file
)
551 file = os
.path
.join(prefix
, _file
.name
[1:])
552 assert file.startswith("%s/" % prefix
)
556 # Rename configuration files.
557 if _file
.is_config():
558 # Skip already removed config files.
564 file_save
= "%s%s" % (file, CONFIG_FILE_SUFFIX_SAVE
)
567 shutil
.move(file, file_save
)
568 except shutil
.Error
, e
:
572 file_save
= os
.path
.relpath(file_save
, prefix
)
573 messages
.append(_("Config file saved as %s.") % file_save
)
576 # Preserve datafiles.
577 if _file
.is_datafile():
578 log
.debug(_("Preserving datafile '/%s'") % _file
)
581 # Handle regular files and symlinks.
582 if os
.path
.isfile(file) or os
.path
.islink(file):
583 log
.debug("Removing %s..." % _file
)
587 log
.error("Cannot remove file: %s. Remove manually." % _file
)
589 # Handle directories.
590 # Skip removal if the directory is a mountpoint.
591 elif os
.path
.isdir(file) and not os
.path
.ismount(file):
592 # Try to remove the directory. If it is not empty, OSError is raised,
593 # but we are okay with that.
599 # Handle files that have already been removed
601 elif not os
.path
.exists(file):
604 # Log all unhandled types.
606 log
.warning("Cannot remove file: %s. Filetype is unhandled." % file)