blob: fdaca5eb63d06afa66a7addb129f47da9ecf99e0 [file] [log] [blame]
lowRISC Contributors802543a2019-08-31 12:12:56 +01001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5"""Lint Python for lowRISC rules"""
6
7import argparse
8import os
9import subprocess
10import sys
11
12import pkg_resources
13
14
15# include here because in hook case don't want to import reggen
16def show_and_exit(clitool, packages):
17 util_path = os.path.dirname(os.path.realpath(clitool))
18 os.chdir(util_path)
19 ver = subprocess.run(
20 ["git", "describe", "--always", "--dirty", "--broken"],
21 stdout=subprocess.PIPE).stdout.strip().decode('ascii')
22 if (ver == ''):
23 ver = 'not found (not in Git repository?)'
24 sys.stderr.write(clitool + " Git version " + ver + '\n')
25 for p in packages:
26 sys.stderr.write(p + ' ' + pkg_resources.require(p)[0].version + '\n')
27 exit(0)
28
29
30def check_linter(cmd, cmdfix, dofix, verbose, files, **kwargs):
31 if not files:
32 return
33 if verbose:
34 print('Running %s' % cmd[0])
35 try:
36 subprocess.check_output(
37 cmd + files, stderr=subprocess.STDOUT, **kwargs)
38 return 0
39 except FileNotFoundError:
40 print('%s not found: do you need to install it?' % cmd[0])
41 return 1
42 except subprocess.CalledProcessError as exc:
43 print('Lint failed:', file=sys.stderr)
44 print(' '.join(exc.cmd), file=sys.stderr)
45 if exc.output:
46 output = exc.output.decode(sys.getfilesystemencoding())
47 print(
48 '\t',
49 '\n\t'.join(output.splitlines()),
50 sep='',
51 file=sys.stderr)
52 if dofix:
53 print("Fixing...", file=sys.stderr)
54 subprocess.check_output(
55 cmdfix + files, stderr=subprocess.STDOUT, **kwargs)
56 return 1
57
58
59def filter_ext(extension, files, exclude=None):
60 files = [f for f in files if f.endswith(extension)]
61 if exclude is not None:
62 files = [i for i in files if exclude not in i]
63 return files
64
65
66def lint_files(changed_files, dofix, verbose):
67 err = 0
68 if not isinstance(changed_files, list):
69 changed_files = [
70 i.strip() for i in changed_files.splitlines()
71 if '/external/' not in i
72 ]
73
74 changed_extensions = {
75 ext
76 for root, ext in map(os.path.splitext, changed_files)
77 }
78 if verbose:
79 print('Changed files: ' + str(changed_files))
80 print('Changed extensions: ' + str(changed_extensions))
81
82 if '.py' in changed_extensions:
83 py_files = filter_ext('.py', changed_files)
84 err += check_linter(['yapf', '-d'], ['yapf', '-i'], dofix, verbose,
85 py_files)
86 err += check_linter(['isort', '-c', '-w79'], ['isort', '-w79'], dofix,
87 verbose, py_files)
88
89 # could do similar checks for other file types
90 return err
91
92
93def main():
94 parser = argparse.ArgumentParser(
95 description=__doc__,
96 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
97 parser.add_argument(
98 '--version', action='store_true', help='Show version and exit')
99 parser.add_argument(
100 '-v',
101 '--verbose',
102 action='store_true',
103 help='Verbose output: ls the output directories')
104 parser.add_argument(
105 '-c',
106 '--commit',
107 action='store_true',
108 help='Only check files staged for commit rather than' \
109 'all modified files (forced when run as git hook)')
110 parser.add_argument(
111 '--fix', action='store_true', help='Fix files detected with problems')
112 parser.add_argument(
113 '--hook',
114 action='store_true',
115 help='Install as ../.git/hooks/pre-commit and exit')
116 parser.add_argument(
117 '-f',
118 '--file',
119 metavar='file',
120 nargs='+',
121 default=[],
122 help='File(s) to check instead of deriving from git')
123
124 args = parser.parse_args()
125 if args.version:
126 show_and_exit(__file__, ['yapf', 'isort'])
127
128 util_path = os.path.dirname(os.path.realpath(__file__))
129 repo_root = os.path.abspath(os.path.join(util_path, os.pardir))
130 # check for running as a hook out of $(TOP)/.git/hooks
131 # (symlink will already have this correct)
132 if repo_root.endswith('.git'):
133 repo_root = os.path.abspath(os.path.join(repo_root, os.pardir))
134 running_hook = sys.argv[0].endswith('hooks/pre-commit')
135
136 if args.verbose:
137 print('argv[0] is ' + sys.argv[0] + ' so running_hook is ' +
138 str(running_hook))
139 print('util_path is ' + util_path)
140 print('repo_root is ' + repo_root)
141
142 if len(args.file) > 0:
143 changed_files = args.file
144 else:
145
146 os.chdir(repo_root)
147 if not os.path.isdir(os.path.join(repo_root, '.git')):
148 print(
149 "Script not in expected location in a git repo",
150 file=sys.stderr)
151 sys.exit(1)
152
153 if args.hook:
154 subprocess.run(
155 'ln -s ../../util/lintpy.py .git/hooks/pre-commit'.split())
156 sys.exit(0)
157
158 if running_hook or args.commit:
159 diff_cmd = 'git diff --cached --name-only --diff-filter=ACM'
160 else:
161 diff_cmd = 'git diff --name-only --diff-filter=ACM'
162
163 changed_files = subprocess.check_output(diff_cmd.split())
164 changed_files = changed_files.decode(sys.getfilesystemencoding())
165
166 sys.exit(lint_files(changed_files, args.fix, args.verbose))
167
168
169if __name__ == "__main__":
170 main()