#! /bin/sh


: ${SHTK_MODULESDIR:=/usr/pkg/share/shtk}

if [ ! -f "${SHTK_MODULESDIR}/base.subr" ]; then
    echo "${0##*/}: E: Cannot open ${SHTK_MODULESDIR}/base.subr" 1>&2
    echo "${0##*/}: E: Does SHTK_MODULESDIR point to the right location?" 1>&2
    exit 1
fi

. "${SHTK_MODULESDIR}/base.subr"
# Copyright 2012 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
# * Neither the name of Google Inc. nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# \file sysbuild.sh
# Entry point and main program logic.

shtk_import cli
shtk_import config
shtk_import cvs
shtk_import hw
shtk_import list
shtk_import process


# List of valid configuration variables.
#
# Please remember to update sysbuild.conf(5) if you change this list.
SYSBUILD_CONFIG_VARS="BUILD_ROOT BUILD_TARGETS CVSROOT CVSTAG INCREMENTAL_BUILD
                      MACHINES MKVARS NJOBS RELEASEDIR SRCDIR UPDATE_SOURCES
                      XSRCDIR"


# Paths to installed files.
#
# Can be overriden for test purposes only.
: ${SYSBUILD_ETCDIR="/usr/pkg/etc/sysbuild"}
: ${SYSBUILD_SHAREDIR="/usr/pkg/share/sysbuild"}


# Sets defaults for configuration variables and hooks that need to exist.
#
# This function should be before the configuration file has been loaded.  This
# means that the user can undefine a required configuration variable, but we let
# him shoot himself in the foot if he so desires.
sysbuild_set_defaults() {
    # Please remember to update sysbuild(1) if you change any default values.
    shtk_config_set BUILD_ROOT "${HOME}/sysbuild"
    shtk_config_set BUILD_TARGETS "release"
    shtk_config_set CVSROOT ":ext:anoncvs@anoncvs.NetBSD.org:/cvsroot"
    shtk_config_set INCREMENTAL_BUILD "false"
    shtk_config_set MACHINES "$(uname -m)"
    shtk_config_set NJOBS "$(shtk_hw_ncpus)"
    shtk_config_set RELEASEDIR "${HOME}/sysbuild/release"
    shtk_config_set SRCDIR "${HOME}/sysbuild/src"
    shtk_config_set UPDATE_SOURCES "true"

    pre_build_hook() { true; }
    post_build_hook() { true; }
    pre_fetch_hook() { true; }
    post_fetch_hook() { true; }
}


