summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xscripts/verify-bashisms116
1 files changed, 116 insertions, 0 deletions
diff --git a/scripts/verify-bashisms b/scripts/verify-bashisms
new file mode 100755
index 0000000000..0741e18447
--- /dev/null
+++ b/scripts/verify-bashisms
@@ -0,0 +1,116 @@
1#!/usr/bin/env python3
2
3import sys, os, subprocess, re, shutil
4
5whitelist = (
6 # type is supported by dash
7 'if type systemctl >/dev/null 2>/dev/null; then',
8 'if type systemd-tmpfiles >/dev/null 2>/dev/null; then',
9 'if type update-rc.d >/dev/null 2>/dev/null; then',
10 'command -v',
11 # HOSTNAME is set locally
12 'buildhistory_single_commit "$CMDLINE" "$HOSTNAME"',
13 # False-positive, match is a grep not shell expression
14 'grep "^$groupname:[^:]*:[^:]*:\\([^,]*,\\)*$username\\(,[^,]*\\)*"',
15 # TODO verify dash's '. script args' behaviour
16 '. $target_sdk_dir/${oe_init_build_env_path} $target_sdk_dir >> $LOGFILE'
17 )
18
19def is_whitelisted(s):
20 for w in whitelist:
21 if w in s:
22 return True
23 return False
24
25def process(recipe, function, script):
26 import tempfile
27
28 if not script.startswith("#!"):
29 script = "#! /bin/sh\n" + script
30
31 fn = tempfile.NamedTemporaryFile(mode="w+t")
32 fn.write(script)
33 fn.flush()
34
35 try:
36 subprocess.check_output(("checkbashisms.pl", fn.name), universal_newlines=True, stderr=subprocess.STDOUT)
37 # No bashisms, so just return
38 return
39 except subprocess.CalledProcessError as e:
40 # TODO check exit code is 1
41
42 # Replace the temporary filename with the function and split it
43 output = e.output.replace(fn.name, function).splitlines()
44 if len(results) % 2 != 0:
45 print("Unexpected output from checkbashism: %s" % str(output))
46 return
47
48 # Turn the output into a list of (message, source) values
49 result = []
50 # Check the results against the whitelist
51 for message, source in zip(output[0::2], output[1::2]):
52 if not is_whitelisted(source):
53 result.append((message, source))
54 return result
55
56def get_tinfoil():
57 scripts_path = os.path.dirname(os.path.realpath(__file__))
58 lib_path = scripts_path + '/lib'
59 sys.path = sys.path + [lib_path]
60 import scriptpath
61 scriptpath.add_bitbake_lib_path()
62 import bb.tinfoil
63 tinfoil = bb.tinfoil.Tinfoil()
64 tinfoil.prepare()
65 # tinfoil.logger.setLevel(logging.WARNING)
66 return tinfoil
67
68if __name__=='__main__':
69 import shutil
70 if shutil.which("checkbashisms.pl") is None:
71 print("Cannot find checkbashisms.pl on $PATH")
72 sys.exit(1)
73
74 tinfoil = get_tinfoil()
75
76 # This is only the default configuration and should iterate over
77 # recipecaches to handle multiconfig environments
78 pkg_pn = tinfoil.cooker.recipecaches[""].pkg_pn
79
80 # TODO: use argparse and have --help
81 if len(sys.argv) > 1:
82 initial_pns = sys.argv[1:]
83 else:
84 initial_pns = sorted(pkg_pn)
85
86 pns = []
87 print("Generating file list...")
88 for pn in initial_pns:
89 for fn in pkg_pn[pn]:
90 # There's no point checking multiple BBCLASSEXTENDed variants of the same recipe
91 realfn, _, _ = bb.cache.virtualfn2realfn(fn)
92 if realfn not in pns:
93 pns.append(realfn)
94
95
96 def func(fn):
97 result = []
98 data = tinfoil.parse_recipe_file(fn)
99 for key in data.keys():
100 if data.getVarFlag(key, "func", True) and not data.getVarFlag(key, "python", True):
101 script = data.getVar(key, False)
102 if not script: continue
103 #print ("%s:%s" % (fn, key))
104 r = process(fn, key, script)
105 if r: result.extend(r)
106 return fn, result
107
108 print("Scanning scripts...\n")
109 import multiprocessing
110 pool = multiprocessing.Pool()
111 for pn,results in pool.imap(func, pns):
112 if results:
113 print(pn)
114 for message,source in results:
115 print(" %s\n %s" % (message, source))
116 print()