#!/bin/sh

# vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:

TOPDIR=$PWD

# CVSROOT is inherited from the environment
KERNELDIR=
LINUX=
LUSTRE=
RELEASE=0
DO_SRC=0
DOWNLOAD=1
TAG=
TARGET=
TARGET_ARCHS=
CONFIGURE_FLAGS=
EXTERNAL_PATCHES=
EXTRA_VERSION=
STAGEDIR=
TMPDIR=${TMPDIR:-"/var/tmp"}

# from target file
KERNEL=
SERIES=
CONFIG=
VERSION=

RHBUILD=0
SUSEBUILD=0
LINUX26=0
SUSEBUILD=0

BASE_ARCHS=
BIGMEM_ARCHS=
BOOT_ARCHS=
JENSEN_ARCHS=
SMP_ARCHS=
BIGSMP_ARCHS=
PSERIES64_ARCHS=
UP_ARCHS=

DATE=$(date)

USE_DATESTAMP=1
RPMBUILD=

export CC=${CC:-gcc}

# Readlink is not present on some older distributions: emulate it.
readlink() {
    local path=$1 ll

    if [ -L "$path" ]; then
        ll="$(LC_ALL=C ls -l "$path" 2> /dev/null)" &&
        echo "${ll/* -> }"
    else
        return 1
    fi
}

cleanup()
{
    true
}

error()
{
    [ "$1" ] && echo -e "\n${0##*/}: $1"
}

fatal()
{
    cleanup
    error "$2"
    exit $1
}

is_release()
{
    (( $RELEASE )) || return 0
}

list_targets()
{
    echo -n "Available targets:"
    for target in $TOPDIR/lustre/lustre/kernel_patches/targets/*.target ; do
        target_file=${target##*/}
        echo -n " ${target_file%%.target}"
    done
    echo
}

usage()
{
    cat <<EOF
Usage: ${0##*/} [OPTION]... [-- <lustre configure options>]

  -d CVSROOT
    Specifies the CVS Root to use when pulling files from CVS.  The
    environment variable \$CVSROOT is used if this option is not
    present.

  --external-patches=EXTERNAL_PATCHES
    Directory similar to lustre/lustre/kernel_patches/ that lbuild should
    look for seres and config files in before looking in the lustre
    tree.

  --extraversion=EXTRAVERSION
    Text to use for the rpm release and kernel extraversion.

  --kerneldir=KERNELDIR
    Directory containing Linux source tarballs referenced by target
    files.

  --linux=LINUX
    Directory of Linux kernel sources.  When this option is used, only
    Lustre modules and userspace are built.

  --lustre=LUSTRE
    Path to an existing lustre source tarball to use instead of
    pulling from CVS.

  --nodownload
    Do not try to download a kernel from ftp.lustre.org

  --nosrc
    Do not build a .src.rpm, a full kernel patch, or a patched kernel
    tarball.

  --publish
    Unused.

  --release
    Specifies that the files generated do not include timestamps, and
    that this is an official release.

  --src
    Build a .src.rpm, a full kernel patch, and a patched kernel tarball.

  --stage=DIR
    Directory used to stage packages for release.  RPMs will be placed
    more or less in DIR/<target>-<arch>, and the tarball will be
    placed in DIR.

  --tag=TAG
    A CVS branch/tag name to build from when pulling from CVS.

  --target=TARGET
    The name of the target to build.  The available targets are listed
    below.

  --target-archs=TARGET_ARCHS
    A (space delimited) list of architectures to build.  By default,
    all of the archs supported by the TARGET will be built, in
    addition to a .src.rpm.  This option can limit those, for machines
    that can only build certain archs or if you only want a certain
    arch built (for testing, or a one-off kernel).

    Also note that by using a non-"base" arch (eg, i386) only kernels
    will be built - there will be no lustre-lite-utils package.

  --disable-datestamp
    Prevents the datestamp flag (-D) from being passed to cvs for 
    checkouts. This is a workaround for a problem encountered when 
    using lbuild with tinderbox.

EOF

#   list_targets

    fatal "$1" "$2"
}

check_options()
{
    if [ "$LUSTRE" ] ; then
        [ -r "$LUSTRE" ] || \
            usage 1 "Could not find Lustre source tarball '$LUSTRE'."
    else
        [ "$CVSROOT" ] || \
            usage 1 "Either specify a CVS Root with -d, or a Lustre source tarball with --lustre."
        [ "$TAG" ] || \
            usage 1 "A branch/tag name must be specified with --tag when not building from a tarball."
    fi

    if [ -z "$LINUX" ] ; then
        [ "$KERNELDIR" ] || \
            usage 1 "A kernel directory must be specified with --kerneldir."

        [ -d "$KERNELDIR" ] || \
            usage 1 "$KERNELDIR is not a directory."

        if ! (( $RELEASE )) ; then
            [ "$TAG" ] || \
                usage 1 "When building a snapshot, a tag name must be used."
        fi

        [ "$TARGET" ] || usage 1 "A target must be specified with --target."
#       TARGET_FILE="$TOPDIR/lustre/kernel_patches/targets/$TARGET.target"
#       [ -r "$TARGET_FILE" ] || \
#               usage 1 "Target '$TARGET' was not found."
    fi

    case $TARGET in
        2.6-rhel4)
            CANONICAL_TARGET="rhel-2.6"
            ;;
        2.6-suse)
            CANONICAL_TARGET="sles-2.6"
            ;;
        2.6-sles10)
            CANONICAL_TARGET="sles10-2.6"
            ;;
        hp_pnnl-2.4)
            CANONICAL_TARGET="hp-pnnl-2.4"
            ;;
        2.6-vanilla \
            | suse-2.4.21-2 \
            | rh-2.4 \
            | rhel-2.4 \
            | sles-2.4 \
            | 2.6-patchless)
                CANONICAL_TARGET="$TARGET"
                ;;
    esac

    TIMESTAMP=$(date -d "$DATE" "+%Y%m%d%H%M")

    RPMBUILD=$(which rpmbuild 2>/dev/null | head -1)
    if [ ! "$RPMBUILD" -o "$RPMBUILD" == "" ]; then
        RPMBUILD=$(which rpm 2>/dev/null | head -1)
        if [ ! "$RPMBUILD" -o "$RPMBUILD" == "" ]; then
            usage 1 "Could not find binary for making rpms (tried rpmbuild and rpm)."
        fi
    fi
}

