#!/bin/bash
# This script provides a simple method for tracking software that you 
# have built from source. Run it as follows for more information: 
# createpkg -h
#
# Copyright 2012 Ruarí Ødegaard, Olso, Norway
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
# HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

startdir=$(pwd)
arch=${arch:-$(uname -m)}
src="$startdir/src"
pkg="$startdir/pkg"
buildnr=1
doman=y
dostrip=y
doroot=y
options=""
taropts="-T- -cvvf-"

available()
{
  command -v $1 >/dev/null 2>&1
}

if available xz; then
  compressor=xz
  compext=xz
elif available bzip2; then
  compressor=bzip2
  compext=bz2
else
  compressor=gzip
  compext=gz
fi

if available wget; then
  downloader=wget
elif available curl; then
  downloader="curl -LO"
else
  echo "Neither Wget nor cURL were found, downloading of source(s) may fail." 1>&2
  downloader=echo
fi  

helptext() {
cat << EOH 

This script provides a simple method for tracking software that you have 
built from source. It also simplifies transfering the software to other 
machines for install or backup.

Concepts have been borrowed from CRUX's pkgmk, Arch Linux's makepkg, 
paco (pacKAGE oRGANIZER) and other Linux package managers. Like 
pkgmk/makepkg it works by reading a meta file in the current working 
directory (named "BUILD") that specifies details about where to find the 
software, what it is called and how to build it. This information is 
combined with commands within the script itself, allowing for automation 
of source package download, compilation and creation of installable 
"packages". Like paco these packages include a text based log of all the 
files present (including the log itself), which is saved in /var/logs 
when the package is installed.

Unlike more comprehensive package management tools (including those from 
which it borrows ideas), this script has no major dependencies outside 
of a typical Linux build environment. This means that whilst its 
abilities are quite limited, it is very easy to setup.

To use, create a BUILD file that sets basic variables describing the 
package (e.g. name, version, etc.) and contains a "build" function that 
includes the configure and make steps, remembering to set "\$pkg" as 
your output directory. The included template ("createpkg -t") provides a 
basic outline of how this BUILD file should look. Once a BUILD file has 
been created, run createpkg again (with no options) and an installable 
package should be created (assuming no errors in your build steps).

Installation of these packages simply requires extracting their contents 
directly into the top level of the filesystem as follows:

# tar xf [packagename] -C /

It is possible to view a listing of the contents of the installed 
package at any time by reading the appropriate log file, like so:

$ less /var/log/footprints/[packagename]

Should you ever need to remove the package it can be done by issuing the 
following command:

# cat /var/log/footprints/[packagename] | xargs -d"\n" rm

If you prefer you can be prompted for individual file deletions 
(entering y to confirm) by using the following command:

# cat /var/log/footprints/[packagename] | xargs -pL1 -d"\n" rm

All files that were included in the installed package can be removed in 
this way but not directories. They are intentionally omited from the 
package log since they may have been shared with other software 
previously present on your system.

Note: Whilst you can use the find command to locate empty directories on 
your system, you need to be very careful about removal as some software 
may depend on their precence. It is much safer just to ignore them.

Script options:

  -h, --help                           Print this help text
  -t, --template                       Print a BUILD template on screen
  -T, --vtemplate                      Print verbose BUILD template
  -a, --atemplate <source package>     Attempt to print a BUILD template
                                       using the source package name or
			               URL to work out variables.
EOH
}

template() {
cat << EOT
## Set the application's name, e.g. name=example
name=
## Set the application's own version number, e.g. version=1.0
version=
## If the package will be architecture independent (e.g. software based 
## on Perl, Python, Ruby, etc.), uncomment the following line.
#arch=noarch
## This is packaging number. You can uncomment and increase it if you
## make improved build steps but the version number of the software has
## not increased. It is preset to 1 if you do not define it.
#buildnr=2
## This array is used to unset automatic packaging options, "!root"
## stops all files having root ownership, "!man" stops automatic
## compression of man pages and "!strip" stops symbols being removed
## from binaries. 
#options=(!root !man !strip)
## This is an array that lists the the source package(s). Where possible 
## you should use full URL(s). This will allow for automatic downloading 
## of source if it is not present locally. Using variables in the URL(s) 
## and package name means that you will likely only need to update the 
## version number when a new source package is released. You can also 
## remove this line if you don't want sources automatically fetched and 
## would prefer handle these steps in the build function.
source=("\${name}-\${version}.tar.gz")

## The following are typical build steps for software that builds via 
## GNU Autotools. Uncomment and adjust the examples as needed.
build() {
  ## Switch into the extracted source directory:
  #cd \${name}-\${version}
  ## Configure the software. Adding any switches to suite your own 
  ## requirements:
  #./configure
  ## Make and install the software into \$pkg.
  ## Tip: If \$DESTDIR is not supported you may be able to use --prefix= 
  ## in the previous step).
  #make
  #make install DESTDIR="\$pkg"
}
EOT
}

