#!/bin/bash

#############################################################################
# Copyright (c) 2003-2009 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#############################################################################

# generate a kernel-source rpm package

. ${0%/*}/wd-functions.sh

sort()
{
	LC_ALL=C command sort "$@"
}

rpm_release_timestamp=
rpm_release_string=
source_timestamp=
tolerate_unknown_new_config_options=0
ignore_kabi=
ignore_unsupported_deps=
source rpm/config.sh
until [ "$#" = "0" ] ; do
  case "$1" in
    --dir=*)
      build_dir=${1#*=}
      shift
      ;;
    -d|--dir)
      build_dir=$2
      shift 2
      ;;
    --embargo)
      embargo_filter=1
      shift
      ;;
    -nf|--tolerate-unknown-new-config-options)
      tolerate_unknown_new_config_options=1
      shift
      ;;
    -i|--ignore-kabi)
      ignore_kabi=1
      shift
      ;;
    -iu|--ignore-unsupported-deps)
      ignore_unsupported_deps=1
      shift
      ;;
    -rs|--release-string)
      case "$2" in
      *[' '-]*)
        echo "$1 option argument must not contain dashes or spaces" >&2
	exit 1
	;;
      esac
      rpm_release_string="$2"
      shift 2
      ;;
    -ts|--timestamp)
      rpm_release_timestamp=yes
      shift
      ;;
    -k|--kbuild|--source-timestamp)
      source_timestamp=1
      shift
      ;;
    -h|--help|-v|--version)
	cat <<EOF

${0##*/} perpares a 'kernel-source' package for submission into autobuild

these options are recognized:
    -rs <string>       to append specified string to rpm release number
    -ts                to use the current date as rpm release number
    -nf                to proceed if a new unknown .config option is found during make oldconfig
    -i                 ignore kabi failures
    --source-timestamp to autogenerate a release number based on branch and timestamp (overrides -rs/-ts)
    -d, --dir=DIR      create package in DIR instead of default kernel-source$VARIANT

EOF
	exit 1
	;;
    *)
      echo "unknown option '$1'" >&2
      exit 1
      ;;
  esac