uniqify()
{
    echo $(echo "$*" | xargs -n 1 | sort -u)
}

build_tarball() {
    local TARGET=$1
    local SRPM=$2

    if [ "$TARGET" = "rhel-2.6" -o "$TARGET" = "rhel-2.4" ]; then
        local SPEC=""
        if [ "$TARGET" = "rhel-2.6" ]; then
            SPEC=kernel-2.6.spec
            OLDCONFIG=nonint_oldconfig
        elif [ "$TARGET" = "rhel-2.4" ]; then
            SPEC=kernel-2.4.spec
            OLDCONFIG=oldconfig
        fi

        RPMTOPDIR=$(mktemp -d $KERNELDIR/rpm_XXXXXX)
        mkdir $RPMTOPDIR/BUILD/
        rpm -ivh $KERNELDIR/$SRPM --define "_topdir $RPMTOPDIR" || \
            { rm -rf $RPMTOPDIR; fatal 1 "Error installing kernel SRPM."; }
        $RPMBUILD -bp --nodeps --target i686 $RPMTOPDIR/SPECS/$SPEC --define "_topdir $RPMTOPDIR"
        pushd $RPMTOPDIR/BUILD/kernel-${lnxmaj}/linux-${lnxmaj} && {
            make mrproper
            cp configs/kernel-${lnxmaj}-i686-smp.config .config
            if ! make $OLDCONFIG > /dev/null; then
                fatal 1 "error trying to make $OLDCONFIG while building a tarball from SRPM."
            fi
            make include/linux/version.h 
            rm -f .config
            cd ..
            tar cjf $KERNEL_FILE linux-${lnxmaj}
        }
        popd
        rm -rf $RPMTOPDIR
    fi
}

