Source code for pkgcheck.checks.cleanup
from operator import attrgetter
from snakeoil.mappings import defaultdictkey
from snakeoil.strings import pluralism
from .. import addons, results, sources
from . import Check
[docs]
class RedundantVersion(results.VersionResult, results.Info):
"""Redundant version(s) of a package in a specific slot."""
def __init__(self, slot, later_versions, **kwargs):
super().__init__(**kwargs)
self.slot = slot
self.later_versions = tuple(later_versions)
@property
def desc(self):
s = pluralism(self.later_versions)
versions = ", ".join(self.later_versions)
return f"slot({self.slot}) keywords are overshadowed by version{s}: {versions}"
[docs]
class RedundantVersionCheck(Check):
"""Scan for overshadowed package versions.
Scan for versions that are likely shadowed by later versions from a
keywords standpoint (ignoring live packages that erroneously have
keywords).
Example: pkga-1 is keyworded amd64, pkga-2 is amd64.
pkga-1 can potentially be removed.
"""
_source = sources.PackageRepoSource
required_addons = (addons.profiles.ProfileAddon,)
known_results = frozenset([RedundantVersion])
[docs]
@staticmethod
def mangle_argparser(parser):
parser.plugin.add_argument(
"--stable-only",
action="store_true",
help="consider redundant versions only within stable",
docs="""
If enabled, for each slot, only consider redundant versions
with stable keywords. This is useful for cases of cleanup after
successful stabilization.
""",
)
def __init__(self, *args, profile_addon):
super().__init__(*args)
self.keywords_profiles = {
keyword: sorted(profiles, key=attrgetter("name"))
for keyword, profiles in profile_addon.items()
}
[docs]
def filter_later_profiles_masks(self, visible_cache, pkg, later_versions):
# check both stable/unstable profiles for stable KEYWORDS and only
# unstable profiles for unstable KEYWORDS
keywords = []
for keyword in pkg.sorted_keywords:
if keyword[0] != "~":
keywords.append("~" + keyword)
keywords.append(keyword)
# if a profile exists, where the package is visible, but the later aren't
# then it isn't redundant
visible_profiles = tuple(
profile
for keyword in keywords
for profile in self.keywords_profiles.get(keyword, ())
if visible_cache[(profile, pkg)]
)
return tuple(
later
for later in later_versions
if all(visible_cache[(profile, later)] for profile in visible_profiles)
)
[docs]
def feed(self, pkgset):
if len(pkgset) == 1:
return
# algo is roughly thus; spot stable versions, hunt for subset
# keyworded pkgs that are less then the max version;
# repeats this for every overshadowing detected
# finally, does version comparison down slot lines
stack = []
bad = []
for pkg in reversed(pkgset):
# reduce false positives for idiot keywords/ebuilds
if pkg.live:
continue
curr_set = {x for x in pkg.keywords if not x.startswith("-")}
if not curr_set:
continue
matches = [
ver for ver, keys in stack if ver.slot == pkg.slot and not curr_set.difference(keys)
]
# we've done our checks; now we inject unstable for any stable
# via this, earlier versions that are unstable only get flagged
# as "not needed" since their unstable flag is a subset of the
# stable.
# also, yes, have to use list comp here- we're adding as we go
curr_set.update([f"~{x}" for x in curr_set if not x.startswith("~")])
stack.append((pkg, curr_set))
if matches:
bad.append((pkg, matches))
visible_cache = defaultdictkey(lambda profile_pkg: profile_pkg[0].visible(profile_pkg[1]))
for pkg, matches in reversed(bad):
if self.options.stable_only and all(
key.startswith("~") for x in matches for key in x.keywords
):
continue
if matches := self.filter_later_profiles_masks(visible_cache, pkg, matches):
later_versions = (x.fullver for x in sorted(matches))
yield RedundantVersion(pkg.slot, later_versions, pkg=pkg)