#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only

# "scc" script.

#  Copyright (c) 2010-2013 Wind River Systems, Inc.

#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 2 as
#  published by the Free Software Foundation.

#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#  See the GNU General Public License for more details.

version="0.8"

BASEDIR=$(dirname $BASH_SOURCE)

usage()
{
cat << EOF

 scc [--help] [--configs] [--force] [-i] [-w] [--cmds <comma separated list>] [-o outdir:<meta,cfg,patch>] [-D<var>=<value>] [-I<path>] [-v] infiles

      --help:      This message
      --force:     force overwrite output file if it already exists
      --cmds:      list of supported commands. If not passed, all found commands are loaded,
                   valid commands are: patch, kconf, branch, define
      --configs:   Output the list of compiled configuration fragments (if available), -o <outdir> is
                   used to locate the configuration options
      -D:          define <var> to <value> which will be available to sub scripts
      -I:          include path <path> will be searched for files
      -i:          leave intermediate files on failure
      -w:          only warn on missing files
      -v:          verbose output
      -o:          output directory and artifacts. artifacts follow the colon and ar a comma
                   separated list of:
                       meta:  full meta-series (patches, config, branches, etc)
                       cfg:   configure fragment queue
                       patch: patch queue

      infiles      files to preprocess, and output into outfile or stdout

EOF
}

if [ -z "$1" ]; then
	usage
	exit 1
fi

outdir="."
while [ $# -gt 0 ]; do
	case "$1" in
	    --help)
		usage
		exit
		;;
	    -D*|--D*)
		if [ "$1" == "-D" ] || [ "$1" == "--D" ]; then
		    x=$2; shift;
		else
		    x=`echo $1 | sed s%^-*D%%`
		fi
		defines="$defines $x"
		;;
	    -I*|--I*)
		if [ "$1" == "-I" ] || [ "$1" == "--I" ]; then
		    x=$2; shift;
		else
		    x=`echo $1 | sed s%^-*I%%`
		fi
		if [ -n "$x" ] && [ -d "$x" ]; then
		    include_paths="$include_paths -I$x"
		fi
		;;
	    --force|-f)
		force=t
		;;
            --cmds)
                cmds=$2
                shift
                ;;
            --configs)
                configs=t
                ;;
	    -i)
		intermediate=t
		;;
            -p|--patches)
		patches=t
                unified=
		;;
            -c|--config)
		config=t
                unified=
		;;
	    -o)
                # the first field is the output directory
                outdir=$(echo $2 | cut -d: -f1)
                # the next field is the comma separated list of actions, but if it is
                # outdir again, we have no artifacts passed .. so you get them all
                artifacts=$(echo $2 | cut -d: -f2)
                if [ "${artifacts}" == "${outdir}" ]; then
                    artifacts="meta,patch,cfg"
                fi
		shift
		;;
	    -v) verbose=t
		;;
	    *) break
		;;
	esac
	shift
done

err()
{
    echo "$@" 1>&2
}

# args are the input files
infiles=$@
shift

# If --configs was passed, we dump the current queue and exit
if [ -n "${configs}" ]; then
    if [ ! -f "${outdir}/config.queue" ]; then
        echo "[ERROR]: no configuration queue found in outdir (${outdir})"
        usage
        exit 1
    else
        cat ${outdir}/config.queue | sed "s%^%${outdir}/%g" | sed 's%#.*$%%g' | tr '\n' ' '
        exit 0
    fi
fi

if [ -z "$infiles" ]; then
    err "ERROR: a input file must be supplied"
    exit 1
fi
if [ -n "$outfile" ] && [ -f "$outfile" ]; then
    if [ -z "$force" ]; then
	err "ERROR: output file \"$outfile\" exists, and --force was not passed"
	exit 1
    fi
fi

##
## create variables for use in scripts
##
if [ -n "$defines" ]; then
    vars=$(echo $defines | sed 's/,/ /g')
    for v in "$vars"; do
	# eval makes it available for this script
	eval $v
	# echo makes it available for other scripts
	dump_vars=$(echo "$dump_vars"; echo export $v)
    done
fi

header()
{
    echo "#"
    echo "# scc v$version"
    echo "# processed: `date`"
    echo "#"
    echo "# This is a scc output file, do not edit"
    echo "#"
}

reloc_dir()
{
    dir=$1

    echo "# _reloc_dir $dir"
}

prefix()
{
    prefix=$1
}

# override function takes the name of an already defined function and saves it
# as "<foo>_old" (or save_name if passed), and then declares a stub function of
# the original function name to inhibit any future calls to that function.
override_function()
{
    orig=$1
    save_name=$2

    if [ -z "$save_name" ]; then
        save_name=old_$1
    fi

    local ORIG_FUNC=$(declare -f $orig)
    if [ -n "${ORIG_FUNC}" ]; then
        local NEWNAME_FUNC="$save_name${ORIG_FUNC#$orig}"
        eval "$NEWNAME_FUNC"
        # create the override function
        o="$orig () { true; }"
        eval "${o}"
    fi
}