download_and_build_tarball() {
    local TARGET=$1
    local KERNEL_FILE=$2

    local SRPM=kernel-${lnxmaj}-${lnxrel}.src.rpm

    echo "Downloading http://ftp.lustre.org/kernels/$TARGET/old/$SRPM..."
    if ! wget -nv "http://ftp.lustre.org/kernels/$TARGET/old/$SRPM" \
        -O "$KERNELDIR/$SRPM" ; then
        fatal 1 "Could not download target $TARGET's kernel SRPM $SRPM from ftp.lustre.org."
    fi

    build_tarball $TARGET $SRPM
}

load_target()
{
    EXTRA_VERSION_save="$EXTRA_VERSION"
    for patchesdir in "$EXTERNAL_PATCHES" "$TOPDIR/lustre/lustre/kernel_patches" ; do
        TARGET_FILE="$patchesdir/targets/$TARGET.target"
        [ -r "$TARGET_FILE" ] && break
    done
    [ -r "$TARGET_FILE" ] || \
        fatal 1 "Target $TARGET was not found."

    echo "Loading target config file $TARGET.target..."        

    . "$TARGET_FILE"

    [ "$KERNEL"  ] || fatal 1 "Target $TARGET did not specify a kernel."
    [ "$VERSION" ] || fatal 1 "Target $TARGET did not specify a kernel version."

    if [ "$KERNELDIR" ] ; then
        KERNEL_FILE="$KERNELDIR/$KERNEL"
        if [ ! -r "$KERNELDIR/$KERNEL" ] ; then
            # see if we have an SRPM we can build a tarball for
            KERNEL_SRPM=kernel-${lnxmaj}-${lnxrel}.src.rpm
            if [ -r "$KERNELDIR/$KERNEL_SRPM" ] ; then
                build_tarball $CANONICAL_TARGET $KERNEL_SRPM
            else
                if (( $DOWNLOAD )) ; then
                    echo "Downloading http://ftp.lustre.org/kernels/$CANONICAL_TARGET/old/$KERNEL..."
                    if ! wget -nv "http://ftp.lustre.org/kernels/$CANONICAL_TARGET/old/$KERNEL" -O "$KERNELDIR/$KERNEL" ; then
                        # see if we can do it with an SRPM from the download site
                        download_and_build_tarball $CANONICAL_TARGET $KERNEL_FILE
                    fi
                else
                    fatal 1 "Target $TARGET's kernel file $KERNEL not found in kernel directory $KERNELDIR."
                fi
            fi
        fi
    fi

    if [ "$SERIES" ] ; then
        for series in $SERIES ; do
            for patchesdir in "$EXTERNAL_PATCHES" "$TOPDIR/lustre/lustre/kernel_patches" ; do
                [ -r "$patchesdir/series/$series" ] && continue 2
            done
            fatal 1 "Target $TARGET's series $SERIES could not be found.\nSearched:\n\t$EXTERNAL_PATCHES/series\n\t$TOPDIR/lustre/lustre/kernel_patches/series."
        done
    fi

    CONFIG_FILE="$TOPDIR/lustre/lustre/kernel_patches/kernel_configs/$CONFIG"
    [ -r "$CONFIG_FILE" ] || \
        fatal 1 "Target $TARGET's config file $CONFIG missing from $TOPDIR/lustre/lustre/kernel_patches/kernel_configs/."

    if [ "$EXTRA_VERSION_save" ] ; then
        EXTRA_VERSION="$EXTRA_VERSION_save"
    elif ! (( $RELEASE )) ; then
        # if there is no patch series, then this is not a lustre specific
        # kernel.  don't make it look like one
        if [ -n "$SERIES" ]; then
            #remove the @VERSION@ (lustre version)
            EXTRA_VERSION=$(echo $EXTRA_VERSION | sed -e "s/\(.*_lustre\)\..*/\1/")
            EXTRA_VERSION="${EXTRA_VERSION}-${TAG}.${TIMESTAMP}"
        fi
    fi
    # EXTRA_VERSION=${EXTRA_VERSION//-/_}

    ALL_ARCHS="$BASE_ARCHS $BIGMEM_ARCHS $BOOT_ARCHS $JENSEN_ARCHS $SMP_ARCHS $BIGSMP_ARCHS $PSERIES64_ARCHS $UP_ARCHS"

    BUILD_ARCHS=
    for arch in $(uniqify "$ALL_ARCHS") ; do
        if [ -z "$TARGET_ARCHS" ] || echo "$TARGET_ARCHS" | grep "$arch" >/dev/null 2>/dev/null ; then
            BUILD_ARCHS="$BUILD_ARCHS $arch"
        fi
    done
    [ "$BUILD_ARCHS" ] || usage 1 "No available target archs to build."
    echo "Building for: $BUILD_ARCHS"
}

