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},)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.