# Performs a build for a single machine type.
#
# \param machine_pair The type of the machine to build for. Can optionally
#    contain the architecture in the "machine-arch" form.
do_one_build() {
    local machine_pair="${1}"; shift

    local aflag=
    local mflag=
    case "${machine_pair}" in
        *-*)
            mflag="-m${machine_pair%%-*}" # First word in pair.
            aflag="-a${machine_pair#*-}" # Other words in pair.
            ;;
        *)
            mflag="-m${machine_pair}"
            ;;
    esac

    local basedir="$(shtk_config_get BUILD_ROOT)/${machine_pair}"

    local njobs="$(shtk_config_get NJOBS)"
    local jflag=
    if [ "${njobs}" -gt 1 ]; then
        jflag="-j$(shtk_config_get NJOBS)"
    fi

    local uflag=
    if shtk_config_get_bool INCREMENTAL_BUILD; then
        uflag=-u

        # Get rid of any possibly-old modules; they are a constant source of
        # update-build problems.
        rm -rf "${basedir}/destdir/stand"/*
    else
        shtk_cli_info "Cleaning up previous build files"
        rm -rf "${basedir}/destdir"
        rm -rf "${basedir}/obj"
        rm -rf "${basedir}/tools"
    fi

    local Vflags=
    for Vflag in $(shtk_config_get_default MKVARS ""); do
        Vflags="${Vflags} -V${Vflag}"
    done

    local xflag=
    local Xflag=
    if shtk_config_has XSRCDIR; then
        xflag=-x
        Xflag="-X$(shtk_config_get XSRCDIR)"
    fi

    local targets=
    for target in $(shtk_config_get BUILD_TARGETS); do
        case "${target}" in
            *:*)
                local submachine="$(echo "${target}" | cut -d : -f 1)"
                local subtarget="$(echo "${target}" | cut -d : -f 2-)"
                if [ "${submachine}" = "${machine}" ]; then
                    targets="${targets} ${subtarget}"
                else
                    # The targets have already been validated, so there is
                    # nothing to do in this case.
                    :
                fi
                ;;

            *)
                targets="${targets} ${target}"
                ;;
        esac
    done

    ( cd "$(shtk_config_get SRCDIR)" && shtk_process_run ./build.sh \
        -D"${basedir}/destdir" \
        -M"${basedir}/obj" \
        -N2 \
        -R"$(shtk_config_get RELEASEDIR)" \
        -T"${basedir}/tools" \
        -U \
        ${Vflags} \
        ${Xflag} \
        ${jflag} \
        ${aflag} \
        ${mflag} \
        ${uflag} \
        ${xflag} \
        ${targets} )
}


# Builds the system.
#
# \params ... The options and arguments to the command.
sysbuild_build() {
    local OPTIND  # Cope with bash failing to reinitialize getopt.
    while getopts ':f' arg "${@}"; do
        case "${arg}" in
            f)  # Convenience flag for a "fast mode".
                shtk_config_set "INCREMENTAL_BUILD" "true"
                shtk_config_set "UPDATE_SOURCES" "false"
                ;;

            \?)
                shtk_cli_usage_error "Unknown option -${OPTARG} in build"
                ;;
        esac
    done
    shift $((${OPTIND} - 1))

    [ ${#} -eq 0 ] || shtk_config_set BUILD_TARGETS "${*}"

    local machines="$(shtk_config_get MACHINES)"

    # Ensure that the list of targets reference valid machines, if any.
    local unmatched_targets=
    for target in $(shtk_config_get BUILD_TARGETS); do
        case "${target}" in
            *:*)
                local submachine="$(echo "${target}" | cut -d : -f 1)"
                if ! shtk_list_contains "${submachine}" ${machines}; then
                    if [ -n "${unmatched_targets}" ]; then
                        unmatched_targets="${unmatched_targets} ${target}"
                    else
                        unmatched_targets="${target}"
                    fi
                fi
                ;;

            *)
                ;;
        esac
    done
    [ -z "${unmatched_targets}" ] || shtk_cli_error \
        "The '${unmatched_targets}' targets do not match any machine" \
        "in '${machines}'"

    if shtk_config_get_bool UPDATE_SOURCES; then
        sysbuild_fetch
    fi

    shtk_config_run_hook pre_build_hook
    for machine in ${machines}; do
        do_one_build "${machine}"
    done
    shtk_config_run_hook post_build_hook
}


# Dumps the loaded configuration.
#
# \params ... The options and arguments to the command.
sysbuild_config() {
    [ ${#} -eq 0 ] || shtk_cli_usage_error "config does not take any arguments"

    for var in ${SYSBUILD_CONFIG_VARS}; do
        if shtk_config_has "${var}"; then
            echo "${var} = $(shtk_config_get "${var}")"
        else
            echo "${var} is undefined"
        fi
    done
}


# Outputs shell configuration commands.
#
# \params ... The options and arguments to the command.
sysbuild_env() {
    [ ${#} -lt 2 ] || shtk_cli_usage_error "env takes zero or one arguments"

    local machine=
    if [ ${#} -eq 1 ]; then
        machine="${1}"
    else
        set -- $(shtk_config_get MACHINES)
        [ ${#} -eq 1 ] || shtk_cli_usage_error "No machine name provided as" \
            "an argument and MACHINES contains more than one name"
        machine="${1}"
    fi

    local basedir="$(shtk_config_get BUILD_ROOT)/${machine}"
    local srcdir="$(shtk_config_get SRCDIR)"

    [ -f "${SYSBUILD_SHAREDIR}/env.sh" ] \
        || shtk_cli_error "Cannot open ${SYSBUILD_SHAREDIR}/env.sh"

    # The semicolon after the sourcing of env.sh is required to get this output
    # working when passed through eval.  Running eval on the output collapses
    # everything into a single line, and we need to separate the sourcing of
    # env.sh from the definition of the global variables.
    cat <<EOF
. "${SYSBUILD_SHAREDIR}/env.sh" ;
PATH="${basedir}/tools/bin:\${PATH}"
D="${basedir}/destdir"
O="${basedir}/obj${srcdir}"
S="${srcdir}"
T="${basedir}/tools"
EOF

    if shtk_config_has XSRCDIR; then
        local xsrcdir="$(shtk_config_get XSRCDIR)"
        cat <<EOF
XO="${basedir}/obj${xsrcdir}"
XS="${xsrcdir}"
EOF
    fi
}


# Fetches a copy of the source tree, or updates an existing one.
#
# \params ... The options and arguments to the command.
sysbuild_fetch() {
    [ ${#} -eq 0 ] || shtk_cli_usage_error "fetch does not take any arguments"

    shtk_config_run_hook pre_fetch_hook

    local cvsroot="$(shtk_config_get CVSROOT)"

    shtk_cli_info "Updating base source tree"
    shtk_cvs_fetch "${cvsroot}" src "$(shtk_config_get_default CVSTAG '')" \
        "$(shtk_config_get SRCDIR)"

    if shtk_config_has XSRCDIR; then
        shtk_cli_info "Updating X11 source tree"
        shtk_cvs_fetch "${cvsroot}" xsrc \
            "$(shtk_config_get_default CVSTAG '')" "$(shtk_config_get XSRCDIR)"
    fi

    shtk_config_run_hook post_fetch_hook
}


# Loads the configuration file specified in the command line.
#
# \param config_name Name of the desired configuration.  It can be either a
#     configuration name (no slashes) or a path.
sysbuild_config_load() {
    local config_name="${1}"; shift

    local config_file=
    case "${config_name}" in
        */*|*.conf)
            config_file="${config_name}"
            ;;

        *)
            local candidates=
            candidates="${candidates} ${HOME}/.sysbuild/${config_name}.conf"
            candidates="${candidates} ${SYSBUILD_ETCDIR}/${config_name}.conf"
            for candidate in ${candidates}; do
                if [ -e "${candidate}" ]; then
                    config_file="${candidate}"
                    break
                fi
            done
            [ -n "${config_file}" ] \
                || shtk_cli_usage_error "Cannot locate configuration named" \
                "'${config_name}'"
            ;;
    esac
    shtk_config_load "${config_file}"
}