done
export LANG=POSIX
if test -e scripts/compute-PATCHVERSION.sh; then
    PATCHVERSION=$($(dirname $0)/compute-PATCHVERSION.sh)

    case "$PATCHVERSION" in
    *-*)
        RPMVERSION=${PATCHVERSION%%-*}
        RELEASE_PREFIX=${PATCHVERSION#*-}.
        RELEASE_PREFIX=${RELEASE_PREFIX//-/.}
        ;;
    *)
        RPMVERSION=$PATCHVERSION
        RELEASE_PREFIX=
        ;;
    esac
fi

if [ -n "$rpm_release_timestamp" ]; then
    if test $(( ${#RPMVERSION} + 10 + 2 + 8 + ${#rpm_release_string})) -gt 64
    then
    	echo "${RPMVERSION}-${rpm_release_string}-\${flavour} exceeds the 64 byte 'uname -r' limit. Use a shorter string."
	exit 1
    fi
    rpm_release_string="\`env -i - TZ=GMT date +%Y%m%d\`${rpm_release_string:+_$rpm_release_string}"
fi

case "$build_dir" in
"")
	build_dir=kernel-source$VARIANT
	;;
/* | ./*) ;;
*)
	build_dir=./$build_dir
esac

check_for_merge_conflicts() {
    set -- $(grep -lP '^<{7}(?!<)|^>{7}(?!>)' "$@" 2> /dev/null)
    if [ $# -gt 0 ]; then
	printf "Merge conflicts in %s\n" "$@" >&2
	return 1
    fi
}

# Dot files are skipped by intention, in order not to break osc working
# copies. The linux tarball is not deleted if it is already there
for f in "$build_dir"/*; do
	case "$f" in
	"$build_dir/linux-$SRCVERSION.tar.bz2")
		continue
		;;
	"$build_dir/linux-$SRCVERSION.tar.sign")
		continue
		;;
	"$build_dir"/patches.*)
		rm -rf "$f"
	esac
	rm -f "$f"
done
mkdir -p "$build_dir"
echo "linux-$SRCVERSION.tar.bz2"
get_tarball "$SRCVERSION" "tar.bz2" "$build_dir"

# list of patches to include.
install -m 644 series.conf $build_dir/

# All config files and patches used
referenced_files="$( {
	$(dirname $0)/guards --list < $build_dir/series.conf
	$(dirname $0)/guards --prefix=config --list < config.conf
    } | sort -u )"

for file in $referenced_files; do
	case $file in
	config/* | patches.*/*)
		;;
	*)
		echo "Error: Patches must be placed in the patches.*/ subdirectories: $file" >&2
		exit 1
	esac
done
inconsistent=false
check_for_merge_conflicts $referenced_files kernel-source.changes{,.old} || \
	inconsistent=true
scripts/check-conf || inconsistent=true
scripts/check-cvs-add --committed || inconsistent=true
# FIXME: someone should clean up the mess and make this check fatal
if $inconsistent; then
    echo "Inconsistencies found."
    echo "Please clean up series.conf and/or the patches directories!"
    echo
fi

tsfile=build-source-timestamp
if ! scripts/cvs-wd-timestamp > $build_dir/$tsfile; then
    exit 1
fi

if $using_git; then
    # Always include the git revision
    echo "GIT Revision: $(git rev-parse HEAD)" >> $build_dir/$tsfile
    tag=$(get_branch_name)
    if test -n "$tag"; then
	echo "GIT Branch: $tag" >>$build_dir/$tsfile
    fi
fi


# List all used configurations
config_files="$(
    for arch in $(scripts/arch-symbols --list) ; do
	scripts/guards $(scripts/arch-symbols $arch) < config.conf \
	| sed -e "s,^,$arch ,"
    done)"
flavors="$(echo "$config_files" | sed -e 's,.*/,,' | sort -u)"

