contrib: improve nm-code-format.sh script

- accept directory names in the command line. In that case,
  still honor the excluded files. That is a major improvement
  for me, because I usually only want to reformat a directory
  that I know has changed and it is fast to only process some
  directories.

- pass all files at once to clang-format. For me that gives
  a significant speed improvement (about 3 times faster), although
  clang-format is only single threaded. Possibly clang-format could
  even be faster by checking files in parallel.
  In case of a style error, the script still falls back to
  iterate over all files to find the first bad file and print
  the full diff. But that is considered an unusual case.

- make it correctly work from calling it from a subdirectory.
  In that case, we only check files inside that directory --
  but still correctly honor the excluded files.
This commit is contained in:
Thomas Haller
2021-01-13 11:22:53 +01:00
parent 46da6eae6e
commit 9bef4ece92

View File

@@ -2,74 +2,140 @@
set -e set -e
while (( $# )); do die() {
case $1 in printf '%s\n' "$*" >&2
-i) exit 1
FLAGS="-i" }
shift
;; EXCLUDE=(
-h) ":(exclude)shared/c-list"
":(exclude)shared/c-list"
":(exclude)shared/c-list"
":(exclude)shared/c-rbtree"
":(exclude)shared/c-siphash"
":(exclude)shared/c-stdaux"
":(exclude)shared/n-acd"
":(exclude)shared/n-dhcp4"
":(exclude)shared/nm-std-aux/unaligned.h"
":(exclude)shared/systemd/src"
":(exclude)src/systemd/src"
)
NM_ROOT="$(git rev-parse --show-toplevel)" || die "not inside a git repository"
NM_PREFIX="$(git rev-parse --show-prefix)" || die "not inside a git repository"
if [ ! -f "$NM_ROOT/.clang-format" ]; then
die "Error: the clang-format file in \"$NM_ROOT\" does not exist"
fi
if ! command -v clang-format &> /dev/null; then
die "Error: clang-format is not installed. On RHEL/Fedora/CentOS run 'dnf install clang-tools-extra'"
fi
if test -n "$NM_PREFIX"; then
_EXCLUDE=()
for e in "${EXCLUDE[@]}"; do
REGEX='^:\(exclude\)'"$NM_PREFIX"'([^/].*)$'
if [[ "$e" =~ $REGEX ]]; then
_EXCLUDE+=(":(exclude)${BASH_REMATCH[1]}")
fi
done
EXCLUDE=("${_EXCLUDE[@]}")
fi
FILES=()
HAS_EXPLICIT_FILES=0
SHOW_FILENAMES=0
TEST_ONLY=1
usage() {
printf "Usage: %s [OPTION]... [FILE]...\n" $(basename $0) printf "Usage: %s [OPTION]... [FILE]...\n" $(basename $0)
printf "Reformat source files using NetworkManager's code-style.\n\n" printf "Reformat source files using NetworkManager's code-style.\n\n"
printf "If no file is given the script runs on the whole codebase.\n" printf "If no file is given the script runs on the whole codebase.\n"
printf "If no flag is given no file is touch but errors are reported.\n\n" printf "If no flag is given no file is touch but errors are reported.\n\n"
printf "OPTIONS:\n" printf "OPTIONS:\n"
printf " -i Reformat files\n" printf " -i Reformat files\n"
printf " -n Only check the files (this is the default)\n"
printf " -h Print this help message\n" printf " -h Print this help message\n"
printf " --show-filenames Only print the filenames that would be checked\n"
printf " -- Separate options from filenames/directories\n"
}
HAD_DASHDASH=0
while (( $# )); do
if [ "$HAD_DASHDASH" = 0 ]; then
case "$1" in
-h)
usage
exit 0 exit 0
;; ;;
*) --show-filenames)
FILES+=($1) SHOW_FILENAMES=1
shift shift
continue
;;
-n)
TEST_ONLY=1
shift
continue
;;
-i)
TEST_ONLY=0
shift
continue
;;
--)
HAD_DASHDASH=1
shift
continue
;; ;;
esac esac
done
NM_ROOT=$(git rev-parse --show-toplevel)
EXCLUDE="
:(exclude)shared/c-list
:(exclude)shared/c-list
:(exclude)shared/c-list
:(exclude)shared/c-rbtree
:(exclude)shared/c-siphash
:(exclude)shared/c-stdaux
:(exclude)shared/n-acd
:(exclude)shared/n-dhcp4
:(exclude)shared/nm-std-aux/unaligned.h
:(exclude)shared/systemd/src
:(exclude)src/systemd/src
"
if ! command -v clang-format &> /dev/null; then
echo -n "Error: clang-format is not installed, "
echo "on RHEL/Fedora/CentOS run 'dnf install clang-tools-extra'"
exit 1
fi
if [ ! -f ${NM_ROOT}/.clang-format ]; then
echo "Error: the clang-format file does not exist"
exit 1
fi
FLAGS=${FLAGS:-"--Werror -n --ferror-limit=1"}
if [ -z "${FILES[@]}" ]; then
cd $NM_ROOT
FILES=($(git ls-files --full-name -- '*.[ch]' ${EXCLUDE}))
fi
for f in "${FILES[@]}"; do
if [ -f $f ]; then
if ! clang-format $FLAGS $f &> /dev/null; then
TMP_FILE=$(mktemp)
clang-format $f > $TMP_FILE
git --no-pager diff $f $TMP_FILE || true
rm $TMP_FILE
echo "Error: $(basename $f) code-style is wrong, fix it by running '$0 -i $f)"
exit 1
fi fi
if [ -d "$1" ]; then
FILES+=( $(git ls-files -- "${1}/*.[hc]" "${EXCLUDE[@]}" ) )
elif [ -f "$1" ]; then
FILES+=("$1")
else else
echo "Error: $f No such file" usage >&2
echo >&2
die "Unknown argument \"$1\" which also is neither a file nor a directory."
fi fi
shift
HAS_EXPLICIT_FILES=1
done done
if [ $HAS_EXPLICIT_FILES = 0 ]; then
FILES=( $(git ls-files -- '*.[ch]' "${EXCLUDE[@]}") )
fi
if [ $SHOW_FILENAMES = 1 ]; then
printf '%s\n' "${FILES[@]}"
exit 0
fi
FLAGS_TEST=( --Werror -n --ferror-limit=1 )
if [ $TEST_ONLY = 1 ]; then
# We assume that all formatting is correct. In that mode, passing
# all filenames to clang-format is significantly faster.
#
# Only in case of an error, we iterate over the files one by one
# until we find the first invalid file.
for f in "${FILES[@]}"; do
[ -f $f ] || die "Error: file \"$f\" does not exist (or is not a regular file)"
done
clang-format "${FLAGS_TEST[@]}" "${FILES[@]}" &>/dev/null && exit 0
for f in "${FILES[@]}"; do
[ -f $f ] || die "Error: file \"$f\" does not exist (or is not a regular file)"
if ! clang-format "${FLAGS_TEST[@]}" "$f" &>/dev/null; then
FF="$(mktemp)"
trap 'rm -f "$FF"' EXIT
clang-format "$f" 2>/dev/null > "$FF"
git --no-pager diff "$f" "$FF" || :
die "Error: file \"$f\" has code-style is wrong. Fix it by running "'`'"\"$0\" -i \"$f\""'`'
fi
done
die "an unknown error happened."
fi
clang-format -i "${FILES[@]}"