]> git.ipfire.org Git - pakfire.git/blame - pakfire/depsolve.py
builder: Rename function cleanup -> destroy.
[pakfire.git] / pakfire / depsolve.py
CommitLineData
47a4cb89
MT
1#!/usr/bin/python
2
3import logging
4import re
5
6import packages
7import repository
1de8761d
MT
8import transaction
9import util
47a4cb89 10
348d903a 11from errors import *
47a4cb89 12
1de8761d
MT
13from i18n import _
14
15PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-19s %5s "
16
47a4cb89 17class Requires(object):
1de8761d 18 def __init__(self, pkg, requires, dep=False):
47a4cb89
MT
19 self.pkg = pkg
20 self.requires = requires
1de8761d 21 self.dep = dep
47a4cb89
MT
22
23 def __repr__(self):
24 return "<%s %s>" % (self.__class__.__name__, self.requires)
25
26 def __str__(self):
27 return self.requires
28
29 def __cmp__(self, other):
47a4cb89
MT
30 return cmp(self.requires, other.requires)
31
32 @property
33 def type(self):
34 if self.requires.startswith("/"):
35 return "file"
36
43d3c3d2
MT
37 elif self.requires.startswith("pkgconfig("):
38 return "pkgconfig"
39
268dd720
MT
40 elif ">" in self.requires or "<" in self.requires or "=" in self.requires:
41 return "expr"
42
47a4cb89
MT
43 elif not re.match("^lib.*\.so.*", self.requires):
44 return "lib"
45
46 return "generic"
47
48
49class Conflicts(object):
50 def __init__(self, pkg, conflicts):
51 self.pkg = pkg
52 self.conflicts = conflicts
53
54 def __repr__(self):
55 return "<%s %s>" % (self.__class__.__name__, self.conflicts)
56
57 def __str__(self):
58 return self.conflicts
59
60
47a4cb89
MT
61class Obsoletes(object):
62 def __init__(self, pkg, obsoletes):
63 self.pkg = pkg
64 self.obsoletes = obsoletes
65
66 def __repr__(self):
67 return "<%s %s>" % (self.__class__.__name__, self.obsoletes)
68
69 def __str__(self):
70 return self.obsoletes
71
72
73class DependencySet(object):
74 def __init__(self, pakfire):
75 # Reference all repositories
76 self.repos = pakfire.repos #repository.Repositories()
77
78 # List of packages in this set
79 self.__packages = []
80
81 # Helper lists
82 self.__conflicts = []
47a4cb89
MT
83 self.__requires = []
84 self.__obsoletes = []
85
1de8761d
MT
86 # Create a new transaction set.
87 self.ts = transaction.TransactionSet()
88
47a4cb89
MT
89 # Read-in all packages from the database that have
90 # been installed previously and need to be taken into
91 # account when resolving dependencies.
92 for pkg in self.repos.local.packages:
1de8761d 93 self.add_package(pkg, transaction=False)
47a4cb89 94
1de8761d 95 def add_requires(self, requires, pkg=None, dep=False):
1bba3b2c
MT
96 # XXX for now, we skip the virtual perl requires
97 if requires.startswith("perl(") or requires.startswith("perl>") or requires.startswith("perl="):
98 return
99
1de8761d 100 requires = Requires(pkg, requires, dep)
47a4cb89
MT
101
102 if requires in self.__requires:
103 return
104
47a4cb89
MT
105 for pkg in self.__packages:
106 if pkg.does_provide(requires):
4ef2062a 107 logging.debug("Skipping requires '%s' which is already provided by %s" % (requires.requires, pkg))
47a4cb89
MT
108 return
109
4ef2062a 110 #logging.debug("Adding requires: %s" % requires)
47a4cb89
MT
111 self.__requires.append(requires)
112
47a4cb89
MT
113 def add_obsoletes(self, obsoletes, pkg=None):
114 obsoletes = Obsoletes(pkg, obsoletes)
115
116 self.__obsoletes.append(obsoletes)
117
1de8761d 118 def add_package(self, pkg, dep=False, transaction=True):
47a4cb89
MT
119 #print pkg, sorted(self.__packages)
120 #assert not pkg in self.__packages
121 if pkg in self.__packages:
122 logging.debug("Trying to add package which is already in the dependency set: %s" % pkg)
123 return
124
1de8761d
MT
125 if transaction:
126 transaction_mode = "install"
127 for p in self.__packages:
128 if pkg.name == p.name:
129 transaction_mode = "update"
130 break
131
132 # Add package to transaction set
133 func = getattr(self.ts, transaction_mode)
134 func(pkg, dep=dep)
135
136 #if not isinstance(pkg, packages.DatabasePackage):
137 # logging.info(" --> Adding package to dependency set: %s" % pkg.friendly_name)
47a4cb89
MT
138 self.__packages.append(pkg)
139
39a62912 140 # Add the requirements of the newly added package.
47a4cb89 141 for req in pkg.requires:
1de8761d 142 self.add_requires(req, pkg, dep=True)
47a4cb89 143
39a62912
MT
144 # Remove all requires that are fulfilled by this package.
145 # For that we copy the matching requires to _requires and remove them
146 # afterwards, because changing self.__requires in a "for" loop is not
147 # a good idea.
148 _requires = []
149 for req in self.__requires:
150 if pkg.does_provide(req):
151 _requires.append(req)
152
153 for req in _requires:
154 self.__requires.remove(req)
155
47a4cb89
MT
156 @property
157 def packages(self):
158 if not self.__requires:
159 return self.__packages[:]
160
161 def resolve(self):
162 unresolveable_reqs = []
163
164 while self.__requires:
165 requires = self.__requires.pop(0)
166 logging.debug("Resolving requirement \"%s\"" % requires)
167
168 # Fetch all candidates from the repositories and save the
169 # best one
b6da0663
MT
170 if requires.type == "file":
171 candidates = self.repos.get_by_file(requires.requires)
172 else:
173 candidates = self.repos.get_by_provides(requires)
174
175 # Turn the candidates into a package listing.
176 candidates = packages.PackageListing(candidates)
47a4cb89
MT
177
178 if not candidates:
179 logging.debug(" Got no candidates for that")
180 unresolveable_reqs.append(requires)
181 continue
182
183 logging.debug(" Got candidates for that:")
184 for candidate in candidates:
185 logging.debug(" --> %s" % candidate)
186
187 best = candidates.get_most_recent()
188 if best:
1de8761d 189 self.add_package(best, dep=requires.dep)
47a4cb89 190
348d903a
MT
191 if unresolveable_reqs:
192 raise DependencyError, "Cannot resolve %s" % \
193 " ".join([r.requires for r in unresolveable_reqs])
47a4cb89 194
1de8761d
MT
195 def dump_pkg(self, format, pkg):
196 return format % (
197 pkg.name,
198 pkg.arch,
199 pkg.friendly_version,
200 pkg.repo.name,
201 util.format_size(pkg.size),
202 )
203
204 def dump_pkgs(self, caption, pkgs):
205 if not pkgs:
206 return []
207
208 s = [caption,]
209 for pkg in sorted(pkgs):
210 s.append(self.dump_pkg(PKG_DUMP_FORMAT, pkg))
211 s.append("")
212 return s
213
214 def dump(self):
215 width = 80
216 line = "=" * width
217
218 s = []
219 s.append(line)
220 s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"), _("Repository"), _("Size")))
221 s.append(line)
222
223 s += self.dump_pkgs(_("Installing:"), self.ts.installs)
224 s += self.dump_pkgs(_("Installing for dependencies:"), self.ts.install_deps)
225 s += self.dump_pkgs(_("Updating:"), self.ts.updates)
226 s += self.dump_pkgs(_("Updating for dependencies:"), self.ts.update_deps)
227 s += self.dump_pkgs(_("Removing:"), self.ts.removes)
228 s += self.dump_pkgs(_("Removing for dependencies:"), self.ts.remove_deps)
229
230 s.append(_("Transaction Summary"))
231 s.append(line)
232
233 format = "%-20s %-4d %s"
234
235 if self.ts.installs or self.ts.install_deps:
236 s.append(format % (_("Install"),
237 len(self.ts.installs + self.ts.install_deps), _("Package(s)")))
238
239 if self.ts.updates or self.ts.update_deps:
240 s.append(format % (_("Updates"),
241 len(self.ts.updates + self.ts.update_deps), _("Package(s)")))
242
243 if self.ts.removes or self.ts.remove_deps:
244 s.append(format % (_("Remove"),
245 len(self.ts.removes + self.ts.remove_deps), _("Package(s)")))
246
edd6a268
MT
247 # Calculate the size of all files that need to be downloaded this this
248 # transaction.
249 download_size = sum([p.size for p in self.ts.downloads])
250 if download_size:
251 s.append(_("Total download size: %s") % util.format_size(download_size))
1de8761d
MT
252 s.append("")
253
254 for line in s:
255 logging.info(line)