#!/bin/bash
# minimal config version
MIN_VERSION="3.0"

# Run with tee
if echo "$@" |grep -Eqw '\-v|--verbose' && [ "$1" != 'RESTARTED' ]; then
    # set dir or filename for full log of notamock (with key -v) by environment
    VLOGD=$(realpath ${VLOGD:-./})
    VLOG=${VLOG:-${VLOGD}/$(basename $0)-$(date +%H%M%S).log}
    mkdir -p $(dirname $VLOG)
    date > $VLOG
    echo "==> $(basename $0): log: $VLOG"
    chmod 666 "$VLOG"
    if [ -t 0 ] ; then
        $0 RESTARTED "$@" 2>&1 |tee -a $VLOG | rpmbuild_paint
    else
        $0 RESTARTED "$@" 2>&1 |tee -a $VLOG
    fi
    exit ${PIPESTATUS[0]}
fi

[ "$1" == 'RESTARTED' ] &&  shift

CURPWD=$(pwd)
BUILD_source=$CURPWD
BUILD_dest=$CURPWD
CHANGELOG='no'
PLATFORM=$(rpm --eval %distro_release)
ONLY_ONE=${ONLY_ONE:-"no"}
UPD=${UPD:-"yes"}
BUILD_args=''
REUSABLE=no
BIND=''
DEVNULL='2>/dev/null'
TIME=$(date +%s)
MBINDS=""
ALIAS=""
VARIABLES=()

# unset variables that checks as [ $var ]
unset CLEARPROJECT
unset CLEARCACHE
unset CLEARTMP
unset STATLOG
unset VERBOSE
unset PGREPWAIT
unset NEED_CREATEREPO

CFG=/etc/notamock.cfg
CFGD=/etc/notamock.d

# special config for project
if [ -f './notamock.cfg' ] ; then
    CFG=$(realpath ./notamock.cfg)
    CFGD=''
fi


HLP(){
echo "$0 - simple mock analogue"
echo "Usage:
    cd /project/dir ; $0 <variables> <parameters>"
echo "Parameters:
    -c | --config        -  specify config file, default is /etc/notamock.cfg
    -a | --alias         -  cmdline alias from /etc/notamock.alias
    -h | --help          -  this help
    -r | --release       -  target OS release (ex. 2021.1)
    -v | --verbose       -  verbose mode with a full logging and highlighting
    -u | --update        -  create or recreate new rootfs
    -e | --cmd-pre       -  run command before rpmbuild (default /bin/sh)
    -o | --cmd-post      -  run command after rpmbuild (default /bin/sh)
    -C | --clear-project -  cleanup project dir before build
    -w | --pgrep-wait    -  wait while 'pgrep VAR' before run Package Manager
    -S | --showrc        -  show current configuration and exit
    --clear-cache        -  remove rpms cache
    --clear-tmp          -  remove all from \$TMPD
    --stat               -  statistics log"
echo "Variables:
    all variables specified from config file can be overridden from the cmdline of script in KEY=VALUE format
    also you may set variables from environment, but config file priority is higher
    See /etc/notamock.cfg.
"
echo "Examples:
    notamock -v -r 13        -  build project for rosa13 platform with verbose and colored output
    notamock -v REUSABLE=yes -  build for current platform, retaining the rootfs for future builds of this project
    notamock -v RPMSAVE=yes  -  save BuildRequires packages to notamock's internal RPM repository
    notamock -e              -  pause before rpmbuild to enter interactive shell in the chroot container
"
exit
}

# find and import libmotamock
NOTAMOCKLIB="/usr/share/notamock/libnotamock"
[ -f './libnotamock' ] && NOTAMOCKLIB=./libnotamock
source "$NOTAMOCKLIB"

