diff --git a/.editorconfig b/.editorconfig index c2b4a9aa..f74d4a18 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ end_of_line = lf charset = utf-8 # For all source code -[*.{c,cpp,h,ino}] +[*.{c,cpp,h,ino,sh}] insert_final_newline = true indent_style = tab indent_size = 2 diff --git a/.tools/.bundle_runtime.sh b/.tools/.bundle_runtime.sh new file mode 100755 index 00000000..f27f650a --- /dev/null +++ b/.tools/.bundle_runtime.sh @@ -0,0 +1,95 @@ +#!/bin/bash +### +# +# Tool runtime shim that provides a simple API for running +# tool bundles and convenience functions for other tool developers. +# +## +log() { IFS=" "; >&2 printf "%s\n" "$*"; } +warn() { IFS=" "; >&2 printf "\e[33m%s\e[m\n" "$*"; } +err() { IFS=" "; >&2 printf '\e[31m%s\e[m\n' "$*"; exit 1; } + +is_supported_os() +{ + [[ ${1} == darwin* ]] || [[ ${1} == linux-gnu ]] || [[ ${1} == freebsd ]] || [[ ${1} == msys ]] || [[ ${1} == cygwin ]] +} + +is_installed() +{ + which "${1}" > /dev/null 2>&1 +} + +tools_dir() +{ + git_config_tools_dir='git config mysensors.toolsdir' + + # Set mysensors.toolsdir in git config if it hasn't been set + if [ ! $($git_config_tools_dir) ]; then + $git_config_tools_dir "$( cd "$(dirname "${BASH_SOURCE[0]}")"; git rev-parse --show-prefix )" + git config alias.mystoolspath '![ -z "${GIT_PREFIX}" ] || cd ${GIT_PREFIX}; echo $(git rev-parse --show-cdup)$(git config mysensors.toolsdir)' + fi + + $git_config_tools_dir +} + +configure_git_tool_aliases() +{ + find "$(git mystoolspath)" -name run.sh -print0 | while IFS= read -r -d '' bundle; do + local tool="$(basename "$(dirname "${bundle}")")" + git config alias.${tool} '!f() { $(git mystoolspath)'${tool}'/run.sh $@; }; f' + done +} + +bootstrap_cksum() +{ + echo $(git ls-files -s -- $(git mystoolspath)bootstrap-dev.sh | cut -d' ' -f2) +} + +bootstrap_version() +{ + git_config_bootstrap_version='git config mysensors.bootstrap-cksum' + if [[ $1 == --set ]]; then + $git_config_bootstrap_version $(bootstrap_cksum) + else + echo $($git_config_bootstrap_version) + fi +} + +environment_outdated() +{ + [[ $(bootstrap_version) != $(bootstrap_cksum) ]] +} + +modifiedSourceFiles() +{ + against=$(git rev-parse --verify HEAD >/dev/null 2>&1 && echo HEAD || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904) + git diff $1 --diff-filter=AM --name-only $against | grep -E '.*\.(c|cpp|h|hpp|ino)$' +} + +stagedSourceFiles() +{ + modifiedSourceFiles '--cached' +} + +runBundle() +{ + local bundle="$(dirname "${0}")" + local tool=$(basename "${bundle}") + + local CMD_OPTIONS=$( TOOLCONFIG="${bundle}/config" "${bundle}/options.sh" ) + + $tool $CMD_OPTIONS "$@" +} + +### +# Common environment variables +# +$(git rev-parse --is-inside-work-tree --quiet >/dev/null 2>&1) || err "Working directory is not a git repository. aborting..." + +if environment_outdated && [[ $(basename "${0}") != *bootstrap* ]]; then + err "Your environment is out of date... Re-run $(git mystoolspath)bootstrap-dev.sh and try again" +fi + +GITREPO=$(git rev-parse --show-toplevel) +GITDIR=$(git rev-parse --git-dir) +TOOLSDIR="$(tools_dir)" diff --git a/.tools/README.md b/.tools/README.md new file mode 100644 index 00000000..694c4d34 --- /dev/null +++ b/.tools/README.md @@ -0,0 +1,259 @@ +# Development Tools + +### Overview + +This directory hosts MySensors development tools. The following +conventions are employed to facilitate consistent re-use/invocation +across modalitiies (e.g. local development, continuous integration, +editor linters, etc.) + +1. All common tools are hosted and managed in + the tools directory (used for both local development + and continuous integration) +2. Each tool comprises a directory, akin to a bundle, + that encapsulates declarative command-line options, + configuration files and a run script +3. A single bootstrap script configures a + development environment +4. A lightweight runtime provides a common set of + convenience functions and variables to all scripts +5. Support for all MySensors development environments + +### Usage + +The [boostrap-dev](bootstrap-dev.sh) script completely configures a +development environment. The boostrap-dev script validates development +pre-requisites such as verifying the git repo ```origin``` & +```upstream``` remotes, tools needed for the git client hooks, +installing local commit hooks, and creating git aliases, etc. + +```shell-script +$ cd MySensors # git repo +$ .tools/bootstrap-dev.sh +Checking operating system support: darwin16... +Checking github 'origin' & 'upstream' remotes... +Checking tool/utility pre-requisites... +Installing git client-side hooks... +Configuring git aliases for running mysensor tool bundles... +Successfully configured your repo for MySensors development... Thanks for your support! +$ +``` +**Note:** If the bootstrap can not find required command-line +utilities, you will be requested to install the required tools and +re-run bootstrap.sh. See [Installation instructions for pre-requisite +tools](#installtools). + +Once the bootstrapping process is complete, a git alias for each tool +is available to conveniently run the tool with the pre-configured +mysensors options and settings (no need to remember all those +command-line options or where the configuration files reside). +Furthermore, you can run the command against staged files, unstaged +modified files or a list of files common to most git commands. + +```shell-script +$ # Run cppcheck on an individual file +$ git cppcheck core/MyMessage.cpp +$ +$ # Run cppcheck on all changed files +$ git cppcheck +$ +$ # Run astyle on staged files +$ git astyle --cached +``` + +Available tool aliases: + +* git cppcheck [--cached] [files] +* git astyle [--cached] [files] + +Finally, to maintain code quality and a legible git revision history, +two git hooks are installed that will trigger whenever you commit +changes to your local repo. The hooks will examine your changes to +ensure they satisfy the [MySensors Code Contribution +Guidelines](https://www.mysensors.org/download/contributing) before +you push your changes to GitHub for merging by the core MySensors +team. Gitler will also enforce the coding guidelines so the hooks are +provided to reduce development cycle times by detecting standards +variances locally before pushing to GitHub. + +### Installation instructions for pre-requisite tools + +This first time you run the bootstrap script, it may inform you that +certain development tools are missing from your path or system: + +``` +Checking operating system support: darwin16... +Checking github 'origin' & 'upstream' remotes... +Checking tool/utility prerequisites... +astyle not installed or not in current path. +cppcheck not installed or not in current path. +One or more required tools not found. Install required tools and re-run .tools/bootstrap-dev.sh +``` + +To finish the bootstrap process, you will need to install the required +tools for your specific operating system as follows. Currently we use +Astyle 2.0.5 or later and Cppcheck 1.76 or later. Once you have +installed AStyle and Cppcheck, re-run bootstrap-dev.sh to finish +configuring your development environment. + +##### macOS + +```brew install astyle cppcheck``` + +#### Linux Xenial or later + +```apt-get install astyle cppcheck``` + +#### Linux Trusty or earlier + +##### Note: The apt versions are too old on Trusty so follow the [Linux - Build and Install from Source](#buildFromSource) instructions + +##### Windows - GitHub Git Shell + +###### *IMPORTANT: Be sure to launch PowerShell As Administrator* + +``` +### Install AStyle + +# Download +iwr 'https://sourceforge.net/projects/astyle/files/astyle/astyle%202.05.1/AStyle_2.05.1_windows.zip/download' -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox -OutFile astyle.2.05.zip + +# Unzip the filed & move the C:\Program Files +expand-archive astyle.2.05.zip +mv .\astyle.2.05 'C:\Program Files\AStyle\' + +# Add AStyle to your path +[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\AStyle\bin", [EnvironmentVariableTarget]::Machine) + +### Install Cppcheck (either 64-bit or 32-bit depending upon your version of Windows - pick one below) + +# 64-bit +iwr 'http://github.com/danmar/cppcheck/releases/download/1.76.1/cppcheck-1.76.1-x64-Setup.msi' -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox -OutFile cppcheck-1.76.1-Setup.msi + +# 32-bit +iwr 'http://github.com/danmar/cppcheck/releases/download/1.76.1/cppcheck-1.76.1-x86-Setup.msi' -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox -OutFile cppcheck-1.76.1-Setup.msi + +# Launch installer to install Cppcheck +& .\cppcheck-1.76.1-Setup.msi + +### Add Cppcheck to your path +[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Cppcheck", [EnvironmentVariableTarget]::Machine) + +### At this point you need to reboot for the path changes to take effect +``` + +##### Windows - bash + +###### NOTE: At the time of this writing, the apt vresions of cppcheck and astyle are too old. Follow the [Linux - Build and Install from Source](#buildFromSource) instructions + +##### Windows - Cygwin +``` +Run Either Cygwin Setup-x86-64.exe or Setup-x86.exe depending upon your OS. Select and install astyle and cppcheck +``` + +##### Linux - Build and Install from Source + +``` +### Install AStyle + +# Download +curl -L 'https://sourceforge.net/projects/astyle/files/astyle/astyle%202.05.1/astyle_2.05.1_linux.tar.gz/download' | tar xvz + +# Compile and install +cd astyle/build/gcc && sudo make shared release shared static install + +### Install Cppcheck + +# Download +curl -L 'https://sourceforge.net/projects/cppcheck/files/cppcheck/1.76.1/cppcheck-1.76.1.tar.gz/download' | tar xvz + +# Compile and install +sudo apt-get install libpcre++-dev +sudo make SRCDIR=build CFGDIR=/usr/share/cppcheck HAVE_RULES=yes CXXFLAGS="-O2 -DNDEBUG -Wall -Wno-sign-compare -Wno-unused-function" install + +``` + +### Implementation Details + +*This section is intended for developers curious about the +implementation details or interested in extending the development +environment to support additional tools, aliases, etc.* + +A lightweight [runtime](.bundle_runtime.sh) (less than 70 lines of +code) provides a simple API to consistently run the MySensors toolset +across modalities including, but not limited to, local development and +continuous integration. The core concept is that each tool +encapsulates all runtime configuration settings in a simple, +stand-alone bundle, that can be executed by the bundle runtime. The +runtime provides an API to execute a tool bundle along with +convenience functions and environment variables common to all tools. + +Each tool bundle is laid out as follows: + +``` +${TOOLSDIR} [e.g. ${GITREPO}/.tools}] +.../tool [e.g. cppcheck, astyle] +....../run.sh [tool run script] +....../options.sh [command-line options] +....../config [supporting config files] +........./some.cfg +``` + +All bundle runtime dependencies are defined withing the namespace / +context of a git repo so users are free to rename or move repos or +even use multiple copies of the same repo without encountering +intra-repo tool settings/configuration conflicts. + +During the bootstrap process, certain keys and aliases are defined +that the runtime leverages. + +#####git config keys + +* mysensors.toolsdir = .tools (defined as a repo-relative path - location agnostic) +* mysensors.bootstrap-cksum = \ + +#####git aliases + +* mystoolspath = *returns the absolute path to the tools dir* +* \ = *(e.g. cppcheck) runs the tool bundle* + + NOTE: The \ aliases are auto-generated by enumerating the bundles located +in *mystoolspath*. + +While the ```git args``` API is designed to cover many the +common use cases, tool and hook authors may need to source the +[runtime](.bundle_runtime.sh) into their script context in order to +use the convenience functions and environment variables as follows: + +```shell-script +. "$(git config mystoolspath).bundle_runtime.sh" +``` + +The runtime shim will instantiate the following environment variables +and convenience functions in the script context for use within a tool +or script. + +``` +TOOLSDIR - absolute path to repo tools dir. Maintained in + a location-agnostic fashion (e.g. if the user + moves the repo, changes tools directory within + repo, etc. +GITREPO - absolute path to the git repo +GITDIR - absolute path to the repo .git directory + +runBundle() - Run a given tool / bundle +is_installed() - true if a given utility is installed +supported_os() - true if running on a supported os +log() - log a message in default color to console +warn() - log a message in yellow to console +err() - log a message in red and exit with non-zero status +``` + +Many of the aforementioned details can be safely ignored if you want +to add a new development tool to the toolset. Simply create a bundle +that follows the layout above, declare the tool command-line options +in the [bundle]/options.sh and any configuration files in +[bundle]/config/. While it should not be necessary to alter the +bundle execution behavior beyond declaring command-line options and +configuration files, you can override it by modifing the [bundle] +run.sh script. diff --git a/.tools/astyle/options.sh b/.tools/astyle/options.sh new file mode 100755 index 00000000..64f922d5 --- /dev/null +++ b/.tools/astyle/options.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +OPTIONS="--options="${TOOLCONFIG}"/style.cfg -n" + +echo $OPTIONS diff --git a/.tools/astyle/run.sh b/.tools/astyle/run.sh new file mode 100755 index 00000000..81d18f68 --- /dev/null +++ b/.tools/astyle/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Instantiate the bundle runtime shim +. "$(git mystoolspath).bundle_runtime.sh" + +# Astyle takes one or more files as an argument. +# --cached or for changed +if [[ $# = 0 ]]; then + for file in $(modifiedSourceFiles); do + runBundle $file + done +elif [[ $1 = '--cached' ]]; then + for file in $(stagedSourceFiles); do + runBundle $file + done +else + eval "set -- $(git rev-parse --sq --prefix "$GIT_PREFIX" "$@")" + runBundle "$@" +fi diff --git a/.tools/bootstrap-dev.sh b/.tools/bootstrap-dev.sh new file mode 100755 index 00000000..ce51abab --- /dev/null +++ b/.tools/bootstrap-dev.sh @@ -0,0 +1,83 @@ +#!/bin/bash +### +# OVERVIEW: +# This script bootstraps the MySensors development environment. +# +# USAGE: +# git clone https://github.com/mysensors/MySensors.git +# cd MySensors +# .tools/bootstrap-dev.sh +## + +## +# Import common utility functions and environment variables +. "$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)/.bundle_runtime.sh" + +check_tool_prerequisites() +{ + local err=0 + for preReq in ${1}; do + if ! is_installed $preReq; then + warn "$preReq not installed or not in current path." + err=1 + fi + done + return $err +} + +check_git_remote() +{ + local url=$( git config --get remote.${1}.url ) + [[ $url == ${2} ]] +} + +install_hooks() +{ + for hook in "${1}"/*.sh; do + local hookname=$(basename $hook) + $(cd "${GITDIR}/hooks"; ln -s -f "../../${TOOLSDIR}hooks/${hookname}" "${hookname%.sh}") + done +} + +### +# Main entry +# +# 1. Check that we are bootstrapping a supported OS/environment +# 2. Validate github remotes include valid upstream and origin +# 3. Check for client commit hook pre-requisites +# 4. Install client commit hook pre-requisites +# 5. Define aliases for conveniently running tool bundles +## + +mysrepo="https://github.com/mysensors/MySensors.git" + +#1 +log "Checking operating system support: ${OSTYPE}..." +is_supported_os ${OSTYPE} || { + err "OS ${OSTYPE} is unknown/unsupported, won't install anything" +} + +#2 +log "Checking github 'origin' & 'upstream' remotes..." +check_git_remote "origin" "${mysrepo}" && warn "Git \"origin\" should point to your github fork, not the github MySensors repo ${mysrep}" + +check_git_remote "upstream" "${mysrepo}" || { + warn "Git \"upstream\" remote not found or incorrectly defined. Configuring remote upstream --> ${mysrepo}..." + git remote add upstream "${mysrepo}" || err "git remote add ${mysrep} failed due to error $?" +} + +#3 +log "Checking tool/utility prerequisites..." +check_tool_prerequisites "astyle cppcheck" || err "One or more required tools not found. Install required tools and re-run ${0}" + +#4 +log "Installing client-side git hooks..." +install_hooks "$(git mystoolspath)hooks" || err "Failed to install git hooks due to error $?..." + +#5 +log "Configuring git aliases for running mysensor tools..." +configure_git_tool_aliases || err "Failed to create git aliases due to error $?..." + +bootstrap_version "--set" + +log "Successfully configured your repo for MySensors development... Thanks for your support!" diff --git a/tools/cppcheck/avr.xml b/.tools/cppcheck/config/avr.xml similarity index 100% rename from tools/cppcheck/avr.xml rename to .tools/cppcheck/config/avr.xml diff --git a/tools/cppcheck/includes.cfg b/.tools/cppcheck/config/includes.cfg similarity index 100% rename from tools/cppcheck/includes.cfg rename to .tools/cppcheck/config/includes.cfg diff --git a/tools/cppcheck/suppressions.cfg b/.tools/cppcheck/config/suppressions.cfg similarity index 100% rename from tools/cppcheck/suppressions.cfg rename to .tools/cppcheck/config/suppressions.cfg diff --git a/.tools/cppcheck/options.sh b/.tools/cppcheck/options.sh new file mode 100755 index 00000000..5cc32d2b --- /dev/null +++ b/.tools/cppcheck/options.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +OPTIONS="--quiet \ + --error-exitcode=1 \ + --force \ + --enable=style,information \ + --library=avr \ + --platform="${TOOLCONFIG}"/avr.xml \ + --includes-file="${TOOLCONFIG}"/includes.cfg \ + --suppressions-list="${TOOLCONFIG}"/suppressions.cfg" + +echo $OPTIONS diff --git a/.tools/cppcheck/run.sh b/.tools/cppcheck/run.sh new file mode 100755 index 00000000..f674d458 --- /dev/null +++ b/.tools/cppcheck/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Instantiate the tool runtime shim +. "$(git mystoolspath).bundle_runtime.sh" + +# cppcheck takes one or more files as an argument. +# --cached or for changed +if [[ $# = 0 ]]; then + for file in $(modifiedSourceFiles); do + runBundle $file + done +elif [[ $1 = '--cached' ]]; then + for file in $(stagedSourceFiles); do + runBundle $file + done +else + eval "set -- $(git rev-parse --sq --prefix "$GIT_PREFIX" "$@")" + runBundle "$@" +fi diff --git a/.tools/hooks/pre-commit.sh b/.tools/hooks/pre-commit.sh new file mode 100755 index 00000000..88b6f115 --- /dev/null +++ b/.tools/hooks/pre-commit.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# instantiate mysensors tool runtime shim +. "$(git mystoolspath).bundle_runtime.sh" + +### +# astyle +# +errorsDetected=0 +for file in $(stagedSourceFiles); do + git astyle $file >/dev/null + if ! git diff-files --quiet $file >&2; then + warn "$file has been updated to match the MySensors core coding style." + errorsDetected=1 + fi +done + +[ $errorsDetected == 1 ] && err "Styling updates applied. Review the changes and use 'git add' to update your staged files." + +### +# cppcheck +# +errorsDetected=0 +for file in $(stagedSourceFiles); do + if ! git cppcheck $file >/dev/null; then + warn "$file failed static analysis." + errorsDetected=1 + fi +done + +[ $errorsDetected == 1 ] && err "Correct the errors until cppcheck passees using 'git cppcheck --cached'." + +exit 0 diff --git a/hooks/install-hooks.sh b/hooks/install-hooks.sh deleted file mode 100755 index 3245cba7..00000000 --- a/hooks/install-hooks.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -PRECOMMIT=hooks/pre-commit -GITROOT=$(git rev-parse --git-dir) -if [[ "$OSTYPE" == "linux-gnu" ]]; then - rm -f $GITROOT/$PRECOMMIT - ln -s ../../$PRECOMMIT.sh $GITROOT/$PRECOMMIT -elif [[ "$OSTYPE" == "darwin"* ]]; then - rm -f $GITROOT/$PRECOMMIT - ln -s ../../$PRECOMMIT.sh $GITROOT/$PRECOMMIT -elif [[ "$OSTYPE" == "cygwin" ]]; then - rm -f $GITROOT/$PRECOMMIT - cp $GITROOT/../$PRECOMMIT.sh $GITROOT/$PRECOMMIT -elif [[ "$OSTYPE" == "msys" ]]; then - rm -f $GITROOT/$PRECOMMIT - cp $GITROOT/../$PRECOMMIT.sh $GITROOT/$PRECOMMIT -elif [[ "$OSTYPE" == "freebsd"* ]]; then - rm -f $GITROOT/$PRECOMMIT - ln -s ../../$PRECOMMIT.sh $GITROOT/$PRECOMMIT -else - echo "Unknown/unsupported OS, won't install anything" -fi diff --git a/hooks/pre-commit.sh b/hooks/pre-commit.sh deleted file mode 100755 index e3190464..00000000 --- a/hooks/pre-commit.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - -OPTIONS="--options=.astylerc -n" -CPPCHECK_OPTIONS="--quiet --error-exitcode=1 --force --enable=style,information --library=avr --platform=tools/cppcheck/avr.xml --includes-file=tools/cppcheck/includes.cfg --suppressions-list=tools/cppcheck/suppressions.cfg" -FILES=$(git diff-index --cached $against | grep -E '[MA] .*\.(c|cpp|h|hpp|ino)$' | cut -d' ' -f 2) - -RETURN=0 -ASTYLE=$(which astyle >/dev/null) -if [ $? -ne 0 ]; then - echo "astyle not installed or not in current path. Unable to check source file format policy. Make sure you have a clean commit, or the CI system will reject your pull request." >&2 -else - ASTYLE=astyle - for FILE in $FILES; do - $ASTYLE $OPTIONS $FILE >/dev/null - $(git diff-files --quiet $FILE) >&2 - if [ $? -ne 0 ]; then - echo "[!] $FILE has been updated to match the MySensors core coding style." >&2 - RETURN=1 - fi - done - - if [ $RETURN -ne 0 ]; then - echo "" >&2 - echo "Styling updates applied. Review the changes and use 'git add' to update your staged data." >&2 - exit $RETURN - fi -fi - -RETURN=0 -CPPCHECK=$(which cppcheck >/dev/null) -if [ $? -ne 0 ]; then - echo "cppcheck not installed or not in current path. Unable to do static code analysis. Make sure you have a clean commit, or the CI system will reject your pull request." >&2 -else - CPPCHECK=cppcheck - for FILE in $FILES; do - $CPPCHECK $CPPCHECK_OPTIONS $FILE 2>&1 - if [ $? -eq 1 ]; then - echo "[!] $FILE has coding issues." >&2 - RETURN=1 - fi - done - - if [ $RETURN -ne 0 ]; then - echo "" >&2 - echo "Make sure you have run cppcheck 1.76.1 or newer cleanly with the following options:" >&2 - echo $CPPCHECK $CPPCHECK_OPTIONS $FILES >&2 - exit 1 - fi -fi -exit $RETURN