process_file()
{
    local in=$1
    local ret=0
    local done=""
    local scc_output_file="$(mktemp)"


    # source the file, but clear the path, since we only want commands
    # that we declare as valid to run
    (
        # find the path of valid, external commands. The implementation is available
        # in the command file "auto.cmd", which is always loaded
        basename=$(which basename)
        dirname=$(which dirname)
        mkdir=$(which mkdir)
        cp=$(which cp)
        sed=$(which sed)

        # load the commands. We load them all, and then check to see if we
        # should override a specific command to our "do nothing function"
        for c in ${all_cmds}; do
            if [ -n "${verbose}" ]; then
                echo "[INFO]: sourcing command: $c"
            fi
            source $c
        done

        for z in ${cmds_to_override}; do
            if [ -n "${verbose}" ]; then
                echo "[INFO]: disabling command: $z"
            fi
            override_function $(basename "$z" .cmd)
        done

        unset PATH
        (
            set -e
            eval . $in $outfile_append > ${scc_output_file} 2>&1
        )
    )

    if [ $? -ne 0 ]; then
        echo "[ERROR]: processing of file $in failed"
        cat ${scc_output_file}

        # look for common errors so we can point to the right input file

        # 1) /tmp/tmp.gfN6WsbDHN: line 403: cat: No such file or directory
        #     "grep -oh" will only output what matches, which gets us "line 404: .."
        #     cut gets us the second field, which is the line number
        line=$(cat ${scc_output_file} | grep -oh "line.*:" | cut -f2 -d' ' | sed 's/://g')
        if [ -n "$line" ]; then
            let start_line=$line-20
            let end_line=$line+10
            if [ $start_line -lt 0 ]; then
                start_line=0
            fi
            echo ""
            echo "Context around the error is:"
            echo ""
            sed -n -e "$start_line,$end_line p" -e "$end_line q" $in | sed 's/^/    /'
            echo ""
            echo "See pre-processed file $in for more details"
        fi
	rm -f ${scc_output_file}
        exit 1
    elif [ -n "${verbose}" ]; then
        cat ${scc_output_file}
    fi

    rm -f ${scc_output_file}
    return 0
}

## used in the feature scripts
## deprecated: use "define KERNEL_VERSION <value>"
set_kernel_version()
{
    KERNEL_VERSION=$1
}

# used in feature scripts
#  - encodes a generic git command
git()
{
    echo "# _git $@"
}

# used in feature scripts
# arg1: the tag name
tag()
{
    echo "# _git tag $1"
}

# preprocess the input files into a single large meta-script
if [ -n "$intermediate" ]; then
    intermediate_flags="-i"
fi

pre_processed_file="$outfile.pre"
if [ -z "$outfile" ]; then
    pre_processed_file="$(mktemp)"
fi

# load commands
all_cmds=""
cmds=$(echo ${cmds} | sed 's/,/ /g')
for cmd in ${BASEDIR}/scc-cmds/*.cmd; do
    valid=
    if [ -n "${cmds}" ]; then
        # check the cmd against the command line listed ones
        check_cmd=$(basename "$cmd" .cmd)
        echo $cmds | grep -q -w $check_cmd
        if [ $? -eq 0 ]; then
            # the command is in the valid list
            valid=t
        fi
        # we always load "auto.cmd"
        if [ "$check_cmd" == "auto" ]; then
            valid=t
        fi
    else
        valid=t
    fi

    if [ -n "${valid}" ]; then
        if [ -n "${verbose}" ]; then
            echo "[INFO]: loaded $cmd" >&2
        fi
        cmds_to_source="${cmds_to_source} $cmd"
    else
        cmds_to_override="${cmds_to_override} $cmd"
    fi

    all_cmds="${all_cmds} $cmd"
done

# what artifacts need to be produced ?
#
# We can process the inputs into:
#    patch.queue
#    config.queue
#    meta-series (all commands)

# meta is the entire meta series, so we set up a redirect
echo ${artifacts} | grep -w -q meta
if [ $? -eq 0 ]; then
    outfile="${outdir}/meta-series"
    outfile_append=">> $outfile"
    outfile_start="> $outfile"
fi

# And we optionally set up pointers to a patch, config and merge queues
configqueue="/dev/null"
patchqueue="${outdir}/unused.patch.queue"
mergequeue="${outdir}/unused.merge.queue"

echo ${artifacts} | grep -w -q cfg
if [ $? -eq 0 ]; then
    configqueue="${outdir}/config.queue"
    rm -f "${configqueue}"
fi
echo ${artifacts} | grep -w -q patch
if [ $? -eq 0 ]; then
    # remove the emp variant
    rm -f ${patchqueue}
    patchqueue="${outdir}/patch.queue"
    rm -f "${outdir}"/patch*.queue
fi
echo ${artifacts} | grep -w -q merge
if [ $? -eq 0 ]; then
    # remove the emp variant
    rm -f ${mergequeue}
    mergequeue="${outdir}/merge.queue"
    rm -f "${outdir}"/merge.queue
fi

# preprocess the files.
# This does things like make paths absolute, checks for errors, etc.
spp --force $intermediate_flags -o $pre_processed_file $include_paths $infiles
if [ $? -ne 0 ]; then
    err "ERROR: could not process input files: $infiles"
    err "       See ${pre_processed_file} for details"
    exit 1
fi

eval header $outfile_start

# process the files and produce artifacts
process_file ${pre_processed_file}
ret=$?
if [ $ret -ne 0 ]; then
    if [ -z "$intermediate" ]; then
        if [ -n "$outfile" ]; then
	    rm -f $outfile
        fi
	rm -f ${pre_processed_file}
    fi
else
    rm -f ${pre_processed_file}
fi

echo ${patchqueue} | grep -q unused.queue
if [ $? -eq 0 ]; then
    rm -f ${patchqueue}
fi
echo ${mergequeue} | grep -q unused.queue
if [ $? -eq 0 ]; then
    rm -f ${mergequeue}
fi

exit $ret