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