Wed Jul 25 12:18:24 2012 UTC ()
Initial (re)addition of sysbuild, version 2.0:

sysbuild is a tool to automate the maintenance of the NetBSD sources and
the build of releases by means of a single command and a configuration
file that tells sysbuild what to do.

Since NetBSD 1.6 (released on July 2011), the source tree has included a
very flexible script (build.sh) to build a full NetBSD release for the
current platform or to cross-build for any of the supported platforms.

The flipside of the flexibility is that the script is inconvenient to use
on a daily basis because of the myriad of options it takes.  Furthermore,
managing the source trees that make up NetBSD is not in the scope of
build.sh: the user must fetch these trees and keep them up to date by hand.

While these details are all fine on their own, rebuilding NetBSD frequently
(to keep a system up to date, or just for development tasks) is convoluted.
Developers and users usually find themselves writing their own wrapper
scripts over build.sh to simplify their daily tasks.

sysbuild extends build.sh by adding support for configuration files and
source tree management, allowing NetBSD rebuilds with a single and simple
command.  Make sure to check the sysbuild-user package for a way to
trivially set up periodic NetBSD rebuilds under an dedicated, unprivileged
user.

This is version 2.0 because sysbuild already existed 10 years ago.  It
served the same purpose, but it was removed because it stopped working
with NetBSD 2.0 (can't remember why though).  Also, I'm convinced the
previous implementation was quite bad.


(jmmv)
diff -r0 -r1.3 pkgsrc/sysutils/sysbuild/DESCR
diff -r0 -r1.18 pkgsrc/sysutils/sysbuild/Makefile
diff -r0 -r1.4 pkgsrc/sysutils/sysbuild/PLIST
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/Kyuafile
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/config.subr
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/config_test.sh
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/cvs.subr
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/cvs_test.sh
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/sysbuild.1
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/sysbuild4cron.1
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/sysbuild4cron.sh
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/sysbuild4cron_test.sh
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/sysbuild_test.sh
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/utils.subr
diff -r0 -r1.1 pkgsrc/sysutils/sysbuild/files/utils_test.sh
diff -r0 -r1.6 pkgsrc/sysutils/sysbuild/files/default.conf
diff -r0 -r1.12 pkgsrc/sysutils/sysbuild/files/sysbuild.sh

File Added: pkgsrc/sysutils/sysbuild/DESCR
sysbuild is a tool to automate the maintenance of the NetBSD sources and
the build of releases by means of a single command and a configuration
file that tells sysbuild what to do.

Since NetBSD 1.6 (released on July 2011), the source tree has included a
very flexible script (build.sh) to build a full NetBSD release for the
current platform or to cross-build for any of the supported platforms.

The flipside of the flexibility is that the script is inconvenient to use
on a daily basis because of the myriad of options it takes.  Furthermore,
managing the source trees that make up NetBSD is not in the scope of
build.sh: the user must fetch these trees and keep them up to date by hand.

While these details are all fine on their own, rebuilding NetBSD frequently
(to keep a system up to date, or just for development tasks) is convoluted.
Developers and users usually find themselves writing their own wrapper
scripts over build.sh to simplify their daily tasks.

sysbuild extends build.sh by adding support for configuration files and
source tree management, allowing NetBSD rebuilds with a single and simple
command.  Make sure to check the sysbuild-user package for a way to
trivially set up periodic NetBSD rebuilds under an dedicated, unprivileged
user.

File Added: pkgsrc/sysutils/sysbuild/Makefile
# $NetBSD: Makefile,v 1.18 2012/07/25 12:18:22 jmmv Exp $

DISTNAME=	sysbuild-2.0
CATEGORIES=	sysutils
MASTER_SITES=	# empty
DISTFILES=	# empty

MAINTAINER=	jmmv@NetBSD.org
COMMENT=	Automates builds of NetBSD and manages source trees
LICENSE=	modified-bsd

PKG_INSTALLATION_TYPES=	overwrite pkgviews
PKG_DESTDIR_SUPPORT=	user-destdir

WRKSRC=			${WRKDIR}
NO_CONFIGURE=		YES

BUILD_SUBST=		-e 's,@SYSBUILD_BINDIR@,${PREFIX}/bin,g'
BUILD_SUBST+=		-e 's,@SYSBUILD_EGDIR@,${EGDIR},g'
BUILD_SUBST+=		-e 's,@SYSBUILD_ETCDIR@,${PKG_SYSCONFDIR},g'

PKG_SYSCONFSUBDIR=	sysbuild
EGDIR=			${PREFIX}/share/examples/sysbuild
CONF_FILES+=		${EGDIR}/default.conf ${PKG_SYSCONFDIR}/default.conf

PKG_OPTIONS_VAR=	PKG_OPTIONS.sysbuild
PKG_SUPPORTED_OPTIONS=	tests
PKG_SUGGESTED_OPTIONS=	tests

.include "../../mk/bsd.options.mk"

.if $(PKG_OPTIONS:Mtests)
TEST_PROGS=	config_test cvs_test sysbuild_test sysbuild4cron_test utils_test

PLIST_SUBST+=	TESTS=
.  include "../../devel/atf-libs/buildlink3.mk"

do-build: build-tests
build-tests:
	cp ${FILESDIR}/Kyuafile ${WRKSRC}
.for file in ${TEST_PROGS}
	${ECHO} '#! ${BUILDLINK_PREFIX.atf-libs}/bin/atf-sh' \
	    >${WRKSRC}/${file}
	${CAT} ${FILESDIR}/*.subr ${FILESDIR}/${file}.sh \
	    | ${SED} ${BUILD_SUBST} >>${WRKSRC}/${file}
	${CHMOD} +x ${WRKSRC}/${file}
.endfor

INSTALLATION_DIRS+=	tests/sysbuild

do-install: install-tests
install-tests:
	${INSTALL_DATA} ${WRKSRC}/Kyuafile ${DESTDIR}${PREFIX}/tests/sysbuild
.for file in ${TEST_PROGS}
	${INSTALL_SCRIPT} ${WRKSRC}/${file} ${DESTDIR}${PREFIX}/tests/sysbuild/
.endfor

do-test:
	cd ${WRKSRC} && PATH="${WRKSRC}:${PATH}" kyua test
.else
PLIST_SUBST+=	TESTS=@comment
.endif

do-build:
.for file in sysbuild sysbuild4cron
	${ECHO} '#! ${SH}' >${WRKSRC}/${file}
	${ECHO} 'set -e' >>${WRKSRC}/${file}
	${CAT} ${FILESDIR}/*.subr ${FILESDIR}/${file}.sh \
	    | ${SED} ${BUILD_SUBST} >>${WRKSRC}/${file}
	${ECHO} '${file}_main "$${@}"' >>${WRKSRC}/${file}
	${CHMOD} +x ${WRKSRC}/${file}
.endfor
.for file in sysbuild.1 sysbuild4cron.1 default.conf
	sed ${BUILD_SUBST} <${FILESDIR}/${file} >${WRKSRC}/${file}
.endfor

INSTALLATION_DIRS+=	bin ${PKGMANDIR}/man1 share/examples/sysbuild

do-install:
	${INSTALL_SCRIPT} ${WRKSRC}/sysbuild ${DESTDIR}${PREFIX}/bin/
	${INSTALL_SCRIPT} ${WRKSRC}/sysbuild4cron ${DESTDIR}${PREFIX}/bin/
	${INSTALL_MAN} ${WRKSRC}/sysbuild.1 \
	    ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1/
	${INSTALL_MAN} ${WRKSRC}/sysbuild4cron.1 \
	    ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1/
.for file in default.conf
	${INSTALL_DATA} ${WRKSRC}/${file} ${DESTDIR}${EGDIR}
.endfor

.include "../../mk/bsd.pkg.mk"

File Added: pkgsrc/sysutils/sysbuild/PLIST
@comment $NetBSD: PLIST,v 1.4 2012/07/25 12:18:22 jmmv Exp $
bin/sysbuild
bin/sysbuild4cron
man/man1/sysbuild.1
man/man1/sysbuild4cron.1
share/examples/sysbuild/default.conf
${TESTS}tests/sysbuild/Kyuafile
${TESTS}tests/sysbuild/config_test
${TESTS}tests/sysbuild/cvs_test
${TESTS}tests/sysbuild/sysbuild_test
${TESTS}tests/sysbuild/sysbuild4cron_test
${TESTS}tests/sysbuild/utils_test

File Added: pkgsrc/sysutils/sysbuild/files/Attic/Kyuafile
syntax("kyuafile", 1)

test_suite("sysbuild")

atf_test_program{name="config_test"}
atf_test_program{name="cvs_test"}
atf_test_program{name="sysbuild_test"}
atf_test_program{name="sysbuild4cron_test"}
atf_test_program{name="utils_test"}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/config.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 config.subr
# Configuration file processing and queries.


# List of valid configuration variables.
#
# This is initialized by config_init and should remain unchanged thorough the
# execution of the program.
CONFIG_VARS=


# List of overrides to apply by config_apply_overrides.
#
# The overrides are recorded and applied separately because they need to happen
# after a configuration file has been loaded.  The contents of this list are
# words of the form: set:<variable> or unset:<variable>.  For those variables
# listed in set:, there is a corresponding config_override_var_<variable>
# variable containing the new value.
CONFIG_OVERRIDES=


# Initializes the configuration module.
#
# \param ... List of configuration variables to recognize in configuration files
#     and user overrides.
config_init() {
    CONFIG_VARS="${@}"
}


# Checks if a configuration variable is known.
#
# \param var Name of the variable to check.
#
# \return True if the variable was registered by config_init, false otherwise.
config_is_valid() {
    local var="${1}"; shift

    local known_var
    for known_var in ${CONFIG_VARS}; do
        if [ "${known_var}" = "${var}" ]; then
            return 0
        fi
    done
    return 1
}


# Checks if a configuration variable is defined.
#
# \param var The name of the variable to check.
#
# \return True if the variable is defined (even if empty), false otherwise.
config_has() {
    local var="${1}"; shift

    local is_unset
    eval is_unset="\${config_var_${var}-yes_this_is_unset}"
    if [ "${is_unset}" = yes_this_is_unset ]; then
        return 1
    else
        return 0
    fi
}


# Gets the value of a defined configuration variable.
#
# \post The value of the variable is printed to stdout, if any.
#
# \post If the variable is not defined, this terminates execution with an error.
#
# \param var Name of the configuration variable to query.
config_get() {
    local var="${1}"; shift

    if config_has "${var}"; then
        eval echo "\${config_var_${var}}"
    else
        utils_error "Required configuration variable ${var} not set"
    fi
}


# Gets the value of configuration variable interpreting it as a boolean.
#
# \param var The variable to query.
#
# \return True if the variable is set to a truth value, false if its value is
# false or if it is not defined.
config_get_bool() {
    local var="${1}"; shift

    if config_has "${var}"; then
        case "$(config_get "${var}")" in
            [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee])
                return 0
                ;;
            [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee])
                return 1
                ;;
            *)
                utils_error "Invalid boolean value in variable ${var}"
                ;;
        esac
    else
        return 1
    fi
}


# Gets the value of a configuration variable with a default fallback.
#
# \post The value of the variable is printed to stdout, if any.
#
# \param var Name of the configuration variable to query.
# \param default Default value to return if the variable is not defined.
config_get_default() {
    local var="${1}"; shift
    local default="${1}"; shift

    if config_has "${var}"; then
        config_get "${var}"
    else
        echo "${default}"
    fi
}


# Sets a configuration variable.
#
# \post Execution terminates if the variable is not valid.
#
# \param var The name of the configuration variable to set.
# \param value The value to set.
config_set() {
    local var="${1}"; shift
    local value="${1}"; shift

    config_is_valid "${var}" \
        || utils_usage_error "Unknown configuration variable ${var}"
    eval "config_var_${var}=\"\${value}\""
}


# Loads a configuration file.
#
# \pre config_init should have been called to register the valid configuration
# variables.  Any non-registered configuration variable found in the file will
# not be available through any of the functions in this module.
#
# \post The configuration module is updated with the values defined in the
# configuration file.
#
# \post Any errors in the processing of the configuration file terminate the
# execution of the script.
#
# \param config_file Path to the file to load.
config_load() {
    local config_file="${1}"; shift

    [ -e "${config_file}" ] || utils_error "Configuration file ${config_file}" \
        "does not exist"

    # User-facing variables.
    local var
    for var in ${CONFIG_VARS}; do
        unset "${var}" || true
        eval local "${var}"
    done

    local real_config_file
    case "${config_file}" in
        */*) real_config_file="${config_file}" ;;
        *) real_config_file="./${config_file}" ;;
    esac
    ( . "${real_config_file}" ) || utils_error "Failed to load configuration" \
        "file ${config_file}"
    . "${real_config_file}"

    for var in ${CONFIG_VARS}; do
        local value
        eval value="\${${var}-unset}"
        [ "${value-unset}" != unset ] || continue

        if [ -n "${value}" ]; then
            config_set "${var}" "${value}"
        else
            unset "config_var_${var}" || true
        fi
    done

    config_apply_overrides
}