tarflags()
{
    case "$1" in
        '')
            fatal 1 "tarflags(): File name argument missing."
            ;;
        *.tar.gz | *.tgz)
            echo 'zxf'
            ;;
        *.tar.bz2)
            echo 'jxf'
            ;;
        *.tar)
            echo 'xf'
            ;;
        *)
            fatal 1 "tarflags(): Unrecognized tar extension in file: $1"
            ;;
    esac
}

untar()
{
    echo "Untarring ${1##*/}..."
    tar $(tarflags "$1") "$1"
}

unpack_lustre()
{
    DIRNAME="lustre-$TAG-$TIMESTAMP"
    if [ "$LUSTRE" ] ; then
        untar "$LUSTRE"
        [ -d lustre ] || ln -sf lustre-[0-9].[0-9]* lustre
    else
        if [ "$USE_DATESTAMP" ]; then
            DATESTAMP="-D '$DATE'"
        else
            DATESTAMP=""
        fi            

        cvs -d "$CVSROOT" -qz3 co $DATESTAMP -d "$DIRNAME" lustre || \
            fatal 1 "There was an error checking out toplevel Lustre from CVS."
        pushd "$DIRNAME" > /dev/null
        ./lustrecvs "$TAG" || \
            fatal 1 "There was an error checking out Lustre/Portals/Build from CVS."
        echo "Creating lustre tarball..."
        sh autogen.sh || fatal 1 "There was an error running autogen.sh."
        ./configure --disable-{modules,utils,liblustre,tests,doc} || \
            fatal 1 "There was an error running ./configure to create makefiles."
        make dist || fatal 1 "There was an error running 'make dist'."
        popd > /dev/null
        fname=`basename $DIRNAME/lustre-*.tar.gz`
        cp $DIRNAME/$fname . || fatal 1 "There was an error copying lustre tarball."
        LUSTRE="$PWD/$fname"
        ln -sf "$DIRNAME" lustre
    fi
}

unpack_linux()
{
    untar "$KERNEL_FILE"
    [ -d linux ] || ln -sf linux* linux
}

