Files
nuttx/tools/checkpatch.sh
raiden00pl 93911d52a8 tools/checkpatch.sh: check format for all commits in patch
If more than one commit is present in the patch, the commit format
must be checked separately for each commit in patch, otherwise not
all errors are detected.

Signed-off-by: raiden00pl <raiden00@railab.me>
2025-09-01 16:37:16 +02:00

421 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# tools/checkpatch.sh
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
TOOLDIR=$(dirname $0)
case "$OSTYPE" in
*bsd*) MAKECMD=gmake;;
*) MAKECMD=make;;
esac
check=check_patch
fail=0
range=0
spell=0
encoding=0
message=0
# CMake
cmake_warning_once=0
# Python
black_warning_once=0
flake8_warning_once=0
isort_warning_once=0
cvt2utf_warning_once=0
codespell_config_file_location_was_shown_once=0
# links
COMMIT_URL="https://github.com/apache/nuttx/blob/master/CONTRIBUTING.md"
usage() {
echo "USAGE: ${0} [options] [list|-]"
echo ""
echo "Options:"
echo "-h"
echo "-c spell check with codespell (install with: pip install codespell)"
echo "-u encoding check with cvt2utf (install with: pip install cvt2utf)"
echo "-r range check only (coupled with -p or -g)"
echo "-p <patch file names> (default)"
echo "-m Check commit message (coupled with -g)"
echo "-g <commit list>"
echo "-f <file list>"
echo "-x format supported files (only .py, requires: pip install black)"
echo "- read standard input mainly used by git pre-commit hook as below:"
echo " git diff --cached | ./tools/checkpatch.sh -"
echo "Where a <commit list> is any syntax supported by git for specifying git revision, see GITREVISIONS(7)"
echo "Where a <patch file names> is a space separated list of patch file names or wildcard. or *.patch"
exit $@
}
is_rust_file() {
file_ext=${@##*.}
file_ext_r=${file_ext/R/r}
file_ext_rs=${file_ext_r/S/s}
if [ "$file_ext_rs" == "rs" ]; then
echo 1
else
echo 0
fi
}
is_python_file() {
if [[ ${@##*.} == 'py' ]]; then
echo 1
else
echo 0
fi
}
is_cmake_file() {
file_name=$(basename $@)
if [ "$file_name" == "CMakeLists.txt" ] || [[ "$file_name" =~ \.cmake$ ]]; then
echo 1
else
echo 0
fi
}
format_file() {
if [ "$(is_python_file $@)" == "1" ]; then
if command -v black >/dev/null; then
echo "Auto-formatting Python file with black: $@"
setupcfg="${TOOLDIR}/../.github/linters/setup.cfg"
isort --settings-path "${setupcfg}" "$@"
black $@
else
echo "$@: error: black not found. Please install with: pip install black"
fail=1
fi
else
# TODO: extend for other file types in the future
echo "$@: error: format files type not implemented"
fail=1
fi
}
check_file() {
if [ -x $@ ]; then
case $@ in
*.bat | *.sh | *.py)
;;
*)
echo "$@: error: execute permissions detected!"
fail=1
;;
esac
fi
if [ "$(is_python_file $@)" == "1" ]; then
setupcfg="${TOOLDIR}/../.github/linters/setup.cfg"
if ! command -v black &> /dev/null; then
if [ $black_warning_once == 0 ]; then
echo -e "\nblack not found, run following command to install:"
echo " $ pip install black"
black_warning_once=1
fi
fail=1
elif ! black --check $@ 2>&1; then
if [ $black_warning_once == 0 ]; then
echo -e "\nblack check failed, run following command to update the style:"
echo -e " $ black <src>\n"
black_warning_once=1
fi
fail=1
fi
if ! command -v flake8 &> /dev/null; then
if [ $flake8_warning_once == 0 ]; then
echo -e "\nflake8 not found, run following command to install:"
echo " $ pip install flake8"
flake8_warning_once=1
fi
fail=1
elif ! flake8 --config "${setupcfg}" "$@" 2>&1; then
if [ $flake8_warning_once == 0 ]; then
echo -e "\nflake8 check failed !!!"
flake8_warning_once=1
fi
fail=1
fi
if ! command -v isort &> /dev/null; then
if [ $isort_warning_once == 0 ]; then
echo -e "\nisort not found, run following command to install:"
echo " $ pip install isort"
isort_warning_once=1
fi
fail=1
elif ! isort --diff --check-only --settings-path "${setupcfg}" "$@" 2>&1; then
if [ $isort_warning_once == 0 ]; then
isort --settings-path "${setupcfg}" "$@"
isort_warning_once=1
fi
fail=1
fi
elif [ "$(is_rust_file $@)" == "1" ]; then
if ! command -v rustfmt &> /dev/null; then
echo -e "\nrustfmt not found, run following command to install:"
echo " $ rustup component add rustfmt"
fail=1
elif ! rustfmt --edition 2021 --check $@ 2>&1; then
fail=1
fi
elif [ "$(is_cmake_file $@)" == "1" ]; then
if ! command -v cmake-format &> /dev/null; then
if [ $cmake_warning_once == 0 ]; then
echo -e "\ncmake-format not found, run following command to install:"
echo " $ pip install cmake-format"
cmake_warning_once=1
fi
fail=1
elif ! cmake-format --check $@ 2>&1; then
if [ $cmake_warning_once == 0 ]; then
echo -e "\ncmake-format check failed, run following command to update the style:"
echo -e " $ cmake-format <src> -o <dst>\n"
cmake-format --check $@ 2>&1
cmake_warning_once=1
fi
fail=1
fi
elif ! $TOOLDIR/nxstyle $@ 2>&1; then
fail=1
fi
if [ $spell != 0 ]; then
if ! command -v codespell &> /dev/null; then
if [ $codespell_config_file_location_was_shown_once == 0 ]; then
echo -e "\ncodespell not found, run following command to install:"
echo " $ pip install codespell"
codespell_config_file_location_was_shown_once=1
fi
fail=1
else
if [ $codespell_config_file_location_was_shown_once != 1 ]; then
# show the configuration file location just once during (not for each input file)
codespell_args="-q 7"
codespell_config_file_location_was_shown_once=1
else
codespell_args=""
fi
if ! codespell $codespell_args ${@: -1}; then
fail=1
fi
fi
fi
if [ $encoding != 0 ]; then
if ! command -v cvt2utf &> /dev/null; then
if [ $cvt2utf_warning_once == 0 ]; then
echo -e "\ncvt2utf not found, run following command to install:"
echo " $ pip install cvt2utf"
cvt2utf_warning_once=1
fi
fail=1
else
md5="$(md5sum $@)"
cvt2utf convert --nobak "$@" &> /dev/null
if [ "$md5" != "$(md5sum $@)" ]; then
if [ $cvt2utf_warning_once == 0 ]; then
echo "$@: error: Non-UTF8 characters detected!"
cvt2utf_warning_once=1
fi
fail=1
fi
fi
fi
}
check_ranges() {
while read; do
if [[ $REPLY =~ ^(\+\+\+\ (b/)?([^[:blank:]]+).*)$ ]]; then
if [ "$ranges" != "" ]; then
if [ $range != 0 ]; then
check_file $ranges $path
else
check_file $path
fi
fi
path=$(realpath "${BASH_REMATCH[3]}")
ranges=""
elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+,[0-9]+)?\ @@.* ]]; then
ranges+="-r ${BASH_REMATCH[2]} "
fi
done
if [ "$ranges" != "" ]; then
if [ $range != 0 ]; then
check_file $ranges $path
else
check_file $path
fi
fi
}
check_patch() {
if ! git apply --check $1; then
fail=1
else
git apply $1
diffs=`cat $1`
check_ranges <<< "$diffs"
git apply -R $1
fi
}
check_msg() {
signedoffby_found=0
num_lines=0
max_line_len=80
min_num_lines=5
first=$(head -n1 <<< "$msg")
# check for Merge line and remove from parsed string
if [[ $first == *Merge* ]]; then
msg="$(echo "$msg" | tail -n +2)"
first=$(head -n2 <<< "$msg")
fi
while IFS= read -r REPLY; do
if [[ $REPLY =~ ^Change-Id ]]; then
echo "Remove Gerrit Change-ID's before submitting upstream"
fail=1
fi
if [[ $REPLY =~ ^VELAPLATO ]]; then
echo "Remove VELAPLATO before submitting upstream"
fail=1
fi
if [[ $REPLY =~ ^[Ww][Ii][Pp]: ]]; then
echo "Remove WIP before submitting upstream"
fail=1
fi
if [[ $REPLY =~ ^Signed-off-by ]]; then
signedoffby_found=1
fi
((num_lines++))
done <<< "$msg"
if ! [[ $first =~ : ]]; then
echo "Commit subject missing colon (e.g. 'subsystem: msg')"
fail=1
fi
if (( ${#first} > $max_line_len )); then
echo "Commit subject too long > $max_line_len"
fail=1
fi
if ! [ $signedoffby_found == 1 ]; then
echo "Missing Signed-off-by"
fail=1
fi
if (( $num_lines < $min_num_lines && $signedoffby_found == 1 )); then
echo "Missing git commit message"
fail=1
fi
}
check_commit() {
if [ $message != 0 ]; then
# check each commit format separately if this is a series of commits
if [[ $1 =~ HEAD ]]; then
for commit in $(git rev-list --no-merges $1); do
msg=`git show -s --format=%B $commit`
check_msg <<< "$msg"
done
else
msg=`git show -s --format=%B $1`
check_msg <<< "$msg"
fi
fi
diffs=`git diff $1`
check_ranges <<< "$diffs"
}
$MAKECMD -C $TOOLDIR -f Makefile.host nxstyle 1>/dev/null
if [ -z "$1" ]; then
usage
exit 0
fi
while [ ! -z "$1" ]; do
case "$1" in
- )
check_ranges
;;
-c )
spell=1
;;
-u )
encoding=1
;;
-x )
check=format_file
;;
-f )
check=check_file
;;
-m )
message=1
;;
-g )
check=check_commit
;;
-h )
usage 0
;;
-p )
check=check_patch
;;
-r )
range=1
;;
-* )
usage 1
;;
* )
break
;;
esac
shift
done
for arg in $@; do
$check $arg
done
if [ $fail == 1 ]; then
echo "Some checks failed. For contributing guidelines, see:"
echo " $COMMIT_URL"
else
echo "All checks pass."
fi
exit $fail