root/releases/pkgcore-checks/0.3.5/pkgcore_checks/metadata_checks.py @ ferringb%2540gmail.com-20070404195935-5rvqbdpd1c67bp8a

Revision ferringb%2540gmail.com-20070404195935-5rvqbdpd1c67bp8a, 12.4 KB (checked in by Brian Harring <ferringb@…>, 22 months ago)

pull in one final tweak for 0.3.4; fixup a potential for a UnboundError?

Line 
1# Copyright: 2006 Brian Harring <ferringb@gmail.com>
2# License: GPL2
3
4import os
5from operator import attrgetter
6from pkgcore_checks import base, util, addons
7
8from pkgcore.util.compatibility import any
9from pkgcore.package.errors import MetadataException
10from pkgcore.ebuild.atom import MalformedAtom, atom
11from pkgcore.fetch import fetchable
12from pkgcore.restrictions import packages
13from pkgcore.util.osutils import listdir_files
14
15from pkgcore.util.demandload import demandload
16demandload(globals(), "pkgcore.util.xml:escape logging")
17
18
19class MetadataError(base.Result):
20    """problem detected with a packages metadata"""
21    __slots__ = ("category", "package", "version", "attr", "msg")
22    threshold = base.versioned_feed
23   
24    def __init__(self, pkg, attr, msg):
25        base.Result.__init__(self)
26        self._store_cpv(pkg)
27        self.attr, self.msg = attr, str(msg)
28   
29    @property
30    def short_desc(self):
31        return "attr(%s): %s" % (self.attr, self.msg)
32
33
34class MissingLicense(base.Result):
35    """used license(s) have no matching license file(s)"""
36
37    __slots__ = ("category", "package", "version", "licenses")
38    threshold = base.versioned_feed
39   
40    def __init__(self, pkg, licenses):
41        self._store_cpv(pkg)
42        self.licenses = tuple(sorted(licenses))
43   
44    @property
45    def short_desc(self):
46        return "no license files: %s" % ', '.join(self.licenses)
47
48
49class LicenseMetadataReport(base.Template):
50
51    """LICENSE metadata key validity checks"""
52
53    known_results = (MetadataError, MissingLicense) + \
54        addons.UseAddon.known_results
55    feed_type = base.versioned_feed
56
57    required_addons = (addons.UseAddon, addons.ProfileAddon,
58        addons.LicenseAddon)
59
60    def __init__(self, options, iuse_handler, profiles, licenses):
61        base.Template.__init__(self, options)
62        self.iuse_filter = iuse_handler.get_filter('license')
63        self.license_handler = licenses
64
65    def start(self):
66        self.licenses = self.license_handler.licenses
67
68    def finish(self, reporter):
69        self.licenses = None
70
71    def feed(self, pkg, reporter):
72        try:
73            licenses = pkg.license
74        except (KeyboardInterrupt, SystemExit):
75            raise
76        except (MetadataException, MalformedAtom, ValueError), e:
77            reporter.add_report(MetadataError(pkg, 'license',
78                "error- %s" % e))
79            del e
80        except Exception, e:
81            logging.exception("unknown exception caught for pkg(%s) attr(%s): "
82                "type(%s), %s" % (pkg, 'license', type(e), e))
83            reporter.add_report(MetadataError(pkg, 'license',
84                "exception- %s" % e))
85            del e
86        else:
87            i = self.iuse_filter((basestring,), pkg, licenses, reporter)
88            if self.licenses is None:
89                # force a walk of it so it'll report if needs be.
90                for x in i:
91                    pass
92            else:
93                licenses = set(i)
94                if not licenses:
95                    reporter.add_report(MetadataError(pkg, "license",
96                        "no license defined"))
97                else:
98                    licenses.difference_update(self.licenses)
99                    if licenses:
100                        reporter.add_report(MissingLicense(pkg, licenses))
101
102
103class IUSEMetadataReport(base.Template):
104
105    """Check IUSE for valid use flags"""
106
107    required_addons = (addons.UseAddon,)
108    known_results = (MetadataError,) + addons.UseAddon.known_results
109
110    feed_type = base.versioned_feed
111
112    def __init__(self, options, iuse_handler):
113        base.Template.__init__(self, options)
114        self.iuse_handler = iuse_handler
115
116    def feed(self, pkg, reporter):
117        if not self.iuse_handler.ignore:
118            iuse = set(pkg.iuse).difference(self.iuse_handler.allowed_iuse(pkg))
119            if iuse:
120                reporter.add_report(MetadataError(pkg, "iuse",
121                    "iuse unknown flags- [ %s ]" % ", ".join(iuse)))
122
123
124class DependencyReport(base.Template):
125
126    """check DEPEND, PDEPEND, RDEPEND and PROVIDES"""
127
128    required_addons = (addons.UseAddon,)
129    known_results = (MetadataError,) + addons.UseAddon.known_results
130
131    feed_type = base.versioned_feed
132
133    attrs = tuple((x, attrgetter(x)) for x in
134        ("depends", "rdepends", "post_rdepends", "provides"))
135
136    def __init__(self, options, iuse_handler):
137        base.Template.__init__(self, options)
138        self.iuse_filter = iuse_handler.get_filter()
139
140    def feed(self, pkg, reporter):
141        for attr_name, getter in self.attrs:
142            try:
143                for x in self.iuse_filter((atom,), pkg, getter(pkg), reporter,
144                    attr=attr_name):
145                    pass
146            except (KeyboardInterrupt, SystemExit):
147                raise
148            except (MetadataException, MalformedAtom, ValueError), e:
149                reporter.add_report(MetadataError(pkg, attr_name,
150                    "error- %s" % e))
151                del e
152            except Exception, e:
153                logging.exception(
154                    "unknown exception caught for pkg(%s) attr(%s): "
155                    "type(%s), %s" % (pkg, attr_name, type(e), e))
156                reporter.add_report(MetadataError(pkg, attr_name,
157                    "exception- %s" % e))
158                del e
159
160
161class StupidKeywords(base.Result):
162    """pkg that is using -*; package.mask in profiles addresses this already"""
163
164    __slots__ = ('category', 'package', 'version')
165    threshold = base.versioned_feed
166
167    def __init__(self, pkg):
168        base.Result.__init__(self)
169        self._store_cpv(pkg)
170   
171    short_desc = ("keywords contain -*; use package.mask or empty keywords "
172        "instead")
173       
174
175class KeywordsReport(base.Template):
176   
177    """
178    check pkgs keywords for sanity; empty keywords, and -* are flagged
179    """
180   
181    feed_type = base.versioned_feed
182    known_results = (StupidKeywords, MetadataError)
183   
184    def feed(self, pkg, reporter):
185        if "-*" in pkg.keywords:
186            reporter.add_report(StupidKeywords(pkg))
187
188
189class MissingUri(base.Result):
190    """restrict=fetch isn't set, yet no full uri exists"""
191    __slots__ = ("category", "package", "version", "filename")
192    threshold = base.versioned_feed
193
194    def __init__(self, pkg, filename):
195        base.Result.__init__(self)
196        self._store_cpv(pkg)
197        self.filename = filename
198   
199    @property
200    def short_desc(self):
201        return "file %s is unfetchable- no URI available, and RESTRICT=fetch " \
202            "isn't set" % self.filename
203   
204
205class BadProto(base.Result):
206    """bad protocol"""
207    __slots__ = ("category", "package", "version", "filename", "bad_uri")
208
209    def __init__(self, pkg, filename, bad_uri):
210        base.Result.__init__(self)
211        self._store_cpv(pkg)
212        self.filename = filename
213        self.bad_uri = tuple(sorted(bad_uri))
214   
215    @property
216    def short_desc(self):
217        return "file %s: bad protocol/uri: %r " % (self.filename, self.bad_uri)
218   
219
220class SrcUriReport(base.Template):
221
222    """SRC_URI related checks.
223
224    verify that it's a valid/fetchable uri, port 80,443,23
225    """
226
227    required_addons = (addons.UseAddon,)
228    feed_type = base.versioned_feed
229    known_results = (BadProto, MissingUri, MetadataError) + \
230        addons.UseAddon.known_results
231
232    valid_protos = frozenset(["http", "https", "ftp"])
233
234    def __init__(self, options, iuse_handler):
235        base.Template.__init__(self, options)
236        self.iuse_filter = iuse_handler.get_filter('fetchables')
237
238    def feed(self, pkg, reporter):
239        try:
240            lacks_uri = set()
241            # set is required here, due to the fact fetchables can
242            # have duplicate entries.
243            for f_inst in set(self.iuse_filter((fetchable,), pkg,
244                pkg.fetchables, reporter)):
245                if not f_inst.uri:
246                    lacks_uri.add(f_inst.filename)
247                else:
248                    bad = set()
249                    for x in f_inst.uri:
250                        i = x.find("://")
251                        if i == -1:
252                            lacks_uri.add(x)
253                        else:
254                            if x[:i] not in self.valid_protos:
255                                bad.add(x)
256                    if bad:
257                        reporter.add_report(
258                            BadProto(pkg, f_inst.filename, bad))
259            if not "fetch" in pkg.restrict:
260                for x in sorted(lacks_uri):
261                    reporter.add_report(MissingUri(pkg, x))
262
263        except (KeyboardInterrupt, SystemExit):
264            raise
265        except (MetadataException, MalformedAtom, ValueError), e:
266            reporter.add_report(MetadataError(pkg, 'fetchables',
267                "error- %s" % e))
268            del e
269        except Exception, e:
270            logging.exception("unknown exception caught for pkg(%s): "
271                "type(%s), %s" % (pkg, type(e), e))
272            reporter.add_report(MetadataError(pkg, 'fetchables',
273                "exception- %s" % e))
274            del e
275
276
277class CrappyDescription(base.Result):
278   
279    """pkg's description sucks in some fashion"""
280
281    __slots__ = ("category", "package", "version", "msg")
282    threshold = base.versioned_feed
283
284    def __init__(self, pkg, msg):
285        base.Result.__init__(self)
286        self._store_cpv(pkg)
287        self.msg = msg
288   
289    @property
290    def short_desc(self):
291        return "description needs improvement: %s" % self.msg
292   
293
294class DescriptionReport(base.Template):
295    """
296    DESCRIPTION checks.
297    check on length (<=250), too short (<5), or generic (lifted from eclass or
298    just using the pkgs name
299    """
300   
301    feed_type = base.versioned_feed
302    known_results = (CrappyDescription,)
303
304    def feed(self, pkg, reporter):
305        s = pkg.description.lower()
306
307        if s.startswith("based on") and "eclass" in s:
308            reporter.add_report(CrappyDescription(pkg,
309                "generic eclass defined description"))
310
311        elif pkg.package == s or pkg.key == s:
312            reporter.add_report(CrappyDescription(pkg,
313                "using the pkg name as the description isn't very helpful"))
314
315        else:
316            l = len(pkg.description)
317            if not l:
318                reporter.add_report(CrappyDescription(pkg,
319                    "empty/unset"))
320            elif l > 250:
321                reporter.add_report(CrappyDescription(pkg,
322                    "over 250 chars in length, bit long"))
323            elif l < 5:
324                reporter.add_report(CrappyDescription(pkg,
325                    "under 10 chars in length- too short"))
326
327
328class BadRestricts(base.Result):
329    """pkg's restrict metadata has unknown/deprecated entries"""
330   
331    __slots__ = ("category", "package", "version", "restricts", "deprecated")
332    threshold = base.versioned_feed
333   
334    def __init__(self, pkg, restricts, deprecated=None):
335        base.Result.__init__(self)
336        self._store_cpv(pkg)
337        self.restricts = restricts
338        self.deprecated = deprecated
339        if not restricts and not deprecated:
340            raise TypeError("deprecated or restricts must not be empty")
341   
342    @property
343    def short_desc(self):
344        s = ''
345        if self.restricts:
346            s = "unknown restricts: %s" % ", ".join(self.restricts)
347        if self.deprecated:
348            if s:
349                s += "; "
350            s += "deprecated (drop the 'no') [ %s ]" % ", ".join(
351                self.deprecated)
352        return s
353       
354
355class RestrictsReport(base.Template):
356    feed_type = base.versioned_feed
357    known_restricts = frozenset(("confcache", "stricter", "mirror", "fetch",
358        "test", "sandbox", "userpriv", "primaryuri", "binchecks", "strip",
359        "multilib-strict"))
360
361    known_results = (BadRestricts, addons.UseAddon.known_results)
362    required_addons = (addons.UseAddon,)
363
364    __doc__ = "check over RESTRICT, looking for unknown restricts\nvalid " \
365        "restricts:%s" % ", ".join(sorted(known_restricts))
366
367    def __init__(self, options, iuse_handler):
368        base.Template.__init__(self, options)
369        self.iuse_filter = iuse_handler.get_filter('restrict')
370
371    def feed(self, pkg, reporter):
372        # ignore conditionals
373        i = self.iuse_filter((basestring,), pkg, pkg.restrict, reporter)
374        bad = set(i).difference(self.known_restricts)
375        if bad:
376            deprecated = set(x for x in bad if x.startswith("no")
377                and x[2:] in self.known_restricts)
378            reporter.add_report(BadRestricts(
379                    pkg, bad.difference(deprecated), deprecated))
Note: See TracBrowser for help on using the browser.