mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			496 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			496 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/bin/sh
 | 
						||
 | 
						||
# Assume that "local" is available.
 | 
						||
# shellcheck disable=SC2039
 | 
						||
 | 
						||
set -e
 | 
						||
# Note: -u causes problems with posh, it barks at “undefined” $@ when no
 | 
						||
# arguments provided.
 | 
						||
test -z "$POSH_VERSION" && set -u
 | 
						||
 | 
						||
log_info() {
 | 
						||
  >&2 printf "pvscheck.sh: %s\n" "$@"
 | 
						||
}
 | 
						||
 | 
						||
get_jobs_num() {
 | 
						||
  if [ -n "${TRAVIS:-}" ] ; then
 | 
						||
    # HACK: /proc/cpuinfo on Travis CI is misleading, so hardcode 1.
 | 
						||
    echo 1
 | 
						||
  else
 | 
						||
    echo $(( $(grep -c "^processor" /proc/cpuinfo) + 1 ))
 | 
						||
  fi
 | 
						||
}
 | 
						||
 | 
						||
help() {
 | 
						||
  echo 'Usage:'
 | 
						||
  echo '  pvscheck.sh [--pvs URL] [--deps] [--environment-cc]'
 | 
						||
  echo '              [target-directory [branch]]'
 | 
						||
  echo '  pvscheck.sh [--pvs URL] [--recheck] [--environment-cc] [--update]'
 | 
						||
  echo '              [target-directory]'
 | 
						||
  echo '  pvscheck.sh [--pvs URL] --only-analyse [target-directory]'
 | 
						||
  echo '  pvscheck.sh [--pvs URL] --pvs-install {target-directory}'
 | 
						||
  echo '  pvscheck.sh --patch [--only-build]'
 | 
						||
  echo
 | 
						||
  echo '    --pvs: Fetch pvs-studio from URL.'
 | 
						||
  echo
 | 
						||
  echo '    --pvs detect: Auto-detect latest version (by scraping viva64.com).'
 | 
						||
  echo
 | 
						||
  echo '    --deps: (for regular run) Use top-level Makefile and build deps.'
 | 
						||
  echo '            Without this it assumes all dependencies are already'
 | 
						||
  echo '            installed.'
 | 
						||
  echo
 | 
						||
  echo '    --environment-cc: (for regular run and --recheck) Do not export'
 | 
						||
  echo '                      CC=clang. Build is still run with CFLAGS=-O0.'
 | 
						||
  echo
 | 
						||
  echo '    --only-build: (for --patch) Only patch files in ./build directory.'
 | 
						||
  echo
 | 
						||
  echo '    --pvs-install: Only install PVS-studio to the specified location.'
 | 
						||
  echo
 | 
						||
  echo '    --patch: patch sources in the current directory.'
 | 
						||
  echo '             Does not patch already patched files.'
 | 
						||
  echo '             Does not run analysis.'
 | 
						||
  echo
 | 
						||
  echo '    --recheck: run analysis on a prepared target directory.'
 | 
						||
  echo
 | 
						||
  echo '    --update: when rechecking first do a pull.'
 | 
						||
  echo
 | 
						||
  echo '    --only-analyse: run analysis on a prepared target directory '
 | 
						||
  echo '                    without building Neovim.'
 | 
						||
  echo
 | 
						||
  echo '    target-directory: Directory where build should occur.'
 | 
						||
  echo '                      Default: ../neovim-pvs'
 | 
						||
  echo
 | 
						||
  echo '    branch: Branch to check.'
 | 
						||
  echo '            Default: master.'
 | 
						||
}
 | 
						||
 | 
						||
getopts_error() {
 | 
						||
  local msg="$1" ; shift
 | 
						||
  local do_help=
 | 
						||
  if test "$msg" = "--help" ; then
 | 
						||
    msg="$1" ; shift
 | 
						||
    do_help=1
 | 
						||
  fi
 | 
						||
  printf '%s\n' "$msg" >&2
 | 
						||
  if test -n "$do_help" ; then
 | 
						||
    printf '\n' >&2
 | 
						||
    help >&2
 | 
						||
  fi
 | 
						||
  echo 'return 1'
 | 
						||
  return 1
 | 
						||
}
 | 
						||
 | 
						||
# Usage `eval "$(getopts_long long_defs -- positionals_defs -- "$@")"`
 | 
						||
#
 | 
						||
# long_defs: list of pairs of arguments like `longopt action`.
 | 
						||
# positionals_defs: list of arguments like `action`.
 | 
						||
#
 | 
						||
# `action` is a space-separated commands:
 | 
						||
#
 | 
						||
#   store_const [const] [varname] [default]
 | 
						||
#     Store constant [const] (default 1) (note: eval’ed) if argument is present
 | 
						||
#     (long options only). Assumes long option accepts no arguments.
 | 
						||
#   store [varname] [default]
 | 
						||
#     Store value. Assumes long option needs an argument.
 | 
						||
#   run {func} [varname] [default]
 | 
						||
#     Run function {func} and store its output to the [varname]. Assumes no
 | 
						||
#     arguments accepted (long options only).
 | 
						||
#   modify {func} [varname] [default]
 | 
						||
#     Like run, but assumes a single argument, passed to function {func} as $1.
 | 
						||
#
 | 
						||
#   All actions stores empty value if neither [varname] nor [default] are
 | 
						||
#   present. [default] is evaled by top-level `eval`, so be careful. Also note
 | 
						||
#   that no arguments may contain spaces, including [default] and [const].
 | 
						||
