distro-manifest-generator: add support for JSON output format
[AGL/meta-agl.git] / scripts / distro-manifest-generator.sh
index 9a910e5..834cde2 100755 (executable)
 #
 ################################################################################
 
+mode=deploy
+manifest=
+verbose=0
+format=bash
+sourcefile=
+
+function info() { echo "$@" >&2; }
+function error() { echo "$BASH_SOURCE: $@" >&2; }
+function out() { echo -n "$@"; }
+function out_object() {
+       # expected stdin stream is:
+       # --------------
+       # key
+       # value
+       # key
+       # value
+       # ...
+       # --------------
+       local sep=""
+       local k
+       case $format in
+               bash)
+                       while read x; do
+                               [[ -z "$k" ]] && { k="$x"; continue; }
+                               out "$sep${k}="
+                               out_value "$x"
+                               sep=$'\n'
+                               k=
+                       done
+                       out "$sep"
+                       ;;
+               json)
+                       out "{"
+                       while read x; do
+                               [[ -z "$k" ]] && { k="$x"; continue; }
+                               out "$sep\"${k}\":"
+                               out_value "$x"
+                               sep=","
+                               k=
+                       done
+                       out "}"
+                       ;;
+       esac
+}
+
+function out_array() {
+       # expected stdin stream is:
+       # --------------
+       # value
+       # value
+       # ...
+       # --------------
+       local sep=""
+       case $format in
+               bash)
+                       while read x; do
+                               out "$sep"
+                               out_value "$x"
+                               sep=" "
+                       done
+                       ;;
+               json)
+                       out "["
+                       while read x; do
+                               out $sep
+                               out_value "$x"
+                               sep=","
+                       done
+                       out "]"
+                       ;;
+       esac
+}
+
+function out_value() {
+       # string
+       # number
+       # object
+       # array
+       # 'true'
+       # 'false'
+       # 'null'
+
+       x=$1
+
+       # litterals
+       if [[ "$x" =~ ^(true|false|null)$ ]]; then
+               out "$x"
+       # number
+       elif [[ "$x" =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]]; then
+               out "$x"
+       # object
+       elif [[ "$x" =~ ^\{.*\}$ ]]; then
+               out "$x"
+       # array
+       elif [[ "$x" =~ ^\[.*\]$ ]]; then
+               out "$x"
+       # string
+       else
+               out "\"$(sed 's/\("\)/\\\1/g' <<<$x)\""
+       fi
+}
+
+function out_comment() {
+       case $format in
+               bash)
+                       [[ "$verbose" == 1 ]] && echo "# $@" || true
+                       ;;
+               json)
+                       ;;
+       esac
+}
+
 function _getgitmanifest() {
        # this function takes the setup.manifest generated by setup script and uses DIST_METADIR
        # to analyze git repos
 
        local manifest=$1 mode=$2
-       [[ -f $manifest ]] && source $manifest || { echo "$BASH_SOURCE: Invalid setup manifest '$manifest'" >&2; return 1; }
+       [[ -f $manifest ]] && source $manifest || { error "Invalid setup manifest '$manifest'"; return 1; }
        [[ ! -d "$DIST_METADIR" ]] && {
-               echo "$BASH_SOURCE: Invalid meta directory. Check variable DIST_METADIR in manifest file '$manifest'." >&2
-               echo "$BASH_SOURCE: Also, check directory '$DIST_METADIR'." >&2
+               error "Invalid meta directory. Check variable DIST_METADIR in manifest file '$manifest'."
+               error "$BASH_SOURCE: Also, check directory '$DIST_METADIR'."
                return 2
        }
        local GIT=$(which git) REALPATH=$(which realpath)
-       [[ ! -x $GIT ]] && { echo "$BASH_SOURCE: Unable to find git command in $PATH." >&2; return 3; }
-       [[ ! -x $REALPATH ]] && { echo "$BASH_SOURCE: Unable to find realpath command in $PATH." >&2; return 4; }
+       [[ ! -x $GIT ]] && { error "$BASH_SOURCE: Unable to find git command in $PATH."; return 3; }
+       [[ ! -x $REALPATH ]] && { error "$BASH_SOURCE: Unable to find realpath command in $PATH."; return 4; }
 
        local gitrepo gitrev metagitdir sep=""
        DIST_LAYERS=""
@@ -58,6 +170,18 @@ function _getgitmanifest() {
        # layers checksum
        DIST_LAYERS_MD5=$(echo $DIST_LAYERS|md5sum -|awk '{print $1;}')
 
+       # in json, transform layers in an object, features in array
+       [[ "$format" == "json" ]] && {
+               DIST_FEATURES=$(for x in $DIST_FEATURES; do
+                       echo $x
+               done | out_array)
+               DIST_LAYERS=$(for x in $DIST_LAYERS; do
+                       echo ${x%%:*}
+                       echo ${x#*:}
+               done | out_object)
+       }
+
+
        # compute build hash
        DIST_BUILD_HASH="F${DIST_FEATURES_MD5:0:8}-L${DIST_LAYERS_MD5:0:8}"
        DIST_BUILD_ID="${DIST_DISTRO_NAME}-${DIST_MACHINE}-F${DIST_FEATURES_MD5:0:8}-L${DIST_LAYERS_MD5:0:8}"
@@ -81,66 +205,99 @@ function _getgitmanifest() {
        EXTRA_VARS[target]="DIST_LAYERS DIST_BUILD_HASH DIST_BUILD_ID"
        EXTRA_VARS[sdk]="DIST_LAYERS DIST_BUILD_HASH DIST_BUILD_ID"
 
-       echo "# setup variables in $mode mode"
-       for x in ${SETUP_VARS[$mode]}; do
-               echo "$x=\"${!x}\""
-       done
-       echo
+       # BITBAKE_VARS may be defined from external file to source (--source arg)
+       # this is used to dump extra vars from inside bitbake recipe
 
-       echo "# set by $BASH_SOURCE"
-       for x in ${EXTRA_VARS[$mode]}; do
-               echo "$x=\"${!x}\""
-       done
+       { for x in ${SETUP_VARS[$mode]} ${EXTRA_VARS[$mode]} ${BITBAKE_VARS[$mode]}; do
+               k=$x
+               [[ "$format" == "json" ]] && {
+                       k=${k#DIST_} # remove prefix
+                       k=${k,,*} # to lower case
+               }
+               echo $k
+               echo ${!x}
+       done } | out_object
 }
 
 function getmanifest() {
        local rc=0
-       echo "# DISTRO BUILD MANIFEST"
-       echo
+       out_comment "DISTRO BUILD MANIFEST"
+       out_comment
 
        # add layers manifest
-       echo "# ----- this fragment has been generated by $BASH_SOURCE"
+       out_comment "----- this fragment has been generated by $BASH_SOURCE"
        _getgitmanifest $1 $2 || rc=$?
-       echo "# ------------ end of $BASH_SOURCE fragment --------"
+       out_comment "------------ end of $BASH_SOURCE fragment --------"
 
        return $rc
 }
 
+function __usage() {
+       cat <<EOF >&2
+Usage: $BASH_SOURCE [-v|--verbose] [-f|--format <fmt>] [-m|--mode <mode>] [-s|--source <file>] <setup_manifest_file>
+   Options:
+      -v|--verbose: generate comments in the output file
+      -s|--source: extra file to source (get extra variables generated from bitbake recipe)
+      -f|--format: specify output format: 'bash' or 'json'
+      -m|--mode: specify the destination for the generated manifest
+         'deploy' : for the tmp/deploy/images/* directories
+         'target' : for the manifest to be installed inside a target image
+         'sdk'    : for the manifest to be installed inside the SDK
+
+   <setup_manifest_file> is the input manifest generated from setup script
+EOF
+}
+
 set -e
 
-verbose=0
-if [[ "$1" =~ ^(-v|--verbose)$ ]]; then
-       shift
-       verbose=1
-fi
-
-if [[ -f "$1" ]]; then
-       manifest=$1
-       shift
-
-       # default mode
-       mode=${1:-deploy}
-       case $mode in
-               deploy|target|sdk) mode=$mode;;
-               *) echo "Invalid mode specified. Allow modes are: deploy target sdk"; exit 42;;
+tmp=$(getopt -o h,v,m:,f:,s: --long help,verbose,mode:,format:,source: -n "$BASH_SOURCE" -- "$@") || {
+       error "Invalid arguments."
+       __usage
+       exit 1
+}
+eval set -- $tmp
+
+while true; do
+       case "$1" in
+               -h|--help) __usage; exit 0;;
+               -v|--verbose) verbose=1; shift ;;
+               -f|--format) format=$2; shift 2;;
+               -m|--mode) mode=$2; shift 2;;
+               -s|--source) sourcefile=$2; shift 2;;
+               --) shift; break;;
+               *) fatal "Internal error";;
        esac
+done
 
-       getmanifest $manifest $mode | { [[ $verbose == 1 ]] && cat || sed -e 's/#.*$//g;/^\s*$/d'; }
-       exit ${PIPESTATUS[0]}
-else
-       cat <<EOF >&2
-Usage: $0 [-v|--verbose] <setup_manifest_file> [mode]
-   Options:
-      -v|--verbose: generate comments in the output file
+manifest=$1
+shift
+[[ ! -f "$manifest" ]] && { __usage; exit 1; }
 
-   <setup_manifest_file> is generated from setup script in the specified build dir
+case $mode in
+       deploy|target|sdk) ;;
+       *) error "Invalid mode specified. Allowed modes are: 'deploy', 'target', 'sdk'"; __usage; exit 42;;
+esac
 
-   [mode] specifies the destination for the generated manifest
-   Accepted values:
-      'deploy' : for the tmp/deploy/images/* directories
-      'target' : for the manifest to be installed inside a target image
-      'sdk'    : for the manifest to be installed inside the SDK
-EOF
-       exit 56
-fi
+case $format in
+       bash|json) ;;
+       *) error "Invalid format specified. Allowed formats are 'json' or 'bash'"; __usage; exit 43;;
+esac
+
+info "Generating manifest: mode=$mode format=$format manifest=$manifest"
+[[ -f "$sourcefile" ]] && {
+       info "Sourcing file $sourcefile"
+       . $sourcefile
+       # this may define extra vars: to be taken into account BITBAKE_VARS must be defined
+}
+
+[[ "$format" == "json" ]] && {
+       # if jq is present, use it to format json output
+       jq=$(which jq || true)
+       [[ -n "$jq" ]] && {
+               getmanifest $manifest $mode | $jq ""
+               exit ${PIPESTATUS[0]}
+       }
+}
+
+getmanifest $manifest $mode