1#!/usr/bin/env bash 2 3rootdir=$(readlink -f "$(dirname "$0")")/.. 4source "$rootdir/scripts/common.sh" 5 6cd "$rootdir" 7 8# exit on errors 9set -e 10 11if ! hash nproc 2> /dev/null; then 12 13 function nproc() { 14 echo 8 15 } 16 17fi 18 19function version_lt() { 20 [ $(echo -e "$1\n$2" | sort -V | head -1) != "$1" ] 21} 22 23function array_contains_string() { 24 name="$1[@]" 25 array=("${!name}") 26 27 for element in "${array[@]}"; do 28 if [ "$element" = "$2" ]; then 29 return $(true) 30 fi 31 done 32 33 return $(false) 34} 35 36rc=0 37 38function check_permissions() { 39 echo -n "Checking file permissions..." 40 41 local rc=0 42 43 while read -r perm _res0 _res1 path; do 44 if [ ! -f "$path" ]; then 45 continue 46 fi 47 48 # Skip symlinks 49 if [[ -L $path ]]; then 50 continue 51 fi 52 fname=$(basename -- "$path") 53 54 case ${fname##*.} in 55 c | h | cpp | cc | cxx | hh | hpp | md | html | js | json | svg | Doxyfile | yml | LICENSE | README | conf | in | Makefile | mk | gitignore | go | txt) 56 # These file types should never be executable 57 if [ "$perm" -eq 100755 ]; then 58 echo "ERROR: $path is marked executable but is a code file." 59 rc=1 60 fi 61 ;; 62 *) 63 shebang=$(head -n 1 $path | cut -c1-3) 64 65 # git only tracks the execute bit, so will only ever return 755 or 644 as the permission. 66 if [ "$perm" -eq 100755 ]; then 67 # If the file has execute permission, it should start with a shebang. 68 if [ "$shebang" != "#!/" ]; then 69 echo "ERROR: $path is marked executable but does not start with a shebang." 70 rc=1 71 fi 72 else 73 # If the file doesnot have execute permissions, it should not start with a shebang. 74 if [ "$shebang" = "#!/" ]; then 75 echo "ERROR: $path is not marked executable but starts with a shebang." 76 rc=1 77 fi 78 fi 79 ;; 80 esac 81 82 done <<< "$(git grep -I --name-only --untracked -e . | git ls-files -s)" 83 84 if [ $rc -eq 0 ]; then 85 echo " OK" 86 fi 87 88 return $rc 89} 90 91function check_c_style() { 92 local rc=0 93 94 if hash astyle; then 95 echo -n "Checking coding style..." 96 if [ "$(astyle -V)" \< "Artistic Style Version 3" ]; then 97 echo -n " Your astyle version is too old so skipping coding style checks. Please update astyle to at least 3.0.1 version..." 98 else 99 rm -f astyle.log 100 touch astyle.log 101 # Exclude rte_vhost code imported from DPDK - we want to keep the original code 102 # as-is to enable ongoing work to synch with a generic upstream DPDK vhost library, 103 # rather than making diffs more complicated by a lot of changes to follow SPDK 104 # coding standards. 105 git ls-files '*.[ch]' '*.cpp' '*.cc' '*.cxx' '*.hh' '*.hpp' \ 106 | grep -v rte_vhost | grep -v cpp_headers \ 107 | xargs -P$(nproc) -n10 astyle --options=.astylerc >> astyle.log 108 if grep -q "^Formatted" astyle.log; then 109 echo " errors detected" 110 git diff --ignore-submodules=all 111 sed -i -e 's/ / /g' astyle.log 112 grep --color=auto "^Formatted.*" astyle.log 113 echo "Incorrect code style detected in one or more files." 114 echo "The files have been automatically formatted." 115 echo "Remember to add the files to your commit." 116 rc=1 117 else 118 echo " OK" 119 fi 120 rm -f astyle.log 121 fi 122 else 123 echo "You do not have astyle installed so your code style is not being checked!" 124 fi 125 return $rc 126} 127 128function check_comment_style() { 129 local rc=0 130 131 echo -n "Checking comment style..." 132 133 git grep --line-number -e '\/[*][^ *-]' -- '*.[ch]' > comment.log || true 134 git grep --line-number -e '[^ ][*]\/' -- '*.[ch]' ':!lib/rte_vhost*/*' >> comment.log || true 135 git grep --line-number -e '^[*]' -- '*.[ch]' >> comment.log || true 136 git grep --line-number -e '\s\/\/' -- '*.[ch]' >> comment.log || true 137 git grep --line-number -e '^\/\/' -- '*.[ch]' >> comment.log || true 138 139 if [ -s comment.log ]; then 140 echo " Incorrect comment formatting detected" 141 cat comment.log 142 rc=1 143 else 144 echo " OK" 145 fi 146 rm -f comment.log 147 148 return $rc 149} 150 151function check_spaces_before_tabs() { 152 local rc=0 153 154 echo -n "Checking for spaces before tabs..." 155 git grep --line-number $' \t' -- './*' ':!*.patch' > whitespace.log || true 156 if [ -s whitespace.log ]; then 157 echo " Spaces before tabs detected" 158 cat whitespace.log 159 rc=1 160 else 161 echo " OK" 162 fi 163 rm -f whitespace.log 164 165 return $rc 166} 167 168function check_trailing_whitespace() { 169 local rc=0 170 171 echo -n "Checking trailing whitespace in output strings..." 172 173 git grep --line-number -e ' \\n"' -- '*.[ch]' > whitespace.log || true 174 175 if [ -s whitespace.log ]; then 176 echo " Incorrect trailing whitespace detected" 177 cat whitespace.log 178 rc=1 179 else 180 echo " OK" 181 fi 182 rm -f whitespace.log 183 184 return $rc 185} 186 187function check_forbidden_functions() { 188 local rc=0 189 190 echo -n "Checking for use of forbidden library functions..." 191 192 git grep --line-number -w '\(atoi\|atol\|atoll\|strncpy\|strcpy\|strcat\|sprintf\|vsprintf\)' -- './*.c' ':!lib/rte_vhost*/**' > badfunc.log || true 193 if [ -s badfunc.log ]; then 194 echo " Forbidden library functions detected" 195 cat badfunc.log 196 rc=1 197 else 198 echo " OK" 199 fi 200 rm -f badfunc.log 201 202 return $rc 203} 204 205function check_cunit_style() { 206 local rc=0 207 208 echo -n "Checking for use of forbidden CUnit macros..." 209 210 git grep --line-number -w 'CU_ASSERT_FATAL' -- 'test/*' ':!test/spdk_cunit.h' > badcunit.log || true 211 if [ -s badcunit.log ]; then 212 echo " Forbidden CU_ASSERT_FATAL usage detected - use SPDK_CU_ASSERT_FATAL instead" 213 cat badcunit.log 214 rc=1 215 else 216 echo " OK" 217 fi 218 rm -f badcunit.log 219 220 return $rc 221} 222 223function check_eof() { 224 local rc=0 225 226 echo -n "Checking blank lines at end of file..." 227 228 if ! git grep -I -l -e . -z './*' ':!*.patch' \ 229 | xargs -0 -P$(nproc) -n1 scripts/eofnl > eofnl.log; then 230 echo " Incorrect end-of-file formatting detected" 231 cat eofnl.log 232 rc=1 233 else 234 echo " OK" 235 fi 236 rm -f eofnl.log 237 238 return $rc 239} 240 241function check_posix_includes() { 242 local rc=0 243 244 echo -n "Checking for POSIX includes..." 245 git grep -I -i -f scripts/posix.txt -- './*' ':!include/spdk/stdinc.h' ':!include/linux/**' ':!lib/rte_vhost*/**' ':!scripts/posix.txt' ':!*.patch' > scripts/posix.log || true 246 if [ -s scripts/posix.log ]; then 247 echo "POSIX includes detected. Please include spdk/stdinc.h instead." 248 cat scripts/posix.log 249 rc=1 250 else 251 echo " OK" 252 fi 253 rm -f scripts/posix.log 254 255 return $rc 256} 257 258function check_naming_conventions() { 259 local rc=0 260 261 echo -n "Checking for proper function naming conventions..." 262 # commit_to_compare = HEAD - 1. 263 commit_to_compare="$(git log --pretty=oneline --skip=1 -n 1 | awk '{print $1}')" 264 failed_naming_conventions=false 265 changed_c_libs=() 266 declared_symbols=() 267 268 # Build an array of all the modified C files. 269 mapfile -t changed_c_libs < <(git diff --name-only HEAD $commit_to_compare -- lib/**/*.c module/**/*.c) 270 # Matching groups are 1. qualifiers / return type. 2. function name 3. argument list / comments and stuff after that. 271 # Capture just the names of newly added (or modified) function definitions. 272 mapfile -t declared_symbols < <(git diff -U0 $commit_to_compare HEAD -- include/spdk*/*.h | sed -En 's/(^[+].*)(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p') 273 274 for c_file in "${changed_c_libs[@]}"; do 275 lib_map_file="mk/spdk_blank.map" 276 defined_symbols=() 277 removed_symbols=() 278 exported_symbols=() 279 if ls "$(dirname $c_file)"/*.map &> /dev/null; then 280 lib_map_file="$(ls "$(dirname $c_file)"/*.map)" 281 fi 282 # Matching groups are 1. leading +sign. 2, function name 3. argument list / anything after that. 283 # Capture just the names of newly added (or modified) functions that start with "spdk_" 284 mapfile -t defined_symbols < <(git diff -U0 $commit_to_compare HEAD -- $c_file | sed -En 's/(^[+])(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p') 285 # Capture the names of removed symbols to catch edge cases where we just move definitions around. 286 mapfile -t removed_symbols < <(git diff -U0 $commit_to_compare HEAD -- $c_file | sed -En 's/(^[-])(spdk[a-z,A-Z,0-9,_]*)(\(.*)/\2/p') 287 for symbol in "${removed_symbols[@]}"; do 288 for i in "${!defined_symbols[@]}"; do 289 if [[ ${defined_symbols[i]} = "$symbol" ]]; then 290 unset -v 'defined_symbols[i]' 291 fi 292 done 293 done 294 # It's possible that we just modified a functions arguments so unfortunately we can't just look at changed lines in this function. 295 # matching groups are 1. All leading whitespace 2. function name. Capture just the symbol name. 296 mapfile -t exported_symbols < <(sed -En 's/(^[[:space:]]*)(spdk[a-z,A-Z,0-9,_]*);/\2/p' < $lib_map_file) 297 for defined_symbol in "${defined_symbols[@]}"; do 298 # 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. 299 if [ "$defined_symbol" = '' ]; then 300 continue 301 fi 302 not_exported=true 303 not_declared=true 304 if array_contains_string exported_symbols $defined_symbol; then 305 not_exported=false 306 fi 307 308 if array_contains_string declared_symbols $defined_symbol; then 309 not_declared=false 310 fi 311 312 if $not_exported || $not_declared; then 313 if ! $failed_naming_conventions; then 314 echo " found naming convention errors." 315 fi 316 echo "function $defined_symbol starts with spdk_ which is reserved for public API functions." 317 echo "Please add this function to its corresponding map file and a public header or remove the spdk_ prefix." 318 failed_naming_conventions=true 319 rc=1 320 fi 321 done 322 done 323 324 if ! $failed_naming_conventions; then 325 echo " OK" 326 fi 327 328 return $rc 329} 330 331function check_include_style() { 332 local rc=0 333 334 echo -n "Checking #include style..." 335 git grep -I -i --line-number "#include <spdk/" -- '*.[ch]' > scripts/includes.log || true 336 if [ -s scripts/includes.log ]; then 337 echo "Incorrect #include syntax. #includes of spdk/ files should use quotes." 338 cat scripts/includes.log 339 rc=1 340 else 341 echo " OK" 342 fi 343 rm -f scripts/includes.log 344 345 return $rc 346} 347 348function check_python_style() { 349 local rc=0 350 351 if hash pycodestyle 2> /dev/null; then 352 PEP8=pycodestyle 353 elif hash pep8 2> /dev/null; then 354 PEP8=pep8 355 fi 356 357 if [ -n "${PEP8}" ]; then 358 echo -n "Checking Python style..." 359 360 PEP8_ARGS+=" --max-line-length=140" 361 362 error=0 363 git ls-files '*.py' | xargs -P$(nproc) -n1 $PEP8 $PEP8_ARGS > pep8.log || error=1 364 if [ $error -ne 0 ]; then 365 echo " Python formatting errors detected" 366 cat pep8.log 367 rc=1 368 else 369 echo " OK" 370 fi 371 rm -f pep8.log 372 else 373 echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!" 374 fi 375 376 return $rc 377} 378 379function get_bash_files() { 380 local sh shebang 381 382 mapfile -t sh < <(git ls-files '*.sh') 383 mapfile -t shebang < <(git grep -l '^#!.*bash') 384 385 printf '%s\n' "${sh[@]}" "${shebang[@]}" | sort -u 386} 387 388function check_bash_style() { 389 local rc=0 390 391 # find compatible shfmt binary 392 shfmt_bins=$(compgen -c | grep '^shfmt' || true) 393 for bin in $shfmt_bins; do 394 if version_lt "$("$bin" --version)" "3.1.0"; then 395 shfmt=$bin 396 break 397 fi 398 done 399 400 if [ -n "$shfmt" ]; then 401 shfmt_cmdline=() sh_files=() 402 403 mapfile -t sh_files < <(get_bash_files) 404 405 if ((${#sh_files[@]})); then 406 printf 'Checking .sh formatting style...' 407 408 shfmt_cmdline+=(-i 0) # indent_style = tab|indent_size = 0 409 shfmt_cmdline+=(-bn) # binary_next_line = true 410 shfmt_cmdline+=(-ci) # switch_case_indent = true 411 shfmt_cmdline+=(-ln bash) # shell_variant = bash (default) 412 shfmt_cmdline+=(-d) # diffOut - print diff of the changes and exit with != 0 413 shfmt_cmdline+=(-sr) # redirect operators will be followed by a space 414 415 diff=${output_dir:-$PWD}/$shfmt.patch 416 417 # Explicitly tell shfmt to not look for .editorconfig. .editorconfig is also not looked up 418 # in case any formatting arguments has been passed on its cmdline. 419 if ! SHFMT_NO_EDITORCONFIG=true "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then 420 # In case shfmt detects an actual syntax error it will write out a proper message on 421 # its stderr, hence the diff file should remain empty. 422 if [[ -s $diff ]]; then 423 diff_out=$(< "$diff") 424 fi 425 426 cat <<- ERROR_SHFMT 427 428 * Errors in style formatting have been detected. 429 ${diff_out:+* Please, review the generated patch at $diff 430 431 # _START_OF_THE_DIFF 432 433 ${diff_out:-ERROR} 434 435 # _END_OF_THE_DIFF 436 } 437 438 ERROR_SHFMT 439 rc=1 440 else 441 rm -f "$diff" 442 printf ' OK\n' 443 fi 444 fi 445 else 446 echo "shfmt not detected, Bash style formatting check is skipped" 447 fi 448 449 return $rc 450} 451 452function check_bash_static_analysis() { 453 local rc=0 454 455 if hash shellcheck 2> /dev/null; then 456 echo -n "Checking Bash style..." 457 458 shellcheck_v=$(shellcheck --version | grep -P "version: [0-9\.]+" | cut -d " " -f2) 459 460 # SHCK_EXCLUDE contains a list of all of the spellcheck errors found in SPDK scripts 461 # currently. New errors should only be added to this list if the cost of fixing them 462 # is deemed too high. For more information about the errors, go to: 463 # https://github.com/koalaman/shellcheck/wiki/Checks 464 # Error descriptions can also be found at: https://github.com/koalaman/shellcheck/wiki 465 # SPDK fails some error checks which have been deprecated in later versions of shellcheck. 466 # We will not try to fix these error checks, but instead just leave the error types here 467 # so that we can still run with older versions of shellcheck. 468 SHCK_EXCLUDE="SC1117" 469 # SPDK has decided to not fix violations of these errors. 470 # We are aware about below exclude list and we want this errors to be excluded. 471 # SC1083: This {/} is literal. Check expression (missing ;/\n?) or quote it. 472 # SC1090: Can't follow non-constant source. Use a directive to specify location. 473 # SC1091: Not following: (error message here) 474 # SC2001: See if you can use ${variable//search/replace} instead. 475 # SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames. 476 # SC2015: Note that A && B || C is not if-then-else. C may run when A is true. 477 # SC2016: Expressions don't expand in single quotes, use double quotes for that. 478 # SC2034: foo appears unused. Verify it or export it. 479 # SC2046: Quote this to prevent word splitting. 480 # SC2086: Double quote to prevent globbing and word splitting. 481 # SC2119: Use foo "$@" if function's $1 should mean script's $1. 482 # SC2120: foo references arguments, but none are ever passed. 483 # SC2128: Expanding an array without an index only gives the first element. 484 # SC2148: Add shebang to the top of your script. 485 # SC2153: Possible Misspelling: MYVARIABLE may not be assigned, but MY_VARIABLE is. 486 # SC2154: var is referenced but not assigned. 487 # SC2164: Use cd ... || exit in case cd fails. 488 # SC2174: When used with -p, -m only applies to the deepest directory. 489 # SC2178: Variable was used as an array but is now assigned a string. 490 # SC2206: Quote to prevent word splitting/globbing, 491 # or split robustly with mapfile or read -a. 492 # SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting). 493 # SC2223: This default assignment may cause DoS due to globbing. Quote it. 494 SHCK_EXCLUDE="$SHCK_EXCLUDE,SC1083,SC1090,SC1091,SC2010,SC2015,SC2016,SC2034,SC2046,SC2086,\ 495SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2164,SC2174,SC2178,SC2001,SC2206,SC2207,SC2223" 496 497 SHCK_FORMAT="tty" 498 SHCK_APPLY=false 499 SHCH_ARGS="-e $SHCK_EXCLUDE -f $SHCK_FORMAT" 500 501 if ge "$shellcheck_v" 0.4.0; then 502 SHCH_ARGS+=" -x" 503 else 504 echo "shellcheck $shellcheck_v detected, recommended >= 0.4.0." 505 fi 506 507 get_bash_files | xargs -P$(nproc) -n1 shellcheck $SHCH_ARGS &> shellcheck.log 508 if [[ -s shellcheck.log ]]; then 509 echo " Bash formatting errors detected!" 510 511 cat shellcheck.log 512 if $SHCK_APPLY; then 513 git apply shellcheck.log 514 echo "Bash errors were automatically corrected." 515 echo "Please remember to add the changes to your commit." 516 fi 517 rc=1 518 else 519 echo " OK" 520 fi 521 rm -f shellcheck.log 522 else 523 echo "You do not have shellcheck installed so your Bash style is not being checked!" 524 fi 525 526 return $rc 527} 528 529function check_changelog() { 530 local rc=0 531 532 # Check if any of the public interfaces were modified by this patch. 533 # Warn the user to consider updating the changelog any changes 534 # are detected. 535 echo -n "Checking whether CHANGELOG.md should be updated..." 536 staged=$(git diff --name-only --cached .) 537 working=$(git status -s --porcelain --ignore-submodules | grep -iv "??" | awk '{print $2}') 538 files="$staged $working" 539 if [[ "$files" = " " ]]; then 540 files=$(git diff-tree --no-commit-id --name-only -r HEAD) 541 fi 542 543 has_changelog=0 544 for f in $files; do 545 if [[ $f == CHANGELOG.md ]]; then 546 # The user has a changelog entry, so exit. 547 has_changelog=1 548 break 549 fi 550 done 551 552 needs_changelog=0 553 if [ $has_changelog -eq 0 ]; then 554 for f in $files; do 555 if [[ $f == include/spdk/* ]] || [[ $f == scripts/rpc.py ]] || [[ $f == etc/* ]]; then 556 echo "" 557 echo -n "$f was modified. Consider updating CHANGELOG.md." 558 needs_changelog=1 559 fi 560 done 561 fi 562 563 if [ $needs_changelog -eq 0 ]; then 564 echo " OK" 565 else 566 echo "" 567 fi 568 569 return $rc 570} 571 572function check_json_rpc() { 573 local rc=0 574 575 echo -n "Checking that all RPCs are documented..." 576 while IFS='"' read -r _ rpc _; do 577 if ! grep -q "^### $rpc" doc/jsonrpc.md; then 578 echo "Missing JSON-RPC documentation for ${rpc}" 579 rc=1 580 fi 581 done < <(git grep -h -E "^SPDK_RPC_REGISTER\(" ':!test/*') 582 583 if [ $rc -eq 0 ]; then 584 echo " OK" 585 fi 586 return $rc 587} 588 589function check_markdown_format() { 590 local rc=0 591 592 if hash mdl 2> /dev/null; then 593 echo -n "Checking markdown files format..." 594 mdl -g -s $rootdir/mdl_rules.rb . > mdl.log || true 595 if [ -s mdl.log ]; then 596 echo " Errors in .md files detected:" 597 cat mdl.log 598 rc=1 599 else 600 echo " OK" 601 fi 602 rm -f mdl.log 603 else 604 echo "You do not have markdownlint installed so .md files not being checked!" 605 fi 606 607 return $rc 608} 609 610function check_rpc_args() { 611 local rc=0 612 613 echo -n "Checking rpc.py argument option names..." 614 grep add_argument scripts/rpc.py | grep -oP "(?<=--)[a-z0-9\-\_]*(?=\')" | grep "_" > badargs.log 615 616 if [[ -s badargs.log ]]; then 617 echo "rpc.py arguments with underscores detected!" 618 cat badargs.log 619 echo "Please convert the underscores to dashes." 620 rc=1 621 else 622 echo " OK" 623 fi 624 rm -f badargs.log 625 return $rc 626} 627 628rc=0 629 630check_permissions || rc=1 631check_c_style || rc=1 632 633GIT_VERSION=$(git --version | cut -d' ' -f3) 634 635if version_lt "1.9.5" "${GIT_VERSION}"; then 636 # git <1.9.5 doesn't support pathspec magic exclude 637 echo " Your git version is too old to perform all tests. Please update git to at least 1.9.5 version..." 638 exit $rc 639fi 640 641check_comment_style || rc=1 642check_markdown_format || rc=1 643check_spaces_before_tabs || rc=1 644check_trailing_whitespace || rc=1 645check_forbidden_functions || rc=1 646check_cunit_style || rc=1 647check_eof || rc=1 648check_posix_includes || rc=1 649check_naming_conventions || rc=1 650check_include_style || rc=1 651check_python_style || rc=1 652check_bash_style || rc=1 653check_bash_static_analysis || rc=1 654check_changelog || rc=1 655check_json_rpc || rc=1 656check_rpc_args || rc=1 657 658exit $rc 659