for flavor in $flavors ; do
    echo "kernel-$flavor.spec"

    extra_needs=
    case $flavor in
	um)
	    extra_needs="BuildRequires:  libpcap xorg-x11-devel" ;;
    esac

    # Find all architectures for this spec file
    set -- $(
	echo "$config_files" \
	| sed -e "/\/$flavor\$/!d" \
	      -e "s, .*,,g" \
	| sort -u)
    archs="$*"
    
    # Compute @PROVIDES_OBSOLETES@ expansion
    head="" ; tail=""
    for arch in $archs ; do
	p=( $(scripts/guards $(scripts/arch-symbols $arch) $flavor p \
		< rpm/old-packages.conf) )
	o=( $(scripts/guards $(scripts/arch-symbols $arch) $flavor o \
		< rpm/old-packages.conf) )

	# Do we have an override config file or an additional patch?
	if [ -e $arch-$flavor.conf ]; then
	    echo "Override config: $arch-$flavor.conf"
	    cp $arch-$flavor.conf $build_dir/
	fi
	if [ -e $arch-$flavor.diff ]; then
	    echo "Extra patch: $arch-$flavor.diff"
	    cp $arch-$flavor.diff $build_dir/
	fi

	[ $arch = i386 ] && arch="%ix86"
	nl=$'\n'
	if [ ${#p[@]} -o ${#p[@]} ]; then
	    [ -n "$head" ] && head="${head}%else$nl"
	    head="${head}%ifarch $arch$nl"
	    [ -n "$p" ] && head="${head}Provides:     ${p[@]}$nl"
	    [ -n "$o" ] && head="${head}Obsoletes:    ${o[@]}$nl"
	    tail="%endif$nl$tail"
	fi
    done
    prov_obs="$head${tail%$'\n'}"

    # If we build this spec file for only one architecture,  the
    # enclosing if is not needed
    if [ $(set -- $archs ; echo $#) -eq 1 ]; then
	prov_obs="$(echo "$prov_obs" | grep -v '%ifarch\|%endif')"
    fi

    # Special km modules for this kernel (SLES9)
    if test -e rpm/km.conf; then
        set -- $(scripts/guards $(
                    for arch in $archs; do
                        scripts/arch-symbols $arch
                    done
                    ) $flavor < rpm/km.conf)
        extra_kms=$*
    else
        extra_kms=
    fi

    # In ExclusiveArch in the spec file, we must specify %ix86 instead
    # of i386.
    archs="$(echo $archs | sed -e 's,i386,%ix86,g')"

    # Summary and description
    if test -e rpm/package-descriptions; then
	description=$(sed "1,/^=== kernel-$flavor ===/d; /^===/,\$ d" rpm/package-descriptions)
	if test -z "$description"; then
	    echo "warning: no description for kernel-$flavor found" >&2
	    summary="The Linux Kernel"
	    description="The Linux Kernel."
	else
	    summary=$(echo "$description"  | head -n 1)
	    # escape newlines for the sed 's' command
	    description=$(echo "$description" | tail -n +3 | \
		sed 's/$/\\/; $ s/\\$//')
	fi
    else
	summary="The Linux Kernel"
	description="The Linux Kernel."
    fi

    # Generate spec file
    sed -r -e "s,@NAME@,kernel-$flavor,g" \
	-e "s,@SUMMARY@,$summary,g" \
	-e "s~@DESCRIPTION@~$description~g" \
	-e "s,@(FLAVOR|CFGNAME)@,$flavor,g" \
	-e "s,@VARIANT@,$VARIANT,g" \
	-e "s,@(SRC)?VERSION@,$SRCVERSION,g" \
	-e "s,@PATCHVERSION@,$PATCHVERSION,g" \
	-e "s,@RPMVERSION@,$RPMVERSION,g" \
	-e "s,@PRECONF@,1,g" \
	-e "s,@NO_DEBUG@,,g" \
	-e "s,@KERNEL_MODULE_PACKAGES@,kernel-module-packages,g" \
	-e "s,@EXTRA_KMS@,$extra_kms,g" \
	-e "s,@ARCHS@,$archs,g" \
	-e "s,@PROVIDES_OBSOLETES@,${prov_obs//$'\n'/\\n},g" \
	-e "s,@EXTRA_NEEDS@,$extra_needs,g" \
	-e "s,@TOLERATE_UNKNOWN_NEW_CONFIG_OPTIONS@,$tolerate_unknown_new_config_options,g" \
	-e "s,@RELEASE_PREFIX@,$RELEASE_PREFIX,g" \
      < rpm/kernel-binary.spec.in \
    > $build_dir/kernel-$flavor.spec
done

install_changes() {
    local changes=$1
    cat kernel-source.changes > "$changes"
    if test -e kernel-source.changes.old; then
        cat "$_" >>"$changes"
    fi
    chmod 644 $changes
}

for flavor in $flavors ; do
    install_changes $build_dir/kernel-$flavor.changes
done

binary_spec_files=$(
    n=50
    for flavor in syms $flavors ; do
	printf "%-14s%s\n" "Source$n:" "kernel-$flavor.spec"
	n=$[$n+1]
    done
)
binary_spec_files=${binary_spec_files//$'\n'/\\n}
binary_spec_files_list=$(
    for flavor in syms $flavors; do
        echo -n "kernel-$flavor.spec "
    done
)

CLEANFILES=()
trap 'if test -n "$CLEANFILES"; then rm -rf "${CLEANFILES[@]}"; fi' EXIT
tmpdir=$(mktemp -dt ${0##*/}.XXXXXX)
CLEANFILES=("${CLEANFILES[@]}" "$tmpdir")

EXTRA_SYMBOLS=$([ -e extra-symbols ] && cat extra-symbols)

# Compute @BUILD_REQUIRES@ expansion
prepare_source_and_syms() {
    local name=$1
    local head="" tail="" nl ARCH_SYMBOLS packages flavor av arch build_req

    archs=
    build_requires=

    for arch in $(scripts/arch-symbols --list); do
	ARCH_SYMBOLS=$(scripts/arch-symbols $arch)

	# Exclude flavors that have a different set of patches: we assume that
	# the user won't change series.conf so much that two flavors that differ
	# at tar-up.sh time will become identical later.

	set -- $ARCH_SYMBOLS $EXTRA_SYMBOLS
	case $name in
	(*-rt)
	    set -- RT "$@"
	    ;;
	esac
	scripts/guards "$@" < series.conf > $tmpdir/$name.patches

	packages=
	for arch_flavor in $(scripts/guards $ARCH_SYMBOLS $EXTRA_SYMBOLS syms \
				< config.conf); do
	    flavor=${arch_flavor#*/}
	    av=${arch_flavor//\//_}
	    set -- kernel-$flavor $flavor \
		   $(case $flavor in (rt|rt_*) echo RT ;; esac)

	    # The patch selection for kernel-vanilla is a hack.
	    [ $flavor = vanilla ] && continue

	    scripts/guards $* $ARCH_SYMBOLS $EXTRA_SYMBOLS < series.conf \
		> $tmpdir/kernel-$av.patches
	    diff -q $tmpdir/{$name,kernel-$av}.patches > /dev/null \
		|| continue
	    packages="$packages kernel-$flavor"
	done

	set -- $packages
	if [ $# -gt 0 ]; then
	    [ $arch = i386 ] && arch="%ix86"
	    nl=$'\n'
	    [ -n "$head" ] && head="${head}%else$nl"
	    head="${head}%ifarch $arch$nl"
	    head="${head}BuildRequires: $*$nl"
	    tail="%endif$nl$tail"
	    archs="$archs $arch"
	fi
    done
    build_requires="$head${tail%$'\n'}"
    build_requires="${build_requires//$'\n'/\\n}"
    archs=${archs# }
}

echo "kernel-source.spec"
prepare_source_and_syms kernel-syms # compute archs and build_requires
sed -r -e "s,@NAME@,kernel-source,g" \
    -e "s,@(SRC)?VERSION@,$SRCVERSION,g" \
    -e "s,@PATCHVERSION@,$PATCHVERSION,g" \
    -e "s,@RPMVERSION@,$RPMVERSION,g" \
    -e "s,@PRECONF@,1,g" \
    -e "s,@ARCHS@,$archs,g" \
    -e "s,@BINARY_SPEC_FILES@,$binary_spec_files,g" \
    -e "s,@BINARY_SPEC_FILES_LIST@,$binary_spec_files_list,g" \
    -e "s,@TOLERATE_UNKNOWN_NEW_CONFIG_OPTIONS@,$tolerate_unknown_new_config_options," \
    -e "s,@RELEASE_PREFIX@,$RELEASE_PREFIX,g" \
  < rpm/kernel-source.spec.in \
> $build_dir/kernel-source.spec
install_changes $build_dir/kernel-source.changes

echo "kernel-syms.spec"
sed -r -e "s,@NAME@,kernel-syms,g" \
    -e "s,@VARIANT@,,g" \
    -e "s,@(SRC)?VERSION@,$SRCVERSION,g" \
    -e "s,@PATCHVERSION@,$PATCHVERSION,g" \
    -e "s,@RPMVERSION@,$RPMVERSION,g" \
    -e "s,@PRECONF@,1,g" \
    -e "s,@ARCHS@,$archs,g" \
    -e "s,@BUILD_REQUIRES@,$build_requires,g" \
    -e "s,@RELEASE_PREFIX@,$RELEASE_PREFIX,g" \
  < rpm/kernel-syms.spec.in \
> $build_dir/kernel-syms.spec
install_changes $build_dir/kernel-syms.changes

echo "kernel-dummy.spec"
sed -r -e "s,@NAME@,kernel-dummy,g" \
    -e "s,@(SRC)?VERSION@,$SRCVERSION,g" \
    -e "s,@PATCHVERSION@,$PATCHVERSION,g" \
    -e "s,@RPMVERSION@,$RPMVERSION,g" \
    -e "s,@RELEASE_PREFIX@,$RELEASE_PREFIX,g" \
  < rpm/kernel-dummy.spec.in \
> $build_dir/kernel-dummy.spec
install_changes $build_dir/kernel-dummy.changes

echo "Copying various files..."
cp -a                       \
    config.conf             \
    supported.conf          \
    rpm/*                   \
    scripts/guards          \
    scripts/arch-symbols    \
    doc/README.SUSE         \
    $build_dir
rm -f "$build_dir"/*spec.in "$build_dir"/get_release_number.sh.in \
    "$build_dir"/old-packages.conf "$build_dir"/km.conf \
    "$build_dir"/package-descriptions
# Not all files are in all branches
for f in misc/extract-modaliases scripts/kabi-checks; do
    if test -e "$f"; then
        cp -a "$f" "$build_dir"
    fi
done
if grep -q '^Source.*:[[:space:]]*log\.sh[[:space:]]*$' rpm/kernel-source.spec.in; then
	cp -p scripts/rpm-log.sh "$build_dir"/log.sh
fi

if [ -e extra-symbols ]; then
	install -m 755					\
		extra-symbols				\
		$build_dir
fi


if [ -x /work/src/bin/tools/convert_changes_to_rpm_changelog ]; then
    /work/src/bin/tools/convert_changes_to_rpm_changelog \
	"$build_dir"/kernel-source*.changes >"$build_dir"/rpm_changelog
    for spec in "$build_dir"/*.spec; do
        (echo "%changelog"; cat "$build_dir"/rpm_changelog) >>"$spec"
    done
    rm -f "$build_dir"/rpm_changelog
fi


if [ -n "$source_timestamp" ]; then
	ts="$(head -n 1 $build_dir/$tsfile)"
	branch=$(sed -nre 's/^(CVS|GIT) Branch: //p' \
		 $build_dir/$tsfile)
	rpm_release_string=${branch:-HEAD}_$(date --utc '+%Y%m%d%H%M%S' -d "$ts")
fi

sed -e "s:@RELEASE_PREFIX@:$RELEASE_PREFIX:"		\
    -e "s:@RELEASE_SUFFIX@:$rpm_release_string:"	\
    rpm/get_release_number.sh.in			\
    > $build_dir/get_release_number.sh
chmod 755 $build_dir/get_release_number.sh

# Usage:
# stable_tar [-t <timestamp>] [-C <dir>] [--exclude=...] <tarball> <files> ...
# if -t is not given, files must be within a git repository
stable_tar() {
    local tarball mtime chdir="." tar_opts=()

    while test $# -gt 2; do
        case "$1" in
        -t)
            mtime=$2
            shift 2
            ;;
        -C)
            chdir=$2
	    tar_opts=("${tar_opts[@]}" -C "$2")
            shift 2
            ;;
        --exclude=*)
            tar_opts=("${tar_opts[@]}" "$1")
            shift
            ;;
        --exclude)
            tar_opts=("${tar_opts[@]}" "$1" "$2")
            shift 2
            ;;
        *)
            break
        esac
    done
    tarball=$1
    shift

    if test -z "$mtime" && $using_git; then
	local dirs=$(printf '%s\n' "$@" | sed 's:/.*::' | sort -u)
        mtime="$(cd "$chdir"
            echo "${dirs[@]}" | xargs git log -1 --pretty=tformat:%ct -- | sort -n | \
            tail -n 1)"
    fi
    if test -n "$mtime"; then
        tar_opts=("${tar_opts[@]}" --mtime "$mtime")
    fi
    case "$IBS_PROJECT" in
    SUSE:SLE-9*)
        tar_opts=("${tar_opts[@]}" --no-paxheaders)
    esac
    printf '%s\n' "$@" | \
	    scripts/stable-tar.pl "${tar_opts[@]}" -T - | bzip2 -9 >"$tarball"
    case "${PIPESTATUS[*]}" in
    *[1-9]*)
        exit 1
    esac
}

# create the *.tar.bz2 files in parallel: Spawn a job for each cpu
# present; wait for all of them to finish; submit a new set of jobs.
# This is not a very efficient algorithm and it can result in anomalies
# where adding a cpu slows the script down, so improvements are welcome.
slots=$(getconf _NPROCESSORS_ONLN)
if test 0$slots -lt 1; then
	slots=1
fi
used=0
wait_archives()
{
	if test $used -gt 0; then
		wait
		if grep -q '[^0]' "$tmpdir"/result-*; then
			exit 1
		fi
		used=0
		rm -f "$tmpdir"/result-*
	fi
}
do_archive()
{
	if test $slots -eq 1; then
		stable_tar "$@"
		return
	fi
	if test $used -eq $slots; then
		wait_archives
	fi
	(stable_tar "$@"; echo $? >"$tmpdir/result-$used") &
	let used++
}

# The first directory level determines the archive name
all_archives="$(
    echo "$referenced_files" \
    | sed -e 's,/.*,,' \
    | uniq )"
for archive in $all_archives; do
    echo "$archive.tar.bz2"

    files="$(echo "$referenced_files" | sed -ne "\:^${archive//./\\.}/:p")"
    if [ -n "$files" ]; then
	do_archive $build_dir/$archive.tar.bz2 $files
    fi
done

if test -d kabi; then
    echo "kabi.tar.bz2"
    do_archive $build_dir/kabi.tar.bz2 kabi
fi

for kmp in  novell-kmp hello; do
    if test ! -d "doc/$kmp"; then
        continue
    fi
    echo "$kmp.tar.bz2"
    do_archive -C doc --exclude='*.o' --exclude='*.ko' --exclude='*.*.cmd' \
        "$build_dir/$kmp.tar.bz2" "$kmp"
done
wait_archives

# Create empty dummys for any *.tar.bz2 archive mentioned in the spec file
# not already created: patches.addon is empty by intention; others currently
# may contain no patches.
archives=$(sed -ne 's,^Source[0-9]*:.*[ \t/]\([^/]*\)\.tar\.bz2$,\1,p' \
           $build_dir/*.spec | sort -u)
for archive in $archives; do
    case "$archive" in
    *%*)
        # skip archive names with macros
        continue
    esac
    if test -e "$build_dir/$archive.tar.bz2"; then
        continue
    fi
    echo "$archive.tar.bz2 (empty)"
    tmpdir2=$(mktemp -dt ${0##*/}.XXXXXX)
    CLEANFILES=("${CLEANFILES[@]}" "$tmpdir2")
    mkdir -p $tmpdir2/$archive
    stable_tar -C $tmpdir2 -t 1234567890 $build_dir/$archive.tar.bz2 $archive
done

# Force mbuild to choose build hosts with enough memory available:
echo $((1024*1024)) > $build_dir/minmem
# Force mbuild to choose build hosts with enough disk space available:
echo $((6*1024)) > $build_dir/needed_space_in_mb
if [ -n "$ignore_kabi" ]; then
    echo > $build_dir/IGNORE-KABI-BADNESS
fi
if [ -n "$ignore_unsupported_deps" ]; then
    echo > $build_dir/IGNORE-UNSUPPORTED-DEPS
fi
