aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pkgcheck/addons.py4
-rw-r--r--src/pkgcheck/checks/profiles.py55
-rw-r--r--src/pkgcheck/git.py4
-rw-r--r--src/pkgcheck/scripts/pkgcheck.py2
-rw-r--r--src/pkgcheck/sources.py52
-rw-r--r--tests/module/test_git.py4
6 files changed, 80 insertions, 41 deletions
diff --git a/src/pkgcheck/addons.py b/src/pkgcheck/addons.py
index 34d4e9e1..fe1871f0 100644
--- a/src/pkgcheck/addons.py
+++ b/src/pkgcheck/addons.py
@@ -131,6 +131,10 @@ class ProfileData:
return immutable, enabled
+class ProfileNode(profiles_mod.ProfileNode):
+ """Re-inherited to disable instance caching."""
+
+
class ProfilesArgs(arghparse.CommaSeparatedNegations):
"""Parse profiles args for the ProfileAddon."""
diff --git a/src/pkgcheck/checks/profiles.py b/src/pkgcheck/checks/profiles.py
index e05a0a98..4e5b57f7 100644
--- a/src/pkgcheck/checks/profiles.py
+++ b/src/pkgcheck/checks/profiles.py
@@ -88,14 +88,10 @@ class ProfileError(results.ProfilesResult, results.LogError):
"""Erroneously formatted data in various profile files."""
-class _ProfileNode(profiles_mod.ProfileNode):
- """Re-inherited to disable instance caching."""
-
-
class ProfilesCheck(Check):
"""Scan repo profiles for unknown flags/packages."""
- _source = (sources.EmptySource, (base.profiles_scope,))
+ _source = sources.ProfilesRepoSource
required_addons = (addons.UseAddon, addons.KeywordsAddon)
known_results = frozenset([
UnknownProfilePackages, UnknownProfilePackageUse, UnknownProfileUse,
@@ -119,23 +115,23 @@ class ProfilesCheck(Check):
local_iuse | self.iuse_handler.global_iuse |
self.iuse_handler.global_iuse_expand | self.iuse_handler.global_iuse_implicit)
- def finish(self):
+ def feed(self, profile):
unknown_pkgs = defaultdict(lambda: defaultdict(list))
unknown_pkg_use = defaultdict(lambda: defaultdict(list))
unknown_use = defaultdict(lambda: defaultdict(list))
unknown_keywords = defaultdict(lambda: defaultdict(list))
- def _pkg_atoms(filename, profile, vals):
+ def _pkg_atoms(filename, node, vals):
for x in iflatten_instance(vals, atom_cls):
if not self.search_repo.match(x):
- unknown_pkgs[profile.path][filename].append(x)
+ unknown_pkgs[node.path][filename].append(x)
- def _pkg_keywords(filename, profile, vals):
+ def _pkg_keywords(filename, node, vals):
for atom, keywords in vals:
if invalid := set(keywords) - self.keywords.valid:
- unknown_keywords[profile.path][filename].append((atom, invalid))
+ unknown_keywords[node.path][filename].append((atom, invalid))
- def _pkg_use(filename, profile, vals):
+ def _pkg_use(filename, node, vals):
# TODO: give ChunkedDataDict some dict view methods
d = vals
if isinstance(d, misc.ChunkedDataDict):
@@ -148,36 +144,36 @@ class ProfilesCheck(Check):
unknown_disabled = set(disabled) - available
unknown_enabled = set(enabled) - available
if unknown_disabled:
- unknown_pkg_use[profile.path][filename].append(
+ unknown_pkg_use[node.path][filename].append(
(a, ('-' + u for u in unknown_disabled)))
if unknown_enabled:
- unknown_pkg_use[profile.path][filename].append(
+ unknown_pkg_use[node.path][filename].append(
(a, unknown_enabled))
else:
- unknown_pkgs[profile.path][filename].append(a)
+ unknown_pkgs[node.path][filename].append(a)
- def _use(filename, profile, vals):
+ def _use(filename, node, vals):
# TODO: give ChunkedDataDict some dict view methods
d = vals.render_to_dict()
for _, entries in d.items():
for _, disabled, enabled in entries:
if unknown_disabled := set(disabled) - self.available_iuse:
- unknown_use[profile.path][filename].extend(
+ unknown_use[node.path][filename].extend(
('-' + u for u in unknown_disabled))
if unknown_enabled := set(enabled) - self.available_iuse:
- unknown_use[profile.path][filename].extend(
+ unknown_use[node.path][filename].extend(
unknown_enabled)
- def _deprecated(filename, profile, vals):
+ def _deprecated(filename, node, vals):
# make sure replacement profile exists
if vals is not None:
replacement, msg = vals
try:
- _ProfileNode(pjoin(self.profiles_dir, replacement))
+ addons.ProfileNode(pjoin(self.profiles_dir, replacement))
except profiles_mod.ProfileError:
yield ProfileError(
f'nonexistent replacement {replacement!r} '
- f'for deprecated profile: {profile.name!r}')
+ f'for deprecated profile: {node.name!r}')
file_parse_map = {
'packages': ('packages', _pkg_atoms),
@@ -205,17 +201,14 @@ class ProfilesCheck(Check):
report_profile_warnings = lambda x: profile_reports.append(ProfileWarning(x))
report_profile_errors = lambda x: profile_reports.append(ProfileError(x))
- for root, _dirs, files in os.walk(self.profiles_dir):
- if root not in self.non_profile_dirs:
- profile = _ProfileNode(root)
- for f in set(files).intersection(file_parse_map.keys()):
- attr, func = file_parse_map[f]
- # convert log warnings/errors into reports
- with patch('pkgcore.log.logger.error', report_profile_errors), \
- patch('pkgcore.log.logger.warning', report_profile_warnings):
- vals = getattr(profile, attr)
- if results := func(f, profile, vals):
- yield from results
+ for f in profile.files.intersection(file_parse_map.keys()):
+ attr, func = file_parse_map[f]
+ # convert log warnings/errors into reports
+ with patch('pkgcore.log.logger.error', report_profile_errors), \
+ patch('pkgcore.log.logger.warning', report_profile_warnings):
+ vals = getattr(profile.node, attr)
+ if results := func(f, profile.node, vals):
+ yield from results
yield from profile_reports
diff --git a/src/pkgcheck/git.py b/src/pkgcheck/git.py
index 7e7eb6a5..7f038e01 100644
--- a/src/pkgcheck/git.py
+++ b/src/pkgcheck/git.py
@@ -337,11 +337,11 @@ class _ScanCommits(argparse.Action):
restrict = packages.OrRestriction(*sorted(pkgs))
restrictions.append((base.package_scope, restrict))
if eclasses:
- restrictions.append((base.eclass_scope, base.contains_restriction(eclasses)))
+ restrictions.append((base.eclass_scope, frozenset(eclasses)))
if profiles:
# TODO: Support incremental profile scanning (#200) currently this
# enables all profiles scope checks.
- restrictions.append((base.profiles_scope, base.contains_restriction(profiles)))
+ restrictions.append((base.profiles_scope, frozenset(profiles)))
# no relevant targets, exit early
if not restrictions:
diff --git a/src/pkgcheck/scripts/pkgcheck.py b/src/pkgcheck/scripts/pkgcheck.py
index 69e3b365..5c1357aa 100644
--- a/src/pkgcheck/scripts/pkgcheck.py
+++ b/src/pkgcheck/scripts/pkgcheck.py
@@ -442,7 +442,7 @@ def generate_restricts(repo, targets):
# support eclass target restrictions
if eclasses:
- yield base.eclass_scope, base.contains_restriction(eclasses)
+ yield base.eclass_scope, frozenset(eclasses)
@scan.bind_delayed_default(1000, 'filter')
diff --git a/src/pkgcheck/sources.py b/src/pkgcheck/sources.py
index fea91e1c..a62c89e8 100644
--- a/src/pkgcheck/sources.py
+++ b/src/pkgcheck/sources.py
@@ -1,11 +1,13 @@
"""Custom package sources used for feeding checks."""
import os
-from collections import deque
+from collections import defaultdict, deque
+from collections.abc import Set
+from dataclasses import dataclass
from operator import attrgetter
from pkgcore.ebuild.repository import UnconfiguredTree
-from pkgcore.restrictions import packages
+from pkgcore.restrictions import packages, restriction
from snakeoil.osutils import listdir_files, pjoin
from . import addons, base
@@ -159,9 +161,49 @@ class EclassRepoSource(RepoSource):
self.eclass_dir = pjoin(self.repo.location, 'eclass')
def itermatch(self, restrict, **kwargs):
- for name in self.eclasses:
- if restrict.match([name]):
- yield Eclass(name, pjoin(self.eclass_dir, f'{name}.eclass'))
+ if isinstance(restrict, Set):
+ # --commits or path restriction
+ eclasses = sorted(restrict.intersection(self.eclasses))
+ else:
+ # matching all eclasses
+ eclasses = self.eclasses
+
+ for name in eclasses:
+ yield Eclass(name, pjoin(self.eclass_dir, f'{name}.eclass'))
+
+
+@dataclass
+class Profile:
+ """Generic profile object."""
+ node: addons.ProfileNode
+ files: set
+
+
+class ProfilesRepoSource(RepoSource):
+ """Repository profiles file source."""
+
+ scope = base.profiles_scope
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.profiles_dir = self.repo.config.profiles_base
+ self.non_profile_dirs = {
+ f'profiles/{x}' for x in addons.ProfileAddon.non_profile_dirs}
+ self._prefix_len = len(self.repo.location.rstrip(os.sep)) + 1
+
+ def itermatch(self, restrict, **kwargs):
+ if isinstance(restrict, Set):
+ # --commits or path restriction
+ paths = defaultdict(list)
+ for x in restrict:
+ paths[pjoin(self.repo.location, os.path.dirname(x))].append(os.path.basename(x))
+ for root, files in sorted(paths.items()):
+ yield Profile(addons.ProfileNode(root), set(files))
+ else:
+ # matching all profiles
+ for root, _dirs, files in os.walk(self.profiles_dir):
+ if root[self._prefix_len:] not in self.non_profile_dirs:
+ yield Profile(addons.ProfileNode(root), set(files))
class _RawRepo(UnconfiguredTree):
diff --git a/tests/module/test_git.py b/tests/module/test_git.py
index af743658..526eb1c7 100644
--- a/tests/module/test_git.py
+++ b/tests/module/test_git.py
@@ -88,7 +88,7 @@ class TestPkgcheckScanCommitsParseArgs:
assert restrictions[0] == \
(base.package_scope, packages.OrRestriction(*atom_restricts))
assert restrictions[1][0] == base.eclass_scope
- assert restrictions[1][1].match(['foo'])
+ assert restrictions[1][1] == frozenset(['foo'])
def test_commits_profiles(self):
output = 'dev-libs/foo/metadata.xml\x00media-libs/bar/bar-0.ebuild\x00profiles/package.mask\x00'
@@ -101,7 +101,7 @@ class TestPkgcheckScanCommitsParseArgs:
assert restrictions[0] == \
(base.package_scope, packages.OrRestriction(*atom_restricts))
assert restrictions[1][0] == base.profiles_scope
- assert restrictions[1][1].match(['profiles/package.mask'])
+ assert restrictions[1][1] == frozenset(['profiles/package.mask'])
def test_commits_ignored_changes(self):
output = [