#!/usr/bin/env bash # 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. ################################################################################ ############################## Constants ############################## ################################################################################ # Terminal colors TTY_RESET="\033[0m" TTY_BOLD="\033[1m" TTY_FOUND="\033[35m" TTY_SUCCESS="\033[32m" TTY_ERROR="\033[31m" TTY_STEP="\033[94m" 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"" # Error codes EPERM=1 # Operation not permitted ENOENT=2 # No such file or directory ENOEXEC=8 # Exec format error ENODEV=19 # No such device EINVAL=22 # Invalid argument ENOPKG=65 # Package not installed # 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" # Logical cores count, cross platform N_PROC=$( nproc 2>/dev/null || # Linux sysctl -n hw.logicalcpu 2>/dev/null || # macOS getconf _NPROCESSORS_ONLN 2>/dev/null # POSIX ) ################################################################################ ############################## 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 } check_build() { declare host_build=$1 [ "$found_cmake" = true ] || { printf ""$ERR": CMake must be installed\n"; return $ENOPKG; } if [ "$host_build" = false ]; then [ "$found_miosixgpp" = true ] || { printf ""$ERR": arm-miosix-eabi-g++ must be installed\n"; return $ENOPKG; } fi } ################################################################################ ############################## 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[@]}" } # 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[@]}" } # 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 $EINVAL 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 $EINVAL 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_STEP""$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 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 command -v st-flash > /dev/null 2>&1 && found_stflash=true command -v ST-LINK_CLI.exe > /dev/null 2>&1 && found_stlink=true } # 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() { build_dir="$build_host_dir" [ ! -f "$build_dir/$CTEST_FILENAME" ] || sed -i.bak 's/^subdirs/# subdirs/' "$build_dir/$CTEST_FILENAME" } ################################################################################ ############################## Subcommands ############################## ################################################################################ # Check if the project is configured # The syntax is: check_configured "config_debug" "config_verbose" "config_host" check_configured() { build_dir="$1" config_debug="$2" config_verbose="$3" config_host="$4" 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 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 $ENOPKG1; } 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_impl() { target="$1" build_dir="$2" config_debug="$3" config_verbose="$4" config_host="$5" check_build "$config_host" || return check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return step "Build" declare opts get_build_opts opts $jobs cmake --build "$build_dir" --target "$target" "${opts[@]}" } flash_impl() { target="$1" build_dir="$2" reset="$3" # check if the target is flashable [ -f "$build_dir/$target.bin" ] || { printf ""$ERR": target '$target' is not flashable"; return $ENOEXEC; } # flash the target step "Flash" declare -a flash_opts [ "$reset" = true ] && flash_opts+=("--connect-under-reset") if [ "$found_stflash" = 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 printf ""$ERR": No flashing software found!\n" return $ENOPKG fi } 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" 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 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() { 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" } lint_clangformat() { step "Lint clang-format" echo "Running clang-format..." opts=(--style=file --Werror --dry-run) # find all the source files declare files=$(find "$source_dir/src" \ -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \)) # count files and spread them evenly across cores: files / nproc + 1 declare files_per_proc=$( echo "$files" | wc -l | \ xargs -I {} bash -c "expr \( {} / $N_PROC \) + 1" ) echo "$files" | xargs -n $files_per_proc -P 0 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} build_impl "$target" "$build_dir" "$config_debug" "$config_verbose" "$config_host" } 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() { 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 $ENOENT fi # build the target build_impl "$target" "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return # flash the target flash_impl "$target" "$build_dir" "$reset" } run() { IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" IFS=' ' read -r -a args <<< "$(filter_args "$@")" jobs_flag=("-j" "--jobs") device_flag=("-D" "--device") baudrate_flag=("-B" "--baudrate") known_flags=("-d" "-v" "-r" "-D=" "-B=" "-j=" "--debug" "--verbose" "--reset" "--device=" "--baudrate=" "--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 $ENOENT fi build_impl "$target" "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return flash_impl "$target" "$build_dir" "$reset" || return device=$(get_flag_value device_flag[@] flags[@]) baudrate=$(get_flag_value baudrate_flag[@] flags[@]) # If no device was specified, try to find it based on what we commonly use if [ -z "$device" ]; then if [ -e "/dev/ttyACM0" ]; then device="/dev/ttyACM0" elif [ -e "/dev/ttyUSB0" ]; then device="/dev/ttyUSB0" else printf ""$ERR": No device specified and no default device found\n" return $ENODEV fi fi # Set Skyward's default baudrate, if none was specified if [ -z "$baudrate" ]; then baudrate="115200" fi step "Run - $device @ $baudrate" # tty devices use \r\n for newlines by default # disable newlines on \r with `-icrnl` to avoid double newlines stty --file $device $baudrate -icrnl || return # connect to the device cat $device } test() { 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 know_flags[@] flags[@] || return cmd_args 1 args[@] || return build_dir="$build_host_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 config_host=true target=${args[0]:-catch-tests-boardcore} if [ -z "$target" ]; then printf ""$ERR": No target specified\n" return $ENOENT fi # build the target build_impl "$target" "$build_dir" "$config_debug" "$config_verbose" "$config_host" || return # run the tests step "Test" cmake_disable_excluded_tests "$build_dir" ( cd "$build_dir" || return; ctest ) } list() { IFS=' ' read -r -a flags <<< "$(filter_flags "$@")" IFS=' ' read -r -a args <<< "$(filter_args "$@")" known_flags=() cmd_flags known_flags[@] flags[@] || return cmd_args 1 args[@] || return config_debug=false config_verbose=false config_host=false build_dir="$build_default_dir" list_type=${args[0]} if [ -z "$list_type" ]; then printf ""$ERR": No list type specified\n" return $EINVAL fi check_build "$config_host" || return check_configured "$build_dir" "$config_debug" "$config_verbose" "$config_host" &> /dev/null || return 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 $EINVAL fi } lint() { 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 declare ret=0 if [ "$found_python" = true ]; then lint_copyright [ $? -ne 0 ] && ret=-1 lint_find [ $? -ne 0 ] && ret=-1 else echo "Python not found, skipping copyright and find..." 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 [ $? -ne 0 ] && ret=-1 else echo "Cppcheck not found, skipping cppcheck..." fi if [ "$found_clangformat" = true ]; then lint_clangformat [ $? -ne 0 ] && ret=-1 else echo "Clang-format not found, skipping clang-format..." fi return $ret } format() { 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_clangformat" = false ]; then printf ""$ERR": clang-format must be installed\n" return $ENOPKG fi step "Format" echo "Running clang-format..." opts=(--style=file --Werror -i) # find all the source files declare files=$(find "$source_dir/src" \ -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \)) # count files and spread them evenly across cores: files / nproc + 1 declare files_per_proc=$( echo "$files" | wc -l | \ xargs -I {} bash -c "expr \( {} / $N_PROC \) + 1" ) echo "$files" | xargs -n $files_per_proc -P 0 clang-format "${opts[@]}" } help() { echo "Usage: sbs (command) <args> [options] " echo "" echo "Commands:" echo " build - Build the specified target. If none is specified, all targets are built" echo " -> <target=all> [-d | --debug] [-v | --verbose] [-j | --jobs=<jobs>]" echo " flash - Build and flash the specified target" echo " -> <target> [-d | --debug] [-v | --verbose] [-r | --reset] [-j | --jobs=<jobs>]" echo " run - Build and flash the specified target and connect to serial device" echo " -> <target> [-d | --debug] [-v | --verbose] [-r | --reset] [-j | --jobs=<jobs>]" echo " [-D | --device=<device>] [-B | --baudrate=<baudrate>]" echo " test - Run the specified test. If none is specified, catch tests are run" echo " -> <target> [-d | --debug] [-v | --verbose] [-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 " -D, --device: Serial device to connect to (default: /dev/ttyACM0 or /dev/ttyUSB0)" echo " -B, --baudrate: Baudrate for the serial device (default: 115200)" echo "" } ################################################################################ ############################## 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 $ENOPKG fi echo "Retrieving targets..." targets=$(list "targets") declare ret=$? [ $ret -ne 0 ] && printf "$targets\n" && return $ret # split the targets on newlines IFS=$'\n' read -rd '' -a targets <<< "$targets" echo "Found ${#targets[@]} targets" echo "Installing completion files..." sudo python "$sbs_base/scripts/autocomplete.py" "--install" "${targets[@]}" } 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 $ENOPKG fi echo "Uninstalling completion files..." sudo python "$sbs_base/scripts/autocomplete.py" "--uninstall" } ################################################################################ ############################## Main ############################## ################################################################################ # exit immediately if no argument was provided if [ "$#" -eq 0 ]; then help exit $EINVAL fi welcome() { printf "$banner" print_configuration } # populate global variables for all commands find_deps init_dirs for arg in "$@"; do case $arg in build) welcome; build "${@:2}"; exit ;; clean) welcome; clean "${@:2}"; exit ;; flash) welcome; flash "${@:2}"; exit ;; run) welcome; run "${@:2}"; exit ;; test) welcome; test "${@:2}"; exit ;; lint) welcome; lint "${@:2}"; exit ;; format) welcome; format; exit ;; install) welcome; install; exit ;; uninstall) welcome; uninstall; exit ;; list) list "${@:2}"; exit ;; *) help; exit $EINVAL;; esac done