#!/bin/zsh
setopt extendedglob nomatch
TRAPERR() { exit 1 }

arch=(x86_64) # arches to build ("any" builds x86_64 and repo-adds x86_64 and i686)
check=1
chroot=extra # The chroot to use (without arch)
mailaddr= # send failed build logs where?
packagedir=~/packages # root directory for packages
pkgname=()
builddeps=()
repodb=$USERNAME.db.tar.gz # repo db filename
repodir=~/public_html/repo # repo to use. arch dir is appended
repohost=
srcdest=.
usage=0

argerror() {
  echo "type: 'mkpkg -h' for more information" >&2
  exit 1
}

usage() {
  echo 'Usage:  mkpkg [OPTION]... PACKAGE...'
  echo 'Automatically builds PACKAGE(s) and adds each to a repository'
  echo
  echo 'Options:'
  echo '  -f  Ignore check_* requests'
  echo '  -h  Display this message. Specify twice for more details'
}

usage2() {
  echo
  echo '--- Configurables ---'
  echo '  arch        Arches to build (array); "any" is special-cased.'
  echo "                Default: $(declare -m arch)"
  echo
  echo '  chroot      Chroot setup to use. Does not include architecture.'
  echo "                Default: $(declare -m chroot)"
  echo
  echo '  mailaddr    Where to mail logs from failed builds. May be an array.'
  echo "                Default: $(declare -m mailaddr)"
  echo
  echo '  packagedir  Root directory for packages.'
  echo "                Default: $(declare -m packagedir)"
  echo
  echo '  pkgname     Product pkgnames, not unlike makepkg. REQUIRED'
  echo "                Default: $(declare -m pkgname)"
  echo
  echo '  builddeps   Additional pkgnames to install from local repo (array).'
  echo "                Default: $(declare -m builddeps)"
  echo
  echo '  repodb      Repo db filename.'
  echo "                Default: $(declare -m repodb)"
  echo
  echo '  repodir     Repo to use. Architecture dir is appended.'
  echo "                Default: $(declare -m repodir)"
  echo
  echo '  repohost    Repo host name. Local host if empty.'
  echo "                Default: $(declare -m repohost)"
  echo
  echo '  srcdest     Where to find sources. Used by check_* functions.'
  echo "                Default: $(declare -m srcdest)"
  echo
  echo '  build()     Calls archbuild.'
  echo '                First argument: arch'
  echo '                Rest: makechrootpkg args'
  echo
  echo '--- Default build function ---'
  declare -f build
  echo
  echo '--- Program outline ---'
  echo ' - source ~/.mkpkg'
  echo ' - cd $packagedir'
  echo ' - for PACKAGE:'
  echo '    - cd PACKAGE'
  echo '    - source MKPKG'
  echo '    - Remove old built packages'
  echo '    - for $arch:'
  echo '       - Call build() with $arch makechrootpkg_args'
  echo '          - On failure, mail logs if $mailaddr is set'
  echo '       - Copy built packages to $repohost:$repodir/$arch'
  echo '       - Repo-add built packages to $repodb'
  echo '       - Remove old package files'
  echo '    - Remove duplicate log files'
  echo
  echo '--- Quick start ---'
  echo " 1. Put a PKGBUILD for foo-git into $packagedir/foo-git/"
  echo ' 2. Also put a file named MKPKG into this directory, containing:'
  echo '      pkgname=(foo-git)'
  echo ' 3. Now run: mkpkg foo-git'
  echo
  echo 'mkpkg will build the package and publish it in a repository named'
  echo "  $repodir/\$arch/$repodb"
  echo 'If $repohost is set, scp and ssh will be used to modify the repo remotely. You'
  echo 'need to make sure mkpkg is able to ssh into this host without an authentication'
  echo 'prompt.'
  echo
  echo 'For builds conditional on VCS updates, run check_{bzr,git,hg,svn} DIR [BRANCH]'
  echo 'from MKPKG, with DIR being a makepkg VCS source and BRANCH a branch (optional).'
  echo 'mkpkg will attempt to update the repository. If it is up to date, the build is'
  echo 'skipped. The option -f can be used to suppress this behavior, so packages are'
  echo 'always built.'
  echo
  echo 'The primary use of this script is running it from cron to build nightlies.'
}

check_bzr() (
  [[ -d $srcdest/$1 ]] || return 0

  cd $srcdest/$1
  bzr pull &>/dev/null || :
  echo $0 $* $(bzr revno) >> $checkfile
)

check_git() (
  [[ -d $srcdest/$1 ]] || return 0
  : ${2:=HEAD}

  cd $srcdest/$1
  git fetch --all -p &>/dev/null || :
  echo $0 $* $(git rev-parse $2) >> $checkfile
)

check_hg() (
  [[ -d $srcdest/$1 ]] || return 0
  : ${2:=tip}

  cd $srcdest/$1
  hg pull &>/dev/null || :
  echo $0 $* $(hg id -ir $2) >> $checkfile
)

check_svn() (
  [[ -d $srcdest/$1 ]] || return 0

  cd $srcdest/$1
  svn up &>/dev/null || :
  echo $0 $* $(svnversion) >> $checkfile
)

build() {
  [[ $chroot == multilib* ]] && 1=
  ${chroot}${1:+-$1}-build -- ${*:2}
}

# Parameters: arch
repoadd() {
  local -a files

  files=( ${^pkgname}-[^-]##-[^-]##-($1|any).pkg.tar*~*.(sig|log)(-om[1]) )
  files+=( ${^files}.sig(N) )

  if [[ -n $repohost ]]; then
    ssh -q $repohost mkdir -p $repodir/$1
    scp -q ./${^files} $repohost:$repodir/$1/
    ssh -q $repohost zsh -c ${(qq):-":
      setopt extendedglob nomatch
      repodir=${(q)repodir}
      repodb=${(q)repodb}
      pkgname=(${"${(@q)pkgname}"})
      files=(${"${(@q)files}"})
      $(declare -f repoadd_remote)
      repoadd_remote ${(q)1}
    "}
  else
    mkdir -p $repodir/$1
    cp $files $repodir/$1/
    repoadd_remote $1
  fi
}

# Parameters: arch
repoadd_remote() {
  local -a repofiles

  pushd $repodir/$1

  # Add to databases
  repo-add -q $repodb ${files:#*.sig}

  # clean up old packages
  repofiles=( ${^pkgname}-[^-]##-[^-]##-($1|any).pkg.tar* )
  rm -f ${repofiles:|files}

  popd
}

# Parameters: message logfile
maillog() {
  if [[ -n $mailaddr ]]; then
    xz <$2 >$2.xz
    mail -s "mkpkg failure" -a $2.xz $mailaddr <<MESSAGE
${1}
MESSAGE
    rm $2.xz
  else
    echo $1 >&2
  fi
}

# Parameters: arch
build_and_add() {
  local -a buildargs
  local dep

  buildargs=(-l $USERNAME-mkpkg -T)
  for dep in $builddeps; do
    buildargs+=(-I $repodir/$1/${dep}-[^-]##-[^-]##-($1|any).pkg.tar*~*.sig(-om[1]) )
  done

  if build $1 $buildargs &>$1.log; then
    repoadd $1 || :
    return 0
  else
    maillog "$0:$LINENO: '$1' build for '$package' failed" $1.log
    return 1
  fi
}

mkpkg_one() (
  cd $package

  checkfile=$PWD/.mkpkg_check.$$

  source MKPKG

  if [[ ! -s $checkfile ]]; then
    rm -f $checkfile
  elif [[ $check == 1 ]] && diff -q .mkpkg_check $checkfile &>/dev/null; then
    rm -f $checkfile
    exit 0
  else
    mv -f $checkfile .mkpkg_check
  fi

  if [[ -z $pkgname ]]; then
    echo "$0:$LINENO: no pkgnames found for '$package'" >&2
    echo "Please set \$pkgname in $PWD/MKPKG" >&2
    exit 1
  fi

  rm -f ${^pkgname}-[^-]##-[^-]##-[^-]##.pkg.tar*(N)

  for a in $arch; do
    case $a in
      any) build_and_add x86_64 && repoadd i686 || : ;;
      i686|x86_64) build_and_add $a || : ;;
      *) echo "$0:$LINENO: invalid arch '$a' for '$package'" >&2 ;;
    esac
  done

  rm -f *-*.log(|.[0-9])(N) logpipe.*(N)
)

[[ -f ~/.mkpkg ]] && source ~/.mkpkg

while getopts fh option; do
  case $option in
    f) check=0 ;;
    h) (( usage += 1 )) ;;
    \?) argerror ;;
  esac
done
shift $((OPTIND - 1))

if (( usage > 0 )); then
  usage
  (( usage > 1 )) && usage2
  exit 0
fi

if [[ -z $1 ]]; then
  echo "$0:$LINENO: no packages given" >&2
  argerror
fi

cd $packagedir

for package; do
  mkpkg_one
done

# vim:set sw=2 et:
