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