diff options
-rw-r--r-- | src/pkgcheck/addons.py | 4 | ||||
-rw-r--r-- | src/pkgcheck/checks/profiles.py | 55 | ||||
-rw-r--r-- | src/pkgcheck/git.py | 4 | ||||
-rw-r--r-- | src/pkgcheck/scripts/pkgcheck.py | 2 | ||||
-rw-r--r-- | src/pkgcheck/sources.py | 52 | ||||
-rw-r--r-- | tests/module/test_git.py | 4 |
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 = [ |