#!/bin/bash

get-modules() {
    local module
    for module in $(make -f "${HOST_MK}" showmodules); do
        echo ${module}:${module}.clean
    done
}

get-targets-impl() {
    local target=showtargets
    if ${BUILD_TESTS}; then
        target=showalltargets
    fi
    make -f - ${target} <<'EOM'
gb_PARTIALBUILD := T
include config_$(gb_Side).mk
include $(SRCDIR)/solenv/gbuild/gbuild.mk
gb_FULLDEPS :=

lo_REGISTERED_TARGETS :=

define gb_Module_register_target
gb_Module_CURRENTTARGET := $(1)
gb_Module_CURRENTCLEANTARGET := $(2)

lo_REGISTERED_TARGETS += $(1):$(2)
lo_TEST_TARGET_PATTERNS := CppunitTest/% CustomTarget/bridgetest.done:%

endef

# ignore
gb_Module_add_subsequentcheck_targets :=

$(eval $(call gb_Module_make_global_targets,$(SRCDIR)/RepositoryModule_$(gb_Side).mk))

.PHONY : showtargets showalltargets

# no tests
showtargets :; @echo $(filter-out $(addprefix $(WORKDIR)/,$(lo_TEST_TARGET_PATTERNS)),$(lo_REGISTERED_TARGETS))

showalltargets:; @echo $(lo_REGISTERED_TARGETS)

EOM
}

get-targets() {
    get-targets-impl | grep '^/' | tr ' ' "\n"
}

collect-targets() {
    if ${BUILD_MODULES}; then
        get-modules
    else
        get-targets
    fi | sort > "${STATEDIR}/targets"
}

get-config-var() {
    make --eval 'get-var :; @echo $($(VAR))' -f config_host.mk get-var VAR="$1"
}

make-targets() {
    make gb_FULLDEPS= -j ${PARALLELISM} -sr -f ${HOST_MK} $@
}

check-clean() {
    [[ -z $(find "${OUTDIR}" "${WORKDIR}" \! -type d -print | head -n 1) ]]
}

get-module-target() {
    local target=$1
    local runtarget="${WORKDIR}/Module/${target}"
    if ${BUILD_TESTS}; then
        runtarget="${runtarget} ${WORKDIR}/Module/check/${target} ${WORKDIR}/Module/slowcheck/${target}"
    fi
    echo "${runtarget}"
}

get-module-clean-target() {
    local target=$1
    echo "${WORKDIR}/Clean/Module/${target}"
}

build-target() {
    local target="$1"
    local cleantarget="$2"
    local runtarget="${target}"

    if ${BUILD_MODULES}; then
        runtarget="$(get-module-target ${target})"
        cleantarget="$(get-module-clean-target ${target})"
    fi

    if make-targets ${runtarget}; then
        if ${CLEAN}; then
            if ! ( make-targets ${cleantarget} && check-clean ); then
                return 2
            fi
        fi
    else
        return 1
    fi

    return 0
}

clean() {
    #  Do not use make clean! It removes build dirs too when cross-compiling.
    rm -rf "${WORKDIR}" "${OUTDIR}"
}

prepare-build() {
    rm -f "${STATEDIR}/failed" "${STATEDIR}/finished"
    touch "${STATEDIR}/failed" "${STATEDIR}/finished"
    clean
    # if [[ ${PARALLELISM} -eq 1 ]]; then
        # # populate ccache to make serial builds a bit faster
        # make
        # clean
    # fi
}

do-build-targets() {
    local targets="$1"
    local succeeded="$2"
    local failed="$3"
    local finished="${STATEDIR}/finished"
    local current
    for current in $(comm -23 "${targets}" "${finished}"); do
        local target="${current%:*}"
        local cleantarget="${current#*:}"
        if build-target "${target}" "${cleantarget}"; then
            ${succeeded} "${target}" "${cleantarget}"
        else
            ${failed} "${target}" "${cleantarget}" $?
        fi
        [[ $? -eq 0 ]] || break
    done
    # make sure the finished targets remain in sorted order, so comm
    # works in case of rebuilding failed builds
    sort -u "${finished}" > "${finished}.tmp" && mv "${finished}.tmp" "${finished}"
}

save-succeeded() {
    echo "$1:$2" >> "${STATEDIR}/finished"
    clean
}

save-failed() {
    echo "$1:$2" >> "${STATEDIR}/failed"
    clean
}

build-failed() {
    local target="$1"
    local cleantarget="$2"
    local code=$3
    local cmd="make gb_FULLDEPS= -j ${PARALLELISM} -sr -f ${HOST_MK}"

    if ${BUILD_MODULES}; then
        cleantarget="$(get-module-clean-target ${target})"
        target="$(get-module-target ${target})"
    fi

    case ${code} in
        1)
            echo "build failed:"
            echo "${cmd} ${target}"
            ;;
        2)
            echo "clean failed or left files:"
            echo "${cmd} ${cleantarget}"
            ;;
        *)
            echo >&2 "unknown failure code: ${code}"
            ;;
    esac
    return ${code}
}

build-targets() {
    if ${BUILD_FAILED}; then
        do-build-targets "${STATEDIR}/failed" save-succeeded build-failed
    else
        do-build-targets "${STATEDIR}/targets" save-succeeded save-failed
    fi
}

usage() {
    cat <<EOH
Look for broken dependencies in libreoffice build. It is highly
recommended to use this tool with ccache enabled.

Usage:
lo-rebuild-all.sh [ -b ] [ -c ] [ -f ] [ -h ] [ -n ] [ -s ] [ -t ]

Options:
    -b      check "build" side of cross-compilation (does nothing)
    -c      check if "clean" removes all built files (make clean
            actually only works on module level and I am not quite sure
            it is possible to get it working generally)
    -f      re-run failed builds
    -h      show this text
    -n      do not build tests
    -s      force serial build (slower, but better chance to catch missing deps)
    -t      build individual targets (default builds whole modules)
EOH
}

SRCDIR="$(pwd)"
CLEAN=false
BUILD_FAILED=false
BUILD_MODULES=true
BUILD_SERIAL=false
BUILD_TESTS=true
export gb_Side=host

# a  de g ijklm opqr  uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
while getopts :bcfhnst opt; do
    case ${opt} in
        b) gb_Side=build ;;
        c) CLEAN=true ;;
        f) BUILD_FAILED=true ;;
        h) usage; exit ;;
        n) BUILD_TESTS=false ;;
        s) BUILD_SERIAL=true ;;
        t) BUILD_MODULES=false ;;
        *) echo >&2 "unknown option '${opt}'"; exit 1 ;;
    esac
done
shift $((OPTIND - 1))
[[ -n $1 ]] && SRCDIR="$1"

cd "${SRCDIR}"

# TODO: enable this when my gerrit patch is merged. Fix occurrences in code.
#INSTDIR="$(get-config-var INSTDIR)"
OUTDIR="$(get-config-var OUTDIR)"
WORKDIR="$(get-config-var WORKDIR)"
STATEDIR="$(pwd)"
HOST_MK="${SRCDIR}/Makefile.gbuild"
if ${BUILD_SERIAL}; then
    PARALLELISM=1
else
    PARALLELISM=$(get-config-var PARALLELISM)
fi

export LC_ALL=C

collect-targets
${BUILD_FAILED} || prepare-build
build-targets

# vim: set ts=4 sw=4 et:
