#!/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

# if this is SLE11_BRANCH and earlier, hand over to scripts/tar-up-old.sh
if test ! -x rpm/mkspec; then
    exec ${0%.sh}-old.sh "$@"
fi

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

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

tolerate_unknown_new_config_options=
ignore_kabi=
mkspec_args=()
arch=
flavor=
source rpm/config.sh
until [ "$#" = "0" ] ; do
  case "$1" in
    --dir=*)
      build_dir=${1#*=}
      shift
      ;;
    -d|--dir)
      build_dir=$2
      shift 2
      ;;
    --embargo)
      # obsolete
      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)
      # ignored, set %supported_modules_check in the spec instead
      shift
      ;;
    -rs|--release-string)
      case "$2" in
      *' '*)
        echo "$1 option argument must not contain spaces" >&2
        exit 1
      esac
      mkspec_args=("${mkspec_args[@]}" --release "$2")
      shift 2
      ;;
    -a|--arch)
      arch=$2; shift 2 ;;
    -f|--flavor|--flavour)
      flavor=$2; shift 2 ;;
    --vanilla) flavor="vanilla"; shift ;;
    -h|--help|-v|--version)
	cat <<EOF

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

these options are recognized:
    -nf                to proceed if a new unknown .config option is found during make oldconfig
    -i                 ignore kabi failures
    -d, --dir=DIR      create package in DIR instead of default kernel-source$VARIANT
    -a, --arch=ARCH    create package for architecture ARCH only
    -f, --flavor=FLAVOR create package for FLAVOR only
    --vanilla	       like --flavor=vanilla

EOF
	exit 1
	;;
    *)
      echo "unknown option '$1'" >&2
      exit 1
      ;;
  esac
done
export LANG=POSIX

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
}

suffix=$(sed -rn 's/^Source0:.*\.(tar\.[a-z0-9]*)$/\1/p' rpm/kernel-source.spec.in)
# 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
	"linux-$SRCVERSION.$suffix")
		continue
		;;
	"linux-$SRCVERSION.tar.sign")
		continue
		;;
	patches.*)
		rm -rf "$f"
	esac
	rm -f "$f"
done
mkdir -p "$build_dir"
echo "linux-$SRCVERSION.$suffix"
get_tarball "$SRCVERSION" "$suffix" "$build_dir" "$URL"

# 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

[ "$flavor" == "vanilla" ] &&  \
    sed -i '/^$\|\s*#\|patches\.\(kernel\.org\|rpmify\)/b; s/\(.*\)/#### \1/' \
    $build_dir/series.conf

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

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

localversion=$(get_localversion $SRCVERSION)
[ -n "$localversion" ] && echo -n "$localversion" > $build_dir/localversion

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

CLEANFILES=()
trap 'if test -n "$CLEANFILES"; then rm -rf "${CLEANFILES[@]}"; fi' EXIT
tmpdir=$(mktemp -dt ${0##*/}.XXXXXX)
CLEANFILES=("${CLEANFILES[@]}" "$tmpdir")
rpmfiles=$(ls -d rpm/* | grep -v -e "~$" -e "[.]orig$" -e "[.]rej$" | { while read x ; do [ -d "$x" ] || echo "$x" ; done ; } )
rpmstatus=$(for i in $rpmfiles ; do git status -s $i ; done)
[ -z "$rpmstatus" ] || { inconsistent=true ; echo "$rpmstatus" ; }

# 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

cp -p $rpmfiles config.conf supported.conf doc/* $build_dir
match="${flavor:+\\/$flavor$}"
match="${arch:+^+\\($(echo -n "${arch}" | sed 's/[, ]\+/\\\|/g')\\)\\>${match:+.*}}${match}"
[ -n "$match" ] && sed -i "/^$\|\s*#\|${match}/b; s/\(.*\)/#### \1/" $build_dir/config.conf
if test -e misc/extract-modaliases; then
	cp misc/extract-modaliases $build_dir
fi
# install this file only if the spec file references it
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
rm -f "$build_dir/kernel-source.changes.old" "$build_dir/gitlog-fixups" "$build_dir/gitlog-excludes"
if test -e "$build_dir"/config-options.changes; then
	# Rename to  avoid triggering a build service rule error
	mv "$build_dir"/config-options.changes \
		"$build_dir"/config-options.changes.txt
fi
rm -f "$build_dir/config-subst"

changelog=$build_dir/kernel-source$VARIANT.changes
if test -e kernel-source.changes; then
    cat kernel-source.changes{,.old} >"$changelog"
elif $using_git; then
    exclude=()
    # Exclude commits in the scripts branch, these are rarely interesting for
    # users of the rpm packages.
    # FIXME: the remote might have a different name than "origin" or there
    # might be no remote at all.
    for remote in $(git remote)
    do
        if git cat-file -e ${remote}/scripts 2>/dev/null; then
            exclude[${#exclude[@]}]=^${remote}/scripts
        fi
    done
    if git cat-file -e scripts 2>/dev/null; then
        exclude[${#exclude[@]}]=^scripts
    fi
    if test ${#exclude[@]} -eq 0; then
        echo "warning: no scripts or origin/scripts branch found" >&2
        echo "warning: rpm changelog will have some useless entries" >&2
    fi
    changes_stop=$(sed 1q rpm/kernel-source.changes.old)
    case "$changes_stop" in
    last\ commit:\ *)
        exclude[${#exclude[@]}]=^${changes_stop#*: }
        ;;
    *)
        echo "expected \"last commit: <commit>\" in rpm/kernel-source.changes.old" >&2
        exit 1
    esac
    if test -e rpm/gitlog-excludes; then
	exclude=(--excludes "$_" "${exclude[@]}")
    fi
    if test -e rpm/gitlog-fixups; then
	    exclude=(--fixups "$_" "${exclude[@]}")
    fi
    scripts/gitlog2changes "${exclude[@]}" HEAD -- >"$changelog"
    sed 1d rpm/kernel-source.changes.old >>"$changelog"
    scripts/rpm-changes-merge.pl -1 "$changelog"
else
    touch "$changelog"
fi

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

# 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
    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

if test -d sysctl && \
	grep -q '^Source.*\<sysctl\.tar\.bz2' "$build_dir/kernel-source.spec.in"
then
	echo "sysctl.tar.bz2"
	do_archive $build_dir/sysctl.tar.bz2 sysctl
fi
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/kernel-source.spec.in | 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

if [ -n "$ignore_kabi" ]; then
    echo > $build_dir/IGNORE-KABI-BADNESS
fi
if [ -n "$tolerate_unknown_new_config_options" ]; then
    echo > $build_dir/TOLERATE-UNKNOWN-NEW-CONFIG-OPTIONS
fi

echo "cd $build_dir; ./mkspec ${mkspec_args[@]}"
patches=$PWD
cd "$build_dir"
./mkspec --patches "$patches" "${mkspec_args[@]}"