getopts_long() {
 | 
						||
  local positional=
 | 
						||
  local opt_bases=""
 | 
						||
  while test $# -gt 0 ; do
 | 
						||
    local arg="$1" ; shift
 | 
						||
    local opt_base=
 | 
						||
    local act=
 | 
						||
    local opt_name=
 | 
						||
    if test -z "$positional" ; then
 | 
						||
      if test "$arg" = "--" ; then
 | 
						||
        positional=0
 | 
						||
        continue
 | 
						||
      fi
 | 
						||
      act="$1" ; shift
 | 
						||
      opt_name="$(echo "$arg" | tr '-' '_')"
 | 
						||
      opt_base="longopt_$opt_name"
 | 
						||
    else
 | 
						||
      if test "$arg" = "--" ; then
 | 
						||
        break
 | 
						||
      fi
 | 
						||
      : $(( positional+=1 ))
 | 
						||
      act="$arg"
 | 
						||
      opt_name="arg_$positional"
 | 
						||
      opt_base="positional_$positional"
 | 
						||
    fi
 | 
						||
    opt_bases="$opt_bases $opt_base"
 | 
						||
    eval "local varname_$opt_base=$opt_name"
 | 
						||
    local i=0
 | 
						||
    for act_subarg in $act ; do
 | 
						||
      eval "local act_$(( i+=1 ))_$opt_base=\"\$act_subarg\""
 | 
						||
    done
 | 
						||
  done
 | 
						||
  # Process options
 | 
						||
  local positional=0
 | 
						||
  local force_positional=
 | 
						||
  while test $# -gt 0 ; do
 | 
						||
    local argument="$1" ; shift
 | 
						||
    local opt_base=
 | 
						||
    local has_equal=
 | 
						||
    local equal_arg=
 | 
						||
    local is_positional=
 | 
						||
    if test "$argument" = "--" ; then
 | 
						||
      force_positional=1
 | 
						||
      continue
 | 
						||
    elif test -z "$force_positional" && test "${argument#--}" != "$argument"
 | 
						||
    then
 | 
						||
      local opt_name="${argument#--}"
 | 
						||
      local opt_name_striparg="${opt_name%%=*}"
 | 
						||
      if test "$opt_name" = "$opt_name_striparg" ; then
 | 
						||
        has_equal=0
 | 
						||
      else
 | 
						||
        has_equal=1
 | 
						||
        equal_arg="${argument#*=}"
 | 
						||
        opt_name="$opt_name_striparg"
 | 
						||
      fi
 | 
						||
      # Use trailing x to prevent stripping newlines
 | 
						||
      opt_name="$(printf '%sx' "$opt_name" | tr '-' '_')"
 | 
						||
      opt_name="${opt_name%x}"
 | 
						||
      if test -n "$(printf '%sx' "$opt_name" | tr -d 'a-z_')" ; then
 | 
						||
        getopts_error "Option contains invalid characters: $opt_name"
 | 
						||
      fi
 | 
						||
      opt_base="longopt_$opt_name"
 | 
						||
    else
 | 
						||
      : $(( positional+=1 ))
 | 
						||
      opt_base="positional_$positional"
 | 
						||
      is_positional=1
 | 
						||
    fi
 | 
						||
    if test -n "$opt_base" ; then
 | 
						||
      eval "local occurred_$opt_base=1"
 | 
						||
 | 
						||
      eval "local act_1=\"\${act_1_$opt_base:-}\""
 | 
						||
      eval "local varname=\"\${varname_$opt_base:-}\""
 | 
						||
      local need_val=
 | 
						||
      local func=
 | 
						||
      case "$act_1" in
 | 
						||
        (store_const)
 | 
						||
          eval "local const=\"\${act_2_${opt_base}:-1}\""
 | 
						||
          eval "local varname=\"\${act_3_${opt_base}:-$varname}\""
 | 
						||
          printf 'local %s=%s\n' "$varname" "$const"
 | 
						||
          ;;
 | 
						||
        (store)
 | 
						||
          eval "varname=\"\${act_2_${opt_base}:-$varname}\""
 | 
						||
          need_val=1
 | 
						||
          ;;
 | 
						||
        (run)
 | 
						||
          eval "func=\"\${act_2_${opt_base}}\""
 | 
						||
          eval "varname=\"\${act_3_${opt_base}:-$varname}\""
 | 
						||
          printf 'local %s="$(%s)"\n' "$varname" "$func"
 | 
						||
          ;;
 | 
						||
        (modify)
 | 
						||
          eval "func=\"\${act_2_${opt_base}}\""
 | 
						||
          eval "varname=\"\${act_3_${opt_base}:-$varname}\""
 | 
						||
          need_val=1
 | 
						||
          ;;
 | 
						||
        ("")
 | 
						||
          getopts_error --help "Wrong argument: $argument"
 | 
						||
          ;;
 | 
						||
      esac
 | 
						||
      if test -n "$need_val" ; then
 | 
						||
        local val=
 | 
						||
        if test -z "$is_positional" ; then
 | 
						||
          if test $has_equal = 1 ; then
 | 
						||
            val="$equal_arg"
 | 
						||
          else
 | 
						||
            if test $# -eq 0 ; then
 | 
						||
              getopts_error "Missing argument for $opt_name"
 | 
						||
            fi
 | 
						||
            val="$1" ; shift
 | 
						||
          fi
 | 
						||
        else
 | 
						||
          val="$argument"
 | 
						||
        fi
 | 
						||
        local escaped_val="'$(printf "%s" "$val" | sed "s/'/'\\\\''/g")'"
 | 
						||
        case "$act_1" in
 | 
						||
          (store)
 | 
						||
            printf 'local %s=%s\n' "$varname" "$escaped_val"
 | 
						||
            ;;
 | 
						||
          (modify)
 | 
						||
            printf 'local %s="$(%s %s)"\n' "$varname" "$func" "$escaped_val"
 | 
						||
            ;;
 | 
						||
        esac
 | 
						||
      fi
 | 
						||
    fi
 | 
						||
  done
 | 
						||
  # Print default values when no values were provided
 | 
						||
  local opt_base=
 | 
						||
  for opt_base in $opt_bases ; do
 | 
						||
    eval "local occurred=\"\${occurred_$opt_base:-}\""
 | 
						||
    if test -n "$occurred" ; then
 | 
						||
      continue
 | 
						||
    fi
 | 
						||
    eval "local act_1=\"\$act_1_$opt_base\""
 | 
						||
    eval "local varname=\"\$varname_$opt_base\""
 | 
						||
    case "$act_1" in
 | 
						||
      (store)
 | 
						||
        eval "local varname=\"\${act_2_${opt_base}:-$varname}\""
 | 
						||
        eval "local default=\"\${act_3_${opt_base}:-}\""
 | 
						||
        printf 'local %s=%s\n' "$varname" "$default"
 | 
						||
        ;;
 | 
						||
      (store_const|run|modify)
 | 
						||
        eval "local varname=\"\${act_3_${opt_base}:-$varname}\""
 | 
						||
        eval "local default=\"\${act_4_${opt_base}:-}\""
 | 
						||
        printf 'local %s=%s\n' "$varname" "$default"
 | 
						||
        ;;
 | 
						||
    esac
 | 
						||
  done
 | 
						||
}
 | 
						||
 | 
						||
