check_format: Introduce shfmt tooling for .sh style enforcement

Add new dev tool for enforcing proper formatting of the Bash code
across the entire repo. This is done in order of defining a common
set of good practices to follow when writing .sh|Bash code.

As powerful as shfmt may be, it allows only for some specific rules
to be enforced, hence it still needs to work side by side with
shellcheck syntax-wise. If it comes to style, following rules are
being enforced:

  * indent_style = tab - Lines must be indented with tabs. The exception
			 from this rule is the use of heredocs with
			 <<BASH redirect operator. Spaces can be used to
			 format the line only if it's already preceded
			 with a tab.

  * binary_next_line = true - Lines can start with logical operators. E.g:

			      if [[ -v foo ]] \
			      	&& [[ -v bar ]]; then
				 ...
			      fi
  * switch_case_indent = true - case|esac patterns are indented with tabs.

  * space_redirects = true - redirect operators are followed with a space.
			     E.g: > foo over >foo.

In addition, shfmt will enforce its own Bash-style for different parts
of the code as well. Examples and more details can be found here:

  https://github.com/mvdan/sh

Change-Id: I6e5c8d79e6dba9c6471010f3d0f563dd34e62fd6
Signed-off-by: Michal Berger <michalx.berger@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/1418
Reviewed-by: Karol Latecki <karol.latecki@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Darek Stojaczyk <dariusz.stojaczyk@intel.com>
Community-CI: Mellanox Build Bot
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
This commit is contained in:
Michal Berger 2020-03-23 15:14:18 +01:00 committed by Jim Harris
parent 844c8ec383
commit d596ba87b2
2 changed files with 130 additions and 0 deletions

View File

