1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Run lint checks on the specified files."""
10 from chromite.cbuildbot import constants
11 from chromite.lib import cros_build_lib
12 from chromite.lib import git
14 from chromite import cros
17 PYTHON_EXTENSIONS = frozenset(['.py'])
19 # Note these are defined to keep in line with cpplint.py. Technically, we could
20 # include additional ones, but cpplint.py would just filter them out.
21 CPP_EXTENSIONS = frozenset(['.cc', '.cpp', '.h'])
24 def _GetProjectPath(path):
25 """Find the absolute path of the git checkout that contains |path|."""
26 if git.FindRepoCheckoutRoot(path):
27 manifest = git.ManifestCheckout.Cached(path)
28 return manifest.FindCheckoutFromPath(path).GetPath(absolute=True)
30 # Maybe they're running on a file outside of a checkout.
31 # e.g. cros lint ~/foo.py /tmp/test.py
32 return os.path.dirname(path)
35 def _GetPylintGroups(paths):
36 """Return a dictionary mapping pylintrc files to lists of paths."""
39 if path.endswith('.py'):
40 path = os.path.realpath(path)
41 project_path = _GetProjectPath(path)
42 parent = os.path.dirname(path)
43 while project_path and parent.startswith(project_path):
44 pylintrc = os.path.join(parent, 'pylintrc')
45 if os.path.isfile(pylintrc):
47 parent = os.path.dirname(parent)
48 if project_path is None or not os.path.isfile(pylintrc):
49 pylintrc = os.path.join(constants.SOURCE_ROOT, 'chromite', 'pylintrc')
50 groups.setdefault(pylintrc, []).append(path)
54 def _GetPythonPath(paths):
55 """Return the set of Python library paths to use."""
57 # Add the Portage installation inside the chroot to the Python path.
58 # This ensures that scripts that need to import portage can do so.
59 os.path.join(constants.SOURCE_ROOT, 'chroot', 'usr', 'lib', 'portage',
62 # Scripts outside of chromite expect the scripts in src/scripts/lib to
64 os.path.join(constants.CROSUTILS_DIR, 'lib'),
66 # Allow platform projects to be imported by name (e.g. crostestutils).
67 os.path.join(constants.SOURCE_ROOT, 'src', 'platform'),
69 # Ideally we'd modify meta_path in pylint to handle our virtual chromite
70 # module, but that's not possible currently. We'll have to deal with
71 # that at some point if we want `cros lint` to work when the dir is not
73 constants.SOURCE_ROOT,
75 # Also allow scripts to import from their current directory.
76 ] + list(set(os.path.dirname(x) for x in paths))
79 def _CpplintFiles(files, debug):
80 """Returns true if cpplint ran successfully on all files."""
81 cmd = ['cpplint.py'] + files
82 res = cros_build_lib.RunCommand(cmd,
85 return res.returncode != 0
88 def _PylintFiles(files, debug):
89 """Returns true if pylint ran successfully on all files."""
91 for pylintrc, paths in sorted(_GetPylintGroups(files).items()):
92 paths = sorted(list(set([os.path.realpath(x) for x in paths])))
93 cmd = ['pylint', '--rcfile=%s' % pylintrc] + paths
94 extra_env = {'PYTHONPATH': ':'.join(_GetPythonPath(paths))}
95 res = cros_build_lib.RunCommand(cmd, extra_env=extra_env,
98 if res.returncode != 0:
104 def _BreakoutFilesByLinter(files):
105 """Maps a linter method to the list of files to lint."""
108 extension = os.path.splitext(f)[1]
109 if extension in PYTHON_EXTENSIONS:
110 pylint_list = map_to_return.setdefault(_PylintFiles, [])
111 pylint_list.append(f)
112 elif extension in CPP_EXTENSIONS:
113 cpplint_list = map_to_return.setdefault(_CpplintFiles, [])
114 cpplint_list.append(f)
119 @cros.CommandDecorator('lint')
120 class LintCommand(cros.CrosCommand):
121 """Run lint checks on the specified files."""
124 Right now, only supports cpplint and pylint. We may also in the future
125 run other checks (e.g. pyflakes, etc.)
129 def AddParser(cls, parser):
130 super(LintCommand, cls).AddParser(parser)
131 parser.add_argument('files', help='Files to lint', nargs='*')
134 files = self.options.files
136 # Running with no arguments is allowed to make the repo upload hook
137 # simple, but print a warning so that if someone runs this manually
138 # they are aware that nothing was linted.
139 cros_build_lib.Warning('No files provided to lint. Doing nothing.')
142 linter_map = _BreakoutFilesByLinter(files)
143 for linter, files in linter_map.iteritems():
144 errors = linter(files, self.options.debug)