#!/bin/bash # # This file is distributed under the MIT License. See LICENSE.md for details. # set -e function print_usage() { cat > /dev/stderr << END_USAGE_TEXT SYNOPSIS $0 [] [FILES...] DESCRIPTION Checks that the files specified in FILES... respect the rev.ng coding conventions, and prints all the violations. If the FILES... arguments are missing, checks that all the C++ source files in the project that have already been added to the git repository project respect the rev.ng coding conventions. OPTIONS --help Prints this help and exit --force-format In addition to checking the rev.ng coding conventions on the specified files, uses clang-format to try to enforce the coding conventions automatically. If this fails, exit with failure, otherwise exit with success. WARNING: using this option will overwrite your files, make sure to backup important stuff. --use-local-clang-format-file By default, clang-format is executed with a hard-coded configuration that should apply to all the projects under the rev.ng umbrella. When this option is passed, the hard-coded configuration is ignored, and clang-format looks for configuration in a .clang-format file in the current directory or in its parent directories. This allows to use different clang-format configuration on per-project basis, in cases where the hard-coded configuration does not fit (e.g Qt projects where Qt coding conventions are a better fit). See clang-format documentation for more details. --print-clang-format-config Print the clang-format configuration to stdout, ignoring all the other arguments except for --use-local-clang-format-file. The conventions are not checked. FILES List of filenames of the files for which you want to check the rev.ng coding conventions. RETURN VALUES On success exit code is 0. On failure, i.e. if there is at least one file that is not respecting the coding conventions, exit code is 1. END_USAGE_TEXT } SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FORCE_FORMAT="0" USE_CLANG_FORMAT_FILE="0" PRINT_CLANG_FORMAT_CONFIG="0" while [[ $# > 0 ]]; do key="$1" case $key in --print-clang-format-config) PRINT_CLANG_FORMAT_CONFIG="1" shift # past argument ;; --force-format) FORCE_FORMAT="1" shift # past argument ;; --use-local-clang-format-file) USE_CLANG_FORMAT_FILE="1" shift # past argument ;; --help) print_usage exit 0 ;; -*) echo "Error: unrecognized option $key" > /dev/stderr print_usage exit 1 ;; *) break ;; esac done CLANG_FORMAT_STYLE_FILE="$SCRIPT_PATH/clang-format-style-file" if ! test -e "$CLANG_FORMAT_STYLE_FILE"; then CLANG_FORMAT_STYLE_FILE="$SCRIPT_PATH/../../share/revng/clang-format-style-file" if ! test -e "$CLANG_FORMAT_STYLE_FILE"; then echo "Can't find clang-format-style-file" > /dev/stderr exit 1 fi fi CLANG_FORMAT_STYLE=$(cat "$CLANG_FORMAT_STYLE_FILE") if test "$USE_CLANG_FORMAT_FILE" -gt 0; then CLANG_FORMAT_STYLE=file fi # If the user passed the --print-clang-format-config, dump the config and exit # with success. if test "$PRINT_CLANG_FORMAT_CONFIG" -gt 0; then clang-format --dry-run -style="$CLANG_FORMAT_STYLE" --dump-config exit 0 fi if [[ $# -eq 0 ]]; then FILES="$(git ls-files | grep -E '\.(c|cc|cpp|h|hpp)$')" else FILES="$@" fi GREP="git grep -n --color=always" # Run clang-format on FILES function run_clang_format() { if test "$FORCE_FORMAT" -gt 0; then clang-format -style="$CLANG_FORMAT_STYLE" -i $FILES || true else clang-format --dry-run -style="$CLANG_FORMAT_STYLE" -i $FILES fi } # Run black function run_black() { readarray -t PYTHON_FILES < <(( git grep -l '^#!.*python' && git ls-files | grep '\.py$' ) | sort -u ) if [[ ${#PYTHON_FILES[@]} -eq 0 ]]; then return; fi if test "$FORCE_FORMAT" -gt 0; then black -q -l 100 "${PYTHON_FILES[@]}" else black --check -l 100 "${PYTHON_FILES[@]}" | grep 'Oh no' fi } # Run cmake-format function run_cmake_format() { if test "$FORCE_FORMAT" -gt 0; then cmake-format -i $(git ls-files | grep -i cmake) -l error else cmake-format --check $(git ls-files | grep -i cmake) -l error fi } # Run revng-specific checks on files function run_revng_checks() { # Check for lines longer than 80 columns $GREP -E '^.{81,}$' $FILES | cat # Things should never match for REGEXP in '\(--> 0\)' ';;' '^\s*->.*;$' 'Twine [^&]'; do $GREP "$REGEXP" $FILES | cat done # Things should never match (except in support.c) FILTERED_FILES="$(echo $FILES | tr ' ' '\n' | grep -v \ -e '^runtime/support\.c$' \ -e '^lib/Support/Assert\.cpp$' \ )" for REGEXP in '\babort(' '\bassert(' 'assert(false' 'llvm_unreachable'; do $GREP "$REGEXP" $FILTERED_FILES | cat done # Things should never be at the end of a line for REGEXP in '::' '<' 'RegisterPass.*>' '} else' '\bopt\b.*>'; do $GREP "$REGEXP\$" $FILES | cat done # Includes should never use <..> except for C++ standard includes $GREP "^\s*#include <.*\.hpp>" $FILES | cat CXX_FILES="$(echo $FILES | tr ' ' '\n' | grep -v \ -e '^runtime/support\.h$' \ -e '^include/revng/Runtime/.*$' \ -e '^include/revng/PipelineC/.*$' \ -e '^include/revng/Support/Assert\.h$' \ -e '^include/revng/Support/ClassSentinel\.h$' \ -e '\.c$' \ )" $GREP "^\s*#include <.*\.h>" $CXX_FILES | cat # Parenthesis at the end of line (except for raw strings) $GREP "(\$" $FILES | grep -v 'R"LLVM.*(' | cat # Ban whitespaces at the end of a line $GREP " \$" | cat # Ban tabs altogether $GREP -P "\t" | cat # Ensure all files end with a newline git ls-files | while read FILE; do if test -s "$FILE" && ! test $(tail -c 1 "$FILE" | base64) == "Cg=="; then echo "$FILE does not end with a newline" fi done # Things should never be at the beginning of a line for REGEXP in '\*>' '/[^/\*]' ':[^:\(]*)' '==' '\!=' '<[^<]' '>' '>=' '<=' '//\s*WIP' '#if\s*[01]'; do $GREP "^\s*$REGEXP" $FILES | cat done # Check there are no static functions in C++ header files for FILE in $CXX_FILES; do if [[ $FILE == *h ]]; then $GREP -H '^static\b[^=]*$' "$FILE" | cat head -n1 "$FILE" | grep -E '^#pragma once$' > /dev/null || echo "$FILE: header does not start with #pragma once" fi done } if test $( ( run_clang_format && run_black && run_cmake_format && run_revng_checks | sort -u ) |& tee /dev/stderr | wc -c ) -ne 0; then exit 1 fi