dpdk/devtools/checkpatches.sh
<<
>>
Prefs
   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 must be set.
  45
  46        The patches to check can be from stdin, files specified on the command line,
  47        latest git commits limited with -n option, or commits in the git range
  48        specified with -r option (default: "origin/main..").
  49        END_OF_HELP
  50}
  51
  52check_forbidden_additions() { # <patch>
  53        res=0
  54
  55        # refrain from new additions of rte_panic() and rte_exit()
  56        # multiple folders and expressions are separated by spaces
  57        awk -v FOLDERS="lib drivers" \
  58                -v EXPRESSIONS="rte_panic\\\( rte_exit\\\(" \
  59                -v RET_ON_FAIL=1 \
  60                -v MESSAGE='Using rte_panic/rte_exit' \
  61                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
  62                "$1" || res=1
  63
  64        # refrain from using compiler attribute without defining a common macro
  65        awk -v FOLDERS="lib drivers app examples" \
  66                -v EXPRESSIONS="__attribute__" \
  67                -v RET_ON_FAIL=1 \
  68                -v MESSAGE='Using compiler attribute directly' \
  69                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
  70                "$1" || res=1
  71
  72        # check %l or %ll format specifier
  73        awk -v FOLDERS='lib drivers app examples' \
  74                -v EXPRESSIONS='%ll*[xud]' \
  75                -v RET_ON_FAIL=1 \
  76                -v MESSAGE='Using %l format, prefer %PRI*64 if type is [u]int64_t' \
  77                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
  78                "$1" || res=1
  79
  80        # forbid variable declaration inside "for" loop
  81        awk -v FOLDERS='.' \
  82                -v EXPRESSIONS='for[[:space:]]*\\((char|u?int|unsigned|s?size_t)' \
  83                -v RET_ON_FAIL=1 \
  84                -v MESSAGE='Declaring a variable inside for()' \
  85                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
  86                "$1" || res=1
  87
  88        # refrain from new additions of 16/32/64 bits rte_atomicNN_xxx()
  89        awk -v FOLDERS="lib drivers app examples" \
  90                -v EXPRESSIONS="rte_atomic[0-9][0-9]_.*\\\(" \
  91                -v RET_ON_FAIL=1 \
  92                -v MESSAGE='Using rte_atomicNN_xxx' \
  93                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
  94                "$1" || res=1
  95
  96        # refrain from new additions of rte_smp_[r/w]mb()
  97        awk -v FOLDERS="lib drivers app examples" \
  98                -v EXPRESSIONS="rte_smp_(r|w)?mb\\\(" \
  99                -v RET_ON_FAIL=1 \
 100                -v MESSAGE='Using rte_smp_[r/w]mb' \
 101                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 102                "$1" || res=1
 103
 104        # refrain from using compiler __sync_xxx builtins
 105        awk -v FOLDERS="lib drivers app examples" \
 106                -v EXPRESSIONS="__sync_.*\\\(" \
 107                -v RET_ON_FAIL=1 \
 108                -v MESSAGE='Using __sync_xxx builtins' \
 109                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 110                "$1" || res=1
 111
 112        # refrain from using compiler __atomic_thread_fence()
 113        # It should be avoided on x86 for SMP case.
 114        awk -v FOLDERS="lib drivers app examples" \
 115                -v EXPRESSIONS="__atomic_thread_fence\\\(" \
 116                -v RET_ON_FAIL=1 \
 117                -v MESSAGE='Using __atomic_thread_fence' \
 118                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 119                "$1" || res=1
 120
 121        # forbid use of __reserved which is a reserved keyword in Windows system headers
 122        awk -v FOLDERS="lib drivers app examples" \
 123                -v EXPRESSIONS='\\<__reserved\\>' \
 124                -v RET_ON_FAIL=1 \
 125                -v MESSAGE='Using __reserved' \
 126                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 127                "$1" || res=1
 128
 129        # forbid use of experimental build flag except in examples
 130        awk -v FOLDERS='lib drivers app' \
 131                -v EXPRESSIONS='-DALLOW_EXPERIMENTAL_API allow_experimental_apis' \
 132                -v RET_ON_FAIL=1 \
 133                -v MESSAGE='Using experimental build flag for in-tree compilation' \
 134                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 135                "$1" || res=1
 136
 137        # refrain from using RTE_LOG_REGISTER for drivers and libs
 138        awk -v FOLDERS='lib drivers' \
 139                -v EXPRESSIONS='\\<RTE_LOG_REGISTER\\>' \
 140                -v RET_ON_FAIL=1 \
 141                -v MESSAGE='Using RTE_LOG_REGISTER, prefer RTE_LOG_REGISTER_(DEFAULT|SUFFIX)' \
 142                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 143                "$1" || res=1
 144
 145        # SVG must be included with wildcard extension to allow conversion
 146        awk -v FOLDERS='doc' \
 147                -v EXPRESSIONS='::[[:space:]]*[^[:space:]]*\\.svg' \
 148                -v RET_ON_FAIL=1 \
 149                -v MESSAGE='Using explicit .svg extension instead of .*' \
 150                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 151                "$1" || res=1
 152
 153        # links must prefer https over http
 154        awk -v FOLDERS='doc' \
 155                -v EXPRESSIONS='http://.*dpdk.org' \
 156                -v RET_ON_FAIL=1 \
 157                -v MESSAGE='Using non https link to dpdk.org' \
 158                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 159                "$1" || res=1
 160
 161        # '// XXX is not set' must be preferred over '#undef XXX'
 162        awk -v FOLDERS='config/rte_config.h' \
 163                -v EXPRESSIONS='#undef' \
 164                -v RET_ON_FAIL=1 \
 165                -v MESSAGE='Using "#undef XXX", prefer "// XXX is not set"' \
 166                -f $(dirname $(readlink -f $0))/check-forbidden-tokens.awk \
 167                "$1" || res=1
 168
 169        return $res
 170}
 171
 172check_experimental_tags() { # <patch>
 173        res=0
 174
 175        cat "$1" |awk '
 176        BEGIN {
 177                current_file = "";
 178                ret = 0;
 179        }
 180        /^+++ b\// {
 181                current_file = $2;
 182        }
 183        /^+.*__rte_experimental/ {
 184                if (current_file ~ ".c$" ) {
 185                        print "Please only put __rte_experimental tags in " \
 186                                "headers ("current_file")";
 187                        ret = 1;
 188                }
 189                if ($1 != "+__rte_experimental" || $2 != "") {
 190                        print "__rte_experimental must appear alone on the line" \
 191                                " immediately preceding the return type of a function."
 192                        ret = 1;
 193                }
 194        }
 195        END {
 196                exit ret;
 197        }' || res=1
 198
 199        return $res
 200}
 201
 202check_internal_tags() { # <patch>
 203        res=0
 204
 205        cat "$1" |awk '
 206        BEGIN {
 207                current_file = "";
 208                ret = 0;
 209        }
 210        /^+++ b\// {
 211                current_file = $2;
 212        }
 213        /^+.*__rte_internal/ {
 214                if (current_file ~ ".c$" ) {
 215                        print "Please only put __rte_internal tags in " \
 216                                "headers ("current_file")";
 217                        ret = 1;
 218                }
 219                if ($1 != "+__rte_internal" || $2 != "") {
 220                        print "__rte_internal must appear alone on the line" \
 221                                " immediately preceding the return type of" \
 222                                " a function."
 223                        ret = 1;
 224                }
 225        }
 226        END {
 227                exit ret;
 228        }' || res=1
 229
 230        return $res
 231}
 232
 233check_release_notes() { # <patch>
 234        rel_notes_prefix=doc/guides/rel_notes/release_
 235        IFS=. read year month release < VERSION
 236        current_rel_notes=${rel_notes_prefix}${year}_${month}.rst
 237
 238        ! grep -e '^--- a/'$rel_notes_prefix -e '^+++ b/'$rel_notes_prefix "$1" |
 239                grep -v $current_rel_notes
 240}
 241
 242number=0
 243range='origin/main..'
 244quiet=false
 245verbose=false
 246while getopts hn:qr:v ARG ; do
 247        case $ARG in
 248                n ) number=$OPTARG ;;
 249                q ) quiet=true ;;
 250                r ) range=$OPTARG ;;
 251                v ) verbose=true ;;
 252                h ) print_usage ; exit 0 ;;
 253                ? ) print_usage ; exit 1 ;;
 254        esac
 255done
 256shift $(($OPTIND - 1))
 257
 258if [ ! -f "$DPDK_CHECKPATCH_PATH" ] || [ ! -x "$DPDK_CHECKPATCH_PATH" ] ; then
 259        print_usage >&2
 260        echo
 261        echo 'Cannot execute DPDK_CHECKPATCH_PATH' >&2
 262        exit 1
 263fi
 264
 265print_headline() { # <title>
 266        printf '\n### %s\n\n' "$1"
 267        headline_printed=true
 268}
 269
 270total=0
 271status=0
 272
 273check () { # <patch> <commit> <title>
 274        local ret=0
 275        headline_printed=false
 276
 277        total=$(($total + 1))
 278        ! $verbose || print_headline "$3"
 279        if [ -n "$1" ] ; then
 280                tmpinput=$1
 281        else
 282                tmpinput=$(mktemp -t dpdk.checkpatches.XXXXXX)
 283                trap "rm -f '$tmpinput'" INT
 284
 285                if [ -n "$2" ] ; then
 286                        git format-patch --find-renames \
 287                        --no-stat --stdout -1 $commit > "$tmpinput"
 288                else
 289                        cat > "$tmpinput"
 290                fi
 291        fi
 292
 293        ! $verbose || printf 'Running checkpatch.pl:\n'
 294        report=$($DPDK_CHECKPATCH_PATH $options "$tmpinput" 2>/dev/null)
 295        if [ $? -ne 0 ] ; then
 296                $headline_printed || print_headline "$3"
 297                printf '%s\n' "$report" | sed -n '1,/^total:.*lines checked$/p'
 298                ret=1
 299        fi
 300
 301        ! $verbose || printf '\nChecking API additions/removals:\n'
 302        report=$($VALIDATE_NEW_API "$tmpinput")
 303        if [ $? -ne 0 ] ; then
 304                $headline_printed || print_headline "$3"
 305                printf '%s\n' "$report"
 306                ret=1
 307        fi
 308
 309        ! $verbose || printf '\nChecking forbidden tokens additions:\n'
 310        report=$(check_forbidden_additions "$tmpinput")
 311        if [ $? -ne 0 ] ; then
 312                $headline_printed || print_headline "$3"
 313                printf '%s\n' "$report"
 314                ret=1
 315        fi
 316
 317        ! $verbose || printf '\nChecking __rte_experimental tags:\n'
 318        report=$(check_experimental_tags "$tmpinput")
 319        if [ $? -ne 0 ] ; then
 320                $headline_printed || print_headline "$3"
 321                printf '%s\n' "$report"
 322                ret=1
 323        fi
 324
 325        ! $verbose || printf '\nChecking __rte_internal tags:\n'
 326        report=$(check_internal_tags "$tmpinput")
 327        if [ $? -ne 0 ] ; then
 328                $headline_printed || print_headline "$3"
 329                printf '%s\n' "$report"
 330                ret=1
 331        fi
 332
 333        ! $verbose || printf '\nChecking release notes updates:\n'
 334        report=$(check_release_notes "$tmpinput")
 335        if [ $? -ne 0 ] ; then
 336                $headline_printed || print_headline "$3"
 337                printf '%s\n' "$report"
 338                ret=1
 339        fi
 340
 341        if [ "$tmpinput" != "$1" ]; then
 342                rm -f "$tmpinput"
 343                trap - INT
 344        fi
 345        [ $ret -eq 0 ] && return 0
 346
 347        status=$(($status + 1))
 348}
 349
 350if [ -n "$1" ] ; then
 351        for patch in "$@" ; do
 352                # Subject can be on 2 lines
 353                subject=$(sed '/^Subject: */!d;s///;N;s,\n[[:space:]]\+, ,;s,\n.*,,;q' "$patch")
 354                check "$patch" '' "$subject"
 355        done
 356elif [ ! -t 0 ] ; then # stdin
 357        subject=$(while read header value ; do
 358                if [ "$header" = 'Subject:' ] ; then
 359                        IFS= read next
 360                        continuation=$(echo "$next" | sed -n 's,^[[:space:]]\+, ,p')
 361                        echo $value$continuation
 362                        break
 363                fi
 364        done)
 365        check '' '' "$subject"
 366else
 367        if [ $number -eq 0 ] ; then
 368                commits=$(git rev-list --reverse $range)
 369        else
 370                commits=$(git rev-list --reverse --max-count=$number HEAD)
 371        fi
 372        for commit in $commits ; do
 373                subject=$(git log --format='%s' -1 $commit)
 374                check '' $commit "$subject"
 375        done
 376fi
 377pass=$(($total - $status))
 378$quiet || printf '\n%d/%d valid patch' $pass $total
 379$quiet || [ $pass -le 1 ] || printf 'es'
 380$quiet || printf '\n'
 381exit $status
 382