~aleteoryx/muditaos

4929c07f77ee08d8f844615b9767d2966765dc31 — Radosław Wicik 5 years ago d75f555
[EGD-3743] add script for checking and adding copyright notice (#852)

Co-authored-by: Radoslaw Wicik <radoslaw@wicik.pl>
6 files changed, 473 insertions(+), 200 deletions(-)

M .github/workflows/main.yml
M config/common_scripsts_lib
A config/license_header_check.sh
D config/pre-commit-check-only.hook
M config/pre-commit.hook
A config/style_check_hook.sh
M .github/workflows/main.yml => .github/workflows/main.yml +4 -1
@@ 12,9 12,12 @@ jobs:
        token: ${{ secrets.GitHub_PAT }}
        submodules: recursive
        fetch-depth: 0
    - name: "Copyright notice check"
      run: |
        config/license_header_check.sh --ci --check-only
    - name: "Style checking"
      run: |
        ./config/pre-commit.hook --last
        ./config/style_check_hook.sh --last
    - name: "Unit Tests"
      run: |
        ./configure.sh linux Debug && \

M config/common_scripsts_lib => config/common_scripsts_lib +9 -0
@@ 1,5 1,14 @@
#!/bin/bash

RED='\e[38;2;255;13;0m'
YELLOW="\e[38;2;255;232;0m"
GREEN="\e[38;2;0;222;27m"
ORANGE="\e[38;2;255;100;0m"

OK="${GREEN}OK!\e[0m"
ERROR="${RED}Error!\e[0m"
FIXED="${YELLOW}Fixed!\e[0m"

function printVar(){
    echo "$1: '${!1}'"
}

A config/license_header_check.sh => config/license_header_check.sh +244 -0
@@ 0,0 1,244 @@
#!/bin/bash
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. 
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md


SCRIPT=$(readlink -f $0)
SCRIPT_DIR="$(dirname ${SCRIPT})"
REPO_ROOT="${SCRIPT_DIR%/*}"

#. ${SCRIPT_DIR}/bootstrap_config
. ${SCRIPT_DIR}/common_scripsts_lib
. ${SCRIPT_DIR}/format-config.sh

#printVar SCRIPT
#printVar SCRIPT_DIR
#printVar REPO_ROOT

pushd ${REPO_ROOT} >> /dev/null

LICENSE1="Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved."
LICENSE2="For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md"

CHECK_ONLY="false"
EXIT_CODE=0

#file extension; comment;checker/replacer function
declare -A FileTypes=( 
            ["header"]="hpp;cppChecker;"
            ["source"]="cpp;cppChecker;"
            ["python"]="py;pythonChecker;"
            ["shell"]="sh;bashChecker;"
        )
declare -a paths_to_ignore


function cppChecker(){
    FILE=$1
    LICENSE_LINE1="//${LICENSE1}"
    LICENSE_LINE2="//${LICENSE2}"

    CHECK_LICENSE=$(head -n5 ${FILE} | grep "${LICENSE1}")
    if [[ -z ${CHECK_LICENSE} ]]; then
        if [[ ${CHECK_ONLY} == "true" ]]; then
            echo -e "${ERROR}"
            EXIT_CODE=1
        else 
            sed -i "1i${LICENSE_LINE1}" ${FILE}
            sed -i "2i${LICENSE_LINE2}" ${FILE}
            echo -e "${FIXED}"
        fi
    else 
        echo -e "${OK}"
    fi
}

function scriptChecker(){
    CHECK_LICENSE=$( head -n5 ${FILE} | grep "${LICENSE1}")
    if [[ -z ${CHECK_LICENSE} ]]; then
        if [[ ${CHECK_ONLY} == "true" ]]; then
            echo -e "${ERROR}"
            EXIT_CODE=1
        else
            FIST_LINE=$(head -n1 ${FILE}|grep "$SHA_BANG_EXEC")
            if [[ -n "${FIST_LINE}" ]]; then
                sed -i "2i${LICENSE_LINE1}" ${FILE}
                sed -i "3i${LICENSE_LINE2}" ${FILE}
            else 
                sed -i "1i${LICENSE_LINE1}" ${FILE}
                sed -i "2i${LICENSE_LINE2}" ${FILE}
            fi
            echo -e "${FIXED}"
        fi
    else 
        echo -e "${OK}"
    fi

}
function pythonChecker(){
    FILE=$1
    LICENSE_LINE1="#${LICENSE1}"
    LICENSE_LINE2="#${LICENSE2}"
    SHA_BANG_EXEC="python"
    scriptChecker
    
} 

function bashChecker(){
    FILE=$1
    LICENSE_LINE1="#${LICENSE1}"
    LICENSE_LINE2="#${LICENSE2}"
    SHA_BANG_EXEC="bash"
    scriptChecker
}

SUBMODULES=( $(git submodule | cut -f3 -d" " ) )

function extractValues() {
    KEY=$1

    readarray -td ';' DATA <<< ${FileTypes[${KEY}]}

    FILE_EXT=${DATA[0]}
    CHECK_FUNCTION=${DATA[1]}
}

function excludePathFromFind(){
    # ignore submodule paths
    I=0
    SUB_COUNT=${#SUBMODULES[@]}
    while [[ $I -lt ${SUB_COUNT} ]];
    do 
        module=${SUBMODULES[$I]}
        NOT_PATH="${NOT_PATH} -not ( -path ./${module} -prune )"
        I=$(( $I + 1))
    done
    
    # ignore paths excluded from format
    I=0
    IGNORE_PATHS_COUNT=${#ignore_paths[@]}
    while [[ $I -lt ${IGNORE_PATHS_COUNT} ]]
    do
        path=${ignore_paths[$I]}
        if [[ -d ${path} ]]; then
            NOT_PATH="${NOT_PATH} -not ( -path ${path%*/} -prune )"
        else
            paths_to_ignore+=("${path}")
        fi
        I=$(( $I + 1 ))
    done
    declare -p paths_to_ignore
    
}

function findFiles() {
    # find files except submodule directories
    echo "find . ${NOT_PATH} -iname \"*.${FILE_EXT}\" -print0 "
    readarray -d '' FILES_TO_CHECK < <(find . ${NOT_PATH} -iname "*.${FILE_EXT}" -print0)
}

function shouldnt_ignore() {
    FILE=$1
    for el in ${paths_to_ignore[@]}; do
        if [[ ${FILE}  =~ ^${el}.* ]]; then
            echo -e "${ORANGE}Ignore: ${FILE} checking due to: $el match!\e[0m"
            return 1
        fi
    done
    return 0
}

function help() {
    cat <<END
    check and update header files with copyright notice
    ussage:
        $0 [--help | --check-only]
        $0 --hook [ --check-only]
                Run as git "pre-commith.hook", checks against files in stash
        $0 --ci 
                Run on CI, check files that are new to the current master
            
            --help              - this help message
            --check-only        - only checks, do not change a file

END
    exit 0
}

function hookMain() {
    parseArgs $@
    readarray -d '' FILES_TO_CHECK < <( ${GET_FILES_CMD} )

    for FILE_TYPE in "${!FileTypes[@]}" 
    do
        echo "=== ${FILE_TYPE} ==="
        extractValues ${FILE_TYPE}
        I=0
        while [[ $I -lt ${#FILES_TO_CHECK[@]} ]]
        do
            FILE=${FILES_TO_CHECK[$I]}
            if shouldnt_ignore ${FILE}; then
                if [[ ${FILE} =~ .*\.${FILE_EXT}$ ]]; then
                    echo -en "${FILE}:"
                    ${CHECK_FUNCTION} ${FILE}
                    git add ${FILE}
                fi
            fi
            I=$(( $I + 1 ))
        done
    done
    echo "Hook ${EXIT_CODE}"
    exit ${EXIT_CODE}
}

function standAloneMain() {
    NOT_PATH=""
    excludePathFromFind
    
    for FILE_TYPE in "${!FileTypes[@]}"
    do
        echo "=== ${FILE_TYPE} ==="
        extractValues ${FILE_TYPE}
        findFiles
        I=0
        while [[ $I -lt ${#FILES_TO_CHECK[@]} ]]
        do
            FILE=${FILES_TO_CHECK[$I]}
            if shouldnt_ignore ${FILE}; then
                echo -en "${I}/${#FILES_TO_CHECK[@]}:{${FILE}: "
                ${CHECK_FUNCTION} ${FILE}
            fi
            I=$(( $I + 1 ))
        done
    done
    echo "StandAlone: ${EXIT_CODE}"
    exit ${EXIT_CODE}
}

function parseArgs() {
    if [[ $# -ge 1 ]]; then
        case $1 in
            "--help")
                help
                ;;
            "--check-only")
                CHECK_ONLY=true
                ;;
            "--hook")
                GET_FILES_CMD="git diff --cached --name-only --diff-filter=A -z ${against:-HEAD}"
                shift
                hookMain $@
                ;;
            "--ci")
                shift
                CHECK_ONLY=true
                GET_FILES_CMD="git diff --name-only --diff-filter=A -z remotes/origin/master...HEAD"
                hookMain $@
                ;;
            *)
            ;;
        esac
    fi
}
parseArgs $@
standAloneMain

D config/pre-commit-check-only.hook => config/pre-commit-check-only.hook +0 -1
@@ 1,1 0,0 @@
pre-commit.hook
\ No newline at end of file

M config/pre-commit.hook => config/pre-commit.hook +11 -198
@@ 1,201 1,14 @@
#!/bin/bash 
# taken from: https://raw.githubusercontent.com/andrewseidl/githook-clang-format/master/clang-format.hook
# - with just added check for cpp file & check for proper version of clang-format
# - with use of clang-format diff.py - for changed chunk changes only
# might be worth to consider: https://github.com/Sarcasm/run-clang-format
#!/bin/bash
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. 
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#run pre-commit hooks

set -e 
L_GIT_DIR=$(git rev-parse --show-toplevel)
source $L_GIT_DIR/config/format-config.sh

RED='\e[38;2;255;13;0m'
YELLOW="\e[38;2;255;232;0m"
GREEN="\e[38;2;0;222;27m"
ORANGE="\e[38;2;255;100;0m"

OK="${GREEN}OK!\e[0m"
ERROR="${RED}Error!\e[0m"
FIXED="${YELLOW}Fixed!\e[0m"


# if autoformatting was disabled by user - then ignore this commit hook
if [[ $DISABLE_AUTO_FORMATTING ]]; then
    [[ $VERBOSE ]] && echo "auto formatting is disabled"
    exit 0
fi

# check if clang-format in path is in proper version, version is 3rd column in `clang-format --version`
CVER=$( [[ $(which clang-format) ]] && echo $(clang-format --version | cut -d ' ' -f 3 | cut -d '.' -f 1) || echo "0")
# check for either clang-format or clang-format-9
if [[ $CVER -lt 10 && ! $(which clang-format-10) ]]; then
    cat << EOF >&1
Either install:
    clang-format in at least version 10 and set as default"
    or
    clang-format-10

    Your clang format version in path used:
        $(clang-format --version):
        $(clang-format-10 --version)
    git commit aborted"
EOF
    exit 1
fi

get_clang_format_script() {
    local declare searchpaths=(
        $(which "clang-format-diff.py")                                 # clang-format-diff in path
        "/usr/share/clang/clang-format-10/clang-format-diff.py"          # clang-format-diff location for clang format 9 Ubuntu/Debian 18.04
        "/usr/share/clang/clang-format-${CVER}/clang-format-diff.py"    # clang-format-diff location on Ubuntu/Debian
        "/usr/share/clang/clang-format-diff.py"                         # clang-format_diff location on Arch last resort
    )
    local tool=""
    for el in ${searchpaths[@]}; do
        if [[ -f ${el} ]]; then
            tool=${el}
            break
        fi
    done
    if [[ ${tool} == "" ]]; then
        echo clang-format-diff not found in path and: ${sarchpaths[@]} >2
    fi
    echo "${tool}"
}

L_CLANG_DIFF_TOOL=$(get_clang_format_script)
if ! [[ $L_CLANG_DIFF_TOOL ]] || [[ $L_CLANG_DIFF_TOOL == "" ]]; then
    exit 1
fi

check_file() {
    file="${1}"
    last_commit="${2}"
    if [ -f $file ]; then
        #[[ $VERBOSE ]] && echo -en "Checking: \e[33m${file}\e[0m :\t"
        results["${file}"]="${OK}"
        case ${last_commit} in
        "True")
            if [[ ${FIX,,} == "true" ]]; then
                git diff -U0 --no-color remotes/origin/master...HEAD ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 -i
                STATUS=$(git status --short -- ${file})
                if [ -n "${STATUS}" ]; then
                    git add "${file}"
                    results["${file}"]="${FIXED}";
                fi
            else
                OUT=$(git diff -U0 --no-color remotes/origin/master...HEAD ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 )
                if [ -n "${OUT}" ]; then
                    results["${file}"]="${ERROR}"
                    return 1
                fi
            fi
            ;;
        "Stage")
            if [[ ${FIX,,} == "true" ]]; then
                git diff -U0 --no-color --cached ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 -i
                STATUS=$(git status --short -- ${file})
                if [ -n "${STATUS}" ]; then
                    git add "${file}"
                    results["${file}"]="${FIXED}";
                fi
            else
                OUT=$(git diff -U0 --no-color --cached ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1)
                if [ -n "${OUT}" ]; then
                    results["${file}"]="${ERROR}"
                    return 1
                fi
            fi
            ;;
        *)
            OUT=$(git diff -U0 --no-color --cached ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 )
            if [ -n ${OUT} ]; then
                results["${file}"]="${ERROR}"
                return 1
            fi
            ;;
        esac
    fi
    return 0
}

# bash function using above config function
shouldnt_ignore() {
    # change full name path to path relative to root git dir
    local fname=${1/"$L_GIT_DIR"/"./"}
    for el in ${ignore_paths[@]}; do
        if [[ ./${fname}  =~ ^${el}.* ]]; then
            [[ $VERBOSE ]] && echo "Ignore: ${fname} formatting due to: $el match!"
            return 1
        fi
    done
    return 0
}

function help() {
        echo "Runs clang-format on source files"
        cat <<- EOM
		ussge:
		   $0 [option]
		        --about             does nothing
		        --last              checks current branch against origin/master - doesn't fix
		        --branch-fix        checks current branch against origin/master and fixes errors - you have to 'git add' and 'git commit' them
		        --fix               fix style in currently staged files (ignores user.fixinstage variale), this is usefull for manual style applying
		
		If you would like to automatially fix style before commit
		add to your git config "user.fixinstage" variable with value "True"
		by calling:
		    git config user.fixinstage True
		EOM
}


case "${1}" in
    --about|--help )
        help
        FILES=''
        LAST=""
        exit 0
        ;;
    --last)
        FILES=$(git diff --name-only remotes/origin/master...HEAD)
        LAST="True"
        FIX="false"
        ;;
    --branch-fix)
        FILES=$(git diff --name-only remotes/origin/master...HEAD)
        LAST="True"
        FIX="true"
        ;;
    --fix)
        FILES=$(git diff-index --cached --name-only HEAD)
        LAST="Stage" 
        FIX=true
        ;;
    *)
        FILES=$(git diff-index --cached --name-only HEAD)
        LAST="Stage" 
        FIX=$(git config user.fixinstage)
        FIX=${FIX:-false}
        ;;