get_pvs_comment() {
 | 
						||
  local tgt="$1" ; shift
 | 
						||
 | 
						||
  cat > "$tgt/pvs-comment" << EOF
 | 
						||
// This is an open source non-commercial project. Dear PVS-Studio, please check
 | 
						||
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
 | 
						||
 | 
						||
EOF
 | 
						||
}
 | 
						||
 | 
						||
install_pvs() {(
 | 
						||
  local tgt="$1" ; shift
 | 
						||
  local pvs_url="$1" ; shift
 | 
						||
 | 
						||
  cd "$tgt"
 | 
						||
 | 
						||
  if test -d pvs-studio ; then
 | 
						||
    log_info 'install_pvs: "pvs-studio" directory already exists, skipping install'
 | 
						||
    return 0
 | 
						||
  fi
 | 
						||
 | 
						||
  mkdir pvs-studio
 | 
						||
  cd pvs-studio
 | 
						||
 | 
						||
  curl -L -o pvs-studio.tar.gz "$pvs_url"
 | 
						||
  tar xzf pvs-studio.tar.gz
 | 
						||
  rm pvs-studio.tar.gz
 | 
						||
  local pvsdir="$(find . -maxdepth 1 -mindepth 1)"
 | 
						||
  find "$pvsdir" -maxdepth 1 -mindepth 1 -exec mv '{}' . \;
 | 
						||
  rmdir "$pvsdir"
 | 
						||
)}
 | 
						||
 | 
						||
create_compile_commands() {(
 | 
						||
  local tgt="$1" ; shift
 | 
						||
  local deps="$1" ; shift
 | 
						||
  local environment_cc="$1" ; shift
 | 
						||
 | 
						||
  if test -z "$environment_cc" ; then
 | 
						||
    export CC=clang
 | 
						||
  fi
 | 
						||
  export CFLAGS=' -O0 '
 | 
						||
 | 
						||
  if test -z "$deps" ; then
 | 
						||
    mkdir -p "$tgt/build"
 | 
						||
    (
 | 
						||
      cd "$tgt/build"
 | 
						||
 | 
						||
      cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="$PWD/root"
 | 
						||
      make -j"$(get_jobs_num)"
 | 
						||
    )
 | 
						||
  else
 | 
						||
    (
 | 
						||
      cd "$tgt"
 | 
						||
 | 
						||
      make -j"$(get_jobs_num)" CMAKE_EXTRA_FLAGS=" -DCMAKE_INSTALL_PREFIX=$PWD/root -DCMAKE_BUILD_TYPE=Debug "
 | 
						||
    )
 | 
						||
  fi
 | 
						||
  find "$tgt/build/src/nvim/auto" -name '*.test-include.c' -delete
 | 
						||
)}
 | 
						||
 | 
						||
# Warning: realdir below only cares about directories unlike realpath.
 | 
						||
#
 | 
						||
# realpath is not available in Ubuntu trusty yet.
 | 
						||
realdir() {(
 | 
						||
  local dir="$1"
 | 
						||
  local add=""
 | 
						||
  while ! cd "$dir" 2>/dev/null ; do
 | 
						||
    add="${dir##*/}/$add"
 | 
						||
    local new_dir="${dir%/*}"
 | 
						||
    if test "$new_dir" = "$dir" ; then
 | 
						||
      return 1
 | 
						||
    fi
 | 
						||
    dir="$new_dir"
 | 
						||
  done
 | 
						||
  printf '%s\n' "$PWD/$add"
 | 
						||
)}
 | 
						||
 | 
						||
