set -o errexit
set -o errtrace
set -o nounset
set -o pipefail
ROOT="$(realpath "$(dirname "$0")"/../)"
GITHUB_STEP_SUMMARY=${GITHUB_STEP_SUMMARY:-/dev/stdout}
IN_GITHUB_CONTEXT=false
if [[ $GITHUB_STEP_SUMMARY != "/dev/stdout" ]]; then
IN_GITHUB_CONTEXT=true
fi
GREEN="\033[32m"
RED="\033[31m"
NORMAL="\033[0m"
ERROR_LOG=""
info()
{
printf "%b %b\n" "${GREEN}✓" "${NORMAL}${*}"
}
error()
{
printf "%b %b\n" "${RED}✗" "${NORMAL}${*}"
}
checkerr()
{
local code=$?
local title="$1"
local out="$2"
local success=true
if [[ $code == 127 ]]; then
success=false
fi
if [[ ${#out} != 0 ]]; then
if $IN_GITHUB_CONTEXT; then
echo "$out" | while read -r file line; do
echo "::error file=$file,line=${line:-1},title=Format error::$file require to be formatted"
done
fi
success=false
fi
if $success; then
info "$title"
gh_summary_success "$title"
else
error "$title"
SUCCESS=false
gh_summary_failure "$title"
fi
}
gh_group_start()
{
if $IN_GITHUB_CONTEXT; then
echo "::group::$1"
fi
}
gh_group_end()
{
if $IN_GITHUB_CONTEXT; then
echo "::endgroup::"
fi
}
gh_summary_success()
{
if $IN_GITHUB_CONTEXT; then
echo ":white_check_mark: $1" >>"$GITHUB_STEP_SUMMARY"
fi
}
gh_summary_failure()
{
if $IN_GITHUB_CONTEXT; then
echo ":x: $1" >>"$GITHUB_STEP_SUMMARY"
ERROR_LOG+=" - $1"$'\n'
fi
}
gh_error()
{
error "$4"
if $IN_GITHUB_CONTEXT; then
echo "::error file=$1,line=${2:-1},title=$3::$4"
fi
}
gh_error_simple()
{
if $IN_GITHUB_CONTEXT; then
echo "::error title=$1::$2"
fi
}
run-shellcheck()
{
if ! command -v shellcheck >/dev/null; then
gh_error_simple "Shellcheck missing" "shellcheck is not installed"
error "Lint shellcheck"
SUCCESS=false
gh_summary_failure "Lint shellcheck"
return 0
fi
checkerr "Lint shellcheck" "$(echo $SHELL_SCRIPTS | xargs shellcheck --severity=warning)"
}
SUCCESS=true
SHELL_SCRIPTS=$(git grep -l '' :/ | xargs printf "'%s' " | xargs -L 1 shfmt -f)
gh_group_start "Linting nix files with nixpkgs-fmt"
checkerr "Lint nix" "$(nixpkgs-fmt --check "$ROOT")"
gh_group_end
gh_group_start "Linting shell scripts with shfmt"
checkerr "Lint shell" "$(echo $SHELL_SCRIPTS | xargs -L 1 shfmt -s -l -i 2 -ci -fn)"
gh_group_end
gh_group_start "Linting shell scripts with shellcheck"
run-shellcheck
gh_group_end
gh_group_start "Linting GitHub Actions workflows with actionlint"
if ! command -v actionlint >/dev/null; then
gh_error_simple "actionlint missing" "actionlint is not installed"
error "Lint GitHub Actions"
SUCCESS=false
gh_summary_failure "Lint GitHub Actions"
elif ! actionlint; then
error "Lint GitHub Actions"
SUCCESS=false
gh_summary_failure "Lint GitHub Actions"
else
info "Lint GitHub Actions"
gh_summary_success "Lint GitHub Actions"
fi
gh_group_end
gh_group_start "Linting python scripts with ruff (format)"
if ! diff=$(ruff format --check --diff "$ROOT" 2>&1); then
echo "$diff"
gh_error_simple "Format error" "$diff"
error "Lint Python (ruff format)"
SUCCESS=false
gh_summary_failure "Lint Python (ruff format)"
else
info "Lint Python (ruff format)"
gh_summary_success "Lint Python (ruff format)"
fi
gh_group_end
gh_group_start "Linting python scripts with ruff (check)"
if ! out=$(ruff check "$ROOT" 2>&1); then
echo "$out"
gh_error_simple "Lint error" "$out"
error "Lint Python (ruff check)"
SUCCESS=false
gh_summary_failure "Lint Python (ruff check)"
else
info "Lint Python (ruff check)"
gh_summary_success "Lint Python (ruff check)"
fi
gh_group_end
gh_group_start "Linting c files with clang-format"
lint-c-files()
{
for file in $(git ls-files -- ":/*.c" ":/*.h"); do
if [[ ! -L $file ]]; then
clang-format --Werror --dry-run "$file" 2>&1 | grep "error:" | cut -d ':' -f 1,2 | tr ':' ' '
fi
done
}
checkerr "Lint C" "$(lint-c-files)"
gh_group_end
lint-clang-tidy()
{
if ! command -v clang-tidy >/dev/null; then
gh_error_simple "clang-tidy missing" "clang-tidy is not installed"
error "Lint clang-tidy"
SUCCESS=false
gh_summary_failure "Lint clang-tidy"
return 0
fi
local c_files=(":(glob)mldsa/src/*.c" ":(glob)mldsa/src/fips202/*.c")
local h_files=(":(glob)mldsa/src/*.h" ":(glob)mldsa/src/fips202/*.h")
local nproc success=true
nproc=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
run-clang-tidy()
{
local label="$1" pathspecs="$2" out
shift 2
if ! out=$(git ls-files -- $pathspecs |
xargs -P "$nproc" -I {} \
clang-tidy --quiet {} -- -I"$ROOT"/mldsa -std=c90 "$@" 2>&1); then
echo "$out"
gh_error_simple "clang-tidy error" "clang-tidy reported findings ($label)"
success=false
fi
}
for params in 44 65 87; do
run-clang-tidy "ML-DSA-$params .c" "${c_files[*]}" \
-DMLD_CONFIG_PARAMETER_SET="$params"
run-clang-tidy "ML-DSA-$params .h" "${h_files[*]}" \
-x c -DMLD_CONFIG_PARAMETER_SET="$params"
done
if $success; then
info "Lint clang-tidy"
gh_summary_success "Lint clang-tidy"
else
error "Lint clang-tidy"
SUCCESS=false
gh_summary_failure "Lint clang-tidy"
fi
}
gh_group_start "Static analysis with clang-tidy"
lint-clang-tidy
gh_group_end
check-eol-dry-run()
{
for file in $(git ls-files -- ":/" ":/!:*.png"); do
if [[ ! -L $file && $(tail -c1 "$file" | wc -l) == 0 ]]; then
l=$(wc -l <"$file")
echo "$file $l"
fi
done
}
gh_group_start "Checking eol"
checkerr "Check eol" "$(check-eol-dry-run)"
gh_group_end
check-spdx()
{
local success=true
for file in $(git ls-files -- ":/" ":/!:*.json" ":/!:*.png" ":/!:*LICENSE*" ":/!:.git*" ":/!:flake.lock"); do
if [[ ! -L $file && $(grep "SPDX-License-Identifier:" "$file" | wc -l) == 0 ]]; then
gh_error "$file" "${line:-1}" "Missing license header error" "$file is missing SPDX License header"
success=false
fi
done
for file in $(git ls-files -- "*.[chsS]" "*.py" "*.mk" "*.yml" "**/Makefile*" ":/!proofs/cbmc/*.py" ":/!examples/bring_your_own_fips202/custom_fips202/tiny_sha3/*" ":/!examples/custom_backend/mldsa_native/src/fips202/native/custom/src/*"); do
if [[ ! -L $file && $(grep "Copyright (c) The mldsa-native project authors" "$file" | wc -l) == 0 ]]; then
gh_error "$file" "${line:-1}" "Missing copyright header error" "$file is missing copyright header"
success=false
fi
done
for file in $(git ls-files -- "*.[chsSi]" | grep "^dev/\|^mldsa/"); do
if [[ ! -L $file && $(grep "SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT" "$file" | wc -l) == 0 ]]; then
gh_error "$file" "${line:-1}" "Missing license header error" "$file is not licensed under 'Apache-2.0 OR ISC OR MIT'"
success=false
fi
done
if $success; then
info "Check SPDX + Copyright"
gh_summary_success "Check SPDX + Copyright"
else
error "Check SPDX + Copyright"
SUCCESS=false
gh_summary_failure "Check SPDX + Copyright"
fi
}
gh_group_start "Checking SPDX + Copyright headers"
check-spdx
gh_group_end
check-autogenerated-files()
{
if python3 "$ROOT"/scripts/autogen --dry-run; then
info "Check native auto-generated files"
gh_summary_success "Check native auto-generated files"
else
error "Check native auto-generated files"
gh_summary_failure "Check native auto-generated files"
SUCCESS=false
fi
}
gh_group_start "Check native auto-generated files"
check-autogenerated-files
gh_group_end
check-magic()
{
if python3 "$ROOT"/scripts/check-magic >/dev/null; then
info "Check magic constants"
gh_summary_success "Check magic constants"
else
error "Check magic constants"
gh_summary_failure "Check magic constants"
SUCCESS=false
fi
}
gh_group_start "Check magic constants"
check-magic
gh_group_end
check-contracts()
{
if python3 "$ROOT"/scripts/check-contracts >/dev/null; then
info "Check contracts"
gh_summary_success "Check contracts"
else
error "Check contracts"
gh_summary_failure "Check contracts"
SUCCESS=false
fi
}
gh_group_start "Check contracts"
check-contracts
gh_group_end
check-doxygen()
{
if ! command -v doxygen >/dev/null; then
gh_error_simple "doxygen missing" "doxygen is not installed"
error "Lint Doxygen comments"
SUCCESS=false
gh_summary_failure "Lint Doxygen comments"
return 0
fi
local out
if out=$(cd "$ROOT" && doxygen scripts/Doxyfile.lint 2>&1) && [[ -z $out ]]; then
info "Lint Doxygen comments"
gh_summary_success "Lint Doxygen comments"
else
echo "$out"
gh_error_simple "Doxygen lint error" "$out"
error "Lint Doxygen comments"
SUCCESS=false
gh_summary_failure "Lint Doxygen comments"
fi
rm -rf "$ROOT"/.doxylint-xml
}
gh_group_start "Linting Doxygen comments"
check-doxygen
gh_group_end
check-hol-light-imports()
{
if python3 "$ROOT"/scripts/check-hol-light-imports; then
info "Check HOL-Light imports"
gh_summary_success "Check HOL-Light imports"
else
error "Check HOL-Light imports"
gh_summary_failure "Check HOL-Light imports"
SUCCESS=false
fi
}
gh_group_start "Check HOL-Light imports"
check-hol-light-imports
gh_group_end
if ! $SUCCESS; then
if $IN_GITHUB_CONTEXT; then
printf "%b%s%b\n" "${RED}" "The following checks failed, expand each for more details." "${NORMAL}"
echo "$ERROR_LOG"
fi
exit 1
fi