2011-04-11 08:39:38 -05:00
|
|
|
#!/usr/bin/python
|
|
|
|
#
|
|
|
|
# Authors:
|
|
|
|
# Jakub Hrozek <jhrozek@redhat.com>
|
|
|
|
# Jan Cholasta <jcholast@redhat.com>
|
|
|
|
#
|
|
|
|
# Copyright (C) 2011 Red Hat
|
|
|
|
# see file 'COPYING' for use and warranty information
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
from optparse import OptionParser
|
|
|
|
from fnmatch import fnmatch, fnmatchcase
|
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
try:
|
|
|
|
from pylint import checkers
|
|
|
|
from pylint.lint import PyLinter
|
|
|
|
from pylint.reporters.text import ParseableTextReporter
|
|
|
|
from pylint.checkers.typecheck import TypeChecker
|
|
|
|
from logilab.astng import Class, Instance, InferenceError
|
|
|
|
except ImportError:
|
|
|
|
print >> sys.stderr, "To use {0}, please install pylint.".format(sys.argv[0])
|
|
|
|
sys.exit(32)
|
2011-04-11 08:39:38 -05:00
|
|
|
|
|
|
|
# File names to ignore when searching for python source files
|
|
|
|
IGNORE_FILES = ('.*', '*~', '*.in', '*.pyc', '*.pyo')
|
2013-01-28 07:55:20 -06:00
|
|
|
IGNORE_PATHS = ('build', 'rpmbuild', 'dist', 'install/po/test_i18n.py',
|
2013-05-21 06:40:27 -05:00
|
|
|
'lite-server.py', 'make-lint', 'make-test', 'ipatests')
|
2011-04-11 08:39:38 -05:00
|
|
|
|
|
|
|
class IPATypeChecker(TypeChecker):
|
2013-01-28 07:55:20 -06:00
|
|
|
NAMESPACE_ATTRS = ['Command', 'Object', 'Method', 'Property', 'Backend',
|
|
|
|
'Updater']
|
|
|
|
LOGGING_ATTRS = ['log', 'debug', 'info', 'warning', 'error', 'exception',
|
|
|
|
'critical']
|
|
|
|
|
|
|
|
# 'class': ['generated', 'properties']
|
2011-04-11 08:39:38 -05:00
|
|
|
ignore = {
|
2013-01-28 07:55:20 -06:00
|
|
|
# Python standard library & 3rd party classes
|
|
|
|
'krbV.Principal': ['name'],
|
|
|
|
'socket._socketobject': ['sendall'],
|
|
|
|
# should be 'subprocess.Popen'
|
|
|
|
'.Popen': ['stdin', 'stdout', 'stderr', 'pid', 'returncode', 'poll',
|
|
|
|
'wait', 'communicate'],
|
|
|
|
'urlparse.ResultMixin': ['scheme', 'netloc', 'path', 'query',
|
|
|
|
'fragment', 'username', 'password', 'hostname', 'port'],
|
|
|
|
'urlparse.ParseResult': ['params'],
|
|
|
|
|
|
|
|
# IPA classes
|
|
|
|
'ipalib.base.NameSpace': ['add', 'mod', 'del', 'show', 'find'],
|
2011-11-21 09:50:27 -06:00
|
|
|
'ipalib.cli.Collector': ['__options'],
|
|
|
|
'ipalib.config.Env': ['*'],
|
2013-01-28 07:55:20 -06:00
|
|
|
'ipalib.krb_utils.KRB5_CCache': LOGGING_ATTRS,
|
2011-11-21 09:50:27 -06:00
|
|
|
'ipalib.parameters.Param': ['cli_name', 'cli_short_name', 'label',
|
2013-01-28 07:55:20 -06:00
|
|
|
'default', 'doc', 'required', 'multivalue', 'primary_key',
|
|
|
|
'normalizer', 'default_from', 'autofill', 'query', 'attribute',
|
2012-03-15 03:32:37 -05:00
|
|
|
'include', 'exclude', 'flags', 'hint', 'alwaysask', 'sortorder',
|
2013-02-14 10:34:16 -06:00
|
|
|
'csv', 'option_group'],
|
2011-11-21 09:50:27 -06:00
|
|
|
'ipalib.parameters.Bool': ['truths', 'falsehoods'],
|
|
|
|
'ipalib.parameters.Data': ['minlength', 'maxlength', 'length',
|
|
|
|
'pattern', 'pattern_errmsg'],
|
2013-01-28 07:55:20 -06:00
|
|
|
'ipalib.parameters.Str': ['noextrawhitespace'],
|
|
|
|
'ipalib.parameters.Password': ['confirm'],
|
2011-11-21 09:50:27 -06:00
|
|
|
'ipalib.parameters.File': ['stdin_if_missing'],
|
2013-01-28 07:55:20 -06:00
|
|
|
'ipalib.plugins.dns.DNSRecord': ['validatedns', 'normalizedns'],
|
|
|
|
'ipalib.parameters.Enum': ['values'],
|
|
|
|
'ipalib.parameters.Number': ['minvalue', 'maxvalue'],
|
|
|
|
'ipalib.parameters.Decimal': ['precision', 'exponential',
|
|
|
|
'numberclass'],
|
|
|
|
'ipalib.plugable.API': NAMESPACE_ATTRS + LOGGING_ATTRS,
|
|
|
|
'ipalib.plugable.Plugin': ['api', 'env'] + NAMESPACE_ATTRS +
|
|
|
|
LOGGING_ATTRS,
|
|
|
|
'ipalib.session.AuthManager': LOGGING_ATTRS,
|
|
|
|
'ipalib.session.SessionAuthManager': LOGGING_ATTRS,
|
|
|
|
'ipalib.session.SessionManager': LOGGING_ATTRS,
|
|
|
|
'ipaserver.install.ldapupdate.LDAPUpdate': LOGGING_ATTRS,
|
|
|
|
'ipaserver.rpcserver.KerberosSession': ['api'] + LOGGING_ATTRS,
|
2011-04-11 08:39:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
def _related_classes(self, klass):
|
|
|
|
yield klass
|
|
|
|
for base in klass.ancestors():
|
|
|
|
yield base
|
|
|
|
|
|
|
|
def _class_full_name(self, klass):
|
|
|
|
return klass.root().name + '.' + klass.name
|
|
|
|
|
|
|
|
def _find_ignored_attrs(self, owner):
|
|
|
|
attrs = []
|
|
|
|
for klass in self._related_classes(owner):
|
|
|
|
name = self._class_full_name(klass)
|
|
|
|
if name in self.ignore:
|
|
|
|
attrs += self.ignore[name]
|
|
|
|
return attrs
|
|
|
|
|
|
|
|
def visit_getattr(self, node):
|
|
|
|
try:
|
2011-04-28 11:32:25 -05:00
|
|
|
inferred = list(node.expr.infer())
|
2011-04-11 08:39:38 -05:00
|
|
|
except InferenceError:
|
2011-04-28 11:32:25 -05:00
|
|
|
inferred = []
|
2011-04-11 08:39:38 -05:00
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
for owner in inferred:
|
2011-11-30 08:08:23 -06:00
|
|
|
if not isinstance(owner, Class) and type(owner) is not Instance:
|
2011-04-11 08:39:38 -05:00
|
|
|
continue
|
|
|
|
|
|
|
|
ignored = self._find_ignored_attrs(owner)
|
|
|
|
for pattern in ignored:
|
|
|
|
if fnmatchcase(node.attrname, pattern):
|
|
|
|
return
|
|
|
|
|
|
|
|
super(IPATypeChecker, self).visit_getattr(node)
|
|
|
|
|
|
|
|
class IPALinter(PyLinter):
|
|
|
|
ignore = (TypeChecker,)
|
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
def __init__(self):
|
|
|
|
super(IPALinter, self).__init__()
|
|
|
|
|
|
|
|
self.missing = set()
|
|
|
|
|
2011-04-11 08:39:38 -05:00
|
|
|
def register_checker(self, checker):
|
|
|
|
if type(checker) in self.ignore:
|
|
|
|
return
|
|
|
|
super(IPALinter, self).register_checker(checker)
|
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
def add_message(self, msg_id, line=None, node=None, args=None):
|
|
|
|
if line is None and node is not None:
|
|
|
|
line = node.fromlineno
|
2011-04-11 08:39:38 -05:00
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
# Record missing packages
|
|
|
|
if msg_id == 'F0401' and self.is_message_enabled(msg_id, line):
|
|
|
|
self.missing.add(args)
|
|
|
|
|
|
|
|
super(IPALinter, self).add_message(msg_id, line, node, args)
|
|
|
|
|
|
|
|
def find_files(path, basepath):
|
2011-04-11 08:39:38 -05:00
|
|
|
entries = os.listdir(path)
|
|
|
|
|
|
|
|
# If this directory is a python package, look no further
|
|
|
|
if '__init__.py' in entries:
|
|
|
|
return [path]
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for filename in entries:
|
2011-04-28 11:32:25 -05:00
|
|
|
filepath = os.path.join(path, filename)
|
|
|
|
|
2011-04-11 08:39:38 -05:00
|
|
|
for pattern in IGNORE_FILES:
|
|
|
|
if fnmatch(filename, pattern):
|
|
|
|
filename = None
|
|
|
|
break
|
2011-04-28 11:32:25 -05:00
|
|
|
if filename is None:
|
2011-04-11 08:39:38 -05:00
|
|
|
continue
|
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
for pattern in IGNORE_PATHS:
|
|
|
|
patpath = os.path.join(basepath, pattern).replace(os.sep, '/')
|
|
|
|
if filepath == patpath:
|
|
|
|
filename = None
|
|
|
|
break
|
|
|
|
if filename is None:
|
|
|
|
continue
|
2011-04-11 08:39:38 -05:00
|
|
|
|
|
|
|
if os.path.islink(filepath):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Recurse into subdirectories
|
|
|
|
if os.path.isdir(filepath):
|
|
|
|
result += find_files(filepath, basepath)
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Add all *.py files
|
|
|
|
if filename.endswith('.py'):
|
|
|
|
result.append(filepath)
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Add any other files beginning with a shebang and having
|
|
|
|
# the word "python" on the first line
|
|
|
|
file = open(filepath, 'r')
|
|
|
|
line = file.readline(128)
|
|
|
|
file.close()
|
|
|
|
|
|
|
|
if line[:2] == '#!' and line.find('python') >= 0:
|
|
|
|
result.append(filepath)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
def main():
|
|
|
|
optparser = OptionParser()
|
|
|
|
optparser.add_option('--no-fail', help='report success even if errors were found',
|
|
|
|
dest='fail', default=True, action='store_false')
|
|
|
|
optparser.add_option('--enable-noerror', help='enable warnings and other non-error messages',
|
|
|
|
dest='errors_only', default=True, action='store_false')
|
|
|
|
|
|
|
|
options, args = optparser.parse_args()
|
|
|
|
cwd = os.getcwd()
|
|
|
|
|
|
|
|
if len(args) == 0:
|
|
|
|
files = find_files(cwd, cwd)
|
|
|
|
else:
|
|
|
|
files = args
|
|
|
|
|
|
|
|
for filename in files:
|
|
|
|
dirname = os.path.dirname(filename)
|
|
|
|
if dirname not in sys.path:
|
|
|
|
sys.path.insert(0, dirname)
|
|
|
|
|
|
|
|
linter = IPALinter()
|
|
|
|
checkers.initialize(linter)
|
|
|
|
linter.register_checker(IPATypeChecker(linter))
|
|
|
|
|
|
|
|
if options.errors_only:
|
|
|
|
linter.disable_noerror_messages()
|
2011-04-28 11:32:25 -05:00
|
|
|
linter.enable('F')
|
2011-04-11 08:39:38 -05:00
|
|
|
linter.set_reporter(ParseableTextReporter())
|
|
|
|
linter.set_option('include-ids', True)
|
|
|
|
linter.set_option('reports', False)
|
2011-04-28 11:32:25 -05:00
|
|
|
linter.set_option('persistent', False)
|
2011-04-11 08:39:38 -05:00
|
|
|
|
|
|
|
linter.check(files)
|
|
|
|
|
2011-04-28 11:32:25 -05:00
|
|
|
if linter.msg_status != 0:
|
|
|
|
print >> sys.stderr, """
|
|
|
|
===============================================================================
|
|
|
|
Errors were found during the static code check.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if len(linter.missing) > 0:
|
|
|
|
print >> sys.stderr, "There are some missing imports:"
|
|
|
|
for mod in sorted(linter.missing):
|
|
|
|
print >> sys.stderr, " " + mod
|
|
|
|
print >> sys.stderr, """
|
|
|
|
Please make sure all of the required and optional (python-krbV, python-rhsm)
|
|
|
|
python packages are installed.
|
|
|
|
"""
|
|
|
|
|
|
|
|
print >> sys.stderr, """\
|
|
|
|
If you are certain that any of the reported errors are false positives, please
|
|
|
|
mark them in the source code according to the pylint documentation.
|
|
|
|
===============================================================================
|
|
|
|
"""
|
|
|
|
|
2011-04-11 08:39:38 -05:00
|
|
|
if options.fail:
|
|
|
|
return linter.msg_status
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(main())
|