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