blob: f35514fdfd3cbef33d1a669301ede90b802ec97e [file] [log] [blame]
Lukasz Dalek997f1f32021-06-17 18:01:27 +02001#!/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
6import sys
7import os
8import argparse
9import subprocess
10import logging
11import difflib
12
13logger = logging.getLogger('verible_format')
14
15VERIBLE_ARGS = ["--formal_parameters_indentation=indent",
16 "--named_parameter_indentation=indent",
17 "--named_port_indentation=indent",
18 "--port_declarations_indentation=indent"]
19
20
21def get_repo_top():
22 return subprocess.run(['git', 'rev-parse', '--show-toplevel'],
23 check=True, universal_newlines=True,
24 stdout=subprocess.PIPE).stdout.strip()
25
26
27def get_verible_executable_path():
28 try:
29 return subprocess.run(['which', 'verible-verilog-format'],
30 check=True, universal_newlines=True,
31 stdout=subprocess.PIPE).stdout.strip()
32 except subprocess.CalledProcessError:
33 return None
34
35
36def get_verible_version(verible_exec_path):
37 return subprocess.run([verible_exec_path, '--version'],
38 check=True, universal_newlines=True,
39 stdout=subprocess.PIPE).stdout.strip().split('\n')[0]
40
41
42def process_file(filename_abs, verible_exec_path,
43 inplace=False, show_diff=False, show_cst=False):
44 args = [verible_exec_path] + VERIBLE_ARGS
45 if inplace:
46 args.append('--inplace')
47 args.append(filename_abs)
48
49 verible = subprocess.run(args, check=False, universal_newlines=True,
50 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
51
52 ret = 0
53
54 if len(verible.stderr) > 0:
55 logger.info('ERROR: ' + str(filename_abs))
56 logger.debug(verible.stderr)
57 ret = 1
58 elif not inplace:
59 with open(filename_abs, 'r') as fp:
60 orig = fp.read().split('\n')
61
62 formatted = verible.stdout.split('\n')
63
64 diff = list(difflib.unified_diff(orig, formatted, lineterm='', n=3,
65 fromfile=filename_abs,
66 tofile=filename_abs + '.formatted'))
67 ret = len(diff) > 0
68
69 if not show_diff and len(diff) > 0:
70 logger.info('File differs: ' + filename_abs)
71
72 if show_diff and len(diff) > 0:
73 logger.info('')
74 for line in diff:
75 logger.info(line)
76
77 if len(diff) > 0 and show_cst:
78 cst = subprocess.run(['verible-verilog-syntax',
79 '--printtree', filename_abs],
80 check=False, universal_newlines=True,
81 stdout=subprocess.PIPE)
82 logger.info(cst.stdout)
83
84 return ret
85
86
87def main():
88 parser = argparse.ArgumentParser(description='Format source code with Verible formatter')
89 parser.add_argument('-q', '--quiet', action='store_true', default=False,
90 help='print only errors and warnings')
91 parser.add_argument('-v', '--verbose', action='store_true', default=False,
92 help='print extra debug messages')
93 parser.add_argument('--show-diff', action='store_true', default=False,
94 help='print diff (when files differ)')
95 parser.add_argument('--show-cst', action='store_true', default=False,
96 help='print CST tree')
97 parser.add_argument('--progress', action='store_true', default=False,
98 help='show progress')
99 parser.add_argument('--inplace', action='store_true', default=False,
100 help='format files in place (overwrite original files)')
101 parser.add_argument('-l', '--allowlist', action='store_true', default=False,
102 help='process only files from allow list')
103 parser.add_argument('-a', '--all', action='store_true', default=False,
104 help='process all files in repository (.sv and .svh)')
105 parser.add_argument('-f', '--files', metavar='file', type=str, nargs='+', default=[],
106 help='process provided files')
107 args = parser.parse_args()
108
109 if not args.quiet:
110 logger.addHandler(logging.StreamHandler())
111 logger.setLevel(logging.INFO)
112
113 if args.verbose:
114 logger.setLevel(logging.DEBUG)
115
116 if args.inplace:
117 logger.warning('WARNING: Operating in-place - so make sure to make a backup or '
118 'run this on an experimental branch')
119
120 verible_exec_path = get_verible_executable_path()
121
122 if not verible_exec_path:
123 logger.error('verible-verilog-format either not installed or not visible in PATH')
124 sys.exit(1)
125 else:
126 logger.info('Using Verible formatter version: ' + get_verible_version(verible_exec_path))
127
128 logger.debug('repo_top: ' + get_repo_top())
129 logger.debug('verible exec: ' + verible_exec_path)
130 logger.debug('verible args: ' + str(VERIBLE_ARGS))
131
132 repo_top_dir = get_repo_top()
133
134 verible_files = []
135
136 for f in args.files:
137 filename_abs = f
138
139 # Prepend repository absolute top path if relative path given
140 if f[0:1] != '/':
141 filename_abs = repo_top_dir + '/' + filename_abs
142
143 if os.path.exists(filename_abs):
144 verible_files.append(filename_abs)
145
146 if args.allowlist:
147 with open(repo_top_dir + '/util/verible-format-allowlist.txt', 'r') as fp:
148 for line in fp:
149 if line[0] == '#':
150 continue
151
152 filename = line.strip()
153
154 if len(filename) == 0:
155 continue
156
157 filename_abs = repo_top_dir + '/' + filename
158 if os.path.exists(filename_abs):
159 verible_files.append(filename_abs)
160
161 if args.all:
162 for root, dirs, files in os.walk(repo_top_dir):
163 # TODO(ldk): Skip '/hw/vendor' and '/hw/top_*'?
164 for f in files:
165 if not (f.endswith('.sv') or f.endswith('.svh')):
166 continue
167
168 filename_abs = root + '/' + f
169 if os.path.exists(filename_abs):
170 verible_files.append(filename_abs)
171
172 logger.debug('files to format: ' + str(verible_files))
173
174 ret = 0
175 for i, f in enumerate(verible_files):
176 r = process_file(f, verible_exec_path,
177 inplace=args.inplace,
178 show_diff=args.show_diff,
179 show_cst=args.show_cst)
180 ret = ret + r
181
182 if args.progress:
183 print(f'\rProcessed: {i} / {len(verible_files)}\r', end='')
184
185 if args.progress:
186 print('')
187
188 return ret > 0
189
190
191if __name__ == "__main__":
192 exit_code = main()
193 sys.exit(exit_code)