patch_linux()
{
    [ "$SERIES" ] || return 0
    FULL_PATCH="$PWD/lustre-kernel-${TARGET}-${EXTRA_VERSION}.patch"
    [ -f "$FULL_PATCH" ] && rm -f "$FULL_PATCH"
    pushd linux >/dev/null
    for series in $SERIES ; do
        echo -n "Applying series $series:"
        for patchesdir in "$EXTERNAL_PATCHES" "$TOPDIR/lustre/lustre/kernel_patches" ; do
            [ -r "$patchesdir/series/$series" ] || continue
            SERIES_FILE="$patchesdir/series/$series"
            for patch in $(<"$SERIES_FILE") ; do
                echo -n " $patch"
                PATCH_FILE="$patchesdir/patches/$patch"
                [ -r "$PATCH_FILE" ] || \
                    fatal 1 "Patch $patch does not exist in Lustre tree."
                cat "$PATCH_FILE" >> "$FULL_PATCH" || \
                    fatal 1 "Error adding patch $patch to full patch."
                patch -s -p1 < "$PATCH_FILE" || fatal 1 "Error applying patch $patch."
            done
            break
        done
        echo
    done
    popd >/dev/null
    echo "Full patch has been saved in ${FULL_PATCH##*/}."
    echo "Replacing .config files..."
    [ -d linux/configs ] || mkdir linux/configs || \
        fatal 1 "Error creating configs directory."
    rm -f linux/configs/*
    copysuccess=0
    for patchesdir in "$EXTERNAL_PATCHES" "lustre/lustre/kernel_patches" ; do
        [ "$patchesdir" ] && \
            cp -v $patchesdir/kernel_configs/kernel-${VERSION}-${TARGET}*.config linux/configs/ >/dev/null && copysuccess=1
    done
    [ "$copysuccess" = "1" ] || \
        fatal 1 "Error copying in kernel configs."
}

pack_linux()
{
    TARBALL="$(readlink linux)-$EXTRA_VERSION.tar.gz"
    echo "Creating patched linux tarball $TARBALL..."
    tar zcf "$TARBALL" "$(readlink linux)" \
        --exclude "CVS" --exclude ".cvsignore" || \
        --exclude "*.orig" --exclude "*~" --exclude "*.rej" || \
        fatal 1 "Error creating patched Linux tarball."
}

clean_linux()
{
    [ -d linux ] || return 0
    echo "Cleaning linux..."
    [ -L linux ] && rm -rf $(readlink linux)
    rm -rf linux
}

prep_kernel_build()
{
    # make .spec file
    ENABLE_INIT_SCRIPTS=""
    sed \
        -e "s^@BASE_ARCHS@^$BASE_ARCHS^g" \
        -e "s^@BIGMEM_ARCHS@^$BIGMEM_ARCHS^g" \
        -e "s^@BIGSMP_ARCHS@^$BIGSMP_ARCHS^g" \
        -e "s^@BOOT_ARCHS@^$BOOT_ARCHS^g" \
        -e "s^@CONFIGURE_FLAGS@^$CONFIGURE_FLAGS^g" \
        -e "s^@ENABLE_INIT_SCRIPTS@^$ENABLE_INIT_SCRIPTS^g" \
        -e "s^@JENSEN_ARCHS@^$BOOT_ARCHS^g" \
        -e "s^@KERNEL_EXTRA_VERSION@^$EXTRA_VERSION^g" \
        -e "s^@KERNEL_RELEASE@^${EXTRA_VERSION//-/_}^g" \
        -e "s^@KERNEL_SOURCE@^$KERNEL^g" \
        -e "s^@KERNEL_VERSION@^$VERSION^g" \
        -e "s^@LINUX26@^$LINUX26^g" \
        -e "s^@LUSTRE_SOURCE@^${LUSTRE##*/}^g" \
        -e "s^@LUSTRE_TARGET@^$TARGET^g" \
        -e "s^@PSERIES64_ARCHS@^$PSERIES64_ARCHS^g" \
        -e "s^@RHBUILD@^$RHBUILD^g" \
        -e "s^@SMP_ARCHS@^$SMP_ARCHS^g" \
        -e "s^@SUSEBUILD@^$SUSEBUILD^g" \
        -e "s^@SUSEBUILD@^$SUSEBUILD^g" \
        -e "s^@UP_ARCHS@^$UP_ARCHS^g" \
        < $TOPDIR/lustre/build/lustre-kernel-2.4.spec.in \
        > lustre-kernel-2.4.spec
    [ -d SRPMS ] || mkdir SRPMS
    [ -d RPMS ] || mkdir RPMS
    [ -d BUILD ] || mkdir BUILD
    [ -d SOURCES ] || mkdir SOURCES
    for script in linux-{rhconfig.h,merge-config.awk,merge-modules.awk} \
        suse-{functions.sh,post.sh,postun.sh,trigger-script.sh.in} \
        sles8-{pre,post,postun,update_{INITRD_MODULES,rcfile_setting}}.sh ; do
        cp $TOPDIR/lustre/build/$script SOURCES
    done
    cp "$LUSTRE" "$KERNEL_FILE" SOURCES
    if [ "$EXTERNAL_PATCHES" -a -d "$EXTERNAL_PATCHES" ] ; then
        tar zcf SOURCES/external-patches.tar.gz -C "$EXTERNAL_PATCHES" series targets patches kernel_configs
    else
        touch SOURCES/external-patches.tar.gz
    fi
}

clean_lustre()
{
    [ -d lustre ] || return 0
    echo "Cleaning Lustre..."
    [ -L lustre ] && rm -rf $(readlink lustre)
    rm -rf lustre
}

