#!/bin/bash

# Version: $Id: bpkg 15 2005-05-29 13:18:56Z athomas $

#
# bpkg is a cross-platform automatic packaging utility. It is similar to
# CheckInstall (in fact it uses InstallWatch from CheckInstall) but goes
# further by attempting to automate the entire extract, configure, build and
# install process.
#
# Copyright (C) 2005  Alec Thomas
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
#

# Default autoconf paths
PREFIX=/usr
SYSCONFDIR=/etc
LOCALSTATEDIR=/var/state
DATADIR=/usr/share
# Where to store built packages
PKGDIR=/usr/src/build/packages
# Skip configure phase by default?
SKIPCONFIGURE=0
# Program used to download source
DOWNLOADER='wget -nv -c'
# Attempt to automatically download and extract source files given on the
# command line?
AUTOEXTRACT=0
# Create backups of each file about to be overwritten during installation
BACKUP=1
# Do not track files installed to these locations
IGNOREPATHS="/tmp /dev /root /usr/src"

#
# XXXXXXXXXXXXXX Not really user-modifiable below here XXXXXXXXXXXXX
#
VERSION="0.1"
PACKAGER=auto
SELF=`basename $0`
SRCDIR=$PWD
MAKEFILE=Makefile
PACKAGE=`basename "$PWD" | rev | cut -d- -f2- | rev`
PACKAGEVER=`basename "$PWD" | rev | cut -d- -f1 | rev`
PACKAGEREL=1
INSTALLLOG="/tmp/$SELF.$PACKAGE.$$.log"
TMPDIR=/tmp/$SELF.$PACKAGE.$$
DESTROOT=/tmp/$SELF.$PACKAGE.$$.pkg
TMPFILES="$TMPDIR $DESTROOT $INSTALLLOG"

# Make sure some default directories exist
mkdir -p $TMPDIR $PKGDIR || exit 1

colour()
{
	if tty > /dev/null 2>&1; then
		case $1 in
			black) echo -e "\[e30m" ;;
			red) echo -e "\e[31m" ;;
			green) echo -e "\e[32m" ;;
			brown) echo -e "\e[33m" ;;
			blue) echo -e "\e[34m" ;;
			magenta) echo -e "\e[35m" ;;
			cyan) echo -e "\e[36m" ;;
			white) echo -e "\e[37m" ;;
			bold) echo -e "\e[1m" ;;
			underline) echo -e "\e[4m" ;;
			reverse) echo -e "\e[7m" ;;
			*) return 1 ;;
		esac
	else
		case $1 in
			black|red|green|brown|blue|magenta|cyan|white|bold|underline|reverse) return 0 ;;
			*) return 1 ;;
		esac
	fi
	return 0
}

print() {
	while colour $1 > /dev/null 2>&1; do
		echo -n `colour $1`
		shift
	done

	echo
	echo "    buildpkg -- $*"
	echo -e "\e[0m"
}

notice() {
	print bold green "notice -- $*"
}

warning() {
	print bold brown "warning -- $*"
}

error() {
	echo
	print bold red "error -- $*"
	exit 1
}

# usage: installer <command> [<args>]
#   eg. installer make install
installer()
{
	umask 022
	which installwatch > /dev/null 2>&1 || error "installwatch is required by $SELF"
	unset INSTALLWATCH_BACKUP_PATH
	if [ $BACKUP = 1 ]; then
		export INSTALLWATCH_BACKUP_PATH=$SRCDIR/backup-`date +%Y%m%d%H%M%S-backup`
	fi
	installwatch -o $INSTALLLOG "$@" || exit $?
	if [ $BACKUP = 1 -a -d $INSTALLWATCH_BACKUP_PATH ]; then
		notice "Archiving backup to $INSTALLWATCH_BACKUP_PATH.tar.gz"
		(cd $INSTALLWATCH_BACKUP_PATH && tar cfz $INSTALLWATCH_BACKUP_PATH.tar.gz . 2> /dev/null) || error "backup archiving failed"
		rm -rf $INSTALLWATCH_BACKUP_PATH
	fi
	unset INSTALLWATCH_BACKUP_PATH
	# Move filtered file list into $DESTROOT
	cat $INSTALLLOG | awk '{print $3 "\n" $4}' | egrep -v "^(${IGNOREPATHS// /|}|$SRCDIR)" | sort | uniq | while read FILE; do
		if [ -e "$FILE" -a ! -d "$FILE" ]; then
			mkdir -p "$DESTROOT/`dirname $FILE`"
			cp -a "$FILE" "$DESTROOT/`dirname $FILE`"
		fi
	done
}