@ -227,6 +227,84 @@ else
echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!" echo "You do not have pycodestyle or pep8 installed so your Python style is not being checked!"
fi fi
shfmt="shfmt-3.1.0"
if hash "$shfmt" 2> /dev/null; then
shfmt_cmdline=() silly_plural=()
silly_plural[1]="s"
commits=() sh_files=() sh_files_repo=() sh_files_staged=()
mapfile -t sh_files_repo < <(git ls-files '*.sh')
# Fetch .sh files only from the commits that are targeted for merge
while read -r _ commit; do
commits+=("$commit")
done < <(git cherry -v origin/master)
mapfile -t sh_files < <(git diff --name-only HEAD origin/master "${sh_files_repo[@]}")
# In case of a call from a pre-commit git hook
mapfile -t sh_files_staged < <(
IFS="|"
git diff --cached --name-only "${sh_files_repo[@]}" | grep -v "${sh_files[*]}"
)
if ((${#sh_files[@]})); then
printf 'Checking .sh formatting style...\n\n'
printf ' Found %u updated|new .sh file%s from the following commits:\n' \
"${#sh_files[@]}" "${silly_plural[${#sh_files[@]} > 1 ? 1 : 0]}"
printf ' * %s\n' "${commits[@]}"
if ((${#sh_files_staged[@]})); then
printf ' Found %u .sh file%s in staging area:\n' \
"${#sh_files_staged[@]}" "${silly_plural[${#sh_files_staged[@]} > 1 ? 1 : 0]}"
printf ' * %s\n' "${sh_files_staged[@]}"
sh_files+=("${sh_files_staged[@]}")
fi
printf ' Running %s against the following file%s:\n' "$shfmt" "${silly_plural[${#sh_files[@]} > 1 ? 1 : 0]}"
printf ' * %s\n' "${sh_files[@]}"
shfmt_cmdline+=(-i 0) # indent_style = tab|indent_size = 0
shfmt_cmdline+=(-bn) # binary_next_line = true
shfmt_cmdline+=(-ci) # switch_case_indent = true
shfmt_cmdline+=(-ln bash) # shell_variant = bash (default)
shfmt_cmdline+=(-d) # diffOut - print diff of the changes and exit with != 0
shfmt_cmdline+=(-sr) # redirect operators will be followed by a space
diff=$PWD/$shfmt.patch
# Explicitly tell shfmt to not look for .editorconfig. .editorconfig is also not looked up
# in case any formatting arguments has been passed on its cmdline.
if ! SHFMT_NO_EDITORCONFIG=true "$shfmt" "${shfmt_cmdline[@]}" "${sh_files[@]}" > "$diff"; then
# In case shfmt detects an actual syntax error it will write out a proper message on
# its stderr, hence the diff file should remain empty.
if [[ -s $diff ]]; then
diff_out=$(< "$diff")
fi
cat <<- ERROR_SHFMT
* Errors in style formatting have been detected.
${diff_out:+* Please, review the generated patch at $diff
# _START_OF_THE_DIFF
${diff_out:-ERROR}
# _END_OF_THE_DIFF
}
ERROR_SHFMT
rc=1
else
rm -f "$diff"
printf 'OK\n'
fi
fi
else
printf '%s not detected, Bash style formatting check is skipped\n' "$shfmt"
fi
if hash shellcheck 2> /dev/null; then if hash shellcheck 2> /dev/null; then
echo -n "Checking Bash style..." echo -n "Checking Bash style..."

View File

@ -28,6 +28,53 @@ function install_all_dependencies() {
INSTALL_DOCS=true INSTALL_DOCS=true
} }
function install_shfmt() {
# Fetch version that has been tested
local shfmt_version=3.1.0
local shfmt=shfmt-$shfmt_version
local shfmt_dir=${SHFMT_DIR:-/opt/shfmt}
local shfmt_dir_out=${SHFMT_DIR_OUT:-/usr/bin}
local shfmt_url
local os
if hash "$shfmt" && [[ $("$shfmt" --version) == "v$shfmt_version" ]]; then
echo "$shfmt already installed"
return 0
fi 2> /dev/null
os=$(uname -s)
case "$os" in
Linux) shfmt_url=https://github.com/mvdan/sh/releases/download/v$shfmt_version/shfmt_v${shfmt_version}_linux_amd64 ;;
FreeBSD) shfmt_url=https://github.com/mvdan/sh/releases/download/v$shfmt_version/shfmt_v${shfmt_version}_freebsd_amd64 ;;
*)
echo "Not supported OS (${os:-Unknown}), skipping"
return 0
;;
esac
mkdir -p "$shfmt_dir"
mkdir -p "$shfmt_dir_out"
echo "Fetching ${shfmt_url##*/}"...
local err
if err=$(curl -f -Lo"$shfmt_dir/$shfmt" "$shfmt_url" 2>&1); then
chmod +x "$shfmt_dir/$shfmt"
ln -sf "$shfmt_dir/$shfmt" "$shfmt_dir_out"
else
cat <<- CURL_ERR
* Fetching $shfmt_url failed, $shfmt will not be available for format check.
* Error:
$err
CURL_ERR
return 0
fi
echo "$shfmt installed"
}
INSTALL_CRYPTO=false INSTALL_CRYPTO=false
INSTALL_DEV_TOOLS=false INSTALL_DEV_TOOLS=false
INSTALL_PMEM=false INSTALL_PMEM=false
@ -119,6 +166,7 @@ if [ -s /etc/redhat-release ]; then
yum install -y git astyle sg3_utils pciutils yum install -y git astyle sg3_utils pciutils
# Additional (optional) dependencies for showing backtrace in logs # Additional (optional) dependencies for showing backtrace in logs
yum install -y libunwind-devel || true yum install -y libunwind-devel || true
install_shfmt
fi fi
if [[ $INSTALL_PMEM == "true" ]]; then if [[ $INSTALL_PMEM == "true" ]]; then
# Additional dependencies for building pmem based backends # Additional dependencies for building pmem based backends
@ -167,6 +215,7 @@ elif [ -f /etc/debian_version ]; then
apt-get install -y libunwind-dev || true apt-get install -y libunwind-dev || true
# Additional dependecies for nvmf performance test script # Additional dependecies for nvmf performance test script
apt-get install -y python3-paramiko apt-get install -y python3-paramiko
install_shfmt
fi fi
if [[ $INSTALL_PMEM == "true" ]]; then if [[ $INSTALL_PMEM == "true" ]]; then
# Additional dependencies for building pmem based backends # Additional dependencies for building pmem based backends
@ -205,6 +254,7 @@ elif [ -f /etc/SuSE-release ] || [ -f /etc/SUSE-brand ]; then
pciutils ShellCheck pciutils ShellCheck
# Additional (optional) dependencies for showing backtrace in logs # Additional (optional) dependencies for showing backtrace in logs
zypper install libunwind-devel || true zypper install libunwind-devel || true
install_shfmt
fi fi
if [[ $INSTALL_PMEM == "true" ]]; then if [[ $INSTALL_PMEM == "true" ]]; then
# Additional dependencies for building pmem based backends # Additional dependencies for building pmem based backends
@ -232,6 +282,7 @@ elif [ $(uname -s) = "FreeBSD" ]; then
# Tools for developers # Tools for developers
pkg install -y devel/astyle bash py27-pycodestyle \ pkg install -y devel/astyle bash py27-pycodestyle \
misc/e2fsprogs-libuuid sysutils/sg3_utils nasm misc/e2fsprogs-libuuid sysutils/sg3_utils nasm
install_shfmt
fi fi
if [[ $INSTALL_DOCS == "true" ]]; then if [[ $INSTALL_DOCS == "true" ]]; then
# Additional dependencies for building docs # Additional dependencies for building docs
@ -272,6 +323,7 @@ elif [ -f /etc/arch-release ]; then
makepkg -si --needed --noconfirm; makepkg -si --needed --noconfirm;
cd .. && rm -rf lcov-git; cd .. && rm -rf lcov-git;
popd" popd"
install_shfmt
fi fi
if [[ $INSTALL_PMEM == "true" ]]; then if [[ $INSTALL_PMEM == "true" ]]; then
# Additional dependencies for building pmem based backends # Additional dependencies for building pmem based backends