build_kernel()
{
    echo "Building kernel + Lustre RPMs for: $BUILD_ARCHS..."
    targets=
    for arch in $BUILD_ARCHS ; do
        targets="--target $arch $targets"
    done

    $RPMBUILD $targets -bb lustre-kernel-2.4.spec \
        --define "_tmppath $TMPDIR" \
        --define "_topdir $TOPDIR" || \
        fatal 1 "Error building rpms for $BUILD_ARCHS."

    if (( $DO_SRC )) ; then
        $RPMBUILD -bs lustre-kernel-2.4.spec \
            --define "_tmppath $TMPDIR" \
            --define "_topdir $TOPDIR" || \
            fatal 1 "Error building .src.rpm."
    fi
}

build_lustre()
{
    [ -d SRPMS ] || mkdir SRPMS
    [ -d RPMS ] || mkdir RPMS
    [ -d BUILD ] || mkdir BUILD
    [ -d SOURCES ] || mkdir SOURCES

    cp "$LUSTRE" SOURCES

    pushd lustre >/dev/null

    echo "Building Lustre RPMs for: $BUILD_ARCHS..."
    targets=
    for arch in $BUILD_ARCHS ; do
        targets="--target $arch $targets"
    done

    ./configure "--with-linux=${LINUX}" ${CONFIGURE_FLAGS}

    $RPMBUILD $targets -bb build/lustre.spec \
        --define "_tmppath $TMPDIR" \
        --define "_topdir $TOPDIR" || \
        fatal 1 "Error building rpms for $BUILD_ARCHS."

    popd >/dev/null
}

stage()
{
    [ "$STAGEDIR" ] || return 0

    for arch in $BUILD_ARCHS ; do
        rpmdir="${STAGEDIR}/${CANONICAL_TARGET}-${arch}"
        echo "${0##*/}: Copying RPMs into ${rpmdir}"
        mkdir -p "${rpmdir}"
        cp -v RPMS/${arch}/*.rpm "${rpmdir}"
        if [ -d RPMS/noarch ] ; then
            cp -v RPMS/noarch/*.rpm "${rpmdir}"
        fi
    done

    cp -v "$LUSTRE" "$STAGEDIR"
}

[ -r ~/.lbuildrc ] && . ~/.lbuildrc

options=$(getopt -o d:D:h -l disable-datestamp,external-patches:,extraversion:,kerneldir:,linux:,lustre:,nodownload,nosrc,publish,release,src,stage:,tag:,target:,target-archs:,with-linux: -- "$@")

if [ $? != 0 ] ; then
    usage 1
fi

eval set -- "$options"
    
while [ "$1" ] ; do
    case "$1" in
        '')
            usage 1
            ;;
        -d)
            CVSROOT=$2
            shift 2
            ;;
        -D)
            DATE=$2
            shift 2
            ;;
        --external-patches)
            EXTERNAL_PATCHES=$2
            shift 2
            ;;
        --extraversion)
            EXTRA_VERSION=$2
            shift 2
            ;;
        --help | -h)
            usage 0
            ;;
        --kerneldir)
            KERNELDIR=$2
            shift 2
            ;;
        --linux | --with-linux)
            LINUX=$2
            shift 2
            ;;
        --lustre)
            LUSTRE=$2
            shift 2
            ;;
        --nodownload)
            DOWNLOAD=0
            shift 1
            ;;
        --nosrc)
            DO_SRC=0
            shift 1
            ;;
        --publish)
            shift
            ;;
        --release)
            RELEASE=1
            shift
            ;;
        --src)
            DO_SRC=1
            shift 1
            ;;
        --stage)
            STAGEDIR=$2
            shift 2
            ;;
        --tag)
            TAG=$2
            shift 2
            ;;
        --target)
            TARGET=$2
            shift 2
            ;;
        --target-archs)
            TARGET_ARCHS=$2
            shift 2
            ;;
        --disable-datestamp)
            USE_DATESTAMP=
            shift
            ;;
        --)
            shift
            CONFIGURE_FLAGS=$@
            break
            ;; 
        *)
            usage 1 "Unrecognized option: $1"
            ;;
    esac
done

check_options

unpack_lustre

# prep_build needs the .spec.in from the lustre source
if [ -z "$LINUX" ] ; then
    load_target
    if (( $DO_SRC )) ; then
        unpack_linux
        patch_linux
        pack_linux
        clean_linux
    fi

    prep_kernel_build
    clean_lustre

    build_kernel
else
    build_lustre
fi

stage
