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