xref: /dpdk/devtools/checkpatches.sh (revision 53e6597643e47652af29baa24df7566fffbf8b0c)
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
251number=0
252range='origin/main..'
253quiet=false
254verbose=false
255while getopts hn:qr:v ARG ; do
256	case $ARG in
257		n ) number=$OPTARG ;;
258		q ) quiet=true ;;
259		r ) range=$OPTARG ;;
260		v ) verbose=true ;;
261		h ) print_usage ; exit 0 ;;
262		? ) print_usage ; exit 1 ;;
263	esac
264done
265shift $(($OPTIND - 1))
266
267if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
268	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
269	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
270		DPDK_CHECKPATCH_PATH="$default_path"
271	else
272		print_usage >&2
273		echo
274		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
275		exit 1
276	fi
277fi
278
279print_headline() { # <title>
280	printf '\n### %s\n\n' "$1"
281	headline_printed=true
282}
283
284total=0
285status=0
286
287check () { # <patch-file> <commit>
288	local ret=0
289	local subject=''
290	headline_printed=false
291
292	total=$(($total + 1))
293	if [ -n "$1" ] ; then
294		tmpinput=$1
295	else
296		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
297		trap "rm -f '$tmpinput'" INT
298
299		if [ -n "$2" ] ; then
300			git format-patch --find-renames \
301			--no-stat --stdout -1 $commit > "$tmpinput"
302		else
303			cat > "$tmpinput"
304		fi
305	fi
306
307	# Subject can be on 2 lines
308	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
309	! $verbose || print_headline "$subject"
310
311	! $verbose || printf 'Running checkpatch.pl:\n'
312	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
313	if [ $? -ne 0 ] ; then
314		$headline_printed || print_headline "$subject"
315		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
316		ret=1
317	fi
318
319	! $verbose || printf '\nChecking API additions/removals:\n'
320	report=$($VALIDATE_NEW_API "$tmpinput")
321	if [ $? -ne 0 ] ; then
322		$headline_printed || print_headline "$subject"
323		printf '%s\n' "$report"
324		ret=1
325	fi
326
327	! $verbose || printf '\nChecking forbidden tokens additions:\n'
328	report=$(check_forbidden_additions "$tmpinput")
329	if [ $? -ne 0 ] ; then
330		$headline_printed || print_headline "$subject"
331		printf '%s\n' "$report"
332		ret=1
333	fi
334
335	! $verbose || printf '\nChecking __rte_experimental tags:\n'
336	report=$(check_experimental_tags "$tmpinput")
337	if [ $? -ne 0 ] ; then
338		$headline_printed || print_headline "$subject"
339		printf '%s\n' "$report"
340		ret=1
341	fi
342
343	! $verbose || printf '\nChecking __rte_internal tags:\n'
344	report=$(check_internal_tags "$tmpinput")
345	if [ $? -ne 0 ] ; then
346		$headline_printed || print_headline "$subject"
347		printf '%s\n' "$report"
348		ret=1
349	fi
350
351	! $verbose || printf '\nChecking release notes updates:\n'
352	report=$(check_release_notes "$tmpinput")
353	if [ $? -ne 0 ] ; then
354		$headline_printed || print_headline "$subject"
355		printf '%s\n' "$report"
356		ret=1
357	fi
358
359	if [ "$tmpinput" != "$1" ]; then
360		rm -f "$tmpinput"
361		trap - INT
362	fi
363	[ $ret -eq 0 ] && return 0
364
365	status=$(($status + 1))
366}
367
368if [ -n "$1" ] ; then
369	for patch in "$@" ; do
370		check "$patch" ''
371	done
372elif [ ! -t 0 ] ; then # stdin
373	check '' ''
374else
375	if [ $number -eq 0 ] ; then
376		commits=$(git rev-list --reverse $range)
377	else
378		commits=$(git rev-list --reverse --max-count=$number HEAD)
379	fi
380	for commit in $commits ; do
381		check '' $commit
382	done
383fi
384pass=$(($total - $status))
385$quiet || printf '\n%d/%d valid patch' $pass $total
386$quiet || [ $pass -le 1 ] || printf 'es'
387$quiet || printf '\n'
388exit $status
389