diff --git a/.gitignore b/.gitignore index b343c6b2757c76d96ce39f08c5d2e62e0fe30fc5..8077771daa9fc897842910eedac0a0756f99a6a1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ STM32F429zi_Discovery.xml ._* .DS_Store build +build-host cmake-build-* *.sublime-workspace *.sublime-project diff --git a/cmake/sbs.cmake b/cmake/sbs.cmake index 7cd5a66c2c8cf0578c4ecaa613577d82498a1a14..dc56212db454cba1f02a9945ec405ff1ceb543e2 100644 --- a/cmake/sbs.cmake +++ b/cmake/sbs.cmake @@ -28,15 +28,6 @@ cmake_path(GET CMAKE_CURRENT_LIST_DIR PARENT_PATH SBS_BASE) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) include(boardcore) -# Command to print all the available boards used by the sbs script -string(REPLACE ";" "\\n" BOARDS_STR "${MIOSIX_BOARDS};${BOARDCORE_BOARDS}") -add_custom_target( - help-boards - COMMAND printf ${BOARDS_STR} - COMMENT "All boards available:" - VERBATIM -) - # Function to link the Boardcore library to the target function(sbs_target TARGET OPT_BOARD) if(NOT OPT_BOARD) diff --git a/sbs b/sbs index f867e996023a6212e05367413329cbe40ce40a03..bdf753882f2a1461e070f5e59d8ce3251052b6e8 100755 --- a/sbs +++ b/sbs @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Copyright (c) 2021 Skyward Experimental Rocketry -# Author: Damiano Amatruda +# Copyright (c) 2024 Skyward Experimental Rocketry +# Author: Davide Basso # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -21,38 +21,235 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -print_banner() { - cat <<EOF -+---------------------------------------------------------------+ -| ____ _ _ | -| / ___|| | ___ ___ ____ _ _ __ __| | | -| \\___ \\| |/ / | | \\ \\ /\\ / / _\` | '__/ _\` | | -| ___) | <| |_| |\\ V V / (_| | | | (_| | | -| |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_| \\__,_| | -| ____ |___/ _ ____ _ | -| | __ ) _ _(_) | __| | / ___| _ _ ___| |_ ___ _ __ ___ | -| | _ \\| | | | | |/ _\` | \\___ \\| | | / __| __/ _ \\ '_ \` _ \\ | -| | |_) | |_| | | | (_| | ___) | |_| \\__ \\ || __/ | | | | | | -| |____/ \\__,_|_|_|\\__,_| |____/ \\__, |___/\\__\\___|_| |_| |_| | -+----------------------------------|___/-------------------v3.3-+ -EOF +################################################################################ +############################## Constants ############################## +################################################################################ + +# Terminal colors +TTY_RESET="\033[0m" +TTY_BOLD="\033[1m" +TTY_FOUND="\033[38;5;200m" +TTY_SUCCESS="\033[38;5;40m" +TTY_ERROR="\033[38;5;160m" + +TTY_LOGO_1="\033[38;5;200m" +TTY_LOGO_2="\033[38;5;164m" +TTY_LOGO_3="\033[38;5;93m" +TTY_LOGO_4="\033[38;5;57m" +TTY_LOGO_5="\033[38;5;27m" + +# Error message +ERR="\n"$TTY_ERROR""$TTY_BOLD"ERROR"$TTY_RESET"" + +# Filenames/Dirnames +CMAKE_FILENAME="CMakeCache.txt" +CTEST_FILENAME="CTestTestfile.cmake" +DEBUG_FILENAME=".sbs_debug" +VERBOSE_FILENAME=".sbs_verbose" +BUILD_DEFAULT_DIRNAME="build" +BUILD_HOST_DIRNAME="build-host" + +################################################################################ +############################## Global States ############################## +################################################################################ + +sbs_base= +source_dir= +build_default_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 + +################################################################################ +############################## Banner ############################## +################################################################################ + +banner=" +$TTY_LOGO_1 _____ ____ _____ +$TTY_LOGO_2 ___________ / ___// __ ) ___/ ________________________________ +$TTY_LOGO_3 __________ \__ \/ __ \__ \ _________________________________ +$TTY_LOGO_4 _________ ___/ / /_/ /__/ / ____________________________v4.0__ +$TTY_LOGO_5 /____/_____/____/ +$TTY_RESET +" + +print_configuration() { + step "Dependencies" + + yf=""$TTY_SUCCESS""$TTY_BOLD"Yes"$TTY_RESET"" + nf=""$TTY_ERROR""$TTY_BOLD"No"$TTY_RESET"" + + printf "cmake: "; [ "$found_cmake" = true ] && printf "$yf\n" || printf "$nf\n" + printf "arm-miosix-eabi-g++: "; [ "$found_miosixgpp" = true ] && printf "$yf\n" || printf "$nf\n" + printf "ccache: "; [ "$found_ccache" = true ] && printf "$yf\n" || printf "$nf\n" + printf "ninja: "; [ "$found_ninja" = true ] && printf "$yf\n" || printf "$nf\n" + printf "python: "; [ "$found_python" = true ] && printf "$yf\n" || printf "$nf\n" + printf "cppcheck: "; [ "$found_cppcheck" = true ] && printf "$yf\n" || printf "$nf\n" + printf "clang-tidy: "; [ "$found_clangtidy" = true ] && printf "$yf\n" || printf "$nf\n" + printf "clang-format: "; [ "$found_clangformat" = true ] && printf "$yf\n" || printf "$nf\n" + + printf "flasher: "; + if [ "$found_stflash" = true ]; then + printf ""$TTY_FOUND""$TTY_BOLD"st-flash"$TTY_RESET"\n" + elif [ "$found_stlink" = true ]; then + printf ""$TTY_FOUND""$TTY_BOLD"st-link"$TTY_RESET"\n" + else + printf "$nf\n" + fi + + [ "$found_cmake" = true ] || { printf ""$ERR": CMake must be installed\n"; return 1; } + [ "$found_miosixgpp" = true ] || { printf ""$ERR": arm-miosix-eabi-g++ must be installed\n"; return 1; } } -ohai() { - printf "\n${TTY_BLUE}==>${TTY_RESET}${TTY_BOLD} %s${TTY_RESET}\n" "$@" +################################################################################ +############################## CLI ############################## +################################################################################ + +# Filter all the arguments that are flags +# The syntax is: filter_flags "arg1" "arg2" ... "argN" +# Returns a list of flags in the form "--flag=value" or "--flag" +filter_flags() { + flags=() + prev_is_flag=false + + for arg in "$@"; do + if [[ "$arg" == -* ]]; then + flags+=("$arg") + prev_is_flag=true + else + if [ "$prev_is_flag" = true ]; then + # if the previous argument was a flag, then this is a flag value + flags[-1]="${flags[-1]}=$arg" + fi + + prev_is_flag=false + fi + done + + echo "${flags[@]}" } -init_dirs() { - sbs_base="$(cd -- "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)" - source_dir="$PWD" - build_default_dir="$source_dir/$BUILD_DEFAULT_DIRNAME" - build_host_dir="$source_dir/$BUILD_HOST_DIRNAME" - toolchain_file="$sbs_base/libs/miosix-kernel/miosix/cmake/toolchain.cmake" + +# Filter all the arguments that are not flags +filter_args() { + args=() + prev_is_flag=false + + for arg in "$@"; do + if [[ "$arg" == -* ]]; then + prev_is_flag=true + elif [ "$prev_is_flag" = true ]; then + prev_is_flag=false + else + args+=("$arg") + fi + done + + echo "${args[@]}" } -find_deps() { - ohai "Find dependencies" - +# Checks that no unknown flags are passed +# The syntax is: cmd_flags "known_flags" "flags" +cmd_flags() { + known_flags=("${!1}") + flags=("${!2}") + + for flag in "${flags[@]}"; do + # ignore '' + if [ -z "$flag" ]; then + continue + fi + + found=false + for known_flag in "${known_flags[@]}"; do + # if the know_flag is of type --flag=, allow flags with value + if [[ "$known_flag" == *= ]]; then + if [[ "$flag" == "$known_flag"* ]]; then + found=true + break + fi + else + if [ "$flag" == "$known_flag" ]; then + found=true + break + fi + fi + done + + if [ "$found" = false ]; then + printf ""$ERR": Unknown flag $flag\n" + return 1 + fi + done +} + +# Checks that no more than a certain number of arguments are passed +# The syntax is: cmd_args "max_args" "arg1" "arg2" ... "argN" +cmd_args() { + max_args=$1 + shift + args=("${!1}") + if [ "${#args[@]}" -gt "$max_args" ]; then + printf ""$ERR": Too many arguments\n" + return 1 + fi +} + +# Checks if a flag exists in the list of flags +# The syntax is: flag_exists "flag" "flag1" "flag2" ... "flagN" +flag_exists() { + flag=$1 + shift + flags=("${!1}") + + if [[ " ${flags[@]} " =~ " ${flag} " ]]; then + echo "true" + else + echo "false" + fi +} + +# Get the value of a flag +# The syntax is: get_flag_value "possible_flags" "flags" +get_flag_value() { + possible_flags=("${!1}") + flags=("${!2}") + + for flag in "${flags[@]}"; do + for possible_flag in "${possible_flags[@]}"; do + if [[ "$flag" == "$possible_flag"* ]]; then + # strip the value from the flag + echo "${flag#*=}" + return + fi + done + done +} + +# Print a step message +step() { + printf "\n"$TTY_LOGO_5""$TTY_BOLD"$1"$TTY_RESET"\n" + # Print - for every letter in the pre string + for (( i=0; i<${#1}; i++ )); do printf -- "-"; done; + printf "\n"; +} + +################################################################################ +############################## Internals ############################## +################################################################################ + +# Find all the dependencies +find_deps() { command -v cmake > /dev/null 2>&1 && found_cmake=true command -v arm-miosix-eabi-g++ > /dev/null 2>&1 && found_miosixgpp=true command -v ccache > /dev/null 2>&1 && found_ccache=true @@ -63,57 +260,51 @@ find_deps() { command -v clang-format > /dev/null 2>&1 && found_clangformat=true command -v st-flash > /dev/null 2>&1 && found_stflash=true command -v ST-LINK_CLI.exe > /dev/null 2>&1 && found_stlink=true - - printf "Found CMake: "; [ "$found_cmake" = true ] && echo "yes" || echo "no" - printf "Found arm-miosix-eabi-g++: "; [ "$found_miosixgpp" = 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" - printf "Found flasher: "; [ "$found_stflash" = true ] && echo "st-flash" \ - || { [ "$found_stlink" = true ] && echo "st-link" || echo "no"; } - - [ "$found_cmake" = true ] || { echo "Error: CMake must be installed"; return 1; } - [ "$found_miosixgpp" = true ] || { echo "Error: arm-miosix-eabi-g++ must be installed"; return 1; } } -# Workaround: Disable tests in excluded subdirectories -# See: https://gitlab.kitware.com/cmake/cmake/-/issues/20212 +# Initialize all the directories +init_dirs() { + sbs_base="$(cd -- "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)" + source_dir="$PWD" + build_default_dir="$source_dir/$BUILD_DEFAULT_DIRNAME" + build_host_dir="$source_dir/$BUILD_HOST_DIRNAME" + toolchain_file="$sbs_base/libs/miosix-kernel/miosix/cmake/toolchain.cmake" +} + +# Get the build options for cmake +# The syntax is: get_build_opts "opts" "jobs" +get_build_opts() { + build_opts=() + jobs=$2 + + if [ -n "$jobs" ]; then + echo "- Building with $jobs jobs" + build_opts+=("-j $jobs") + fi +} + +# workaround: disable tests in excluded subdirectories +# see: https://gitlab.kitware.com/cmake/cmake/-/issues/20212 cmake_disable_excluded_tests() { - declare build_dir="$1" - + build_dir="$build_host_dir" + [ ! -f "$build_dir/$CTEST_FILENAME" ] || sed -i.bak 's/^subdirs/# subdirs/' "$build_dir/$CTEST_FILENAME" } -configure() { - declare build_dir="$1" - - ohai "Configure" - - [ -f "$toolchain_file" ] || { echo "Error: CMake Toolchain File for Miosix was not found"; return 1; } - - declare -a defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON) - defs+=(-DCMAKE_C_FLAGS=-fdiagnostics-color=always -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always) - [ "$config_host" = false ] && defs+=(-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" -DBUILD_TESTING=OFF) - [ "$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" -} +################################################################################ +############################## Subcommands ############################## +################################################################################ +# Check if the project is configured +# The syntax is: check_configured "config_debug" "config_verbose" "config_host" check_configured() { - declare build_dir="$1" + build_dir="$1" + config_debug="$2" + config_verbose="$3" + config_host="$4" + + to_reconfigure=false - declare to_reconfigure=false if [ ! -d "$build_dir" ]; then to_reconfigure=true elif [ ! -f "$build_dir/$CMAKE_FILENAME" ]; then @@ -129,318 +320,436 @@ check_configured() { fi if [ "$to_reconfigure" = true ]; then - configure "$build_dir" + step "Configure" + + printf "Reconfiguring project with the following options:\n" + printf " - Debug: %s\n" "$config_debug" + printf " - Verbose: %s\n" "$config_verbose" + printf " - Host: %s\n" "$config_host" + + [ -f "$toolchain_file" ] || { printf ""$ERR": CMake Toolchain File for Miosix was not found\n"; return 1; } + + declare -a defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON) + defs+=(-DCMAKE_C_FLAGS=-fdiagnostics-color=always -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always) + [ "$config_host" = false ] && defs+=(-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" -DBUILD_TESTING=OFF) + [ "$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" fi } -build() { - declare build_dir="$1" - declare target="$2" - - check_configured "$build_dir" || return +lint_copyright() { + step "Lint copyright" + + "$sbs_base/scripts/linter.py" --copyright "$source_dir/src" +} + +lint_find() { + step "Lint find" + + "$sbs_base/scripts/linter.py" --find "$source_dir/src" +} + +lint_clangtidy() { + build_dir="$1" - ohai "Build" + step "Lint clang-tidy" + + config_debug=false + config_verbose=false + + 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 -a opts - get_build_opts opts + declare opts=() + [ "$to_edit" = true ] && opts+=(--fix-notes --fix-errors) - cmake --build "$build_dir" "${opts[@]}" --target "$target" + 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[@]}" {} \; } -build_all() { - declare build_dir="$build_default_dir" - - build "$build_dir" all +lint_cppcheck() { + step "Lint cppcheck" + + echo "Running cppcheck..." + + cppcheck --language=c++ --std=c++14 --enable=all --inline-suppr \ + --suppress=unmatchedSuppression --suppress=unusedFunction \ + --suppress=missingInclude --error-exitcode=1 -q \ + "$source_dir/src" } -clean() { - declare build_desc="$1" - declare build_dir="$2" - - ohai "Clean ($build_desc)" +lint_clangformat() { + step "Lint clang-format" + + echo "Running clang-format..." + + opts=(--style=file --Werror --dry-run) + + find "$source_dir/src" \ + -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \ + -exec clang-format "${opts[@]}" {} \; +} + +################################################################################ +############################## Native Commands ############################## +################################################################################ + +build() { + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + jobs_flag=("-j" "--jobs") + known_flags=("-d" "-v" "-j=" "--debug" "--verbose" "--jobs=") + + cmd_flags known_flags[@] flags[@] || return + cmd_args 1 args[@] || return + + jobs=$(get_flag_value jobs_flag[@] flags[@]) + + [ "$(flag_exists "-d" flags[@])" = true ] || [ "$(flag_exists "--debug" flags[@])" = true ] && config_debug=true || config_debug=false + [ "$(flag_exists "-v" flags[@])" = true ] || [ "$(flag_exists "--verbose" flags[@])" = true ] && config_verbose=true || config_verbose=false + config_host=false + + build_dir="$build_default_dir" + + target=${args[0]:-all} + + check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return - if [ -f "$build_dir/$CMAKE_FILENAME" ]; then - declare -a opts - get_build_opts opts - - cmake --build "$build_dir" "${opts[@]}" --target clean - fi + step "Build" + + declare opts + get_build_opts opts $jobs - echo "Removing build folder..." - rm -rf "$build_dir" + cmake --build "$build_dir" "${opts[@]}" --target "$target" } -clean_all() { - clean "Default" "$build_default_dir" - clean "Host" "$build_host_dir" +clean() { + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + known_flags=() + + step "Clean" + cmd_flags known_flags[@] flags[@] || return + cmd_args 0 args[@] || return + + echo "Cleaning build directories..." + rm -rf "$build_default_dir" + rm -rf "$build_host_dir" } flash() { - declare target="$1" - declare build_dir="$build_default_dir" + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + jobs_flag=("-j" "--jobs") + known_flags=("-d" "-v" "-r" "-j=" "--debug" "--verbose" "--reset" "--jobs=") + + cmd_flags known_flags[@] flags[@] || return + cmd_args 1 args[@] || return + + build_dir="$build_default_dir" + + jobs=$(get_flag_value jobs_flag[@] flags[@]) + + [ "$(flag_exists "-d" flags[@])" = true ] || [ "$(flag_exists "--debug" flags[@])" = true ] && config_debug=true || config_debug=false + [ "$(flag_exists "-v" flags[@])" = true ] || [ "$(flag_exists "--verbose" flags[@])" = true ] && config_verbose=true || config_verbose=false + [ "$(flag_exists "-r" flags[@])" = true ] || [ "$(flag_exists "--reset" flags[@])" = true ] && reset=true || reset=false + config_host=false + + target=${args[0]} + + if [ -z "$target" ]; then + printf ""$ERR": No target specified\n" + return 1 + fi + + check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return - build "$build_dir" "$target" || return + # build the target + step "Build" + + declare opts + get_build_opts opts $jobs - ohai "Flash" + cmake --build "$build_dir" "${opts[@]}" --target "$target" - [ -f "$build_dir/$target.bin" ] || { echo "Error: target '$target' is not flashable"; return 1; } + # check if the target is flashable + [ -f "$build_dir/$target.bin" ] || { printf ""$ERR": target '$target' is not flashable"; return 1; } + # flash the target + step "Flash" + + flash_opts=() + [ "$reset" = true ] && flash_opts+=("--connect-under-reset") + if [ "$found_stflash" = true ]; then - st-flash --reset write "$build_dir/$target.bin" 0x8000000 - elif [ "$found_stlink" = true ]; then + st-flash --reset "${flash_opts[@]}" write "$build_dir/$target.bin" 0x8000000 + elif [ "$found_stlink" = true ]; then ST-LINK_CLI.exe -P "$build_dir/$target.bin" 0x8000000 -V -Rst else - echo "Error: No flashing software found!" + printf ""$ERR": No flashing software found!\n" return 1 fi } -run_tests() { - declare target="$1" - declare build_dir="$build_host_dir" - +test() { + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + jobs_flag=("-j" "--jobs") + known_flags=("-j=" "--jobs=") + + cmd_flags know_flags[@] flags[@] || return + cmd_args 1 args[@] || return + + config_debug=true + config_verbose=false config_host=true + build_dir="$build_host_dir" + + jobs=$(get_flag_value jobs_flag[@] flags[@]) + + target=${args[0]} + + if [ -z "$target" ]; then + printf ""$ERR": No target specified\n" + return 1 + fi + + check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return + + # build the target + step "Build" + + declare opts + get_build_opts opts $jobs - build "$build_dir" "$target" || return + cmake --build "$build_dir" "${opts[@]}" --target "$target" - ohai "Test" + # run the tests + step "Test" cmake_disable_excluded_tests "$build_dir" ( cd "$build_dir" || return; ctest ) } list() { - declare build_dir="$build_default_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 -o '^[^/]*\.bin' | cut -f 1 -d '.' -} + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" -boards() { - declare build_dir="$build_default_dir" - - check_configured "$build_dir" || return - - ohai "List boards" - - declare -a opts - get_build_opts opts - - cmake --build "$build_dir" "${opts[@]}" --target help-boards -} + known_flags=() -lint_copyright() { - ohai "Lint (Copyright)" - - "$sbs_base/scripts/linter.py" --copyright "$source_dir/src" -} + cmd_flags known_flags[@] flags[@] || return + cmd_args 1 args[@] || return -lint_find() { - ohai "Lint (Find)" - - "$sbs_base/scripts/linter.py" --find "$source_dir/src" -} + config_debug=false + config_verbose=false + config_host=false + build_dir="$build_default_dir" -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[@]}" {} \; -} + check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return -lint_cppcheck() { - ohai "Lint (Cppcheck)" - - declare -a opts=() - [ -n "$jobs" ] && opts+=("-j $jobs") - - cppcheck --language=c++ --std=c++14 --enable=all --inline-suppr \ - --suppress=unmatchedSuppression --suppress=unusedFunction \ - --suppress=missingInclude --error-exitcode=1 -q "${opts[@]}" \ - "$source_dir/src" -} + list_type=${args[0]} -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[@]}" {} \; + if [ -z "$list_type" ]; then + printf ""$ERR": No list type specified\n" + return 1 + fi + + if [ "$list_type" = "targets" ]; then + cmake --build "$build_dir" --target help \ + | grep -o '^[^/]*\.bin' | cut -f 1 -d '.' + elif [ "$list_type" = "boards" ]; then + cmake --build "$build_dir" --target help | awk -F '[-:]' '/^boardcore/ {print $2}' + else + printf ""$ERR": Unknown list type $list_type\n" + return 1 + fi } lint() { - declare to_edit="$1" - + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + known_flags=() + + cmd_flags known_flags[@] flags[@] || return + cmd_args 0 args[@] || return + if [ "$found_python" = true ]; then lint_copyright lint_find + else + echo "Python not found, skipping copyright and find..." fi - - if [ "$found_clangtidy" = true ] && [ "$lint_clangtidy" = true ]; then - lint_clangtidy "$build_default_dir" - fi - + + # Disable clang-tidy for now, as too many false positives + # if [ "$found_clangtidy" = true ]; then + # lint_clangtidy "$build_default_dir" + # else + # echo "Clang-tidy not found, skipping clang-tidy..." + # fi + if [ "$found_cppcheck" = true ]; then lint_cppcheck + else + echo "Cppcheck not found, skipping cppcheck..." fi - + if [ "$found_clangformat" = true ]; then - lint_clangformat "$to_edit" + lint_clangformat + else + echo "Clang-format not found, skipping clang-format..." fi } -set_debug() { - config_debug=true -} +format() { + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + known_flags=() -set_verbose() { - config_verbose=true + cmd_flags known_flags[@] flags[@] || return + cmd_args 0 args[@] || return + + step "Format" + + echo "Running clang-format..." + + opts=(--style=file --Werror -i) + + find "$source_dir/src" \ + -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \ + -exec clang-format "${opts[@]}" {} \; } -set_jobs() { - jobs="$1" +help() { + echo "Usage: sbs (command) <args> [options] " + echo "" + echo "Commands:" + echo " build - Build the specified target" + echo " -> <target=all> [-d | --debug] [-v | --verbose] [-j | --jobs=<jobs>]" + echo " flash - Flash the specified target" + echo " -> <target> [-d | --debug] [-v | --verbose] [-r | --reset] [-j | --jobs=<jobs>]" + echo " test - Run the tests for the specified target" + echo " -> <target> [-j | --jobs=<jobs>]" + echo " list - List the available targets or boards" + echo " -> <targets | boards>" + echo " clean - Clean the build directory" + echo " lint - Lint the source code" + echo " format - Format the source code" + echo " install - Install autocomplete" + echo " uninstall - Uninstall autocomplete" + echo "" + echo "Options:" + echo " -d, --debug: Build in debug mode" + echo " -v, --verbose: Build in verbose mode" + echo " -r, --reset: Reset the target before flashing" + echo " -j, --jobs: Number of jobs to run in parallel" + echo "" } -get_build_opts() { - declare -n build_opts=$1 - [ -n "$jobs" ] && build_opts=("-j $jobs") +################################################################################ +############################## Python Commands ############################## +################################################################################ + + +install() { + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + known_flags=() + + cmd_flags known_flags[@] flags[@] || return + cmd_args 0 args[@] || return + + step "Install autocomplete" + + if [ "$found_python" = false ]; then + printf ""$ERR": Python is required to install autocomplete\n" + return 1 + fi + + echo "Retrieving targets..." + + targets=$(list "targets") + + # split the targets on newlines + IFS=$'\n' read -rd '' -a targets <<< "$targets" + + echo "Found ${#targets[@]} targets" + + python "$sbs_base/scripts/autocomplete.py" "--install" "${targets[@]}" } -set_clangtidy() { - lint_clangtidy=true +uninstall() { + IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" + IFS=' ' read -r -a args <<< "$(filter_args "$@")" + + known_flags=() + + cmd_flags known_flags[@] flags[@] || return + cmd_args 0 args[@] || return + + step "Uninstall autocomplete" + + if [ "$found_python" = false ]; then + printf ""$ERR": Python is required to uninstall autocomplete\n" + return 1 + fi + + python "$sbs_base/scripts/autocomplete.py" "--uninstall" } -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 - -r, --boards List all boards available - - Build Options: - -b TARGET, --build TARGET - Build a specific target - -f TARGET, --flash TARGET - Build and flash a specific target - -t TEST, --test TEST Build a specific test natively and run it - -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 +################################################################################ +############################## Main ############################## +################################################################################ + +init_no_output() { + find_deps + init_dirs } -CMAKE_FILENAME="CMakeCache.txt" -CTEST_FILENAME="CTestTestfile.cmake" -DEBUG_FILENAME=".sbs_debug" -VERBOSE_FILENAME=".sbs_verbose" -BUILD_DEFAULT_DIRNAME="build" -BUILD_HOST_DIRNAME="cmake-build-host" -TTY_BLUE="\033[34m" -TTY_BOLD="\033[1m" -TTY_RESET="\033[0m" +init() { + printf "$banner" -sbs_base= -source_dir= -build_default_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 -config_host=false -jobs= -lint_clangtidy=false + find_deps + init_dirs + print_configuration +} -print_banner -init_dirs for arg in "$@"; do - shift - case "$arg" in - --boards) set -- "$@" "-r";; - --build) set -- "$@" "-b";; - --clang-tidy) set_clangtidy;; - --clean) set -- "$@" "-c";; - --configure) set -- "$@" "-u";; - --debug) set -- "$@" "-d";; - --edit) set -- "$@" "-e";; - --flash) set -- "$@" "-f";; - --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:cdef:hj:lnrt:uv opt; do - case "$opt" in - b) find_deps && build "$build_default_dir" "$OPTARG"; exit;; - c) find_deps && clean_all; exit;; - d) set_debug;; - e) find_deps && lint true; exit;; - f) find_deps && flash "$OPTARG"; exit;; - h) usage; exit 0;; - j) set_jobs "$OPTARG";; - l) find_deps && list; exit;; - n) find_deps && lint false; exit;; - r) find_deps && boards; exit;; - t) find_deps && run_tests "$OPTARG"; exit;; - u) find_deps && configure "$build_default_dir"; exit;; - v) set_verbose;; - ?) usage; exit 2;; + case $arg in + build) init; build "${@:2}"; exit ;; + clean) init; clean "${@:2}"; exit ;; + flash) init; flash "${@:2}"; exit ;; + test) init; test "${@:2}"; exit ;; + lint) init; lint "${@:2}"; exit ;; + format) init; format; exit ;; + install) init; install; exit ;; + uninstall) init; uninstall; exit ;; + list) init_no_output; list "${@:2}"; exit ;; + *) help; exit ;; esac done -shift $((OPTIND - 1)) -find_deps && build_all +if [ "$#" -eq 0 ]; then + help +fi \ No newline at end of file diff --git a/scripts/autocomplete.py b/scripts/autocomplete.py new file mode 100755 index 0000000000000000000000000000000000000000..d90181a01fd4ab7344cd68e3578c5c83e9fa037f --- /dev/null +++ b/scripts/autocomplete.py @@ -0,0 +1,293 @@ +# Copyright (c) 2024 Skyward Experimental Rocketry +# Author: Davide Basso +# +# 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. + +import os +import sys +import inspect +import platform +from shutil import which +from pathlib import Path + +LINUX_BASH_COMPLETION_FOLDER = "/etc/bash_completion.d" +SBS_AUTOCOMPLETE_FOLDER = "sbs" +SBS_AUTOCOMPLETE_MAIN = "sbs-autocomplete.sh" +COMMANDS = [ + "build", + "clean", + "flash", + "list", + "test", + "lint", + "format", + "install", + "uninstall", +] +COMMANDS_WITH_TARGET = ["build", "flash", "test"] + + +def strip_extension(file: str): + return os.path.splitext(file)[0] + + +def write_autocomplete_script(file, project_name: str, targets: list[str]): + bash = inspect.cleandoc( + f"""#!/bin/bash + # Skyward Build System autocompletion script + # This script is automatically generated by sbs + # Do not edit manually + + _sbs_{project_name}() + {{ + local cur prev starting cmds cmds_with_target targets + COMPREPLY=() + cur="${{COMP_WORDS[COMP_CWORD]}}" + prev="${{COMP_WORDS[COMP_CWORD-1]}}" + starting="${{COMP_WORDS[0]}}" + + cmds=({' '.join([f'"{c}"' for c in COMMANDS])}) + cmds_with_target=({' '.join([f'"{c}"' for c in COMMANDS_WITH_TARGET])}) + targets=({' '.join([f'"{t}"' for t in targets])}) + + if [[ ${{prev}} == "./sbs" ]] ; then + COMPREPLY=( $(compgen -W "{' '.join(COMMANDS)}" -- "${{cur}}") ) + elif [[ ${{starting}} == "./sbs" && " ${{cmds_with_target[*]}} " =~ [[:space:]]${{prev}}[[:space:]] ]]; then + COMPREPLY=( $(compgen -W "{' '.join(targets)}" -- "${{cur}}") ) + fi + + return 0 + }} + """ + ) + file.write(bash) + + +def write_base_script(file): + bash = inspect.cleandoc( + f"""#!/bin/bash + # Skyward Build System autocompletion script + # This script is automatically generated by sbs + # Do not edit manually + + _sbs__base() + {{ + local cur prev starting cmds + COMPREPLY=() + cur="${{COMP_WORDS[COMP_CWORD]}}" + prev="${{COMP_WORDS[COMP_CWORD-1]}}" + starting="${{COMP_WORDS[1]}}" + + cmds=({' '.join([f'"{c}"' for c in COMMANDS])}) + + if [[ ${{prev}} == sbs ]] ; then + COMPREPLY=( $(compgen -W "{' '.join(COMMANDS)}" -- "${{cur}}") ) + fi + + return 0 + }} + """ + ) + file.write(bash) + + +def write_main_script(file, folder: str, projects_scripts: list[str]): + newline = "\n " + bash = inspect.cleandoc( + f"""#!/bin/bash + # Skyward Build System autocompletion script + # This script is automatically generated by sbs + # Do not edit manually + + source "{folder}/__base.sh" + # Source into all the project scripts + {newline.join([f'source "{folder}/{s}"' for s in projects_scripts])} + + stringContain() {{ case $2 in *$1* ) return 0;; *) return 1;; esac ;}} + + _sbs() + {{ + local projects + projects=({' '.join([f'"{strip_extension(s)}"' for s in projects_scripts])}) + + # Check if current working directory is under any project folder + for project in ${{projects[*]}}; + do + if stringContain "$project" "$PWD"; then + _sbs_$project + return 0 + fi + done + + _sbs__base + + return 0 + }} + + complete -F _sbs ./sbs + """ + ) + file.write(bash) + + +def find_git_bash_completion_folder(): + # Git bash stores bash completion files inside \mingw64\share\git\completion + # We first get the path of the git executable, and use it to find the completion folder + git_path = which("git") + mingw64_path = os.path.dirname(os.path.dirname(git_path)) + return os.path.join(mingw64_path, "share", "git", "completion") + + +def add_autocomplete_script_to_bashrc(path: str): + home = Path.home() + bashrc_path = os.path.join(home, ".bashrc") + + fixed_path = path.replace("\\", "/") + cmd = f'# Skyward Build System autocompletion: \nsource "{fixed_path}"' + # Check if the script is already sourced + with open(bashrc_path, "r") as f: + bashrc = f.read() + if cmd in bashrc: + print(f"Autocomplete script already sourced in {bashrc_path}") + return + # Add the script to bashrc + with open(bashrc_path, "a") as f: + f.write(f"\n{cmd}") + print(f"Added autocomplete script to {bashrc_path}") + + +def find_folders(): + if platform.system() == "Linux": + if not os.path.exists(LINUX_BASH_COMPLETION_FOLDER): + raise OSError( + f"Could not find bash completion folder {LINUX_BASH_COMPLETION_FOLDER};" + ) + autocomplete_folder = LINUX_BASH_COMPLETION_FOLDER + print(f"Detected Linux platform, using {autocomplete_folder}") + elif platform.system() == "Windows": + autocomplete_folder = find_git_bash_completion_folder() + print(f"Detected Windows platform, using {autocomplete_folder}") + add_autocomplete_script_to_bashrc( + os.path.join(autocomplete_folder, SBS_AUTOCOMPLETE_MAIN) + ) + else: + raise OSError(f"Unsupported platform {platform.system()}") + + project_name = os.path.basename(os.getcwd()) + sbs_scripts_folder = os.path.join(autocomplete_folder, SBS_AUTOCOMPLETE_FOLDER) + main_file_path = os.path.join(autocomplete_folder, SBS_AUTOCOMPLETE_MAIN) + autocomplete_file_path = os.path.join(sbs_scripts_folder, project_name + ".sh") + base_script_path = os.path.join(sbs_scripts_folder, "__base.sh") + + return ( + project_name, + sbs_scripts_folder, + main_file_path, + autocomplete_file_path, + base_script_path, + ) + + +def install_autocomplete(targets: list[str]): + print(f"\nSetting up autocomplete for current project...") + + ( + project_name, + sbs_scripts_folder, + main_file_path, + autocomplete_file_path, + base_script_path, + ) = find_folders() + + # Create paths for autocomplete scripts + os.makedirs(sbs_scripts_folder, exist_ok=True) + + # Generate autocomplete script + if os.path.exists(autocomplete_file_path): + print(f"Removing old autocomplete script {autocomplete_file_path}...") + os.remove(autocomplete_file_path) + + with open(autocomplete_file_path, "w+") as f: + print(f"Writing autocomplete script to {autocomplete_file_path}...") + write_autocomplete_script(f, project_name, targets) + + projects_scripts = os.listdir(sbs_scripts_folder) + if "__base.sh" in projects_scripts: + projects_scripts.remove("__base.sh") + + # Write base script + if os.path.exists(base_script_path): + print(f"Removing old base script {base_script_path}...") + os.remove(base_script_path) + + with open(base_script_path, "w+") as f: + print(f"Writing base script to {base_script_path}...") + write_base_script(f) + + # Generate main script + if os.path.exists(main_file_path): + os.remove(main_file_path) + + with open(main_file_path, "w+") as f: + print(f"Writing main script to {main_file_path}...") + write_main_script(f, sbs_scripts_folder, projects_scripts) + + print(f"Done, restart your shell to see the changes\n") + + +def uninstall_autocomplete(): + print("Uninstalling autocomplete script...") + + ( + project_name, + sbs_scripts_folder, + main_file_path, + autocomplete_file_path, + base_script_path, + ) = find_folders() + + if os.path.exists(autocomplete_file_path): + print(f"Removing autocomplete script {autocomplete_file_path}...") + os.remove(autocomplete_file_path) + else: + print(f"Could not find autocomplete script {autocomplete_file_path}") + + if os.path.exists(base_script_path): + print(f"Removing base script {base_script_path}...") + os.remove(base_script_path) + else: + print(f"Could not find base script {base_script_path}") + + if os.path.exists(main_file_path): + print(f"Removing main script {main_file_path}...") + os.remove(main_file_path) + else: + print(f"Could not find main script {main_file_path}") + + print("Done, restart your shell to see the changes\n") + + +if __name__ == "__main__": + type = sys.argv[1] + if type == "--install": + install_autocomplete(sys.argv[2:]) + elif type == "--uninstall": + uninstall_autocomplete() + else: + raise ValueError(f"Unknown argument {type}") diff --git a/scripts/linter.py b/scripts/linter.py index 1beb95593ea5117b5b7996362ce5e6f4e7e9e82a..c31d95e7a5bbe8cc71023246a4a792a9deea6846 100755 --- a/scripts/linter.py +++ b/scripts/linter.py @@ -34,50 +34,69 @@ from os import pathconf, walk from os.path import join # 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 = ',' +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') + 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') + "--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') + "--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') + "--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') + "--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 @@ -92,22 +111,22 @@ def print_banner(): print(r"+------------------------------+") -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' +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): - print(Colors.GREEN + 'Copyright check' + Colors.RESET) + print(Colors.GREEN + "Copyright check" + Colors.RESET) # Statistics totalCheckdFilesCounter = 0 @@ -117,7 +136,7 @@ def check_copyright(directory): # 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'))]: + for filename in [f for f in filenames if f.endswith((".cpp", ".h"))]: totalCheckdFilesCounter += 1 # Prepare the complete filepath @@ -130,19 +149,29 @@ def check_copyright(directory): filesWithErrorsCounter += 1 # The file's copyright notice does not match the template! - print(Colors.YELLOW + 'Wrong copyright notice in file {0}'.format( - currentFilepath) + Colors.RESET) + 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)] + 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': - print('\'Authors\' should to be changed to \'Author\' in {0}'.format( - currentFilepath)) - if len(fileAuthors) > 1 and match.group(1)[-1] != 's': - print('\'Author\' should to be changed to \'Authors\' in {0}'.format( - currentFilepath)) + if len(fileAuthors) == 1 and match.group(1)[-1] == "s": + print( + "'Authors' should to be changed to 'Author' in {0}".format( + currentFilepath + ) + ) + if len(fileAuthors) > 1 and match.group(1)[-1] != "s": + print( + "'Author' should to be changed to 'Authors' in {0}".format( + currentFilepath + ) + ) # Save statistics on authors for author in fileAuthors: @@ -153,29 +182,35 @@ def check_copyright(directory): averageAuthorsPerFile += len(fileAuthors) averageAuthorsPerFile /= totalCheckdFilesCounter - print('Checked {} files'.format(totalCheckdFilesCounter)) + print("Checked {} files".format(totalCheckdFilesCounter)) if filesWithErrorsCounter == 0: - print('All the files have the correct copyright notice') + print("All the files have the correct copyright notice") else: - 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): - print('{:.2} authors per file'.format( - averageAuthorsPerFile)) - - print('Number of mentions per author:') + 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: + print("{:.2} authors per file".format(averageAuthorsPerFile)) + + print("Number of mentions per author:") for author in sorted(authors.items(), key=lambda item: item[1], reverse=True): - print('{:3} - {}'.format(author[1], author[0])) + print("{:3} - {}".format(author[1], author[0])) # Exit if error if at least one file isn't correct - if (filesWithErrorsCounter > 0): + 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): - print(Colors.GREEN + 'Formatting check' + Colors.RESET) + print(Colors.GREEN + "Formatting check" + Colors.RESET) # Statistics totalCheckdFilesCounter = 0 @@ -183,7 +218,7 @@ def check_format(directory): # 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'))]: + for filename in [f for f in filenames if f.endswith((".cpp", ".h", "c"))]: totalCheckdFilesCounter += 1 # Prepare the complete filepath @@ -191,31 +226,57 @@ def check_format(directory): # 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) + [ + "clang-format", + "-style=file", + "--dry-run", + "--Werror", + "--ferror-limit=1", + currentFilepath, + ], + stderr=DEVNULL, + ) # If and error occurs warn the user - if (returnCode != 0): + if returnCode != 0: filesWithErrorsCounter += 1 - if (not args.quiet): - print(Colors.YELLOW + 'Wrong code format for file {1}'.format( - returnCode, currentFilepath) + Colors.RESET) + if not args.quiet: + print( + Colors.YELLOW + + "Wrong code format for file {1}".format( + returnCode, currentFilepath + ) + + Colors.RESET + ) - print('Checked {} files'.format(totalCheckdFilesCounter)) + print("Checked {} files".format(totalCheckdFilesCounter)) if filesWithErrorsCounter == 0: - print('All the files match the Skyward formatting style') + print("All the files match the Skyward formatting style") else: - print(Colors.RED + '{:4.1f}% ({}/{}) of all analyzed files do not match Skyward formatting style!'.format( - 100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter), Colors.RESET) + 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): + if filesWithErrorsCounter > 0: exit(-1) -def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFilter=None): +def find_in_code( + directory, searchTerm, extensionFilters=(".cpp", ".h"), pathFilter=None +): print( - Colors.GREEN + 'Checking for \'{}\' in code files'.format(searchTerm) + Colors.RESET) + Colors.GREEN + + "Checking for '{}' in code files".format(searchTerm) + + Colors.RESET + ) # Statistics totalCheckdFilesCounter = 0 @@ -223,7 +284,12 @@ def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFil # 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)]: + 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 @@ -233,67 +299,95 @@ def find_in_code(directory, searchTerm, extensionFilters=('.cpp', '.h'), pathFil with open(currentFilepath) as file: fileContent = file.read() # Check for linter off flag - if re.search('linter off', fileContent, re.M): + 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): - print(Colors.YELLOW + 'Found \'{}\' in file {}'.format(searchTerm, - currentFilepath) + Colors.RESET) - - print('Checked {} files'.format(totalCheckdFilesCounter)) + if not args.quiet: + print( + Colors.YELLOW + + "Found '{}' in file {}".format( + searchTerm, currentFilepath + ) + + Colors.RESET + ) + + print("Checked {} files".format(totalCheckdFilesCounter)) if filesWithErrorsCounter == 0: - print( - 'All the files does not contain \'{}\''.format(searchTerm)) + print("All the files do not contain '{}'".format(searchTerm)) else: - print(Colors.RED + '{:.1f}% ({}/{}) of all analyzed files contain \'{}\'!'.format( - 100*filesWithErrorsCounter/totalCheckdFilesCounter, filesWithErrorsCounter, totalCheckdFilesCounter, searchTerm) + Colors.RESET) + 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, '^ *throw ', pathFilter='catch') + 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, "^ *throw ", pathFilter="catch") if sum > 0: exit(-1) def check_cppcheck(directory): - print(Colors.GREEN + 'cppcheck' + Colors.RESET) + 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) + 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 = re.findall(r"\[(\w+)\]", result.decode("utf-8")) errors = Counter(errors) - if (not args.quiet): - print('cppcheck found the following errors:') + if not args.quiet: + print("cppcheck found the following errors:") for error in errors: - print('{:3} - {}'.format(errors[error], error)) + print("{:3} - {}".format(errors[error], error)) totalErrors = sum(errors.values()) - if (totalErrors > 0): + if totalErrors > 0: print( - Colors.RED + 'cppcheck found {} errors in total'.format(totalErrors) + Colors.RESET) + Colors.RED + + "cppcheck found {} errors in total".format(totalErrors) + + Colors.RESET + ) exit(-1) else: - print('cppcheck did not find any errors') + print("cppcheck did not find any errors") except CalledProcessError as e: - print(e.output.decode('utf-8')) + print(e.output.decode("utf-8")) exit(-1) + # ------------------------------------------------------------- # MAIN # ------------------------------------------------------------- @@ -302,26 +396,26 @@ def check_cppcheck(directory): parser = config_cmd_parser() args = parser.parse_args() -if (not args.directory): - print('No directory specified') - print('') +if not args.directory: + print("No directory specified") + print("") parser.print_help() exit(-1) -if (args.copyright): +if args.copyright: check_copyright(args.directory) -if (args.format): +if args.format: check_format(args.directory) -if (args.cppcheck): +if args.cppcheck: check_cppcheck(args.directory) -if (args.find): +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): +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) diff --git a/src/shared/sensors/Vectornav/VN300/VN300.cpp b/src/shared/sensors/Vectornav/VN300/VN300.cpp index 55feeebda4484a9e9dc8d6e77f541bfd8fb64a5b..ef7b62bfab699427d3ef7cb2fa8a1e43869ff780 100755 --- a/src/shared/sensors/Vectornav/VN300/VN300.cpp +++ b/src/shared/sensors/Vectornav/VN300/VN300.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2023 Skyward Experimental Rocketry - * Author: Lorenzo Cucchi, Fabrizio Monti + * Authors: Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/shared/sensors/Vectornav/VN300/VN300.h b/src/shared/sensors/Vectornav/VN300/VN300.h index 9bf642020b9c6990dd3db4ec3303c9e3c0348846..a106bdf98d13e2476289a1874dcc3af20fcbc1bc 100755 --- a/src/shared/sensors/Vectornav/VN300/VN300.h +++ b/src/shared/sensors/Vectornav/VN300/VN300.h @@ -1,5 +1,5 @@ /* Copyright (c) 2023 Skyward Experimental Rocketry - * Author: Lorenzo Cucchi, Fabrizio Monti + * Authors: Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/shared/sensors/Vectornav/VN300/VN300Data.h b/src/shared/sensors/Vectornav/VN300/VN300Data.h index 538fecbfc5310079dee9c7ab4e7e83b002777b36..5953da14c339b1f849706fa70a792cf338e6625b 100644 --- a/src/shared/sensors/Vectornav/VN300/VN300Data.h +++ b/src/shared/sensors/Vectornav/VN300/VN300Data.h @@ -1,5 +1,5 @@ /* Copyright (c) 2023 Skyward Experimental Rocketry - * Author: Lorenzo Cucchi, Fabrizio Monti + * Authors: Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/shared/sensors/Vectornav/VN300/VN300Defs.h b/src/shared/sensors/Vectornav/VN300/VN300Defs.h index b7037a26b36a03aabd3c6b51bd527b10b62b0cff..7e2b186d0ce0263e18806a2fe65acafce8336796 100755 --- a/src/shared/sensors/Vectornav/VN300/VN300Defs.h +++ b/src/shared/sensors/Vectornav/VN300/VN300Defs.h @@ -1,5 +1,5 @@ /* Copyright (c) 2023 Skyward Experimental Rocketry - * Author: Lorenzo Cucchi, Fabrizio Monti + * Authors: Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/shared/sensors/Vectornav/VNCommonSerial.cpp b/src/shared/sensors/Vectornav/VNCommonSerial.cpp index c4859c0b6cead5e27d26d70ce0b0b18d190fffc1..599e889054fe4b7ea70ecc0cb96e04b1f9e6a6a5 100644 --- a/src/shared/sensors/Vectornav/VNCommonSerial.cpp +++ b/src/shared/sensors/Vectornav/VNCommonSerial.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2024 Skyward Experimental Rocketry - * Author: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti + * Authors: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/shared/sensors/Vectornav/VNCommonSerial.h b/src/shared/sensors/Vectornav/VNCommonSerial.h index 08c64c3d50238b225928915d5034901942d44e7d..e25b6d2256597ff7b77be1674b8931cd24e869d2 100644 --- a/src/shared/sensors/Vectornav/VNCommonSerial.h +++ b/src/shared/sensors/Vectornav/VNCommonSerial.h @@ -1,5 +1,5 @@ /* Copyright (c) 2024 Skyward Experimental Rocketry - * Author: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti + * Authors: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/tests/sensors/test-vn300.cpp b/src/tests/sensors/test-vn300.cpp index a9d5cbaaffb9ffd257d6cf0f425cded50708e891..408165eb3ec1d021767c4fe705242a927c83a368 100644 --- a/src/tests/sensors/test-vn300.cpp +++ b/src/tests/sensors/test-vn300.cpp @@ -1,5 +1,5 @@ /* Copyright (c) 2023 Skyward Experimental Rocketry - * Author: Lorenzo Cucchi, Fabrizio Monti + * Authors: Lorenzo Cucchi, Fabrizio Monti * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal