Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH 2/2 v2] depgraph: soname dependency resolution (282639)
Date: Fri, 13 Feb 2015 18:35:48
Message-Id: 1423852509-6638-1-git-send-email-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH 2/2] depgraph: soname dependency resolution (282639) by Zac Medico
1 Soname dependency resolution is disabled by default, since it will not
2 work correctly unless all available installed and binary packages have
3 been built by a version of portage which generates REQUIRES and PROVIDES
4 metadata.
5
6 Soname dependency resolution is enabled when --ignore-soname-deps=n is
7 specified, and one of the following is true:
8
9 * --usepkgonly option is enabled
10
11 * removal actions (--depclean and --prune)
12
13 Soname dependencies are automatically ignored for dependency
14 calculations that can pull unbuilt ebuilds into the dependency graph,
15 since unbuilt ebuilds do not have any soname dependency metadata,
16 making it impossible to determine whether an unresolved soname
17 dependency can be satisfied. Therefore, --usepkgonly must be used
18 in order to enable soname depedency resolution when installing packages.
19
20 A new soname.provided file is supported for profiles, making it possible
21 to selectively ignore soname dependencies (see the portage(5) man page).
22
23 When soname dependency resolution is enabled, the soname dependencies
24 are represented as SonameAtom instances which expose an interface that
25 is minimally compatible with Atom instances. This allows both types of
26 atoms to be satisfied using mostly the same mechanisms, with minimal
27 use of conditional logic to handle the differences. Both atom classes
28 have "soname" and "package" attributes that make it convenient for
29 conditional code to distinguish package atoms and soname atoms. Both
30 classes also implement a match method, so that it is possible to match
31 a Package instance using identical syntax for both types of atoms.
32
33 Since soname dependencies and slot-operator := dependencies share many
34 properties, the slot-operator rebuild code has been generalized to
35 handle both types of dependencies. Many of the existing unit tests
36 involving slot-operator dependencies have been copied and adapted to
37 test soname dependencies (the new tests are located in the
38 pym/portage/tests/resolver/soname/ directory).
39
40 X-Gentoo-Bug: 282639
41 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=282639
42 ---
43 PATCH 2/2 v2 fixes depgraph._iter_match_pkgs_soname to yield packages in
44 descending order.
45
46 man/emerge.1 | 13 +
47 man/portage.5 | 23 ++
48 pym/_emerge/FakeVartree.py | 16 +-
49 pym/_emerge/Package.py | 55 +++-
50 pym/_emerge/actions.py | 9 +-
51 pym/_emerge/create_depgraph_params.py | 6 +
52 pym/_emerge/create_world_atom.py | 6 +-
53 pym/_emerge/depgraph.py | 347 +++++++++++++++------
54 pym/_emerge/main.py | 10 +
55 pym/_emerge/resolver/DbapiProvidesIndex.py | 101 ++++++
56 pym/_emerge/resolver/output.py | 10 +-
57 pym/_emerge/resolver/package_tracker.py | 42 ++-
58 pym/_emerge/resolver/slot_collision.py | 35 ++-
59 pym/portage/dbapi/DummyTree.py | 16 +
60 pym/portage/dep/__init__.py | 25 ++
61 pym/portage/dep/soname/SonameAtom.py | 72 +++++
62 pym/portage/dep/soname/parse.py | 47 +++
63 pym/portage/package/ebuild/config.py | 13 +
64 pym/portage/tests/resolver/ResolverPlayground.py | 3 +
65 pym/portage/tests/resolver/soname/__init__.py | 2 +
66 pym/portage/tests/resolver/soname/__test__.py | 2 +
67 .../tests/resolver/soname/test_autounmask.py | 103 ++++++
68 pym/portage/tests/resolver/soname/test_depclean.py | 61 ++++
69 .../tests/resolver/soname/test_downgrade.py | 240 ++++++++++++++
70 .../tests/resolver/soname/test_or_choices.py | 92 ++++++
71 .../tests/resolver/soname/test_reinstall.py | 87 ++++++
72 .../tests/resolver/soname/test_skip_update.py | 86 +++++
73 .../soname/test_slot_conflict_reinstall.py | 342 ++++++++++++++++++++
74 .../resolver/soname/test_slot_conflict_update.py | 117 +++++++
75 .../tests/resolver/soname/test_soname_provided.py | 78 +++++
76 .../tests/resolver/soname/test_unsatisfiable.py | 71 +++++
77 .../tests/resolver/soname/test_unsatisfied.py | 87 ++++++
78 pym/portage/tests/resolver/test_package_tracker.py | 4 +-
79 33 files changed, 2099 insertions(+), 122 deletions(-)
80 create mode 100644 pym/_emerge/resolver/DbapiProvidesIndex.py
81 create mode 100644 pym/portage/dbapi/DummyTree.py
82 create mode 100644 pym/portage/dep/soname/SonameAtom.py
83 create mode 100644 pym/portage/dep/soname/parse.py
84 create mode 100644 pym/portage/tests/resolver/soname/__init__.py
85 create mode 100644 pym/portage/tests/resolver/soname/__test__.py
86 create mode 100644 pym/portage/tests/resolver/soname/test_autounmask.py
87 create mode 100644 pym/portage/tests/resolver/soname/test_depclean.py
88 create mode 100644 pym/portage/tests/resolver/soname/test_downgrade.py
89 create mode 100644 pym/portage/tests/resolver/soname/test_or_choices.py
90 create mode 100644 pym/portage/tests/resolver/soname/test_reinstall.py
91 create mode 100644 pym/portage/tests/resolver/soname/test_skip_update.py
92 create mode 100644 pym/portage/tests/resolver/soname/test_slot_conflict_reinstall.py
93 create mode 100644 pym/portage/tests/resolver/soname/test_slot_conflict_update.py
94 create mode 100644 pym/portage/tests/resolver/soname/test_soname_provided.py
95 create mode 100644 pym/portage/tests/resolver/soname/test_unsatisfiable.py
96 create mode 100644 pym/portage/tests/resolver/soname/test_unsatisfied.py
97
98 diff --git a/man/emerge.1 b/man/emerge.1
99 index fd9140f..7d8d003 100644
100 --- a/man/emerge.1
101 +++ b/man/emerge.1
102 @@ -568,6 +568,19 @@ only for debugging purposes, and it only affects built packages
103 that specify slot/sub\-slot := operator dependencies which are
104 supported beginning with \fBEAPI 5\fR.
105 .TP
106 +.BR "\-\-ignore\-soname\-deps < y | n >"
107 +Ignore the soname dependencies of binary and installed packages. This
108 +option is enabled by default, since soname dependencies are relatively
109 +new, and the required metadata is not guaranteed to exist for binary and
110 +installed packages built with older versions of portage. Also, soname
111 +dependencies will be automatically ignored for dependency calculations
112 +that can pull unbuilt ebuilds into the dependency graph, since unbuilt
113 +ebuilds do not have any soname dependency metadata, making it impossible
114 +to determine whether an unresolved soname dependency can be satisfied.
115 +Therefore, \fB\-\-usepkgonly\fR (or \fB\-\-getbinpkgonly\fR) must be
116 +used in order to enable soname depedency resolution when installing
117 +packages.
118 +.TP
119 .BR "-j [JOBS], \-\-jobs[=JOBS]"
120 Specifies the number of packages to build simultaneously. If this option is
121 given without an argument, emerge will not limit the number of jobs that can
122 diff --git a/man/portage.5 b/man/portage.5
123 index cec4e2f..ed5140d 100644
124 --- a/man/portage.5
125 +++ b/man/portage.5
126 @@ -36,6 +36,7 @@ package.use.stable.force
127 package.use.stable.mask
128 parent
129 profile.bashrc
130 +soname.provided
131 use.force
132 use.mask
133 use.stable.mask
134 @@ -504,6 +505,28 @@ If needed, this file can be used to set up a special environment for ebuilds,
135 different from the standard root environment. The syntax is the same as for
136 any other bash script.
137 .TP
138 +.BR soname.provided
139 +A list of sonames that portage should assume have been provided. This
140 +is useful for using portage to install binary packages on top of a base
141 +image which lacks /var/db/pkg for some reason (perhaps the image was
142 +assembled by another package manager, or by Linux From Scratch).
143 +
144 +.I Format:
145 +.nf
146 +\- comments begin with # (no inline comments)
147 +\- line begins with a multilib category
148 +\- multilib category is followed by one or more sonames
149 +\- only one multilib category is allowed per line
150 +\- prefixing an soname with a '\-' will negate a parent profile setting
151 +.fi
152 +
153 +.I Example:
154 +.nf
155 +# provide libc and ld-linux sonames for x86_32 and x86_64 categories
156 +x86_32 ld-linux.so.2 libc.so.6
157 +x86_64 ld-linux-x86-64.so.2 libc.so.6
158 +.fi
159 +.TP
160 \fBuse.force\fR and \fBuse.stable.force\fR
161 Some USE flags don't make sense to disable under certain conditions. Here we
162 list forced flags.
163 diff --git a/pym/_emerge/FakeVartree.py b/pym/_emerge/FakeVartree.py
164 index 254f667..ebe07bb 100644
165 --- a/pym/_emerge/FakeVartree.py
166 +++ b/pym/_emerge/FakeVartree.py
167 @@ -17,6 +17,7 @@ from portage.eapi import _get_eapi_attrs
168 from portage.exception import InvalidData, InvalidDependString
169 from portage.update import grab_updates, parse_updates, update_dbentries
170 from portage.versions import _pkg_str
171 +from _emerge.resolver.DbapiProvidesIndex import PackageDbapiProvidesIndex
172
173 if sys.hexversion >= 0x3000000:
174 long = int
175 @@ -24,12 +25,15 @@ if sys.hexversion >= 0x3000000:
176 else:
177 _unicode = unicode
178
179 -class FakeVardbapi(PackageVirtualDbapi):
180 +class FakeVardbGetPath(object):
181 """
182 Implements the vardbapi.getpath() method which is used in error handling
183 code for the Package class and vartree.get_provide().
184 """
185 - def getpath(self, cpv, filename=None):
186 + def __init__(self, vardb):
187 + self.settings = vardb.settings
188 +
189 + def __call__(self, cpv, filename=None):
190 path = os.path.join(self.settings['EROOT'], VDB_PATH, cpv)
191 if filename is not None:
192 path =os.path.join(path, filename)
193 @@ -50,7 +54,8 @@ class FakeVartree(vartree):
194 is not a matching ebuild in the tree). Instances of this class are not
195 populated until the sync() method is called."""
196 def __init__(self, root_config, pkg_cache=None, pkg_root_config=None,
197 - dynamic_deps=True, ignore_built_slot_operator_deps=False):
198 + dynamic_deps=True, ignore_built_slot_operator_deps=False,
199 + soname_deps=False):
200 self._root_config = root_config
201 self._dynamic_deps = dynamic_deps
202 self._ignore_built_slot_operator_deps = ignore_built_slot_operator_deps
203 @@ -68,7 +73,10 @@ class FakeVartree(vartree):
204 mykeys.append("_mtime_")
205 self._db_keys = mykeys
206 self._pkg_cache = pkg_cache
207 - self.dbapi = FakeVardbapi(real_vartree.settings)
208 + self.dbapi = PackageVirtualDbapi(real_vartree.settings)
209 + if soname_deps:
210 + self.dbapi = PackageDbapiProvidesIndex(self.dbapi)
211 + self.dbapi.getpath = FakeVardbGetPath(self.dbapi)
212 self.dbapi._aux_cache_keys = set(self._db_keys)
213
214 # Initialize variables needed for lazy cache pulls of the live ebuild
215 diff --git a/pym/_emerge/Package.py b/pym/_emerge/Package.py
216 index 518dbf6..e8a13cb 100644
217 --- a/pym/_emerge/Package.py
218 +++ b/pym/_emerge/Package.py
219 @@ -13,9 +13,10 @@ from portage.cache.mappings import slot_dict_class
220 from portage.const import EBUILD_PHASES
221 from portage.dep import Atom, check_required_use, use_reduce, \
222 paren_enclose, _slot_separator, _repo_separator
223 +from portage.dep.soname.parse import parse_soname_deps
224 from portage.versions import _pkg_str, _unknown_repo
225 from portage.eapi import _get_eapi_attrs, eapi_has_use_aliases
226 -from portage.exception import InvalidDependString
227 +from portage.exception import InvalidData, InvalidDependString
228 from portage.localization import _
229 from _emerge.Task import Task
230
231 @@ -36,7 +37,8 @@ class Package(Task):
232 "inherited", "iuse", "mtime",
233 "pf", "root", "slot", "sub_slot", "slot_atom", "version") + \
234 ("_invalid", "_masks", "_metadata", "_provided_cps",
235 - "_raw_metadata", "_use", "_validated_atoms", "_visible")
236 + "_raw_metadata", "_provides", "_requires", "_use",
237 + "_validated_atoms", "_visible")
238
239 metadata_keys = [
240 "BUILD_TIME", "CHOST", "COUNTER", "DEPEND", "EAPI",
241 @@ -189,6 +191,16 @@ class Package(Task):
242 def stable(self):
243 return self.cpv.stable
244
245 + @property
246 + def provides(self):
247 + self.invalid
248 + return self._provides
249 +
250 + @property
251 + def requires(self):
252 + self.invalid
253 + return self._requires
254 +
255 @classmethod
256 def _gen_hash_key(cls, cpv=None, installed=None, onlydeps=None,
257 operation=None, repo_name=None, root_config=None,
258 @@ -311,6 +323,21 @@ class Package(Task):
259 if not self.installed:
260 self._metadata_exception(k, e)
261
262 + if self.built:
263 + k = 'PROVIDES'
264 + try:
265 + self._provides = frozenset(
266 + parse_soname_deps(self._metadata[k]))
267 + except InvalidData as e:
268 + self._invalid_metadata(k + ".syntax", "%s: %s" % (k, e))
269 +
270 + k = 'REQUIRES'
271 + try:
272 + self._requires = frozenset(
273 + parse_soname_deps(self._metadata[k]))
274 + except InvalidData as e:
275 + self._invalid_metadata(k + ".syntax", "%s: %s" % (k, e))
276 +
277 def copy(self):
278 return Package(built=self.built, cpv=self.cpv, depth=self.depth,
279 installed=self.installed, metadata=self._raw_metadata,
280 @@ -727,32 +754,48 @@ class Package(Task):
281
282 def __lt__(self, other):
283 if other.cp != self.cp:
284 - return False
285 + return self.cp < other.cp
286 if portage.vercmp(self.version, other.version) < 0:
287 return True
288 return False
289
290 def __le__(self, other):
291 if other.cp != self.cp:
292 - return False
293 + return self.cp <= other.cp
294 if portage.vercmp(self.version, other.version) <= 0:
295 return True
296 return False
297
298 def __gt__(self, other):
299 if other.cp != self.cp:
300 - return False
301 + return self.cp > other.cp
302 if portage.vercmp(self.version, other.version) > 0:
303 return True
304 return False
305
306 def __ge__(self, other):
307 if other.cp != self.cp:
308 - return False
309 + return self.cp >= other.cp
310 if portage.vercmp(self.version, other.version) >= 0:
311 return True
312 return False
313
314 + def with_use(self, use):
315 + """
316 + Return an Package instance with the specified USE flags. The
317 + current instance may be returned if it has identical USE flags.
318 + @param use: a set of USE flags
319 + @type use: frozenset
320 + @return: A package with the specified USE flags
321 + @rtype: Package
322 + """
323 + if use is not self.use.enabled:
324 + pkg = self.copy()
325 + pkg._metadata["USE"] = " ".join(use)
326 + else:
327 + pkg = self
328 + return pkg
329 +
330 _all_metadata_keys = set(x for x in portage.auxdbkeys \
331 if not x.startswith("UNUSED_"))
332 _all_metadata_keys.update(Package.metadata_keys)
333 diff --git a/pym/_emerge/actions.py b/pym/_emerge/actions.py
334 index d393c78..fa4fe19 100644
335 --- a/pym/_emerge/actions.py
336 +++ b/pym/_emerge/actions.py
337 @@ -787,7 +787,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
338 for pkg in vardb:
339 if spinner is not None:
340 spinner.update()
341 - pkgs_for_cp = vardb.match_pkgs(pkg.cp)
342 + pkgs_for_cp = vardb.match_pkgs(Atom(pkg.cp))
343 if not pkgs_for_cp or pkg not in pkgs_for_cp:
344 raise AssertionError("package expected in matches: " + \
345 "cp = %s, cpv = %s matches = %s" % \
346 @@ -931,8 +931,13 @@ def calc_depclean(settings, trees, ldpath_mtimes,
347
348 parent_strs = []
349 for parent, atoms in parent_atom_dict.items():
350 + # Display package atoms and soname
351 + # atoms in separate groups.
352 + atoms = sorted(atoms, reverse=True,
353 + key=operator.attrgetter('package'))
354 parent_strs.append("%s requires %s" %
355 - (getattr(parent, "cpv", parent), ", ".join(atoms)))
356 + (getattr(parent, "cpv", parent),
357 + ", ".join(_unicode(atom) for atom in atoms)))
358 parent_strs.sort()
359 msg = []
360 msg.append(" %s pulled in by:\n" % (child_node.cpv,))
361 diff --git a/pym/_emerge/create_depgraph_params.py b/pym/_emerge/create_depgraph_params.py
362 index 11e20f4..2c64928 100644
363 --- a/pym/_emerge/create_depgraph_params.py
364 +++ b/pym/_emerge/create_depgraph_params.py
365 @@ -21,6 +21,9 @@ def create_depgraph_params(myopts, myaction):
366 # removal by the --depclean action as soon as possible
367 # ignore_built_slot_operator_deps: ignore the slot/sub-slot := operator parts
368 # of dependencies that have been recorded when packages where built
369 + # ignore_soname_deps: ignore the soname dependencies of built
370 + # packages, so that they do not trigger dependency resolution
371 + # failures, or cause packages to be rebuilt or replaced.
372 # with_test_deps: pull in test deps for packages matched by arguments
373 # changed_deps: rebuild installed packages with outdated deps
374 # binpkg_changed_deps: reject binary packages with outdated deps
375 @@ -34,6 +37,9 @@ def create_depgraph_params(myopts, myaction):
376 if ignore_built_slot_operator_deps is not None:
377 myparams["ignore_built_slot_operator_deps"] = ignore_built_slot_operator_deps
378
379 + myparams["ignore_soname_deps"] = myopts.get(
380 + "--ignore-soname-deps", "y")
381 +
382 dynamic_deps = myopts.get("--dynamic-deps")
383 if dynamic_deps is not None:
384 myparams["dynamic_deps"] = dynamic_deps
385 diff --git a/pym/_emerge/create_world_atom.py b/pym/_emerge/create_world_atom.py
386 index ac994cc..74b0fa5 100644
387 --- a/pym/_emerge/create_world_atom.py
388 +++ b/pym/_emerge/create_world_atom.py
389 @@ -3,7 +3,7 @@
390
391 import sys
392
393 -from portage.dep import _repo_separator
394 +from portage.dep import Atom, _repo_separator
395 from portage.exception import InvalidData
396
397 if sys.hexversion >= 0x3000000:
398 @@ -40,7 +40,7 @@ def create_world_atom(pkg, args_set, root_config):
399 repos.append(portdb.repositories.get_name_for_location(tree))
400
401 available_slots = set()
402 - for cpv in portdb.match(cp):
403 + for cpv in portdb.match(Atom(cp)):
404 for repo in repos:
405 try:
406 available_slots.add(portdb._pkg_str(_unicode(cpv), repo).slot)
407 @@ -52,7 +52,7 @@ def create_world_atom(pkg, args_set, root_config):
408 if not slotted:
409 # check the vdb in case this is multislot
410 available_slots = set(vardb._pkg_str(cpv, None).slot \
411 - for cpv in vardb.match(cp))
412 + for cpv in vardb.match(Atom(cp)))
413 slotted = len(available_slots) > 1 or \
414 (len(available_slots) == 1 and "0" not in available_slots)
415 if slotted and arg_atom.without_repo != cp:
416 diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
417 index 1184dd6..63c89a4 100644
418 --- a/pym/_emerge/depgraph.py
419 +++ b/pym/_emerge/depgraph.py
420 @@ -20,6 +20,7 @@ from portage import _unicode_decode, _unicode_encode, _encodings
421 from portage.const import PORTAGE_PACKAGE_ATOM, USER_CONFIG_PATH, VCS_DIRS
422 from portage.dbapi import dbapi
423 from portage.dbapi.dep_expand import dep_expand
424 +from portage.dbapi.DummyTree import DummyTree
425 from portage.dbapi._similar_name_search import similar_name_search
426 from portage.dep import Atom, best_match_to_list, extract_affecting_use, \
427 check_required_use, human_readable_required_use, match_from_list, \
428 @@ -77,6 +78,7 @@ from _emerge.UseFlagDisplay import pkg_use_display
429 from _emerge.UserQuery import UserQuery
430
431 from _emerge.resolver.backtracking import Backtracker, BacktrackParameter
432 +from _emerge.resolver.DbapiProvidesIndex import DbapiProvidesIndex
433 from _emerge.resolver.package_tracker import PackageTracker, PackageTrackerDbapiWrapper
434 from _emerge.resolver.slot_collision import slot_conflict_handler
435 from _emerge.resolver.circular_dependency import circular_dependency_handler
436 @@ -125,6 +127,13 @@ class _frozen_depgraph_config(object):
437 # All Package instances
438 self._pkg_cache = {}
439 self._highest_license_masked = {}
440 + # We can't know that an soname dep is unsatisfied if there are
441 + # any unbuilt ebuilds in the graph, since unbuilt ebuilds have
442 + # no soname data. Therefore, only enable soname dependency
443 + # resolution if --usepkgonly is enabled, or for removal actions.
444 + self.soname_deps_enabled = (
445 + ("--usepkgonly" in myopts or "remove" in params) and
446 + params.get("ignore_soname_deps") != "y")
447 dynamic_deps = myopts.get("--dynamic-deps", "y") != "n"
448 ignore_built_slot_operator_deps = myopts.get(
449 "--ignore-built-slot-operator-deps", "n") == "y"
450 @@ -143,9 +152,13 @@ class _frozen_depgraph_config(object):
451 pkg_cache=self._pkg_cache,
452 pkg_root_config=self.roots[myroot],
453 dynamic_deps=dynamic_deps,
454 - ignore_built_slot_operator_deps=ignore_built_slot_operator_deps)
455 + ignore_built_slot_operator_deps=ignore_built_slot_operator_deps,
456 + soname_deps=self.soname_deps_enabled)
457 self.pkgsettings[myroot] = portage.config(
458 clone=self.trees[myroot]["vartree"].settings)
459 + if self.soname_deps_enabled and "remove" not in params:
460 + self.trees[myroot]["bintree"] = DummyTree(
461 + DbapiProvidesIndex(trees[myroot]["bintree"].dbapi))
462
463 self._required_set_names = set(["world"])
464
465 @@ -428,7 +441,9 @@ class _dynamic_depgraph_config(object):
466 self._traverse_ignored_deps = False
467 self._complete_mode = False
468 self._slot_operator_deps = {}
469 - self._package_tracker = PackageTracker()
470 + self._installed_sonames = collections.defaultdict(list)
471 + self._package_tracker = PackageTracker(
472 + soname_deps=depgraph._frozen_config.soname_deps_enabled)
473 # Track missed updates caused by solved conflicts.
474 self._conflict_missed_update = collections.defaultdict(dict)
475
476 @@ -536,6 +551,18 @@ class depgraph(object):
477
478 self.query = UserQuery(myopts).query
479
480 + def _index_binpkgs(self):
481 + for root in self._frozen_config.trees:
482 + bindb = self._frozen_config.trees[root]["bintree"].dbapi
483 + if bindb._provides_index:
484 + # don't repeat this when backtracking
485 + continue
486 + root_config = self._frozen_config.roots[root]
487 + for cpv in self._frozen_config._trees_orig[
488 + root]["bintree"].dbapi.cpv_all():
489 + bindb._provides_inject(
490 + self._pkg(cpv, "binary", root_config))
491 +
492 def _load_vdb(self):
493 """
494 Load installed package metadata if appropriate. This used to be called
495 @@ -571,6 +598,7 @@ class depgraph(object):
496 if not dynamic_deps:
497 for pkg in vardb:
498 self._dynamic_config._package_tracker.add_installed_pkg(pkg)
499 + self._add_installed_sonames(pkg)
500 else:
501 max_jobs = self._frozen_config.myopts.get("--jobs")
502 max_load = self._frozen_config.myopts.get("--load-average")
503 @@ -589,6 +617,7 @@ class depgraph(object):
504 for pkg in fake_vartree.dbapi:
505 self._spinner_update()
506 self._dynamic_config._package_tracker.add_installed_pkg(pkg)
507 + self._add_installed_sonames(pkg)
508 ebuild_path, repo_path = \
509 portdb.findname2(pkg.cpv, myrepo=pkg.repo)
510 if ebuild_path is None:
511 @@ -631,6 +660,8 @@ class depgraph(object):
512 """
513
514 debug = "--debug" in self._frozen_config.myopts
515 + installed_sonames = self._dynamic_config._installed_sonames
516 + package_tracker = self._dynamic_config._package_tracker
517
518 # Get all atoms that might have caused a forced rebuild.
519 atoms = {}
520 @@ -666,6 +697,32 @@ class depgraph(object):
521 if inst_pkg is reinst_pkg or reinst_pkg is None:
522 continue
523
524 + if (inst_pkg is not None and
525 + inst_pkg.requires is not None):
526 + for atom in inst_pkg.requires:
527 + initial_providers = installed_sonames.get(
528 + (root, atom))
529 + if initial_providers is None:
530 + continue
531 + final_provider = next(
532 + package_tracker.match(root, atom),
533 + None)
534 + if final_provider:
535 + continue
536 + for provider in initial_providers:
537 + # Find the replacement child.
538 + child = next((pkg for pkg in
539 + package_tracker.match(
540 + root, provider.slot_atom)
541 + if not pkg.installed), None)
542 +
543 + if child is None:
544 + continue
545 +
546 + forced_rebuilds.setdefault(
547 + root, {}).setdefault(
548 + child, set()).add(inst_pkg)
549 +
550 # Generate pseudo-deps for any slot-operator deps of
551 # inst_pkg. Its deps aren't in _slot_operator_deps
552 # because it hasn't been added to the graph, but we
553 @@ -1216,9 +1273,6 @@ class depgraph(object):
554 if is_non_conflict_parent:
555 parent = non_conflict_node
556
557 - atom_set = InternalPackageSet(
558 - initial_atoms=(atom,), allow_repo=True)
559 -
560 matched = []
561 for pkg in conflict:
562 if (pkg is highest_pkg and
563 @@ -1230,8 +1284,8 @@ class depgraph(object):
564 # version into the graph (bug #531656).
565 non_matching_forced.add(highest_pkg)
566
567 - if atom_set.findAtomForPackage(pkg, \
568 - modified_use=self._pkg_use_enabled(pkg)) and \
569 + if atom.match(pkg.with_use(
570 + self._pkg_use_enabled(pkg))) and \
571 not (is_arg_parent and pkg.installed):
572 matched.append(pkg)
573
574 @@ -1448,13 +1502,8 @@ class depgraph(object):
575 for parent_atom in slot_parent_atoms:
576 if parent_atom in parent_atoms:
577 continue
578 - # Use package set for matching since it will match via
579 - # PROVIDE when necessary, while match_from_list does not.
580 parent, atom = parent_atom
581 - atom_set = InternalPackageSet(
582 - initial_atoms=(atom,), allow_repo=True)
583 - if atom_set.findAtomForPackage(pkg,
584 - modified_use=self._pkg_use_enabled(pkg)):
585 + if atom.match(pkg.with_use(self._pkg_use_enabled(pkg))):
586 parent_atoms.add(parent_atom)
587 else:
588 all_match = False
589 @@ -1533,7 +1582,11 @@ class depgraph(object):
590 if not isinstance(parent, Package):
591 continue
592
593 - if atom.slot_operator != "=" or not parent.built:
594 + if not parent.built:
595 + continue
596 +
597 + if not atom.soname and not (
598 + atom.package and atom.slot_operator_built):
599 continue
600
601 if pkg not in conflict_pkgs:
602 @@ -1740,7 +1793,7 @@ class depgraph(object):
603 """
604 built_slot_operator_parents = set()
605 for parent, atom in self._dynamic_config._parent_atoms.get(existing_pkg, []):
606 - if atom.slot_operator_built:
607 + if atom.soname or atom.slot_operator_built:
608 built_slot_operator_parents.add(parent)
609
610 for parent, atom in self._dynamic_config._parent_atoms.get(existing_pkg, []):
611 @@ -1778,6 +1831,9 @@ class depgraph(object):
612 for replacement_parent in self._iter_similar_available(dep.parent,
613 dep.parent.slot_atom, autounmask_level=autounmask_level):
614
615 + if replacement_parent is dep.parent:
616 + continue
617 +
618 if replacement_parent < dep.parent:
619 if want_downgrade_parent is None:
620 want_downgrade_parent = self._downgrade_probe(
621 @@ -1796,35 +1852,51 @@ class depgraph(object):
622 except InvalidDependString:
623 continue
624
625 + if replacement_parent.requires is not None:
626 + atoms = list(atoms)
627 + atoms.extend(replacement_parent.requires)
628 +
629 # List of list of child,atom pairs for each atom.
630 replacement_candidates = []
631 # Set of all packages all atoms can agree on.
632 all_candidate_pkgs = None
633 + atom_not_selected = False
634
635 for atom in atoms:
636 - if atom.blocker or \
637 - atom.cp != dep.atom.cp:
638 - continue
639
640 - # Discard USE deps, we're only searching for an approximate
641 - # pattern, and dealing with USE states is too complex for
642 - # this purpose.
643 - unevaluated_atom = atom.unevaluated_atom
644 - atom = atom.without_use
645 + if not atom.package:
646 + unevaluated_atom = None
647 + if atom.match(dep.child):
648 + # We are searching for a replacement_parent
649 + # atom that will pull in a different child,
650 + # so continue checking the rest of the atoms.
651 + continue
652 + else:
653
654 - if replacement_parent.built and \
655 - portage.dep._match_slot(atom, dep.child):
656 - # Our selected replacement_parent appears to be built
657 - # for the existing child selection. So, discard this
658 - # parent and search for another.
659 - break
660 + if atom.blocker or \
661 + atom.cp != dep.child.cp:
662 + continue
663 +
664 + # Discard USE deps, we're only searching for an
665 + # approximate pattern, and dealing with USE states
666 + # is too complex for this purpose.
667 + unevaluated_atom = atom.unevaluated_atom
668 + atom = atom.without_use
669 +
670 + if replacement_parent.built and \
671 + portage.dep._match_slot(atom, dep.child):
672 + # We are searching for a replacement_parent
673 + # atom that will pull in a different child,
674 + # so continue checking the rest of the atoms.
675 + continue
676
677 candidate_pkg_atoms = []
678 candidate_pkgs = []
679 for pkg in self._iter_similar_available(
680 dep.child, atom):
681 - if pkg.slot == dep.child.slot and \
682 - pkg.sub_slot == dep.child.sub_slot:
683 + if (dep.atom.package and
684 + pkg.slot == dep.child.slot and
685 + pkg.sub_slot == dep.child.sub_slot):
686 # If slot/sub-slot is identical, then there's
687 # no point in updating.
688 continue
689 @@ -1863,7 +1935,8 @@ class depgraph(object):
690 # slot conflict).
691 insignificant = True
692
693 - if not insignificant:
694 + if (not insignificant and
695 + unevaluated_atom is not None):
696 # Evaluate USE conditionals and || deps, in order
697 # to see if this atom is really desirable, since
698 # otherwise we may trigger an undesirable rebuild
699 @@ -1872,14 +1945,19 @@ class depgraph(object):
700 selected_atoms = self._select_atoms_probe(
701 dep.child.root, replacement_parent)
702 if unevaluated_atom not in selected_atoms:
703 - continue
704 + atom_not_selected = True
705 + break
706
707 if not insignificant and \
708 check_reverse_dependencies(dep.child, pkg,
709 replacement_parent=replacement_parent):
710
711 - candidate_pkg_atoms.append((pkg, unevaluated_atom))
712 + candidate_pkg_atoms.append(
713 + (pkg, unevaluated_atom or atom))
714 candidate_pkgs.append(pkg)
715 +
716 + if atom_not_selected:
717 + continue
718 replacement_candidates.append(candidate_pkg_atoms)
719 if all_candidate_pkgs is None:
720 all_candidate_pkgs = set(candidate_pkgs)
721 @@ -2183,10 +2261,8 @@ class depgraph(object):
722 for dep in slot_info:
723
724 atom = dep.atom
725 - if atom.slot_operator is None:
726 - continue
727
728 - if not atom.slot_operator_built:
729 + if not (atom.soname or atom.slot_operator_built):
730 new_child_slot = self._slot_change_probe(dep)
731 if new_child_slot is not None:
732 self._slot_change_backtrack(dep, new_child_slot)
733 @@ -2471,7 +2547,7 @@ class depgraph(object):
734 (dep.parent,
735 self._dynamic_config._runtime_pkg_mask[
736 dep.parent]), noiselevel=-1)
737 - elif dep.atom.slot_operator_built and \
738 + elif dep.atom.package and dep.atom.slot_operator_built and \
739 self._slot_operator_unsatisfied_probe(dep):
740 self._slot_operator_unsatisfied_backtrack(dep)
741 return 1
742 @@ -2483,8 +2559,9 @@ class depgraph(object):
743 # return None, and eventually we come through here
744 # and skip the "missing dependency" backtracking path.
745 dep_pkg, existing_node = \
746 - self._select_package(dep.root, dep.atom.without_use,
747 - onlydeps=dep.onlydeps)
748 + self._select_package(dep.root,
749 + dep.atom.without_use if dep.atom.package
750 + else dep.atom, onlydeps=dep.onlydeps)
751 if dep_pkg is None:
752 self._dynamic_config._backtrack_infos["missing dependency"] = dep
753 self._dynamic_config._need_restart = True
754 @@ -2520,11 +2597,8 @@ class depgraph(object):
755 matches = pkg.cpv == existing_node.cpv
756 if pkg != existing_node and \
757 atom is not None:
758 - # Use package set for matching since it will match via
759 - # PROVIDE when necessary, while match_from_list does not.
760 - matches = bool(InternalPackageSet(initial_atoms=(atom,),
761 - allow_repo=True).findAtomForPackage(existing_node,
762 - modified_use=self._pkg_use_enabled(existing_node)))
763 + matches = atom.match(existing_node.with_use(
764 + self._pkg_use_enabled(existing_node)))
765
766 return (existing_node, matches)
767
768 @@ -2563,8 +2637,8 @@ class depgraph(object):
769 # Display the specific atom from SetArg or
770 # Package types.
771 uneval = ""
772 - if dep.atom and dep.atom.unevaluated_atom and \
773 - dep.atom is not dep.atom.unevaluated_atom:
774 + if (dep.atom and dep.atom.package and
775 + dep.atom is not dep.atom.unevaluated_atom):
776 uneval = " (%s)" % (dep.atom.unevaluated_atom,)
777 writemsg_level(
778 "%s%s%s required by %s\n" %
779 @@ -2728,8 +2802,9 @@ class depgraph(object):
780 not (deep is not True and depth > deep))
781
782 dep.child = pkg
783 - if (not pkg.onlydeps and
784 - dep.atom and dep.atom.slot_operator is not None):
785 + if not pkg.onlydeps and dep.atom and (
786 + dep.atom.soname or
787 + dep.atom.slot_operator == "="):
788 self._add_slot_operator_dep(dep)
789
790 recurse = deep is True or depth + 1 <= deep
791 @@ -2745,6 +2820,32 @@ class depgraph(object):
792 dep_stack.append(pkg)
793 return 1
794
795 + def _add_installed_sonames(self, pkg):
796 + if (self._frozen_config.soname_deps_enabled and
797 + pkg.provides is not None):
798 + for atom in pkg.provides:
799 + self._dynamic_config._installed_sonames[
800 + (pkg.root, atom)].append(pkg)
801 +
802 + def _add_pkg_soname_deps(self, pkg, allow_unsatisfied=False):
803 + if (self._frozen_config.soname_deps_enabled and
804 + pkg.requires is not None):
805 + if isinstance(pkg.depth, int):
806 + depth = pkg.depth + 1
807 + else:
808 + depth = pkg.depth
809 + soname_provided = self._frozen_config.roots[
810 + pkg.root].settings.soname_provided
811 + for atom in pkg.requires:
812 + if atom in soname_provided:
813 + continue
814 + dep = Dependency(atom=atom, blocker=False, depth=depth,
815 + parent=pkg, priority=self._priority(runtime=True),
816 + root=pkg.root)
817 + if not self._add_dep(dep,
818 + allow_unsatisfied=allow_unsatisfied):
819 + return False
820 + return True
821
822 def _remove_pkg(self, pkg):
823 """
824 @@ -2832,6 +2933,10 @@ class depgraph(object):
825
826 def _add_pkg_deps(self, pkg, allow_unsatisfied=False):
827
828 + if not self._add_pkg_soname_deps(pkg,
829 + allow_unsatisfied=allow_unsatisfied):
830 + return False
831 +
832 myroot = pkg.root
833 metadata = pkg._metadata
834 removal_action = "remove" in self._dynamic_config.myparams
835 @@ -3482,6 +3587,9 @@ class depgraph(object):
836 self._dynamic_config._initial_arg_list and call self._resolve to create the
837 appropriate depgraph and return a favorite list."""
838 self._load_vdb()
839 + if (self._frozen_config.soname_deps_enabled and
840 + "remove" not in self._dynamic_config.myparams):
841 + self._index_binpkgs()
842 debug = "--debug" in self._frozen_config.myopts
843 root_config = self._frozen_config.roots[self._frozen_config.target_root]
844 sets = root_config.sets
845 @@ -4451,7 +4559,7 @@ class depgraph(object):
846 # (aka unsatisfied_dependency is not None) we
847 # need that the start_node doesn't match the atom.
848 if not unsatisfied_dependency or \
849 - not InternalPackageSet(initial_atoms=(patom,)).findAtomForPackage(start_node):
850 + not patom.match(start_node):
851 start_node_parent_atoms.setdefault(patom, []).append(ppkg)
852
853 if start_node_parent_atoms:
854 @@ -4459,7 +4567,16 @@ class depgraph(object):
855 # If not, then this package got pulled in by an Arg and
856 # will be correctly handled by the code that handles later
857 # packages in the dep chain.
858 - best_match = best_match_to_list(node.cpv, start_node_parent_atoms)
859 + if (any(not x.package for x in start_node_parent_atoms) and
860 + any(x.package for x in start_node_parent_atoms)):
861 + for x in list(start_node_parent_atoms):
862 + if not x.package:
863 + del start_node_parent_atoms[x]
864 + if next(iter(start_node_parent_atoms)).package:
865 + best_match = best_match_to_list(node.cpv,
866 + start_node_parent_atoms)
867 + else:
868 + best_match = next(iter(start_node_parent_atoms))
869
870 child = node
871 for ppkg in start_node_parent_atoms[best_match]:
872 @@ -4486,10 +4603,11 @@ class depgraph(object):
873 for ppkg, patom in all_parents[child]:
874 if ppkg == node:
875 if child is start_node and unsatisfied_dependency and \
876 - InternalPackageSet(initial_atoms=(patom,)).findAtomForPackage(child):
877 + patom.match(child):
878 # This atom is satisfied by child, there must be another atom.
879 continue
880 - atom = patom.unevaluated_atom
881 + atom = (patom.unevaluated_atom
882 + if patom.package else patom)
883 break
884
885 dep_strings = set()
886 @@ -4558,8 +4676,7 @@ class depgraph(object):
887 # flag changes.
888 for ppkg, atom in all_parents[start_node]:
889 if parent is ppkg:
890 - atom_set = InternalPackageSet(initial_atoms=(atom,))
891 - if not atom_set.findAtomForPackage(start_node):
892 + if not atom.match(start_node):
893 parent_unsatisfied = parent
894 break
895 else:
896 @@ -4602,11 +4719,13 @@ class depgraph(object):
897 """
898 backtrack_mask = False
899 autounmask_broke_use_dep = False
900 - atom_set = InternalPackageSet(initial_atoms=(atom.without_use,),
901 - allow_repo=True)
902 - atom_set_with_use = InternalPackageSet(initial_atoms=(atom,),
903 - allow_repo=True)
904 - xinfo = '"%s"' % atom.unevaluated_atom
905 + if atom.package:
906 + xinfo = '"%s"' % atom.unevaluated_atom
907 + atom_without_use = atom.without_use
908 + else:
909 + xinfo = '"%s"' % atom
910 + atom_without_use = None
911 +
912 if arg:
913 xinfo='"%s"' % arg
914 if isinstance(myparent, AtomArg):
915 @@ -4630,12 +4749,18 @@ class depgraph(object):
916 for db, pkg_type, built, installed, db_keys in dbs:
917 if installed:
918 continue
919 - if hasattr(db, "xmatch"):
920 + if atom.soname:
921 + if not isinstance(db, DbapiProvidesIndex):
922 + continue
923 + cpv_list = db.match(atom)
924 + elif hasattr(db, "xmatch"):
925 cpv_list = db.xmatch("match-all-cpv-only", atom.without_use)
926 else:
927 cpv_list = db.match(atom.without_use)
928
929 - if atom.repo is None and hasattr(db, "getRepositories"):
930 + if atom.soname:
931 + repo_list = [None]
932 + elif atom.repo is None and hasattr(db, "getRepositories"):
933 repo_list = db.getRepositories()
934 else:
935 repo_list = [atom.repo]
936 @@ -4666,8 +4791,10 @@ class depgraph(object):
937 masked_packages.append(
938 (root_config, pkgsettings, cpv, repo, metadata, mreasons))
939 continue
940 - if not atom_set.findAtomForPackage(pkg,
941 - modified_use=self._pkg_use_enabled(pkg)):
942 + if atom.soname and not atom.match(pkg):
943 + continue
944 + if (atom_without_use is not None and
945 + not atom_without_use.match(pkg)):
946 continue
947 if pkg in self._dynamic_config._runtime_pkg_mask:
948 backtrack_reasons = \
949 @@ -4680,12 +4807,12 @@ class depgraph(object):
950 mreasons = ["exclude option"]
951 if mreasons:
952 masked_pkg_instances.add(pkg)
953 - if atom.unevaluated_atom.use:
954 + if atom.package and atom.unevaluated_atom.use:
955 try:
956 if not pkg.iuse.is_valid_flag(atom.unevaluated_atom.use.required) \
957 or atom.violated_conditionals(self._pkg_use_enabled(pkg), pkg.iuse.is_valid_flag).use:
958 missing_use.append(pkg)
959 - if atom_set_with_use.findAtomForPackage(pkg):
960 + if atom.match(pkg):
961 autounmask_broke_use_dep = True
962 if not mreasons:
963 continue
964 @@ -4952,7 +5079,7 @@ class depgraph(object):
965 mask_docs = True
966 else:
967 cp_exists = False
968 - if not atom.cp.startswith("null/"):
969 + if atom.package and not atom.cp.startswith("null/"):
970 for pkg in self._iter_match_pkgs_any(
971 root_config, Atom(atom.cp)):
972 cp_exists = True
973 @@ -5011,7 +5138,28 @@ class depgraph(object):
974 pkg_type, atom, onlydeps=onlydeps):
975 yield pkg
976
977 - def _iter_match_pkgs(self, root_config, pkg_type, atom, onlydeps=False):
978 + def _iter_match_pkgs(self, root_config, pkg_type, atom,
979 + onlydeps=False):
980 + if atom.package:
981 + return self._iter_match_pkgs_atom(root_config, pkg_type,
982 + atom, onlydeps=onlydeps)
983 + else:
984 + return self._iter_match_pkgs_soname(root_config, pkg_type,
985 + atom, onlydeps=onlydeps)
986 +
987 + def _iter_match_pkgs_soname(self, root_config, pkg_type, atom,
988 + onlydeps=False):
989 + db = root_config.trees[self.pkg_tree_map[pkg_type]].dbapi
990 + installed = pkg_type == 'installed'
991 +
992 + if isinstance(db, DbapiProvidesIndex):
993 + # descending order
994 + for cpv in reversed(db.match(atom)):
995 + yield self._pkg(cpv, pkg_type, root_config,
996 + installed=installed, onlydeps=onlydeps)
997 +
998 + def _iter_match_pkgs_atom(self, root_config, pkg_type, atom,
999 + onlydeps=False):
1000 """
1001 Iterate over Package instances of pkg_type matching the given atom.
1002 This does not check visibility and it also does not match USE for
1003 @@ -5114,14 +5262,21 @@ class depgraph(object):
1004 return
1005
1006 def _select_pkg_highest_available(self, root, atom, onlydeps=False, parent=None):
1007 - cache_key = (root, atom, atom.unevaluated_atom, onlydeps, self._dynamic_config._autounmask)
1008 + if atom.package:
1009 + cache_key = (root, atom, atom.unevaluated_atom, onlydeps,
1010 + self._dynamic_config._autounmask)
1011 + self._dynamic_config._highest_pkg_cache_cp_map.\
1012 + setdefault((root, atom.cp), []).append(cache_key)
1013 + else:
1014 + cache_key = (root, atom, onlydeps,
1015 + self._dynamic_config._autounmask)
1016 + self._dynamic_config._highest_pkg_cache_cp_map.\
1017 + setdefault((root, atom), []).append(cache_key)
1018 ret = self._dynamic_config._highest_pkg_cache.get(cache_key)
1019 if ret is not None:
1020 return ret
1021 ret = self._select_pkg_highest_available_imp(root, atom, onlydeps=onlydeps, parent=parent)
1022 self._dynamic_config._highest_pkg_cache[cache_key] = ret
1023 - self._dynamic_config._highest_pkg_cache_cp_map. \
1024 - setdefault(atom.cp, []).append(cache_key)
1025 pkg, existing = ret
1026 if pkg is not None:
1027 if self._pkg_visibility_check(pkg) and \
1028 @@ -5136,11 +5291,15 @@ class depgraph(object):
1029 return False
1030
1031 def _prune_highest_pkg_cache(self, pkg):
1032 + cache = self._dynamic_config._highest_pkg_cache
1033 + key_map = self._dynamic_config._highest_pkg_cache_cp_map
1034 for cp in pkg.provided_cps:
1035 - for cache_key in self._dynamic_config. \
1036 - _highest_pkg_cache_cp_map.pop(cp, []):
1037 - self._dynamic_config._highest_pkg_cache.pop(
1038 - cache_key, None)
1039 + for cache_key in key_map.pop((pkg.root, cp), []):
1040 + cache.pop(cache_key, None)
1041 + if pkg.provides is not None:
1042 + for atom in pkg.provides:
1043 + for cache_key in key_map.pop((pkg.root, atom), []):
1044 + cache.pop(cache_key, None)
1045
1046 def _want_installed_pkg(self, pkg):
1047 """
1048 @@ -5540,12 +5699,13 @@ class depgraph(object):
1049 # List of acceptable packages, ordered by type preference.
1050 matched_packages = []
1051 highest_version = None
1052 - if not isinstance(atom, portage.dep.Atom):
1053 - atom = portage.dep.Atom(atom)
1054 - atom_cp = atom.cp
1055 - have_new_virt = atom_cp.startswith("virtual/") and \
1056 - self._have_new_virt(root, atom_cp)
1057 - atom_set = InternalPackageSet(initial_atoms=(atom,), allow_repo=True)
1058 + atom_cp = None
1059 + have_new_virt = None
1060 + if atom.package:
1061 + atom_cp = atom.cp
1062 + have_new_virt = (atom_cp.startswith("virtual/") and
1063 + self._have_new_virt(root, atom_cp))
1064 +
1065 existing_node = None
1066 myeb = None
1067 rebuilt_binaries = 'rebuilt_binaries' in self._dynamic_config.myparams
1068 @@ -5590,9 +5750,10 @@ class depgraph(object):
1069 # Ignore USE deps for the initial match since we want to
1070 # ensure that updates aren't missed solely due to the user's
1071 # USE configuration.
1072 - for pkg in self._iter_match_pkgs(root_config, pkg_type, atom.without_use,
1073 + for pkg in self._iter_match_pkgs(root_config, pkg_type,
1074 + atom.without_use if atom.package else atom,
1075 onlydeps=onlydeps):
1076 - if pkg.cp != atom_cp and have_new_virt:
1077 + if have_new_virt is True and pkg.cp != atom_cp:
1078 # pull in a new-style virtual instead
1079 continue
1080 if pkg in self._dynamic_config._runtime_pkg_mask:
1081 @@ -5711,14 +5872,14 @@ class depgraph(object):
1082 if not installed and myarg:
1083 found_available_arg = True
1084
1085 - if atom.unevaluated_atom.use:
1086 + if atom.package and atom.unevaluated_atom.use:
1087 #Make sure we don't miss a 'missing IUSE'.
1088 if pkg.iuse.get_missing_iuse(atom.unevaluated_atom.use.required):
1089 # Don't add this to packages_with_invalid_use_config
1090 # since IUSE cannot be adjusted by the user.
1091 continue
1092
1093 - if atom.use:
1094 + if atom.package and atom.use is not None:
1095
1096 if autounmask_level and autounmask_level.allow_use_changes and not pkg.built:
1097 target_use = {}
1098 @@ -5778,7 +5939,7 @@ class depgraph(object):
1099 packages_with_invalid_use_config.append(pkg)
1100 continue
1101
1102 - if pkg.cp == atom_cp:
1103 + if atom_cp is None or pkg.cp == atom_cp:
1104 if highest_version is None:
1105 highest_version = pkg
1106 elif pkg > highest_version:
1107 @@ -5797,9 +5958,11 @@ class depgraph(object):
1108
1109 # Use PackageSet.findAtomForPackage()
1110 # for PROVIDE support.
1111 - if atom_set.findAtomForPackage(e_pkg, modified_use=self._pkg_use_enabled(e_pkg)):
1112 + if atom.match(e_pkg.with_use(
1113 + self._pkg_use_enabled(e_pkg))):
1114 if highest_version and \
1115 - e_pkg.cp == atom_cp and \
1116 + (atom_cp is None or
1117 + e_pkg.cp == atom_cp) and \
1118 e_pkg < highest_version and \
1119 e_pkg.slot_atom != highest_version.slot_atom:
1120 # There is a higher version available in a
1121 @@ -5865,7 +6028,8 @@ class depgraph(object):
1122 # Compare current config to installed package
1123 # and do not reinstall if possible.
1124 if not installed and not useoldpkg and cpv in vardb.match(atom):
1125 - inst_pkg = vardb.match_pkgs('=' + pkg.cpv)[0]
1126 + inst_pkg = vardb.match_pkgs(
1127 + Atom('=' + pkg.cpv))[0]
1128 if "--newrepo" in self._frozen_config.myopts and pkg.repo != inst_pkg.repo:
1129 reinstall = True
1130 elif reinstall_use:
1131 @@ -5906,8 +6070,9 @@ class depgraph(object):
1132
1133 # Filter out any old-style virtual matches if they are
1134 # mixed with new-style virtual matches.
1135 - cp = atom.cp
1136 + cp = atom_cp
1137 if len(matched_packages) > 1 and \
1138 + cp is not None and \
1139 "virtual" == portage.catsplit(cp)[0]:
1140 for pkg in matched_packages:
1141 if pkg.cp != cp:
1142 @@ -6893,7 +7058,7 @@ class depgraph(object):
1143 runtime_deps = InternalPackageSet(
1144 initial_atoms=[PORTAGE_PACKAGE_ATOM])
1145 running_portage = self._frozen_config.trees[running_root]["vartree"].dbapi.match_pkgs(
1146 - PORTAGE_PACKAGE_ATOM)
1147 + Atom(PORTAGE_PACKAGE_ATOM))
1148 replacement_portage = list(self._dynamic_config._package_tracker.match(
1149 running_root, Atom(PORTAGE_PACKAGE_ATOM)))
1150
1151 diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py
1152 index 5d5e936..b56277c 100644
1153 --- a/pym/_emerge/main.py
1154 +++ b/pym/_emerge/main.py
1155 @@ -455,6 +455,16 @@ def parse_opts(tmpcmdline, silent=False):
1156 "choices": y_or_n
1157 },
1158
1159 + "--ignore-soname-deps": {
1160 + "help": "Ignore the soname dependencies of binary and "
1161 + "installed packages. This option is enabled by "
1162 + "default, since soname dependencies are relatively "
1163 + "new, and the required metadata is not guaranteed to "
1164 + "exist for binary and installed packages built with "
1165 + "older versions of portage.",
1166 + "choices": y_or_n
1167 + },
1168 +
1169 "--jobs": {
1170
1171 "shortopt" : "-j",
1172 diff --git a/pym/_emerge/resolver/DbapiProvidesIndex.py b/pym/_emerge/resolver/DbapiProvidesIndex.py
1173 new file mode 100644
1174 index 0000000..59ae719
1175 --- /dev/null
1176 +++ b/pym/_emerge/resolver/DbapiProvidesIndex.py
1177 @@ -0,0 +1,101 @@
1178 +# Copyright 2015 Gentoo Foundation
1179 +# Distributed under the terms of the GNU General Public License v2
1180 +
1181 +import bisect
1182 +import collections
1183 +import sys
1184 +
1185 +class DbapiProvidesIndex(object):
1186 + """
1187 + The DbapiProvidesIndex class is used to wrap existing dbapi
1188 + interfaces, index packages by the sonames that they provide, and
1189 + implement the dbapi.match method for SonameAtom instances. Since
1190 + this class acts as a wrapper, it can be used conditionally, so that
1191 + soname indexing overhead is avoided when soname dependency
1192 + resolution is disabled.
1193 +
1194 + Since it's possible for soname atom match results to consist of
1195 + packages with multiple categories or names, it is essential that
1196 + Package.__lt__ behave meaningfully when Package.cp is dissimilar,
1197 + so that match results will be correctly ordered by version for each
1198 + value of Package.cp.
1199 + """
1200 +
1201 + _copy_attrs = ('aux_get', 'aux_update', 'categories', 'cpv_all',
1202 + 'cpv_exists', 'cp_all', 'cp_list', 'getfetchsizes',
1203 + 'settings', '_aux_cache_keys', '_clear_cache',
1204 + '_cpv_sort_ascending', '_pkg_str', '_pkg_str_aux_keys')
1205 +
1206 + def __init__(self, db):
1207 + self._db = db
1208 + for k in self._copy_attrs:
1209 + try:
1210 + setattr(self, k, getattr(db, k))
1211 + except AttributeError:
1212 + pass
1213 + self._provides_index = collections.defaultdict(list)
1214 +
1215 + def match(self, atom, use_cache=DeprecationWarning):
1216 + if atom.soname:
1217 + result = self._match_soname(atom)
1218 + else:
1219 + result = self._db.match(atom)
1220 + return result
1221 +
1222 + def _match_soname(self, atom):
1223 + result = self._provides_index.get(atom)
1224 + if result is None:
1225 + result = []
1226 + else:
1227 + result = [pkg.cpv for pkg in result]
1228 + return result
1229 +
1230 + def _provides_inject(self, pkg):
1231 + index = self._provides_index
1232 + for atom in pkg.provides:
1233 + # Use bisect.insort for ordered match results.
1234 + bisect.insort(index[atom], pkg)
1235 +
1236 +class PackageDbapiProvidesIndex(DbapiProvidesIndex):
1237 + """
1238 + This class extends DbapiProvidesIndex in order to make it suitable
1239 + for wrapping a PackageVirtualDbapi instance.
1240 + """
1241 +
1242 + _copy_attrs = DbapiProvidesIndex._copy_attrs + (
1243 + "clear", "get", "_cpv_map")
1244 +
1245 + def clear(self):
1246 + self._db.clear()
1247 + self._provides_index.clear()
1248 +
1249 + def __bool__(self):
1250 + return bool(self._db)
1251 +
1252 + if sys.hexversion < 0x3000000:
1253 + __nonzero__ = __bool__
1254 +
1255 + def __iter__(self):
1256 + return iter(self._db)
1257 +
1258 + def __contains__(self, item):
1259 + return item in self._db
1260 +
1261 + def match_pkgs(self, atom):
1262 + return [self._db._cpv_map[cpv] for cpv in self.match(atom)]
1263 +
1264 + def cpv_inject(self, pkg):
1265 + self._db.cpv_inject(pkg)
1266 + self._provides_inject(pkg)
1267 +
1268 + def cpv_remove(self, pkg):
1269 + self._db.cpv_remove(pkg)
1270 + index = self._provides_index
1271 + for atom in pkg.provides:
1272 + items = index[atom]
1273 + try:
1274 + items.remove(pkg)
1275 + except ValueError:
1276 + pass
1277 + if not items:
1278 + del index[atom]
1279 diff --git a/pym/_emerge/resolver/output.py b/pym/_emerge/resolver/output.py
1280 index 14d1b28..7df0302 100644
1281 --- a/pym/_emerge/resolver/output.py
1282 +++ b/pym/_emerge/resolver/output.py
1283 @@ -15,7 +15,7 @@ import sys
1284 import portage
1285 from portage import os
1286 from portage.dbapi.dep_expand import dep_expand
1287 -from portage.dep import cpvequal, _repo_separator, _slot_separator
1288 +from portage.dep import Atom, cpvequal, _repo_separator, _slot_separator
1289 from portage.eapi import _get_eapi_attrs
1290 from portage.exception import InvalidDependString, SignatureException
1291 from portage.localization import localized_size
1292 @@ -659,7 +659,8 @@ class Display(object):
1293
1294 if self.vardb.cpv_exists(pkg.cpv):
1295 # Do a cpv match first, in case the SLOT has changed.
1296 - pkg_info.previous_pkg = self.vardb.match_pkgs('=' + pkg.cpv)[0]
1297 + pkg_info.previous_pkg = self.vardb.match_pkgs(
1298 + Atom('=' + pkg.cpv))[0]
1299 else:
1300 slot_matches = self.vardb.match_pkgs(pkg.slot_atom)
1301 if slot_matches:
1302 @@ -742,7 +743,7 @@ class Display(object):
1303 """
1304 myoldbest = []
1305 myinslotlist = None
1306 - installed_versions = self.vardb.match_pkgs(pkg.cp)
1307 + installed_versions = self.vardb.match_pkgs(Atom(pkg.cp))
1308 if self.vardb.cpv_exists(pkg.cpv):
1309 pkg_info.attr_display.replace = True
1310 installed_version = pkg_info.previous_pkg
1311 @@ -931,6 +932,9 @@ def format_unmatched_atom(pkg, atom, pkg_use_enabled):
1312 # 4. repository
1313 # 5. USE
1314
1315 + if atom.soname:
1316 + return "%s" % (atom,), ""
1317 +
1318 highlight = set()
1319
1320 def perform_coloring():
1321 diff --git a/pym/_emerge/resolver/package_tracker.py b/pym/_emerge/resolver/package_tracker.py
1322 index 406d5ce..398d4cf 100644
1323 --- a/pym/_emerge/resolver/package_tracker.py
1324 +++ b/pym/_emerge/resolver/package_tracker.py
1325 @@ -3,6 +3,7 @@
1326
1327 from __future__ import print_function
1328
1329 +import bisect
1330 import collections
1331
1332 import portage
1333 @@ -43,7 +44,11 @@ class PackageTracker(object):
1334 3) Packages that block each other.
1335 """
1336
1337 - def __init__(self):
1338 + def __init__(self, soname_deps=False):
1339 + """
1340 + @param soname_deps: enable soname match support
1341 + @type soname_deps: bool
1342 + """
1343 # Mapping from package keys to set of packages.
1344 self._cp_pkg_map = collections.defaultdict(list)
1345 self._cp_vdb_pkg_map = collections.defaultdict(list)
1346 @@ -61,6 +66,10 @@ class PackageTracker(object):
1347 self._replaced_by = collections.defaultdict(list)
1348
1349 self._match_cache = collections.defaultdict(dict)
1350 + if soname_deps:
1351 + self._provides_index = collections.defaultdict(list)
1352 + else:
1353 + self._provides_index = None
1354
1355 def add_pkg(self, pkg):
1356 """
1357 @@ -85,8 +94,19 @@ class PackageTracker(object):
1358 self._replacing[pkg].append(installed)
1359 self._replaced_by[installed].append(pkg)
1360
1361 + self._add_provides(pkg)
1362 +
1363 self._match_cache.pop(cp_key, None)
1364
1365 + def _add_provides(self, pkg):
1366 + if (self._provides_index is not None and
1367 + pkg.provides is not None):
1368 + index = self._provides_index
1369 + root = pkg.root
1370 + for atom in pkg.provides:
1371 + # Use bisect.insort for ordered match results.
1372 + bisect.insort(index[(root, atom)], pkg)
1373 +
1374 def add_installed_pkg(self, installed):
1375 """
1376 Add an installed package during vdb load. These packages
1377 @@ -133,6 +153,19 @@ class PackageTracker(object):
1378 del self._replaced_by[installed]
1379 del self._replacing[pkg]
1380
1381 + if self._provides_index is not None:
1382 + index = self._provides_index
1383 + root = pkg.root
1384 + for atom in pkg.provides:
1385 + key = (root, atom)
1386 + items = index[key]
1387 + try:
1388 + items.remove(pkg)
1389 + except ValueError:
1390 + pass
1391 + if not items:
1392 + del index[key]
1393 +
1394 self._match_cache.pop(cp_key, None)
1395
1396 def discard_pkg(self, pkg):
1397 @@ -151,6 +184,9 @@ class PackageTracker(object):
1398 If 'installed' is True, installed non-replaced
1399 packages may also be returned.
1400 """
1401 + if atom.soname:
1402 + return iter(self._provides_index.get((root, atom), []))
1403 +
1404 cp_key = root, atom.cp
1405 cache_key = root, atom, atom.unevaluated_atom, installed
1406 try:
1407 @@ -285,8 +321,6 @@ class PackageTrackerDbapiWrapper(object):
1408 self._package_tracker.add_pkg(pkg)
1409
1410 def match_pkgs(self, atom):
1411 - if not isinstance(atom, Atom):
1412 - atom = Atom(atom)
1413 ret = sorted(self._package_tracker.match(self._root, atom),
1414 key=cmp_sort_key(lambda x, y: vercmp(x.version, y.version)))
1415 return ret
1416 @@ -298,4 +332,4 @@ class PackageTrackerDbapiWrapper(object):
1417 return self.match_pkgs(atom)
1418
1419 def cp_list(self, cp):
1420 - return self.match_pkgs(cp)
1421 + return self.match_pkgs(Atom(cp))
1422 diff --git a/pym/_emerge/resolver/slot_collision.py b/pym/_emerge/resolver/slot_collision.py
1423 index baeab08..5473d72 100644
1424 --- a/pym/_emerge/resolver/slot_collision.py
1425 +++ b/pym/_emerge/resolver/slot_collision.py
1426 @@ -271,16 +271,27 @@ class slot_conflict_handler(object):
1427 num_all_specific_atoms = 0
1428
1429 for ppkg, atom in parent_atoms:
1430 - atom_set = InternalPackageSet(initial_atoms=(atom,))
1431 - atom_without_use_set = InternalPackageSet(initial_atoms=(atom.without_use,))
1432 - atom_without_use_and_slot_set = InternalPackageSet(initial_atoms=(
1433 - atom.without_use.without_slot,))
1434 + if not atom.soname:
1435 + atom_set = InternalPackageSet(
1436 + initial_atoms=(atom,))
1437 + atom_without_use_set = InternalPackageSet(
1438 + initial_atoms=(atom.without_use,))
1439 + atom_without_use_and_slot_set = \
1440 + InternalPackageSet(initial_atoms=(
1441 + atom.without_use.without_slot,))
1442
1443 for other_pkg in pkgs:
1444 if other_pkg == pkg:
1445 continue
1446
1447 - if not atom_without_use_and_slot_set.findAtomForPackage(other_pkg, \
1448 + if atom.soname:
1449 + # The soname does not match.
1450 + key = ("soname", atom)
1451 + atoms = collision_reasons.get(key, set())
1452 + atoms.add((ppkg, atom, other_pkg))
1453 + num_all_specific_atoms += 1
1454 + collision_reasons[key] = atoms
1455 + elif not atom_without_use_and_slot_set.findAtomForPackage(other_pkg,
1456 modified_use=_pkg_use_enabled(other_pkg)):
1457 if atom.operator is not None:
1458 # The version range does not match.
1459 @@ -381,7 +392,7 @@ class slot_conflict_handler(object):
1460 if not verboseconflicts:
1461 selected_for_display.update(
1462 best_matches.values())
1463 - elif type == "slot":
1464 + elif type in ("soname", "slot"):
1465 for ppkg, atom, other_pkg in parents:
1466 selected_for_display.add((ppkg, atom))
1467 if not verboseconflicts:
1468 @@ -532,7 +543,10 @@ class slot_conflict_handler(object):
1469 ordered_list.append(parent_atom)
1470 for parent_atom in ordered_list:
1471 parent, atom = parent_atom
1472 - if isinstance(parent, PackageArg):
1473 + if atom.soname:
1474 + msg.append("%s required by %s\n" %
1475 + (atom, parent))
1476 + elif isinstance(parent, PackageArg):
1477 # For PackageArg it's
1478 # redundant to display the atom attribute.
1479 msg.append("%s\n" % (parent,))
1480 @@ -677,6 +691,9 @@ class slot_conflict_handler(object):
1481 for id, pkg in enumerate(config):
1482 involved_flags = {}
1483 for ppkg, atom in all_conflict_atoms_by_slotatom[id]:
1484 + if not atom.package:
1485 + continue
1486 +
1487 if ppkg in conflict_nodes and not ppkg in config:
1488 #The parent is part of a slot conflict itself and is
1489 #not part of the current config.
1490 @@ -882,6 +899,8 @@ class slot_conflict_handler(object):
1491
1492 #Go through all (parent, atom) pairs for the current slot conflict.
1493 for ppkg, atom in all_conflict_atoms_by_slotatom[id]:
1494 + if not atom.package:
1495 + continue
1496 use = atom.unevaluated_atom.use
1497 if not use:
1498 #No need to force something for an atom without USE conditionals.
1499 @@ -952,6 +971,8 @@ class slot_conflict_handler(object):
1500 new_use = old_use
1501
1502 for ppkg, atom in all_conflict_atoms_by_slotatom[id]:
1503 + if not atom.package:
1504 + continue
1505 if not hasattr(ppkg, "use"):
1506 #It's a SetArg or something like that.
1507 continue
1508 diff --git a/pym/portage/dbapi/DummyTree.py b/pym/portage/dbapi/DummyTree.py
1509 new file mode 100644
1510 index 0000000..6579e88
1511 --- /dev/null
1512 +++ b/pym/portage/dbapi/DummyTree.py
1513 @@ -0,0 +1,16 @@
1514 +# Copyright 2015 Gentoo Foundation
1515 +# Distributed under the terms of the GNU General Public License v2
1516 +
1517 +class DummyTree(object):
1518 + """
1519 + Most internal code only accesses the "dbapi" attribute of the
1520 + binarytree, portagetree, and vartree classes. DummyTree is useful
1521 + in cases where alternative dbapi implementations (or wrappers that
1522 + modify or extend behavior of existing dbapi implementations) are
1523 + needed, since it allows these implementations to be exposed through
1524 + an interface which is minimally compatible with the *tree classes.
1525 + """
1526 + __slots__ = ("dbapi",)
1527 +
1528 + def __init__(self, dbapi):
1529 + self.dbapi = dbapi
1530 diff --git a/pym/portage/dep/__init__.py b/pym/portage/dep/__init__.py
1531 index c457df0..e2e416c 100644
1532 --- a/pym/portage/dep/__init__.py
1533 +++ b/pym/portage/dep/__init__.py
1534 @@ -1172,6 +1172,12 @@ class Atom(_unicode):
1535 class emulates most of the str methods that are useful with atoms.
1536 """
1537
1538 + # Distiguishes package atoms from other atom types
1539 + package = True
1540 +
1541 + # Distiguishes soname atoms from other atom types
1542 + soname = False
1543 +
1544 class _blocker(object):
1545 __slots__ = ("overlap",)
1546
1547 @@ -1561,6 +1567,25 @@ class Atom(_unicode):
1548 memo[id(self)] = self
1549 return self
1550
1551 + def match(self, pkg):
1552 + """
1553 + Check if the given package instance matches this atom. This
1554 + includes support for virtual matches via PROVIDE metadata.
1555 +
1556 + @param pkg: a Package instance
1557 + @type pkg: Package
1558 + @return: True if this atom matches pkg, otherwise False
1559 + @rtype: bool
1560 + """
1561 + if pkg.cp == self.cp:
1562 + return bool(match_from_list(self, [pkg]))
1563 + else:
1564 + for provided_cp in pkg.provided_cps:
1565 + if provided_cp == self.cp:
1566 + return bool(match_from_list(
1567 + self.replace(self.cp, provided_cp, 1), [pkg]))
1568 + return False
1569 +
1570 _extended_cp_re_cache = {}
1571
1572 def extended_cp_match(extended_cp, other_cp):
1573 diff --git a/pym/portage/dep/soname/SonameAtom.py b/pym/portage/dep/soname/SonameAtom.py
1574 new file mode 100644
1575 index 0000000..a7dad97
1576 --- /dev/null
1577 +++ b/pym/portage/dep/soname/SonameAtom.py
1578 @@ -0,0 +1,72 @@
1579 +# Copyright 2015 Gentoo Foundation
1580 +# Distributed under the terms of the GNU General Public License v2
1581 +
1582 +from __future__ import unicode_literals
1583 +
1584 +import sys
1585 +
1586 +from portage import _encodings, _unicode_encode
1587 +
1588 +class SonameAtom(object):
1589 +
1590 + __slots__ = ("multilib_category", "soname", "_hash_key",
1591 + "_hash_value")
1592 +
1593 + # Distiguishes package atoms from other atom types
1594 + package = False
1595 +
1596 + def __init__(self, multilib_category, soname):
1597 + object.__setattr__(self, "multilib_category", multilib_category)
1598 + object.__setattr__(self, "soname", soname)
1599 + object.__setattr__(self, "_hash_key",
1600 + (multilib_category, soname))
1601 + object.__setattr__(self, "_hash_value", hash(self._hash_key))
1602 +
1603 + def __setattr__(self, name, value):
1604 + raise AttributeError("SonameAtom instances are immutable",
1605 + self.__class__, name, value)
1606 +
1607 + def __hash__(self):
1608 + return self._hash_value
1609 +
1610 + def __eq__(self, other):
1611 + try:
1612 + return self._hash_key == other._hash_key
1613 + except AttributeError:
1614 + return False
1615 +
1616 + def __ne__(self, other):
1617 + try:
1618 + return self._hash_key != other._hash_key
1619 + except AttributeError:
1620 + return True
1621 +
1622 + def __repr__(self):
1623 + return "%s('%s', '%s')" % (
1624 + self.__class__.__name__,
1625 + self.multilib_category,
1626 + self.soname
1627 + )
1628 +
1629 + def __str__(self):
1630 + return "%s: %s" % (self.multilib_category, self.soname)
1631 +
1632 + if sys.hexversion < 0x3000000:
1633 +
1634 + __unicode__ = __str__
1635 +
1636 + def __str__(self):
1637 + return _unicode_encode(self.__unicode__(),
1638 + encoding=_encodings['content'])
1639 +
1640 + def match(self, pkg):
1641 + """
1642 + Check if the given package instance matches this atom. Unbuilt
1643 + ebuilds, which do not have soname metadata, will never match.
1644 +
1645 + @param pkg: a Package instance
1646 + @type pkg: Package
1647 + @return: True if this atom matches pkg, otherwise False
1648 + @rtype: bool
1649 + """
1650 + return pkg.provides is not None and self in pkg.provides
1651 diff --git a/pym/portage/dep/soname/parse.py b/pym/portage/dep/soname/parse.py
1652 new file mode 100644
1653 index 0000000..3f37572
1654 --- /dev/null
1655 +++ b/pym/portage/dep/soname/parse.py
1656 @@ -0,0 +1,47 @@
1657 +# Copyright 2015 Gentoo Foundation
1658 +# Distributed under the terms of the GNU General Public License v2
1659 +
1660 +from __future__ import unicode_literals
1661 +
1662 +from portage.exception import InvalidData
1663 +from portage.localization import _
1664 +from portage.dep.soname.SonameAtom import SonameAtom
1665 +
1666 +_error_empty_category = _("Multilib category empty: %s")
1667 +_error_missing_category = _("Multilib category missing: %s")
1668 +_error_duplicate_category = _("Multilib category occurs"
1669 + " more than once: %s")
1670 +
1671 +def parse_soname_deps(s):
1672 + """
1673 + Parse a REQUIRES or PROVIDES dependency string, and raise
1674 + InvalidData if necessary.
1675 +
1676 + @param s: REQUIRES or PROVIDES string
1677 + @type s: str
1678 + @rtype: iter
1679 + @return: An iterator of SonameAtom instances
1680 + """
1681 +
1682 + categories = set()
1683 + category = None
1684 + previous_soname = None
1685 + for soname in s.split():
1686 + if soname.endswith(":"):
1687 + if category is not None and previous_soname is None:
1688 + raise InvalidData(_error_empty_category % category)
1689 +
1690 + category = soname[:-1]
1691 + previous_soname = None
1692 + if category in categories:
1693 + raise InvalidData(_error_duplicate_category % category)
1694 + categories.add(category)
1695 +
1696 + elif category is None:
1697 + raise InvalidData(_error_missing_category % soname)
1698 + else:
1699 + previous_soname = soname
1700 + yield SonameAtom(category, soname)
1701 +
1702 + if category is not None and previous_soname is None:
1703 + raise InvalidData(_error_empty_category % category)
1704 diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py
1705 index e119498..71fe4df 100644
1706 --- a/pym/portage/package/ebuild/config.py
1707 +++ b/pym/portage/package/ebuild/config.py
1708 @@ -22,6 +22,7 @@ from _emerge.Package import Package
1709 import portage
1710 portage.proxy.lazyimport.lazyimport(globals(),
1711 'portage.data:portage_gid',
1712 + 'portage.dep.soname.SonameAtom:SonameAtom',
1713 'portage.dbapi.vartree:vartree',
1714 'portage.package.ebuild.doebuild:_phase_func_map',
1715 )
1716 @@ -230,6 +231,7 @@ class config(object):
1717 self._features_overrides = []
1718 self._make_defaults = None
1719 self._parent_stable = None
1720 + self._soname_provided = None
1721
1722 # _unknown_features records unknown features that
1723 # have triggered warning messages, and ensures that
1724 @@ -266,6 +268,7 @@ class config(object):
1725 self.make_defaults_use = clone.make_defaults_use
1726 self.mycpv = clone.mycpv
1727 self._setcpv_args_hash = clone._setcpv_args_hash
1728 + self._soname_provided = clone._soname_provided
1729
1730 # immutable attributes (internal policy ensures lack of mutation)
1731 self._locations_manager = clone._locations_manager
1732 @@ -1049,6 +1052,16 @@ class config(object):
1733 def punmaskdict(self):
1734 return self._mask_manager._punmaskdict.copy()
1735
1736 + @property
1737 + def soname_provided(self):
1738 + if self._soname_provided is None:
1739 + d = stack_dictlist((grabdict(
1740 + os.path.join(x, "soname.provided"), recursive=True)
1741 + for x in self.profiles), incremental=True)
1742 + self._soname_provided = frozenset(SonameAtom(cat, soname)
1743 + for cat, sonames in d.items() for soname in sonames)
1744 + return self._soname_provided
1745 +
1746 def expandLicenseTokens(self, tokens):
1747 """ Take a token from ACCEPT_LICENSE or package.license and expand it
1748 if it's a group token (indicated by @) or just return it if it's not a
1749 diff --git a/pym/portage/tests/resolver/ResolverPlayground.py b/pym/portage/tests/resolver/ResolverPlayground.py
1750 index b5c0446..84ad17c 100644
1751 --- a/pym/portage/tests/resolver/ResolverPlayground.py
1752 +++ b/pym/portage/tests/resolver/ResolverPlayground.py
1753 @@ -40,6 +40,7 @@ class ResolverPlayground(object):
1754 config_files = frozenset(("eapi", "layout.conf", "make.conf", "package.accept_keywords",
1755 "package.keywords", "package.license", "package.mask", "package.properties",
1756 "package.unmask", "package.use", "package.use.aliases", "package.use.stable.mask",
1757 + "soname.provided",
1758 "unpack_dependencies", "use.aliases", "use.force", "use.mask", "layout.conf"))
1759
1760 metadata_xml_template = """<?xml version="1.0" encoding="UTF-8"?>
1761 @@ -254,6 +255,8 @@ class ResolverPlayground(object):
1762 unknown_keys.discard("COUNTER")
1763 unknown_keys.discard("repository")
1764 unknown_keys.discard("USE")
1765 + unknown_keys.discard("PROVIDES")
1766 + unknown_keys.discard("REQUIRES")
1767 if unknown_keys:
1768 raise ValueError("metadata of installed '%s' contains unknown keys: %s" %
1769 (cpv, sorted(unknown_keys)))
1770 diff --git a/pym/portage/tests/resolver/soname/__init__.py b/pym/portage/tests/resolver/soname/__init__.py
1771 new file mode 100644
1772 index 0000000..4725d33
1773 --- /dev/null
1774 +++ b/pym/portage/tests/resolver/soname/__init__.py
1775 @@ -0,0 +1,2 @@
1776 +# Copyright 2015 Gentoo Foundation
1777 +# Distributed under the terms of the GNU General Public License v2
1778 diff --git a/pym/portage/tests/resolver/soname/__test__.py b/pym/portage/tests/resolver/soname/__test__.py
1779 new file mode 100644
1780 index 0000000..4725d33
1781 --- /dev/null
1782 +++ b/pym/portage/tests/resolver/soname/__test__.py
1783 @@ -0,0 +1,2 @@
1784 +# Copyright 2015 Gentoo Foundation
1785 +# Distributed under the terms of the GNU General Public License v2
1786 diff --git a/pym/portage/tests/resolver/soname/test_autounmask.py b/pym/portage/tests/resolver/soname/test_autounmask.py
1787 new file mode 100644
1788 index 0000000..be0f94e
1789 --- /dev/null
1790 +++ b/pym/portage/tests/resolver/soname/test_autounmask.py
1791 @@ -0,0 +1,103 @@
1792 +# Copyright 2015 Gentoo Foundation
1793 +# Distributed under the terms of the GNU General Public License v2
1794 +
1795 +from portage.tests import TestCase
1796 +from portage.tests.resolver.ResolverPlayground import (
1797 + ResolverPlayground, ResolverPlaygroundTestCase)
1798 +
1799 +class SonameAutoUnmaskTestCase(TestCase):
1800 +
1801 + def testSonameAutoUnmask(self):
1802 +
1803 + binpkgs = {
1804 + "dev-libs/icu-49" : {
1805 + "KEYWORDS": "x86",
1806 + "PROVIDES": "x86_32: libicu.so.49",
1807 + },
1808 + "dev-libs/icu-4.8" : {
1809 + "KEYWORDS": "x86",
1810 + "PROVIDES": "x86_32: libicu.so.48",
1811 + },
1812 + "dev-libs/libxml2-2.7.8" : {
1813 + "KEYWORDS": "~x86",
1814 + "DEPEND": "dev-libs/icu",
1815 + "RDEPEND": "dev-libs/icu",
1816 + "REQUIRES": "x86_32: libicu.so.49",
1817 + },
1818 + }
1819 +
1820 + installed = {
1821 + "dev-libs/icu-4.8" : {
1822 + "KEYWORDS": "x86",
1823 + "PROVIDES": "x86_32: libicu.so.48",
1824 + },
1825 + "dev-libs/libxml2-2.7.8" : {
1826 + "KEYWORDS": "~x86",
1827 + "DEPEND": "dev-libs/icu",
1828 + "RDEPEND": "dev-libs/icu",
1829 + "REQUIRES": "x86_32: libicu.so.48",
1830 + },
1831 + }
1832 +
1833 + world = ["dev-libs/libxml2"]
1834 +
1835 + test_cases = (
1836 +
1837 + ResolverPlaygroundTestCase(
1838 + ["dev-libs/icu"],
1839 + options = {
1840 + "--autounmask": True,
1841 + "--ignore-soname-deps": "n",
1842 + "--oneshot": True,
1843 + "--usepkgonly": True,
1844 + },
1845 + success = False,
1846 + mergelist = [
1847 + "[binary]dev-libs/icu-49",
1848 + "[binary]dev-libs/libxml2-2.7.8"
1849 + ],
1850 + unstable_keywords = ['dev-libs/libxml2-2.7.8'],
1851 + ),
1852 +
1853 + ResolverPlaygroundTestCase(
1854 + ["dev-libs/icu"],
1855 + options = {
1856 + "--autounmask": True,
1857 + "--ignore-soname-deps": "y",
1858 + "--oneshot": True,
1859 + "--usepkgonly": True,
1860 + },
1861 + success = True,
1862 + mergelist = [
1863 + "[binary]dev-libs/icu-49"
1864 + ]
1865 + ),
1866 +
1867 + # Test that dev-libs/icu-49 update is skipped due to
1868 + # dev-libs/libxml2-2.7.8 being masked by KEYWORDS. Note
1869 + # that this result is questionable, since the installed
1870 + # dev-libs/libxml2-2.7.8 instance is also masked!
1871 + ResolverPlaygroundTestCase(
1872 + ["@world"],
1873 + options = {
1874 + "--autounmask": True,
1875 + "--deep": True,
1876 + "--ignore-soname-deps": "n",
1877 + "--update": True,
1878 + "--usepkgonly": True,
1879 + },
1880 + success = True,
1881 + mergelist = [],
1882 + ),
1883 +
1884 + )
1885 +
1886 + playground = ResolverPlayground(binpkgs=binpkgs,
1887 + installed=installed, world=world, debug=False)
1888 + try:
1889 + for test_case in test_cases:
1890 + playground.run_TestCase(test_case)
1891 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
1892 + finally:
1893 + playground.debug = False
1894 + playground.cleanup()
1895 diff --git a/pym/portage/tests/resolver/soname/test_depclean.py b/pym/portage/tests/resolver/soname/test_depclean.py
1896 new file mode 100644
1897 index 0000000..50cc169
1898 --- /dev/null
1899 +++ b/pym/portage/tests/resolver/soname/test_depclean.py
1900 @@ -0,0 +1,61 @@
1901 +# Copyright 2015 Gentoo Foundation
1902 +# Distributed under the terms of the GNU General Public License v2
1903 +
1904 +from portage.tests import TestCase
1905 +from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
1906 + ResolverPlaygroundTestCase)
1907 +
1908 +class SonameDepcleanTestCase(TestCase):
1909 +
1910 + def testSonameDepclean(self):
1911 +
1912 + installed = {
1913 + "app-misc/A-1" : {
1914 + "RDEPEND": "dev-libs/B",
1915 + "DEPEND": "dev-libs/B",
1916 + "REQUIRES": "x86_32: libB.so.1 libc.so.6",
1917 + },
1918 + "dev-libs/B-1" : {
1919 + "PROVIDES": "x86_32: libB.so.1",
1920 + },
1921 + "sys-libs/glibc-2.19-r1" : {
1922 + "PROVIDES": "x86_32: libc.so.6"
1923 + },
1924 + }
1925 +
1926 + world = ("app-misc/A",)
1927 +
1928 + test_cases = (
1929 +
1930 + ResolverPlaygroundTestCase(
1931 + [],
1932 + options={
1933 + "--depclean": True,
1934 + "--ignore-soname-deps": "n",
1935 + },
1936 + success=True,
1937 + cleanlist=[]
1938 + ),
1939 +
1940 + ResolverPlaygroundTestCase(
1941 + [],
1942 + options={
1943 + "--depclean": True,
1944 + "--ignore-soname-deps": "y",
1945 + },
1946 + success=True,
1947 + cleanlist=["sys-libs/glibc-2.19-r1"]
1948 + ),
1949 + )
1950 +
1951 + playground = ResolverPlayground(debug=False,
1952 + installed=installed, world=world)
1953 + try:
1954 + for test_case in test_cases:
1955 + playground.run_TestCase(test_case)
1956 + self.assertEqual(test_case.test_success, True,
1957 + test_case.fail_msg)
1958 + finally:
1959 + # Disable debug so that cleanup works.
1960 + playground.debug = False
1961 + playground.cleanup()
1962 diff --git a/pym/portage/tests/resolver/soname/test_downgrade.py b/pym/portage/tests/resolver/soname/test_downgrade.py
1963 new file mode 100644
1964 index 0000000..a95be34
1965 --- /dev/null
1966 +++ b/pym/portage/tests/resolver/soname/test_downgrade.py
1967 @@ -0,0 +1,240 @@
1968 +# Copyright 2015 Gentoo Foundation
1969 +# Distributed under the terms of the GNU General Public License v2
1970 +
1971 +from portage.tests import TestCase
1972 +from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
1973 + ResolverPlaygroundTestCase)
1974 +
1975 +class SonameDowngradeTestCase(TestCase):
1976 +
1977 + def testSingleSlot(self):
1978 +
1979 + ebuilds = {
1980 + "dev-libs/icu-49" : {
1981 + },
1982 + "dev-libs/icu-4.8" : {
1983 + },
1984 + "dev-libs/libxml2-2.7.8" : {
1985 + "DEPEND": "dev-libs/icu",
1986 + "RDEPEND": "dev-libs/icu",
1987 + },
1988 + }
1989 +
1990 + binpkgs = {
1991 + "dev-libs/icu-49" : {
1992 + "PROVIDES": "x86_32: libicu.so.49",
1993 + },
1994 + "dev-libs/icu-4.8" : {
1995 + "PROVIDES": "x86_32: libicu.so.48",
1996 + },
1997 + "dev-libs/libxml2-2.7.8" : {
1998 + "DEPEND": "dev-libs/icu",
1999 + "RDEPEND": "dev-libs/icu",
2000 + "REQUIRES": "x86_32: libicu.so.48",
2001 + },
2002 + }
2003 + installed = {
2004 + "dev-libs/icu-49" : {
2005 + "PROVIDES": "x86_32: libicu.so.49",
2006 + },
2007 + "dev-libs/libxml2-2.7.8" : {
2008 + "DEPEND": "dev-libs/icu",
2009 + "RDEPEND": "dev-libs/icu",
2010 + "REQUIRES": "x86_32: libicu.so.49",
2011 + },
2012 + }
2013 +
2014 + user_config = {
2015 + "package.mask" : (
2016 + ">=dev-libs/icu-49",
2017 + ),
2018 + }
2019 +
2020 + world = ["dev-libs/libxml2"]
2021 +
2022 + test_cases = (
2023 +
2024 + ResolverPlaygroundTestCase(
2025 + ["dev-libs/icu"],
2026 + options = {
2027 + "--autounmask": "n",
2028 + "--ignore-soname-deps": "n",
2029 + "--oneshot": True,
2030 + "--usepkgonly": True
2031 + },
2032 + success = True,
2033 + mergelist = [
2034 + "[binary]dev-libs/icu-4.8",
2035 + "[binary]dev-libs/libxml2-2.7.8"
2036 + ]
2037 + ),
2038 +
2039 + ResolverPlaygroundTestCase(
2040 + ["dev-libs/icu"],
2041 + options = {
2042 + "--autounmask": "n",
2043 + "--ignore-soname-deps": "y",
2044 + "--oneshot": True,
2045 + "--usepkgonly": True
2046 + },
2047 + success = True,
2048 + mergelist = [
2049 + "[binary]dev-libs/icu-4.8",
2050 + ]
2051 + ),
2052 +
2053 + ResolverPlaygroundTestCase(
2054 + ["@world"],
2055 + options = {
2056 + "--autounmask": "n",
2057 + "--deep": True,
2058 + "--ignore-soname-deps": "n",
2059 + "--update": True,
2060 + "--usepkgonly": True,
2061 + },
2062 + success = True,
2063 + mergelist = [
2064 + "[binary]dev-libs/icu-4.8",
2065 + "[binary]dev-libs/libxml2-2.7.8"
2066 + ]
2067 + ),
2068 +
2069 + # In this case, soname dependencies are not respected,
2070 + # because --usepkgonly is not enabled. This could be
2071 + # handled differently, by respecting soname dependencies
2072 + # as long as no unbuilt ebuilds get pulled into the graph.
2073 + # However, that kind of conditional dependency accounting
2074 + # would add a significant amount of complexity.
2075 + ResolverPlaygroundTestCase(
2076 + ["@world"],
2077 + options = {
2078 + "--deep": True,
2079 + "--ignore-soname-deps": "n",
2080 + "--update": True,
2081 + "--usepkg": True,
2082 + },
2083 + success = True,
2084 + mergelist = [
2085 + "[binary]dev-libs/icu-4.8",
2086 + ]
2087 + ),
2088 +
2089 + ResolverPlaygroundTestCase(
2090 + ["@world"],
2091 + options = {
2092 + "--deep": True,
2093 + "--update": True,
2094 + },
2095 + success = True,
2096 + mergelist = [
2097 + "dev-libs/icu-4.8",
2098 + ]
2099 + ),
2100 + )
2101 +
2102 + playground = ResolverPlayground(binpkgs=binpkgs,
2103 + ebuilds=ebuilds, installed=installed,
2104 + user_config=user_config, world=world, debug=False)
2105 + try:
2106 + for test_case in test_cases:
2107 + playground.run_TestCase(test_case)
2108 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
2109 + finally:
2110 + # Disable debug so that cleanup works.
2111 + playground.debug = False
2112 + playground.cleanup()
2113 +
2114 + def testTwoSlots(self):
2115 +
2116 + ebuilds = {
2117 + "dev-libs/glib-1.2.10" : {
2118 + "SLOT": "1"
2119 + },
2120 + "dev-libs/glib-2.30.2" : {
2121 + "SLOT": "2"
2122 + },
2123 + "dev-libs/dbus-glib-0.98" : {
2124 + "EAPI": "1",
2125 + "DEPEND": "dev-libs/glib:2",
2126 + "RDEPEND": "dev-libs/glib:2"
2127 + },
2128 + }
2129 + binpkgs = {
2130 + "dev-libs/glib-1.2.10" : {
2131 + "SLOT": "1",
2132 + "PROVIDES": "x86_32: libglib-1.0.so.0",
2133 + },
2134 + "dev-libs/glib-2.30.2" : {
2135 + "PROVIDES": "x86_32: libglib-2.0.so.30",
2136 + "SLOT": "2",
2137 + },
2138 + "dev-libs/glib-2.32.3" : {
2139 + "PROVIDES": "x86_32: libglib-2.0.so.32",
2140 + "SLOT": "2",
2141 + },
2142 + "dev-libs/dbus-glib-0.98" : {
2143 + "EAPI": "1",
2144 + "DEPEND": "dev-libs/glib:2",
2145 + "RDEPEND": "dev-libs/glib:2",
2146 + "REQUIRES": "x86_32: libglib-2.0.so.30",
2147 + },
2148 + }
2149 + installed = {
2150 + "dev-libs/glib-1.2.10" : {
2151 + "PROVIDES": "x86_32: libglib-1.0.so.0",
2152 + "SLOT": "1",
2153 + },
2154 + "dev-libs/glib-2.32.3" : {
2155 + "PROVIDES": "x86_32: libglib-2.0.so.32",
2156 + "SLOT": "2",
2157 + },
2158 + "dev-libs/dbus-glib-0.98" : {
2159 + "EAPI": "1",
2160 + "DEPEND": "dev-libs/glib:2",
2161 + "RDEPEND": "dev-libs/glib:2",
2162 + "REQUIRES": "x86_32: libglib-2.0.so.32",
2163 + },
2164 + }
2165 +
2166 + user_config = {
2167 + "package.mask" : (
2168 + ">=dev-libs/glib-2.32",
2169 + ),
2170 + }
2171 +
2172 + world = [
2173 + "dev-libs/glib:1",
2174 + "dev-libs/dbus-glib",
2175 + ]
2176 +
2177 + test_cases = (
2178 +
2179 + ResolverPlaygroundTestCase(
2180 + ["@world"],
2181 + options = {
2182 + "--autounmask": "n",
2183 + "--deep": True,
2184 + "--ignore-soname-deps": "n",
2185 + "--update": True,
2186 + "--usepkgonly": True,
2187 + },
2188 + success = True,
2189 + mergelist = [
2190 + "[binary]dev-libs/glib-2.30.2",
2191 + "[binary]dev-libs/dbus-glib-0.98"
2192 + ]
2193 + ),
2194 +
2195 + )
2196 +
2197 + playground = ResolverPlayground(ebuilds=ebuilds, binpkgs=binpkgs,
2198 + installed=installed, user_config=user_config, world=world,
2199 + debug=False)
2200 + try:
2201 + for test_case in test_cases:
2202 + playground.run_TestCase(test_case)
2203 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
2204 + finally:
2205 + # Disable debug so that cleanup works.
2206 + playground.debug = False
2207 + playground.cleanup()
2208 diff --git a/pym/portage/tests/resolver/soname/test_or_choices.py b/pym/portage/tests/resolver/soname/test_or_choices.py
2209 new file mode 100644
2210 index 0000000..2420cd3
2211 --- /dev/null
2212 +++ b/pym/portage/tests/resolver/soname/test_or_choices.py
2213 @@ -0,0 +1,92 @@
2214 +# Copyright 2015 Gentoo Foundation
2215 +# Distributed under the terms of the GNU General Public License v2
2216 +
2217 +from portage.tests import TestCase
2218 +from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
2219 + ResolverPlaygroundTestCase)
2220 +
2221 +class SonameOrChoicesTestCase(TestCase):
2222 +
2223 + def testSonameConflictMissedUpdate(self):
2224 +
2225 + binpkgs = {
2226 + "dev-lang/ocaml-4.02.1" : {
2227 + "EAPI": "5",
2228 + "PROVIDES": "x86_32: libocaml-4.02.1.so",
2229 + },
2230 +
2231 + "dev-lang/ocaml-4.01.0" : {
2232 + "EAPI": "5",
2233 + "PROVIDES": "x86_32: libocaml-4.01.0.so",
2234 + },
2235 +
2236 + "dev-ml/lablgl-1.05" : {
2237 + "DEPEND": (">=dev-lang/ocaml-3.10.2 "
2238 + "|| ( dev-ml/labltk <dev-lang/ocaml-4.02 )"),
2239 + "RDEPEND": (">=dev-lang/ocaml-3.10.2 "
2240 + "|| ( dev-ml/labltk <dev-lang/ocaml-4.02 )"),
2241 + "REQUIRES": "x86_32: libocaml-4.02.1.so",
2242 + },
2243 +
2244 + "dev-ml/labltk-8.06.0" : {
2245 + "EAPI": "5",
2246 + "SLOT": "0/8.06.0",
2247 + "DEPEND": ">=dev-lang/ocaml-4.02",
2248 + "RDEPEND": ">=dev-lang/ocaml-4.02",
2249 + "REQUIRES": "x86_32: libocaml-4.02.1.so",
2250 + },
2251 + }
2252 +
2253 + installed = {
2254 + "dev-lang/ocaml-4.01.0" : {
2255 + "EAPI": "5",
2256 + "PROVIDES": "x86_32: libocaml-4.01.0.so",
2257 + },
2258 +
2259 + "dev-ml/lablgl-1.05" : {
2260 + "DEPEND": (">=dev-lang/ocaml-3.10.2 "
2261 + "|| ( dev-ml/labltk <dev-lang/ocaml-4.02 )"),
2262 + "RDEPEND": (">=dev-lang/ocaml-3.10.2 "
2263 + "|| ( dev-ml/labltk <dev-lang/ocaml-4.02 )"),
2264 + "REQUIRES": "x86_32: libocaml-4.01.0.so",
2265 + },
2266 + }
2267 +
2268 + world = (
2269 + "dev-lang/ocaml",
2270 + "dev-ml/lablgl",
2271 + )
2272 +
2273 + test_cases = (
2274 +
2275 + # bug #531656: If an ocaml update is desirable,
2276 + # then we need to pull in dev-ml/labltk.
2277 + ResolverPlaygroundTestCase(
2278 + ["@world"],
2279 + options = {
2280 + "--deep": True,
2281 + "--ignore-soname-deps": "n",
2282 + "--update": True,
2283 + "--usepkgonly": True
2284 + },
2285 + success = True,
2286 + mergelist = [
2287 + "[binary]dev-lang/ocaml-4.02.1",
2288 + "[binary]dev-ml/labltk-8.06.0",
2289 + "[binary]dev-ml/lablgl-1.05",
2290 + ]
2291 + ),
2292 +
2293 + )
2294 +
2295 + playground = ResolverPlayground(debug=False,
2296 + binpkgs=binpkgs, installed=installed, world=world)
2297 + try:
2298 + for test_case in test_cases:
2299 + playground.run_TestCase(test_case)
2300 + self.assertEqual(test_case.test_success, True,
2301 + test_case.fail_msg)
2302 + finally:
2303 + # Disable debug so that cleanup works.
2304 + playground.debug = False
2305 + playground.cleanup()
2306 diff --git a/pym/portage/tests/resolver/soname/test_reinstall.py b/pym/portage/tests/resolver/soname/test_reinstall.py
2307 new file mode 100644
2308 index 0000000..b8f2d2c
2309 --- /dev/null
2310 +++ b/pym/portage/tests/resolver/soname/test_reinstall.py
2311 @@ -0,0 +1,87 @@
2312 +# Copyright 2015 Gentoo Foundation
2313 +# Distributed under the terms of the GNU General Public License v2
2314 +
2315 +from portage.tests import TestCase
2316 +from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
2317 + ResolverPlaygroundTestCase)
2318 +
2319 +class SonameReinstallTestCase(TestCase):
2320 +
2321 + def testSonameReinstall(self):
2322 +
2323 + binpkgs = {
2324 + "app-misc/A-1" : {
2325 + "RDEPEND": "dev-libs/B",
2326 + "DEPEND": "dev-libs/B",
2327 + "REQUIRES": "x86_32: libB.so.2",
2328 + },
2329 + "dev-libs/B-2" : {
2330 + "PROVIDES": "x86_32: libB.so.2",
2331 + },
2332 + "dev-libs/B-1" : {
2333 + "PROVIDES": "x86_32: libB.so.1",
2334 + },
2335 + }
2336 +
2337 + installed = {
2338 + "app-misc/A-1" : {
2339 + "RDEPEND": "dev-libs/B",
2340 + "DEPEND": "dev-libs/B",
2341 + "REQUIRES": "x86_32: libB.so.1",
2342 + },
2343 + "dev-libs/B-1" : {
2344 + "PROVIDES": "x86_32: libB.so.1",
2345 + },
2346 + }
2347 +
2348 + world = ("app-misc/A",)
2349 +
2350 + test_cases = (
2351 +
2352 + # Test that --ignore-soname-deps prevents the above
2353 + # rebuild from being triggered.
2354 + ResolverPlaygroundTestCase(
2355 + ["@world"],
2356 + options = {
2357 + "--deep": True,
2358 + "--ignore-soname-deps": "n",
2359 + "--update": True,
2360 + "--usepkgonly": True
2361 + },
2362 + success = True,
2363 + mergelist = [
2364 + "[binary]dev-libs/B-2",
2365 + "[binary]app-misc/A-1",
2366 + ]
2367 + ),
2368 +
2369 + # Test that --ignore-soname-deps prevents the above
2370 + # reinstall from being triggered.
2371 + ResolverPlaygroundTestCase(
2372 + ["@world"],
2373 + options = {
2374 + "--deep": True,
2375 + "--ignore-soname-deps": "y",
2376 + "--update": True,
2377 + "--usepkgonly": True
2378 + },
2379 + success = True,
2380 + mergelist = [
2381 + "[binary]dev-libs/B-2",
2382 + ]
2383 + ),
2384 +
2385 + )
2386 +
2387 + playground = ResolverPlayground(debug=False,
2388 + binpkgs=binpkgs, installed=installed,
2389 + world=world)
2390 + try:
2391 + for test_case in test_cases:
2392 + playground.run_TestCase(test_case)
2393 + self.assertEqual(test_case.test_success, True,
2394 + test_case.fail_msg)
2395 + finally:
2396 + # Disable debug so that cleanup works.
2397 + playground.debug = False
2398 + playground.cleanup()
2399 diff --git a/pym/portage/tests/resolver/soname/test_skip_update.py b/pym/portage/tests/resolver/soname/test_skip_update.py
2400 new file mode 100644
2401 index 0000000..67e1e02
2402 --- /dev/null
2403 +++ b/pym/portage/tests/resolver/soname/test_skip_update.py
2404 @@ -0,0 +1,86 @@
2405 +# Copyright 2015 Gentoo Foundation
2406 +# Distributed under the terms of the GNU General Public License v2
2407 +
2408 +from portage.tests import TestCase
2409 +from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
2410 + ResolverPlaygroundTestCase)
2411 +
2412 +class SonameSkipUpdateTestCase(TestCase):
2413 +
2414 + def testSonameSkipUpdate(self):
2415 +
2416 + binpkgs = {
2417 + "app-misc/A-1" : {
2418 + "RDEPEND": "dev-libs/B",
2419 + "DEPEND": "dev-libs/B",
2420 + "REQUIRES": "x86_32: libB.so.1",
2421 + },
2422 + "dev-libs/B-2" : {
2423 + "PROVIDES": "x86_32: libB.so.2",
2424 + },
2425 + "dev-libs/B-1" : {
2426 + "PROVIDES": "x86_32: libB.so.1",
2427 + },
2428 + }
2429 +
2430 + installed = {
2431 + "app-misc/A-1" : {
2432 + "RDEPEND": "dev-libs/B",
2433 + "DEPEND": "dev-libs/B",
2434 + "REQUIRES": "x86_32: libB.so.1",
2435 + },
2436 + "dev-libs/B-1" : {
2437 + "PROVIDES": "x86_32: libB.so.1",
2438 + },
2439 + }
2440 +
2441 + world = ("app-misc/A",)
2442 +
2443 + test_cases = (
2444 +
2445 + # Test that --ignore-soname-deps allows the upgrade,
2446 + # even though it will break an soname dependency of
2447 + # app-misc/A-1.
2448 + ResolverPlaygroundTestCase(
2449 + ["@world"],
2450 + options = {
2451 + "--deep": True,
2452 + "--ignore-soname-deps": "y",
2453 + "--update": True,
2454 + "--usepkgonly": True
2455 + },
2456 + success = True,
2457 + mergelist = [
2458 + "[binary]dev-libs/B-2",
2459 + ]
2460 + ),
2461 +
2462 + # Test that upgrade to B-2 is skipped with --usepkgonly
2463 + # because it will break an soname dependency that
2464 + # cannot be satisfied by the available binary packages.
2465 + ResolverPlaygroundTestCase(
2466 + ["@world"],
2467 + options = {
2468 + "--deep": True,
2469 + "--ignore-soname-deps": "n",
2470 + "--update": True,
2471 + "--usepkgonly": True
2472 + },
2473 + success = True,
2474 + mergelist = []
2475 + ),
2476 +
2477 + )
2478 +
2479 + playground = ResolverPlayground(debug=False,
2480 + binpkgs=binpkgs, installed=installed,
2481 + world=world)
2482 + try:
2483 + for test_case in test_cases:
2484 + playground.run_TestCase(test_case)
2485 + self.assertEqual(test_case.test_success, True,
2486 + test_case.fail_msg)
2487 + finally:
2488 + # Disable debug so that cleanup works.
2489 + playground.debug = False
2490 + playground.cleanup()
2491 diff --git a/pym/portage/tests/resolver/soname/test_slot_conflict_reinstall.py b/pym/portage/tests/resolver/soname/test_slot_conflict_reinstall.py
2492 new file mode 100644
2493 index 0000000..40e6995
2494 --- /dev/null
2495 +++ b/pym/portage/tests/resolver/soname/test_slot_conflict_reinstall.py
2496 @@ -0,0 +1,342 @@
2497 +# Copyright 2015 Gentoo Foundation
2498 +# Distributed under the terms of the GNU General Public License v2
2499 +
2500 +from portage.tests import TestCase
2501 +from portage.tests.resolver.ResolverPlayground import (
2502 + ResolverPlayground, ResolverPlaygroundTestCase)
2503 +
2504 +class SonameSlotConflictReinstallTestCase(TestCase):
2505 +
2506 + def testSonameSlotConflictReinstall(self):
2507 +
2508 + binpkgs = {
2509 +
2510 + "app-misc/A-1" : {
2511 + "PROVIDES": "x86_32: libA-1.so",
2512 + },
2513 +
2514 + "app-misc/A-2" : {
2515 + "PROVIDES": "x86_32: libA-2.so",
2516 + },
2517 +
2518 + "app-misc/B-0" : {
2519 + "DEPEND": "app-misc/A",
2520 + "RDEPEND": "app-misc/A",
2521 + "REQUIRES": "x86_32: libA-2.so",
2522 + },
2523 +
2524 + "app-misc/C-0" : {
2525 + "EAPI": "5",
2526 + "DEPEND": "<app-misc/A-2",
2527 + "RDEPEND": "<app-misc/A-2"
2528 + },
2529 +
2530 + "app-misc/D-1" : {
2531 + "PROVIDES": "x86_32: libD-1.so",
2532 + },
2533 +
2534 + "app-misc/D-2" : {
2535 + "PROVIDES": "x86_32: libD-2.so",
2536 + },
2537 +
2538 + "app-misc/E-0" : {
2539 + "DEPEND": "app-misc/D",
2540 + "RDEPEND": "app-misc/D",
2541 + "REQUIRES": "x86_32: libD-2.so",
2542 + },
2543 +
2544 + }
2545 +
2546 + installed = {
2547 +
2548 + "app-misc/A-1" : {
2549 + "PROVIDES": "x86_32: libA-1.so",
2550 + },
2551 +
2552 + "app-misc/B-0" : {
2553 + "DEPEND": "app-misc/A",
2554 + "RDEPEND": "app-misc/A",
2555 + "REQUIRES": "x86_32: libA-1.so",
2556 + },
2557 +
2558 + "app-misc/C-0" : {
2559 + "DEPEND": "<app-misc/A-2",
2560 + "RDEPEND": "<app-misc/A-2"
2561 + },
2562 +
2563 + "app-misc/D-1" : {
2564 + "PROVIDES": "x86_32: libD-1.so",
2565 + },
2566 +
2567 + "app-misc/E-0" : {
2568 + "DEPEND": "app-misc/D",
2569 + "RDEPEND": "app-misc/D",
2570 + "REQUIRES": "x86_32: libD-1.so",
2571 + },
2572 +
2573 + }
2574 +
2575 + world = ["app-misc/B", "app-misc/C", "app-misc/E"]
2576 +
2577 + test_cases = (
2578 +
2579 + # Test bug #439688, where a slot conflict prevents an
2580 + # upgrade and we don't want to trigger unnecessary rebuilds.
2581 + ResolverPlaygroundTestCase(
2582 + ["@world"],
2583 + options = {
2584 + "--deep": True,
2585 + "--ignore-soname-deps": "n",
2586 + "--update": True,
2587 + "--usepkgonly": True,
2588 + },
2589 + success = True,
2590 + mergelist = [
2591 + "[binary]app-misc/D-2",
2592 + "[binary]app-misc/E-0"
2593 + ]
2594 + ),
2595 +
2596 + )
2597 +
2598 + playground = ResolverPlayground(binpkgs=binpkgs,
2599 + installed=installed, world=world, debug=False)
2600 + try:
2601 + for test_case in test_cases:
2602 + playground.run_TestCase(test_case)
2603 + self.assertEqual(test_case.test_success,
2604 + True, test_case.fail_msg)
2605 + finally:
2606 + playground.debug = False
2607 + playground.cleanup()
2608 +
2609 + def testSonameSlotConflictMassRebuild(self):
2610 + """
2611 + Bug 486580
2612 + Before this bug was fixed, emerge would backtrack for each
2613 + package that needs a rebuild. This could cause it to hit the
2614 + backtrack limit and not rebuild all needed packages.
2615 + """
2616 + binpkgs = {
2617 +
2618 + "app-misc/A-1" : {
2619 + "DEPEND": "app-misc/B",
2620 + "RDEPEND": "app-misc/B",
2621 + "REQUIRES": "x86_32: libB-2.so",
2622 + },
2623 +
2624 + "app-misc/B-1" : {
2625 + "SLOT": "1",
2626 + "PROVIDES": "x86_32: libB-1.so",
2627 + },
2628 +
2629 + "app-misc/B-2" : {
2630 + "SLOT": "2",
2631 + "PROVIDES": "x86_32: libB-2.so",
2632 + },
2633 + }
2634 +
2635 + installed = {
2636 + "app-misc/B-1" : {
2637 + "SLOT": "1",
2638 + "PROVIDES": "x86_32: libB-1.so",
2639 + },
2640 + }
2641 +
2642 + expected_mergelist = [
2643 + '[binary]app-misc/A-1',
2644 + '[binary]app-misc/B-2'
2645 + ]
2646 +
2647 + for i in range(5):
2648 + binpkgs["app-misc/C%sC-1" % i] = {
2649 + "DEPEND": "app-misc/B",
2650 + "RDEPEND": "app-misc/B",
2651 + "REQUIRES": "x86_32: libB-2.so",
2652 + }
2653 +
2654 + installed["app-misc/C%sC-1" % i] = {
2655 + "DEPEND": "app-misc/B",
2656 + "RDEPEND": "app-misc/B",
2657 + "REQUIRES": "x86_32: libB-1.so",
2658 + }
2659 + for x in ("DEPEND", "RDEPEND"):
2660 + binpkgs["app-misc/A-1"][x] += " app-misc/C%sC" % i
2661 +
2662 + expected_mergelist.append("[binary]app-misc/C%sC-1" % i)
2663 +
2664 +
2665 + test_cases = (
2666 + ResolverPlaygroundTestCase(
2667 + ["app-misc/A"],
2668 + ignore_mergelist_order=True,
2669 + all_permutations=True,
2670 + options = {
2671 + "--backtrack": 3,
2672 + "--deep": True,
2673 + "--ignore-soname-deps": "n",
2674 + "--update": True,
2675 + "--usepkgonly": True,
2676 + },
2677 + success = True,
2678 + mergelist = expected_mergelist),
2679 + )
2680 +
2681 + world = []
2682 +
2683 + playground = ResolverPlayground(binpkgs=binpkgs,
2684 + installed=installed, world=world, debug=False)
2685 + try:
2686 + for test_case in test_cases:
2687 + playground.run_TestCase(test_case)
2688 + self.assertEqual(test_case.test_success,
2689 + True, test_case.fail_msg)
2690 + finally:
2691 + playground.debug = False
2692 + playground.cleanup()
2693 +
2694 + def testSonameSlotConflictForgottenChild(self):
2695 + """
2696 + Similar to testSonameSlotConflictMassRebuild above, but this
2697 + time the rebuilds are scheduled, but the package causing the
2698 + rebuild (the child) is not installed.
2699 + """
2700 + binpkgs = {
2701 +
2702 + "app-misc/A-2" : {
2703 + "DEPEND": "app-misc/B app-misc/C",
2704 + "RDEPEND": "app-misc/B app-misc/C",
2705 + "REQUIRES": "x86_32: libB-2.so",
2706 + },
2707 +
2708 + "app-misc/B-2" : {
2709 + "PROVIDES": "x86_32: libB-2.so",
2710 + "SLOT": "2",
2711 + },
2712 +
2713 + "app-misc/C-1": {
2714 + "DEPEND": "app-misc/B",
2715 + "RDEPEND": "app-misc/B",
2716 + "REQUIRES": "x86_32: libB-2.so",
2717 + },
2718 + }
2719 +
2720 + installed = {
2721 + "app-misc/A-1" : {
2722 + "DEPEND": "app-misc/B app-misc/C",
2723 + "RDEPEND": "app-misc/B app-misc/C",
2724 + "REQUIRES": "x86_32: libB-1.so",
2725 + },
2726 +
2727 + "app-misc/B-1" : {
2728 + "PROVIDES": "x86_32: libB-1.so",
2729 + "SLOT": "1",
2730 + },
2731 +
2732 + "app-misc/C-1": {
2733 + "DEPEND": "app-misc/B",
2734 + "RDEPEND": "app-misc/B",
2735 + "REQUIRES": "x86_32: libB-1.so",
2736 + },
2737 + }
2738 +
2739 + test_cases = (
2740 + ResolverPlaygroundTestCase(
2741 + ["app-misc/A"],
2742 + options = {
2743 + "--ignore-soname-deps": "n",
2744 + "--usepkgonly": True,
2745 + },
2746 + success = True,
2747 + mergelist = [
2748 + '[binary]app-misc/B-2',
2749 + '[binary]app-misc/C-1',
2750 + '[binary]app-misc/A-2',
2751 + ]
2752 + ),
2753 + )
2754 +
2755 + world = []
2756 +
2757 + playground = ResolverPlayground(binpkgs=binpkgs,
2758 + installed=installed, world=world, debug=False)
2759 + try:
2760 + for test_case in test_cases:
2761 + playground.run_TestCase(test_case)
2762 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
2763 + finally:
2764 + playground.debug = False
2765 + playground.cleanup()
2766 +
2767 + def testSonameSlotConflictMixedDependencies(self):
2768 + """
2769 + Bug 487198
2770 + For parents with mixed >= and < dependencies, we scheduled
2771 + reinstalls for the >= atom, but in the end didn't install the
2772 + child update because of the < atom.
2773 + """
2774 + binpkgs = {
2775 + "cat/slotted-lib-1" : {
2776 + "PROVIDES": "x86_32: lib1.so",
2777 + "SLOT": "1",
2778 + },
2779 + "cat/slotted-lib-2" : {
2780 + "PROVIDES": "x86_32: lib2.so",
2781 + "SLOT": "2",
2782 + },
2783 + "cat/slotted-lib-3" : {
2784 + "PROVIDES": "x86_32: lib3.so",
2785 + "SLOT": "3",
2786 + },
2787 + "cat/slotted-lib-4" : {
2788 + "PROVIDES": "x86_32: lib4.so",
2789 + "SLOT": "4",
2790 + },
2791 + "cat/slotted-lib-5" : {
2792 + "PROVIDES": "x86_32: lib5.so",
2793 + "SLOT": "5",
2794 + },
2795 + "cat/user-1" : {
2796 + "DEPEND": ">=cat/slotted-lib-2 <cat/slotted-lib-4",
2797 + "RDEPEND": ">=cat/slotted-lib-2 <cat/slotted-lib-4",
2798 + "REQUIRES": "x86_32: lib3.so",
2799 + },
2800 + }
2801 +
2802 + installed = {
2803 + "cat/slotted-lib-3" : {
2804 + "PROVIDES": "x86_32: lib3.so",
2805 + "SLOT": "3",
2806 + },
2807 + "cat/user-1" : {
2808 + "DEPEND": ">=cat/slotted-lib-2 <cat/slotted-lib-4",
2809 + "RDEPEND": ">=cat/slotted-lib-2 <cat/slotted-lib-4",
2810 + "REQUIRES": "x86_32: lib3.so",
2811 + },
2812 + }
2813 +
2814 + test_cases = (
2815 + ResolverPlaygroundTestCase(
2816 + ["cat/user"],
2817 + options = {
2818 + "--deep": True,
2819 + "--ignore-soname-deps": "n",
2820 + "--update": True,
2821 + "--usepkgonly": True,
2822 + },
2823 + success = True,
2824 + mergelist = []),
2825 + )
2826 +
2827 + world = []
2828 +
2829 + playground = ResolverPlayground(binpkgs=binpkgs,
2830 + installed=installed, world=world, debug=False)
2831 + try:
2832 + for test_case in test_cases:
2833 + playground.run_TestCase(test_case)
2834 + self.assertEqual(test_case.test_success,
2835 + True, test_case.fail_msg)
2836 + finally:
2837 + playground.debug = False
2838 + playground.cleanup()
2839 diff --git a/pym/portage/tests/resolver/soname/test_slot_conflict_update.py b/pym/portage/tests/resolver/soname/test_slot_conflict_update.py
2840 new file mode 100644
2841 index 0000000..c607496
2842 --- /dev/null
2843 +++ b/pym/portage/tests/resolver/soname/test_slot_conflict_update.py
2844 @@ -0,0 +1,117 @@
2845 +# Copyright 2015 Gentoo Foundation
2846 +# Distributed under the terms of the GNU General Public License v2
2847 +
2848 +from portage.tests import TestCase
2849 +from portage.tests.resolver.ResolverPlayground import (
2850 + ResolverPlayground, ResolverPlaygroundTestCase)
2851 +
2852 +class SonameSlotConflictUpdateTestCase(TestCase):
2853 +
2854 + def testSonameSlotConflictUpdate(self):
2855 +
2856 + binpkgs = {
2857 +
2858 + "app-text/podofo-0.9.2" : {
2859 + "RDEPEND" : "dev-util/boost-build",
2860 + },
2861 +
2862 + "dev-cpp/libcmis-0.3.1" : {
2863 + "DEPEND": "dev-libs/boost",
2864 + "RDEPEND": "dev-libs/boost",
2865 + "REQUIRES": "x86_32: libboost-1.53.so",
2866 + },
2867 +
2868 + "dev-libs/boost-1.53.0" : {
2869 + "PROVIDES": "x86_32: libboost-1.53.so",
2870 + "RDEPEND" : "=dev-util/boost-build-1.53.0",
2871 + },
2872 +
2873 + "dev-libs/boost-1.52.0" : {
2874 + "PROVIDES": "x86_32: libboost-1.52.so",
2875 + "RDEPEND" : "=dev-util/boost-build-1.52.0",
2876 + },
2877 +
2878 + "dev-util/boost-build-1.53.0" : {
2879 + },
2880 +
2881 + "dev-util/boost-build-1.52.0" : {
2882 + },
2883 +
2884 +
2885 + }
2886 +
2887 + installed = {
2888 +
2889 + "app-text/podofo-0.9.2" : {
2890 + "RDEPEND" : "dev-util/boost-build",
2891 + },
2892 +
2893 + "dev-cpp/libcmis-0.3.1" : {
2894 + "DEPEND": "dev-libs/boost",
2895 + "RDEPEND": "dev-libs/boost",
2896 + "REQUIRES": "x86_32: libboost-1.52.so",
2897 + },
2898 +
2899 + "dev-util/boost-build-1.52.0" : {
2900 + },
2901 +
2902 + "dev-libs/boost-1.52.0" : {
2903 + "PROVIDES": "x86_32: libboost-1.52.so",
2904 + "RDEPEND" : "=dev-util/boost-build-1.52.0",
2905 + },
2906 +
2907 + }
2908 +
2909 + world = [
2910 + "dev-cpp/libcmis",
2911 + "dev-libs/boost",
2912 + "app-text/podofo",
2913 + ]
2914 +
2915 + test_cases = (
2916 +
2917 + ResolverPlaygroundTestCase(
2918 + world,
2919 + all_permutations = True,
2920 + options = {
2921 + "--deep": True,
2922 + "--ignore-soname-deps": "n",
2923 + "--update": True,
2924 + "--usepkgonly": True,
2925 + },
2926 + success = True,
2927 + mergelist = [
2928 + '[binary]dev-util/boost-build-1.53.0',
2929 + '[binary]dev-libs/boost-1.53.0',
2930 + '[binary]dev-cpp/libcmis-0.3.1'
2931 + ]
2932 + ),
2933 +
2934 + ResolverPlaygroundTestCase(
2935 + world,
2936 + all_permutations = True,
2937 + options = {
2938 + "--deep": True,
2939 + "--ignore-soname-deps": "y",
2940 + "--update": True,
2941 + "--usepkgonly": True,
2942 + },
2943 + success = True,
2944 + mergelist = [
2945 + '[binary]dev-util/boost-build-1.53.0',
2946 + '[binary]dev-libs/boost-1.53.0',
2947 + ]
2948 + ),
2949 +
2950 + )
2951 +
2952 + playground = ResolverPlayground(binpkgs=binpkgs,
2953 + installed=installed, world=world, debug=False)
2954 + try:
2955 + for test_case in test_cases:
2956 + playground.run_TestCase(test_case)
2957 + self.assertEqual(test_case.test_success,
2958 + True, test_case.fail_msg)
2959 + finally:
2960 + playground.debug = False
2961 + playground.cleanup()
2962 diff --git a/pym/portage/tests/resolver/soname/test_soname_provided.py b/pym/portage/tests/resolver/soname/test_soname_provided.py
2963 new file mode 100644
2964 index 0000000..162da47
2965 --- /dev/null
2966 +++ b/pym/portage/tests/resolver/soname/test_soname_provided.py
2967 @@ -0,0 +1,78 @@
2968 +# Copyright 2015 Gentoo Foundation
2969 +# Distributed under the terms of the GNU General Public License v2
2970 +
2971 +from portage.tests import TestCase
2972 +from portage.tests.resolver.ResolverPlayground import (
2973 + ResolverPlayground, ResolverPlaygroundTestCase)
2974 +
2975 +class SonameProvidedTestCase(TestCase):
2976 +
2977 + def testSonameProvided(self):
2978 +
2979 + binpkgs = {
2980 + "app-misc/A-1" : {
2981 + "EAPI": "5",
2982 + "PROVIDES": "x86_32: libA.so.1",
2983 + },
2984 + "app-misc/B-1" : {
2985 + "DEPEND": "app-misc/A",
2986 + "RDEPEND": "app-misc/A",
2987 + "REQUIRES": "x86_32: libA.so.2",
2988 + },
2989 + "app-misc/B-0" : {
2990 + "DEPEND": "app-misc/A",
2991 + "RDEPEND": "app-misc/A",
2992 + "REQUIRES": "x86_32: libA.so.1",
2993 + },
2994 + }
2995 +
2996 + installed = {
2997 + "app-misc/A-1" : {
2998 + "EAPI": "5",
2999 + "PROVIDES": "x86_32: libA.so.1",
3000 + },
3001 +
3002 + "app-misc/B-0" : {
3003 + "DEPEND": "app-misc/A",
3004 + "RDEPEND": "app-misc/A",
3005 + "REQUIRES": "x86_32: libA.so.1",
3006 + },
3007 + }
3008 +
3009 + world = ["app-misc/B"]
3010 +
3011 + profile = {
3012 + "soname.provided": (
3013 + "x86_32 libA.so.2",
3014 + ),
3015 + }
3016 +
3017 + test_cases = (
3018 +
3019 + # Allow update due to soname dependency satisfied by
3020 + # soname.provided.
3021 + ResolverPlaygroundTestCase(
3022 + ["@world"],
3023 + options = {
3024 + "--deep": True,
3025 + "--ignore-soname-deps": "n",
3026 + "--update": True,
3027 + "--usepkgonly": True,
3028 + },
3029 + success = True,
3030 + mergelist = ["[binary]app-misc/B-1"],
3031 + ),
3032 +
3033 + )
3034 +
3035 + playground = ResolverPlayground(binpkgs=binpkgs, debug=False,
3036 + profile=profile, installed=installed, world=world)
3037 + try:
3038 + for test_case in test_cases:
3039 + playground.run_TestCase(test_case)
3040 + self.assertEqual(
3041 + test_case.test_success, True, test_case.fail_msg)
3042 + finally:
3043 + # Disable debug so that cleanup works.
3044 + playground.debug = False
3045 + playground.cleanup()
3046 diff --git a/pym/portage/tests/resolver/soname/test_unsatisfiable.py b/pym/portage/tests/resolver/soname/test_unsatisfiable.py
3047 new file mode 100644
3048 index 0000000..039a9df
3049 --- /dev/null
3050 +++ b/pym/portage/tests/resolver/soname/test_unsatisfiable.py
3051 @@ -0,0 +1,71 @@
3052 +# Copyright 2015 Gentoo Foundation
3053 +# Distributed under the terms of the GNU General Public License v2
3054 +
3055 +from portage.tests import TestCase
3056 +from portage.tests.resolver.ResolverPlayground import (
3057 + ResolverPlayground, ResolverPlaygroundTestCase)
3058 +
3059 +class SonameUnsatisfiableTestCase(TestCase):
3060 +
3061 + def testSonameUnsatisfiable(self):
3062 +
3063 + binpkgs = {
3064 + "app-misc/A-1" : {
3065 + "EAPI": "5",
3066 + "PROVIDES": "x86_32: libA.so.1",
3067 + },
3068 + "app-misc/B-1" : {
3069 + "DEPEND": "app-misc/A",
3070 + "RDEPEND": "app-misc/A",
3071 + "REQUIRES": "x86_32: libA.so.2",
3072 + },
3073 + "app-misc/B-0" : {
3074 + "DEPEND": "app-misc/A",
3075 + "RDEPEND": "app-misc/A",
3076 + "REQUIRES": "x86_32: libA.so.1",
3077 + },
3078 + }
3079 +
3080 + installed = {
3081 + "app-misc/A-1" : {
3082 + "EAPI": "5",
3083 + "PROVIDES": "x86_32: libA.so.1",
3084 + },
3085 +
3086 + "app-misc/B-0" : {
3087 + "DEPEND": "app-misc/A",
3088 + "RDEPEND": "app-misc/A",
3089 + "REQUIRES": "x86_32: libA.so.1",
3090 + },
3091 + }
3092 +
3093 + world = ["app-misc/B"]
3094 +
3095 + test_cases = (
3096 +
3097 + # Skip update due to unsatisfied soname dependency.
3098 + ResolverPlaygroundTestCase(
3099 + ["@world"],
3100 + options = {
3101 + "--deep": True,
3102 + "--ignore-soname-deps": "n",
3103 + "--update": True,
3104 + "--usepkgonly": True,
3105 + },
3106 + success = True,
3107 + mergelist = [],
3108 + ),
3109 +
3110 + )
3111 +
3112 + playground = ResolverPlayground(binpkgs=binpkgs, debug=False,
3113 + installed=installed, world=world)
3114 + try:
3115 + for test_case in test_cases:
3116 + playground.run_TestCase(test_case)
3117 + self.assertEqual(
3118 + test_case.test_success, True, test_case.fail_msg)
3119 + finally:
3120 + # Disable debug so that cleanup works.
3121 + playground.debug = False
3122 + playground.cleanup()
3123 diff --git a/pym/portage/tests/resolver/soname/test_unsatisfied.py b/pym/portage/tests/resolver/soname/test_unsatisfied.py
3124 new file mode 100644
3125 index 0000000..27cdcc4
3126 --- /dev/null
3127 +++ b/pym/portage/tests/resolver/soname/test_unsatisfied.py
3128 @@ -0,0 +1,87 @@
3129 +# Copyright 2015 Gentoo Foundation
3130 +# Distributed under the terms of the GNU General Public License v2
3131 +
3132 +from portage.tests import TestCase
3133 +from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
3134 + ResolverPlaygroundTestCase)
3135 +
3136 +class SonameUnsatisfiedTestCase(TestCase):
3137 +
3138 + def testSonameUnsatisfied(self):
3139 +
3140 + binpkgs = {
3141 + "app-misc/A-1" : {
3142 + "EAPI": "5",
3143 + "PROVIDES": "x86_32: libA.so.1",
3144 + },
3145 + "app-misc/A-2" : {
3146 + "EAPI": "5",
3147 + "PROVIDES": "x86_32: libA.so.2",
3148 + },
3149 + "app-misc/B-0" : {
3150 + "DEPEND": "app-misc/A",
3151 + "RDEPEND": "app-misc/A",
3152 + "REQUIRES": "x86_32: libA.so.2",
3153 + }
3154 + }
3155 +
3156 + installed = {
3157 + "app-misc/A-2" : {
3158 + "EAPI": "5",
3159 + "PROVIDES": "x86_32: libA.so.2",
3160 + },
3161 +
3162 + "app-misc/B-0" : {
3163 + "DEPEND": "app-misc/A",
3164 + "RDEPEND": "app-misc/A",
3165 + "REQUIRES": "x86_32: libA.so.1",
3166 + }
3167 + }
3168 +
3169 + world = ["app-misc/B"]
3170 +
3171 + test_cases = (
3172 +
3173 + # Demonstrate bug #439694, where a broken
3174 + # soname dependency needs to trigger a reinstall.
3175 + ResolverPlaygroundTestCase(
3176 + ["@world"],
3177 + options = {
3178 + "--deep": True,
3179 + "--ignore-soname-deps": "n",
3180 + "--update": True,
3181 + "--usepkgonly": True,
3182 + },
3183 + success = True,
3184 + mergelist = [
3185 + "[binary]app-misc/B-0"
3186 + ]
3187 + ),
3188 +
3189 + # This doesn't trigger a reinstall, since there's no version
3190 + # change to trigger complete graph mode, and initially
3191 + # unsatisfied deps are ignored in complete graph mode anyway.
3192 + ResolverPlaygroundTestCase(
3193 + ["app-misc/A"],
3194 + options = {
3195 + "--ignore-soname-deps": "n",
3196 + "--oneshot": True,
3197 + "--usepkgonly": True,
3198 + },
3199 + success = True,
3200 + mergelist = [
3201 + "[binary]app-misc/A-2"
3202 + ]
3203 + ),
3204 + )
3205 +
3206 + playground = ResolverPlayground(binpkgs=binpkgs, debug=False,
3207 + installed=installed, world=world)
3208 + try:
3209 + for test_case in test_cases:
3210 + playground.run_TestCase(test_case)
3211 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
3212 + finally:
3213 + # Disable debug so that cleanup works.
3214 + playground.debug = False
3215 + playground.cleanup()
3216 diff --git a/pym/portage/tests/resolver/test_package_tracker.py b/pym/portage/tests/resolver/test_package_tracker.py
3217 index 8fa3513..468c3d8 100644
3218 --- a/pym/portage/tests/resolver/test_package_tracker.py
3219 +++ b/pym/portage/tests/resolver/test_package_tracker.py
3220 @@ -116,11 +116,11 @@ class PackageTrackerTestCase(TestCase):
3221 if pkg.root == "/" and pkg.cp == x_atom:
3222 self.assertTrue(pkg in matches)
3223 self.assertTrue(not dbapi.cp_list(y_atom))
3224 - matches = dbapi.match(x_atom)
3225 + matches = dbapi.match(Atom(x_atom))
3226 for pkg in pkgs:
3227 if pkg.root == "/" and pkg.cp == x_atom:
3228 self.assertTrue(pkg in matches)
3229 - self.assertTrue(not dbapi.match(y_atom))
3230 + self.assertTrue(not dbapi.match(Atom(y_atom)))
3231
3232 check_dbapi([])
3233
3234 --
3235 2.0.5

Replies