root/pkgcore-checks/pkgcore_checks/metadata_checks.py @ ferringb%2540gmail.com-20080624174036-q45fk4fxguj9kjlv

Revision ferringb%2540gmail.com-20080624174036-q45fk4fxguj9kjlv, 12.7 kB (checked in by Brian Harring <ferringb@…>, 7 months ago)

punt trailing whitespace

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