esac

declare -A results

EXIT_CODE=0
for file in ${FILES}; do
    if [[ ${file} =~ ^.*\.(cpp|hpp|c|h|cxx|gcc|cc)$ ]] && shouldnt_ignore ${file}; then
        check_file "${file}" ${LAST}
        RESULT=$?
        if [[ ${RESULT} -eq 1 ]]; then
            EXIT_CODE=1
        fi
    fi
done

for FILE in "${!results[@]}"
do
    echo -e "  ${FILE} : ${results[${FILE}]}"
done

exit ${EXIT_CODE}

pushd $L_GIT_DIR
echo "=== check copyright ==="
./config/license_header_check.sh --hook
echo "=== check style ==="
./config/style_check_hook.sh
popd

A config/style_check_hook.sh => config/style_check_hook.sh +205 -0
@@ 0,0 1,205 @@
#!/bin/bash 
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. 
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

# taken from: https://raw.githubusercontent.com/andrewseidl/githook-clang-format/master/clang-format.hook
# - with just added check for cpp file & check for proper version of clang-format
# - with use of clang-format diff.py - for changed chunk changes only
# might be worth to consider: https://github.com/Sarcasm/run-clang-format


L_GIT_DIR=$(git rev-parse --show-toplevel)
source $L_GIT_DIR/config/format-config.sh

