Refactored tools structure & conventions for reuse (#685)

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

References #672
This commit is contained in:
Bruce Lacey
2016-12-11 00:40:32 -08:00
committed by Olivier
parent 37881d56ba
commit 28e0086291
14 changed files with 526 additions and 79 deletions

View File

@@ -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

95
.tools/.bundle_runtime.sh Executable file
View File

@@ -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)"

259
.tools/README.md Normal file
View File

@@ -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.
### <a name="installtools"></a>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
```
##### <a name="buildFromSource"></a>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 sha of bootstrap-dev.sh used to detect an outdated environment>
#####git aliases
* mystoolspath = *returns the absolute path to the tools dir*
* \<bundle name> = *(e.g. cppcheck) runs the tool bundle*
NOTE: The \<bundle> aliases are auto-generated by enumerating the bundles located
in *mystoolspath*.
While the ```git <tool> 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.

5
.tools/astyle/options.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
OPTIONS="--options="${TOOLCONFIG}"/style.cfg -n"
echo $OPTIONS

19
.tools/astyle/run.sh Executable file
View File

@@ -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 <no arg> 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

83
.tools/bootstrap-dev.sh Executable file
View File

@@ -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!"

12
.tools/cppcheck/options.sh Executable file
View File

@@ -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

19
.tools/cppcheck/run.sh Executable file
View File

@@ -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 <no arg> 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

33
.tools/hooks/pre-commit.sh Executable file
View File

@@ -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

View File

@@ -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

View File

@@ -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