# Applies recorded overrides to the current configuration.
#
# This is just a helper function for config_load and should not be used
# directly.
#
# \post The configuration data in memory is modified according to the data
# recorded in the overrides.
#
# \post The contents of CONFIG_OVERRIDES is cleared.
config_apply_overrides() {
    for override in ${CONFIG_OVERRIDES}; do
        case "${override}" in
            set:*)
                local var="$(echo ${override} | cut -d : -f 2)"
                local value
                eval value="\"\${config_override_var_${var}}\""
                config_set "${var}" "${value}"
                ;;
            unset:*)
                local var="$(echo ${override} | cut -d : -f 2)"
                unset "config_var_${var}" || true
                ;;
        esac
    done
    CONFIG_OVERRIDES=
}


# Records an override to be applied to the configuration.
#
# Overrides are configuration variables set through the command line.  These can
# be set before loading the configuration file with config_load.
#
# \post Any errors in the processing of the configuration override terminate the
# execution of the script.
#
# \param arg An override of the form variable=value.
config_override() {
    local arg="${1}"; shift

    case "${arg}" in
        *=*)
            ;;
        *)
            utils_usage_error "Invalid configuration override" \
                "${arg}; must be of the form variable=value"
            ;;
    esac
    local var="$(echo "${arg}" | cut -d = -f 1)"
    local value="$(echo "${arg}" | cut -d = -f 2-)"

    [ -n "${var}" ] || utils_usage_error "Invalid configuration override" \
        "${arg}; must be of the form variable=value"
    config_is_valid "${var}" \
        || utils_usage_error "Unknown configuration variable ${var}"

    if [ -n "${value}" ]; then
        eval "config_override_var_${var}=\"${value}\""
        CONFIG_OVERRIDES="${CONFIG_OVERRIDES} set:${var}"
    else
        CONFIG_OVERRIDES="${CONFIG_OVERRIDES} unset:${var}"
    fi
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/config_test.sh
# 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.


atf_test_case is_valid__true
is_valid__true_body() {
    config_init VAR1 VAR3
    for var in VAR1 VAR3; do
        config_is_valid "${var}" || atf_fail "${var} not found"
    done
}


atf_test_case is_valid__false
is_valid__false_body() {
    config_init VAR1 VAR3
    for var in VAR11 VAR2 VAR; do
        if config_is_valid "${var}"; then
            atf_fail "${var} found but was not registered"
        fi
    done
}


atf_test_case has__true__empty
has__true__empty_body() {
    config_init TESTVAR
    config_set TESTVAR ""
    config_has TESTVAR || atf_fail "Expected variable not found"
}


atf_test_case has__true__not_empty
has__true__not_empty_body() {
    config_init TESTVAR
    config_set TESTVAR "foo"
    config_has TESTVAR || atf_fail "Expected variable not found"
}


atf_test_case has__false
has__false_body() {
    config_init TESTVAR
    if config_has TESTVAR; then
        atf_fail "Unexpected variable found"
    fi
}


atf_test_case get__ok__empty
get__ok__empty_body() {
    config_init TESTVAR

    config_set TESTVAR ""
    [ -z "$(config_get TESTVAR)" ] || atf_fail "Failed to query value"
}


atf_test_case get__ok__not_empty
get__ok__not_empty_body() {
    config_init TESTVAR

    config_set TESTVAR some-value
    [ "$(config_get TESTVAR)" = some-value ] || atf_fail "Failed to query value"
}