patch_sources() {(
 | 
						||
  local tgt="$1" ; shift
 | 
						||
  local only_bulid="${1}" ; shift
 | 
						||
 | 
						||
  get_pvs_comment "$tgt"
 | 
						||
 | 
						||
  local sh_script='
 | 
						||
    pvs_comment="$(cat pvs-comment ; echo -n EOS)"
 | 
						||
    filehead="$(head -c $(( ${#pvs_comment} - 3 )) "$1" ; echo -n EOS)"
 | 
						||
    if test "x$filehead" != "x$pvs_comment" ; then
 | 
						||
      cat pvs-comment "$1" > "$1.tmp"
 | 
						||
      mv "$1.tmp" "$1"
 | 
						||
    fi
 | 
						||
  '
 | 
						||
 | 
						||
  cd "$tgt"
 | 
						||
 | 
						||
  if test "$only_build" != "--only-build" ; then
 | 
						||
    find \
 | 
						||
      src/nvim test/functional/fixtures test/unit/fixtures \
 | 
						||
      \( -name '*.c' -a '!' -path '*xdiff*' \) \
 | 
						||
      -exec /bin/sh -c "$sh_script" - '{}' \;
 | 
						||
  fi
 | 
						||
 | 
						||
  find \
 | 
						||
    build/src/nvim/auto build/config \
 | 
						||
    -name '*.c' -not -name '*.test-include.c' \
 | 
						||
    -exec /bin/sh -c "$sh_script" - '{}' \;
 | 
						||
 | 
						||
  rm pvs-comment
 | 
						||
)}
 | 
						||
 | 
						||
run_analysis() {(
 | 
						||
  local tgt="$1" ; shift
 | 
						||
 | 
						||
  cd "$tgt"
 | 
						||
 | 
						||
  if [ ! -r PVS-Studio.lic ]; then
 | 
						||
    pvs-studio-analyzer credentials -o PVS-Studio.lic 'PVS-Studio Free' 'FREE-FREE-FREE-FREE'
 | 
						||
  fi
 | 
						||
 | 
						||
  # pvs-studio-analyzer exits with a non-zero exit code when there are detected
 | 
						||
  # errors, so ignore its return
 | 
						||
  pvs-studio-analyzer \
 | 
						||
    analyze \
 | 
						||
      --lic-file PVS-Studio.lic \
 | 
						||
      --threads "$(get_jobs_num)" \
 | 
						||
      --exclude-path src/cjson \
 | 
						||
      --exclude-path src/xdiff \
 | 
						||
      --output-file PVS-studio.log \
 | 
						||
      --file build/compile_commands.json \
 | 
						||
      --sourcetree-root . || true
 | 
						||
 | 
						||
  rm -rf PVS-studio.{xml,err,tsk,html.d}
 | 
						||
  local plog_args="PVS-studio.log --srcRoot . --excludedCodes V011,V1042,V1051,V1074"
 | 
						||
  plog-converter $plog_args --renderTypes xml       --output PVS-studio.xml
 | 
						||
  plog-converter $plog_args --renderTypes errorfile --output PVS-studio.err
 | 
						||
  plog-converter $plog_args --renderTypes tasklist  --output PVS-studio.tsk
 | 
						||
  plog-converter $plog_args --renderTypes fullhtml  --output PVS-studio.html.d
 | 
						||
)}
 | 
						||
 | 
						||
detect_url() {
 | 
						||
  local url="${1:-detect}"
 | 
						||
  if test "$url" = detect ; then
 | 
						||
    curl --silent -L 'https://pvs-studio.com/en/pvs-studio/download-all/' \
 | 
						||
    | grep -o 'https\{0,1\}://[^"<>]\{1,\}/pvs-studio[^/"<>]*-x86_64\.tgz' \
 | 
						||
    || echo FAILED
 | 
						||
  else
 | 
						||
    printf '%s' "$url"
 | 
						||
  fi
 | 
						||
}
 | 
						||
 | 
						||
