xref: /spdk/scripts/check_format.sh (revision 91a594ad8b3c0d00f25c9a20dfc8f1f2dfce81ce)
1#!/usr/bin/env bash
2
3readonly BASEDIR=$(readlink -f $(dirname $0))/..
4cd $BASEDIR
5
6# exit on errors
7set -e
8
9if ! hash nproc 2> /dev/null; then
10
11	function nproc() {
12		echo 8
13	}
14
15fi
16
17function version_lt() {
18	[ $(echo -e "$1\n$2" | sort -V | head -1) != "$1" ]
19}
20
21rc=0
22
23echo -n "Checking file permissions..."
24
25while read -r perm _res0 _res1 path; do
26	if [ ! -f "$path" ]; then
27		continue
28	fi
29
30	fname=$(basename -- "$path")
31
32	case ${fname##*.} in
33		c | h | cpp | cc | cxx | hh | hpp | md | html | js | json | svg | Doxyfile | yml | LICENSE | README | conf | in | Makefile | mk | gitignore | go | txt)
34			# These file types should never be executable
35			if [ "$perm" -eq 100755 ]; then
36				echo "ERROR: $path is marked executable but is a code file."
37				rc=1
38			fi
39			;;
40		*)
41			shebang=$(head -n 1 $path | cut -c1-3)
42
43			# git only tracks the execute bit, so will only ever return 755 or 644 as the permission.
44			if [ "$perm" -eq 100755 ]; then
45				# If the file has execute permission, it should start with a shebang.
46				if [ "$shebang" != "#!/" ]; then
47					echo "ERROR: $path is marked executable but does not start with a shebang."
48					rc=1
49				fi
50			else
51				# If the file doesnot have execute permissions, it should not start with a shebang.
52				if [ "$shebang" = "#!/" ]; then
53					echo "ERROR: $path is not marked executable but starts with a shebang."
54					rc=1
55				fi
56			fi
57			;;
58	esac
59
60done <<< "$(git grep -I --name-only --untracked -e . | git ls-files -s)"
61
62if [ $rc -eq 0 ]; then
63	echo " OK"
64fi
65
66if hash astyle; then
67	echo -n "Checking coding style..."
68	if [ "$(astyle -V)" \< "Artistic Style Version 3" ]; then
69		echo -n " Your astyle version is too old so skipping coding style checks. Please update astyle to at least 3.0.1 version..."
70	else
71		rm -f astyle.log
72		touch astyle.log
73		# Exclude rte_vhost code imported from DPDK - we want to keep the original code
74		#  as-is to enable ongoing work to synch with a generic upstream DPDK vhost library,
75		#  rather than making diffs more complicated by a lot of changes to follow SPDK
76		#  coding standards.
77		git ls-files '*.[ch]' '*.cpp' '*.cc' '*.cxx' '*.hh' '*.hpp' \
78			| grep -v rte_vhost | grep -v cpp_headers \
79			| xargs -P$(nproc) -n10 astyle --options=.astylerc >> astyle.log
80		if grep -q "^Formatted" astyle.log; then
81			echo " errors detected"
82			git diff
83			sed -i -e 's/  / /g' astyle.log
84			grep --color=auto "^Formatted.*" astyle.log
85			echo "Incorrect code style detected in one or more files."
86			echo "The files have been automatically formatted."
87			echo "Remember to add the files to your commit."
88			rc=1
89		else
90			echo " OK"
91		fi
92		rm -f astyle.log
93	fi
94else
95	echo "You do not have astyle installed so your code style is not being checked!"
96fi
97
98GIT_VERSION=$(git --version | cut -d' ' -f3)
99
100if version_lt "1.9.5" "${GIT_VERSION}"; then
101	# git <1.9.5 doesn't support pathspec magic exclude
102	echo " Your git version is too old to perform all tests. Please update git to at least 1.9.5 version..."
103	exit 0
104fi
105
106echo -n "Checking comment style..."
107
108git grep --line-number -e '/[*][^ *-]' -- '*.[ch]' > comment.log || true
109git grep --line-number -e '[^ ][*]/' -- '*.[ch]' ':!lib/rte_vhost*/*' >> comment.log || true
110git grep --line-number -e '^[*]' -- '*.[ch]' >> comment.log || true
111git grep --line-number -e '\s//' -- '*.[ch]' >> comment.log || true
112git grep --line-number -e '^//' -- '*.[ch]' >> comment.log || true
113
114if [ -s comment.log ]; then
115	echo " Incorrect comment formatting detected"
116	cat comment.log
117	rc=1
118else
119	echo " OK"
120fi
121rm -f comment.log
122
123echo -n "Checking for spaces before tabs..."
124git grep --line-number $' \t' -- './*' ':!*.patch' > whitespace.log || true
125if [ -s whitespace.log ]; then
126	echo " Spaces before tabs detected"
127	cat whitespace.log
128	rc=1
129else
130	echo " OK"
131fi
132rm -f whitespace.log
133
134echo -n "Checking trailing whitespace in output strings..."
135
136git grep --line-number -e ' \\n"' -- '*.[ch]' > whitespace.log || true
137
138if [ -s whitespace.log ]; then
139	echo " Incorrect trailing whitespace detected"
140	cat whitespace.log
141	rc=1
142else
143	echo " OK"
144fi
145rm -f whitespace.log
146
147echo -n "Checking for use of forbidden library functions..."
148
149git grep --line-number -w '\(atoi\|atol\|atoll\|strncpy\|strcpy\|strcat\|sprintf\|vsprintf\)' -- './*.c' ':!lib/rte_vhost*/**' > badfunc.log || true
150if [ -s badfunc.log ]; then
151	echo " Forbidden library functions detected"
152	cat badfunc.log
153	rc=1
154else
155	echo " OK"
156fi
157rm -f badfunc.log
158
159echo -n "Checking for use of forbidden CUnit macros..."
160
161git grep --line-number -w 'CU_ASSERT_FATAL' -- 'test/*' ':!test/spdk_cunit.h' > badcunit.log || true
162if [ -s badcunit.log ]; then
163	echo " Forbidden CU_ASSERT_FATAL usage detected - use SPDK_CU_ASSERT_FATAL instead"
164	cat badcunit.log
165	rc=1
166else
167	echo " OK"
168fi
169rm -f badcunit.log
170
171echo -n "Checking blank lines at end of file..."
172
173if ! git grep -I -l -e . -z './*' ':!*.patch' \
174	| xargs -0 -P$(nproc) -n1 scripts/eofnl > eofnl.log; then
175	echo " Incorrect end-of-file formatting detected"
176	cat eofnl.log
177	rc=1
178else
179	echo " OK"
180fi
181rm -f eofnl.log
182
183echo -n "Checking for POSIX includes..."
184git grep -I -i -f scripts/posix.txt -- './*' ':!include/spdk/stdinc.h' ':!include/linux/**' ':!lib/rte_vhost*/**' ':!scripts/posix.txt' ':!*.patch' > scripts/posix.log || true
185if [ -s scripts/posix.log ]; then
186	echo "POSIX includes detected. Please include spdk/stdinc.h instead."
187	cat scripts/posix.log
188	rc=1
189else
190	echo " OK"
191fi
192rm -f scripts/posix.log
193
194echo -n "Checking #include style..."
195git grep -I -i --line-number "#include <spdk/" -- '*.[ch]' > scripts/includes.log || true
196if [ -s scripts/includes.log ]; then
197	echo "Incorrect #include syntax. #includes of spdk/ files should use quotes."
198	cat scripts/includes.log
199	rc=1
200else
201	echo " OK"
202fi
203rm -f scripts/includes.log
204
205if hash pycodestyle 2> /dev/null; then
206	PEP8=pycodestyle
207elif hash pep8 2> /dev/null; then
208	PEP8=pep8
209fi
210
211if [ -n "${PEP8}" ]; then
212	echo -n "Checking Python style..."
213
214	PEP8_ARGS+=" --max-line-length=140"
215
216	error=0
217	git ls-files '*.py' | xargs -P$(nproc) -n1 $PEP8 $PEP8_ARGS > pep8.log || error=1
218	if [ $error -ne 0 ]; then
219		echo " Python formatting errors detected"
220		cat pep8.log
221		rc=1
222	else
223		echo " OK"
224	fi
225	rm -f pep8.log
226else
227	echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!"
228fi
229
230shfmt="shfmt-3.1.0"
231if hash "$shfmt" 2> /dev/null; then
232	shfmt_cmdline=() silly_plural=()
233
234	silly_plural[1]="s"
235
236	commits=() sh_files=() sh_files_repo=() sh_files_staged=()
237
238	mapfile -t sh_files_repo < <(git ls-files '*.sh')
239	# Fetch .sh files only from the commits that are targeted for merge
240	while read -r _ commit; do
241		commits+=("$commit")
242	done < <(git cherry -v origin/master)
243
244	mapfile -t sh_files < <(git diff --name-only HEAD origin/master "${sh_files_repo[@]}")
245	# In case of a call from a pre-commit git hook
246	mapfile -t sh_files_staged < <(
247		IFS="|"
248		git diff --cached --name-only "${sh_files_repo[@]}" | grep -v "${sh_files[*]}"
249	)
250
251	if ((${#sh_files[@]})); then
252		printf 'Checking .sh formatting style...\n\n'
253		printf '  Found %u updated|new .sh file%s from the following commits:\n' \
254			"${#sh_files[@]}" "${silly_plural[${#sh_files[@]} > 1 ? 1 : 0]}"
255		printf '   * %s\n' "${commits[@]}"
256
257		if ((${#sh_files_staged[@]})); then
258			printf '  Found %u .sh file%s in staging area:\n' \
259				"${#sh_files_staged[@]}" "${silly_plural[${#sh_files_staged[@]} > 1 ? 1 : 0]}"
260			printf '    * %s\n' "${sh_files_staged[@]}"
261			sh_files+=("${sh_files_staged[@]}")
262		fi
263
264		printf '  Running %s against the following file%s:\n' "$shfmt" "${silly_plural[${#sh_files[@]} > 1 ? 1 : 0]}"
265		printf '   * %s\n' "${sh_files[@]}"
266
267		shfmt_cmdline+=(-i 0)     # indent_style = tab|indent_size = 0
268		shfmt_cmdline+=(-bn)      # binary_next_line = true
269		shfmt_cmdline+=(-ci)      # switch_case_indent = true
270		shfmt_cmdline+=(-ln bash) # shell_variant = bash (default)
271		shfmt_cmdline+=(-d)       # diffOut - print diff of the changes and exit with != 0
272		shfmt_cmdline+=(-sr)      # redirect operators will be followed by a space
273
274		diff=$PWD/$shfmt.patch
275
276		# Explicitly tell shfmt to not look for .editorconfig. .editorconfig is also not looked up
277		# in case any formatting arguments has been passed on its cmdline.
278		if ! SHFMT_NO_EDITORCONFIG=true "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then
279			# In case shfmt detects an actual syntax error it will write out a proper message on
280			# its stderr, hence the diff file should remain empty.
281			if [[ -s $diff ]]; then
282				diff_out=$(< "$diff")
283			fi
284
285			cat <<- ERROR_SHFMT
286
287				* Errors in style formatting have been detected.
288				${diff_out:+* Please, review the generated patch at $diff
289
290				# _START_OF_THE_DIFF
291
292				${diff_out:-ERROR}
293
294				# _END_OF_THE_DIFF
295				}
296
297			ERROR_SHFMT
298			rc=1
299		else
300			rm -f "$diff"
301			printf 'OK\n'
302		fi
303	fi
304else
305	printf '%s not detected, Bash style formatting check is skipped\n' "$shfmt"
306fi
307
308if hash shellcheck 2> /dev/null; then
309	echo -n "Checking Bash style..."
310
311	shellcheck_v=$(shellcheck --version | grep -P "version: [0-9\.]+" | cut -d " " -f2)
312
313	# SHCK_EXCLUDE contains a list of all of the spellcheck errors found in SPDK scripts
314	# currently. New errors should only be added to this list if the cost of fixing them
315	# is deemed too high. For more information about the errors, go to:
316	# https://github.com/koalaman/shellcheck/wiki/Checks
317	# Error descriptions can also be found at: https://github.com/koalaman/shellcheck/wiki
318	# SPDK fails some error checks which have been deprecated in later versions of shellcheck.
319	# We will not try to fix these error checks, but instead just leave the error types here
320	# so that we can still run with older versions of shellcheck.
321	SHCK_EXCLUDE="SC1117"
322	# SPDK has decided to not fix violations of these errors.
323	# We are aware about below exclude list and we want this errors to be excluded.
324	# SC1083: This {/} is literal. Check expression (missing ;/\n?) or quote it.
325	# SC1090: Can't follow non-constant source. Use a directive to specify location.
326	# SC1091: Not following: (error message here)
327	# SC2001: See if you can use ${variable//search/replace} instead.
328	# SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.
329	# SC2015: Note that A && B || C is not if-then-else. C may run when A is true.
330	# SC2016: Expressions don't expand in single quotes, use double quotes for that.
331	# SC2034: foo appears unused. Verify it or export it.
332	# SC2046: Quote this to prevent word splitting.
333	# SC2086: Double quote to prevent globbing and word splitting.
334	# SC2119: Use foo "$@" if function's $1 should mean script's $1.
335	# SC2120: foo references arguments, but none are ever passed.
336	# SC2148: Add shebang to the top of your script.
337	# SC2153: Possible Misspelling: MYVARIABLE may not be assigned, but MY_VARIABLE is.
338	# SC2154: var is referenced but not assigned.
339	# SC2164: Use cd ... || exit in case cd fails.
340	# SC2174: When used with -p, -m only applies to the deepest directory.
341	# SC2206: Quote to prevent word splitting/globbing,
342	#         or split robustly with mapfile or read -a.
343	# SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
344	# SC2223: This default assignment may cause DoS due to globbing. Quote it.
345	SHCK_EXCLUDE="$SHCK_EXCLUDE,SC1083,SC1090,SC1091,SC2010,SC2015,SC2016,SC2034,SC2046,SC2086,\
346SC2119,SC2120,SC2148,SC2153,SC2154,SC2164,SC2174,SC2001,SC2206,SC2207,SC2223"
347
348	SHCK_FORMAT="diff"
349	SHCK_APPLY=true
350	if [ "$shellcheck_v" \< "0.7.0" ]; then
351		SHCK_FORMAT="tty"
352		SHCK_APPLY=false
353	fi
354	SHCH_ARGS=" -x -e $SHCK_EXCLUDE -f $SHCK_FORMAT"
355
356	error=0
357	git ls-files '*.sh' | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS &> shellcheck.log || error=1
358	if [ $error -ne 0 ]; then
359		echo " Bash formatting errors detected!"
360
361		# Some errors are not auto-fixable. Fall back to tty output.
362		if grep -q "Use another format to see them." shellcheck.log; then
363			SHCK_FORMAT="tty"
364			SHCK_APPLY=false
365			SHCH_ARGS=" -e $SHCK_EXCLUDE -f $SHCK_FORMAT"
366			git ls-files '*.sh' | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS > shellcheck.log || error=1
367		fi
368
369		cat shellcheck.log
370		if $SHCK_APPLY; then
371			git apply shellcheck.log
372			echo "Bash errors were automatically corrected."
373			echo "Please remember to add the changes to your commit."
374		fi
375		rc=1
376	else
377		echo " OK"
378	fi
379	rm -f shellcheck.log
380else
381	echo "You do not have shellcheck installed so your Bash style is not being checked!"
382fi
383
384# Check if any of the public interfaces were modified by this patch.
385# Warn the user to consider updating the changelog any changes
386# are detected.
387echo -n "Checking whether CHANGELOG.md should be updated..."
388staged=$(git diff --name-only --cached .)
389working=$(git status -s --porcelain --ignore-submodules | grep -iv "??" | awk '{print $2}')
390files="$staged $working"
391if [[ "$files" = " " ]]; then
392	files=$(git diff-tree --no-commit-id --name-only -r HEAD)
393fi
394
395has_changelog=0
396for f in $files; do
397	if [[ $f == CHANGELOG.md ]]; then
398		# The user has a changelog entry, so exit.
399		has_changelog=1
400		break
401	fi
402done
403
404needs_changelog=0
405if [ $has_changelog -eq 0 ]; then
406	for f in $files; do
407		if [[ $f == include/spdk/* ]] || [[ $f == scripts/rpc.py ]] || [[ $f == etc/* ]]; then
408			echo ""
409			echo -n "$f was modified. Consider updating CHANGELOG.md."
410			needs_changelog=1
411		fi
412	done
413fi
414
415if [ $needs_changelog -eq 0 ]; then
416	echo " OK"
417else
418	echo ""
419fi
420
421exit $rc
422