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