| 1 | # Copyright: 2006 Brian Harring <ferringb@gmail.com> |
|---|
| 2 | # License: GPL2 |
|---|
| 3 | |
|---|
| 4 | from pkgcore.util.compatibility import any |
|---|
| 5 | from pkgcore.util.demandload import demandload |
|---|
| 6 | from pkgcore_checks import base, addons |
|---|
| 7 | from pkgcore.util.iterables import caching_iter |
|---|
| 8 | from pkgcore.restrictions import boolean |
|---|
| 9 | from pkgcore.ebuild.atom import atom |
|---|
| 10 | from pkgcore.package import virtual |
|---|
| 11 | from pkgcore_checks.util import get_cpvstr |
|---|
| 12 | demandload( |
|---|
| 13 | globals(), |
|---|
| 14 | "urllib:urlopen " |
|---|
| 15 | "pkgcore.util.xml:escape " |
|---|
| 16 | "pkgcore.log:logger " |
|---|
| 17 | ) |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | class SuggestRemoval(base.Result): |
|---|
| 21 | |
|---|
| 22 | """pkg isn't ported, stablize the targets and it can likely go away""" |
|---|
| 23 | |
|---|
| 24 | __slots__ = ("category", "package", "version", "ported") |
|---|
| 25 | |
|---|
| 26 | threshold = base.versioned_feed |
|---|
| 27 | |
|---|
| 28 | def __init__(self, pkg, ported): |
|---|
| 29 | base.Result.__init__(self) |
|---|
| 30 | self._store_cpv(pkg) |
|---|
| 31 | self.ported = tuple(get_cpvstr(x) for x in ported) |
|---|
| 32 | |
|---|
| 33 | @property |
|---|
| 34 | def short_desc(self): |
|---|
| 35 | return "version is unported, suggest removal for one of the ported " \ |
|---|
| 36 | "versions: [ %s ]" % ', '.join(self.ported) |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | class BadRange(base.Result): |
|---|
| 40 | |
|---|
| 41 | """ |
|---|
| 42 | look for virtual/x11 atoms that don't intersect =virtual/x11-6.9 |
|---|
| 43 | """ |
|---|
| 44 | |
|---|
| 45 | __slots__ = ("category", "package", "version", "attr", "atoms") |
|---|
| 46 | |
|---|
| 47 | threshold = base.versioned_feed |
|---|
| 48 | |
|---|
| 49 | def __init__(self, pkg, attr, atom_inst): |
|---|
| 50 | base.Result.__init__(self) |
|---|
| 51 | self._store_cpv(pkg) |
|---|
| 52 | self.attr = attr |
|---|
| 53 | self.atoms = tuple(str(x) for x in atom_inst) |
|---|
| 54 | |
|---|
| 55 | @property |
|---|
| 56 | def short_desc(self): |
|---|
| 57 | return "%s: virtual/x11 atoms must match version 6.9: %s" % ( |
|---|
| 58 | self.attr, ', '.join(self.atoms)) |
|---|
| 59 | |
|---|
| 60 | |
|---|
| 61 | class NotPorted(base.Result): |
|---|
| 62 | |
|---|
| 63 | """standalone virtual/x11 atom, not ported.""" |
|---|
| 64 | |
|---|
| 65 | __slots__ = ("category", "package", "version", "attr", "or_block") |
|---|
| 66 | |
|---|
| 67 | threshold = base.versioned_feed |
|---|
| 68 | |
|---|
| 69 | def __init__(self, pkg, attr, or_block): |
|---|
| 70 | base.Result.__init__(self) |
|---|
| 71 | self._store_cpv(pkg) |
|---|
| 72 | self.attr = attr |
|---|
| 73 | self.or_block = or_block |
|---|
| 74 | |
|---|
| 75 | @property |
|---|
| 76 | def short_desc(self): |
|---|
| 77 | return "%s: has standalone virtual/x11 in an OR block" % self.attr |
|---|
| 78 | |
|---|
| 79 | |
|---|
| 80 | class VisibilityCausedNotPorted(base.Result): |
|---|
| 81 | |
|---|
| 82 | """ |
|---|
| 83 | ported, but due to visibility (mask'ing/keywords), knocked back to |
|---|
| 84 | effectively not ported |
|---|
| 85 | """ |
|---|
| 86 | |
|---|
| 87 | __slots__ = ("category", "package", "version", "attr", "keyword", |
|---|
| 88 | "profile", "failed") |
|---|
| 89 | |
|---|
| 90 | threshold = base.versioned_feed |
|---|
| 91 | |
|---|
| 92 | def __init__(self, pkg, keyword, profile, attr, failed): |
|---|
| 93 | base.Result.__init__(self) |
|---|
| 94 | self._store_cpv(pkg) |
|---|
| 95 | self.attr = attr |
|---|
| 96 | self.keyword = keyword |
|---|
| 97 | self.profile = profile |
|---|
| 98 | self.failed = tuple(str(x) for x in failed) |
|---|
| 99 | |
|---|
| 100 | @property |
|---|
| 101 | def short_desc(self): |
|---|
| 102 | return "attr(%s): keyword(%s): profile(%s): visibility induced " \ |
|---|
| 103 | " unported, fix via [ %s ]" % (self.attr, self.keyword, |
|---|
| 104 | self.profile, ', '.join(self.failed)) |
|---|
| 105 | |
|---|
| 106 | |
|---|
| 107 | class ModularXPortingReport(base.Template): |
|---|
| 108 | |
|---|
| 109 | """modular X porting report. |
|---|
| 110 | Scans for dependencies that require monolithic X, or via visibility |
|---|
| 111 | limiters from profiles, are forced to use monolithic X |
|---|
| 112 | """ |
|---|
| 113 | feed_type = base.package_feed |
|---|
| 114 | required_addons = ( |
|---|
| 115 | addons.ArchesAddon, addons.QueryCacheAddon, addons.EvaluateDepSetAddon) |
|---|
| 116 | known_results = (SuggestRemoval, BadRange, NotPorted, |
|---|
| 117 | VisibilityCausedNotPorted) |
|---|
| 118 | |
|---|
| 119 | valid_modx_pkgs_url = \ |
|---|
| 120 | "http://www.gentoo.org/proj/en/desktop/x/x11/modular-x-packages.txt" |
|---|
| 121 | |
|---|
| 122 | @classmethod |
|---|
| 123 | def mangle_option_parser(cls, parser): |
|---|
| 124 | parser.add_option( |
|---|
| 125 | '--mod-x-packages', |
|---|
| 126 | help='location to cache %s' % (cls.valid_modx_pkgs_url,)) |
|---|
| 127 | |
|---|
| 128 | def __init__(self, options, arches, query_cache, depset_cache): |
|---|
| 129 | base.Template.__init__(self, options) |
|---|
| 130 | self.query_cache = query_cache.query_cache |
|---|
| 131 | self.depset_cache = depset_cache |
|---|
| 132 | self.arches = frozenset(x.lstrip("~") for x in options.arches) |
|---|
| 133 | # use 7.1 so it catches any >7.0 |
|---|
| 134 | self.x7 = virtual.package(None, "virtual/x11-7.1") |
|---|
| 135 | self.x6 = virtual.package(None, "virtual/x11-6.9") |
|---|
| 136 | if self.options.mod_x_packages is not None: |
|---|
| 137 | try: |
|---|
| 138 | package_list = open(self.options.mod_x_packages, 'r') |
|---|
| 139 | except (IOError, OSError), e: |
|---|
| 140 | logger.warn( |
|---|
| 141 | 'modular X package file cannot be opened (%s), refetching', |
|---|
| 142 | e) |
|---|
| 143 | package_list = list(urlopen(self.valid_modx_pkgs_url)) |
|---|
| 144 | try: |
|---|
| 145 | f = open(self.options.mod_x_packages, 'w') |
|---|
| 146 | for line in package_list: |
|---|
| 147 | f.write(line) |
|---|
| 148 | except (IOError, OSError), e: |
|---|
| 149 | logger.warn( |
|---|
| 150 | 'modular X package file could not be written (%s)', e) |
|---|
| 151 | else: |
|---|
| 152 | package_list = urlopen(self.valid_modx_pkgs_url) |
|---|
| 153 | self.valid_modx_keys = frozenset(x for x in |
|---|
| 154 | (y.strip() for y in package_list) if |
|---|
| 155 | x and x != "virtual/x11") |
|---|
| 156 | |
|---|
| 157 | def feed(self, pkgset, reporter): |
|---|
| 158 | # query_cache gets caching_iter partial repo searches shoved into it- |
|---|
| 159 | # reason is simple, it's likely that versions of this pkg probably |
|---|
| 160 | # use similar deps- so we're forcing those packages that were |
|---|
| 161 | # accessed for atom matching to remain in memory. |
|---|
| 162 | # end result is less going to disk |
|---|
| 163 | unported = [] |
|---|
| 164 | for pkg in pkgset: |
|---|
| 165 | self.check_pkg(pkg, reporter, unported) |
|---|
| 166 | |
|---|
| 167 | if unported: |
|---|
| 168 | for u in unported: |
|---|
| 169 | l = [pkg for pkg in pkgset if pkg not in unported] |
|---|
| 170 | if l: |
|---|
| 171 | reporter.add_report(SuggestRemoval(u, l)) |
|---|
| 172 | |
|---|
| 173 | def check_pkg(self, pkg, reporter, unported): |
|---|
| 174 | failed = [] |
|---|
| 175 | |
|---|
| 176 | bool_or = boolean.OrRestriction |
|---|
| 177 | for attr, depset in (("depends", pkg.depends), |
|---|
| 178 | ("rdepends", pkg.rdepends), ("pdepends", pkg.post_rdepends)): |
|---|
| 179 | stack = [depset.evaluate_depset([], tristate_filter=[] |
|---|
| 180 | ).restrictions] |
|---|
| 181 | bad_range = set() |
|---|
| 182 | bad_blocks = set() |
|---|
| 183 | while stack: |
|---|
| 184 | for a in stack.pop(-1): |
|---|
| 185 | if isinstance(a, atom): |
|---|
| 186 | if a.key == "virtual/x11" and not a.blocks: |
|---|
| 187 | if not a.match(self.x6): |
|---|
| 188 | bad_range.add(a) |
|---|
| 189 | bad_blocks.add((a,)) |
|---|
| 190 | elif isinstance(a, bool_or): |
|---|
| 191 | for block in a.iter_dnf_solutions(): |
|---|
| 192 | i = iter(block) |
|---|
| 193 | for x in i: |
|---|
| 194 | if x.blocks: |
|---|
| 195 | continue |
|---|
| 196 | if x.key == "virtual/x11": |
|---|
| 197 | break |
|---|
| 198 | else: |
|---|
| 199 | continue |
|---|
| 200 | for x in i: |
|---|
| 201 | if not x.blocks and \ |
|---|
| 202 | x.key in self.valid_modx_keys: |
|---|
| 203 | break |
|---|
| 204 | else: |
|---|
| 205 | for or_block in a.cnf_solutions(): |
|---|
| 206 | if not any(True for x in or_block if |
|---|
| 207 | x.key == "virtual/x11" |
|---|
| 208 | and not x.blocks): |
|---|
| 209 | continue |
|---|
| 210 | |
|---|
| 211 | if any(True for x in or_block if |
|---|
| 212 | x.key in self.valid_modx_keys |
|---|
| 213 | and not x.blocks): |
|---|
| 214 | break |
|---|
| 215 | else: |
|---|
| 216 | # standalone virtual/x11 |
|---|
| 217 | bad_blocks.add(tuple(block)) |
|---|
| 218 | break |
|---|
| 219 | else: |
|---|
| 220 | stack.append(a.restrictions) |
|---|
| 221 | if bad_range: |
|---|
| 222 | reporter.add_report(BadRange(pkg, attr, sorted(bad_range))) |
|---|
| 223 | if bad_blocks: |
|---|
| 224 | for bad in sorted(bad_blocks): |
|---|
| 225 | reporter.add_report(NotPorted(pkg, attr, bad)) |
|---|
| 226 | if bad_range or bad_blocks: |
|---|
| 227 | failed.append(attr) |
|---|
| 228 | |
|---|
| 229 | if failed: |
|---|
| 230 | unported.append(pkg) |
|---|
| 231 | |
|---|
| 232 | if len(failed) == 2: |
|---|
| 233 | # no point in trying it out, will fail anyways |
|---|
| 234 | return |
|---|
| 235 | |
|---|
| 236 | skip_depends = "depends" in failed |
|---|
| 237 | skip_rdepends = "rdepends" in failed |
|---|
| 238 | skip_pdepends = "pdepends" in failed |
|---|
| 239 | del failed |
|---|
| 240 | |
|---|
| 241 | # ok heres the rules of the road. |
|---|
| 242 | # valid: || ( modx <virtual/x11-7 ), || ( modx virtual/x11 ) |
|---|
| 243 | # not valid: >=virtual/x11-7 anywhere, virtual/x11 floating |
|---|
| 244 | # not valid: x11-base/xorg-x11 floating |
|---|
| 245 | |
|---|
| 246 | if not skip_depends: |
|---|
| 247 | for edepset, profiles in \ |
|---|
| 248 | self.depset_cache.collapse_evaluate_depset(pkg, "depends", |
|---|
| 249 | pkg.depends): |
|---|
| 250 | self.process_depset(pkg, "depends", edepset, profiles, |
|---|
| 251 | reporter) |
|---|
| 252 | |
|---|
| 253 | if not skip_rdepends: |
|---|
| 254 | for edepset, profiles in \ |
|---|
| 255 | self.depset_cache.collapse_evaluate_depset(pkg, "rdepends", |
|---|
| 256 | pkg.rdepends): |
|---|
| 257 | self.process_depset(pkg, "rdepends", edepset, profiles, |
|---|
| 258 | reporter) |
|---|
| 259 | |
|---|
| 260 | if not skip_pdepends: |
|---|
| 261 | for edepset, profiles in self.depset_cache.collapse_evaluate_depset( |
|---|
| 262 | pkg, "post_rdepends", pkg.post_rdepends): |
|---|
| 263 | self.process_depset(pkg, "post_rdepends", edepset, profiles, |
|---|
| 264 | reporter) |
|---|
| 265 | |
|---|
| 266 | def process_depset(self, pkg, attr, depset, profiles, reporter): |
|---|
| 267 | |
|---|
| 268 | csolutions = depset.cnf_solutions() |
|---|
| 269 | failed = set() |
|---|
| 270 | for profile in profiles: |
|---|
| 271 | failed.clear() |
|---|
| 272 | cache = profile.cache |
|---|
| 273 | insoluable = profile.insoluable |
|---|
| 274 | visible = profile.visible |
|---|
| 275 | for or_block in csolutions: |
|---|
| 276 | if not any(True for x in or_block if x.key == "virtual/x11"): |
|---|
| 277 | continue |
|---|
| 278 | |
|---|
| 279 | # we know a virtual/x11 is in this options. |
|---|
| 280 | # better have a modx node in options, else it's bad. |
|---|
| 281 | modx_candidates = [x for x in or_block if |
|---|
| 282 | x.key in self.valid_modx_keys] |
|---|
| 283 | for a in modx_candidates: |
|---|
| 284 | if a.blocks: |
|---|
| 285 | # weird. |
|---|
| 286 | continue |
|---|
| 287 | h = str(a) |
|---|
| 288 | if h in insoluable: |
|---|
| 289 | continue |
|---|
| 290 | elif h in cache: |
|---|
| 291 | break |
|---|
| 292 | elif h not in self.query_cache: |
|---|
| 293 | self.query_cache[h] = caching_iter( |
|---|
| 294 | self.options.search_repo.itermatch(a)) |
|---|
| 295 | # if a provider is visible, good to go. |
|---|
| 296 | if any(True for pkg in self.query_cache[h] if visible(pkg)): |
|---|
| 297 | cache.add(h) |
|---|
| 298 | break |
|---|
| 299 | else: |
|---|
| 300 | insoluable.add(h) |
|---|
| 301 | else: |
|---|
| 302 | failed.update(modx_candidates) |
|---|
| 303 | if failed: |
|---|
| 304 | reporter.add_report(VisibilityCausedNotPorted(pkg, |
|---|
| 305 | profile.key, profile.name, attr, sorted(failed))) |
|---|