commit:     f2d4e371da2bfa834644973d4dd0bede50deafb4
Author:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
AuthorDate: Sat Jan 13 09:16:10 2024 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Sat Jan 13 09:16:10 2024 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/pkgcheck.git/commit/?id=f2d4e371

UnstatedIuse: check for unstated IUSE in "?" dependencies

Check for cases where a dependency uses conditional use dependency
with unknown USE flag. PMS states:

  It is an error for an ebuild to use a conditional use dependency when
  that ebuild does not have the flag in IUSE_EFFECTIVE.

Bug: https://bugs.gentoo.org/921841
Requested-by: Sam James <sam <AT> gentoo.org>
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 src/pkgcheck/checks/metadata.py                    | 33 ++++++++++++++--------
 .../DependencyCheck/MissingUseDepDefault/fix.patch |  4 +--
 .../UnstatedIuse/expected-verbose.json             |  1 +
 .../DependencyCheck/UnstatedIuse/expected.json     |  1 +
 .../MissingUseDepDefault-0.ebuild                  |  1 +
 .../MissingUseDepDefault/metadata.xml              |  7 +++++
 .../UnstatedIuse/UnstatedIuse-0.ebuild             |  3 ++
 tests/checks/test_metadata.py                      |  2 +-
 8 files changed, 37 insertions(+), 15 deletions(-)

diff --git a/src/pkgcheck/checks/metadata.py b/src/pkgcheck/checks/metadata.py
index 2aba62f0..2cbb8985 100644
--- a/src/pkgcheck/checks/metadata.py
+++ b/src/pkgcheck/checks/metadata.py
@@ -7,8 +7,8 @@ from difflib import SequenceMatcher
 from functools import partial
 from operator import attrgetter
 
-from pkgcore.ebuild import atom as atom_mod, restricts
-from pkgcore.ebuild.atom import atom as atom_cls
+from pkgcore.ebuild import restricts
+from pkgcore.ebuild.atom import atom as atom_cls, transitive_use_atom
 from pkgcore.ebuild.eapi import get_eapi
 from pkgcore.ebuild.misc import sort_keywords
 from pkgcore.fetch import fetchable, unknown_mirror
@@ -871,7 +871,7 @@ class DependencyCheck(Check):
 
     required_addons = (addons.UseAddon,)
     known_results = frozenset(
-        [
+        {
             BadDependency,
             MissingPackageRevision,
             MissingUseDepDefault,
@@ -883,7 +883,7 @@ class DependencyCheck(Check):
             InvalidBdepend,
             InvalidIdepend,
             MisplacedWeakBlocker,
-        ]
+        }
     )
 
     def __init__(self, *args, use_addon):
@@ -929,12 +929,9 @@ class DependencyCheck(Check):
             )
             yield from unstated
 
+            unknowns_useflags = set()
             for node in nodes:
-                if isinstance(node, boolean.OrRestriction):
-                    in_or_restriction = True
-                else:
-                    in_or_restriction = False
-
+                in_or_restriction = isinstance(node, boolean.OrRestriction)
                 for atom in iflatten_instance(node, (atom_cls,)):
                     # Skip reporting blockers on deprecated packages; the 
primary
                     # purpose of deprecations is to get rid of dependencies
@@ -959,6 +956,15 @@ class DependencyCheck(Check):
                     if atom.op == "=" and not atom.revision:
                         yield MissingPackageRevision(attr, str(atom), pkg=pkg)
 
