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

Replies