atf_test_case get__undefined_variable
get__undefined_variable_body() {
    config_init TESTVAR

    if ( config_get TESTVAR ) >out 2>err; then
        atf_fail "Got unset variable successfully"
    else
        grep "Required configuration variable TESTVAR not set" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case get_bool__true
get_bool__true_body() {
    config_init TESTVAR

    for value in yes Yes true True; do
        config_set TESTVAR "${value}"
        config_get_bool TESTVAR || atf_fail "Expected true, but got false"
    done
}


atf_test_case get_bool__false
get_bool__false_body() {
    config_init TESTVAR

    for value in no No false False; do
        config_set TESTVAR "${value}"
        if config_get_bool TESTVAR; then
            atf_fail "Expected false, but got true"
        fi
    done
}


atf_test_case get_bool__undefined_variable
get_bool__undefined_variable_body() {
    config_init TESTVAR

    if config_get_bool TESTVAR; then
        atf_fail "Expected false, but got true"
    fi
}


atf_test_case get_bool__invalid_value
get_bool__invalid_value_body() {
    config_init TESTVAR

    config_set TESTVAR not-a-boolean
    if ( config_get_bool TESTVAR ) >out 2>err; then
        atf_fail "Got invalid boolean value successfully"
    else
        grep "Invalid boolean value in variable TESTVAR" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case get_default__defined__empty
get_default__defined__empty_body() {
    config_init TESTVAR
    config_set TESTVAR ""
    [ "$(config_get_default TESTVAR 'foo')" = "" ] \
        || atf_fail "Did not fetch defined value"
}


atf_test_case get_default__defined__not_empty
get_default__defined__not_empty_body() {
    config_init TESTVAR
    config_set TESTVAR "bar"
    [ "$(config_get_default TESTVAR 'foo')" = "bar" ] \
        || atf_fail "Did not fetch defined value"
}


atf_test_case get_default__default__empty
get_default__default__empty_body() {
    config_init TESTVAR
    [ "$(config_get_default TESTVAR '')" = "" ] \
        || atf_fail "Did not fetch default value"
}


atf_test_case get_default__default__not_empty
get_default__default__not_empty_body() {
    config_init TESTVAR
    [ "$(config_get_default TESTVAR 'foo')" = "foo" ] \
        || atf_fail "Did not fetch default value"
}


atf_test_case set__ok
set__ok_body() {
    config_init TESTVAR

    config_set TESTVAR some-value
    [ "${config_var_TESTVAR}" = some-value ] || atf_fail "Failed to set value"
}


atf_test_case set__unknown_variable
set__unknown_variable_body() {
    config_init TESTVAR

    if ( config_set TESTVAR2 some-value ) >out 2>err; then
        atf_fail "Set unknown variable successfully"
    else
        grep "Unknown configuration variable TESTVAR2" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case load__filter_variables
load__filter_variables_body() {
    config_init Z VAR1 EMPTY

    cat >test.conf <<EOF
A=foo
Z=bar
VAR1="some text"
VAR2="some other text"
EOF

    config_load $(pwd)/test.conf || atf_fail "Failed to load test configuration"

    [ "${config_var_Z}" = bar ] || \
        atf_fail "Z not found in configuration"
    [ "${config_var_VAR1}" = "some text" ] || \
        atf_fail "VAR1 not found in configuration"

    [ "${config_var_EMPTY-has_not_been_set}" = has_not_been_set ] || \
        atf_fail "Undefined variable set, but should not have been"

    [ "${config_var_A-unset}" = unset ] || \
        atf_fail "A set in configuration, but not expected"
    [ "${config_var_VAR2-unset}" = unset ] || \
        atf_fail "VAR2 set in configuration, but not expected"
}


atf_test_case load__allow_undefine
load__allow_undefine_body() {
    config_init UNDEFINE

    cat >test.conf <<EOF
UNDEFINE=
EOF

    config_set UNDEFINE "remove me"
    config_load $(pwd)/test.conf || atf_fail "Failed to load test configuration"
    if config_has UNDEFINE; then
        atf_fail "Undefine attempt from configuration did not work"
    fi
}


atf_test_case load__current_directory
load__current_directory_body() {
    config_init A

    cat >test.conf <<EOF
A=foo
EOF

    config_load test.conf || atf_fail "Failed to load test configuration"

    [ "${config_var_A}" = foo ] || \
        atf_fail "A not found in configuration"
}


atf_test_case load__missing_file
load__missing_file_body() {
    if ( config_load missing.conf ) >out 2>err; then
        atf_fail "Missing configuration file load succeeded"
    else
        grep "Configuration file missing.conf does not exist" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case load__invalid_file
load__invalid_file_body() {
    echo "this file is invalid" >invalid.conf

    if ( config_load invalid.conf ) >out 2>err; then
        atf_fail "Invalid configuration file load succeeded"
    else
        cat err
        grep "Failed to load configuration file invalid.conf" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case override__ok_before_load
override__ok_before_load_body() {
    config_init VAR1 VAR2

    cat >test.conf <<EOF
VAR1="override me"
VAR2="do not override me"
EOF

    config_override "VAR1=new value"
    config_load test.conf || atf_fail "Failed to load test configuration"

    [ "${config_var_VAR1}" = "new value" ] || atf_fail "Override failed"
    [ "${config_var_VAR2}" = "do not override me" ] \
        || atf_fail "Overrode more than one variable"
}


atf_test_case override__not_ok_after_load
override__not_ok_after_load_body() {
    config_init VAR1 VAR2

    cat >test.conf <<EOF
VAR1="override me"
VAR2="do not override me"
EOF

    config_load test.conf || atf_fail "Failed to load test configuration"
    config_override "VAR1=new value"

    [ "${config_var_VAR1}" = "override me" ] \
        || atf_fail "Override succeeded, but it should not have"
    [ "${config_var_VAR2}" = "do not override me" ] \
        || atf_fail "Overrode more than one variable"
}


atf_test_case override__invalid_format
override__invalid_format_body() {
    for arg in foo =bar ''; do
        if ( config_override "${arg}" ) >out 2>err; then
            atf_fail "Invalid configuration override ${arg} succeeded"
        else
            cat err
            grep "Invalid configuration override ${arg}" err >/dev/null \
                || atf_fail "Expected error message not found"
        fi
    done
}


atf_test_case override__unknown_variable
override__unknown_variable_body() {
    config_init Z VAR1
    for arg in A=b VAR2=d; do
        if ( config_override "${arg}" ) >out 2>err; then
            atf_fail "Invalid configuration override ${arg} succeeded"
        else
            cat err
            grep "Unknown configuration variable ${var}" err >/dev/null \
                || atf_fail "Expected error message not found"
        fi
    done
}


atf_init_test_cases() {
    atf_add_test_case is_valid__true
    atf_add_test_case is_valid__false

    atf_add_test_case has__true__empty
    atf_add_test_case has__true__not_empty
    atf_add_test_case has__false

    atf_add_test_case get__ok__empty
    atf_add_test_case get__ok__not_empty
    atf_add_test_case get__undefined_variable

    atf_add_test_case get_bool__true
    atf_add_test_case get_bool__false
    atf_add_test_case get_bool__undefined_variable
    atf_add_test_case get_bool__invalid_value

    atf_add_test_case get_default__defined__empty
    atf_add_test_case get_default__defined__not_empty
    atf_add_test_case get_default__default__empty
    atf_add_test_case get_default__default__not_empty

    atf_add_test_case set__ok
    atf_add_test_case set__unknown_variable

    atf_add_test_case load__filter_variables
    atf_add_test_case load__allow_undefine
    atf_add_test_case load__current_directory
    atf_add_test_case load__missing_file
    atf_add_test_case load__invalid_file

    atf_add_test_case override__ok_before_load
    atf_add_test_case override__not_ok_after_load
    atf_add_test_case override__invalid_format
    atf_add_test_case override__unknown_variable
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/cvs.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 cvs.subr
# Utilities to invoke CVS and wrappers to simplify its operation.


# Checks out or updates a working copy.
#
# \param cvsroot Location of the CVS repository.
# \param module Module to check out, when the working copy does not exist yet.
# \param tag CVS tag to use, if any; may be empty.
# \param directory Target directory in which to store the checked out files or
#     the updated files.
cvs_fetch() {
    local cvsroot="${1}"; shift
    local module="${1}"; shift
    local tag="${1}"; shift
    local directory="${1}"; shift

    if [ -d "${directory}" ]; then
        cvs_update "${cvsroot}" "${tag}" "${directory}"
    else
        cvs_checkout "${cvsroot}" "${module}" "${tag}" "${directory}"
    fi
}


# Checks out a new directory from CVS.
#
# \param cvsroot Location of the CVS repository.
# \param module The name of the module to check out.
# \param tag CVS tag to use, if any; may be empty.
# \param directory The directory in which to perform the checkout.
cvs_checkout() {
    local cvsroot="${1}"; shift
    local module="${1}"; shift
    local tag="${1}"; shift
    local directory="${1}"; shift

    local rflag=
    [ -z "${tag}" ] || rflag="-r${tag}"

    [ ! -d "${directory}" ] || utils_error "Cannot checkout into" \
        "${directory}; directory already exists"
    mkdir -p "${directory}"
    mkdir "${directory}"/.cvs-checkout \
        || utils_error "Failed to create ${directory}"
    ( cd "${directory}"/.cvs-checkout &&
        utils_run cvs -d"${cvsroot}" -q checkout -P ${rflag} "${module}" )
    mv "${directory}"/.cvs-checkout/"${module}"/* "${directory}"/ \
        || true  # Maybe the checkout yielded no files...
    rm -rf "${directory}"/.cvs-checkout
}


# Updates an existing directory from CVS.
#
# \param cvsroot Location of the CVS repository.
# \param tag CVS tag to use, if any; may be empty.
# \param directory The directory in which to perform the update.
cvs_update() {
    local cvsroot="${1}"; shift
    local tag="${1}"; shift
    local directory="${1}"; shift

    local rflag=
    [ -z "${tag}" ] || rflag="-r${tag}"

    [ -d "${directory}" ] || utils_error "Cannot update ${directory};" \
        "directory does not exist"

    if [ -d "${directory}/.cvs-checkout" ]; then
        # Attempt to resume a previously aborted cvs_checkout.
        mv "${directory}"/.cvs-checkout/*/* "${directory}"/ \
            || true  # Maybe the checkout yielded no files...
        rm -rf "${directory}"/.cvs-checkout
    fi

    ( cd "${directory}" && \
        utils_run cvs -d"${cvsroot}" -q update -d -P ${rflag} )
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/cvs_test.sh
# 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.


MOCK_CVSROOT=":local:$(pwd)/cvsroot"


# Creates a local CVS repository with a variety of modules.
#
# \param repository Path to the repository to create.
# \param ... Modules to create.
init_cvsroot() {
    local repository="${1}"; shift

    atf_check -o ignore -e ignore cvs -d "${repository}" init

    for module in "${@}"; do
        mkdir module
        cd module
        echo "first revision" >"file-in-${module}"
        cvs -d "${repository}" import -m "Import." "${module}" VENDOR_TAG \
            release_tag
        cd -
        rm -rf module
    done
}


atf_test_case fetch
fetch_body() {
    init_cvsroot "${MOCK_CVSROOT}" src

    cvs_fetch "${MOCK_CVSROOT}" src "" first
    grep "first revision" first/file-in-src >/dev/null \
        || atf_fail "Unexpected version found"

    cp -rf first second
    echo "second revision" >second/file-in-src
    ( cd second && cvs commit -m "Second commit." )

    cvs_fetch "${MOCK_CVSROOT}" src "" first
    grep "second revision" first/file-in-src >/dev/null \
        || atf_fail "Unexpected version found"
}


atf_test_case checkout__same_name
checkout__same_name_body() {
    init_cvsroot "${MOCK_CVSROOT}" first second
    cvs_checkout "${MOCK_CVSROOT}" first "" $(pwd)/a/b/c/first
    [ -f a/b/c/first/file-in-first ] || atf_fail "Files not checked out"
    if [ -f a/b/c/second/file-in-second ]; then
        atf_fail "Unexpected module checked out"
    fi
}


atf_test_case checkout__different_name
checkout__different_name_body() {
    init_cvsroot "${MOCK_CVSROOT}" first second
    cvs_checkout "${MOCK_CVSROOT}" first "" $(pwd)/a/b/c/second
    [ -f a/b/c/second/file-in-first ] || atf_fail "Files not checked out"
}


atf_test_case checkout__already_exists
checkout__already_exists_body() {
    mkdir usr/src
    if ( cvs_checkout "${MOCK_CVSROOT}" src "" $(pwd)/usr/src ) >out 2>err; then
        atf_fail "Checkout succeeded, but should not"
    else
        grep "Cannot checkout into $(pwd)/usr/src.*exists" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case checkout__permission_denied
checkout__permission_denied_head() {
    atf_set "require.user" "unprivileged"
}
checkout__permission_denied_body() {
    init_cvsroot "${MOCK_CVSROOT}" src
    mkdir usr
    chmod 555 usr
    if ( cvs_checkout "${MOCK_CVSROOT}" src "" $(pwd)/usr/src ) >out 2>err; then
        atf_fail "Checkout succeeded, but should not"
    else
        grep "Failed to create $(pwd)/usr/src" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_test_case update__ok
update__ok_body() {
    init_cvsroot "${MOCK_CVSROOT}" first second

    cvs -d "${MOCK_CVSROOT}" checkout first
    mv first copy
    cvs -d "${MOCK_CVSROOT}" checkout first

    cvs_update "${MOCK_CVSROOT}" "" first
    grep "first revision" first/file-in-first >/dev/null \
        || atf_fail "Unexpected version found"

    echo "second revision" >copy/file-in-first
    ( cd copy && cvs commit -m "Second commit." )

    cvs_update "${MOCK_CVSROOT}" "" first
    grep "second revision" first/file-in-first >/dev/null \
        || atf_fail "Unexpected version found"
}


atf_test_case update__resume_checkout
update__resume_checkout_body() {
    init_cvsroot "${MOCK_CVSROOT}" first

    cvs -d "${MOCK_CVSROOT}" checkout first
    mv first copy

    mkdir -p first/.cvs-checkout/first
    mv copy/CVS first/.cvs-checkout/first
    rm -rf copy

    cvs_update "${MOCK_CVSROOT}" "" first
    grep "first revision" first/file-in-first >/dev/null \
        || atf_fail "Unexpected version found"
}


atf_test_case update__does_not_exist
update__does_not_exist_body() {
    if ( cvs_update "${MOCK_CVSROOT}" "" src ) >out 2>err; then
        atf_fail "Update succeeded, but should not"
    else
        grep "Cannot update src; .*not exist" err >/dev/null \
            || atf_fail "Expected error message not found"
    fi
}


atf_init_test_cases() {
    atf_add_test_case fetch

    atf_add_test_case checkout__same_name
    atf_add_test_case checkout__different_name
    atf_add_test_case checkout__already_exists
    atf_add_test_case checkout__permission_denied

    atf_add_test_case update__ok
    atf_add_test_case update__resume_checkout
    atf_add_test_case update__does_not_exist
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/sysbuild.1
.\" 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.
.Dd July 25, 2012
.Dt SYSBUILD 1
.Os
.Sh NAME
.Nm sysbuild
.Nd performs fully automatic builds of NetBSD
.Sh SYNOPSIS
.Nm
.Op Fl c Ar config_name
.Op Fl o Ar variable=value
build
.Op Fl f
.Op Ar build_targets
.Nm
.Op Fl c Ar config_name
.Op Fl o Ar variable=value
config
.Nm
.Op Fl c Ar config_name
.Op Fl o Ar variable=value
fetch
.Sh DESCRIPTION
.Nm
is a utility to build NetBSD releases in a fully automated manner.
The process is made unattended by relying on a configuration file that
prespecifies the location of all build components and the desired goals of
the build.
If you want to plug
.Nm
into
.Xr cron 8 ,
please take a look to
.Xr sysbuild4cron 1
now.
.Pp
.Nm
can be seen as a simple wrapper over
.Xr cvs 1
and the
.Nm build.sh
script that ships with the NetBSD source tree.
.Nm
provides the following additional features:
.Bl -bullet
.It
Automatically fetch or update the NetBSD source trees (both src and xsrc)
before performing a build.
.It
Preconfigure the way
.Nm build.sh
is invoked by storing all relevant details in a configuration file.
.It
Perform builds for a variety of platforms with a single invocation.
.It
Trivially set up periodic
.Nx
rebuilds by adding a single line to your
.Xr crontab 5 .
.El
.Pp
As you can see in the
.Sx SYNOPSIS
section,
.Nm
provides a subcommand-interface: the tool has several modes of operation,
and the particular mode to use for a given run is selected by the first
non-option argument in the command line.
.Pp
The following options apply to all commands:
.Bl -tag -width XXXX
.It Fl c Ar config_name
Specifies the configuration file to use.
.Pp
If
.Ar config_name
includes any directory separator (aka, one or more slashes) or the
.Sq .conf
suffix, then this specifies the path of the configuration file to load.
.Pp
.If
.Ar config_name
is a plain name without any directory components nor extension, then this
specifies the name of the configuration.
.Nm
will first look for a configuration file in
.Pa ~/.sysbuild/<config_name>.conf
and, if not found, will use
.Pa @SYSBUILD_ETCDIR@/<config_name>.conf .
Any of the two files must exist, or else
.Nm
will exit with an error.
.It Fl o Ar variable=value
Applies an override to the loaded configuration.
.Pp
The
.Ar variable
part of the argument must be any of the recognized configuration variables
described in
.Sx Configuration file
below.
The
.Ar value ,
if not empty, specifies the value to set the configuration variable to.
If
.Ar value
is empty, then the configuration variable is unset.
.El
.Ss The build command
The build command provides the main functionality of
.Nm .
It performs the following steps:
.Bl -ordered
.It
If
.Va UPDATE_SOURCES
is true, the fetch command is invoked first.
See
.Sx The fetch command
for more details.
.It
For every machine type listed in
.Va MACHINES ,
issues a
.Nm build.sh
call for that particular machine using the rest of the settings defined in
the configuration file.
The targets passed to the
.Nm build.sh
script are those defined in the
.Va BUILD_TARGETS
variable, or the arguments passed through the command line if any.
.El
.Pp
The following options apply only to the build command:
.Bl -tag -width XXXX
.It Fl f
Enables fast mode, which skips updating the source trees and performs
update builds.
This is a shorthand for these generic flags:
.Fl o Ar INCREMENTAL_BUILD=true
.Fl o Ar UPDATE_SOURCES=false .
.El
.Ss The config command
The config command dumps the loaded configuration to the standard output.
The format of the output is not a script, so it cannot be fed back into
.Nm .
The purpose of this command is to aid in debugging the configuration of the
tool before performing any builds, particularly when the configuration
files use shell logic to determine the value of any variables.
.Ss The fetch command
The fetch command downloads or updates the
.Nx
source trees, which include src and, optionally, xsrc.
.Pp
If the modules do not exist yet in the locations specified by
.Va SRCDIR
and
.Va XSRCDIR ,
this performs an initial CVS checkout of the trees.
If the modules exist, this performs a CVS update.
.Pp
The
.Va CVSROOT
and
.Va CVSTAG
variables are used to determine where to get the sources from and whether a
particular tag is desired.
.Pp
The major use of this subcommand is the following.
Consider that you wish to use the standard locations of
.Pa /usr/src
and
.Pa /usr/xsrc
for your source trees, and that you would like to keep these owned by root
while, at the same time, you run your NetBSD builds as an unprivileged user.
In this situation, you can use the
.Sq fetch
command as root only, set
.Va UPDATE_SOURCES
to
.Sq false
in your user configuration files, and do your builds as another user.
.Ss Configuration file
Configuration files for
.Nm
are plain shell scripts that define a set of recognized variables.
.Pp
As scripts, they can perform any magic they desire to deduce the value of
the configuration variables.
For example, the default configuration file shipped with
.Nm
automatically deduces the value of the
.Va NJOBS
variable by looking at how many CPUs are available in the system.
.Pp
The following variables configure the source trees:
.Bl -tag -width INCREMENTAL_BUILD
.It Va CVSROOT
Location of the CVS root from which to check out or update the src and xsrc
modules.
.Pp
Default:
.Sq :ext:anoncvs@anoncvs.NetBSD.org:/cvsroot
.It Va CVSTAG
CVS tag to use during checkouts or updates of the src and xsrc modules.
.Pp
Default: not defined.
.It Va SRCDIR
Path to the src module.
If you want
.Nm
to perform an update of this directory before every build, you will need
write access to it.
Otherwise, you can use a read-only directory.
.Pp
Default:
.Pa /usr/src .
.It Va UPDATE_SOURCES
Whether to perform an update of the source tree before every build or not.
.Pp
Default:
.Sq true .
.It Va XSRCDIR
Path to the xsrc module.
If you want
.Nm
to perform an update of this directory before every build, you will need
write access to it.
Otherwise, you can use a read-only directory.
.Pp
Defining this variable causes
.Nm
to build the X Window System as part of the build of NetBSD.
.Pp
Default: not defined.
.El
.Pp
The following variables configure the location of the build files:
.Bl -tag -width INCREMENTAL_BUILD
.It Va BUILD_ROOT
Path to the directory in which to place build files.
These include the obj tree, the destdir and the cross-build tools generated
by the
.Nm build.sh
script.
The contents of this directory are sorted by machine type so that it can be
shared across builds for different machines.
.Pp
Default:
.Pa ~/sysbuild/ .
.It Va RELEASEDIR
Path to the directory that will contain the build products (aka the release
files).
The contents of this directory are sorted by machine type so that it can be
shared across builds for different machines.
.Pp
Default:
.Pa ~/sysbuild/release .
.El
.Pp
The following variables configure the build process:
.Bl -tag -width INCREMENTAL_BUILD
.It Va BUILD_TARGETS
Whitespace-separated list of targets to pass to
.Nm build.sh
during builds.
.Pp
Default:
.Sq release .
.It Va INCREMENTAL_BUILD
Whether to perform update builds or not.
An update build reuses existing obj, destdir and tools trees.
.Pp
Of special note is that
.Nm
will take care of pruning files likely to cause breakage during update
builds before performing the build itself.
As an example, this includes deleting all the modules from
.Pa destdir/stand/<platform>/ .
.Pp
Default:
.Sq false .
.It Va MACHINES
Whitespace-separated list of machine types to build for.
.Pp
Default: the name of the host machine type.
.It Va NJOBS
Number of parallel jobs to use during the build.
If not set, disables parallel builds (which, due to
.Nm make 1
semantics, is not the same as setting this to 1).
.Pp
Default: not defined.
.El
.Sh FILES
.Bl -tag -width XXXX
.It Pa @SYSBUILD_ETCDIR@/
Directory containing all system-wide configuration files.
.It Pa @SYSBUILD_ETCDIR@/default.conf
Default configuration file to load if the user does not have a
corresponding
.Pa ~/.sysbuild/default.conf
file and the
.Fl c
flag is not provided.
.It Pa ~/.sysbuild/
Directory containing all user-specific configuration files.
.It Pa ~/.sysbuild/default.conf
Default configuration file to load when the
.Fl c
flag is not provided.
.El
.Sh SEE ALSO
.Xr cvs 1 ,
.Xr sysbuild4cron 1 ,
.Xr hier 7 and
.Pa /usr/src/BUILDING .
.Sh AUTHORS
The
.Nm
utility was developed by
.An Julio Merino
.Aq jmmv@NetBSD.org .

File Added: pkgsrc/sysutils/sysbuild/files/Attic/sysbuild4cron.1
.\" 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.
.Dd July 25, 2012
.Dt SYSBUILD4CRON 1
.Os
.Sh NAME
.Nm sysbuild4cron
.Nd performs fully automatic builds of NetBSD from cron
.Sh SYNOPSIS
.Nm
.Op Fl l Ar logdir
.Op Fl r Ar recipient
--
.Ar sysbuild_arguments
.Sh DESCRIPTION
.Nm
is a wrapper over
.Xr sysbuild 1
that allows plugging
.Nx
builds into
.Xr cron 8
with minimal effort.
.Pp
.Nm
executes
.Xr sysbuild 1
with the given arguments after the
.Sq --
marker, stores the output of the build in a log file and, if the exit code of
the tool indicated an error, proceeds to send an error report to a given
recipient.
.Pp
Unless
.Nm
detects an error in the usage of this wrapper script, the tool does not print
any output to the console and does not return any error codes.
All errors are reported by means of email.
.Pp
The following options are supported:
.Bl -tag -width XXXX
.It Fl l Ar logdir
Path to the directory that will keep the logs of all the calls to
.Xr sysbuild 1 .
.Pp
Default:
.Pa ~/sysbuild/log .
.It Fl r Ar recipient
Email address of the recipient of the failure messages.
.Pp
Default: the current username, without a domain part.
.El
.Sh SEE ALSO
.Xr crontab 1 ,
.Xr sysbuild 1 ,
.Xr crontab 5 .
.Sh AUTHORS
The
.Nm
utility was developed by
.An Julio Merino
.Aq jmmv@NetBSD.org .

File Added: pkgsrc/sysutils/sysbuild/files/Attic/sysbuild4cron.sh
# 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 sysbuild4cron.sh
# Entry point and main program logic.


# Paths to installed files.
#
# Can be overriden for test purposes only.
: ${SYSBUILD_BINDIR="@SYSBUILD_BINDIR@"}


# Sends an email notification upon a failure.
#
# \param logfile File containing the output of the failed command.
# \param recipient Who to send the email to.
# \param ... Command that failed.
sysbuild4cron_email() {
    local logfile="${1}"; shift
    local recipient="${1}"; shift

    local maxlines=100
    {
        echo "The following command has failed:"
        echo
        echo "    $*"
        echo
        echo "The output of the failed command has been left in:"
        echo
        echo "    ${logfile}"
        echo
        echo "The last ${maxlines} of the log follow:"
        echo
        tail -n${maxlines} "${logfile}"
    } | mail -s "sysbuild failure report" "${recipient}"
}


# Entry point to the program.
#
# \param ... Command-line arguments to be processed.
#
# \return An exit code to be returned to the user.
sysbuild4cron_main() {
    local logdir="${HOME}/sysbuild/log"
    local recipient="$(id -u -n)"

    while getopts ':l:r:' arg "${@}"; do
        case "${arg}" in
            l)  # Location of logs.
                case "${OPTARG}" in
                    /*) logdir="${OPTARG}" ;;
                    *) logdir="$(pwd)/${OPTARG}" ;;
                esac
                ;;

            r)  # Email recipient.
                recipient="${OPTARG}"
                ;;

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

    set -- "${SYSBUILD_BINDIR}/sysbuild" "${@}"

    if [ ! -d "${logdir}" ]; then
        mkdir -p "$(dirname "${logdir}")" >/dev/null 2>/dev/null || true
        mkdir "${logdir}" \
            || utils_error "Failed to create directory '${logdir}'"
    fi

    local exit_code=0
    local logfile="${logdir}/${Utils_ProgName}.$(date +%Y%m%d%H%M%S).log"
    "${@}" >"${logfile}" 2>&1 \
        || sysbuild4cron_email "${logfile}" "${recipient}" "${@}"
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/sysbuild4cron_test.sh
# 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.


# Creates a fake program that records its invocations for later processing.
#
# The fake program, when invoked, will append its arguments to a commands.log
# file in the test case's work directory.
#
# \param binary The path to the program to create.
# \param get_stdin Whether to capture stdin or not.
create_mock_binary() {
    local binary="${1}"; shift
    local get_stdin="${1}"; shift

    cat >"${binary}" <<EOF
#! /bin/sh

logfile="${HOME}/commands.log"
echo "Command: \${0##*/}" >>"\${logfile}"
for arg in "\${@}"; do
    echo "Arg: \${arg}" >>"\${logfile}"
done
    [ "${get_stdin}" = no ] || sed -e 's,^,stdin: ,' >>"\${logfile}"
    echo >>"\${logfile}"
EOF
    chmod +x "${binary}"
}


setup_mocks() {
    mkdir bin
    create_mock_binary bin/mail yes
    create_mock_binary bin/sysbuild no
    PATH="$(pwd)/bin:${PATH}"
    SYSBUILD_BINDIR="$(pwd)/bin"; export SYSBUILD_BINDIR
}


atf_test_case no_args
no_args_body() {
    setup_mocks
    atf_check sysbuild4cron

    cat >expout <<EOF
Command: sysbuild

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case some_args
some_args_body() {
    setup_mocks
    atf_check sysbuild4cron -- -k -Z foo bar

    cat >expout <<EOF
Command: sysbuild
Arg: -k
Arg: -Z
Arg: foo
Arg: bar

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case sysbuild_fails
sysbuild_fails_body() {
    setup_mocks
    for number in $(seq 150); do
        echo "echo line ${number}" >>bin/sysbuild
    done
    echo "exit 1" >>bin/sysbuild

    atf_check sysbuild4cron a

    name="$(cd sysbuild/log && echo sysbuild4cron.*.log)"
    cat >expout <<EOF
Command: sysbuild
Arg: a

Command: mail
Arg: -s
Arg: sysbuild failure report
Arg: jmmv
stdin: The following command has failed:
stdin: 
stdin:     $(pwd)/bin/sysbuild a
stdin: 
stdin: The output of the failed command has been left in:
stdin: 
stdin:     $(pwd)/sysbuild/log/${name}
stdin: 
stdin: The last 100 of the log follow:
stdin: 
EOF
    for number in $(seq 51 150); do
        echo "stdin: line ${number}" >>expout
    done
    echo >>expout

    atf_check -o file:expout cat commands.log
}


atf_test_case custom_flags
custom_flags_body() {
    setup_mocks
    echo "exit 1" >>bin/sysbuild

    atf_check sysbuild4cron -l path/to/logs -r somebody@example.net

    name="$(cd path/to/logs && echo sysbuild4cron.*.log)"
    cat >expout <<EOF
Command: sysbuild

Command: mail
Arg: -s
Arg: sysbuild failure report
Arg: somebody@example.net
stdin: The following command has failed:
stdin: 
stdin:     $(pwd)/bin/sysbuild
stdin: 
stdin: The output of the failed command has been left in:
stdin: 
stdin:     $(pwd)/path/to/logs/${name}
stdin: 
stdin: The last 100 of the log follow:
stdin: 

EOF

    atf_check -o file:expout cat commands.log
}


atf_test_case capture_out_and_err
capture_out_and_err_body() {
    setup_mocks
    echo "echo foo" >>bin/sysbuild
    echo "echo bar 1>&2" >>bin/sysbuild
    echo "exit 1" >>bin/sysbuild

    atf_check sysbuild4cron

    atf_check -o match:"stdin: foo" -o match:"stdin: bar" cat commands.log
}


atf_test_case unknown_flag
unknown_flag_body() {
    cat >experr <<EOF
sysbuild4cron: E: Unknown option -Z
Type 'man sysbuild4cron' for help
EOF
    atf_check -s exit:1 -e file:experr sysbuild4cron -Z
}


atf_init_test_cases() {
    atf_add_test_case no_args
    atf_add_test_case some_args
    atf_add_test_case sysbuild_fails
    atf_add_test_case custom_flags
    atf_add_test_case capture_out_and_err
    atf_add_test_case unknown_flag
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/sysbuild_test.sh
# 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.


# Path to a local cvsroot for testing purposes.
MOCK_CVSROOT=":local:$(pwd)/cvsroot"


# Creates a fake program that records its invocations for later processing.
#
# The fake program, when invoked, will append its arguments to a commands.log
# file in the test case's work directory.
#
# \param binary The path to the program to create.
# \param delegate If set to 'yes', execute the real program afterwards.
create_mock_binary() {
    local binary="${1}"; shift
    local delegate=no
    [ ${#} -eq 0 ] || { delegate="${1}"; shift; }

    cat >"${binary}" <<EOF
#! /bin/sh

logfile="${HOME}/commands.log"
echo "Command: \${0##*/}" >>"\${logfile}"
echo "Directory: \$(pwd)" >>"\${logfile}"
for arg in "\${@}"; do
    echo "Arg: \${arg}" >>"\${logfile}"
done
    echo >>"\${logfile}"
EOF

    if [ "${delegate}" = yes ]; then
        cat >>"${binary}" <<EOF
PATH="${PATH}"
exec "\${0##*/}" "\${@}"
EOF
    fi

    chmod +x "${binary}"
}


# Creates a fake CVS repository with a src and an xsrc module.
#
# \param repository Path to the repository to create.
create_mock_cvsroot() {
    local repository="${1}"; shift

    atf_check -o ignore -e ignore cvs -d "${repository}" init

    mkdir src
    cd src
    create_mock_binary build.sh
    echo "first revision" >file-in-src
    cvs -d "${repository}" import -m "Import." src VENDOR_TAG release_tag
    cd -
    rm -rf src

    mkdir xsrc
    cd xsrc
    echo "first revision" >file-in-xsrc
    cvs -d "${repository}" import -m "Import." xsrc VENDOR_TAG release_tag
    cd -
    rm -rf xsrc
}


# Creates a fake id(1) binary for use by the "unprivileged-user" tests.
create_mock_id() {
    [ -n "@SYSBUILD_USER@" ] \
        || atf_skip "Package built without unprivileged-user"

    cat >id <<EOF
#! /bin/sh

echo @SYSBUILD_USER@
EOF
    chmod +x id
    PATH="$(pwd):${PATH}"
}


atf_test_case build__custom_dirs
build__custom_dirs_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"

    create_mock_binary cvs yes
    PATH="$(pwd):${PATH}"

    cat >test.conf <<EOF
BUILD_ROOT=$(pwd)/b
CVSROOT=${MOCK_CVSROOT}
RELEASEDIR=$(pwd)/r
SRCDIR=$(pwd)/s
XSRCDIR=$(pwd)/x
EOF

    atf_check -o save:stdout -e save:stderr sysbuild -c test.conf build

    cat >expout <<EOF
Command: cvs
Directory: ${HOME}/s/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: src

Command: cvs
Directory: ${HOME}/x/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: xsrc

Command: build.sh
Directory: ${HOME}/s
Arg: -D${HOME}/b/$(uname -m)/destdir
Arg: -M${HOME}/b/$(uname -m)/obj
Arg: -N2
Arg: -R${HOME}/r
Arg: -T${HOME}/b/$(uname -m)/tools
Arg: -U
Arg: -X${HOME}/x
Arg: -m$(uname -m)
Arg: -x
Arg: release

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case build__defaults
build__defaults_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"

    create_mock_binary cvs yes
    PATH="$(pwd):${PATH}"

    atf_check -o save:stdout -e save:stderr sysbuild \
        -c /dev/null -o CVSROOT="${MOCK_CVSROOT}" build

    cat >expout <<EOF
Command: cvs
Directory: ${HOME}/sysbuild/src/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: src

Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/$(uname -m)/destdir
Arg: -M${HOME}/sysbuild/$(uname -m)/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/$(uname -m)/tools
Arg: -U
Arg: -m$(uname -m)
Arg: release

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case build__remove_all
build__remove_all_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"
    mkdir sysbuild
    cd sysbuild
    atf_check -o ignore -e ignore cvs -d"${MOCK_CVSROOT}" checkout -P src
    cd -

    create_mock_binary cvs
    PATH="$(pwd):${PATH}"

    mkdir -p "sysbuild/$(uname -m)/destdir/a"
    mkdir -p "sysbuild/$(uname -m)/obj/b"
    mkdir -p "sysbuild/$(uname -m)/tools/c"
    mkdir -p "sysbuild/$(uname -m)/keep-me"

    atf_check -o save:stdout -e save:stderr sysbuild \
        -c /dev/null -o CVSROOT="${MOCK_CVSROOT}" build

    [ ! -d "sysbuild/$(uname -m)/destdir" ] || atf_fail "destdir not removed"
    [ ! -d "sysbuild/$(uname -m)/obj" ] || atf_fail "obj not removed"
    [ ! -d "sysbuild/$(uname -m)/tools" ] || atf_fail "tools not removed"
    [ -d "sysbuild/$(uname -m)/keep-me" ] || atf_fail "All of buildroot removed"
}


atf_test_case build__fast_mode
build__fast_mode_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"
    mkdir sysbuild
    cd sysbuild
    atf_check -o ignore -e ignore cvs -d"${MOCK_CVSROOT}" checkout -P src
    cd -

    create_mock_binary cvs
    PATH="$(pwd):${PATH}"

    mkdir -p "sysbuild/$(uname -m)/destdir/bin"
    mkdir -p "sysbuild/$(uname -m)/destdir/stand/$(uname -m)/1.2.3"

    atf_check -o save:stdout -e save:stderr sysbuild \
        -c /dev/null -o CVSROOT="${MOCK_CVSROOT}" build -f

    cat >expout <<EOF
Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/$(uname -m)/destdir
Arg: -M${HOME}/sysbuild/$(uname -m)/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/$(uname -m)/tools
Arg: -U
Arg: -m$(uname -m)
Arg: -u
Arg: release

EOF
    atf_check -o file:expout cat commands.log

    [ -d "sysbuild/$(uname -m)/destdir/bin" ] \
        || atf_fail "Deleted a directory that should not have been deleted"
    [ ! -d "sysbuild/$(uname -m)/destdir/stand/$(uname -m)/1.2.3" ] \
        || atf_fail "Obsolete modules not deleted"
}


atf_test_case build__many_machines
build__many_machines_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"

    create_mock_binary cvs yes
    PATH="$(pwd):${PATH}"

    atf_check -o save:stdout -e save:stderr sysbuild \
        -c /dev/null -o CVSROOT="${MOCK_CVSROOT}" \
        -o MACHINES="amd64 macppc shark" -o NJOBS=2 build

    cat >expout <<EOF
Command: cvs
Directory: ${HOME}/sysbuild/src/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: src

Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/amd64/destdir
Arg: -M${HOME}/sysbuild/amd64/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/amd64/tools
Arg: -U
Arg: -j2
Arg: -mamd64
Arg: release

Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/macppc/destdir
Arg: -M${HOME}/sysbuild/macppc/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/macppc/tools
Arg: -U
Arg: -j2
Arg: -mmacppc
Arg: release

Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/shark/destdir
Arg: -M${HOME}/sysbuild/shark/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/shark/tools
Arg: -U
Arg: -j2
Arg: -mshark
Arg: release

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case build__with_x11
build__with_x11_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"

    create_mock_binary cvs yes
    PATH="$(pwd):${PATH}"

    atf_check -o save:stdout -e save:stderr sysbuild \
        -c /dev/null -o CVSROOT="${MOCK_CVSROOT}" \
        -o XSRCDIR="${HOME}/sysbuild/xsrc" build

    cat >expout <<EOF
Command: cvs
Directory: ${HOME}/sysbuild/src/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: src

Command: cvs
Directory: ${HOME}/sysbuild/xsrc/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: xsrc

Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/$(uname -m)/destdir
Arg: -M${HOME}/sysbuild/$(uname -m)/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/$(uname -m)/tools
Arg: -U
Arg: -X${HOME}/sysbuild/xsrc
Arg: -m$(uname -m)
Arg: -x
Arg: release

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case build__some_args
build__some_args_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"

    create_mock_binary cvs yes
    PATH="$(pwd):${PATH}"

    atf_check -o save:stdout -e save:stderr sysbuild \
        -c /dev/null -o CVSROOT="${MOCK_CVSROOT}" build a foo b

    cat >expout <<EOF
Command: cvs
Directory: ${HOME}/sysbuild/src/.cvs-checkout
Arg: -d${MOCK_CVSROOT}
Arg: -q
Arg: checkout
Arg: -P
Arg: src

Command: build.sh
Directory: ${HOME}/sysbuild/src
Arg: -D${HOME}/sysbuild/$(uname -m)/destdir
Arg: -M${HOME}/sysbuild/$(uname -m)/obj
Arg: -N2
Arg: -R${HOME}/sysbuild/release
Arg: -T${HOME}/sysbuild/$(uname -m)/tools
Arg: -U
Arg: -m$(uname -m)
Arg: a
Arg: foo
Arg: b

EOF
    atf_check -o file:expout cat commands.log
}


atf_test_case config__builtins
config__builtins_body() {
    cat >expout <<EOF
BUILD_ROOT = ${HOME}/sysbuild
BUILD_TARGETS = release
CVSROOT = :ext:anoncvs@anoncvs.NetBSD.org:/cvsroot
CVSTAG is undefined
INCREMENTAL_BUILD = false
MACHINES = $(uname -m)
NJOBS is undefined
RELEASEDIR = ${HOME}/sysbuild/release
SRCDIR = ${HOME}/sysbuild/src
UPDATE_SOURCES = true
XSRCDIR is undefined
EOF
    atf_check -o file:expout sysbuild -c /dev/null config
}


atf_test_case config__path__components
config__path__components_body() {
    mkdir .sysbuild
    mkdir system
    export SYSBUILD_ETCDIR="$(pwd)/system"

    echo "BUILD_TARGETS=foo" >my-file
    atf_check -o match:"BUILD_TARGETS = foo" sysbuild -c ./my-file config
}


atf_test_case config__path__extension
config__path__extension_body() {
    mkdir .sysbuild
    mkdir system
    export SYSBUILD_ETCDIR="$(pwd)/system"

    echo "BUILD_TARGETS=bar" >my-file.conf
    atf_check -o match:"BUILD_TARGETS = bar" sysbuild -c my-file.conf config
}


atf_test_case config__name__system_directory
config__name__system_directory_body() {
    mkdir .sysbuild
    mkdir system
    export SYSBUILD_ETCDIR="$(pwd)/system"

    echo "BUILD_TARGETS='some value'" >system/foo.conf
    atf_check -o match:"BUILD_TARGETS = some value" sysbuild -c foo config
}


atf_test_case config__name__user_directory
config__name__user_directory_body() {
    mkdir .sysbuild
    mkdir system
    export SYSBUILD_ETCDIR="$(pwd)/system"

    echo "BUILD_TARGETS='some value'" >system/foo.conf
    echo "BUILD_TARGETS='other value'" >.sysbuild/foo.conf
    atf_check -o match:"BUILD_TARGETS = other value" sysbuild -c foo config
}


atf_test_case config__name__not_found
config__name__not_found_body() {
    mkdir .sysbuild
    mkdir system
    export SYSBUILD_ETCDIR="$(pwd)/system"

    cat >experr <<EOF
sysbuild: E: Cannot locate configuration named 'foobar'
Type 'man sysbuild' for help
EOF
    atf_check -s exit:1 -o empty -e file:experr sysbuild -c foobar config
}


atf_test_case config__overrides
config__overrides_body() {
    cat >custom.conf <<EOF
BUILD_ROOT=/tmp/test
CVSTAG=the-tag-override
EOF

    cat >expout <<EOF
BUILD_ROOT = /tmp/test
BUILD_TARGETS = release
CVSROOT = foo bar
CVSTAG = the-new-tag
INCREMENTAL_BUILD = false
MACHINES = $(uname -m)
NJOBS is undefined
RELEASEDIR = ${HOME}/sysbuild/release
SRCDIR is undefined
UPDATE_SOURCES = true
XSRCDIR is undefined
EOF
    atf_check -o file:expout sysbuild -c custom.conf -o CVSROOT="foo bar" \
        -o CVSTAG=the-new-tag -o SRCDIR= config
}


atf_test_case config__too_many_args
config__too_many_args_body() {
    cat >experr <<EOF
sysbuild: E: config does not take any arguments
Type 'man sysbuild' for help
EOF
    atf_check -s exit:1 -e file:experr sysbuild -c /dev/null config foo
}


atf_test_case fetch__checkout__src_only
fetch__checkout__src_only_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"
    cat >test.conf <<EOF
CVSROOT="${MOCK_CVSROOT}"
SRCDIR="$(pwd)/checkout/src"
XSRCDIR=
EOF

    atf_check -o ignore -e not-match:"xsrc" sysbuild -c test.conf fetch
    test -f checkout/src/file-in-src || atf_fail "src not checked out"
    test ! -d checkout/xsrc || atf_fail "xsrc checked out but not requested"
}


atf_test_case fetch__checkout__src_and_xsrc
fetch__checkout__src_and_xsrc_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"
    cat >test.conf <<EOF
CVSROOT="${MOCK_CVSROOT}"
SRCDIR="$(pwd)/checkout/src"
XSRCDIR="$(pwd)/checkout/xsrc"
EOF

    atf_check -o ignore -e ignore sysbuild -c test.conf fetch
    test -f checkout/src/file-in-src || atf_fail "src not checked out"
    test -f checkout/xsrc/file-in-xsrc || atf_fail "xsrc not checked out"
}


atf_test_case fetch__update__src_only
fetch__update__src_only_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"
    cat >test.conf <<EOF
CVSROOT="${MOCK_CVSROOT}"
SRCDIR="$(pwd)/checkout/src"
XSRCDIR=
EOF

    mkdir checkout
    cd checkout
    atf_check -o ignore -e ignore cvs -d"${MOCK_CVSROOT}" checkout -P src
    cd -

    cp -rf checkout/src src-copy
    cd src-copy
    echo "second revision" >file-in-src
    cvs commit -m "Second revision."
    cd -

    test -f checkout/src/file-in-src || atf_fail "src not present yet"
    if grep "second revision" checkout/src/file-in-src >/dev/null; then
        atf_fail "second revision already present"
    fi

    atf_check -o ignore -e not-match:"xsrc" sysbuild -c test.conf fetch

    grep "second revision" checkout/src/file-in-src >/dev/null \
        || atf_fail "src not updated"
    test ! -d checkout/xsrc || atf_fail "xsrc checked out but not requested"
}


atf_test_case fetch__update__src_and_xsrc
fetch__update__src_and_xsrc_body() {
    create_mock_cvsroot "${MOCK_CVSROOT}"
    cat >test.conf <<EOF
CVSROOT="${MOCK_CVSROOT}"
SRCDIR="$(pwd)/checkout/src"
XSRCDIR="$(pwd)/checkout/xsrc"
EOF

    mkdir checkout
    cd checkout
    atf_check -o ignore -e ignore cvs -d"${MOCK_CVSROOT}" checkout -P src xsrc
    cd -

    cp -rf checkout/src src-copy
    cd src-copy
    echo "second revision" >file-in-src
    cvs commit -m "Second revision."
    cd -

    cp -rf checkout/xsrc xsrc-copy
    cd xsrc-copy
    echo "second revision" >file-in-xsrc
    cvs commit -m "Second revision."
    cd -

    test -f checkout/src/file-in-src || atf_fail "src not present yet"
    if grep "second revision" checkout/src/file-in-src >/dev/null; then
        atf_fail "second revision already present"
    fi
    test -f checkout/xsrc/file-in-xsrc || atf_fail "xsrc not present yet"
    if grep "second revision" checkout/xsrc/file-in-xsrc >/dev/null; then
        atf_fail "second revision already present"
    fi

    atf_check -o ignore -e ignore sysbuild -c test.conf fetch

    grep "second revision" checkout/src/file-in-src >/dev/null \
        || atf_fail "src not updated"
    grep "second revision" checkout/xsrc/file-in-xsrc >/dev/null \
        || atf_fail "xsrc not updated"
}


atf_test_case fetch__too_many_args
fetch__too_many_args_body() {
    cat >experr <<EOF
sysbuild: E: fetch does not take any arguments
Type 'man sysbuild' for help
EOF
    atf_check -s exit:1 -e file:experr sysbuild -c /dev/null fetch foo
}


atf_test_case no_command
no_command_body() {
    cat >experr <<EOF
sysbuild: E: No command specified
Type 'man sysbuild' for help
EOF
    atf_check -s exit:1 -e file:experr sysbuild
}


atf_test_case unknown_command
unknown_command_body() {
    cat >experr <<EOF
sysbuild: E: Unknown command foo
Type 'man sysbuild' for help
EOF
    atf_check -s exit:1 -e file:experr sysbuild foo
}


atf_test_case unknown_flag
unknown_flag_body() {
    cat >experr <<EOF
sysbuild: E: Unknown option -Z
Type 'man sysbuild' for help
EOF
    atf_check -s exit:1 -e file:experr sysbuild -Z
}


atf_init_test_cases() {
    atf_add_test_case build__custom_dirs
    atf_add_test_case build__defaults
    atf_add_test_case build__remove_all
    atf_add_test_case build__fast_mode
    atf_add_test_case build__many_machines
    atf_add_test_case build__with_x11
    atf_add_test_case build__some_args

    atf_add_test_case config__builtins
    atf_add_test_case config__path__components
    atf_add_test_case config__path__extension
    atf_add_test_case config__name__system_directory
    atf_add_test_case config__name__user_directory
    atf_add_test_case config__name__not_found
    atf_add_test_case config__overrides
    atf_add_test_case config__too_many_args

    atf_add_test_case fetch__checkout__src_only
    atf_add_test_case fetch__checkout__src_and_xsrc
    atf_add_test_case fetch__update__src_only
    atf_add_test_case fetch__update__src_and_xsrc
    atf_add_test_case fetch__too_many_args

    atf_add_test_case no_command
    atf_add_test_case unknown_command
    atf_add_test_case unknown_flag
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/utils.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 utils.subr
# Miscellaneous utility functions and global variables.


# Base name of the running script.
Utils_ProgName="${0##*/}"


# Prints a runtime error and exits.
#
# \param ... The message to print.  Can be provided as multiple words and, in
#     that case, they are joined together by a single whitespace.
utils_error() {
    echo "${Utils_ProgName}: E: $*" 1>&2
    exit 1
}


# Prints an informational message.
#
# \param ... The message to print.  Can be provided as multiple words and, in
#     that case, they are joined together by a single whitespace.
utils_info() {
    echo "${Utils_ProgName}: I: $*" 1>&2
}


# Prints a runtime warning.
#
# \param ... The message to print.  Can be provided as multiple words and, in
#     that case, they are joined together by a single whitespace.
utils_warning() {
    echo "${Utils_ProgName}: W: $*" 1>&2
}


# Prints an usage error and exits.
#
# \param ... The message to print.  Can be provided as multiple words and, in
#     that case, they are joined together by a single whitespace.
utils_usage_error() {
    echo "${Utils_ProgName}: E: $*" 1>&2
    echo "Type 'man ${Utils_ProgName}' for help" 1>&2
    exit 1
}


# Executes a command with logging.
#
# \param ... The command to execute.
#
# \return The exit code of the executed command.
utils_run() {
    utils_info "Running '${@}' in $(pwd)"
    local ret=0
    "${@}" || ret="${?}"
    if [ ${ret} -eq 0 ]; then
        utils_info "Command finished successfully"
    else
        utils_warning "Command failed with code ${ret}"
    fi
    return "${ret}"
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/utils_test.sh
# 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.


atf_test_case global_progname
global_progname_body() {
    [ "${Utils_ProgName}" = utils_test ] \
        || atf_fail "Invalid value in Utils_ProgName"
}


atf_test_case error
error_body() {
    if ( utils_error "This is" "a message"; echo "not seen" ) >out 2>err; then
        atf_fail "utils_error did not exit with an error"
    else
        grep "utils_test: E: This is a message" err >/dev/null \
            || atf_fail "Expected error message not found"
        [ ! -s out ] || atf_fail "Unexpected output in stdout"
    fi
}


atf_test_case info
info_body() {
    ( utils_info "This is" "a message"; echo "continuing" ) >out 2>err
    grep "utils_test: I: This is a message" err >/dev/null \
        || atf_fail "Expected info message not found"
    grep "continuing" out >/dev/null || atf_fail "Execution aborted"
}


atf_test_case usage_error
usage_error_body() {
    if ( utils_usage_error "This is" "a message"; echo "not seen" ) >out 2>err
    then
        atf_fail "utils_usage_error did not exit with an error"
    else
        grep "utils_test: E: This is a message" err >/dev/null \
            || atf_fail "Expected error message not found"
        grep "Type 'man utils_test' for help" err >/dev/null \
            || atf_fail "Expected instructional message not found"
        [ ! -s out ] || atf_fail "Unexpected output in stdout"
    fi
}


atf_test_case warning
warning_body() {
    ( utils_warning "This is" "a message"; echo "continuing" ) >out 2>err
    grep "utils_test: W: This is a message" err >/dev/null \
        || atf_fail "Expected info message not found"
    grep "continuing" out >/dev/null || atf_fail "Execution aborted"
}


atf_test_case run__ok
run__ok_body() {
    cat >helper.sh <<EOF
#! /bin/sh
echo "This exits cleanly:" "\${@}"
exit 0
EOF
    chmod +x helper.sh

    utils_run ./helper.sh one two three >out 2>err \
        || atf_fail "Got an unexpected error code"

    cat >expout <<EOF
This exits cleanly: one two three
EOF
    atf_check -o file:expout cat out

    cat >experr <<EOF
utils_test: I: Running './helper.sh one two three' in $(pwd)
utils_test: I: Command finished successfully
EOF
    atf_check -o file:experr cat err
}


atf_test_case run__fail
run__fail_body() {
    cat >helper.sh <<EOF
#! /bin/sh
echo "This exits with an error:" "\${@}"
exit 42
EOF
    chmod +x helper.sh

    code=0
    utils_run ./helper.sh one two three >out 2>err || code="${?}"
    [ ${code} -eq 42 ] \
        || atf_fail "Did not get the expected error code; got ${code}"

    cat >expout <<EOF
This exits with an error: one two three
EOF
    atf_check -o file:expout cat out

    cat >experr <<EOF
utils_test: I: Running './helper.sh one two three' in $(pwd)
utils_test: W: Command failed with code 42
EOF
    atf_check -o file:experr cat err
}


atf_init_test_cases() {
    atf_add_test_case global_progname

    atf_add_test_case error
    atf_add_test_case info
    atf_add_test_case warning

    atf_add_test_case usage_error

    atf_add_test_case run__ok
    atf_add_test_case run__fail
}

File Added: pkgsrc/sysutils/sysbuild/files/Attic/default.conf
# $NetBSD: default.conf,v 1.6 2012/07/25 12:18:23 jmmv Exp $

# System-wide configuration file for sysbuild(1).
#
# This default configuration file tells sysbuild to use the source trees from
# the standard locations documented by hier(7) and allows any user to easily
# perform NetBSD builds on their home directories using those.

# Place all build files in the user's home directory.
BUILD_ROOT="${HOME}/sysbuild"

# Store release files relative to the build root.
RELEASEDIR="${BUILD_ROOT}/release"

# Use the standard source trees as documented by hier(7), and honor if the
# system has X installed or not.
SRCDIR="/usr/src"
[ ! -f /etc/mtree/set.xbase ] || XSRCDIR="/usr/xsrc"

# Build for the current machine only.
MACHINES="$(uname -m)"

# What to build for every machine.
BUILD_TARGETS="release"

# Do not perform update builds by default.
INCREMENTAL_BUILD="no"

# Determine the CVS root and the current release.
if [ -f "${SRCDIR}/CVS/Root" ]; then
    CVSROOT="$(cat ${SRCDIR}/CVS/Root)"
else
    CVSROOT=":ext:anoncvs@anoncvs.NetBSD.org:/cvsroot"
fi
if [ -f "${SRCDIR}/CVS/Tag" ]; then
    CVSTAG="$(cat ${SRCDIR}/CVS/Tag)"
else
    if ! head -n 1 /etc/release | grep 99 >/dev/null; then
        CVSTAG="$(head -n 1 /etc/release | cut -d . -f 1 \
                  | tr '[A-Z] ' '[a-z]-')"
    fi
fi

# Use as many CPUs as are available.
NJOBS="$(/sbin/sysctl -n hw.ncpuonline)"

File Added: pkgsrc/sysutils/sysbuild/files/Attic/sysbuild.sh
# 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.


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


# Paths to installed files.
#
# Can be overriden for test purposes only.
: ${SYSBUILD_ETCDIR="@SYSBUILD_ETCDIR@"}


# Sets defaults for configuration variables that need a value.
#
# 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.
    config_set BUILD_ROOT "${HOME}/sysbuild"
    config_set BUILD_TARGETS "release"
    config_set CVSROOT ":ext:anoncvs@anoncvs.NetBSD.org:/cvsroot"
    config_set INCREMENTAL_BUILD "false"
    config_set MACHINES "$(uname -m)"
    config_set RELEASEDIR "${HOME}/sysbuild/release"
    config_set SRCDIR "${HOME}/sysbuild/src"
    config_set UPDATE_SOURCES "true"
}


# Performs a build for a single machine type.
#
# \param machine The type of the machine to build for.
do_one_build() {
    local machine="${1}"; shift

    local basedir="$(config_get BUILD_ROOT)/${machine}"

    local jflag=
    if config_has NJOBS; then
        jflag="-j$(config_get NJOBS)"
    fi

    local uflag=
    if 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
        utils_info "Cleaning up previous build files"
        rm -rf "${basedir}/destdir"
        rm -rf "${basedir}/obj"
        rm -rf "${basedir}/tools"
    fi

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

    ( cd "$(config_get SRCDIR)" && utils_run ./build.sh \
        -D"${basedir}/destdir" \
        -M"${basedir}/obj" \
        -N2 \
        -R"$(config_get RELEASEDIR)" \
        -T"${basedir}/tools" \
        -U \
        ${Xflag} \
        ${jflag} \
        -m"${machine}" \
        ${uflag} \
        ${xflag} \
        $(config_get BUILD_TARGETS) )
}


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

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

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

    if config_get_bool UPDATE_SOURCES; then
        sysbuild_fetch
    fi

    for machine in $(config_get MACHINES); do
        do_one_build "${machine}"
    done
}


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

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


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

    local cvsroot="$(config_get CVSROOT)"

    utils_info "Updating base source tree"
    cvs_fetch "${cvsroot}" src "$(config_get_default CVSTAG '')" \
        "$(config_get SRCDIR)"

    if config_has XSRCDIR; then
        utils_info "Updating X11 source tree"
        cvs_fetch "${cvsroot}" xsrc "$(config_get_default CVSTAG '')" \
            "$(config_get XSRCDIR)"
    fi
}


# 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}" ] \
                || utils_usage_error "Cannot locate configuration named" \
                "'${config_name}'"
            ;;
    esac
    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.
sysbuild_main() {
    local config_name="default"

    config_init ${SYSBUILD_CONFIG_VARS}

    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.
                config_override "${OPTARG}"
                ;;

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

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

    local exit_code=0

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

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

    return "${exit_code}"
}