RED='\e[38;2;255;13;0m'
YELLOW="\e[38;2;255;232;0m"
GREEN="\e[38;2;0;222;27m"
ORANGE="\e[38;2;255;100;0m"

OK="${GREEN}OK!\e[0m"
ERROR="${RED}Error!\e[0m"
FIXED="${YELLOW}Fixed!\e[0m"


# if autoformatting was disabled by user - then ignore this commit hook
if [[ $DISABLE_AUTO_FORMATTING ]]; then
    [[ $VERBOSE ]] && echo "auto formatting is disabled"
    exit 0
fi

# check if clang-format in path is in proper version, version is 3rd column in `clang-format --version`
CVER=$( [[ $(which clang-format) ]] && echo $(clang-format --version | cut -d ' ' -f 3 | cut -d '.' -f 1) || echo "0")
# check for either clang-format or clang-format-9
if [[ $CVER -lt 10 && ! $(which clang-format-10) ]]; then
    cat << EOF >&1
Either install:
    clang-format in at least version 10 and set as default"
    or
    clang-format-10

    Your clang format version in path used:
        $(clang-format --version):
        $(clang-format-10 --version)
    git commit aborted"
EOF
    exit 1
fi

get_clang_format_script() {
    local declare searchpaths=(
        $(which "clang-format-diff.py")                                 # clang-format-diff in path
        "/usr/share/clang/clang-format-10/clang-format-diff.py"          # clang-format-diff location for clang format 9 Ubuntu/Debian 18.04
        "/usr/share/clang/clang-format-${CVER}/clang-format-diff.py"    # clang-format-diff location on Ubuntu/Debian
        "/usr/share/clang/clang-format-diff.py"                         # clang-format_diff location on Arch last resort
    )
    local tool=""
    for el in ${searchpaths[@]}; do
        if [[ -f ${el} ]]; then
            tool=${el}
            break
        fi
    done
    if [[ ${tool} == "" ]]; then
        echo clang-format-diff not found in path and: ${sarchpaths[@]} >2
    fi
    echo "${tool}"
}