do_check() {
 | 
						||
  local tgt="$1" ; shift
 | 
						||
  local branch="$1" ; shift
 | 
						||
  local pvs_url="$1" ; shift
 | 
						||
  local deps="$1" ; shift
 | 
						||
  local environment_cc="$1" ; shift
 | 
						||
 | 
						||
  if test -z "$pvs_url" || test "$pvs_url" = FAILED ; then
 | 
						||
    pvs_url="$(detect_url detect)"
 | 
						||
    if test -z "$pvs_url" || test "$pvs_url" = FAILED ; then
 | 
						||
      echo "failed to auto-detect PVS URL"
 | 
						||
      exit 1
 | 
						||
    fi
 | 
						||
    echo "Auto-detected PVS URL: ${pvs_url}"
 | 
						||
  fi
 | 
						||
 | 
						||
  git clone --branch="$branch" . "$tgt"
 | 
						||
 | 
						||
  install_pvs "$tgt" "$pvs_url"
 | 
						||
 | 
						||
  do_recheck "$tgt" "$deps" "$environment_cc" ""
 | 
						||
}
 | 
						||
 | 
						||
do_recheck() {
 | 
						||
  local tgt="$1" ; shift
 | 
						||
  local deps="$1" ; shift
 | 
						||
  local environment_cc="$1" ; shift
 | 
						||
  local update="$1" ; shift
 | 
						||
 | 
						||
  if test -n "$update" ; then
 | 
						||
    (
 | 
						||
      cd "$tgt"
 | 
						||
      local branch="$(git rev-parse --abbrev-ref HEAD)"
 | 
						||
      git checkout --detach
 | 
						||
      git fetch -f origin "${branch}:${branch}"
 | 
						||
      git checkout -f "$branch"
 | 
						||
    )
 | 
						||
  fi
 | 
						||
 | 
						||
  create_compile_commands "$tgt" "$deps" "$environment_cc"
 | 
						||
 | 
						||
  do_analysis "$tgt"
 | 
						||
}
 | 
						||
 | 
						||
do_analysis() {
 | 
						||
  local tgt="$1" ; shift
 | 
						||
 | 
						||
  if test -d "$tgt/pvs-studio" ; then
 | 
						||
    local saved_pwd="$PWD"
 | 
						||
    cd "$tgt/pvs-studio"
 | 
						||
    export PATH="$PWD/bin${PATH+:}${PATH}"
 | 
						||
    cd "$saved_pwd"
 | 
						||
  fi
 | 
						||
 | 
						||
  run_analysis "$tgt"
 | 
						||
}
 | 
						||
 | 
						||
main() {
 | 
						||
  eval "$(
 | 
						||
    getopts_long \
 | 
						||
      help store_const \
 | 
						||
      pvs 'modify detect_url pvs_url' \
 | 
						||
      patch store_const \
 | 
						||
      only-build 'store_const --only-build' \
 | 
						||
      recheck store_const \
 | 
						||
      only-analyse store_const \
 | 
						||
      pvs-install store_const \
 | 
						||
      deps store_const \
 | 
						||
      environment-cc store_const \
 | 
						||
      update store_const \
 | 
						||
      -- \
 | 
						||
      'modify realdir tgt "$PWD/../neovim-pvs"' \
 | 
						||
      'store branch master' \
 | 
						||
      -- "$@"
 | 
						||
  )"
 | 
						||
 | 
						||
  if test -n "$help" ; then
 | 
						||
    help
 | 
						||
    return 0
 | 
						||
  fi
 | 
						||
 | 
						||
  if test -n "$patch" ; then
 | 
						||
    patch_sources "$tgt" "$only_build"
 | 
						||
  elif test -n "$pvs_install" ; then
 | 
						||
    install_pvs "$tgt" "$pvs_url"
 | 
						||
  elif test -n "$recheck" ; then
 | 
						||
    do_recheck "$tgt" "$deps" "$environment_cc" "$update"
 | 
						||
  elif test -n "$only_analyse" ; then
 | 
						||
    do_analysis "$tgt"
 | 
						||
  else
 | 
						||
    do_check "$tgt" "$branch" "$pvs_url" "$deps" "$environment_cc"
 | 
						||
  fi
 | 
						||
}
 | 
						||
 | 
						||
main "$@"
 |