commit:     6bcdbd769bfa8ebf78e31887e4fd54eaf1c47032
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Sun Dec 25 23:23:15 2022 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Mon Dec 26 17:27:24 2022 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/pkgcore.git/commit/?id=6bcdbd76

Add USE_EXPAND expansion awareness for /etc/portage/package.use/* files.

Specifically, if you have:
`*/* PYTHON_TARGETS: -python2_7 python3_9`

Pkgcore was treating `PYTHON_TARGETS:` as a use flag.  That's obviously
wrong, and the parsing should be tightened there.

Closes: https://github.com/pkgcore/pkgcore/issues/384
Signed-off-by: Brian Harring <ferringb <AT> gmail.com>
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 src/pkgcore/ebuild/domain.py | 44 ++++++++++++++++++++++++++++++++++++++------
 tests/ebuild/test_domain.py  | 28 ++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/src/pkgcore/ebuild/domain.py b/src/pkgcore/ebuild/domain.py
index 7e76f1112..6f86f7d14 100644
--- a/src/pkgcore/ebuild/domain.py
+++ b/src/pkgcore/ebuild/domain.py
@@ -66,15 +66,47 @@ def package_masks(iterable):
             logger.warning(f"{path!r}, line {lineno}: parsing error: {e}")
 
 
-def package_keywords_splitter(iterable):
+def restriction_payload_splitter(iterable, post_process=lambda x: x):
     for line, lineno, path in iterable:
         v = line.split()
         try:
-            yield parse_match(v[0]), tuple(v[1:]), line, lineno, path
+            # TODO: expand this invocation to allow threading token level 
validation down.
+            # things like "is this a valid use flag?"
+            yield parse_match(v[0]), tuple(post_process(v[1:])), line, lineno, 
path
         except ParseError as e:
             logger.warning(f"{path!r}, line {lineno}: parsing error: {e}")
 
 
+def package_use_splitter(iterable):
+    """Parse package.use user configuration files
+
+    Basic syntax is <query> (?:-?use_f )* (?:USE_EXPAND: (?:flags)*)
+    IE, a restriction to match, use flags to turn on.  If a 'flag' ends in ':'
+    then it's considered a USE_EXPAND directive, and all that follow are 
values of that
+    USE_EXPAND target and should be expanded into their normalized/long form.
+    """
+
+    def f(tokens: list[str]):
+        i = iter(tokens)
+        for idx, x in enumerate(i):
+            if x.endswith(":"):
+                # we encountered `USE_EXPAND:` , thus all following tokens
+                # are values of that.
+                x = x.lower()[:-1]
+                l = tokens[0:idx]
+                for flag in i:
+                    if flag.startswith("-"):
+                        flag = f"-{x}_{flag[1:]}"
+                    else:
+                        flag = f"{x}_{flag}"
+                    l.append(flag)
+                return l
+        # if we made it here, there's no USE_EXPAND; thus just return the 
original sequence
+        return tokens
+
+    return restriction_payload_splitter(iterable, post_process=f)
+
+
 def package_env_splitter(basedir, iterable):
     for line, lineno, path in iterable:
         val = line.split()
@@ -396,25 +428,25 @@ class domain(config_domain):
         return tuple(x[0] for x in data)
 
     # TODO: deprecated, remove in 0.11
-    @load_property("package.keywords", parse_func=package_keywords_splitter)
+    @load_property("package.keywords", parse_func=restriction_payload_splitter)
     def pkg_keywords(self, data, debug=False):
         if debug:
             return tuple(data)
         return tuple((x[0], stable_unique(x[1])) for x in data)
 
-    @load_property("package.accept_keywords", 
parse_func=package_keywords_splitter)
+    @load_property("package.accept_keywords", 
parse_func=restriction_payload_splitter)
     def pkg_accept_keywords(self, data, debug=False):
         if debug:
             return tuple(data)
         return tuple((x[0], stable_unique(x[1])) for x in data)
 
-    @load_property("package.license", parse_func=package_keywords_splitter)
+    @load_property("package.license", parse_func=restriction_payload_splitter)
     def pkg_licenses(self, data, debug=False):
         if debug:
             return tuple(data)
         return tuple((x[0], stable_unique(x[1])) for x in data)
 
-    @load_property("package.use", parse_func=package_keywords_splitter)
+    @load_property("package.use", parse_func=package_use_splitter)
     def pkg_use(self, data, debug=False):
         if debug:
             return tuple(data)

diff --git a/tests/ebuild/test_domain.py b/tests/ebuild/test_domain.py
index 6629cfb9a..87c3d489f 100644
--- a/tests/ebuild/test_domain.py
+++ b/tests/ebuild/test_domain.py
@@ -1,3 +1,4 @@
+import textwrap
 from unittest import mock
 
 import pytest
@@ -54,3 +55,30 @@ class TestDomain:
                 (packages.AlwaysTrue, ((), ("X",))),
                 (packages.AlwaysTrue, (("X",), ("Y",))),
             ) == self.mk_domain().pkg_use
+
+    def test_use_expand_syntax(self):
+        puse = self.confdir / "package.use"
+        puse.mkdir()
+        open(puse / "a", "w").write(
+            textwrap.dedent(
+                """
+                */* x_y1
+                # unrelated is there to verify that it's unaffected by the 
USE_EXPAND
+                */* unrelated X: -y1 y2
+                """
+            )
+        )
+
+        assert (
+            (packages.AlwaysTrue, ((), ("x_y1",))),
+            (
+                packages.AlwaysTrue,
+                (
+                    ("x_y1",),
+                    (
+                        "unrelated",
+                        "x_y2",
+                    ),
+                ),
+            ),
+        ) == self.mk_domain().pkg_use

Reply via email to