if [ "$1" = "-h" -o "$1" = "--help" ]; then
  helptext
  exit 0
elif [ "$1" = "-t" -o "$1" = "--template" ]; then
  template | grep -v "##"
  exit 0
elif [ "$1" = "-T" -o "$1" = "--vtemplate" ]; then
  template
  exit 0
elif [ "$1" = "-a" -o "$1" = "--atemplate" ]; then
  if [ -n "$2" ]; then
    package=$(echo $(echo "$2" | sed 's,.*/,,') | rev | sed -nr 's#(zg|zx|2zb)\.rat((\.crs|\.giro)){,1}\.([0-9a-z.]+)[_-](.*)#\4/\5#p' | rev)
    
    if [ -z "$package" ]; then
      echo "Could not work out package name and version!"
      exit 1
    fi
    
    aname=${package/\/*/}
    aversion=${package/*\//}
    
    echo "name=$aname"
    echo "version=$aversion"
    echo "#arch=noarch"
    echo "#buildnr=2"
    echo "#options=(!root !man !strip)"
    echo "source=(\"$2\")" | sed "s/${aname}/\${name}/g;s/${aversion}/\${version}/g;"
    echo 
    echo "build() {"
    echo "  cd \${name}-\${version}"
    echo "  ./configure"
    echo "  make"
    echo "  make install DESTDIR=\"\$pkg\""
    echo "}"
    exit 0
  else
    echo "You must provide the URL to the source package"
    exit 1
  fi
fi

if [ ! -e "BUILD" ]; then
  echo "No BUILD file found. You must create one first!" 1>&2
  helptext
  exit 1
fi

. BUILD

rm -fr "$src" "$pkg"
mkdir "$src" "$pkg"

for files in "${source[@]}"; do
  sourcefile=$(basename $files)
  if [ ! -e "$sourcefile" ]; then
    $downloader "$files"
  fi
  ( cd "$src"; ln -s "$startdir"/$sourcefile . )
  tar -xf $sourcefile -C "$src" 2>/dev/null || true
done

cd "$src"

set -eu

build

pkgname=${name}-${version}-${arch}-${buildnr}

for opts in "${options[@]}"; do
  case $opts in
    !man) doman=n ;;
    !strip) dostrip=n ;;
    !root) doroot=n ;;
  esac
done

if [ "$doman" = "y" ]; then
  find "$pkg" -regex ".*/man[1-8]/.*\.[1-8]" -type f -exec gzip -9 {} \;
  for l in $(find "$pkg" -regex ".*/man[1-8]/.*\.[1-8]" -type l); do
    lbase=$(basename $l)
    (
      cd "$(dirname $l)"
      ln -s $(readlink ${lbase}).gz ${lbase}.gz
      rm $lbase
    )
  done
fi

if [ "$dostrip" = "y" ]; then
  for bin in $(find "$pkg" -type f -exec file {} \; | sed -rn "s/(.*): (setuid )*ELF.*(executable|object).*not stripped/\1/p"); do
    strip --strip-unneeded $bin
  done
fi

if [ "$doroot" = "y" ]; then
  taropts="$taropts --owner 0 --group 0"
fi

cd "$pkg"

find . ! -type d | sed 's,^\.,,' > "$startdir/footprint"
echo "/var/log/footprints/$pkgname" >> "$startdir/footprint"
install -Dm 644 "$startdir/footprint" var/log/footprints/$pkgname

echo -e "\nCompressing $pkgname\n"

sed 's,^/,,' var/log/footprints/$pkgname | tar $taropts | $compressor > "$startdir/$pkgname.pkg.tar.$compext"

echo -e "\nCreated: $pkgname.pkg.tar.$compext\n"