| 1 | # Copyright: 2006 Brian Harring <ferringb@gmail.com> |
|---|
| 2 | # License: GPL2 |
|---|
| 3 | |
|---|
| 4 | from pkgcore.util.compatibility import any |
|---|
| 5 | from pkgcore_checks import base, addons |
|---|
| 6 | from pkgcore.util.iterables import caching_iter |
|---|
| 7 | from pkgcore.util.lists import stable_unique, iflatten_instance |
|---|
| 8 | from pkgcore.ebuild.atom import atom |
|---|
| 9 | from pkgcore.util.demandload import demandload |
|---|
| 10 | demandload(globals(), "pkgcore.util.xml:escape ") |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | class VisibleVcsPkg(base.Result): |
|---|
| 14 | """pkg is vcs based, but visible""" |
|---|
| 15 | |
|---|
| 16 | __slots__ = ("category", "package", "version", "profile", "arch") |
|---|
| 17 | |
|---|
| 18 | threshold = base.versioned_feed |
|---|
| 19 | |
|---|
| 20 | def __init__(self, pkg, arch, profile): |
|---|
| 21 | base.Result.__init__(self) |
|---|
| 22 | self._store_cpv(pkg) |
|---|
| 23 | self.arch = arch.lstrip("~") |
|---|
| 24 | self.profile = profile |
|---|
| 25 | |
|---|
| 26 | @property |
|---|
| 27 | def short_desc(self): |
|---|
| 28 | return "VCS version visible for arch %s, profile %s" % ( |
|---|
| 29 | self.arch, self.profile) |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | class NonExistantDeps(base.Result): |
|---|
| 33 | """No matches exist for a depset element""" |
|---|
| 34 | |
|---|
| 35 | __slots__ = ("category", "package", "version", "attr", "atoms") |
|---|
| 36 | |
|---|
| 37 | threshold = base.versioned_feed |
|---|
| 38 | |
|---|
| 39 | def __init__(self, pkg, attr, nonexistant_atoms): |
|---|
| 40 | base.Result.__init__(self) |
|---|
| 41 | self._store_cpv(pkg) |
|---|
| 42 | self.attr = attr |
|---|
| 43 | self.atoms = tuple(str(x) for x in nonexistant_atoms) |
|---|
| 44 | |
|---|
| 45 | @property |
|---|
| 46 | def short_desc(self): |
|---|
| 47 | return "depset %s: nonexistant atoms [ %s ]" % ( |
|---|
| 48 | self.attr, ', '.join(self.atoms)) |
|---|
| 49 | |
|---|
| 50 | |
|---|
| 51 | class NonsolvableDeps(base.Result): |
|---|
| 52 | """No potential solution for a depset attribute""" |
|---|
| 53 | |
|---|
| 54 | __slots__ = ("category", "package", "version", "attr", "profile", |
|---|
| 55 | "keyword", "potentials") |
|---|
| 56 | |
|---|
| 57 | threshold = base.versioned_feed |
|---|
| 58 | |
|---|
| 59 | def __init__(self, pkg, attr, keyword, profile, horked): |
|---|
| 60 | base.Result.__init__(self) |
|---|
| 61 | self._store_cpv(pkg) |
|---|
| 62 | self.attr = attr |
|---|
| 63 | self.profile = profile |
|---|
| 64 | self.keyword = keyword |
|---|
| 65 | self.potentials = tuple(str(x) for x in stable_unique(horked)) |
|---|
| 66 | |
|---|
| 67 | @property |
|---|
| 68 | def short_desc(self): |
|---|
| 69 | return "nonsolvable depset(%s) keyword(%s) profile (%s): " \ |
|---|
| 70 | "solutions: [ %s ]" % (self.attr, self.keyword, self.profile, |
|---|
| 71 | ', '.join(self.potentials)) |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | class VisibilityReport(base.Template): |
|---|
| 75 | |
|---|
| 76 | """Visibility dependency scans. |
|---|
| 77 | Check that at least one solution is possible for a pkg, checking all |
|---|
| 78 | profiles (defined by arch.list) visibility modifiers per stable/unstable |
|---|
| 79 | keyword |
|---|
| 80 | """ |
|---|
| 81 | |
|---|
| 82 | feed_type = base.versioned_feed |
|---|
| 83 | required_addons = ( |
|---|
| 84 | addons.ArchesAddon, addons.QueryCacheAddon, addons.ProfileAddon, |
|---|
| 85 | addons.EvaluateDepSetAddon) |
|---|
| 86 | known_results = (VisibleVcsPkg, NonExistantDeps, NonsolvableDeps) |
|---|
| 87 | |
|---|
| 88 | vcs_eclasses = frozenset(["subversion", "git", "cvs", "darcs"]) |
|---|
| 89 | |
|---|
| 90 | def __init__(self, options, arches, query_cache, profiles, depset_cache): |
|---|
| 91 | base.Template.__init__(self, options) |
|---|
| 92 | self.query_cache = query_cache.query_cache |
|---|
| 93 | self.depset_cache = depset_cache |
|---|
| 94 | self.profiles = profiles |
|---|
| 95 | self.arches = frozenset(x.lstrip("~") for x in options.arches) |
|---|
| 96 | |
|---|
| 97 | def feed(self, pkg, reporter): |
|---|
| 98 | # query_cache gets caching_iter partial repo searches shoved into it- |
|---|
| 99 | # reason is simple, it's likely that versions of this pkg probably |
|---|
| 100 | # use similar deps- so we're forcing those packages that were |
|---|
| 101 | # accessed for atom matching to remain in memory. |
|---|
| 102 | # end result is less going to disk |
|---|
| 103 | |
|---|
| 104 | fvcs = self.vcs_eclasses |
|---|
| 105 | for eclass in pkg.data["_eclasses_"]: |
|---|
| 106 | if eclass in fvcs: |
|---|
| 107 | # vcs ebuild that better not be visible |
|---|
| 108 | self.check_visibility_vcs(pkg, reporter) |
|---|
| 109 | break |
|---|
| 110 | |
|---|
| 111 | for attr, depset in (("depends", pkg.depends), |
|---|
| 112 | ("rdepends", pkg.rdepends), ("post_rdepends", pkg.post_rdepends)): |
|---|
| 113 | nonexistant = set() |
|---|
| 114 | for node in iflatten_instance(depset, atom): |
|---|
| 115 | |
|---|
| 116 | h = str(node) |
|---|
| 117 | if h not in self.query_cache: |
|---|
| 118 | if h in self.profiles.global_insoluable: |
|---|
| 119 | nonexistant.add(node) |
|---|
| 120 | # insert an empty tuple, so that tight loops further |
|---|
| 121 | # on don't have to use the slower get method |
|---|
| 122 | self.query_cache[h] = () |
|---|
| 123 | |
|---|
| 124 | else: |
|---|
| 125 | matches = caching_iter( |
|---|
| 126 | self.options.search_repo.itermatch(node)) |
|---|
| 127 | if matches: |
|---|
| 128 | self.query_cache[h] = matches |
|---|
| 129 | elif not node.blocks and not node.category == "virtual": |
|---|
| 130 | nonexistant.add(node) |
|---|
| 131 | self.query_cache[h] = () |
|---|
| 132 | self.profiles.global_insoluable.add(h) |
|---|
| 133 | |
|---|
| 134 | if nonexistant: |
|---|
| 135 | reporter.add_report(NonExistantDeps(pkg, attr, nonexistant)) |
|---|
| 136 | |
|---|
| 137 | del nonexistant |
|---|
| 138 | |
|---|
| 139 | for attr, depset in (("depends", pkg.depends), |
|---|
| 140 | ("rdepends", pkg.rdepends), ("post_rdepends", pkg.post_rdepends)): |
|---|
| 141 | |
|---|
| 142 | for edepset, profiles in self.depset_cache.collapse_evaluate_depset( |
|---|
| 143 | pkg, attr, depset): |
|---|
| 144 | |
|---|
| 145 | self.process_depset(pkg, attr, edepset, profiles, reporter) |
|---|
| 146 | |
|---|
| 147 | def check_visibility_vcs(self, pkg, reporter): |
|---|
| 148 | for key, profiles in self.profiles.profile_filters.iteritems(): |
|---|
| 149 | if key.startswith("~") or key.startswith("-"): |
|---|
| 150 | continue |
|---|
| 151 | for profile in profiles: |
|---|
| 152 | if profile.visible(pkg): |
|---|
| 153 | reporter.add_report(VisibleVcsPkg(pkg, |
|---|
| 154 | profile.key, profile.name)) |
|---|
| 155 | |
|---|
| 156 | def process_depset(self, pkg, attr, depset, profiles, reporter): |
|---|
| 157 | csolutions = depset.cnf_solutions() |
|---|
| 158 | |
|---|
| 159 | for profile in profiles: |
|---|
| 160 | failures = set() |
|---|
| 161 | # is it visible? ie, is it masked? |
|---|
| 162 | # if so, skip it. |
|---|
| 163 | # long term, probably should do testing in the same respect we do |
|---|
| 164 | # for other visibility tiers |
|---|
| 165 | cache = profile.cache |
|---|
| 166 | provided = profile.provides_repo.match |
|---|
| 167 | is_virtual = profile.virtuals.match |
|---|
| 168 | insoluable = profile.insoluable |
|---|
| 169 | visible = profile.visible |
|---|
| 170 | for required in csolutions: |
|---|
| 171 | if any(True for a in required if a.blocks): |
|---|
| 172 | continue |
|---|
| 173 | for a in required: |
|---|
| 174 | h = str(a) |
|---|
| 175 | if h in insoluable: |
|---|
| 176 | pass |
|---|
| 177 | elif h in cache: |
|---|
| 178 | break |
|---|
| 179 | elif provided(a): |
|---|
| 180 | break |
|---|
| 181 | elif is_virtual(a): |
|---|
| 182 | cache.add(h) |
|---|
| 183 | break |
|---|
| 184 | elif a.category == "virtual" and h not in self.query_cache: |
|---|
| 185 | insoluable.add(h) |
|---|
| 186 | else: |
|---|
| 187 | if any(True for pkg in self.query_cache[h] if |
|---|
| 188 | visible(pkg)): |
|---|
| 189 | cache.add(h) |
|---|
| 190 | break |
|---|
| 191 | else: |
|---|
| 192 | insoluable.add(h) |
|---|
| 193 | else: |
|---|
| 194 | # no matches. not great, should collect them all |
|---|
| 195 | failures.update(required) |
|---|
| 196 | if failures: |
|---|
| 197 | reporter.add_report(NonsolvableDeps(pkg, attr, profile.key, |
|---|
| 198 | profile.name, list(failures))) |
|---|