L_CLANG_DIFF_TOOL=$(get_clang_format_script)
if ! [[ $L_CLANG_DIFF_TOOL ]] || [[ $L_CLANG_DIFF_TOOL == "" ]]; then
    exit 1
fi

check_file() {
    file="${1}"
    last_commit="${2}"
    if [ -f $file ]; then
        #[[ $VERBOSE ]] && echo -en "Checking: \e[33m${file}\e[0m :\t"
        results["${file}"]="${OK}"
        case ${last_commit} in
        "True")
            if [[ ${FIX,,} == "true" ]]; then
                git diff -U0 --no-color remotes/origin/master...HEAD ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 -i
                STATUS=$(git status --short -- ${file})
                if [ -n "${STATUS}" ]; then
                    git add "${file}"
                    results["${file}"]="${FIXED}";
                fi
            else
                OUT=$(git diff -U0 --no-color remotes/origin/master...HEAD ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 )
                if [ -n "${OUT}" ]; then
                    results["${file}"]="${ERROR}"
                    return 1
                fi
            fi
            ;;
        "Stage")
            if [[ ${FIX,,} == "true" ]]; then
                git diff -U0 --no-color --cached ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 -i
                STATUS=$(git status --short -- ${file})
                if [ -n "${STATUS}" ]; then
                    git add "${file}"
                    results["${file}"]="${FIXED}";
                fi
            else
                OUT=$(git diff -U0 --no-color --cached ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1)
                if [ -n "${OUT}" ]; then
                    results["${file}"]="${ERROR}"
                    return 1
                fi
            fi
            ;;
        *)
            OUT=$(git diff -U0 --no-color --cached ${file} | ${L_CLANG_DIFF_TOOL} -style file -p1 )
            if [ -n ${OUT} ]; then
                results["${file}"]="${ERROR}"
                return 1
            fi
            ;;
        esac
    fi
    return 0
}

