xref: /dpdk/devtools/checkpatches.sh (revision c56185fc183fc0532d2f03aaf04bbf0989ea91a5)
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,AVOID_BUG"
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	# refrain from new additions of 16/32/64 bits rte_atomicNN_xxx()
82	awk -v FOLDERS="lib drivers app examples" \
83		-v EXPRESSIONS="rte_atomic[0-9][0-9]_.*\\\(" \
84		-v RET_ON_FAIL=1 \
85		-v MESSAGE='Using rte_atomicNN_xxx' \
86		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
87		"$1" || res=1
88
89	# refrain from new additions of rte_smp_[r/w]mb()
90	awk -v FOLDERS="lib drivers app examples" \
91		-v EXPRESSIONS="rte_smp_(r|w)?mb\\\(" \
92		-v RET_ON_FAIL=1 \
93		-v MESSAGE='Using rte_smp_[r/w]mb' \
94		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
95		"$1" || res=1
96
97	# refrain from using compiler __sync_xxx builtins
98	awk -v FOLDERS="lib drivers app examples" \
99		-v EXPRESSIONS="__sync_.*\\\(" \
100		-v RET_ON_FAIL=1 \
101		-v MESSAGE='Using __sync_xxx builtins' \
102		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
103		"$1" || res=1
104
105	# refrain from using compiler __atomic_thread_fence()
106	# It should be avoided on x86 for SMP case.
107	awk -v FOLDERS="lib drivers app examples" \
108		-v EXPRESSIONS="__atomic_thread_fence\\\(" \
109		-v RET_ON_FAIL=1 \
110		-v MESSAGE='Using __atomic_thread_fence' \
111		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
112		"$1" || res=1
113
114	# refrain from using compiler __atomic_{add,and,nand,or,sub,xor}_fetch()
115	awk -v FOLDERS="lib drivers app examples" \
116		-v EXPRESSIONS="__atomic_(add|and|nand|or|sub|xor)_fetch\\\(" \
117		-v RET_ON_FAIL=1 \
118		-v MESSAGE='Using __atomic_op_fetch, prefer __atomic_fetch_op' \
119		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
120		"$1" || res=1
121
122	# refrain from using some pthread functions
123	awk -v FOLDERS="lib drivers app examples" \
124		-v EXPRESSIONS="pthread_(create|join|detach|set(_?name_np|affinity_np)|attr_set(inheritsched|schedpolicy))\\\(" \
125		-v RET_ON_FAIL=1 \
126		-v MESSAGE='Using pthread functions, prefer rte_thread' \
127		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
128		"$1" || res=1
129
130	# forbid use of __reserved which is a reserved keyword in Windows system headers
131	awk -v FOLDERS="lib drivers app examples" \
132		-v EXPRESSIONS='\\<__reserved\\>' \
133		-v RET_ON_FAIL=1 \
134		-v MESSAGE='Using __reserved' \
135		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
136		"$1" || res=1
137
138	# forbid use of non abstracted bit count operations
139	awk -v FOLDERS="lib drivers app examples" \
140		-v EXPRESSIONS='\\<__builtin_(clz|clzll|ctz|ctzll|popcount|popcountll)\\>' \
141		-v RET_ON_FAIL=1 \
142		-v MESSAGE='Using __builtin helpers for bit count operations' \
143		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
144		"$1" || res=1
145
146	# forbid inclusion of Linux header for PCI constants
147	awk -v FOLDERS="lib drivers app examples" \
148		-v EXPRESSIONS='include.*linux/pci_regs\\.h' \
149		-v RET_ON_FAIL=1 \
150		-v MESSAGE='Using linux/pci_regs.h, prefer rte_pci.h' \
151		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
152		"$1" || res=1
153
154	# forbid use of experimental build flag except in examples
155	awk -v FOLDERS='lib drivers app' \
156		-v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \
157		-v RET_ON_FAIL=1 \
158		-v MESSAGE='Using experimental build flag for in-tree compilation' \
159		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
160		"$1" || res=1
161
162	# refrain from using RTE_LOG_REGISTER for drivers and libs
163	awk -v FOLDERS='lib drivers' \
164		-v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \
165		-v RET_ON_FAIL=1 \
166		-v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \
167		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
168		"$1" || res=1
169
170	# forbid non-internal thread in drivers and libs
171	awk -v FOLDERS='lib drivers' \
172		-v EXPRESSIONS="rte_thread_(set_name|create_control)\\\(" \
173		-v RET_ON_FAIL=1 \
174		-v MESSAGE='Prefer rte_thread_(set_prefixed_name|create_internal_control)' \
175		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
176		"$1" || res=1
177
178	# forbid inclusion of driver specific headers in apps and examples
179	awk -v FOLDERS='app examples' \
180		-v EXPRESSIONS='include.*_driver\\.h include.*_pmd\\.h' \
181		-v RET_ON_FAIL=1 \
182		-v MESSAGE='Using driver specific headers in applications' \
183		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
184		"$1" || res=1
185
186	# SVG must be included with wildcard extension to allow conversion
187	awk -v FOLDERS='doc' \
188		-v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \
189		-v RET_ON_FAIL=1 \
190		-v MESSAGE='Using explicit .svg extension instead of .*' \
191		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
192		"$1" || res=1
193
194	# links must prefer https over http
195	awk -v FOLDERS='doc' \
196		-v EXPRESSIONS='http://.*dpdk.org' \
197		-v RET_ON_FAIL=1 \
198		-v MESSAGE='Using non https link to dpdk.org' \
199		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
200		"$1" || res=1
201
202	# prefer Sphinx references for internal documentation
203	awk -v FOLDERS='doc' \
204		-v EXPRESSIONS='//doc.dpdk.org/guides/' \
205		-v RET_ON_FAIL=1 \
206		-v MESSAGE='Using explicit URL to doc.dpdk.org, prefer :ref: or :doc:' \
207		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
208		"$1" || res=1
209
210	# '// XXX is not set' must be preferred over '#undef XXX'
211	awk -v FOLDERS='config/rte_config.h' \
212		-v EXPRESSIONS='#undef' \
213		-v RET_ON_FAIL=1 \
214		-v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \
215		-f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
216		"$1" || res=1
217
218	return $res
219}
220
221check_experimental_tags() { # <patch>
222	res=0
223
224	cat "$1" |awk '
225	BEGIN {
226		current_file = "";
227		ret = 0;
228	}
229	/^+++ b\// {
230		current_file = $2;
231	}
232	/^+.*__rte_experimental/ {
233		if (current_file ~ ".c$" ) {
234			print "Please only put __rte_experimental tags in " \
235				"headers ("current_file")";
236			ret = 1;
237		}
238		if ($1 != "+__rte_experimental" || $2 != "") {
239			print "__rte_experimental must appear alone on the line" \
240				" immediately preceding the return type of a function."
241			ret = 1;
242		}
243	}
244	END {
245		exit ret;
246	}' || res=1
247
248	return $res
249}
250
251check_internal_tags() { # <patch>
252	res=0
253
254	cat "$1" |awk '
255	BEGIN {
256		current_file = "";
257		ret = 0;
258	}
259	/^+++ b\// {
260		current_file = $2;
261	}
262	/^+.*__rte_internal/ {
263		if (current_file ~ ".c$" ) {
264			print "Please only put __rte_internal tags in " \
265				"headers ("current_file")";
266			ret = 1;
267		}
268		if ($1 != "+__rte_internal" || $2 != "") {
269			print "__rte_internal must appear alone on the line" \
270				" immediately preceding the return type of" \
271				" a function."
272			ret = 1;
273		}
274	}
275	END {
276		exit ret;
277	}' || res=1
278
279	return $res
280}
281
282check_release_notes() { # <patch>
283	rel_notes_prefix=doc/guides/rel_notes/release_
284	IFS=. read year month release < VERSION
285	current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
286
287	! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
288		grep -v $current_rel_notes
289}
290
291number=0
292range='origin/main..'
293quiet=false
294verbose=false
295while getopts hn:qr:v ARG ; do
296	case $ARG in
297		n ) number=$OPTARG ;;
298		q ) quiet=true ;;
299		r ) range=$OPTARG ;;
300		v ) verbose=true ;;
301		h ) print_usage ; exit 0 ;;
302		? ) print_usage ; exit 1 ;;
303	esac
304done
305shift $(($OPTIND - 1))
306
307if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
308	default_path="/lib/modules/$(uname -r)/source/scripts/checkpatch.pl"
309	if [ -f "$default_path" ] && [ -x "$default_path" ] ; then
310		DPDK_CHECKPATCH_PATH="$default_path"
311	else
312		print_usage >&2
313		echo
314		echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
315		exit 1
316	fi
317fi
318
319print_headline() { # <title>
320	printf '\n### %s\n\n' "$1"
321	headline_printed=true
322}
323
324total=0
325status=0
326
327check () { # <patch-file> <commit>
328	local ret=0
329	local subject=''
330	headline_printed=false
331
332	total=$(($total + 1))
333	if [ -n "$1" ] ; then
334		tmpinput=$1
335	else
336		tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
337		trap "rm -f '$tmpinput'" INT
338
339		if [ -n "$2" ] ; then
340			git format-patch --find-renames \
341			--no-stat --stdout -1 $commit > "$tmpinput"
342		else
343			cat > "$tmpinput"
344		fi
345	fi
346
347	# Subject can be on 2 lines
348	subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$tmpinput")
349	! $verbose || print_headline "$subject"
350
351	! $verbose || printf 'Running checkpatch.pl:\n'
352	report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
353	if [ $? -ne 0 ] ; then
354		$headline_printed || print_headline "$subject"
355		printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
356		ret=1
357	fi
358
359	! $verbose || printf '\nChecking API additions/removals:\n'
360	report=$($VALIDATE_NEW_API "$tmpinput")
361	if [ $? -ne 0 ] ; then
362		$headline_printed || print_headline "$subject"
363		printf '%s\n' "$report"
364		ret=1
365	fi
366
367	! $verbose || printf '\nChecking forbidden tokens additions:\n'
368	report=$(check_forbidden_additions "$tmpinput")
369	if [ $? -ne 0 ] ; then
370		$headline_printed || print_headline "$subject"
371		printf '%s\n' "$report"
372		ret=1
373	fi
374
375	! $verbose || printf '\nChecking __rte_experimental tags:\n'
376	report=$(check_experimental_tags "$tmpinput")
377	if [ $? -ne 0 ] ; then
378		$headline_printed || print_headline "$subject"
379		printf '%s\n' "$report"
380		ret=1
381	fi
382
383	! $verbose || printf '\nChecking __rte_internal tags:\n'
384	report=$(check_internal_tags "$tmpinput")
385	if [ $? -ne 0 ] ; then
386		$headline_printed || print_headline "$subject"
387		printf '%s\n' "$report"
388		ret=1
389	fi
390
391	! $verbose || printf '\nChecking release notes updates:\n'
392	report=$(check_release_notes "$tmpinput")
393	if [ $? -ne 0 ] ; then
394		$headline_printed || print_headline "$subject"
395		printf '%s\n' "$report"
396		ret=1
397	fi
398
399	if [ "$tmpinput" != "$1" ]; then
400		rm -f "$tmpinput"
401		trap - INT
402	fi
403	[ $ret -eq 0 ] && return 0
404
405	status=$(($status + 1))
406}
407
408if [ -n "$1" ] ; then
409	for patch in "$@" ; do
410		check "$patch" ''
411	done
412elif [ ! -t 0 ] ; then # stdin
413	check '' ''
414else
415	if [ $number -eq 0 ] ; then
416		commits=$(git rev-list --reverse $range)
417	else
418		commits=$(git rev-list --reverse --max-count=$number HEAD)
419	fi
420	for commit in $commits ; do
421		check '' $commit
422	done
423fi
424pass=$(($total - $status))
425$quiet || printf '\n%d/%d valid patch' $pass $total
426$quiet || [ $pass -le 1 ] || printf 'es'
427$quiet || printf '\n'
428exit $status
429