diff --git a/.gitignore b/.gitignore index ffdfc5a1b258344c3c77b156362efcd922f899cd..c6fa6bf9ac17771e48f714bec9f8b03930023f76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ SkywardHub.pro.user SkywardHub.pro.user.* -.build build/** **/.DS_Store Deploy/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 8b8f9068e0501fd9b122b1df720c3f68a9560e09..fee18e3da98f25290ed429e4df20d4cb10f71a81 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -110,7 +110,7 @@ "qcloseevent": "cpp", "qthread": "cpp" }, - "editor.defaultFormatter": "chiehyu.vscode-astyle", + "editor.defaultFormatter": "ms-vscode.cpptools", "[xml]": { "editor.defaultFormatter": "redhat.vscode-xml" }, diff --git a/CMakeLists.txt b/CMakeLists.txt index 13c7a13416a72d597d0c9824476839fd270ab7f5..c39ce72eaa5e2c9ecdc1fd2962846c334fd0c47a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ find_package(Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt5 COMPONENTS SerialPort REQUIRED) find_package(Qt5 COMPONENTS PrintSupport REQUIRED) -add_executable(skywardhub +add_executable(groundstation Components/ContextMenuSeparator/contextmenuseparator.cpp Components/ModulesPicker/modulespicker.cpp Components/SaveConfigurationDialog/saveconfigurationdialog.cpp @@ -70,7 +70,7 @@ add_executable(skywardhub main.cpp application.qrc ) -target_include_directories(skywardhub PRIVATE ./) -target_link_libraries(skywardhub Qt5::Widgets) -target_link_libraries(skywardhub Qt5::SerialPort) -target_link_libraries(skywardhub Qt5::PrintSupport) +target_include_directories(groundstation PRIVATE ./) +target_link_libraries(groundstation Qt5::Widgets) +target_link_libraries(groundstation Qt5::SerialPort) +target_link_libraries(groundstation Qt5::PrintSupport) diff --git a/sbs b/sbs new file mode 100755 index 0000000000000000000000000000000000000000..6a45fcda52663e9a3797e5626111d63b292d562e --- /dev/null +++ b/sbs @@ -0,0 +1,391 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021 Skyward Experimental Rocketry +# Author: Damiano Amatruda, Alberto Nidasio +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +print_banner() { + cat <<EOF ++------------------------------------------------------------------------------------------+ +| ____ _ _ | +| / ___|| | ___ ___ ____ _ _ __ __| | | +| \\___ \\| |/ / | | \\ \\ /\\ / / _\` | '__/ _\` | | +| ___) | <| |_| |\\ V V / (_| | | | (_| | | +| |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_| \\__,_| | +| ____ |___/ _ ____ _ __ ___ _ | +| | __ ) _ _(_) | __| | / ___| _ _ ___| |_ ___ _ __ ___ / _| ___ _ __ / _ \\| |_ | +| | _ \\| | | | | |/ _\` | \\___ \\| | | / __| __/ _ \\ '_ \` _ \\ | |_ / _ \\| '__| | | | | __| | +| | |_) | |_| | | | (_| | ___) | |_| \\__ \\ || __/ | | | | | | _| (_) | | | |_| | |_ | +| |____/ \\__,_|_|_|\\__,_| |____/ \\__, |___/\\__\\___|_| |_| |_| |_| \\___/|_| \\__\\_\\\\__| | ++---------------------------------|___/-----------------------------------------------v1.0-+ + +EOF +} + +ohai() { + printf "\n${TTY_BLUE}==>${TTY_RESET}${TTY_BOLD} %s${TTY_RESET}\n" "$@" +} + +init_dirs() { + sbs_base="$(cd -- "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)" + source_dir="$PWD" + build_dir="$source_dir/$BUILD_DIRNAME" +} + +find_deps() { + ohai "Find dependencies" + + command -v cmake > /dev/null 2>&1 && found_cmake=true + command -v ccache > /dev/null 2>&1 && found_ccache=true + command -v ninja > /dev/null 2>&1 && found_ninja=true + command -v python > /dev/null 2>&1 && found_python=true + command -v cppcheck > /dev/null 2>&1 && found_cppcheck=true + command -v clang-tidy > /dev/null 2>&1 && found_clangtidy=true + command -v clang-format > /dev/null 2>&1 && found_clangformat=true + + printf "Found CMake: "; [ "$found_cmake" = true ] && echo "yes" || echo "no" + printf "Found Ccache: "; [ "$found_ccache" = true ] && echo "yes" || echo "no" + printf "Found Ninja: "; [ "$found_ninja" = true ] && echo "yes" || echo "no" + printf "Found Python: "; [ "$found_python" = true ] && echo "yes" || echo "no" + printf "Found Cppcheck: "; [ "$found_cppcheck" = true ] && echo "yes" || echo "no" + printf "Found clang-tidy: "; [ "$found_clangtidy" = true ] && echo "yes" || echo "no" + printf "Found clang-format: "; [ "$found_clangformat" = true ] && echo "yes" || echo "no" + + [ "$found_cmake" = true ] || { echo "Error: CMake must be installed"; return 1; } +} + +# Workaround: Disable tests in excluded subdirectories +# See: https://gitlab.kitware.com/cmake/cmake/-/issues/20212 +cmake_disable_excluded_tests() { + declare build_dir="$1" + + [ ! -f "$build_dir/$CTEST_FILENAME" ] || sed -i.bak 's/^subdirs/# subdirs/' "$build_dir/$CTEST_FILENAME" +} + +configure() { + declare build_dir="$1" + + ohai "Configure" + + declare -a defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON) + defs+=(-DCMAKE_CXX_FLAGS=-fdiagnostics-color=always) + [ "$found_ccache" = true ] && defs+=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache) + [ "$config_debug" = true ] && defs+=(-DCMAKE_BUILD_TYPE=Debug) || defs+=(-DCMAKE_BUILD_TYPE=Release) + [ "$config_verbose" = true ] && defs+=(-DCMAKE_VERBOSE_MAKEFILE=ON) + + declare gen + [ "$found_ninja" = true ] && gen=-GNinja || gen=-G"Unix Makefiles" + + cmake -B"$build_dir" "${defs[@]}" "$gen" "$source_dir" || return + + { [ "$config_debug" = true ] && touch "$build_dir/$DEBUG_FILENAME"; } || rm -f "$build_dir/$DEBUG_FILENAME" + { [ "$config_verbose" = true ] && touch "$build_dir/$VERBOSE_FILENAME"; } || rm -f "$build_dir/$VERBOSE_FILENAME" +} + +check_configured() { + declare build_dir="$1" + + declare to_reconfigure=false + if [ ! -d "$build_dir" ]; then + to_reconfigure=true + elif [ ! -f "$build_dir/$CMAKE_FILENAME" ]; then + rm -rf "$build_dir" + to_reconfigure=true + else + [ -f "$build_dir/$DEBUG_FILENAME" ] && found_debug=true || found_debug=false + [ -f "$build_dir/$VERBOSE_FILENAME" ] && found_verbose=true || found_verbose=false + if [ "$config_debug" != "$found_debug" ] \ + || [ "$config_verbose" != "$found_verbose" ]; then + to_reconfigure=true + fi + fi + + if [ "$to_reconfigure" = true ]; then + configure "$build_dir" + fi +} + +build() { + declare build_dir="$1" + declare target="$2" + + check_configured "$build_dir" || return + + ohai "Build" + + declare -a opts + get_build_opts opts + + cmake --build "$build_dir" "${opts[@]}" --target "$target" +} + +build_all() { + declare build_dir="$build_dir" + + build "$build_dir" all +} + +clean() { + declare build_desc="$1" + declare build_dir="$2" + + ohai "Clean ($build_desc)" + + if [ -f "$build_dir/$CMAKE_FILENAME" ]; then + declare -a opts + get_build_opts opts + + cmake --build "$build_dir" "${opts[@]}" --target clean + fi + + echo "Removing build folder..." + rm -rf "$build_dir" +} + +clean_all() { + clean "Default" "$build_dir" + clean "Host" "$build_host_dir" +} + +run() { + declare build_dir="$1" + declare target="$2" + + ohai "Run" + + $build_dir/$target +} + +list() { + declare build_dir="$build_dir" + + check_configured "$build_dir" || return + + ohai "List targets" + + declare -a opts + get_build_opts opts + + echo "[1/1] All SBS targets available:" + cmake --build "$build_dir" "${opts[@]}" --target help \ + | grep -Po '^.*/\K\w*(?=_autogen)' | uniq +} + +lint_copyright() { + ohai "Lint (Copyright)" + + "$sbs_base/scripts/linter.py" --copyright "$source_dir/src" +} + +lint_find() { + ohai "Lint (Find)" + + "$sbs_base/scripts/linter.py" --find "$source_dir/src" +} + +lint_clangtidy() { + declare build_dir="$1" + + check_configured "$build_dir" || return + + ohai "Lint (clang-tidy)" + + defs=(--extra-arg=-D_MIOSIX=1 --extra-arg=-D_MIOSIX_GCC_PATCH_MINOR=1 \ + --extra-arg=-D_MIOSIX_GCC_PATCH_MAJOR=3 --extra-arg=-D__LINT__) + IFS=$'\n' read -rd '' -a incs < \ + <(arm-miosix-eabi-g++ -E -Wp,-v -xc++ /dev/null 2>&1 \ + | sed -n "s/^ /--extra-arg=-isystem/p") + + declare opts=() + [ "$to_edit" = true ] && opts+=(--fix-notes --fix-errors) + + find "$source_dir/src" \ + -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \ + -exec clang-tidy --header-filter=".*" -p="$build_dir" "${defs[@]}" \ + "${incs[@]}" "${opts[@]}" {} \; +} + +lint_cppcheck() { + ohai "Lint (Cppcheck)" + + declare -a opts=() + [ -n "$jobs" ] && opts+=("-j $jobs") + + cppcheck --language=c++ --std=c++11 --enable=all --inline-suppr \ + --suppress=unmatchedSuppression --suppress=unusedFunction \ + --suppress=missingInclude --error-exitcode=1 "${opts[@]}" \ + "$source_dir/src" +} + +lint_clangformat() { + declare to_edit="$1" + + ohai "Lint (clang-format)" + + declare -a opts=(--style=file --Werror) + [ "$to_edit" = true ] && opts+=(-i) || opts+=(--dry-run) + + find "$source_dir/src" \ + -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \ + -exec clang-format "${opts[@]}" {} \; +} + +lint() { + declare to_edit="$1" + + if [ "$found_python" = true ]; then + lint_copyright + lint_find + fi + + if [ "$found_clangtidy" = true ] && [ "$lint_clangtidy" = true ]; then + lint_clangtidy "$build_dir" + fi + + if [ "$found_cppcheck" = true ]; then + lint_cppcheck + fi + + if [ "$found_clangformat" = true ]; then + lint_clangformat "$to_edit" + fi +} + +set_debug() { + config_debug=true +} + +set_verbose() { + config_verbose=true +} + +set_jobs() { + jobs="$1" +} + +get_build_opts() { + declare -n build_opts=$1 + [ -n "$jobs" ] && build_opts=("-j $jobs") +} + +set_clangtidy() { + lint_clangtidy=true +} + +usage() { + echo + cat <<EOF +Usage: $(basename "$0") [OPTIONS] + +OPTIONS: + General Options: + -h, --help Show this help message and exit + -j JOBS, --jobs JOBS Build or lint in parallel using a specific number of jobs + -l, --list List all targets available + + Build Options: + -b TARGET, --build TARGET + Build a specific target + -r TARGET, --run TARGET + Build and run a specific target + -c, --clean Clean the working tree + -u, --configure Force configure and do not build + -d, --debug Enable debug + -v, --verbose Print a verbose output + + Lint Options: + -n, --lint Lint the code + -e, --edit Lint and edit the code + --clang-tidy Lint using also clang-tidy +EOF +} + +CMAKE_FILENAME="CMakeCache.txt" +CMAKE_PREFIX_PATH="~/Qt/5.15.2/gcc_64/lib/cmake/" +DEBUG_FILENAME=".sbsdebug" +VERBOSE_FILENAME=".sbsverbose" +BUILD_DIRNAME="build" +TTY_BLUE="\033[34m" +TTY_BOLD="\033[1m" +TTY_RESET="\033[0m" + +sbs_base= +source_dir= +build_dir= +build_host_dir= +toolchain_file= +found_cmake=false +found_miosixgpp=false +found_ccache=false +found_ninja=false +found_python=false +found_cppcheck=false +found_clangtidy=false +found_clangformat=false +found_stflash=false +found_stlink=false +config_debug=false +config_verbose=false +jobs= +lint_clangtidy=false + +print_banner +init_dirs + +for arg in "$@"; do + shift + case "$arg" in + --build) set -- "$@" "-b";; + --clang-tidy) set_clangtidy;; + --clean) set -- "$@" "-c";; + --configure) set -- "$@" "-u";; + --debug) set -- "$@" "-d";; + --edit) set -- "$@" "-e";; + --run) set -- "$@" "-r";; + --help) set -- "$@" "-h";; + --jobs) set -- "$@" "-j";; + --lint) set -- "$@" "-n";; + --list) set -- "$@" "-l";; + --test) set -- "$@" "-t";; + --verbose) set -- "$@" "-v";; + *) set -- "$@" "$arg" + esac +done + +while getopts b:cder:hj:lnt:uv opt; do + case "$opt" in + b) find_deps && build "$build_dir" "$OPTARG"; exit;; + c) find_deps && clean_all; exit;; + d) set_debug;; + e) find_deps && lint true; exit;; + r) find_deps && build "$build_dir" "$OPTARG" && run "$build_dir" "$OPTARG"; exit;; + h) usage; exit 0;; + j) set_jobs "$OPTARG";; + l) find_deps && list; exit;; + n) find_deps && lint false; exit;; + t) find_deps && run_tests "$OPTARG"; exit;; + u) find_deps && configure "$build_dir"; exit;; + v) set_verbose;; + ?) usage; exit 2;; + esac +done +shift $((OPTIND - 1)) + +find_deps && build_all diff --git a/scripts/linter.py b/scripts/linter.py new file mode 100755 index 0000000000000000000000000000000000000000..a0c8297311cc4c8ccd1331675cc38081a1dc40b2 --- /dev/null +++ b/scripts/linter.py @@ -0,0 +1,335 @@ +#!/usr/bin/python3 + +# Copyright (c) 2021 Skyward Experimental Rocketry +# Author: Alberto Nidasio +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# The linter script offers a lot of functionalities: +# - Checks for the right copyright notice in all .h and .cpp files +# - Checks if all .h and .cpp files respects the clang format specified in .clang-format file +# - Checks if some .h or .cpp files have printfs, asserts or some string used in them + +import re +from collections import Counter +from subprocess import DEVNULL, STDOUT, call, run, check_output, CalledProcessError +from argparse import ArgumentParser, RawTextHelpFormatter +from os import pathconf, walk +from os.path import join, isdir + +# Copyright template for C++ code files and headers +CPP_TEMPLATE = r'\/\* Copyright \(c\) 20\d\d(?:-20\d\d)? Skyward Experimental Rocketry\n' + \ + r' \* (Authors?): (.+)\n' + \ + r' \*\n' + \ + r' \* Permission is hereby granted, free of charge, to any person obtaining a copy\n' + \ + r' \* of this software and associated documentation files \(the "Software"\), to deal\n' + \ + r' \* in the Software without restriction, including without limitation the rights\n' + \ + r' \* to use, copy, modify, merge, publish, distribute, sublicense, and\/or sell\n' + \ + r' \* copies of the Software, and to permit persons to whom the Software is\n' + \ + r' \* furnished to do so, subject to the following conditions:\n' + \ + r' \*\n' + \ + r' \* The above copyright notice and this permission notice shall be included in\n' + \ + r' \* all copies or substantial portions of the Software.\n' + \ + r' \*\n' + \ + r' \* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n' + \ + r' \* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n' + \ + r' \* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n' + \ + r' \* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n' + \ + r' \* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n' + \ + r' \* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n' + \ + r' \* THE SOFTWARE.\n' + \ + r' \*\/' +AUTHOR_DELIMITER = ',' + + +def config_cmd_parser(): + parser = ArgumentParser( + description='The linter script offers a lot of functionalities but it does not modify any file. If no option is specified everything will be checked.', + formatter_class=RawTextHelpFormatter) + parser.add_argument('directory', nargs='?', + help='Directory where to search files') + parser.add_argument( + '--copyright', dest='copyright', + action='store_true', help='Checks for the right copyright notice in all .h and .cpp files') + parser.add_argument( + '--format', dest='format', + action='store_true', help='Checks if all .h and .cpp files respects the clang format specified in .clang-format file') + parser.add_argument( + '--find', dest='find', + action='store_true', help='Checks if some .h or .cpp files have printfs, asserts or some string used in them') + parser.add_argument( + '--cppcheck', dest='cppcheck', + action='store_true', help='Runs cppcheck for static code analysis') + parser.add_argument('-q', '--quiet', dest='quiet', + action='store_true', help='Output only essential messages') + return parser + + +def print_banner(): + # Font: Ivrit + print('+------------------------------+') + print('| _ _ _ |') + print('| | | (_)_ __ | |_ ___ _ __ |') + print('| | | | | \'_ \| __/ _ \ \'__| |') + print('| | |___| | | | | || __/ | |') + print('| |_____|_|_| |_|\__\___|_| |') + print('+------------------------------+') + + +def linter_print(*strings): + print('[linter]', *strings) + + +class Colors(): + BLACK = '\033[30m' + RED = '\033[31m' + GREEN = '\033[32m' + YELLOW = '\033[33m' + BLUE = '\033[34m' + MAGENTA = '\033[35m' + CYAN = '\033[36m' + WHITE = '\033[37m' + UNDERLINE = '\033[4m' + RESET = '\033[0m' + + +# Checks for the right copyright notice in all .h and .cpp files +def check_copyright(directory): + linter_print(Colors.GREEN + 'Copyright check' + Colors.RESET) + + # Statistics + totalCheckdFilesCounter = 0 + filesWithErrorsCounter = 0 + authors = {} + averageAuthorsPerFile = 0 + + # Walk through the directory and check each file + for dirpath, dirnames, filenames in walk(directory): + for filename in [f for f in filenames if f.endswith(('.cpp', '.h'))]: + totalCheckdFilesCounter += 1 + + # Prepare the complete filepath + currentFilepath = join(dirpath, filename) + + # Check the current file + with open(currentFilepath) as file: + match = re.search(CPP_TEMPLATE, file.read()) + if not match: + filesWithErrorsCounter += 1 + + # The file's copyright notice does not match the template! + linter_print(Colors.YELLOW + 'Wrong copyright notice in file {0}'.format( + currentFilepath) + Colors.RESET) + else: + fileAuthors = [a.strip() + for a in match.group(2).split(AUTHOR_DELIMITER)] + + # Check the number of authors against 'Author' or `Authors` + if len(fileAuthors) == 1 and match.group(1)[-1] == 's': + linter_print('\'Authors\' should to be changed to \'Author\' in {0}'.format( + currentFilepath)) + if len(fileAuthors) > 1 and match.group(1)[-1] != 's': + linter_print('\'Author\' should to be changed to \'Authors\' in {0}'.format( + currentFilepath)) + + # Save statistics on authors + for author in fileAuthors: + if author in authors: + authors[author] += 1 + else: + authors[author] = 1 + averageAuthorsPerFile += len(fileAuthors) + averageAuthorsPerFile /= totalCheckdFilesCounter + + linter_print('Checked {} files'.format(totalCheckdFilesCounter)) + if filesWithErrorsCounter == 0: + linter_print('All the files have the correct copyright notice') + else: + linter_print(Colors.RED + '{:.1f}% ({}/{}) of all analyzed files do not match with the copyright template!'.format( + 100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter) + Colors.RESET) + + if(not args.quiet): + linter_print('{:.2} authors per file'.format( + averageAuthorsPerFile)) + linter_print('Number of mentions per author:') + for author in authors: + linter_print('{:3} - {}'.format(authors[author], author)) + + # Exit if error if at least one file isn't correct + if(filesWithErrorsCounter > 0): + exit(-1) + + +# Checks if all .h and .cpp files respects the clang format specified in .clang-format file +def check_format(directory): + linter_print(Colors.GREEN + 'Formatting check' + Colors.RESET) + + # Statistics + totalCheckdFilesCounter = 0 + filesWithErrorsCounter = 0 + + # Walk throgh the directory and check each file + for dirpath, dirnames, filenames in walk(directory): + for filename in [f for f in filenames if f.endswith(('.cpp', '.h', 'c'))]: + totalCheckdFilesCounter += 1 + + # Prepare the complete filepath + currentFilepath = join(dirpath, filename) + + # Dry run clang-format and check if we have an error + returnCode = call( + ['clang-format', '-style=file', '--dry-run', '--Werror', '--ferror-limit=1', currentFilepath], stderr=DEVNULL) + + # If and error occurs warn the user + if(returnCode != 0): + filesWithErrorsCounter += 1 + + if(not args.quiet): + linter_print(Colors.YELLOW + 'Wrong code format for file {1}'.format( + returnCode, currentFilepath) + Colors.RESET) + + linter_print('Checked {} files'.format(totalCheckdFilesCounter)) + if filesWithErrorsCounter == 0: + linter_print('All the files match the Skyward formatting style') + else: + linter_print(Colors.RED + '{:4.1f}% ({}/{}) of all analyzed files do not match Skyward formatting style!'.format( + 100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter), Colors.RESET) + + # Exit if error if at least one file isn't correct + if(filesWithErrorsCounter > 0): + exit(-1) + + +def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFilter=None): + linter_print( + Colors.GREEN + 'Checking for \'{}\' in code files'.format(searchTerm) + Colors.RESET) + + # Statistics + totalCheckdFilesCounter = 0 + filesWithErrorsCounter = 0 + + # Walk through the directory and check each file + for dirpath, dirnames, filenames in walk(directory): + for filename in [f for f in filenames if f.endswith(extensionFilters) and (not pathFilter or dirpath.find(pathFilter) >= 0)]: + totalCheckdFilesCounter += 1 + + # Prepare the complete filepath + currentFilepath = join(dirpath, filename) + + # Check the current file + with open(currentFilepath) as file: + fileContent = file.read() + # Check for linter off flag + if re.search('linter off', fileContent, re.M): + continue + match = re.search(searchTerm, fileContent, re.M) + if match: + filesWithErrorsCounter += 1 + + # The current file has the error + if(not args.quiet): + linter_print(Colors.YELLOW + 'Found \'{}\' in file {}'.format(searchTerm, + currentFilepath) + Colors.RESET) + + linter_print('Checked {} files'.format(totalCheckdFilesCounter)) + if filesWithErrorsCounter == 0: + linter_print( + 'All the files does not contain \'{}\''.format(searchTerm)) + else: + linter_print(Colors.RED + '{:.1f}% ({}/{}) of all analyzed files contain \'{}\'!'.format( + 100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter, searchTerm) + Colors.RESET) + + return filesWithErrorsCounter + + +def check_find(directory): + sum = find_in_code(directory, r'^using namespace', '.h') + sum += find_in_code(directory, r'[^a-zA-Z0-9]printf\(', pathFilter='shared') + sum += find_in_code(directory, r'( |^)assert\(') + sum += find_in_code(directory, '^ *throw ', pathFilter='catch') + + if sum > 0: + exit(-1) + + +def check_cppcheck(directory): + linter_print(Colors.GREEN + 'cppcheck' + Colors.RESET) + # Run cppcheck on the directory + try: + result = check_output(['cppcheck', '-q', '--language=c++', '--template=gcc', '--std=c++11', '--enable=all', '--inline-suppr', + '--suppress=unmatchedSuppression', '--suppress=unusedFunction', '--suppress=missingInclude', + directory], stderr=STDOUT) + + # Parse results and count errors + errors = re.findall(r'\[(\w+)\]', result.decode('utf-8')) + errors = Counter(errors) + + if(not args.quiet): + linter_print('cppcheck found the following errors:') + for error in errors: + linter_print('{:3} - {}'.format(errors[error], error)) + + totalErrors = sum(errors.values()) + if(totalErrors > 0): + linter_print( + Colors.RED + 'cppcheck found {} errors in total'.format(totalErrors) + Colors.RESET) + exit(-1) + else: + linter_print('cppcheck did not find any errors') + + except CalledProcessError as e: + linter_print(e.output.decode('utf-8')) + exit(-1) + +# ------------------------------------------------------------- +# MAIN +# ------------------------------------------------------------- + + +parser = config_cmd_parser() +args = parser.parse_args() + +if(not args.directory): + linter_print('No directory specified') + print('') + parser.print_help() + exit(-1) + +if(not isdir(args.directory)): + linter_print('The specified directory does not exists') + exit(-1) + +if(args.copyright): + check_copyright(args.directory) + +if(args.format): + check_format(args.directory) + +if(args.cppcheck): + check_cppcheck(args.directory) + +if(args.find): + check_find(args.directory) + +# Checks everything if no option is specified +if(not args.copyright and not args.format and not args.find and not args.cppcheck): + check_copyright(args.directory) + check_format(args.directory) + check_find(args.directory) + check_cppcheck(args.directory)