proj/portage:master commit in: lib/portage/tests/dep/, lib/portage/dep/
"Matt Turner" <[email protected]>
| Newsgroups | gmane.linux.gentoo.cvs |
|---|---|
| Message-ID | <1785963928.600c6bc6c2ce8ddf650579b0790a1faadae0d9c5.mattst88@gentoo> |
commit: 600c6bc6c2ce8ddf650579b0790a1faadae0d9c5
Author: Matt Turner <mattst88 <AT> gentoo <DOT> org>
AuthorDate: Sun Aug 2 20:19:03 2026 +0000
Commit: Matt Turner <mattst88 <AT> gentoo <DOT> org>
CommitDate: Wed Aug 5 21:05:28 2026 +0000
URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=600c6bc6
dep: extend C parser fast path to flat=True
The use_reduce fast path previously excluded flat=True, so the
_flatten_atoms consumer (and any other flat caller) stayed on the
pure-Python path. Support it by flattening the C parse tree.
The C parser preserves the raw group/any-of structure with USE
conditionals already evaluated and does not apply the non-flat
bracket/|| optimizations, so flat mode is a straight recursive flatten
of that tree: inline every sublist while keeping operator strings ("||")
and Atoms in order. This reproduces use_reduce(flat=True) exactly,
including the repeated "||" tokens that nested any-of groups produce in
flat mode (which the non-flat path collapses).
Adds flat parity tests (groups, nested ||, active/inactive conditionals,
matchall, mixed) and a differential check over varied dep strings
confirms C and Python flat output are identical.
Profiled cProfile emerge -uDNp @world: _c_fast_use_reduce 34820 ->
39032 calls (the ~4.2k flat callers), _use_reduce_cached 64680 -> 61502.
The remaining Python-path use_reduce calls are the is_valid_flag
_select_atoms path, which the C fast path cannot serve.
Signed-off-by: Matt Turner <mattst88 <AT> gentoo.org>
lib/portage/dep/__init__.py | 20 +++++++++--
lib/portage/tests/dep/test_c_parser.py | 66 ++++++++++++++++++++++++++++++++--
2 files changed, 80 insertions(+), 6 deletions(-)
diff --git a/lib/portage/dep/__init__.py b/lib/portage/dep/__init__.py
index 07c270e65..ef345e807 100644
--- a/lib/portage/dep/__init__.py
+++ b/lib/portage/dep/__init__.py
@@ -200,9 +200,24 @@ def _c_normalize_seq(seq, empty_always_true, under_anyof=False):
return out
-def _c_fast_use_reduce(depstr, uselist, matchall, eapi):
+def _c_flatten_result(items, out, eapi, eapi_attrs, uselist, matchall):
+ """Recursively flatten the C parse tree into use_reduce(flat=True) form.
+ Nested || groups produce repeated '||' tokens, matching the Python path."""
+ for item in items:
+ if isinstance(item, str):
+ out.append(item)
+ elif isinstance(item, list):
+ _c_flatten_result(item, out, eapi, eapi_attrs, uselist, matchall)
+ else:
+ out.append(_c_atom_from_c(item, eapi, eapi_attrs, uselist, matchall))
+ return out
+
+
+def _c_fast_use_reduce(depstr, uselist, matchall, eapi, flat):
raw = _c_dep_parser.parse(depstr, uselist=uselist, matchall=matchall)
eapi_attrs = _get_eapi_attrs(eapi)
+ if flat:
+ return _c_flatten_result(raw, [], eapi, eapi_attrs, uselist, matchall)
result = _c_convert_result(raw, eapi, eapi_attrs, uselist, matchall)
return _c_normalize_seq(result, eapi_attrs.empty_groups_always_true)
@@ -1109,7 +1124,6 @@ def use_reduce(
and token_class is Atom
and not is_src_uri
and not opconvert
- and not flat
and is_valid_flag is None
and subset is None
and not matchnone
@@ -1119,7 +1133,7 @@ def use_reduce(
and (eapi is None or _get_eapi_attrs(eapi).slot_operator)
):
try:
- return _c_fast_use_reduce(depstr, uselist, matchall, eapi)
+ return _c_fast_use_reduce(depstr, uselist, matchall, eapi, flat)
except ValueError as e:
raise InvalidDependString(str(e)) from e
diff --git a/lib/portage/tests/dep/test_c_parser.py b/lib/portage/tests/dep/test_c_parser.py
index 765890452..3e91bd809 100644
--- a/lib/portage/tests/dep/test_c_parser.py
+++ b/lib/portage/tests/dep/test_c_parser.py
@@ -607,13 +607,19 @@ class TestCFastVsPython(TestCase):
if _orig_c_dep_parser is None:
self.skipTest("_parser extension not available")
- def _compare(self, depstr, uselist=None, matchall=False, eapi="8"):
- kw = dict(token_class=Atom, matchall=matchall, eapi=eapi, uselist=uselist or [])
+ def _compare(self, depstr, uselist=None, matchall=False, eapi="8", flat=False):
+ kw = dict(
+ token_class=Atom,
+ matchall=matchall,
+ eapi=eapi,
+ uselist=uselist or [],
+ flat=flat,
+ )
with _use_c_parser(False):
py_result = use_reduce(depstr, **kw)
c_result = use_reduce(depstr, **kw)
ok, msg = _result_equal(c_result, py_result)
- self.assertTrue(ok, f"{depstr!r}: {msg}")
+ self.assertTrue(ok, f"{depstr!r} (flat={flat}): {msg}")
def test_simple_atom(self):
self._compare("dev-libs/foo")
@@ -758,6 +764,60 @@ class TestCFastVsPython(TestCase):
self._compare("|| ( foo? ( dev-libs/a ) )", uselist=[], eapi="8")
self._compare("|| ( foo? ( dev-libs/a ) )", uselist=[], eapi="6")
+ def test_flat_simple(self):
+ self._compare("dev-libs/a dev-libs/b", matchall=True, flat=True)
+
+ def test_flat_or_group(self):
+ self._compare("|| ( dev-libs/a dev-libs/b )", matchall=True, flat=True)
+
+ def test_flat_nested_or(self):
+ # Nested any-of groups keep a '||' token per level in flat mode.
+ self._compare(
+ "|| ( dev-libs/a || ( dev-libs/b dev-libs/c ) )",
+ matchall=True,
+ flat=True,
+ )
+
+ def test_flat_use_conditional_active(self):
+ self._compare("foo? ( dev-libs/a dev-libs/b )", uselist=["foo"], flat=True)
+
+ def test_flat_use_conditional_inactive(self):
+ self._compare("foo? ( dev-libs/a dev-libs/b )", uselist=[], flat=True)
+
+ def test_flat_nested_conditionals(self):
+ self._compare(
+ "a? ( dev-libs/a b? ( dev-libs/b ) ) c? ( dev-libs/c )",
+ uselist=["a", "c"],
+ flat=True,
+ )
+
+ def test_flat_conditional_or_mix(self):
+ self._compare(
+ "foo? ( || ( dev-libs/a dev-libs/b ) ) dev-libs/c",
+ uselist=["foo"],
+ flat=True,
+ )
+
+ def test_flat_atoms_with_use(self):
+ self._compare(
+ "dev-libs/a[x] foo? ( =dev-libs/b-1[y?] )", uselist=["foo"], flat=True
+ )
+
+ def test_flat_matchall(self):
+ self._compare(
+ "a? ( dev-libs/a ) !b? ( dev-libs/b ) || ( dev-libs/c dev-libs/d )",
+ matchall=True,
+ flat=True,
+ )
+
+ def test_flat_complex(self):
+ self._compare(
+ "dev-libs/A >=dev-libs/B-2.0 || ( dev-libs/C dev-libs/D ) "
+ "foo? ( =dev-libs/E-1.0:0[bar,baz?] || ( dev-libs/F dev-libs/G ) )",
+ uselist=["foo"],
+ flat=True,
+ )
+
def test_invalid_raises_same_exception(self):
bad_cases = [
"|| ( dev-libs/a dev-libs/b",