+                    if isinstance(atom, transitive_use_atom) and atom.use is 
not None:
+                        for useflag in atom.use:
+                            if useflag[-1] == "?":
+                                useflag = useflag[:-1].removeprefix("!")
+                                if useflag[-1] == ")":
+                                    useflag = useflag[:-3]
+                                if useflag not in pkg.iuse_stripped:
+                                    unknowns_useflags.add(useflag)
+
                     if atom.blocks:
                         if atom.match(pkg):
                             yield BadDependency(attr, atom, "package blocks 
itself", pkg=pkg)
@@ -969,6 +975,9 @@ class DependencyCheck(Check):
                         elif not atom.blocks_strongly:
                             weak_blocks[attr].add(atom)
 
+            if unknowns_useflags:
+                yield UnstatedIuse(attr, sorted(unknowns_useflags), pkg=pkg)
+
         for attr in ("depend", "bdepend"):
             weak_blocks[attr].difference_update(weak_blocks["rdepend"])
         weak_blocks["idepend"].difference_update(weak_blocks["rdepend"], 
weak_blocks["depend"])
@@ -1597,7 +1606,7 @@ class InvalidProperties(results.MetadataError, 
results.VersionResult):
 class _RestrictPropertiesCheck(Check):
     """Generic check for RESTRICT and PROPERTIES."""
 
-    _attr = None
+    _attr: str = None
     _unknown_result_cls = None
     required_addons = (addons.UseAddon,)
 
@@ -1606,9 +1615,9 @@ class _RestrictPropertiesCheck(Check):
         self.filter = use_addon.get_filter(self._attr)
 
         # pull allowed values from a repo and its masters
-        allowed = []
+        allowed = set()
         for repo in self.options.target_repo.trees:
-            allowed.extend(getattr(repo.config, f"{self._attr}_allowed"))
+            allowed.update(getattr(repo.config, f"{self._attr}_allowed"))
         self.allowed = frozenset(allowed)
 
     def feed(self, pkg):

diff --git 
a/testdata/data/repos/standalone/DependencyCheck/MissingUseDepDefault/fix.patch 
b/testdata/data/repos/standalone/DependencyCheck/MissingUseDepDefault/fix.patch
index c86bbe16..c1cd3750 100644
--- 
a/testdata/data/repos/standalone/DependencyCheck/MissingUseDepDefault/fix.patch
+++ 
b/testdata/data/repos/standalone/DependencyCheck/MissingUseDepDefault/fix.patch
@@ -1,10 +1,10 @@
 diff -Naur 
standalone/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild 
fixed/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild
 --- 
standalone/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild   
   2019-12-02 21:50:34.617257001 -0700
 +++ fixed/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild   
2019-12-02 21:52:56.547749364 -0700
-@@ -3,7 +3,7 @@
- HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+@@ -4,7 +4,7 @@
  SLOT="0"
  LICENSE="BSD"
+ IUSE="foo"
 -DEPEND="stub/stub1[foo]"
 -RDEPEND="|| ( stub/stub2[used] stub/stub2[-foo] )"
 -BDEPEND="stub/stub3[foo?]"

diff --git 
a/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected-verbose.json
 
b/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected-verbose.json
index 799fec6f..12e2f462 100644
--- 
a/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected-verbose.json
+++ 
b/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected-verbose.json
@@ -1,2 +1,3 @@
+{"__class__": "UnstatedIuse", "category": "DependencyCheck", "package": 
"UnstatedIuse", "version": "0", "attr": "depend", "flags": ["used"], "profile": 
null, "num_profiles": null}
 {"__class__": "UnstatedIuse", "category": "DependencyCheck", "package": 
"UnstatedIuse", "version": "0", "attr": "rdepend", "flags": ["used"], 
"profile": "default/amd64", "num_profiles": null}
 {"__class__": "UnstatedIuse", "category": "DependencyCheck", "package": 
"UnstatedIuse", "version": "0", "attr": "rdepend", "flags": ["used"], 
"profile": "default/x86", "num_profiles": null}

diff --git 
a/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected.json 
b/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected.json
index 512b0afa..0950d1f3 100644
--- a/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected.json
+++ b/testdata/data/repos/standalone/DependencyCheck/UnstatedIuse/expected.json
@@ -1 +1,2 @@
+{"__class__": "UnstatedIuse", "category": "DependencyCheck", "package": 
"UnstatedIuse", "version": "0", "attr": "depend", "flags": ["used"], "profile": 
null, "num_profiles": null}
 {"__class__": "UnstatedIuse", "category": "DependencyCheck", "package": 
"UnstatedIuse", "version": "0", "attr": "rdepend", "flags": ["used"], 
"profile": "default/amd64", "num_profiles": 2}

diff --git 
a/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild
 
b/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild
index 53992988..f03b475b 100644
--- 
a/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild
+++ 
b/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/MissingUseDepDefault-0.ebuild
@@ -4,6 +4,7 @@ DESCRIPTION="Ebuild missing USE dependency default"
 HOMEPAGE="https://github.com/pkgcore/pkgcheck";
 SLOT="0"
 LICENSE="BSD"
+IUSE="foo"
 DEPEND="stub/stub1[foo]"
 RDEPEND="|| ( stub/stub2[used] stub/stub2[-foo] )"
 BDEPEND="stub/stub3[foo?]"

diff --git 
a/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/metadata.xml 
b/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/metadata.xml
new file mode 100644
index 00000000..bd802a63
--- /dev/null
+++ 
b/testdata/repos/standalone/DependencyCheck/MissingUseDepDefault/metadata.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd";>
+<pkgmetadata>
+       <use>
+               <flag name="foo">stub</flag>
+       </use>
+</pkgmetadata>

diff --git 
a/testdata/repos/standalone/DependencyCheck/UnstatedIuse/UnstatedIuse-0.ebuild 
b/testdata/repos/standalone/DependencyCheck/UnstatedIuse/UnstatedIuse-0.ebuild
index b7438aaa..1179764b 100644
--- 
a/testdata/repos/standalone/DependencyCheck/UnstatedIuse/UnstatedIuse-0.ebuild
+++ 
b/testdata/repos/standalone/DependencyCheck/UnstatedIuse/UnstatedIuse-0.ebuild
@@ -1,5 +1,8 @@
+EAPI=7
+
 DESCRIPTION="Ebuild with unstated IUSE in depsets"
 HOMEPAGE="https://github.com/pkgcore/pkgcheck";
 SLOT="0"
 LICENSE="BSD"
 RDEPEND="used? ( stub/stub1 )"
+DEPEND="stub/stub4[used?]"

diff --git a/tests/checks/test_metadata.py b/tests/checks/test_metadata.py
index 77c062c6..f6c2f00b 100644
--- a/tests/checks/test_metadata.py
+++ b/tests/checks/test_metadata.py
@@ -953,7 +953,7 @@ class TestDependencyCheck(use_based(), misc.ReportTestCase):
     @pytest.mark.parametrize("attr", dep_attrs)
     def test_depset_missing_usedep_default(self, attr):
         chk = self.mk_check()
-        mk_pkg = partial(self.mk_pkg, attr)
+        mk_pkg = partial(self.mk_pkg, attr, iuse="foo bar baz blah")
 
         # USE flag exists on all matching pkgs
         self.assertNoReport(chk, mk_pkg(eapi="4", depset="dev-libs/foo[bar?]"))

Reply via email to