VOL-4925 - Build and release components.

jjb/shell/github-release.sh
---------------------------
  o Added a display banner to identify running script and version.
    Recent change to use cp -vs- rsync for release/* copy is not
    visible in the job log -- timing issue ?
  o Source common lib stacktrace.sh and traputils.sh to be more
    verbose when the script edits (courtesy of set -e).
  o Debugging statements added.
  o Use stacktrace.sh to display callstack when script exits with error.

jjb/shell/common/README.md
jjb/shell/common/common.sh
jjb/shell/common/example.sh
jjb/shell/common/preserve_argv.sh
jjb/shell/common/common/sh/tempdir.sh
jjb/shell/common/common/sh/traputils.sh
jjb/shell/common/common/sh/stacktrace.sh
-----------------------------------------
  o Create a common library of reusable utility shell scripts.
  o tempdir.sh    - automatic creation and removal of mktempdir()
  o stacktrace.sh - display script call stack.
  o traputils.sh  - register an interrupt handler calling stacktrace on exit.

Change-Id: I563948f078cf33fef4a58be2b7455f07a3bd9e3a
diff --git a/jjb/shell/common/README.md b/jjb/shell/common/README.md
new file mode 100644
index 0000000..4c171b6
--- /dev/null
+++ b/jjb/shell/common/README.md
@@ -0,0 +1,28 @@
+# Common shell utilities
+
+This subdirectory contains library shell scripts that support interrupt
+handling and displaying a stack trace.
+
+## Hierarchy may appear strange (common/common) but setup is intentional:
+
+common/
+├── common
+│   └── sh
+│       ├── stacktrace.sh
+│       ├── tempdir.sh
+│       └── traputils.sh
+├── common.sh
+└── preserve_argv.sh
+
+### Usage: Source individual libraries by path
+
+source common/common/sh/stacktrace.sh
+source common/common/sh/tempdir.sh
+
+### Usage: common.sh -- one-liner for sourcing sets of libraries.
+
+source common/common.sh
+source common/common.sh --tempdir
+source common/common.sh --traputils --stacktrace
+
+
diff --git a/jjb/shell/common/common.sh b/jjb/shell/common/common.sh
new file mode 100644
index 0000000..268c701
--- /dev/null
+++ b/jjb/shell/common/common.sh
@@ -0,0 +1,69 @@
+# -*- sh -*-
+## -----------------------------------------------------------------------
+## Intent:
+##   o This script can be used as a one-liner for sourcing common scripts.
+##   o Preserve command line arguments passed.
+##   o Accept common.sh arguments specifying a set of libraries to load.
+##   o Dependent common libraries will automatically be sourced.
+## -----------------------------------------------------------------------
+## Usage:
+##   o source common.sh --common-args-begin-- --tempdir
+##   o source common.sh --common-args-begin-- --stacktrace
+## -----------------------------------------------------------------------
+
+# __DEBUG_COMMON__=1
+[[ -v __DEBUG_COMMON__ ]] && echo " ** ${BASH_SOURCE[0]}: BEGIN"
+
+## -----------------------------------------------------------------------
+## Intent: Anonymous function used to source common shell libs
+## -----------------------------------------------------------------------
+## Usage: source common.sh '--stacktrace'
+## -----------------------------------------------------------------------
+function __anon_func__()
+{
+    local iam="${BASH_SOURCE[0]%/*}"
+    local cab='--common-args-begin--'
+
+    declare -a args=($*)
+
+    local raw
+    raw="$(readlink --canonicalize-existing --no-newline "${BASH_SOURCE[0]}")"
+    local top="${raw%/*}"
+    local common="${top}/common/sh"
+
+    local arg
+    for arg in "${args[@]}";
+    do
+	    case "$arg" in
+	        --tempdir)    source "${common}"/tempdir.sh    ;;
+	        --traputils)  source "${common}"/traputils.sh  ;;
+	        --stacktrace) source "${common}"/stacktrace.sh ;;
+            *) echo "ERROR ${BASH_SOURCE[0]}: [SKIP] unknown switch=$arg" ;;
+	    esac
+    done
+
+    return
+}
+
+##----------------##
+##---]  MAIN  [---##
+##----------------##
+source "${BASH_SOURCE[0]%/*}/preserve_argv.sh" # pushd @ARGV
+
+if [ $# -gt 0 ] && [ "$1" == '--common-args-begin--' ]; then
+    shift # remove arg marker
+fi
+
+if [ $# -eq 0 ]; then
+    # common.sh defaults
+    set -- '--tempdir' '--traputils' '--stacktrace'
+fi
+
+__anon_func__ "$@"
+unset __anon_func__
+source "${BASH_SOURCE[0]%/*}/preserve_argv.sh" # popd @ARGV
+
+[[ -v __DEBUG_COMMON__ ]] && echo " ** ${BASH_SOURCE[0]}: END"
+: # NOP
+
+# [EOF]
diff --git a/jjb/shell/common/common/sh/stacktrace.sh b/jjb/shell/common/common/sh/stacktrace.sh
new file mode 100644
index 0000000..4bbbe8e
--- /dev/null
+++ b/jjb/shell/common/common/sh/stacktrace.sh
@@ -0,0 +1,73 @@
+# -*- sh -*-
+## -----------------------------------------------------------------------
+## Intent  : Register an interrupt handler to generate a stack trace
+##           whenever shell commands fail or prematurely exist.
+## Usage   : source stacktrace.sh
+## See Also: traputils.sh
+##           https://gist.github.com/ahendrix/7030300
+## -----------------------------------------------------------------------
+## set -e silently exiting on error is less than helpful.
+## Use a call stack function to expose problem source.
+## -----------------------------------------------------------------------
+
+## -----------------------------------------------------------------------
+## Intent: Trap/exit on error displaying context on the way out.
+## -----------------------------------------------------------------------
+function errexit()
+{
+    local err=$?
+    set +o xtrace
+    local code="${1:-1}"
+
+    local prefix="${BASH_SOURCE[1]}:${BASH_LINENO[0]}"
+    echo -e "\nOFFENDER: ${prefix}"
+    if [ $# -gt 0 ] && [ "$1" == '--stacktrace-quiet' ]; then
+        code=1
+    else
+        echo "ERROR: '${BASH_COMMAND}' exited with status $err"
+    fi
+
+    # Print out the stack trace described by $function_stack
+    if [ ${#FUNCNAME[@]} -gt 2 ]
+    then
+	    echo "Call tree:"
+	    for ((i=1;i<${#FUNCNAME[@]}-1;i++))
+	    do
+	        echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
+	    done
+    fi
+
+    echo "Exiting with status ${code}"
+    echo
+    exit "${code}"
+}
+
+##----------------##
+##---]  MAIN  [---##
+##----------------##
+
+# trap ERR to provide an error handler whenever a command exits nonzero
+#  this is a more verbose version of set -o errexit
+trap 'errexit' ERR
+# trap 'errexit' EXIT
+# trap 'errexit' DEBUG
+
+# setting errtrace allows our ERR trap handler to be propagated to functions,
+#  expansions and subshells
+set -o errtrace
+
+## Unit tests may need to disable interrupts to avoid control loss during segv
+# source $(dirname ${BASH_SOURCE})/traputils.sh
+source "${BASH_SOURCE[0]/stacktrace.sh/traputils.sh}"
+
+## -----------------------------------------------------------------------
+## Colon is a shell NOP operator that will set and return $?==0 to caller.
+## Sourced scripts adding ':' as the final operator helps prevent an ugly
+## manifestation.  Syntax errors normally will cause early script
+## termination with no context, NOP allows control to return back to the
+## caller where stacktrace can properly document the entry point.
+## -----------------------------------------------------------------------
+
+: # NOP w/side effects
+
+# EOF
diff --git a/jjb/shell/common/common/sh/tempdir.sh b/jjb/shell/common/common/sh/tempdir.sh
new file mode 100644
index 0000000..40d842e
--- /dev/null
+++ b/jjb/shell/common/common/sh/tempdir.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+## -----------------------------------------------------------------------
+## Intent: Automatic setup/teardown a scratch area for testing.
+## -----------------------------------------------------------------------
+## Usage:
+##   o source common/common.sh --common-args-begin-- --tempdir
+##   o source common/common/sh/tempdir.sh
+## -----------------------------------------------------------------------
+## Note:
+##   o Argument --preserve can be used to inhibit tempdir removal
+##     and test file cleanup at script exit.
+## -----------------------------------------------------------------------
+
+# __DEBUG_COMMON__=1
+[[ -v __DEBUG_COMMON__ ]] && echo "${BASH_SOURCE[0]}: BEGIN"
+
+##-------------------##
+##---]  GLOBALS  [---##
+##-------------------##
+if [[ ! -v __COMMON_TEMP_DIRS__ ]]; then
+    declare -g -a __COMMON_TEMP_DIRS__
+fi
+
+## -----------------------------------------------------------------------
+## Intent: Allocate a transient tmpdir
+## -----------------------------------------------------------------------
+## NOTE:
+##   o Caller should export TMPDIR="${path}"
+##   o TMPDIR= only visible when sourced form top level parent scope
+## -----------------------------------------------------------------------
+function common_tempdir_mkdir()
+{
+    local var="$1"; shift
+    
+    local pkgbase="${0##*/}" # basename
+    local pkgname="${pkgbase%.*}"
+
+    local __junk__
+    local __junk__="$(mktemp -d -t "${pkgname}.XXXXXXXXXX")"
+    
+    __COMMON_TEMP_DIRS__+=("$__junk__")
+
+    export TMPDIR="$__junk__"
+    eval "${var}=${__junk__}"# replace with typeset
+
+    # declare -p __COMMON_TEMP_DIRS__
+    return
+}
+
+## -----------------------------------------------------------------------
+## Intent: Preserve allocated temp directories on exit
+## -----------------------------------------------------------------------
+## Usage:
+##   o kill -s 'SIGSYS' $$
+## -----------------------------------------------------------------------
+function sigtrap_preserve()
+{
+    local dir
+    for dir in "${__COMMON_TEMP_DIRS__[@]}";
+    do
+	    touch "$dir/.preserve"
+    done
+}
+trap sigtrap_preserve SIGSYS
+
+## -----------------------------------------------------------------------
+## Intent: Tempdir cleanup on exit
+## -----------------------------------------------------------------------
+function sigtrap()
+{
+    local dir
+    # declare -p __COMMON_TEMP_DIRS__
+    for dir in "${__COMMON_TEMP_DIRS__[@]}";
+    do
+	if [ -e "$dir/.preserve" ]; then
+	    echo "Preserving test output: $TMPDIR"
+	    find "$TMPDIR" -ls
+	else
+	    /bin/rm -fr "$TMPDIR"
+	fi
+    done
+    return
+}
+trap sigtrap EXIT
+
+[[ -v __DEBUG_COMMON__ ]] && echo "${BASH_SOURCE[0]}: END"
+unset __DEBUG_COMMON__
+
+: # NOP
+
+# [EOF]
diff --git a/jjb/shell/common/common/sh/traputils.sh b/jjb/shell/common/common/sh/traputils.sh
new file mode 100644
index 0000000..ded5cc7
--- /dev/null
+++ b/jjb/shell/common/common/sh/traputils.sh
@@ -0,0 +1,124 @@
+# -*- sh -*-
+## -----------------------------------------------------------------------
+## Intent: Helper script for disabling shell interrupt handlers
+## Usage:  source traputils.sh
+## -----------------------------------------------------------------------
+
+## -----------------------------------------------------------------------
+## Intent:
+## -----------------------------------------------------------------------
+function trap_stack_name()
+{
+    local sig=${1//[^a-zA-Z0-9]/_}
+    echo "__trap_stack_${sig}"
+}
+
+## -----------------------------------------------------------------------
+## Intent:
+## -----------------------------------------------------------------------
+function extract_trap()
+{
+    echo ${@:3:$(($#-3))}
+}
+
+## -----------------------------------------------------------------------
+## Intent:
+## -----------------------------------------------------------------------
+function get_trap()
+{
+    eval echo $(extract_trap `trap -p $1`)
+}
+
+## -----------------------------------------------------------------------
+## Intent: Push current signal handler so a new one can be installed.
+## -----------------------------------------------------------------------
+## Usage:
+##   trap_push 'func_one' 'SIGUSR1' 'SIGUSR2'
+##   trap_push 'func_two' 'SIGUSR1' 'SIGUSR2'
+##   kill -s 'SIGUSR1'   # func_two()
+##   trap_pop 'SIGUSR1'
+##   kill -s 'SIGUSR1'   # func_one()
+##   kill -s 'SIGUSR2'   # func_two()
+## -----------------------------------------------------------------------
+trap_push()
+{
+    local new_trap="$1"; shift
+    declare -a sigs=($*)
+    
+    # local sigs=$*
+    local sig
+    for sig in "${sigs[@]}";
+    do
+	    local stack_name="$(trap_stack_name "$sig")"
+	    local old_trap="$(get_trap "$sig")"
+        
+	    # eval '__trap_stack_SIGUSR1[${#__trap_stack_SIGUSR1[@]}]=$old_trap'
+	    # __trap_stack_SIGUSR1[${#__trap_stack_SIGUSR1[@]}]=one
+	    # trap two SIGUSR1
+        
+	    eval "${stack_name}"'[${#'"${stack_name}"'[@]}]=$old_trap'
+	    trap "${new_trap}" "$sig"
+    done
+    return
+}
+
+## -----------------------------------------------------------------------
+## Intent: Restore the last signal handler pushed
+## -----------------------------------------------------------------------
+function trap_pop()
+{
+    local sigs=$*
+    for sig in $sigs; do
+	    local stack_name=`trap_stack_name "$sig"`
+	    local count; eval 'count=${#'"${stack_name}"'[@]}'
+	    [[ $count -lt 1 ]] && return 127
+	    local new_trap
+	    local ref="${stack_name}"'[${#'"${stack_name}"'[@]}-1]'
+	    local cmd='new_trap=${'"$ref}"; eval $cmd
+	    trap "${new_trap}" "$sig"
+	    eval "unset $ref"
+    done
+}
+
+## -----------------------------------------------------------------------
+## Intent:
+## -----------------------------------------------------------------------
+function trap_prepend()
+{
+    local new_trap=$1
+    shift
+    local sigs=$*
+    for sig in $sigs; do
+	    if [[ -z $(get_trap $sig) ]]; then
+	        trap_push "$new_trap" "$sig"
+	    else
+	        trap_push "$new_trap ; $(get_trap $sig)" "$sig"
+	    fi
+    done
+}
+
+## -----------------------------------------------------------------------
+## Intent:
+## -----------------------------------------------------------------------
+function trap_append()
+{
+    local new_trap=$1
+    shift
+    local sigs=$*
+    for sig in $sigs; do
+	    if [[ -z $(get_trap $sig) ]]; then
+	        trap_push "$new_trap" "$sig"
+	    else
+	        trap_push "$(get_trap $sig) ; $new_trap" "$sig"
+	    fi
+    done
+}
+
+: # NOP
+
+# [SEE ALSO]
+# -----------------------------------------------------------------------
+# https://stackoverflow.com/questions/16115144/bash-save-and-restore-trap-state-easy-way-to-manage-multiple-handlers-for-trap
+# -----------------------------------------------------------------------
+
+# [EOF]
diff --git a/jjb/shell/common/example.sh b/jjb/shell/common/example.sh
new file mode 100755
index 0000000..0c0e46a
--- /dev/null
+++ b/jjb/shell/common/example.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+## -----------------------------------------------------------------------
+## Intent: An example script showing how to source common libraries
+##    and produce a stack trace on script error/exit
+## -----------------------------------------------------------------------
+
+declare -g fatal=1 # assign to view stack trace
+
+echo "$0: ENTER"
+
+echo "$0: Source library shell includes"
+
+declare -g pgmdir="${0%/*}" # dirname($script)
+declare -a common_args=()
+common_args+=('--common-args-begin--')
+# common_args+=('--traputils')
+# common_args+=('--stacktrace')
+# common_args+=('--tempdir')
+source "${pgmdir}/common.sh" "${common_args[@]}"
+
+echo "$0: define foo(), bar() & tans()"
+
+function foo()
+{
+    echo "${FUNCNAME}: hello - stack_frame[1]"
+    bar
+}
+
+function bar()
+{
+    echo "${FUNCNAME}: hello - stack_frame[2]"
+    tans
+}
+
+function tans()
+{
+    declare -g fatal
+    echo "${FUNCNAME}: early exit for stacktrace"
+    [[ $fatal -eq 1 ]] && exit 1
+    return
+}
+
+echo "$0: calling foo() for a stack trace"
+foo
+
+echo "$0: ENTER"
+
+## -----------------------------------------------------------------------
+# % ./example.sh
+# ./example.sh: ENTER
+# ./example.sh: Source library shell includes
+# ./example.sh: define foo(), bar() & tans()
+# ./example.sh: calling foo() for a stack trace
+# foo: hello - stack_frame[1]
+# bar: hello - stack_frame[2]
+# tans: early exit for stacktrace
+# 
+# OFFENDER: ./example.sh:1
+# ERROR: 'exit 1' exited with status 1
+# Call tree:
+# 1: ./example.sh:25 tans(...)
+# 2: ./example.sh:19 bar(...)
+# 3: ./example.sh:37 foo(...)
+# Exiting with status 1
+
+# [EOF]
diff --git a/jjb/shell/common/preserve_argv.sh b/jjb/shell/common/preserve_argv.sh
new file mode 100644
index 0000000..a0879b9
--- /dev/null
+++ b/jjb/shell/common/preserve_argv.sh
@@ -0,0 +1,56 @@
+# -*- sh -*-
+## -----------------------------------------------------------------------
+## Intent:
+##   o This is a helper module for common/common.sh to support passing
+##     switches into the library as part of the source command.
+##   o A delimiter must be inserted into @ARGV to prevent consumption
+##     of command line arguments passed into the parent script.
+## -----------------------------------------------------------------------
+## Usage: source preserve_argv.sh
+## -----------------------------------------------------------------------
+
+# __DEBUG_COMMON__=1
+[[ -v __DEBUG_COMMON__ ]] && echo " ** ${BASH_SOURCE[0]}: BEGIN"
+
+## -----------------------------------------------------------------------
+## Intent: Preserve command line args allowing common.sh sourcing.
+## -----------------------------------------------------------------------
+## Required: Caller is required to inline and shift the @ARGV {cab} marker
+## -----------------------------------------------------------------------
+## Usage: source preserve_argv.sh
+##   source "$(OPT_ROOT}"/common/common.sh --common-args-begin-- --stacktrace
+## -----------------------------------------------------------------------
+function __anon_func_preserve_argv__()
+{
+    local key='__preserve_argv_stack__'
+    local cab='--common-args-begin--'
+    local iam="${BASH_SOURCE[0]%/*}"
+
+    if [[ -v __preserve_argv_stack__ ]]; then
+        # echo " ** ${iam}: [POPD] environment: ${__preserve_argv_stack__[@]}"
+        set -- "${__preserve_argv_stack__[@]}"
+        unset __preserve_argv_stack__
+    elif [ $# -eq 0 ] || [ "$1" != "$cab" ]; then
+        echo " ** ${iam} ERROR: ARGV marker not found: ${cab}"
+        echo " ** command: $0"
+        exit 1
+    else
+        ## caller (common.sh) shift {cab} from argv
+        declare -g -a __preserve_argv_stack__=("$@")
+        # echo " ** ${iam}: [PUSHD] environment: ${__preserve_argv_stack__[@]}"
+    fi
+
+    return
+}
+
+##----------------##
+##---]  MAIN  [---##
+##----------------##
+__anon_func_preserve_argv__ "$@"
+unset __anon_func_preserve_argv__
+
+[[ -v __DEBUG_COMMON__ ]] && echo " ** ${BASH_SOURCE[0]}: CLOSE"
+
+: # NOP for set -e -vs- [[ -v __DEBUG_COMMON__ ]]
+
+# [EOF]