# Entry point to the program.
#
# \param ... Command-line arguments to be processed.
#
# \return An exit code to be returned to the user.
main() {
    local config_name="default"

    shtk_config_init ${SYSBUILD_CONFIG_VARS}

    local OPTIND  # Cope with bash failing to reinitialize getopt.
    while getopts ':c:o:' arg "${@}"; do
        case "${arg}" in
            c)  # Name of the configuration to load.
                config_name="${OPTARG}"
                ;;

            o)  # Override for a particular configuration variable.
                shtk_config_override "${OPTARG}"
                ;;

            :)
                shtk_cli_usage_error "Missing argument to option -${OPTARG}"
                ;;

            \?)
                shtk_cli_usage_error "Unknown option -${OPTARG}"
                ;;
        esac
    done
    shift $((${OPTIND} - 1))

    [ ${#} -ge 1 ] || shtk_cli_usage_error "No command specified"

    local exit_code=0

    local command="${1}"; shift
    case "${command}" in
        build|config|env|fetch)
            sysbuild_set_defaults
            sysbuild_config_load "${config_name}"
            "sysbuild_${command}" "${@}" || exit_code="${?}"
            ;;

        *)
            shtk_cli_usage_error "Unknown command ${command}"
            ;;
    esac

    return "${exit_code}"
}
main "${@}"
