Add the reusable quality-check workflows for C++ and C#
Task: MIL-007#4 Task: MIL-007#5
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
# Steps of the reusable workflow .gitea/workflows/quality-checks-cpp.yml
|
||||
# (ADR-0008). The workflow checks out the calling repository, fetches this file
|
||||
# into shared-scripts/ and runs one command per step:
|
||||
#
|
||||
# quality-checks-cpp.sh validate check the inputs, before anything is installed
|
||||
# quality-checks-cpp.sh install install the exact clang tool versions into a venv
|
||||
# quality-checks-cpp.sh run run clang-format and clang-tidy on the tracked files
|
||||
#
|
||||
# Settings come from the environment, never from interpolated expressions:
|
||||
# WORKING_DIRECTORY, STD, COMPILE_COMMANDS_DIR, CLANG_FORMAT_VERSION,
|
||||
# CLANG_TIDY_VERSION, QC_VENV (and QC_BIN for tests).
|
||||
# Every failure is printed as ::error::[category] message.
|
||||
|
||||
C_STANDARD="${C_STANDARD:-c17}"
|
||||
|
||||
validate_inputs() {
|
||||
set -u
|
||||
case "$WORKING_DIRECTORY" in
|
||||
"" | /* | .. | ../* | */.. | */../*)
|
||||
echo "::error::[configuration] working-directory must be a relative path inside the repository."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if ! [[ "$STD" =~ ^(c\+\+|gnu\+\+)[0-9]{2}$ ]]; then
|
||||
echo "::error::[configuration] std '$STD' is not a C++ standard such as c++20 or gnu++20."
|
||||
exit 1
|
||||
fi
|
||||
case "$COMPILE_COMMANDS_DIR" in
|
||||
/* | .. | ../* | */.. | */../*)
|
||||
echo "::error::[configuration] compile-commands-dir must be a relative path inside the repository."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
local name version
|
||||
for name in CLANG_FORMAT_VERSION CLANG_TIDY_VERSION; do
|
||||
version="${!name}"
|
||||
if ! [[ "$version" =~ ^[0-9]+(\.[0-9]+){1,3}$ ]]; then
|
||||
echo "::error::[configuration] ${name,,} '$version' is not an exact version such as 23.1.3."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "Checking C and C++ in ${WORKING_DIRECTORY} with std ${STD}"
|
||||
}
|
||||
|
||||
install_tools() {
|
||||
set -eu
|
||||
python3 -m venv "$QC_VENV"
|
||||
"$QC_VENV/bin/pip" install --quiet \
|
||||
"clang-format==${CLANG_FORMAT_VERSION}" "clang-tidy==${CLANG_TIDY_VERSION}"
|
||||
}
|
||||
|
||||
# A .clang-format must exist in the working directory or above it, up to the
|
||||
# top of the repository: clang-format finds its style the same way.
|
||||
find_clang_format_config() {
|
||||
local top dir parent
|
||||
top="$(cd "$(git rev-parse --show-toplevel)" && pwd -P)"
|
||||
dir="$(pwd -P)"
|
||||
while true; do
|
||||
if [ -f "$dir/.clang-format" ] || [ -f "$dir/_clang-format" ]; then
|
||||
return 0
|
||||
fi
|
||||
parent="$(dirname "$dir")"
|
||||
# stop at the top of the repository, or at the file system root
|
||||
if [ "$dir" = "$top" ] || [ "$parent" = "$dir" ]; then
|
||||
return 1
|
||||
fi
|
||||
dir="$parent"
|
||||
done
|
||||
}
|
||||
|
||||
run_checks() {
|
||||
set -u
|
||||
QC_BIN="${QC_BIN:-$QC_VENV/bin}"
|
||||
if [ ! -d "$WORKING_DIRECTORY" ]; then
|
||||
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
cd "$WORKING_DIRECTORY"
|
||||
|
||||
local all_files cpp_files c_files
|
||||
mapfile -t all_files < <(git ls-files -- '*.c' '*.cc' '*.cpp' '*.cxx' '*.h' '*.hh' '*.hpp' '*.hxx')
|
||||
if [ "${#all_files[@]}" -eq 0 ]; then
|
||||
echo "::notice::No C or C++ files in '$WORKING_DIRECTORY'; nothing was checked."
|
||||
echo "All selected checks passed."
|
||||
exit 0
|
||||
fi
|
||||
if ! find_clang_format_config; then
|
||||
echo "::error::[configuration] No .clang-format found in '$WORKING_DIRECTORY' or above it; add the style of the project."
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$COMPILE_COMMANDS_DIR" ] && [ ! -f "$COMPILE_COMMANDS_DIR/compile_commands.json" ]; then
|
||||
echo "::error::[configuration] compile-commands-dir '$COMPILE_COMMANDS_DIR' has no compile_commands.json; build it before the call."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# .c files are C; everything else (including headers) is checked as C++.
|
||||
mapfile -t c_files < <(git ls-files -- '*.c')
|
||||
mapfile -t cpp_files < <(git ls-files -- '*.cc' '*.cpp' '*.cxx' '*.h' '*.hh' '*.hpp' '*.hxx')
|
||||
|
||||
local failed=()
|
||||
run_tool() {
|
||||
local name="$1"
|
||||
shift
|
||||
echo "::group::$name"
|
||||
if "$@"; then
|
||||
echo "::endgroup::"
|
||||
echo "PASS $name"
|
||||
else
|
||||
echo "::endgroup::"
|
||||
echo "FAIL $name"
|
||||
failed+=("$name")
|
||||
fi
|
||||
}
|
||||
|
||||
run_tool "clang-format" "$QC_BIN/clang-format" --dry-run -Werror "${all_files[@]}"
|
||||
|
||||
if [ -n "$COMPILE_COMMANDS_DIR" ]; then
|
||||
run_tool "clang-tidy" "$QC_BIN/clang-tidy" --quiet -p "$COMPILE_COMMANDS_DIR" "${all_files[@]}"
|
||||
else
|
||||
if [ "${#cpp_files[@]}" -gt 0 ]; then
|
||||
run_tool "clang-tidy (C++)" "$QC_BIN/clang-tidy" --quiet "${cpp_files[@]}" -- "-std=${STD}"
|
||||
fi
|
||||
if [ "${#c_files[@]}" -gt 0 ]; then
|
||||
run_tool "clang-tidy (C)" "$QC_BIN/clang-tidy" --quiet "${c_files[@]}" -- "-std=${C_STANDARD}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${#failed[@]}" -gt 0 ]; then
|
||||
echo "::error::Quality checks failed: ${failed[*]}"
|
||||
exit 1
|
||||
fi
|
||||
echo "All selected checks passed."
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
validate) validate_inputs ;;
|
||||
install) install_tools ;;
|
||||
run) run_checks ;;
|
||||
*)
|
||||
echo "::error::[configuration] usage: quality-checks-cpp.sh validate|install|run" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
# Steps of the reusable workflow .gitea/workflows/quality-checks-csharp.yml
|
||||
# (ADR-0008). The workflow checks out the calling repository, fetches this file
|
||||
# into shared-scripts/ and runs one command per step:
|
||||
#
|
||||
# quality-checks-csharp.sh validate check the inputs, before anything is installed
|
||||
# quality-checks-csharp.sh install install the exact .NET SDK version
|
||||
# quality-checks-csharp.sh run run dotnet format and dotnet build on the project
|
||||
#
|
||||
# Settings come from the environment, never from interpolated expressions:
|
||||
# WORKING_DIRECTORY, PROJECT, DOTNET_SDK_VERSION, DOTNET_ROOT (and
|
||||
# QC_DOTNET_INSTALL_SCRIPT, QC_DOTNET for tests).
|
||||
# Every failure is printed as ::error::[category] message.
|
||||
|
||||
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
|
||||
|
||||
validate_inputs() {
|
||||
set -u
|
||||
case "$WORKING_DIRECTORY" in
|
||||
"" | /* | .. | ../* | */.. | */../*)
|
||||
echo "::error::[configuration] working-directory must be a relative path inside the repository."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "$PROJECT" in
|
||||
/* | .. | ../* | */.. | */../*)
|
||||
echo "::error::[configuration] project must be a relative path inside the working directory."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# An exact stable version of the .NET SDK, 10 or later (no channel, no range).
|
||||
if ! [[ "$DOTNET_SDK_VERSION" =~ ^([1-9][0-9]+)\.[0-9]+\.[0-9]+$ ]] || [ "${BASH_REMATCH[1]}" -lt 10 ]; then
|
||||
echo "::error::[configuration] dotnet-sdk-version '$DOTNET_SDK_VERSION' is not an exact stable version 10 or later, such as 10.0.401."
|
||||
exit 1
|
||||
fi
|
||||
echo "Checking C# in ${WORKING_DIRECTORY} with .NET SDK ${DOTNET_SDK_VERSION}"
|
||||
}
|
||||
|
||||
install_sdk() {
|
||||
set -eu
|
||||
local script="${QC_DOTNET_INSTALL_SCRIPT:-}"
|
||||
if [ -z "$script" ]; then
|
||||
script="$(mktemp)"
|
||||
curl -fsSL "$DOTNET_INSTALL_URL" -o "$script"
|
||||
fi
|
||||
bash "$script" --version "$DOTNET_SDK_VERSION" --install-dir "$DOTNET_ROOT" --no-path
|
||||
"$DOTNET_ROOT/dotnet" --version
|
||||
}
|
||||
|
||||
run_checks() {
|
||||
set -u
|
||||
if [ ! -d "$WORKING_DIRECTORY" ]; then
|
||||
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
cd "$WORKING_DIRECTORY"
|
||||
local dotnet="${QC_DOTNET:-$DOTNET_ROOT/dotnet}"
|
||||
|
||||
local sources
|
||||
mapfile -t sources < <(git ls-files -- '*.cs')
|
||||
if [ "${#sources[@]}" -eq 0 ]; then
|
||||
echo "::notice::No C# files in '$WORKING_DIRECTORY'; nothing was checked."
|
||||
echo "All selected checks passed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local target="$PROJECT" candidates
|
||||
if [ -z "$target" ]; then
|
||||
mapfile -t candidates < <(git ls-files -- '*.sln' '*.slnx')
|
||||
if [ "${#candidates[@]}" -eq 0 ]; then
|
||||
mapfile -t candidates < <(git ls-files -- '*.csproj')
|
||||
fi
|
||||
case "${#candidates[@]}" in
|
||||
0)
|
||||
echo "::error::[configuration] C# files exist but no solution or project was found in '$WORKING_DIRECTORY'; add one or set the input project."
|
||||
exit 1
|
||||
;;
|
||||
1) target="${candidates[0]}" ;;
|
||||
*)
|
||||
echo "::error::[configuration] Several solutions or projects were found (${candidates[*]}); set the input project."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
elif [ ! -f "$target" ]; then
|
||||
echo "::error::[configuration] project '$target' does not exist in '$WORKING_DIRECTORY'."
|
||||
exit 1
|
||||
fi
|
||||
echo "Checking $target"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
|
||||
local failed=()
|
||||
run_tool() {
|
||||
local name="$1"
|
||||
shift
|
||||
echo "::group::$name"
|
||||
if "$@"; then
|
||||
echo "::endgroup::"
|
||||
echo "PASS $name"
|
||||
else
|
||||
echo "::endgroup::"
|
||||
echo "FAIL $name"
|
||||
failed+=("$name")
|
||||
fi
|
||||
}
|
||||
|
||||
run_tool "dotnet format --verify-no-changes" "$dotnet" format "$target" --verify-no-changes
|
||||
run_tool "dotnet build -warnaserror" "$dotnet" build "$target" --nologo -warnaserror
|
||||
|
||||
if [ "${#failed[@]}" -gt 0 ]; then
|
||||
echo "::error::Quality checks failed: ${failed[*]}"
|
||||
exit 1
|
||||
fi
|
||||
echo "All selected checks passed."
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
validate) validate_inputs ;;
|
||||
install) install_sdk ;;
|
||||
run) run_checks ;;
|
||||
*)
|
||||
echo "::error::[configuration] usage: quality-checks-csharp.sh validate|install|run" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user