lint-shell.py raw
1 #!/usr/bin/env python3
2 #
3 # Copyright (c) 2018-present The Bitcoin Core developers
4 # Distributed under the MIT software license, see the accompanying
5 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
6
7 """
8 Check for shellcheck warnings in shell scripts.
9 """
10
11 import subprocess
12 import re
13 import sys
14
15 # Disabled warnings:
16 DISABLED = [
17 'SC2162', # read without -r will mangle backslashes.
18 ]
19
20 def check_shellcheck_install():
21 try:
22 subprocess.run(['shellcheck', '--version'], stdout=subprocess.DEVNULL, check=True)
23 except FileNotFoundError:
24 print('Skipping shell linting since shellcheck is not installed.')
25 sys.exit(0)
26
27 def get_files(command):
28 output = subprocess.run(command, stdout=subprocess.PIPE, text=True)
29 files = output.stdout.split('\n')
30
31 # remove whitespace element
32 files = list(filter(None, files))
33 return files
34
35 def main():
36 check_shellcheck_install()
37
38 # build the `exclude` flag
39 exclude = '--exclude=' + ','.join(DISABLED)
40
41 # build the `sourced files` list
42 sourced_files_cmd = [
43 'git',
44 'grep',
45 '-El',
46 r'^# shellcheck shell=',
47 ]
48 sourced_files = get_files(sourced_files_cmd)
49
50 # build the `guix files` list
51 guix_files_cmd = [
52 'git',
53 'grep',
54 '-El',
55 r'^#!\/usr\/bin\/env bash',
56 '--',
57 'contrib/guix',
58 'contrib/shell',
59 ]
60 guix_files = get_files(guix_files_cmd)
61
62 # build the other script files list
63 files_cmd = [
64 'git',
65 'ls-files',
66 '--',
67 '*.sh',
68 ]
69 files = get_files(files_cmd)
70 reg = re.compile(r'src/[leveldb,secp256k1,minisketch]')
71
72 def should_exclude(fname: str) -> bool:
73 return bool(reg.match(fname))
74
75 # remove everything that doesn't match this regex
76 files[:] = [file for file in files if not should_exclude(file)]
77
78 # build the `shellcheck` command
79 shellcheck_cmd = [
80 'shellcheck',
81 '--external-sources',
82 '--check-sourced',
83 '--source-path=SCRIPTDIR',
84 ]
85 shellcheck_cmd.append(exclude)
86 shellcheck_cmd.extend(sourced_files)
87 shellcheck_cmd.extend(guix_files)
88 shellcheck_cmd.extend(files)
89
90 # run the `shellcheck` command
91 try:
92 subprocess.check_call(shellcheck_cmd)
93 except subprocess.CalledProcessError:
94 sys.exit(1)
95
96 if __name__ == '__main__':
97 main()
98