pre_chroot(){
    vecho "additional actions for rootfs before chroot"
    pushd $1
        for q in $(seq 0 $(( ${#chroot_act[@]} - 1 )) ) ; do
            echo "==> $(basename $0): run: ${chroot_act[$q]}"
            ${chroot_act[$q]}
        done
    popd
}

save_to_cache() {
    if [ -z "$RPMCACHE" ] || [ "$RPMCACHE" = 'no' ] ; then
        return
    fi
    vecho "Save rpms to cache: $RPMCACHE"
    DIRFROM=$1
    echo ''
    mkdir -p $RPMCACHE
    (find ${DIRFROM}/var/cache/dnf -name "*rpm" -type f
        find /var/cache/dnf -name "*rpm" -type f |grep $PLATFORM ) |while read rpm ; do
        if ! [ -f "${RPMCACHE}/$(basename $rpm)" ] ; then
            mv -f "$rpm" ${RPMCACHE}/ && echo -e "${REWRITE}==> $(basename $0): To cache: $(basename $rpm)"
            : > "${RPMCACHE}/.need_update_metadata"
        fi
    done
    echo -e ${REWRITE}
    if [ -f  "${RPMCACHE}/.need_update_metadata" ] ; then
        rm -f "${RPMCACHE}/.need_update_metadata"
        waitfor "${RPMCACHE}/.repodata" "==> $(basename $0): waiting while another notamock process use $CREATE_REPO"
        vrun "$CREATE_REPO $RPMCACHE"
    fi
}

getdeps() {
    spec=$1 ; shift
    ROOT=$1 ; shift
    args=$@
    vecho "Installing build requires"
    if [ "$PM_install_BR" == "INTERNAL" ] ; then
        vecho "get BuildRequires $spec $args"
        rpms=$(LC_ALL=C chroot $ROOT rpmspec -q  --define="_sourcedir $(dirname $spec |sed 's:'$ROOT':/:')"  \
        --buildrequires $(echo $spec |sed 's:'$ROOT':/:') $args |sed -e "s:^:\':" -e "s:$:\':")
        if [ -n "$rpms" ] ; then
            vecho "$PM_install $PM_opts $PM_root_opt $ROOT"
            vecho "$rpms"
            pgrep_wait $PGREPWAIT
            echo "$rpms" |xargs $PM_install $PM_opts $PM_root_opt $ROOT  && return 0
            return 1
        else
            vecho "BuildRequires resolved. Nothing to do"
            return 0
        fi
    elif command -v $(echo $PM_install_BR $args|cut -f1 -d " ") ; then
        vecho "$PM_install_BR ... $spec"
        pgrep_wait $PGREPWAIT
        vrun $PM_install_BR $PM_root_opt $ROOT $PM_opts $spec || return 1
    else
        echo "Unknown command: $PM_install"
        return 1
    fi
}

echo "==> $(basename $0): find parameters"

parse_args(){
    while [ -n "$1" ] ; do
        case "${1}" in
        "-h" | "--help") HLP ;;
        "-a" | "--alias")   shift
                            ALIAS=$1 ;;
        "-c" | "--config")  shift
                            CFG="$1"
                            CFGD='' ;;
        "-r" | "--release") shift ; PLATFORM="$1" ;;
        "-u" | "--update") RF_BUILD=yes ;;
        "-v" | "--verbose") DEVNULL=''
                            VERBOSE=yes;;
        "-S" | "--showrc" ) SHOWRC=yes
                            VERBOSE=yes;;
        "-e" | "--cmd-pre") if [ "$2" ] && [ ${2:0:1} != '-' ] ; then
                                CMDPRE="$2" ; shift
                            else
                                CMDPRE='/bin/sh'
                            fi ;;
        "-o" | "--cmd-post") if [ "$2" ] && [ ${2:0:1} != '-' ] ; then
                                CMDPOST="$2" ; shift
                            else
                                CMDPOST='/bin/sh'
                            fi ;;
        "-w" | "--pgrep-wait" ) shift ; PGREPWAIT=$1 ;;
        "-C" | "--clear-project" ) CLEARPROJECT=yes ;;
        "--clear-cache" ) CLEARCACHE=yes ;;
        "--clear-tmp" ) CLEARTMP=yes ;;
        "--stat" ) STATLOG=yes ; VERBOSE=yes ;;
        "-"*[A-Za-z]*)  BUILD_args="$BUILD_args $1" ;;
        *)  if [[ "$1" == *"="* ]] && [[ "$1" != "-"* ]] ; then
                VARIABLES+=("$1")
            elif echo $1 |grep -qe 'src.rpm$' ;then
                BUILD_source=$(basename $1 |sed 's:-[[:digit:]].*src.rpm::')
                BUILD_dest=$BUILD_source
                SRCRPM=$(realpath $1)
                mkdir -p $BUILD_source
                pushd $BUILD_source || exit ${LINENO}
                    rpm2cpio $SRCRPM | cpio -idmv
                popd
            else
                echo_exit "Unknown parameter: $1" ${LINENO}
            fi;;
        esac
    shift
    done
}

