#!/bin/sh
# -*- shell-script -*-

# Copyright (c) 2004 Gregory D. Troxel.  All rights reserved.
# Copyright (c) 2004--2009 BBN Technologies Corp.  All rights reserved.
# Copyright (c) 2010--2014 Raytheon BBN Technologies.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. 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.
# 3. Neither the name of BBN Technologies 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 RAYTHEON BBN TECHNOLOGIES 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 BBN TECHNOLOGIES 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.

# This software was developed under funding from DARPA's ACERT program
# under contract number NBCH050166.

## This script supports automatic building and installing of NetBSD,
## and functions from netbsd-2 onward.  It is mostly a wrapper around
## build.sh.

## The script is documented by included comments, as it is assumed
## that users of the script will have some understanding of how it
## works.

## The script assumes that src and xsrc are present in the current
## directory, checked out with matching (possibly null) tags.

## It is intended that builds be done unprivileged, and that installs
## are done as root.

## Build a release with x for the current architecture
##   BUILD-NetBSD -x all
## Build a release without x
##   BUILD-NetBSD all
## Build a release with x for sparc64
##   BUILD-NetBSD -x sparc64
## Install an already built release
##   BUILD-NetBSD -x install
## Install just kernels from an already built release
##   BUILD-NetBSD -x installkernel

log() { printf %s\\n "$*"; }
logn() { printf %s "$*"; }
error() { log "ERROR: $@" >&2; }
fatal() { error "$@"; exit 1; }
try() { "$@" || fatal "'$@' failed"; }

logn "BUILDALL OVERALL start "; date

## The following files store information about the kernel config that
## should be installed on the current machine, and the branch of
## NetBSD that the machine is currently running.
kernel_file=/.BUILD-NetBSD.kernel
branch_file=/.BUILD-NetBSD.branch

SRCFILE=sys/arch/i386/conf/GENERIC
X11SRCFILE=external/mit/xdm/dist/Makefile.am

pwd=$(try /bin/pwd) || exit 1
uid=$(try id -u) || exit 1

# Check a source file to verify that we (probably) have a source tree.
if [ ! -f src/"$SRCFILE" ]; then
    fatal src/"$SRCFILE not present"
fi

## Find branch for use in OBJDIR etc.

# Check for a user-declared branch.
if [ -f .branch ]; then
    branch=$(try cat .branch) || exit 1
fi

# Infer branch from CVS, if present.
if [ -z "$branch" ] && [ -d src/CVS ]; then

    # CVS branches have an explicit tag, and the trunk is called "current".
    if [ -f src/CVS/Tag ]; then
	cvstag=$(try cat src/CVS/Tag) || exit 1
    else
	cvstag='Tcurrent'
    fi

    # Set token for directory name based on branch.
    case "$cvstag" in
	Tcurrent) branch=current;;
	Tnetbsd-*) branch=${cvstag#Tnetbsd-};;
	*) fatal "UNKNOWN/UNSUPPORTED BRANCH $progname";;
    esac

    # Check for supported branches (NetBSD v3 or newer).
    [ "${branch}" = current ] || [ "${branch%%-*}" -ge 3 ] \
	|| fatal "UNKNOWN/UNSUPPORTED BRANCH $progname"
fi

if [ -z "$branch" ]; then
    error "No .branch defined and cannot infer branch from VCS."
    error "(.branch is used for naming, and also in case building is different)"
    exit 1;
fi

log "Building with branch token $branch"

## Set up build directories.
# The general plan is to have a separate obj/dest/release dir for each
# source tree.  "Each source tree" distinguishes branches of NetBSD
# and also which user is doing the build.  This requires thought, so
# we simply force that thought and fail if it is not specified, rather
# than attempting to infer what is desired and getting it wrong.

# Allowable contents of .tree are
#   single word, e.g. "auto" to denote system-level build of $branch
#   two words:
#	path absolute	to denote that the objroot shoud be path
#	path relative	to denote that the objroot shoud be ./path
# For a single word foo, we'll place the container for objdir, tooldir,
# and so on at /usr/obj/foo-$branch.  For "local bar", we'll place it as ./bar.
TREEFILE=.tree
if [ -r "$TREEFILE" ]; then
    read tree1 tree2 < $TREEFILE
else
    error "The file .tree does not exist.  BUILD-NetBSD requires that you"
    error "specify the location of the OBJDIR container directory."
    error "Read the comments in the script for instructions."
    exit 1
fi

if [ "$tree1" = "" ]; then
    fatal ".tree file is empty"
fi

