proj/pkgcore/pkgcore:master commit in: /, src/pkgcore/ebuild/, tests/ebuild/, tests/restrictions/
"Arthur Zamarin" <[email protected]>
| Newsgroups | gmane.linux.gentoo.cvs |
|---|---|
| Message-ID | <1786088049.64f1319f0bb147d61cb080f07acd5f416d9c680e.arthurzam@gentoo> |
commit: 64f1319f0bb147d61cb080f07acd5f416d9c680e
Author: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
AuthorDate: Fri Aug 7 07:31:59 2026 +0000
Commit: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Fri Aug 7 07:34:09 2026 +0000
URL: https://gitweb.gentoo.org/proj/pkgcore/pkgcore.git/commit/?id=64f1319f
conditionals: finalize the empty DepSet default
DepSet.__init__ defaulted restrictions to "", so every empty depset -
what ebuild_src returns for a package lacking REQUIRED_USE, BDEPEND or
IDEPEND - held a str rather than a tuple. boolean.base.__hash__ reads
that as "not finalized" and raises TypeError, making those depsets
permanently unhashable and reporting finalized=False in their repr.
Nothing hashed them until 0.12.37 put an lru_cache on the compiled
REQUIRED_USE constraints, keyed on the restriction. From then on
find_constraint_satisfaction() blew up on any package without a
REQUIRED_USE, which broke `pkgdev tatt` for most of the tree:
File "pkgcore/restrictions/boolean.py", line 37, in __hash__
raise TypeError(f"{self!r} isn't finalized")
TypeError: <DepSet negate=False type='package' finalized=False
restrictions='' ...> isn't finalized
Default to an empty tuple instead. str(), len(), iteration and __eq__
(which compares set(restrictions)) are unchanged for the empty case, so
the only difference is that the depset is now finalized, hence hashable.
Reported-by: Eli Schwartz <eschwartz <AT> gentoo.org>
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>
NEWS.rst | 15 +++++++++++++++
src/pkgcore/ebuild/conditionals.py | 2 +-
tests/ebuild/test_conditionals.py | 5 +++++
tests/restrictions/test_required_use.py | 9 +++++++++
4 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/NEWS.rst b/NEWS.rst
index cbbeb9450..6cdf28118 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -2,6 +2,21 @@
Release Notes
=============
+----------------------------
+pkgcore 0.12.38 (unreleased)
+----------------------------
+
+Fixes
+~~~~~
+
+- ``pkgcore.ebuild.conditionals``: the empty depset returned for packages
+ lacking ``REQUIRED_USE``, ``BDEPEND`` or ``IDEPEND`` is now finalized, and
+ thus hashable again. Since caching the compiled REQUIRED_USE constraints in
+ 0.12.37 requires hashing the depset, this broke ``pkgdev tatt`` with a
+ ``TypeError`` for every package without ``REQUIRED_USE``
+ (Arthur Zamarin)
+
+
----------------------------
pkgcore 0.12.37 (2026-07-31)
----------------------------
diff --git a/src/pkgcore/ebuild/conditionals.py b/src/pkgcore/ebuild/conditionals.py
index 201fa9387..166a10866 100644
--- a/src/pkgcore/ebuild/conditionals.py
+++ b/src/pkgcore/ebuild/conditionals.py
@@ -30,7 +30,7 @@ class DepSet(boolean.AndRestriction, caching=False):
def __init__(
self,
- restrictions="",
+ restrictions=(),
element_class=atom,
node_conds=True,
known_conditionals=None,
diff --git a/tests/ebuild/test_conditionals.py b/tests/ebuild/test_conditionals.py
index f310f9c59..cc62fa9d5 100644
--- a/tests/ebuild/test_conditionals.py
+++ b/tests/ebuild/test_conditionals.py
@@ -220,6 +220,11 @@ class TestDepSetParsing(base):
def test_atom_interaction(self):
self.gen_depset("a/b[x(+)]", element_func=atom)
+ def test_empty_depset(self):
+ # an empty depset must be finalized, thus hashable
+ assert not conditionals.DepSet().restrictions
+ assert hash(conditionals.DepSet()) == hash(conditionals.DepSet())
+
class TestDepSetConditionalsInspection(base):
def test_sanity_has_conditionals(self):
diff --git a/tests/restrictions/test_required_use.py b/tests/restrictions/test_required_use.py
index 84168a21d..46d339402 100644
--- a/tests/restrictions/test_required_use.py
+++ b/tests/restrictions/test_required_use.py
@@ -19,6 +19,15 @@ def test_simple():
assert tuple(solver(required_use, {"bar", "foo"})) == ({"bar": True, "foo": True},)
+def test_empty():
+ # packages without REQUIRED_USE get an empty depset, which the constraint
+ # cache must still be able to key on
+ required_use = parse(required_use="")
+ solutions = tuple(solver(required_use, {"bar", "foo"}))
+ assert len(solutions) == 4
+ assert {"bar": True, "foo": True} in solutions
+
+
def test_negative_simple():
required_use = parse(required_use="!bar foo")
assert tuple(solver(required_use, {"bar", "foo"})) == ({"bar": False, "foo": True},)