xref: /dpdk/devtools/checkpatches.sh (revision 33b84a2efca7ac188def108ba8b981daa7572b9a)
1#! /bin/sh
2# SPDX-License-Identifier: BSD-3-Clause
3# Copyright 2015 6WIND S.A.
4
5# Load config options:
6# - DPDK_CHECKPATCH_PATH
7# - DPDK_CHECKPATCH_CODESPELL
8# - DPDK_CHECKPATCH_LINE_LENGTH
9# - DPDK_CHECKPATCH_OPTIONS
10. $(dirname $(readlink -f $0))/load-devel-config
11
12VALIDATE_NEW_API=$(dirname $(readlink -f $0))/check-symbol-change.sh
13
14# Enable codespell by default. This can be overwritten from a config file.
15# Codespell can also be enabled by setting DPDK_CHECKPATCH_CODESPELL to a valid path
16# to a dictionary.txt file if dictionary.txt is not in the default location.
17codespell=${DPDK_CHECKPATCH_CODESPELL:-enable}
18length=${DPDK_CHECKPATCH_LINE_LENGTH:-100}
19
20# override default Linux options
21options="--no-tree"
22if [ "$codespell" = "enable" ] ; then
23    options="$options --codespell"
24elif [ -f "$codespell" ] ; then
25    options="$options --codespell"
26    options="$options --codespellfile $codespell"
27fi
28options="$options --max-line-length=$length"
29options="$options --show-types"
30options="$options --ignore=LINUX_VERSION_CODE,ENOSYS,\
31FILE_PATH_CHANGES,MAINTAINERS_STYLE,SPDX_LICENSE_TAG,\
32VOLATILE,PREFER_PACKED,PREFER_ALIGNED,PREFER_PRINTF,STRLCPY,\
33PREFER_KERNEL_TYPES,PREFER_FALLTHROUGH,BIT_MACRO,CONST_STRUCT,\
34SPLIT_STRING,LONG_LINE_STRING,C99_COMMENT_TOLERANCE,\
35LINE_SPACING,PARENTHESIS_ALIGNMENT,NETWORKING_BLOCK_COMMENT_STYLE,\
36NEW_TYPEDEFS,COMPARISON_TO_NULL"
37options="$options $DPDK_CHECKPATCH_OPTIONS"
38
39print_usage () {
40	cat <<- END_OF_HELP
41	usage: $(basename $0) [-h] [-q] [-v] [-nX|-r range|patch1 [patch2] ...]
42
43	Run Linux kernel checkpatch.pl with DPDK options.
44	The environment variable DPDK_CHECKPATCH_PATH can be set, if not we will
45	try to find the script in the sources of the currently running kernel.
46
47	The patches to check can be from stdin, files specified on the command line,
48	latest git commits limited with -n option, or commits in the git range
49	specified with -r option (default: "origin/main..").
50	END_OF_HELP
51}
52
53check_forbidden_additions() { # <patch>
54	res=0
55
56	# refrain from new additions of rte_panic() and rte_exit()
57	# multiple folders and expressions are separated by spaces
58	awk -v FOLDERS="lib drivers" \
59		-v EXPRESSIONS="rte_panic\\\( rte_exit\\\(" \
60		-v RET_ON_FAIL=1 \
61		-v MESSAGE='Using rte_panic/rte_exit' \
62		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
63		"$1" || res=1
64
65	# refrain from using compiler attribute without defining a common macro
66	awk -v FOLDERS="lib drivers app examples" \
67		-v EXPRESSIONS="__attribute__" \
68		-v RET_ON_FAIL=1 \
69		-v MESSAGE='Using compiler attribute directly' \
70		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
71		"$1" || res=1
72
73	# check %l or %ll format specifier
74	awk -v FOLDERS='lib drivers app examples' \
75		-v EXPRESSIONS='%ll*[xud]' \
76		-v RET_ON_FAIL=1 \
77		-v MESSAGE='Using %l format, prefer %PRI*64 if type is [u]int64_t' \
78		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
79		"$1" || res=1
80
81	# forbid variable declaration inside "for" loop
82	awk -v FOLDERS='.' \
83		-v EXPRESSIONS='for[[:space:]]*\\((char|u?int|unsigned|s?size_t)' \
84		-v RET_ON_FAIL=1 \
85		-v MESSAGE='Declaring a variable inside for()' \
86		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
87		"$1" || res=1
88
89	# refrain from new additions of 16/32/64 bits rte_atomicNN_xxx()
90	awk -v FOLDERS="lib drivers app examples" \
91		-v EXPRESSIONS="rte_atomic[0-9][0-9]_.*\\\(" \
92		-v RET_ON_FAIL=1 \
93		-v MESSAGE='Using rte_atomicNN_xxx' \
94		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
95		"$1" || res=1
96
97	# refrain from new additions of rte_smp_[r/w]mb()
98	awk -v FOLDERS="lib drivers app examples" \
99		-v EXPRESSIONS="rte_smp_(r|w)?mb\\\(" \
100		-v RET_ON_FAIL=1 \
101		-v MESSAGE='Using rte_smp_[r/w]mb' \
102		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
103		"$1" || res=1
104
105	# refrain from using compiler __sync_xxx builtins
106	awk -v FOLDERS="lib drivers app examples" \
107		-v EXPRESSIONS="__sync_.*\\\(" \
108		-v RET_ON_FAIL=1 \
109		-v MESSAGE='Using __sync_xxx builtins' \
110		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
111		"$1" || res=1
112
113	# refrain from using compiler __atomic_thread_fence()
114	# It should be avoided on x86 for SMP case.
115	awk -v FOLDERS="lib drivers app examples" \
116		-v EXPRESSIONS="__atomic_thread_fence\\\(" \
117		-v RET_ON_FAIL=1 \
118		-v MESSAGE='Using __atomic_thread_fence' \
119		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
120		"$1" || res=1
121
122	# forbid use of __reserved which is a reserved keyword in Windows system headers
123	awk -v FOLDERS="lib drivers app examples" \
124		-v EXPRESSIONS='\\<__reserved\\>' \
125		-v RET_ON_FAIL=1 \
126		-v MESSAGE='Using __reserved' \
127		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
128		"$1" || res=1
129
130	# forbid use of experimental build flag except in examples
131	awk -v FOLDERS='lib drivers app' \
132		-v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \
133		-v RET_ON_FAIL=1 \
134		-v MESSAGE='Using experimental build flag for in-tree compilation' \
135		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
136		"$1" || res=1
137
138	# refrain from using RTE_LOG_REGISTER for drivers and libs
139	awk -v FOLDERS='lib drivers' \
140		-v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \
141		-v RET_ON_FAIL=1 \
142		-v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \
143		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
144		"$1" || res=1
145
146	# forbid inclusion of driver specific headers in apps and examples
147	awk -v FOLDERS='app examples' \
148		-v EXPRESSIONS='include.*_driver\\.h include.*_pmd\\.h' \
149		-v RET_ON_FAIL=1 \
150		-v MESSAGE='Using driver specific headers in applications' \
151		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
152		"$1" || res=1
153
154	# SVG must be included with wildcard extension to allow conversion
155	awk -v FOLDERS='doc' \
156		-v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \
157		-v RET_ON_FAIL=1 \
158		-v MESSAGE='Using explicit .svg extension instead of .*' \
159		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
160		"$1" || res=1
161
162	# links must prefer https over http
163	awk -v FOLDERS='doc' \
164		-v EXPRESSIONS='http://.*dpdk.org' \
165		-v RET_ON_FAIL=1 \
166		-v MESSAGE='Using non https link to dpdk.org' \
167		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
168		"$1" || res=1
169
170	# '// XXX is not set' must be preferred over '#undef XXX'
171	awk -v FOLDERS='config/rte_config.h' \
172		-v EXPRESSIONS='#undef' \
173		-v RET_ON_FAIL=1 \
174		-v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \
175		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
176		"$1" || res=1
177
178	return $res
179}
180
181check_experimental_tags() { # <patch>
182	res=0
183
184	cat "$1" |awk '
185	BEGIN {
186		current_file = "";
187		ret = 0;
188	}
189	/^+++ b\// {
190		current_file = $2;
191	}
192	/^+.*__rte_experimental/ {
193		if (current_file ~ ".c$" ) {
194			print "Please only put __rte_experimental tags in " \
195				"headers ("current_file")";
196			ret = 1;
197		}
198		if ($1 != "+__rte_experimental" || $2 != "") {
199			print "__rte_experimental must appear alone on the line" \
200				" immediately preceding the return type of a function."
201			ret = 1;
202		}
203	}
204	END {
205		exit ret;
206	}' || res=1
207
208	return $res
209}
210
211check_internal_tags() { # <patch>
212	res=0
213
214	cat "$1" |awk '
215	BEGIN {
216		current_file = "";
217		ret = 0;
218	}
219	/^+++ b\// {
220		current_file = $2;
221	}
222	/^+.*__rte_internal/ {
223		if (current_file ~ ".c$" ) {
224			print "Please only put __rte_internal tags in " \
225				"headers ("current_file")";
226			ret = 1;
227		}
228		if ($1 != "+__rte_internal" || $2 != "") {
229			print "__rte_internal must appear alone on the line" \
230				" immediately preceding the return type of" \
231				" a function."
232			ret = 1;
233		}
234	}
235	END {
236		exit ret;
237	}' || res=1
238
239	return $res
240}
241
242check_release_notes() { # <patch>
243	rel_notes_prefix=doc/guides/rel_notes/release_
244	IFS=. read year month release < VERSION
245	current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
246
247	! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
248		grep -v $current_rel_notes
249}
250
251check_names() { # <patch>
252	res=0
253
254	old_IFS=$IFS
255	IFS='
256'
257	for contributor in $(sed -rn '/^$/,/^--- / {s/.*: (.*<.*@.*>)/\1/p}' $1); do
258		! grep -qE "^$contributor($| <)" .mailmap || continue
259		name=${contributor%% <*}
260		if grep -q "^$name <" .mailmap; then
261			reason="$name mail differs from primary mail"
262		else
263			reason="$contributor is unknown"
264		fi
265		echo "$reason, please fix the commit message or update .mailmap."
266		res=1
267	done
268	IFS=$old_IFS
269
270	return $res
271}
272
273number=0
274range='origin/main..'
275quiet=false
276verbose=false
277while getopts hn:qr:v ARG ; do
278	case $ARG in
279		n ) number=$OPTARG ;;
280		q ) quiet=true ;;
281		r ) range=$OPTARG ;;
282		v ) verbose=true ;;
283		h ) print_usage ; exit 0 ;;
284		? ) print_usage ; exit 1 ;;
285	esac
286done
287shift $(($OPTIND - 1))
288
289if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
290	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
291	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
292		DPDK_CHECKPATCH_PATH="$default_path"
293	else
294		print_usage >&2
295		echo
296		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
297		exit 1
298	fi
299fi
300
301print_headline() { # <title>
302	printf '\n### %s\n\n' "$1"
303	headline_printed=true
304}
305
306total=0
307status=0
308
309check () { # <patch-file> <commit>
310	local ret=0
311	local subject=''
312	headline_printed=false
313
314	total=$(($total + 1))
315	if [ -n "$1" ] ; then
316		tmpinput=$1
317	else
318		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
319		trap "rm -f '$tmpinput'" INT
320
321		if [ -n "$2" ] ; then
322			git format-patch --find-renames \
323			--no-stat --stdout -1 $commit > "$tmpinput"
324		else
325			cat > "$tmpinput"
326		fi
327	fi
328
329	# Subject can be on 2 lines
330	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
331	! $verbose || print_headline "$subject"
332
333	! $verbose || printf 'Running checkpatch.pl:\n'
334	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
335	if [ $? -ne 0 ] ; then
336		$headline_printed || print_headline "$subject"
337		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
338		ret=1
339	fi
340
341	! $verbose || printf '\nChecking API additions/removals:\n'
342	report=$($VALIDATE_NEW_API "$tmpinput")
343	if [ $? -ne 0 ] ; then
344		$headline_printed || print_headline "$subject"
345		printf '%s\n' "$report"
346		ret=1
347	fi
348
349	! $verbose || printf '\nChecking forbidden tokens additions:\n'
350	report=$(check_forbidden_additions "$tmpinput")
351	if [ $? -ne 0 ] ; then
352		$headline_printed || print_headline "$subject"
353		printf '%s\n' "$report"
354		ret=1
355	fi
356
357	! $verbose || printf '\nChecking __rte_experimental tags:\n'
358	report=$(check_experimental_tags "$tmpinput")
359	if [ $? -ne 0 ] ; then
360		$headline_printed || print_headline "$subject"
361		printf '%s\n' "$report"
362		ret=1
363	fi
364
365	! $verbose || printf '\nChecking __rte_internal tags:\n'
366	report=$(check_internal_tags "$tmpinput")
367	if [ $? -ne 0 ] ; then
368		$headline_printed || print_headline "$subject"
369		printf '%s\n' "$report"
370		ret=1
371	fi
372
373	! $verbose || printf '\nChecking release notes updates:\n'
374	report=$(check_release_notes "$tmpinput")
375	if [ $? -ne 0 ] ; then
376		$headline_printed || print_headline "$subject"
377		printf '%s\n' "$report"
378		ret=1
379	fi
380
381	! $verbose || printf '\nChecking names in commit log:\n'
382	report=$(check_names "$tmpinput")
383	if [ $? -ne 0 ] ; then
384		$headline_printed || print_headline "$subject"
385		printf '%s\n' "$report"
386		ret=1
387	fi
388
389	if [ "$tmpinput" != "$1" ]; then
390		rm -f "$tmpinput"
391		trap - INT
392	fi
393	[ $ret -eq 0 ] && return 0
394
395	status=$(($status + 1))
396}
397
398if [ -n "$1" ] ; then
399	for patch in "$@" ; do
400		check "$patch" ''
401	done
402elif [ ! -t 0 ] ; then # stdin
403	check '' ''
404else
405	if [ $number -eq 0 ] ; then
406		commits=$(git rev-list --reverse $range)
407	else
408		commits=$(git rev-list --reverse --max-count=$number HEAD)
409	fi
410	for commit in $commits ; do
411		check '' $commit
412	done
413fi
414pass=$(($total - $status))
415$quiet || printf '\n%d/%d valid patch' $pass $total
416$quiet || [ $pass -le 1 ] || printf 'es'
417$quiet || printf '\n'
418exit $status
419