# Choose base location.  It is intentional not to allow separating
# tools, obj, destdir, and releasedir.
case "$tree2" in
    "")
	# Be compatible with historical behavior, which if .tree
	# contained "auto" and branch was 5 would build into
	# /usr/obj/auto-5.  This lets one put their username in .tree
	# and keep 4/5/current build trees separate.
	BUILDBASE=/usr/obj/$tree1-$branch
	;;
    "absolute")
	# $tree1 should begin with /.  Arguably not having a leading / should
	# just be an error.
	case "$tree1" in
	    /*) BUILDBASE=${tree1};;
	    *) BUILDBASE=/${tree1};;
	esac
	;;
    "relative")
	BUILDBASE=${pwd}/$tree1
	;;
    *) fatal "Invalid second word <$tree2> in .tree";;
esac


# Use xsrc next to src, to enable cross builds.
X11SRCDIR=${pwd}/xsrc

ETCSETS=etc

# Build X if -x flag is given.
if [ "$1" = "-x" ]; then
    xflag="-x"
    ETCSETS="$ETCSETS xetc"
    shift
    # Check that X11 sources are present.
    if [ ! -f "$X11SRCDIR"/"$X11SRCFILE" ]; then
	fatal "$X11SRCDIR"/"$X11SRCFILE not present."
    fi
else
    xflag=""
fi

# Parse fixed arguments.
cmd=$1
# Note that arch will typically be a "uname -m" value, in simple
# cases, or an alias that defines a combination of uname -m and -p,
# like evbearmv7hf-el which maps to evbarm and earmv7hf.
arch=$2

# Make first-cut decision about UNPRIV (UNPRIV if non-root)
if [ 0 != "${uid}" ]; then
    log "Not ROOT: doing UNPRIVed build"
    pflag="-U"
else
    pflag=""
fi

# Function to check for root at install time, and set -U if build
# was done UNPRIVed.
installrootcheck () {
    if [ 0 != "${uid}" ]; then
	fatal "MUST BE ROOT TO INSTALL"
    fi
    if [ -f "$DESTDIR"/METALOG ]; then
	log "INSTALL with METALOG: doing UNPRIVed install"
	pflag="-U"
    else
	log "INSTALL without METALOG: not setting UNPRIVed"
	pflag=""
    fi
}

# Set 'update' flag to avoid make clean. This is possibly dangerous.
# Remove uflag, or nuke /usr/obj if problematic.
uflag="-u"

# Set parallel flag to have 2 jobs running per CPU.  XXX other OS
jflag="-j2"
uname=$(try uname) || exit 1
if [ "${uname}" = NetBSD ]; then
    sysctl_ncpu=$(try sysctl hw.ncpu) || exit 1
    jflag=-j$(printf %s\\n "${sysctl_ncpu}"|try awk '{print 2*$3}') || exit 1
fi

## Compute and set build.sh environment variables.

# determine arch (do a self-hosted build if not given)
if [ "$arch" = "" ]; then
    arch=$(try uname -m) || exit 1
fi

# Put tools within base (not dependent on $arch).  Put tools bin in PATH
# so we can run nbmake-$arch later.
export TOOLDIR="$BUILDBASE"/tools
TOOLSBIN=$TOOLDIR/bin

# Set OBJDIR to be unique for this architecture.
export OBJDIR="$BUILDBASE"/"$arch"

# Place all destdirs together, separated by architecture.
export DESTDIR="$BUILDBASE"/destdir/"$arch"

# Note that RELEASEDIR is the parent directory, in which there is /$arch.
export RELEASEDIR="$BUILDBASE"/releasedir

# build.sh uses $arch by default
export RELEASEMACHINEDIR=$arch

if [ "$cmd" = "" ]; then
    cmd=all
    log "Command omitted - assuming \"all\""
fi

case "$cmd" in
    # these targets invoke the same-named build.sh target
    distribution|release)
	target=${cmd}
	;;
    # Build an installable ISO image.
    iso)
	target=none
	log "ISO: ASSUMING RELEASE ALREADY COMPLETE!"
	;;
    # first do release, then do iso
    all)
	target=release
	log "ALL: doing full build to iso"
	;;
    # Install from previous distribution.
    install)
	target="install=/"
	installrootcheck
	log "INSTALL: ASSUMING DISTRIBUTION ALREADY COMPLETE!"
	;;
    # Install from previous distribution.
    installkernel)
	target=none
	installrootcheck
	log "INSTALL: ASSUMING DISTRIBUTION ALREADY COMPLETE!"
	;;
    *)
	cat <<EOF >&2
Usage: $0 [-x] [all|distribution|release|iso|install|installkernel]
EOF
	exit 1
	;;
esac

## Look for control file to specify a custom kernel.
if [ -f "$kernel_file" ]; then
    read kern < $kernel_file
fi

# Put tools in path and obtain path to our variant of make.
PATH=$TOOLSBIN:$PATH
MAKE=$TOOLSBIN/nbmake-$arch

## For install, install a kernel before invoking build.sh.
case $cmd in
    ## Install kernel first, so that if userland uses new system calls
    ## and the machine wedges, rebooting will fix it.
    ## XXX Split install to a separate script to enable binary-only updates.
    ## XXX Check the branch, and do just kernel for cross-branch upgrades,
    ## and then on next iteration after reboot do kernel and userland.
    install|installkernel)
	(
	    try cd "$OBJDIR"/sys/arch/"$arch"/compile/"$kern"
	    try $MAKE DESTDIR=/ install
	) || exit 1
	# Install gzipped kernels for GRUB/XEN if they are present.
	(
	    try cd /
	    for f in netbsd-*.gz; do
		k=$(expr "${f}" : 'netbsd-\([A-Z0-9_]*\)\.gz') || continue
		log "COPYING gzipped KERNEL $k"
		try cp -pf "${f}" "${f%.gz}".ok.gz
		try cp -pf "$RELEASEDIR"/"$arch"/binary/kernel/"${f}" /
	    done
	) || exit 1
	;;
esac

## Run build.sh as main target - distribution or release.
if [ "$target" != "none" ]; then
    logn "BUILDALL MAIN $target start "; date
    # Modules change names every time the system version changes, and
    # this results in failed builds due to extra files in DESTDIR.
    try rm -rf "$DESTDIR"/stand
    (
	try cd src
	try ./build.sh -m "$arch" $jflag $xflag $uflag $pflag \
	    -O "$OBJDIR" -T "$TOOLDIR" -D "$DESTDIR" -R "$RELEASEDIR" \
	    -X "$X11SRCDIR" "$target"
    ) || exit 1
    logn "BUILDALL MAIN $target finish "; date
fi

if [ x"$kern" != x ]; then
    if [ ! -f src/sys/arch/"$arch"/conf/"$kern" ]; then
	fatal "CANNOT FIND KERNEL CONFIG src/sys/arch/$arch/conf/$kern!"
    fi
    ## For release (and thus all), our chosen kernel may not be part
    ## of the normal build, so specifically request that it be built.
    case $cmd in
	release|all)
	    logn "BUILDALL KERNEL start $kern for $arch "; date
	    (
		try cd src
		try ./build.sh -m "$arch" $jflag $xflag $uflag $pflag \
		    -O "$OBJDIR" -T "$TOOLDIR" -D "$DESTDIR" -R "$RELEASEDIR" \
		    -X "$X11SRCDIR" kernel="$kern"
	    ) || exit 1
	    logn "BUILDALL KERNEL finish $kern for $arch "; date
	    ;;
    esac
fi

extract_sets() {
    d=$1; shift
    try mkdir -p "${d}"
    for set in "$@"; do
	setf=$RELEASEDIR/$arch/binary/sets/${set}.tgz
	(try cd "${d}"; try pax -rz -pe) <${setf} || exit 1
    done
}

## For release (and thus all), generate an etcmanage manifest.
case $cmd in
    release|all)
	[ -n "${TMPDIR}" ] && [ -d "${TMPDIR}" ] || TMPDIR=/tmp
	tmpdir=${TMPDIR}/BUILD-NetBSD_etcmanage.$$
	try rm -rf "${tmpdir}"
	extract_sets "${tmpdir}" ${ETCSETS}
	try etcmanage --generate-manifest "${tmpdir}" \
	    > $RELEASEDIR/$arch/binary/sets/ETCMANAGE-NetBSD-$branch
	try rm -rf "${tmpdir}"
	;;
esac

## If installing, run etcmanage.
## XXX Work around etcmanage bugs by running it 10 times.
case $cmd in
    install)
	NETBSDETC=/usr/netbsd-etc
	try rm -rf "${NETBSDETC}"
	extract_sets "${NETBSDETC}" ${ETCSETS}

	log "BUILDALL: etcmanage"
	[ -f "${NETBSDETC}"/etc/defaults/rc.conf ] && \
	    try etcmanage --update "${NETBSDETC}"
	# XXX need etc contents in other sets
	try cp -p "${DESTDIR}"/etc/release /etc
	(try cd /dev; try ./MAKEDEV all) || exit 1
	;;
esac

# For iso and all, build the iso image.
case $cmd in
    iso|all)
	logn "BUILDALL ISO start for $arch "; date
	case "$branch" in
	    2*|3*)
		(try cd src/etc; try $MAKE iso-image) || exit 1
		;;
	    *)
		(
		    try cd src
		    try ./build.sh -m "$arch" $jflag $xflag $uflag $pflag \
			-O "$OBJDIR" -T "$TOOLDIR" -D "$DESTDIR" \
			-R "$RELEASEDIR" -X "$X11SRCDIR" iso-image
		) || exit 1
		;;
	esac
	logn "BUILDALL ISO finish for $arch "; date
	;;
esac

logn "BUILDALL OVERALL finish "; date