parse_args $@
if [ -n "$ALIAS" ] && [ -f /etc/notamock.alias ] ; then
    for alias in $(echo "$ALIAS" | tr ',' ' ') ; do
        alias_line=$(grep -m1 "^$alias=" /etc/notamock.alias | sed 's/^[^=]*=//' | sed 's/^"//;s/"$//')
        [ -n "$VERBOSE" ] && echo "==> $(basename $0): processing alias: ${alias}: $alias_line"
        if [ -n "$alias_line" ]; then
            eval "alias_args=($alias_line)"
            parse_args "${alias_args[@]}"
        fi
    done
fi

echo "==> $(basename $0): seting up variables from cmdline"

source "$CFG"
VER=${CFG_VERSION:-0.1}
if [ $(echo -e "$VER\n$MIN_VERSION" |sort -V| tail -n1) != "$VER" ] ; then
    echo "Version of config file is less then minimum required for notamock.
$VER < $MIN_VERSION
Please update $CFG from /usr/share/notamock/default.notamock.cfg"
    exit
fi

if [ -d "$CFGD" ] ; then
    for a in $(ls -1 ${CFGD}/. |sort ) ; do
        vrun source ${CFGD}/$a
    done
fi

for var_expr in "${VARIABLES[@]}"; do
    if [[ "$var_expr" == *"+="* ]]; then
        key="${var_expr%%+=*}"
        value="${var_expr#*+=}"
        [[ ! "$value" =~ ^[\"\'].*[\"\']$ ]] && value="\"$value\""
        eval "$key+=($value)"
    elif [[ "$var_expr" == *"="* ]]; then
        key="${var_expr%%=*}"
        value="${var_expr#*=}"
        [[ ! "$value" =~ ^[\"\'].*[\"\']$ ]] && value="\"$value\""
        eval "$key=$value"
    fi
done

cache_par=''
[ "$RPMCACHE" != 'no' ] && cache_par="$PM_addrep_opt $PM_cache"
PM_opts="$PM_opts \
$PM_cfg_opt
$PM_config \
$cache_par \
$PM_adds"


if [ -n "$VERBOSE" ] ; then
    vecho "Variables current values:"
    for a in $(cat $CFG |grep -E '^[[:alnum:]]+.*=' |grep -Ev '\+=|=\(' |sed 's/\+*=.*//'  |sort -u) REUSABLE VERBOSE ALIAS ; do
        eval echo -e "\  \  ${a}: \$$a"
    done
    vecho "Arrays current values:"
    for a in $(cat $CFG |grep -E '^[[:alnum:]]+.*=' |grep -E '\+=|=\(' |sed 's/\+*=.*//'  |sort -u) VARIABLES; do
        eval echo -e "\  \  ${a}: \${$a[@]}"
    done
    [ "$SHOWRC" ] && exit ${LINENO}
    # parameters conflicts
    if [ -n "$CMDPRE" ] || [ -n "$CMDPOST" ] ; then
        echo "==> $(basename $0):  conflict parameters"
        echo "==> $(basename $0):  verbose mode can not be interactive"
        exit ${LINENO}
    fi
fi

if ! [ -f ${BUILD_source}/*.spec ] ; then
    echo "$BUILD_source is not look like project dir"
    exit ${LINENO}
fi

if [ "$CLEARPROJECT" ] ; then
    echo "Cleanup project: ${BUILD_source}"
    if [ -n "$VERBOSE" ] ; then
        rm -rf ${BUILD_source}/{*.{log,buildlog},*RPMS,BUILD*}
    else
        rm -rf ${BUILD_source}/{*.buildlog,*RPMS,BUILD*}
    fi
fi

if ! [ "$USERID" ] ; then
    USERID=$(ls -Gdn ${BUILD_source} |cut -f3 -d ' ')
    [ "$USERID" -eq 0 ] && USERID=$(ls -Gn ${BUILD_source}/*.spec |cut -f3 -d ' ')
    [ "$USERID" -eq 0 ] && USERID=500
fi

[ "$ONLY_ONE" == yes ] && waitfor "/run/notamock_$PLATFORM" "==> $(basename $0): waiting another notamock process"
date > "/run/notamock_$PLATFORM"

if [ "$CLEARCACHE" ]  ; then
    vrun rm -rf  ${RPMCACHE}
fi

if [ "$RPMCACHE" != 'no' ] && [ ! -d "${RPMCACHE}" ] ; then
    vrun "mkdir -p ${RPMCACHE}"
    vrun "$CREATE_REPO $RPMCACHE"
fi

# Build rootfs if nonexistent or forced with '-u'
if [ ! -f ${BASE}/${ROOTFSNAME}/bin/bash ] || [ "$RF_BUILD" ] ; then
    pgrep_wait $PGREPWAIT
    build_new_rootfs || echo_exit "Build new rootfs - error!" ${LINENO}
elif ! cat /proc/mounts |grep -q "${BASE}/$ROOTFSNAME" ; then
    # wait direct update of rootfs by another notamock process
    waitfor "${BASE}/${ROOTFSNAME}.lock" "==> $(basename $0): waiting while another notamock process update rootfs"
    pgrep_wait $PGREPWAIT
    update_rootfs || echo_exit "Update rootfs - error!" ${LINENO}
    UPD_direct='yes'
fi

vecho "make temporary dirs"
if [ "$CLEARTMP" ] ; then
    vrun full_clear_tmp
else
    mkdir -p $TMPD
fi

[ $? != 0 ] &&  echo_exit "Cannot create temporary directory" ${LINENO}

SUFFIX=$(basename $(realpath $BUILD_source))
if [ "$REUSABLE" == 'yes' ] ; then
    BUILD_DIR=${TMPD}/$SUFFIX
    mkdir -p $BUILD_DIR
    vecho "Build dir: $BUILD_DIR"
else
    BUILD_DIR=$(mktemp -d -p $TMPD --suffix "_$SUFFIX")
fi

echo "==> $(basename $0): temp dir: $BUILD_DIR"
OVERLAY=${BUILD_DIR}/overlay
WORKDIR=${BUILD_DIR}/work
UPPERDIR=${BUILD_DIR}/upper
mkdir -p $OVERLAY $WORKDIR $UPPERDIR

LOWERDIR="${BASE}/$ROOTFSNAME"

vrun mount -t overlay overlay -o lowerdir="${LOWERDIR}",upperdir="$UPPERDIR",workdir="$WORKDIR" "$OVERLAY"
[ $? != 0 ] &&  echo_exit "Mounting overlay - error!" ${LINENO}
trap 'clear_overlay "${INTERNAL_DIR}"' EXIT

if ! [ "$UPD_direct" ] ; then
    vrun $PM_update $PM_root_opt "$OVERLAY" $PM_opts
    [ $? != 0 ] &&  echo_exit "Update layered rootfs - error!" ${LINENO}
else
    vecho "skip udate in chroot"
fi

[ "$STATLOG" ] && vecho "STAGE 1: Container:  $(du -sh "$UPPERDIR" |cut -f1), \
time: $(echo $(($(date +%s) - $TIME)) | awk '{printf "%d:%02d:%02d", $1/3600, ($1/60)%60, $1%60}')"

touch  ${OVERLAY}/etc/machine-id

mkdir -p "${OVERLAY}/$INTERNAL_DIR"
if  [ "$BIND_SOURCE" != 'yes' ] ; then
vecho "copy project to container"
    find $BUILD_source -maxdepth 1 -mindepth 1 \
    ! -name BUILDROOT ! -name BUILD \
    ! -name RPMS ! -name SRPMS \
    ! -name .git -exec cp -fax {} "${OVERLAY}/${INTERNAL_DIR}/" \;
    if [ -f ${BUILD_source}/.git/config ] && [ "$CHANGELOG" != 'no' ] ; then
        grep -iq '%changelog' ${OVERLAY}/${INTERNAL_DIR}/*.spec || (\
        echo -en "\n\n%changelog\n* $(date +'%a %b %d %Y') built by notamock \n- git branch: "
        git --git-dir="${BUILD_source}/.git" --work-tree="$BUILD_source" branch --show-current
        echo
        git --git-dir="${BUILD_source}/.git" --work-tree="$BUILD_source" log \
        --pretty=format:"* %cd %cn <%ce> - %h:%n- %s%n%b%n" --since=1000.days --date=format:'%a %b %d %Y' | \
        sed -e 's/[ \t]*$//' -e 's/%%*/%%/g' ) >> ${OVERLAY}/${INTERNAL_DIR}/*.spec
    fi
else
    vecho "mount project dir to container"
    mount -o bind "$BUILD_source" "${OVERLAY}/${INTERNAL_DIR}/"
fi

trap 'clear_overlay "${INTERNAL_DIR}" "${RPMCACHEBIND}"' EXIT

if [ "$BIND" ] ; then
    MBINDS=''
    inter_mount
    trap 'clear_overlay ${MBINDS} "${INTERNAL_DIR}" "${RPMCACHEBIND}"' EXIT
fi

[ "$STATLOG" ] && vecho "STAGE 2: Container:  $(du -sh "$UPPERDIR" |cut -f1), \
time: $(echo $(($(date +%s) - $TIME)) | awk '{printf "%d:%02d:%02d", $1/3600, ($1/60)%60, $1%60}')"

echo 666 > "${OVERLAY}/exitcode"

for str in $(seq 0 $(( ${#ENV[@]} - 1 )) ) ; do
    echo "==> $(basename $0): set env: ${ENV[$str]}"
    echo "${ENV[$str]}" >> "${OVERLAY}/ENV.lst"
done

echo ======== ENV =======
cat "${OVERLAY}/ENV.lst" 2>/dev/null
echo ====================

vecho "Prepare script that will be started by $CHROOT command"
[ "$BUILD_cmd" = "INTERNAL" ] && BUILD_cmd=build_rpm
cat > "${OVERLAY}/start" << EOF
#!/bin/bash
echo "==> \$(basename \$0): Starting rpm build process"

build_rpm() {
    local d="\$PWD"
    local specs=(*.spec)
    if [ \${#specs[@]} -ne 1 ]; then
        echo "build_rpm: one and only one *.spec required in \$PWD" >&2
        return 1
    fi
    local spec="\${specs[0]}"
    local project="\${spec%.spec}"
    local logname="\${project}.build.\$(/bin/date +%Y%m%d-%H%M%S.\$(printf %05d \$\$)).\$(hostname -s).buildlog"

    rpmbuild \\
        --define "_topdir \${d}" \\
        --define "_sourcedir \${d}" \\
        -ba "\$@" "\${spec}" 2>&1 | tee "\${logname}"
    local rc=\${PIPESTATUS[0]}
    echo "build log is saved in \${logname}"
    return \$rc
}
export -f build_rpm

pushd $INTERNAL_DIR >/dev/null || exit 1
    mkdir -p RPMS SRPMS BUILD BUILDROOT
    echo 0 > /exitcode
    chmod 666 /exitcode
    adduser omv -u $USERID -U -M -o
    chown -R omv:omv ../
    $CMDPRE
    echo "==>$BUILD_cmd $BUILD_args"
    su omv -s /bin/bash -c "(. /ENV.lst
        $BUILD_cmd $BUILD_args
        echo \"\\\$?\" > /exitcode)"
    $CMDPOST
popd >/dev/null 
exit \$(cat /exitcode)
EOF

chmod +x "${OVERLAY}/start"
getdeps "${OVERLAY}/$INTERNAL_DIR/*.spec" "$OVERLAY" "$BUILD_args"
[ $? != 0 ] &&  echo_exit "Installing BuildRequires - error" ${LINENO}

[ "$STATLOG" ] && vecho "STAGE 3: Container:  $(du -sh "$UPPERDIR" |cut -f1), \
time: $(echo $(($(date +%s) - $TIME)) | awk '{printf "%d:%02d:%02d", $1/3600, ($1/60)%60, $1%60}')"

pre_chroot ${OVERLAY}

vecho "run main chroot process"
[ "$CHROOT" == 'INTERNAL' ] && CHROOT="chroot_internal"
vrun $CHROOT "$OVERLAY" /start

[ "$STATLOG" ] && vecho "STAGE 4: Container:  $(du -sh "$UPPERDIR" |cut -f1), \
time: $(echo $(($(date +%s) - $TIME)) | awk '{printf "%d:%02d:%02d", $1/3600, ($1/60)%60, $1%60}')"

n=1
while [ -f ${OVERLAY}/$INTERNAL_DIR/SRPMS/*nosrc.rpm ] && [ $n -lt 6 ] ; do
    n=$(($n + 1))
    NOSRCRPM=$(ls -1 ${OVERLAY}/$INTERNAL_DIR/SRPMS/*nosrc.rpm | tail -n1)
    rpm -q --requires $NOSRCRPM |while read a ; do
        pgrep_wait $PGREPWAIT
        echo \"$a\" |xargs $PM_install $PM_opts $PM_root_opt $OVERLAY
    done
    rm -f $NOSRCRPM
    vecho "run chroot process, iteration: $n"
    vrun $CHROOT "$OVERLAY" /start
done

[ "$STATLOG" ] && vecho "STAGE 5: Container:  $(du -sh "$UPPERDIR" |cut -f1), \
time: $(echo $(($(date +%s) - $TIME)) | awk '{printf "%d:%02d:%02d", $1/3600, ($1/60)%60, $1%60}')"

if [ -n "$RPMSAVE" ] && [ "$RPMSAVE" = 'yes' ] ; then
    save_to_cache ${OVERLAY}
fi

ecode=0
mkdir -p ${BUILD_dest} 2>/dev/null
if [ ! -f  ${OVERLAY}/exitcode ] ; then
    echo "==> $(basename $0): build procces: failed"
    ecode=1
elif [ $(cat ${OVERLAY}/exitcode) != 0 ] ; then
    echo "==> $(basename $0): build procces return: $(cat ${OVERLAY}/exitcode)"
    if [ "$BIND_SOURCE" != 'yes' -o "${BUILD_dest}" != "${BUILD_source}" ] ; then
        cp -an ${OVERLAY}/${INTERNAL_DIR}/*  ${BUILD_dest}/ 2>/dev/null
    fi
    ecode=2
else
    echo "==> $(basename $0): build procces return: 0"
    if [ "$BIND_SOURCE" != 'yes' -o "${BUILD_dest}" != "${BUILD_source}" ] ; then
        mkdir -p ${BUILD_dest}/SRPMS ${BUILD_dest}/RPMS
        chown --reference=${OVERLAY}/${INTERNAL_DIR}/RPMS ${BUILD_dest}/*RPMS
        ls ${OVERLAY}/${INTERNAL_DIR}/SRPMS/* > /dev/null 2>&1 && \
            cp -fpr ${OVERLAY}/${INTERNAL_DIR}/SRPMS/* ${BUILD_dest}/SRPMS/
        if cp -fpr ${OVERLAY}/${INTERNAL_DIR}/RPMS/* ${BUILD_dest}/RPMS/ 2>/dev/null; then
            cp -pn ${OVERLAY}/${INTERNAL_DIR}/*  ${BUILD_dest}/ 2>/dev/null
        else
            cp -prn ${OVERLAY}/${INTERNAL_DIR}/*  ${BUILD_dest}/ 2>/dev/null # do not rewrite existing files
            echo_exit "Oops, failed to get packages from builder" 3
        fi
        rmdir ${BUILD_dest}/*SRPMS 2>/dev/null
    fi
fi

vecho  "post build actions"
if [ $ecode -eq 0 ] ; then
    pushd ${BUILD_dest} > /dev/null
        for q in $(seq 0 $(( ${#post_act[@]} - 1 )) ) ; do
            vrun ${post_act[$q]}
        done
    popd > /dev/null
fi

echo "==> $(basename $0): done."
exit $ecode
