#!/bin/bash

# Version: $Id: bpkg 22 2005-06-08 08:11:54Z 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=$PWD
# 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.2"
PACKAGER=auto
SELF=`basename $0`
SRCDIR=$PWD
MAKEFILE=Makefile
PACKAGE=`basename "$PWD" | rev | cut -d- -f2- | rev | tr A-Z a-z`
PACKAGEVER=`basename "$PWD" | rev | cut -d- -f1 | rev | tr A-Z a-z`
PACKAGEREL=1
INSTALLLOG="/tmp/$SELF.$PACKAGE.$$.log"
TMPDIR=/tmp/$SELF.$PACKAGE.$$
DESTROOT=/tmp/$SELF.$PACKAGE.$$.pkg
TMPFILES="$TMPDIR $DESTROOT $INSTALLLOG"
MAKEINSTALL='make install'

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 "    bpkg -- $*"
	echo -e "\e[0m"
}

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

warning() {
	print bold brown "warning -- $*" 1>&2
}

error() {
	print bold red "error -- $*" 1>&2
	exit 1
}

# usage: installer
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 $MAKEINSTALL || error "package installation failed"
	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
	mkdir -p "$DESTROOT"
	# 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
	test `find $DESTROOT -type f | wc -l` = 0 && error "No files installed. This could mean the source is already installed."
}

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

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

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

packager_pre_arch()
{
	pacman -Q "$PACKAGE" > /dev/null 2>&1 && error "package '$PACKAGE' already installed, remove it before packaging"
}

packager_arch()
{
	installer
	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
	cd $TMPDIR
	cat <<-EOF > 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
	makepkg || error "makepkg failed"
	cp $TMPDIR/*.pkg.tar.gz $PKGDIR
	pacman -fA $PKGDIR/$PACKAGE-$PACKAGEVER*.pkg.tar.gz
	notice "Package is " $PKGDIR/$PACKAGE-$PACKAGEVER*.pkg.tar.gz
}

packager_pre_gentoo()
{
	eval `grep ^PORTDIR_OVERLAY /etc/make.conf`
	if [ -z "$PORTDIR_OVERLAY" ]; then
		error "You have not configured a portage overlay directory. Refer to http://gentoo-wiki.com/HOWTO_Installing_3rd_Party_Ebuilds for more information."
	fi
}

packager_gentoo()
{
	installer
	#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`
	ls -d /usr/portage/*/$PACKAGE > /dev/null 2>&1 && error "Package '$PACKAGE' already exists in portage."
	eval `grep ^PORTDIR_OVERLAY /etc/make.conf`
	if [ ! -d "$PORTDIR_OVERLAY" ]; then
		notice "Creating portage overlay directory: $PORTDIR_OVERLAY"
	fi
	mkdir -p $PORTDIR_OVERLAY/app-misc/$PACKAGE
	cd $PORTDIR_OVERLAY/app-misc/$PACKAGE
	local ebuild=$PACKAGE-$PACKAGEVER.ebuild
	notice "Generating ebuild: $ebuild"
	cat <<-EOF > $ebuild
	inherit eutils flag-o-matic
	DESCRIPTION="$PACKAGE (automatically packaged by $SELF)"
	HOMEPAGE="Unknown, refer to http://swapoff.org/bpkg for information on packager"
	SRC_URI=""
	LICENSE="UNKNOWN"
	SLOT="0"
	DEPENDS=""
	KEYWORDS="alpha amd64 arm hppa ia64 m68k mips ppc ppc64 ppc-macos s390 sh sparc x86 x86-obsd x86-fbsd"

	src_unpack() {
		:
	}

	src_compile() {
		:
	}

	src_install() {
		test -d $DESTROOT || die "You need to install $PACKAGE with $SELF. Can not be emerged."
		cp -a $DESTROOT/* \${D}/ || die "packaging failed"
	}
	EOF
	notice "Integrating package into world"
	ebuild $ebuild digest
	emerge $PACKAGE
}

packager_redhat() {
	packager_rpm "$@"
}

packager_suse() {
	packager_rpm "$@"
}

detect_os()
{
	if [ -r /etc/slackware-version ]; then
		echo slackware
	elif [ -r /etc/redhat-release ]; then
		echo redhat
	elif [ -r /etc/SuSE-release ]; then
		echo suse
	elif [ -r /etc/arch-release ]; then
		echo arch
	elif [ -r /etc/gentoo-release ]; then
		echo gentoo
	else
		error "could not determine packaging system to use"
	fi
}

# Make sure some default directories exist
mkdir -p "$TMPDIR"
if [ ! -w $PKGDIR ]; then
	warning "$PKGDIR is not writeable or does not exist, package will be left in $PWD"
	PKGDIR=$PWD
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 | tr A-Z a-z`
		PACKAGEVER=`basename "$PWD" | rev | cut -d- -f1 | rev | tr A-Z a-z`
	else
		case $1 in
			--version)
				echo $VERSION
				exit 0
			;;
			--detect-os)
				(detect_os) 2> /dev/null || echo unknown
				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:
  --help
    This help.
  --detect-os
    Display the O/S that bpkg thinks you are running.
  --version
    Show $SELF version.
  --skip-configure
    Do not perform the configure phase of bpkg. Useful for already configured
    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. By default this is
    obtained by extracting the package name and version from the package
    source directory name.
  --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.
  --install-with=<command>
    Use the given command to install rather than the default 'make install'.

For further information, including examples, visit http://swapoff.org/bpkg
EOF
				exit 0
			;;
		  --install-with=*)
		  	MAKEINSTALL=`echo "$1" | cut -d= -f2-`
		  ;;
			--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

test $PACKAGER = auto && PACKAGER=`detect_os`

declare -F | grep " packager_pre_$PACKAGER\$" > /dev/null && eval packager_pre_$PACKAGER
declare -F | grep " packager_$PACKAGER\$" > /dev/null || error "no such function packager_$PACKAGER()"

if [ "$PACKAGE" = "$PACKAGEVER" ]; then
	error "Could not determine package version from current directory. Specify with --package=<package>-<version>"
fi

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 $PREFIX
		if grep /usr/local $MAKEFILE > /dev/null; then
			notice "changing all instances of /usr/local in $MAKEFILE to /usr"
			sed -e "s,/usr/local,$PREFIX,g" $MAKEFILE > $MAKEFILE~ && mv $MAKEFILE~ $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 -f --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'"

eval packager_$PACKAGER

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