root/releases/pkgcore-checks/0.3/pkgcore_checks/repo_metadata.py @ ferringb%2540gmail.com-20070210055810-3q5jaqy6maboplv2

Revision ferringb%2540gmail.com-20070210055810-3q5jaqy6maboplv2, 12.6 KB (checked in by Brian Harring <ferringb@…>, 2 years ago)

add manifest2 transition scans; detect broke (not full sha1/sha256/rmd160) m2 entries, detect packages still at m1

Line 
1# Copyright: 2006 Brian Harring <ferringb@gmail.com>
2# License: GPL2
3
4from pkgcore_checks import base, util, addons
5from pkgcore.ebuild.repository import SlavedTree
6from pkgcore.util.osutils import listdir_dirs
7from pkgcore.util.demandload import demandload
8from pkgcore.chksum.errors import MissingChksum
9import operator, itertools
10import os.path
11
12demandload(globals(), "pkgcore.util.xml:escape "
13    "pkgcore.util.osutils:listdir_files,pjoin "
14    "pkgcore.util.lists:iflatten_instance "
15    "pkgcore:fetch "
16    "pkgcore.ebuild:misc "
17)
18
19
20class UnusedLocalFlagsResult(base.Result):
21   
22    """
23    unused use.local.desc flag(s)
24    """
25   
26    __slots__ = ("category", "package", "flags")
27
28    threshold = base.package_feed
29
30    def __init__(self, pkg, flags):
31        base.Result.__init__(self)
32        # tricky, but it works; atoms have the same attrs
33        self._store_cp(pkg)
34        self.flags = tuple(sorted(flags))
35   
36    @property
37    def short_desc(self):
38        return "use.local.desc unused flag(s) %s" % ', '.join(self.flags)
39
40
41class UnusedLocalFlags(base.Template):
42
43    """
44    check for unused use.local.desc entries
45    """
46
47    feed_type = base.package_feed
48    required_addons = (addons.UseAddon,)
49    known_results = (UnusedLocalFlagsResult,) + addons.UseAddon.known_results
50
51    def __init__(self, options, use_handler):
52        base.Template.__init__(self, options)
53        self.iuse_handler = use_handler
54
55    def start(self):
56        self.collapsed = misc.non_incremental_collapsed_restrict_to_data(
57            self.iuse_handler.specific_iuse)
58
59    def feed(self, pkgs, reporter):
60        unused = set()
61        for pkg in pkgs:
62            unused.update(self.collapsed.iter_pull_data(pkg))
63        for pkg in pkgs:
64            unused.difference_update(pkg.iuse)
65        if unused:
66            reporter.add_report(UnusedLocalFlagsResult(pkg, unused))
67
68
69class UnusedGlobalFlagsResult(base.Result):
70   
71    """
72    unused use.desc flag(s)
73    """
74   
75    __slots__ = ("flags",)
76
77    threshold = base.repository_feed
78
79    def __init__(self, flags):
80        base.Result.__init__(self)
81        # tricky, but it works; atoms have the same attrs
82        self.flags = tuple(sorted(flags))
83   
84    @property
85    def short_desc(self):
86        return "use.desc unused flag(s): %s" % ', '.join(self.flags)
87
88
89class UnusedGlobalFlags(base.Template):
90    """
91    check for unused use.desc entries
92    """
93
94    feed_type = base.versioned_feed
95    scope = base.repository_scope
96    required_addons = (addons.UseAddon,)
97    known_results = (UnusedGlobalFlagsResult,) + addons.UseAddon.known_results
98
99    def __init__(self, options, iuse_handler):
100        base.Template.__init__(self, options)
101        self.flags = None
102        self.iuse_handler = iuse_handler
103
104    def start(self):
105        if not isinstance(self.options.target_repo,SlavedTree):
106            self.flags = set(self.iuse_handler.global_iuse)
107
108    def feed(self, pkg, reporter):
109        if self.flags:
110            self.flags.difference_update(pkg.iuse)
111
112    def finish(self, reporter):
113        if self.flags:
114            reporter.add_report(UnusedGlobalFlagsResult(self.flags))
115            self.flags.clear()
116
117
118class UnusedLicenseReport(base.Result):
119    """
120    unused license(s) detected
121    """
122   
123    __slots__ = ("licenses",)
124   
125    threshold = base.repository_feed
126   
127    def __init__(self, licenses):
128        base.Result.__init__(self)
129        self.licenses = tuple(sorted(licenses))
130
131    @property
132    def short_desc(self):
133        return "unused license(s): %s" % ', '.join(self.licenses)
134
135
136class UnusedLicense(base.Template):
137    """
138    unused license file(s) check
139    """
140
141    feed_type = base.versioned_feed
142    scope = base.repository_scope
143    required_addons = (addons.LicenseAddon,)
144    known_results = (UnusedLicenseReport,)
145
146    def __init__(self, options, licenses):
147        base.Template.__init__(self, options)
148        self.licenses = None
149
150    def start(self):
151        self.licenses = set()
152        if isinstance(self.options.target_repo,SlavedTree):
153            if 'licenses' in listdir_dirs(self.options.target_repo.location):
154                self.licenses.update(listdir_files(pjoin(self.options.target_repo.location,"licenses")))
155        else:
156            for license_dir in self.options.license_dirs:
157                self.licenses.update(listdir_files(license_dir))
158
159    def feed(self, pkg, reporter):
160        self.licenses.difference_update(iflatten_instance(pkg.license))
161
162    def finish(self, reporter):
163        if self.licenses:
164            reporter.add_report(UnusedLicenseReport(self.licenses))
165        self.licenses = None
166
167
168def reformat_chksums(iterable):
169    for chf, val1, val2 in iterable:
170        if chf == "size":
171            yield chf, val1, val2
172        else:
173            yield chf, "%x" % val1, "%x" % val2
174   
175
176class ConflictingChksums(base.Result):
177
178    """
179    checksum conflict detected between two files
180    """
181
182    __slots__ = ("category", "package", "version",
183        "filename", "chksums", "others")
184   
185    threshold = base.versioned_feed
186   
187    _sorter = staticmethod(operator.itemgetter(0))
188   
189    def __init__(self, pkg, filename, chksums, others):
190        base.Result.__init__(self)
191        self._store_cpv(pkg)
192        self.filename = filename
193        self.chksums = tuple(sorted(reformat_chksums(chksums),
194            key=self._sorter))
195        self.others = tuple(sorted(others))
196
197    @property
198    def short_desc(self):
199        return "conflicts with (%s) for file %s chksums %s" % (
200            ', '.join(self.others), self.filename, self.chksums)
201
202
203class ConflictingDigests(base.Template):
204    """
205    scan for conflicting digest entries; since this requires
206    keeping all fetchables in memory, this can add up.
207    """
208
209    scope = base.package_scope
210    feed_type = base.versioned_feed
211    known_results = (ConflictingChksums,)
212
213    def __init__(self, options):
214        base.Template.__init__(self, options)
215        self._fetchables = {}
216
217    def feed(self, pkg, reporter):
218        for uri in iflatten_instance(pkg.fetchables, fetch.fetchable):
219            existing = self._fetchables.get(uri.filename, None)
220            if existing is not None:
221                reqed_chksums = existing[0]
222                conflicts = []
223                for chf, val in uri.chksums.iteritems():
224                    oval = reqed_chksums.get(chf, None)
225                    if oval is not None:
226                        if oval != val:
227                            conflicts.append((chf, val, oval))
228
229                if conflicts:
230                    reporter.add_report(ConflictingChksums(
231                        pkg, uri.filename, conflicts, existing[1]))
232                elif len(uri.chksums) > len(existing):
233                    self._fetchables[uri.filename] = existing
234                existing[1].append(util.get_cpvstr(pkg))
235            else:
236                self._fetchables[uri.filename] = \
237                    (uri.chksums, [util.get_cpvstr(pkg)])
238
239    def finish(self, reporter):
240        self._fetchables.clear()
241
242
243class ManifestDigestConflict(base.Result):
244    """
245    Manifest2 and digest file disagree
246    """
247
248    __slots__ = ("category", "package", "version",
249        "msg", "filename")
250
251
252    threshold = base.versioned_feed
253
254    def __init__(self, pkg, filename, msg):
255        base.Result.__init__(self)
256        self._store_cpv(pkg)
257        self.filename = filename
258        self.msg = msg
259   
260    @property
261    def short_desc(self):
262        return "Manifest/digest conflict for file %s: %s" % (self.filename, self.msg)
263
264
265class OrphanedManifestDist(base.Result):
266    """
267    manifest2 has a checksum entry digest lacks
268    """
269   
270    __slots__ = ("category", "package", "version",
271        "files")
272
273    threshold = base.versioned_feed
274
275    def __init__(self, pkg, files):
276        base.Result.__init__(self)
277        self._store_cp(pkg)
278        self.files = tuple(sorted(files))
279   
280    @property
281    def short_desc(self):
282        return "manifest2 knows of files %r, but digest1 doesn't" % (self.files,)
283
284
285class MissingDigest(base.Result):
286    """
287    file lacks checksum data
288    """
289   
290    __slots__ = ("category", "package", "version", "filename")
291
292    threshold = base.versioned_feed
293
294    def __init__(self, pkg, filename):
295        base.Result.__init__(self)
296        self._store_cp(pkg)
297        self.filename = filename
298   
299    @property
300    def short_desc(self):
301        return "file %s has no checksum info" % self.filename
302
303
304class ConflictManifestDigest(base.Template):
305
306    """Scan for conflicts between the Manifest file and digest files."""
307
308    feed_type = base.package_feed
309    known_results = (ManifestDigestConflict, OrphanedManifestDist,
310        MissingDigest)
311
312    repo_grabber = operator.attrgetter("repo")
313
314    def feed(self, full_pkgset, reporter):
315        # sort it by repo.
316        for repo, pkgset in itertools.groupby(full_pkgset, self.repo_grabber):
317            pkgset = list(pkgset)
318            manifest = pkgset[0].manifest
319            if manifest.version == 1:
320                continue
321            f = getattr(repo, "_get_digests", None)
322            if f is None:
323                continue
324            mdigests = manifest.distfiles
325            old_digests = []
326            for pkg in pkgset:
327                try:
328                    digests = f(pkg, force_manifest1=True)
329                    self.check_pkg(pkg, mdigests, digests, reporter)
330                    old_digests += digests.keys()
331                except MissingChksum, e:
332                    reporter.add_report(MissingDigest(pkg,e))
333            orphaned = set(mdigests).difference(old_digests)
334            if orphaned:
335                reporter.add_report(OrphanedManifestDist(pkgset[0], orphaned))
336
337    def check_pkg(self, pkg, mdigests, digests, reporter):
338
339        for fname, chksum in digests.iteritems():
340            mchksum = mdigests.get(fname, None)
341            if mchksum is None:
342                reporter.add_report(ManifestDigestConflict(pkg, fname,
343                    "missing in manifest"))
344                continue
345            conflicts = []
346            for chf in set(chksum).intersection(mchksum):
347                if mchksum[chf] != chksum[chf]:
348                    conflicts.append((chf, mchksum[chf], chksum[chf]))
349            if conflicts:
350                reporter.add_report(ManifestDigestConflict(pkg, fname,
351                    "chksum conflict- %r" %
352                        tuple(sorted(reformat_chksums(conflicts)))
353                ))
354
355
356class MissingChksum(base.Result):
357    """
358    a file in the manifest/digest data lacks required checksums
359    """
360    threshold = base.versioned_feed
361    __slots__ = ('category', 'package', 'version', 'filename', 'missing',
362        'existing')
363
364    def __init__(self, pkg, filename, missing, existing):
365        self._store_cpv(pkg)
366        self.filename, self.missing = filename, tuple(sorted(missing))
367        self.existing = tuple(sorted(existing))
368
369    @property
370    def short_desc(self):
371        return "file %s is missing required chksums: %s; has chksums: %s" % \
372            (self.filename, ', '.join(self.missing), ', '.join(self.existing))
373
374
375class DeprecatedManifest1(base.Result):
376    """
377    a package's checksum data still is manifest1, instead of manifest2
378    """
379   
380    threshold = base.package_feed
381    __slots__ = ("category", "package")
382   
383    def __init__(self, pkg):
384        self._store_cp(pkg)
385   
386    short_desc = "still is using manifest1 format, should be using manifest2"
387
388
389class Manifest2Transition(base.Template):
390
391    """
392    various checks for Manifest1/digest transition to Manifest2;
393    check for packages not converted, check for manifest2 packages lacking
394    required checksums
395    """
396
397    feed_type = base.package_feed
398    known_results = (MissingChksum, DeprecatedManifest1)
399    required_checksums = frozenset(("sha1", "sha256", "rmd160", "size"))
400
401    repo_grabber = operator.attrgetter("repo")
402
403    def feed(self, full_pkgset, reporter):
404        # sort it by repo.
405        for repo, pkgset in itertools.groupby(full_pkgset, self.repo_grabber):
406            pkgset = list(pkgset)
407            manifest = pkgset[0].manifest
408            if manifest.version == 1:
409                reporter.add_report(DeprecatedManifest1(pkgset[0]))
410                continue
411           
412            seen = set()
413            for pkg in pkgset:
414                for f_inst in (iflatten_instance(pkg.fetchables,
415                    fetch.fetchable)):
416                    if f_inst.filename in seen:
417                        continue
418                    missing = self.required_checksums.difference(f_inst.chksums)
419                    if missing:
420                        reporter.add_report(
421                            MissingChksum(pkg, f_inst.filename, missing,
422                                f_inst.chksums))
423                    seen.add(f_inst.filename)
Note: See TracBrowser for help on using the browser.