1#!/usr/bin/env bash 2# SPDX-License-Identifier: BSD-3-Clause 3# Copyright (C) 2015 Intel Corporation 4# Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. 5# All rights reserved. 6# 7if [[ $(uname -s) == Darwin ]]; then 8 # SPDK is not supported on MacOS, but as a developer 9 # convenience we support running the check_format.sh 10 # script on MacOS. 11 # Running "brew install bash coreutils grep" should be 12 # sufficient to get the correct versions of these utilities. 13 if [[ $(type -t mapfile) != builtin ]]; then 14 # We need bash version >= 4.0 for mapfile builtin 15 echo "Please install bash version >= 4.0" 16 exit 1 17 fi 18 if ! hash greadlink 2> /dev/null; then 19 # We need GNU readlink for -f option 20 echo "Please install GNU readlink" 21 exit 1 22 fi 23 if ! hash ggrep 2> /dev/null; then 24 # We need GNU grep for -P option 25 echo "Please install GNU grep" 26 exit 1 27 fi 28 GNU_READLINK="greadlink" 29 GNU_GREP="ggrep" 30else 31 GNU_READLINK="readlink" 32 GNU_GREP="grep" 33fi 34 35rootdir=$($GNU_READLINK -f "$(dirname "$0")")/.. 36source "$rootdir/scripts/common.sh" 37 38cd "$rootdir" 39 40# exit on errors 41set -e 42 43if ! hash nproc 2> /dev/null; then 44 45 function nproc() { 46 echo 8 47 } 48 49fi 50 51function version_lt() { 52 [ $(echo -e "$1\n$2" | sort -V | head -1) != "$1" ] 53} 54 55function array_contains_string() { 56 name="$1[@]" 57 array=("${!name}") 58 59 for element in "${array[@]}"; do 60 if [ "$element" = "$2" ]; then 61 return $(true) 62 fi 63 done 64 65 return $(false) 66} 67 68rc=0 69 70function check_permissions() { 71 local rc=0 files=() 72 73 mapfile -t files < <(git ls-files) 74 mapfile -t files < <(get_diffed_dups "${files[@]}") 75 76 ((${#files[@]} > 0)) || return 0 77 78 echo -n "Checking file permissions..." 79 80 while read -r perm _res0 _res1 path; do 81 if [ ! -f "$path" ]; then 82 continue 83 fi 84 85 # Skip symlinks 86 if [[ -L $path ]]; then 87 continue 88 fi 89 fname=$(basename -- "$path") 90 91 case ${fname##*.} in 92 c | h | cpp | cc | cxx | hh | hpp | md | html | js | json | svg | Doxyfile | yml | LICENSE | README | conf | in | Makefile | mk | gitignore | go | txt) 93 # These file types should never be executable 94 if [ "$perm" -eq 100755 ]; then 95 echo "ERROR: $path is marked executable but is a code file." 96 rc=1 97 fi 98 ;; 99 *) 100 shebang=$(head -n 1 $path | cut -c1-3) 101 102 # git only tracks the execute bit, so will only ever return 755 or 644 as the permission. 103 if [ "$perm" -eq 100755 ]; then 104 # If the file has execute permission, it should start with a shebang. 105 if [ "$shebang" != "#!/" ]; then 106 echo "ERROR: $path is marked executable but does not start with a shebang." 107 rc=1 108 fi 109 else 110 # If the file does not have execute permissions, it should not start with a shebang. 111 if [ "$shebang" = "#!/" ]; then 112 echo "ERROR: $path is not marked executable but starts with a shebang." 113 rc=1 114 fi 115 fi 116 ;; 117 esac 118 119 done < <(git ls-files -s "${files[@]}") 120 121 if [ $rc -eq 0 ]; then 122 echo " OK" 123 fi 124 125 return $rc 126} 127 128function check_c_style() { 129 local rc=0 130 131 if hash astyle; then 132 echo -n "Checking coding style..." 133 version=$(astyle --version | awk '{print $NF}') 134 if lt "$version" 3.0.1; then 135 echo " Your astyle version is too old so skipping coding style checks. Please update astyle to at least 3.0.1 version..." 136 rc=1 137 elif ge "$version" 3.4; then 138 echo " Your astyle version is too new so skipping coding style checks. Please use astyle version < 3.4" 139 rc=1 140 else 141 rm -f astyle.log 142 touch astyle.log 143 # Exclude DPDK header files copied into our tree 144 git ls-files '*.[ch]' ':!:*/env_dpdk/*/*.h' ':!:include/linux/fuse_kernel.h' \ 145 | xargs -P$(nproc) -n10 astyle --break-return-type --attach-return-type-decl \ 146 --options=.astylerc >> astyle.log 147 git ls-files '*.cpp' '*.cc' '*.cxx' '*.hh' '*.hpp' \ 148 | xargs -P$(nproc) -n10 astyle --options=.astylerc >> astyle.log 149 if grep -q "^Formatted" astyle.log; then 150 echo " errors detected" 151 git diff --ignore-submodules=all 152 sed -i -e 's/ / /g' astyle.log 153 grep --color=auto "^Formatted.*" astyle.log 154 echo "Incorrect code style detected in one or more files." 155 echo "The files have been automatically formatted." 156 echo "Remember to add the files to your commit." 157 rc=1 158 else 159 echo " OK" 160 fi 161 rm -f astyle.log 162 fi 163 else 164 echo "You do not have astyle installed so your code style is not being checked!" 165 fi 166 return $rc 167} 168 169function check_comment_style() { 170 local rc=0 171 172 echo -n "Checking comment style..." 173 174 git grep --line-number -e '\/[*][^ *-]' -- '*.[ch]' > comment.log || true 175 git grep --line-number -e '[^ ][*]\/' -- '*.[ch]' ':!lib/rte_vhost*/*' >> comment.log || true 176 git grep --line-number -e '^[*]' -- '*.[ch]' ':!include/linux/fuse_kernel.h' >> comment.log || true 177 git grep --line-number -e '\s\/\/' -- '*.[ch]' >> comment.log || true 178 git grep --line-number -e '^\/\/' -- '*.[ch]' >> comment.log || true 179 180 if [ -s comment.log ]; then 181 echo " Incorrect comment formatting detected" 182 cat comment.log 183 rc=1 184 else 185 echo " OK" 186 fi 187 rm -f comment.log 188 189 return $rc 190} 191 192function check_spaces_before_tabs() { 193 local rc=0 194 195 echo -n "Checking for spaces before tabs..." 196 git grep --line-number $' \t' -- './*' ':!*.patch' > whitespace.log || true 197 if [ -s whitespace.log ]; then 198 echo " Spaces before tabs detected" 199 cat whitespace.log 200 rc=1 201 else 202 echo " OK" 203 fi 204 rm -f whitespace.log 205 206 return $rc 207} 208 209function check_trailing_whitespace() { 210 local rc=0 211 212 echo -n "Checking trailing whitespace in output strings..." 213 214 git grep --line-number -e ' \\n"' -- '*.[ch]' > whitespace.log || true 215 216 if [ -s whitespace.log ]; then 217 echo " Incorrect trailing whitespace detected" 218 cat whitespace.log 219 rc=1 220 else 221 echo " OK" 222 fi 223 rm -f whitespace.log 224 225 return $rc 226} 227 228function check_forbidden_functions() { 229 local rc=0 230 231 echo -n "Checking for use of forbidden library functions..." 232 233 git grep --line-number -w '\(atoi\|atol\|atoll\|strncpy\|strcpy\|strcat\|sprintf\|vsprintf\)' -- './*.c' ':!lib/rte_vhost*/**' > badfunc.log || true 234 if [ -s badfunc.log ]; then 235 echo " Forbidden library functions detected" 236 cat badfunc.log 237 rc=1 238 else 239 echo " OK" 240 fi 241 rm -f badfunc.log 242 243 return $rc 244} 245 246function check_cunit_style() { 247 local rc=0 248 249 echo -n "Checking for use of forbidden CUnit macros..." 250 251 git grep --line-number -w 'CU_ASSERT_FATAL' -- 'test/*' ':!include/spdk_internal/cunit.h' > badcunit.log || true 252 if [ -s badcunit.log ]; then 253 echo " Forbidden CU_ASSERT_FATAL usage detected - use SPDK_CU_ASSERT_FATAL instead" 254 cat badcunit.log 255 rc=1 256 else 257 echo " OK" 258 fi 259 rm -f badcunit.log 260 261 return $rc 262} 263 264function check_eof() { 265 local rc=0 266 267 echo -n "Checking blank lines at end of file..." 268 269 if ! git grep -I -l -e . -z './*' ':!*.patch' \ 270 | xargs -0 -P$(nproc) -n1 scripts/eofnl > eofnl.log; then 271 echo " Incorrect end-of-file formatting detected" 272 cat eofnl.log 273 rc=1 274 else 275 echo " OK" 276 fi 277 rm -f eofnl.log 278 279 return $rc 280} 281 282function check_posix_includes() { 283 local rc=0 284 285 echo -n "Checking for POSIX includes..." 286 git grep -I -i -f scripts/posix.txt -- './*' ':!include/spdk/stdinc.h' \ 287 ':!include/linux/**' ':!scripts/posix.txt' ':!lib/env_dpdk/*/*.h' \ 288 ':!*.patch' ':!configure' > scripts/posix.log || true 289 if [ -s scripts/posix.log ]; then 290 echo "POSIX includes detected. Please include spdk/stdinc.h instead." 291 cat scripts/posix.log 292 rc=1 293 else 294 echo " OK" 295 fi 296 rm -f scripts/posix.log 297 298 return $rc 299} 300 301check_function_conventions() { 302 local rc=0 303 304 echo -n "Checking for proper function naming conventions..." 305 # commit_to_compare = HEAD - 1. 306 commit_to_compare="$(git log --pretty=oneline --skip=1 -n 1 | awk '{print $1}')" 307 failed_naming_conventions=false 308 changed_c_libs=() 309 declared_symbols=() 310 311 # Build an array of all the modified C libraries. 312 mapfile -t changed_c_libs < <(git diff --name-only HEAD $commit_to_compare -- lib/**/*.c module/**/*.c | xargs -r dirname | sort | uniq) 313 # Capture the names of all function declarations 314 mapfile -t declared_symbols < <(grep -Eh 'spdk_[a-zA-Z0-9_]*\(' include/spdk*/*.h \ 315 | sed -En 's/.*(spdk[a-z,A-Z,0-9,_]*)\(.*/\1/p') 316 317 for c_lib in "${changed_c_libs[@]}"; do 318 lib_map_file="mk/spdk_blank.map" 319 defined_symbols=() 320 removed_symbols=() 321 exported_symbols=() 322 if ls "$c_lib"/*.map &> /dev/null; then 323 lib_map_file="$(ls "$c_lib"/*.map)" 324 fi 325 # Matching groups are 1. leading +sign. 2, function name 3. argument list / anything after that. 326 # Capture just the names of newly added (or modified) functions that start with "spdk_" 327 mapfile -t defined_symbols < <(git diff -U0 $commit_to_compare HEAD -- $c_lib | sed -En 's/(^[+])(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p') 328 # Capture the names of removed symbols to catch edge cases where we just move definitions around. 329 mapfile -t removed_symbols < <(git diff -U0 $commit_to_compare HEAD -- $c_lib | sed -En 's/(^[-])(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p') 330 for symbol in "${removed_symbols[@]}"; do 331 for i in "${!defined_symbols[@]}"; do 332 if [[ ${defined_symbols[i]} = "$symbol" ]]; then 333 unset -v 'defined_symbols[i]' 334 fi 335 done 336 done 337 # It's possible that we just modified a functions arguments so unfortunately we can't just look at changed lines in this function. 338 # matching groups are 1. All leading whitespace 2. function name. Capture just the symbol name. 339 mapfile -t exported_symbols < <(sed -En 's/(^[[:space:]]*)(spdk[a-z,A-Z,0-9,_]*);/\2/p' < $lib_map_file) 340 for defined_symbol in "${defined_symbols[@]}"; do 341 # if the list of defined symbols is equal to the list of removed symbols, then we are left with a single empty element. skip it. 342 if [ "$defined_symbol" = '' ]; then 343 continue 344 fi 345 not_exported=true 346 not_declared=true 347 if array_contains_string exported_symbols $defined_symbol; then 348 not_exported=false 349 fi 350 351 if array_contains_string declared_symbols $defined_symbol; then 352 not_declared=false 353 fi 354 355 if $not_exported || $not_declared; then 356 if ! $failed_naming_conventions; then 357 echo " found naming convention errors." 358 fi 359 echo "function $defined_symbol starts with spdk_ which is reserved for public API functions." 360 echo "Please add this function to its corresponding map file and a public header or remove the spdk_ prefix." 361 failed_naming_conventions=true 362 rc=1 363 fi 364 done 365 done 366 367 if ! $failed_naming_conventions; then 368 echo " OK" 369 fi 370 371 return $rc 372} 373 374check_conventions_generic() { 375 local out decltype=$1 excludes=(${@:2}) 376 377 # We only care about the types defined at the beginning of a line to allow stuff like nested 378 # structs. Also, we need to drop any __attribute__ declarations. 379 out=$(git grep -E "^$decltype\s+\w+.*\{$" -- "include/spdk" "${excludes[@]}" \ 380 | sed 's/__attribute__\s*(([[:alnum:]_, ()]*))\s*//g' \ 381 | awk "!/$decltype\s+spdk_/ { \$(NF--)=\"\"; print }") 382 383 if [[ -n "$out" ]]; then 384 cat <<- WARN 385 Found $decltype declarations without the spdk_ prefix: 386 387 $out 388 WARN 389 return 1 390 fi 391} 392 393check_naming_conventions() { 394 check_function_conventions 395 # There are still some enums without the spdk_ prefix. Once they're renamed, we can remove 396 # these excludes 397 check_conventions_generic 'enum' \ 398 ':!include/spdk/blob.h' \ 399 ':!include/spdk/ftl.h' \ 400 ':!include/spdk/idxd_spec.h' \ 401 ':!include/spdk/iscsi_spec.h' \ 402 ':!include/spdk/lvol.h' \ 403 ':!include/spdk/nvmf_fc_spec.h' \ 404 ':!include/spdk/vfio_user_spec.h' 405 # Same with structs 406 check_conventions_generic 'struct' \ 407 ':!include/spdk/ftl.h' \ 408 ':!include/spdk/idxd_spec.h' \ 409 ':!include/spdk/iscsi_spec.h' \ 410 ':!include/spdk/vfio_user_spec.h' 411} 412 413function check_include_style() { 414 local rc=0 415 416 echo -n "Checking #include style..." 417 git grep -I -i --line-number "#include <spdk/" -- '*.[ch]' > scripts/includes.log || true 418 if [ -s scripts/includes.log ]; then 419 echo "Incorrect #include syntax. #includes of spdk/ files should use quotes." 420 cat scripts/includes.log 421 rc=1 422 else 423 echo " OK" 424 fi 425 rm -f scripts/includes.log 426 427 return $rc 428} 429 430function check_opts_structs() { 431 local IFS="|" out types=( 432 spdk_nvme_ns_cmd_ext_io_opts 433 spdk_dif_ctx_init_ext_opts 434 ) 435 436 if out=$(git grep -InE "(\.|->)size[[:space:]]*=[[:space:]]*sizeof\(struct (${types[*]})\)"); then 437 cat <<- WARN 438 Found incorrect *ext_opts struct usage. Use SPDK_SIZEOF() to calculate its size. 439 440 $out 441 WARN 442 return 1 443 fi 444} 445 446function check_attr_packed() { 447 local out 448 449 # For now, we only care about the packed attribute in selected files. We only check those 450 # used by Timberland (see https://github.com/timberland-sig), as they're using msvc, which 451 # doesn't support the __attribute__ keyword. 452 if out=$(git grep -In '__attribute__((packed))' \ 453 'include/spdk/nvme*.h' \ 454 'include/spdk/sock.h' \ 455 'include/spdk_internal/nvme*.h' \ 456 'lib/nvme' 'lib/sock'); then 457 cat <<- WARN 458 Found forbidden __attribute__((packed)). Try to pack the structures manually or 459 use #pragma pack instead. 460 461 $out 462 WARN 463 return 1 464 fi 465} 466 467function check_python_style() { 468 local rc=0 469 470 if hash pycodestyle 2> /dev/null; then 471 PEP8=pycodestyle 472 elif hash pep8 2> /dev/null; then 473 PEP8=pep8 474 fi 475 476 if [ -n "${PEP8}" ]; then 477 echo -n "Checking Python style..." 478 479 PEP8_ARGS=" --max-line-length=140" 480 481 error=0 482 git ls-files '*.py' | xargs -P$(nproc) -n1 $PEP8 $PEP8_ARGS > pep8.log || error=1 483 if [ $error -ne 0 ]; then 484 echo " Python formatting errors detected" 485 cat pep8.log 486 rc=1 487 else 488 echo " OK" 489 fi 490 rm -f pep8.log 491 else 492 echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!" 493 fi 494 495 return $rc 496} 497 498function check_golang_style() { 499 local rc=0 out 500 501 if hash golangci-lint 2> /dev/null; then 502 echo -n "Checking Golang style..." 503 gofmtOut=$(gofmt -d . 2>&1) 504 golintOut=$(find "$rootdir/go" -name go.mod -execdir golangci-lint run \; 2>&1) 505 if [[ -n "$gofmtOut" ]] && [[ $gofmtOut == *"diff"* ]]; then 506 cat <<- WARN 507 Golang formatting errors detected: 508 echo "$gofmtOut" 509 WARN 510 511 return 1 512 elif [[ -n "$golintOut" ]]; then 513 cat <<- WARN 514 Golangci lint failed: 515 echo "$golintOut" 516 WARN 517 518 return 1 519 else 520 echo "OK" 521 fi 522 else 523 echo "You do not have golangci-lint installed, so Golang style will not be checked!" 524 fi 525 return 0 526} 527 528function get_bash_files() { 529 local sh shebang 530 531 mapfile -t sh < <(git ls-files '*.sh') 532 mapfile -t shebang < <(git grep -l '^#!.*bash') 533 534 get_diffed_dups "${sh[@]}" "${shebang[@]}" 535} 536 537function check_bash_style() { 538 local rc=0 supported_shfmt_version=v3.8.0 539 540 # find compatible shfmt binary 541 shfmt_bins=($(compgen -c | grep '^shfmt' | uniq || true)) 542 for bin in "${shfmt_bins[@]}"; do 543 shfmt_version=$("$bin" --version) 544 if [[ $shfmt_version == "$supported_shfmt_version" ]]; then 545 shfmt=$bin 546 break 547 fi 548 done 549 550 if [ -n "$shfmt" ]; then 551 shfmt_cmdline=() sh_files=() 552 553 mapfile -t sh_files < <(get_bash_files) 554 555 if ((${#sh_files[@]})); then 556 printf 'Checking .sh formatting style...' 557 558 shfmt_cmdline+=(-i 0) # indent_style = tab|indent_size = 0 559 shfmt_cmdline+=(-bn) # binary_next_line = true 560 shfmt_cmdline+=(-ci) # switch_case_indent = true 561 shfmt_cmdline+=(-ln bash) # shell_variant = bash (default) 562 shfmt_cmdline+=(-d) # diffOut - print diff of the changes and exit with != 0 563 shfmt_cmdline+=(-sr) # redirect operators will be followed by a space 564 565 diff=${output_dir:-$PWD}/$shfmt.patch 566 567 if ! "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then 568 # In case shfmt detects an actual syntax error it will write out a proper message on 569 # its stderr, hence the diff file should remain empty. 570 rc=1 571 if [[ -s $diff ]]; then 572 if patch --merge -p0 < "$diff"; then 573 diff_out=$(git diff) 574 575 if [[ -n $diff_out ]]; then 576 cat <<- ERROR_SHFMT 577 578 * Errors in style formatting have been detected. 579 Please, review the generated patch at $diff 580 581 # _START_OF_THE_DIFF 582 583 $diff_out 584 585 # _END_OF_THE_DIFF 586 587 ERROR_SHFMT 588 else 589 # Empty diff? This likely means that we reverted to a clean state 590 printf '* Patch reverted, please review your changes and %s\n' "$diff" 591 fi 592 else 593 printf '* Failed to apply %s\n' "$diff" 594 595 fi 596 fi 597 else 598 rm -f "$diff" 599 printf ' OK\n' 600 fi 601 fi 602 else 603 cat <<- MSG 604 Supported version of shfmt not detected${shfmt_bins[*]:+ (only ${shfmt_bins[*]} is available)}. Bash style 605 formatting check is skipped. shfmt-$supported_shfmt_version can be installed using 'scripts/pkgdep.sh -d'. 606 MSG 607 fi 608 609 # Cleanup potential .orig files that shfmt creates 610 local orig_f 611 612 mapfile -t orig_f < <(git diff --name-only) 613 orig_f=("${orig_f[@]/%/.orig}") 614 615 if ((${#orig_f[@]} > 0)); then 616 git clean -f "${orig_f[@]}" 617 fi 618 619 return $rc 620} 621 622function check_bash_static_analysis() { 623 local rc=0 files=() 624 625 mapfile -t files < <(get_bash_files) 626 ((${#files[@]} > 0)) || return 0 627 628 if hash shellcheck 2> /dev/null; then 629 echo -n "Checking Bash static analysis with shellcheck..." 630 631 shellcheck_v=$(shellcheck --version | grep -P "version: [0-9\.]+" | cut -d " " -f2) 632 633 # SHCK_EXCLUDE contains a list of all of the spellcheck errors found in SPDK scripts 634 # currently. New errors should only be added to this list if the cost of fixing them 635 # is deemed too high. For more information about the errors, go to: 636 # https://github.com/koalaman/shellcheck/wiki/Checks 637 # Error descriptions can also be found at: https://github.com/koalaman/shellcheck/wiki 638 # SPDK fails some error checks which have been deprecated in later versions of shellcheck. 639 # We will not try to fix these error checks, but instead just leave the error types here 640 # so that we can still run with older versions of shellcheck. 641 SHCK_EXCLUDE="SC1117" 642 # SPDK has decided to not fix violations of these errors. 643 # We are aware about below exclude list and we want this errors to be excluded. 644 # SC1083: This {/} is literal. Check expression (missing ;/\n?) or quote it. 645 # SC1090: Can't follow non-constant source. Use a directive to specify location. 646 # SC1091: Not following: (error message here) 647 # SC2001: See if you can use ${variable//search/replace} instead. 648 # SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames. 649 # SC2015: Note that A && B || C is not if-then-else. C may run when A is true. 650 # SC2016: Expressions don't expand in single quotes, use double quotes for that. 651 # SC2034: foo appears unused. Verify it or export it. 652 # SC2046: Quote this to prevent word splitting. 653 # SC2086: Double quote to prevent globbing and word splitting. 654 # SC2119: Use foo "$@" if function's $1 should mean script's $1. 655 # SC2120: foo references arguments, but none are ever passed. 656 # SC2128: Expanding an array without an index only gives the first element. 657 # SC2148: Add shebang to the top of your script. 658 # SC2153: Possible Misspelling: MYVARIABLE may not be assigned, but MY_VARIABLE is. 659 # SC2154: var is referenced but not assigned. 660 # SC2164: Use cd ... || exit in case cd fails. 661 # SC2174: When used with -p, -m only applies to the deepest directory. 662 # SC2178: Variable was used as an array but is now assigned a string. 663 # SC2206: Quote to prevent word splitting/globbing, 664 # or split robustly with mapfile or read -a. 665 # SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting). 666 # SC2223: This default assignment may cause DoS due to globbing. Quote it. 667 SHCK_EXCLUDE="$SHCK_EXCLUDE,SC1083,SC1090,SC1091,SC2010,SC2015,SC2016,SC2034,SC2046,SC2086,\ 668SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2164,SC2174,SC2178,SC2001,SC2206,SC2207,SC2223" 669 670 SHCK_FORMAT="tty" 671 SHCK_APPLY=false 672 SHCH_ARGS="-e $SHCK_EXCLUDE -f $SHCK_FORMAT" 673 674 if ge "$shellcheck_v" 0.4.0; then 675 SHCH_ARGS+=" -x" 676 else 677 echo "shellcheck $shellcheck_v detected, recommended >= 0.4.0." 678 fi 679 680 printf '%s\n' "${files[@]}" | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS &> shellcheck.log 681 if [[ -s shellcheck.log ]]; then 682 echo " Bash shellcheck errors detected!" 683 684 cat shellcheck.log 685 if $SHCK_APPLY; then 686 git apply shellcheck.log 687 echo "Bash errors were automatically corrected." 688 echo "Please remember to add the changes to your commit." 689 fi 690 rc=1 691 else 692 echo " OK" 693 fi 694 rm -f shellcheck.log 695 else 696 echo "You do not have shellcheck installed so your Bash static analysis is not being performed!" 697 fi 698 699 return $rc 700} 701 702function check_changelog() { 703 local rc=0 704 705 # Check if any of the public interfaces were modified by this patch. 706 # Warn the user to consider updating the changelog any changes 707 # are detected. 708 echo -n "Checking whether CHANGELOG.md should be updated..." 709 staged=$(git diff --name-only --cached .) 710 working=$(git status -s --porcelain --ignore-submodules | grep -iv "??" | awk '{print $2}') 711 files="$staged $working" 712 if [[ "$files" = " " ]]; then 713 files=$(git diff-tree --no-commit-id --name-only -r HEAD) 714 fi 715 716 has_changelog=0 717 for f in $files; do 718 if [[ $f == CHANGELOG.md ]]; then 719 # The user has a changelog entry, so exit. 720 has_changelog=1 721 break 722 fi 723 done 724 725 needs_changelog=0 726 if [ $has_changelog -eq 0 ]; then 727 for f in $files; do 728 if [[ $f == include/spdk/* ]] || [[ $f == scripts/rpc.py ]] || [[ $f == etc/* ]]; then 729 echo "" 730 echo -n "$f was modified. Consider updating CHANGELOG.md." 731 needs_changelog=1 732 fi 733 done 734 fi 735 736 if [ $needs_changelog -eq 0 ]; then 737 echo " OK" 738 else 739 echo "" 740 fi 741 742 return $rc 743} 744 745function check_json_rpc() { 746 local rc=0 747 748 echo -n "Checking that all RPCs are documented..." 749 while IFS='"' read -r _ rpc _; do 750 if ! grep -q "^### $rpc" doc/jsonrpc.md; then 751 echo "Missing JSON-RPC documentation for ${rpc}" 752 rc=1 753 fi 754 done < <(git grep -h -E "^SPDK_RPC_REGISTER\(" ':!test/*' ':!examples/*') 755 756 if [ $rc -eq 0 ]; then 757 echo " OK" 758 fi 759 return $rc 760} 761 762function check_markdown_format() { 763 local rc=0 764 765 if hash mdl 2> /dev/null; then 766 echo -n "Checking markdown files format..." 767 mdl -g -s $rootdir/mdl_rules.rb . > mdl.log || true 768 if [ -s mdl.log ]; then 769 echo " Errors in .md files detected:" 770 cat mdl.log 771 rc=1 772 else 773 echo " OK" 774 fi 775 rm -f mdl.log 776 else 777 echo "You do not have markdownlint installed so .md files not being checked!" 778 fi 779 780 return $rc 781} 782 783function check_rpc_args() { 784 local rc=0 785 786 echo -n "Checking rpc.py argument option names..." 787 grep add_argument scripts/rpc.py | $GNU_GREP -oP "(?<=--)[a-z0-9\-\_]*(?=\')" | grep "_" > badargs.log 788 789 if [[ -s badargs.log ]]; then 790 echo "rpc.py arguments with underscores detected!" 791 cat badargs.log 792 echo "Please convert the underscores to dashes." 793 rc=1 794 else 795 echo " OK" 796 fi 797 rm -f badargs.log 798 return $rc 799} 800 801function get_files_for_lic() { 802 local f_shebang="" f_suffix=() f_type=() f_all=() exceptions="" 803 804 f_shebang+="bash|" 805 f_shebang+="make|" 806 f_shebang+="perl|" 807 f_shebang+="python|" 808 f_shebang+="sh" 809 810 f_suffix+=("*.c") 811 f_suffix+=("*.cpp") 812 f_suffix+=("*.h") 813 f_suffix+=("*.go") 814 f_suffix+=("*.mk") 815 f_suffix+=("*.pl") 816 f_suffix+=("*.py") 817 f_suffix+=("*.sh") 818 f_suffix+=("*.yaml") 819 820 f_type+=("*Dockerfile") 821 f_type+=("*Makefile") 822 823 # Exclude files that may match the above types but should not 824 # fall under SPDX check. 825 exceptions+="include/linux|" 826 exceptions+="include/spdk/queue_extras.h|" 827 exceptions+="lib/mlx5/mlx5_ifc.h" 828 829 mapfile -t f_all < <( 830 git ls-files "${f_suffix[@]}" "${f_type[@]}" 831 git grep -lE "^#!.*($f_shebang)" 832 ) 833 834 printf '%s\n' "${f_all[@]}" | sort -u | grep -vE "$exceptions" 835} 836 837function check_spdx_lic() { 838 local files_missing_license_header=() hint=() 839 local rc=0 840 841 hint+=("SPDX-License-Identifier: BSD-3-Clause") 842 hint+=("All rights reserved.") 843 844 printf 'Checking SPDX-license...' 845 846 mapfile -t files_missing_license_header < <( 847 grep -LE "SPDX-License-Identifier:.+" $(get_files_for_lic) 848 ) 849 850 if ((${#files_missing_license_header[@]} > 0)); then 851 printf '\nFollowing files are missing SPDX-license header:\n' 852 printf ' @%s\n' "${files_missing_license_header[@]}" 853 printf '\nExample:\n' 854 printf ' # %s\n' "${hint[@]}" 855 return 1 856 fi 857 858 printf 'OK\n' 859} 860 861function get_diffed_files() { 862 # Get files where changes are meant to be committed 863 git diff --name-only HEAD HEAD~1 864 # Get files from staging 865 git diff --name-only --cached HEAD 866 git diff --name-only HEAD 867} 868 869function get_diffed_dups() { 870 local files=("$@") diff=() _diff=() 871 872 # Sort and get rid of duplicates from the main list 873 mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u) 874 # Get staged|committed files 875 mapfile -t diff < <(get_diffed_files | sort -u) 876 877 if [[ ! -v CHECK_FORMAT_ONLY_DIFF ]]; then 878 # Just return the main list 879 printf '%s\n' "${files[@]}" 880 return 0 881 fi 882 883 if ((${#diff[@]} > 0)); then 884 # Check diff'ed files against the main list to see if they are a subset 885 # of it. If yes, then we return the duplicates which are the files that 886 # should be committed, modified. 887 mapfile -t _diff < <( 888 printf '%s\n' "${diff[@]}" "${files[@]}" | sort | uniq -d 889 ) 890 if ((${#_diff[@]} > 0)); then 891 printf '%s\n' "${_diff[@]}" 892 return 0 893 fi 894 fi 895} 896 897function check_extern_c() { 898 local files_missing_extern_c=() 899 900 printf 'Checking extern "C"... ' 901 902 mapfile -t files_missing_extern_c < <( 903 grep -LE "extern \"C\"" $(git ls-files -- ./include/spdk/*.h) 904 ) 905 906 if ((${#files_missing_extern_c[@]} > 0)); then 907 printf '\nFollowing header files are missing extern C:\n' 908 printf ' %s\n' "${files_missing_extern_c[@]}" 909 return 1 910 fi 911 912 printf 'OK\n' 913} 914 915rc=0 916 917check_permissions || rc=1 918check_c_style || rc=1 919 920GIT_VERSION=$(git --version | cut -d' ' -f3) 921 922if version_lt "1.9.5" "${GIT_VERSION}"; then 923 # git <1.9.5 doesn't support pathspec magic exclude 924 echo " Your git version is too old to perform all tests. Please update git to at least 1.9.5 version..." 925 exit $rc 926fi 927 928check_comment_style || rc=1 929check_markdown_format || rc=1 930check_spaces_before_tabs || rc=1 931check_trailing_whitespace || rc=1 932check_forbidden_functions || rc=1 933check_cunit_style || rc=1 934check_eof || rc=1 935check_posix_includes || rc=1 936check_naming_conventions || rc=1 937check_include_style || rc=1 938check_opts_structs || rc=1 939check_attr_packed || rc=1 940check_python_style || rc=1 941check_bash_style || rc=1 942check_bash_static_analysis || rc=1 943check_changelog || rc=1 944check_json_rpc || rc=1 945check_rpc_args || rc=1 946check_spdx_lic || rc=1 947check_golang_style || rc=1 948check_extern_c || rc=1 949 950exit $rc 951