packager_slackware()
{
	installer make install
	cd $DESTROOT
	local out=$PKGDIR/$PACKAGE-$PACKAGEVER-i386-$PACKAGEREL.tgz
	makepkg -c n -l y $out
	notice "Package is" $out
}

packager_rpm()
{
	error "RPM packager not complete"
}

packager_deb()
{
	error "DEB packager not complete"
}

packager_arch()
{
	installer make install
	local configs=`(cd $DESTROOT && find etc -type f) 2> /dev/null`
	local deps=`(cd $DESTROOT && find . -type f | xargs --no-run-if-empty file | grep 'ELF.*executable' | cut -d: -f1 | xargs --no-run-if-empty ldd 2> /dev/null | awk '{print $3}' | grep ^/ | xargs --no-run-if-empty pacman -Qo 2> /dev/null | awk '{print $5}' | sort | uniq) 2> /dev/null`
	notice "detected configuration files: $configs"
	notice "detected dependencies: $deps"
	cat <<-EOF > $TMPDIR/PKGBUILD
	pkgname=$PACKAGE
	pkgver=$PACKAGEVER
	pkgrel=$PACKAGEREL
	pkgdesc="Automatically generated by $SELF"
	url="http://swapoff.org/$SELF"
	depends=($deps)
	backup=($configs)
	source=()
	md5sums=()

	build() {
		cp -a $DESTROOT/* \$startdir/pkg/
	}
	EOF
	cd $TMPDIR
	makepkg || error "makepkg failed"
	cp $TMPDIR/*.pkg.tar.gz $PKGDIR
	notice "Package is " $PKGDIR/$PACKAGE-$PACKAGEVER*.pkg.tar.gz
}

packager_gentoo()
{
	error "Gentoo packager not complete"
}

packager_redhat() {
	packager_rpm "$@"
}

packager_suse() {
	packager_rpm "$@"
}

packager_auto()
{
	if [ -r /etc/slackware-version ]; then
		packager_slackware "$@"
	elif [ -r /etc/redhat-release ]; then
		packager_redhat "$@"
	elif [ -r /etc/SuSE-release ]; then
		packager_suse "$@"
	elif [ -r /etc/arch-release ]; then
		packager_arch "$@"
	elif [ -r /etc/gentoo-release ]; then
		packager_gentoo "$@"
	else
		error "could not determine packaging system to use"
	fi
}

trap "for f in $TMPFILES; do rm -rf \$f; done" EXIT KILL HUP QUIT

quitloop=0
while [ $quitloop = 0 -a $# != 0 ]; do
	if [[ -f "$1" || "$1" = ftp://* || "$1" = http://* ]]; then
		AUTOEXTRACT="$1"
		if [[ "$AUTOEXTRACT" == *bz2 || "$AUTOEXTRACT" == *tbz ]]; then
			FLAGS=j
		elif [[ "$AUTOEXTRACT" == *gz ]]; then
			FLAGS=z
		elif [[ "$AUTOEXTRACT" == *Z ]]; then
			FLAGS=z
		else
			error "unknown archive format for '$AUTOEXTRACT'"
		fi
		# Download package?
		if [[ "$AUTOEXTRACT" = ftp://* || "$AUTOEXTRACT" = http://* ]]; then
			notice "Downloading $AUTOEXTRACT"
			$DOWNLOADER "$AUTOEXTRACT" || error "downloader failed"
			AUTOEXTRACT=`basename "$AUTOEXTRACT"`
		fi
		notice "uncompressing package '$AUTOEXTRACT'"
		tar xf$FLAGS "$AUTOEXTRACT" || error "failed to auto-extract '$AUTOEXTRACT'"
		BUILDDIR=`tar tf$FLAGS "$AUTOEXTRACT" | cut -d/ -f1 | head -1`
		notice "package uncompressed, moving into build directory '$BUILDDIR'"
		cd "$BUILDDIR"
		SRCDIR=$PWD
		PACKAGE=`basename "$PWD" | rev | cut -d- -f2- | rev`
		PACKAGEVER=`basename "$PWD" | rev | cut -d- -f1 | rev`
	else
		case $1 in
			--version)
				echo $VERSION
				exit 0
			;;
			--help)
			cat <<EOF
bpkg [<filename>] [<bpkg-options>] [<configure-options>]

<filename> will be automatically extracted before packaging continues. If not
given, packaging is performed from the current directory.

Any options not recognised by bpkg will be passed on to ./configure if the
package is autoconf based.

Available <bpkg-options> are:
  --version
    Show $SELF version.
  --skip-configure
    Do not perform the configure phase of bpkg. Useful for already extracted
    source.
  --packager=arch|slackware|rpm|deb|gentoo|redhat|suse|auto
    Generate packages using the specified packaging system. 'auto' is the
    default.
  --package=<name>-<version>
    Override package name and version auto-detection. Sometimes necessary if
    the extracted directory name is not useful.
  --unique
    Pass options to autoconf to install into configuration and data directories
    unique to this package. (ie. --sysconfdir=/etc/<package>,
    --localstatedir=/var/state/<package> and --datadir=/usr/share/<package>).
    The base directories are used if this option is not given.

For further information, including examples, visit http://swapoff.org/bpkg
EOF
				exit 0
			;;
			--skip-configure)
				SKIPCONFIGURE=1
			;;
			--prefix=*)
				PREFIX=`echo $1 | cut -d= -f2-`
			;;
			--sysconfdir=*)
				SYSCONFDIR=`echo $1 | cut -d= -f2-`
			;;
			--localstatedir=*)
				LOCALSTATEDIR=`echo $1 | cut -d= -f2-`
			;;
			--datadir=*)
				DATADIR=`echo $1 | cut -d= -f2-`
			;;
			--unique)
				PREFIX=/usr
				SYSCONFDIR=/etc/$PACKAGE
				LOCALSTATEDIR=/var/state/$PACKAGE
				DATADIR=/usr/share/$PACKAGE
			;;
			--package=*)
				PACKAGE=`echo "$1" | cut -d= -f2- | rev | cut -d- -f2- | rev`
				PACKAGEVER=`echo "$1" | cut -d= -f2- | rev | cut -d- -f1 | rev`
			;;
			--packager=*)
				PACKAGER=`echo $1 | cut -d= -f2-`
			;;
			*)
				quitloop=1
				break
			;;
		esac
	fi
	shift
done

if [ $SKIPCONFIGURE = 0 ]; then
	if [ -x ./configure ]; then
		if ! grep -i autoconf ./configure > /dev/null; then
			notice "./configure does not seem to be generated by autoconf, running anyway"
		fi
		notice "running './configure --prefix=$PREFIX --sysconfdir=$SYSCONFDIR --localstatedir=$LOCALSTATEDIR --datadir=$DATADIR $@'"
		./configure --prefix=$PREFIX --sysconfdir=$SYSCONFDIR --localstatedir=$LOCALSTATEDIR --datadir=$DATADIR "$@" || error "configure failed"
	elif [ -x autogen.sh ]; then
		notice "no ./configure, but we have ./autogen.sh - running it"
		./autogen.sh --prefix=$PREFIX --sysconfdir=$SYSCONFDIR --localstatedir=$LOCALSTATEDIR --datadir=$DATADIR "$@" || error "./autogen.sh failed"
		if test ! -r Makefile; then
			notice "./autogen.sh did not run configure, running"
			./configure --prefix=$PREFIX --sysconfdir=$SYSCONFDIR --localstatedir=$LOCALSTATEDIR --datadir=$DATADIR "$@" || error "configure failed"
		fi
	elif [ -r Imakefile ]; then
		notice "running xmkmf on Imakefile"
		xmkmf || error "xmkmf failed"
	elif [ -r GNUmakefile -o -r makefile -o -r Makefile ]; then
		notice "source doesn't appear to use autoconf or Imake, trying standard make"
		test -r $MAKEFILE || MF=makefile
		test -r $MAKEFILE || MF=GNUmakefile
		test -r $MAKEFILE || error "can't determine makefile (tried Makefile, makefile and GNUmakefile)"
		# Change /usr/local to /usr
		if grep /usr/local $MAKEFILE > /dev/null; then
			notice "changing all instances of /usr/local in $MAKEFILE to /usr"
			sed -i 's,/usr/local,/usr,g' $MAKEFILE
		fi
	elif [ -r setup.py ]; then
		which python > /dev/null 2>&1 || error "found setup.py, but didn't find python"
		notice "generating stub Makefile for python"
		cat <<EOF > Makefile
DESTDIR=/

build:
	python setup.py build

install:
	python setup.py install --root=\$(DESTDIR)
EOF
	elif [ -r Makefile.PL ]; then
		perl Makefile.PL
	elif [ -r $PACKAGE.pro ]; then
		qmake $PACKAGE.pro || error "qmake failed"
	else
		error "couldn't auto-detect build mechanism"
	fi
fi

make || error "'make failed'"

declare -F | grep " packager_$PACKAGER\$" > /dev/null || error "no such packager $PACKAGER"

eval packager_$PACKAGER

if [ "$AUTOEXTRACT" != 0 ]; then
	notice "extracted source left in $SRCDIR"
fi