# bash function using above config function
shouldnt_ignore() {
    # change full name path to path relative to root git dir
    local fname=${1/"$L_GIT_DIR"/"./"}
    for el in ${ignore_paths[@]}; do
        if [[ ./${fname}  =~ ^${el}.* ]]; then
            [[ $VERBOSE ]] && echo "Ignore: ${fname} formatting due to: $el match!"
            return 1
        fi
    done
    return 0
}

function help() {
        echo "Runs clang-format on source files"
        cat <<- EOM
		ussge:
		   $0 [option]
		        --about             does nothing
		        --last              checks current branch against origin/master - doesn't fix
		        --branch-fix        checks current branch against origin/master and fixes errors - you have to 'git add' and 'git commit' them
		        --fix               fix style in currently staged files (ignores user.fixinstage variale), this is usefull for manual style applying
		
		If you would like to automatially fix style before commit
		add to your git config "user.fixinstage" variable with value "True"
		by calling:
		    git config user.fixinstage True
		EOM
}


case "${1}" in
    --about|--help )
        help
        FILES=''
        LAST=""
        exit 0
        ;;
    --last)
        FILES=$(git diff --name-only remotes/origin/master...HEAD)
        LAST="True"
        FIX="false"
        ;;
    --branch-fix)
        FILES=$(git diff --name-only remotes/origin/master...HEAD)
        LAST="True"
        FIX="true"
        ;;
    --fix)
        FILES=$(git diff-index --cached --name-only HEAD)
        LAST="Stage" 
        FIX=true
        ;;
    *)
        FILES=$(git diff-index --cached --name-only HEAD)
        LAST="Stage" 
        FIX=$(git config user.fixinstage)
        FIX=${FIX:-false}
        ;;
esac

declare -A results

EXIT_CODE=0
for file in ${FILES}; do
    if [[ ${file} =~ ^.*\.(cpp|hpp|c|h|cxx|gcc|cc)$ ]] && shouldnt_ignore ${file}; then
        check_file "${file}" ${LAST}
        RESULT=$?
        if [[ ${RESULT} -eq 1 ]]; then
            EXIT_CODE=1
        fi
    fi
done

for FILE in "${!results[@]}"
do
    echo -e "  ${FILE} : ${results[${FILE}]}"
done

echo "exit: ${EXIT_CODE}"
exit ${EXIT_CODE}