bin/mdw-build: Force use of `bash' to allow file descriptors >= 10.
[profile] / bin / mdw-build
index 65ea3b0..0f3f7a4 100755 (executable)
 ### along with this program; if not, write to the Free Software Foundation,
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
+###--------------------------------------------------------------------------
+### Conventions for build systems.
+###
+### This script is designed to work with a variety of `make'-based build
+### systems, but there are a number of conventions which must be followed if
+### this is going to work properly.
+###
+###   * There must be a `configure.ac', `configure.in', or `.links' file, or
+###    a `.git' directory in the project top-level, so that we can find it.
+###
+###   * The following `make' variables must be assigned in the top-level
+###    Makefile, after `mdw-build' has constructed it.
+###
+###    distdir         The name of the top-level project directory in the
+###                    source distribution, and the base name for
+###                    distribution archives; should be of the form
+###                    `PROJECT-VERSION'.
+###
+###    The following `make' targets must be available in the top-level
+###    Makefile.
+###
+###    dist            Write to $(distdir).tar.gz a source distribution of
+###                    the package.
+###
+###    distcheck       As for `dist', but also build and test the project.
+###
+###  * The source distribution constructed by `make dist' must contain a file
+###    $(distdir)/RELEASE containing the release name.  This isn't currently
+###    tested, but it might be later.
+
 set -e
 
 ###--------------------------------------------------------------------------
+### Configuration.
+
+unset checkout checkoutrev
+unset setup setupcmd
+unset sign signkey
+unset sbuild sbuildsrv
+unset upload uploadpath
+unset dput dputtarget
+unset build distcheck debian clean vpath native
+for i in \
+  "/etc/mdw-build.conf" \
+  "${XDG_CONFIG_HOME-$HOME/.config}/mdw-build.conf" \
+  "./.mdw-build.conf"
+do
+  if [ -f "$i" ]; then . "$i"; fi
+done
+default_depends () {
+  var=$1 want=$2
+  eval "p=\${$var+t} q=\${$want+t}"
+  case $p,$q in t,*) ;; *,t) eval "$var=yes" ;; *) eval "$var=no" ;; esac
+}
+: ${checkout=yes} ${checkoutrev=HEAD}
+: ${build=test}
+: ${setup=yes} ${setupcmd=!guess}
+: ${distcheck=yes}
+: ${debian=yes}
+: ${clean=yes}
+: ${vpath=yes}
+: ${native=yes}
+: ${make=make}
+: ${test=yes}
+default_depends sbuild sbuildsrv
+default_depends sign signkey
+default_depends upload uploadpath
+default_depends dput dputtarget
+: ${DEB_BUILD_OPTIONS=parallel=4}; export DEB_BUILD_OPTIONS
+
+###--------------------------------------------------------------------------
 ### Parse options.
 
+prog=${0##*/}
+
 usage () {
   cat <<EOF
-Usage: $0 [-vr] BUILDOPT
+Usage: $prog [-v] BUILDOPT
 
 Build options:
 
   [no]checkout[=REV]
   [no]release
-  [no]setup
+  [no]setup[=RUNE]
   [no]distcheck
   [no]debian
-  [no]upload
+  [no]upload[=SERVER:PATH]
+  [no]dput[=TARGET]
   [no]clean
+  [no]vpath
+  [no]sbuild[=SERVER]
+  [no]sign[=KEYID]
+  [no]test
+  [no]native
+  make=MAKE
 EOF
 }
 
 ## Parse simple options.
 verbose=no
-while getopts "hvr" opt; do
+while getopts "hv" opt; do
   case "$opt" in
     h) usage; exit 0 ;;
     v) verbose=yes ;;
@@ -54,26 +131,38 @@ done
 shift $((OPTIND - 1))
 
 ## Parse the build options.
-checkout=yes
-checkoutrev=HEAD
-build=test
-setup=yes
-distcheck=yes
-debian=yes
-upload=yes
-clean=yes
+maybe_set () {
+  var=$1 want=$2
+  eval "p=\${$want+t}\${$want-nil}"
+  case $p in
+    t) eval $var=yes ;;
+    nil) echo >&2 "$prog: $want not set"; exit 1 ;;
+  esac
+}
 for opt; do
   case "$opt" in
     checkout)  checkout=yes checkoutrev=HEAD ;;
     checkout=*) checkout=yes checkoutrev=${opt#*=} ;;
-    nocheckout) checkout=no ;;
     release)   build=release ;;
     norelease) build=test ;;
-
-    setup | distcheck | debian | upload | clean)
+    setup)     setup=yes setupcmd=!guess ;;
+    setup=*)   setup=yes setupcmd=${opt#*=} ;;
+    upload)    maybe_set upload uploadpath ;;
+    upload=*)  upload=yes uploadpath=${opt#*=} ;;
+    sign)      maybe_set sign signkey ;;
+    sign=*)    sign=yes signkey=${opt#*=} ;;
+    sbuild)    maybe_set sbuild sbuildsrv ;;
+    sbuild=*)  sbuild=yes sbuildsrv=${opt#*=} ;;
+    dput)      maybe_set dput dputtarget ;;
+    dput=*)    dput=yes dputtarget=${opt#*=} ;;
+    make=*)    make=${opt#*=} ;;
+
+    distcheck | debian | clean | vpath | native | test)
       eval "$opt=yes"
       ;;
-    nosetup | nodistcheck | nodebian | noupload | noclean)
+    nocheckout | nosetup | nodistcheck | nodebian | \
+      noupload | nodput | noclean | novpath | nonative | notest | \
+      nosbuild | nosign )
       eval "${opt#no}=no"
       ;;
     *)
@@ -83,15 +172,35 @@ for opt; do
   esac
 done
 
+## Parse DEB_BUILD_OPTIONS.
+jobs=1
+set -- $DEB_BUILD_OPTIONS
+for opt; do
+  case "$opt" in
+    parallel=*) jobs=${opt#*=} ;;
+    nocheck) test=no ;;
+  esac
+done
+
+makeopts=""
+case $jobs in 1) ;; *) makeopts="$makeopts -j$jobs" ;; esac
+
 ###--------------------------------------------------------------------------
 ### Utility functions.
 
-exec 3>&2 4>/dev/null 5>&2
+## File descriptor assignments:
+##
+##    0 -- original stdin (never touched)
+## 1, 2 -- stdout, stderr, redirected to 3 while running comamnds
+##  log -- logfile and original stderr (verbose), or logfile only (quiet);
+##             captures command output
+## diag -- logfile; primary diagnostic output
+## term -- orginal stderr; secondary diagnostic output (with colours)
 
 notify () {
   colour=$1 message=$2
-  echo $message >&4
-  echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" >&5
+  echo $message >&$diag
+  echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" >&$term
 }
 
 fail () {
@@ -117,18 +226,18 @@ assign () {
 
 runx () {
   notify 2 "+++ $*"
-  "$@" 2>&3 || fail "$1: exit $?"
+  nice "$@" 2>&$log {log}>&- {diag}>&- {term}>&- || fail "$1: exit $?"
 }
 
-run () { runx "$@" >&3; }
+run () { runx "$@" >&$log; }
 
 yesno () {
-  echo -n "(test $*)" >&4
-  if "$@" >&4 2>&4; then
-    echo "(yes)" >&4
+  echo -n "(test $*)" >&$diag
+  if "$@" >&$diag 2>&$diag {log}>&- {diag}>&- {term}>&-; then
+    echo "(yes)" >&$diag
     echo yes
   else
-    echo "(no)" >&4
+    echo "(no)" >&$diag
     echo no
   fi
 }
@@ -136,8 +245,20 @@ yesno () {
 ###--------------------------------------------------------------------------
 ### Do the building.
 
+## Some preflight checks.
+case $test,$build in
+  no,release) fail "refusing to make release build without testing" ;;
+esac
+case $test,$distcheck in
+  no,yes)
+    info "forcing \`distcheck' off because tsting disabled"
+    distcheck=no
+    ;;
+esac
+
 ## Find the top-level package directory.
-while [ ! -f configure.ac -a ! -f configure.in -a ! -f .links ]; do
+while [ ! -f configure.ac -a ! -f configure.in -a \
+       ! -f .links -a ! -d .git ]; do
   case "$(pwd)" in
     /)
       fail "couldn't find top-level directory"
@@ -145,22 +266,35 @@ while [ ! -f configure.ac -a ! -f configure.in -a ! -f .links ]; do
   esac
   cd ..
 done
-assign srcpath $(pwd)
+toppath=$(pwd)
+
+## Build any necessary qualifiers.
+qual= sep=.
+case ${SBOX_SESSION_DIR+t},${DEB_BUILD_ARCH+t} in
+  t,t) qual=$qual$sep$DEB_BUILD_ARCH; sep=- ;;
+  t,*) fail "unknown build arch" ;;
+esac
 
 ## Construct the output directory.
-assign releasepath $srcpath/dist-$build
-chmod -R +w $releasepath 2>/dev/null|| :
+releasepath=$toppath/dist-$build$qual
+chmod -R +w $releasepath 2>/dev/null || :
 rm -rf $releasepath 2>/dev/null || :
 mkdir $releasepath
+logfile=$releasepath/mdw-build.log
+exec {term}>&2
+exec {diag}>>$logfile || fail "Failed to create log."
 case $verbose in
-  no)
-    exec 4>$releasepath/mdw-build.log 3>&4 ||
-      fail "Failed to create log."
-    ;;
+  no) exec {log}>&$diag ;;
+  yes) exec {log}> >(tee -a $logfile >&$term {term}>&- {diag}>&-) ;;
 esac
 
+## Repeat the earlier assignments for tbe benefit of the logfile.
+assign toppath $toppath
+assign releasepath $releasepath
+assign logfile $logfile
+
 ## Do we have a Git repository?
-case "$checkout,$setup,$(yesno [ -d $srcpath/.git ])" in
+case "$checkout,$setup,$(yesno [ -d $toppath/.git ])" in
   yes,no,*)
     fail "Inconsistent options: can't check out without setup."
     ;;
@@ -169,112 +303,194 @@ case "$checkout,$setup,$(yesno [ -d $srcpath/.git ])" in
     checkout=no gitver=none
     ;;
   yes,yes,yes)
-    cd $srcpath
+    cd $toppath
     [ "$(git ls-files -m)" = "" ] ||
       warn "working tree has uncommitted changes"
-    gitver=$(git describe)
+    ;;
 esac
 
 ## Is there Debian build equipment?
-case "$debian,$(yesno [ -d $srcpath/debian ])" in
+case "$debian,$(yesno [ -d $toppath/debian ])" in
   yes,no)
     info "No debian directory found."
     debian=no debver=none
     ;;
+  no,*)
+    debver=none
+    ;;
   yes,yes)
     debver=$(dpkg-parsechangelog | sed -n 's/^Version: //p')
+    debsrc=$(dpkg-parsechangelog | sed -n 's/^Source: //p')
+    debname=$(git config user.name) debemail=$(git config user.email)
     ;;
 esac
 
-## Check the version number.
-case "$gitver,$debver" in
-  none,* | *,none)
-    ;;
-  *)
-    [ "$gitver" = "$debver" ] ||
-      warn "Git version $gitver doesn't match Debian version $debver"
-    ;;
-esac
-
-## Maybe check ot a copy of the source.
+## Maybe check out a copy of the source.
 case "$checkout" in
   yes)
     cd $releasepath
-    run git clone -sn $srcpath/.git _source
+    run git clone -sn $toppath/.git _source
     assign srcpath $releasepath/_source
     cd $srcpath
-    run git checkout -b mdw-build $checkoutrev
+    run git update-ref refs/heads/mdw-build $checkoutrev ""
+    run git symbolic-ref HEAD refs/heads/mdw-build
+    run git read-tree --reset refs/heads/mdw-build
+    run git checkout-index -afu
+    assign gitversion "$(git describe --abbrev=4)"
+    ;;
+  no)
+    assign srcpath $toppath
+    ;;
+esac
+
+## Check the version number.
+hack_dch_p=no
+case "$gitversion,$debver" in
+  none,* | *,none)
+    ;;
+  *)
+    dvref=$(echo "$debver" | tr '~' '_')
+    if [ "$gitversion" = "$dvref" ]; then
+      assign debversion "$debver"
+    else
+      warn "Git version $gitversion doesn't match Debian version $debver"
+      hack_dch=yes
+      dver=$(echo $gitversion | sed 's/-/+/; s/-/./g')
+      case $debver in *~) dver=$debver$dver ;; esac
+      assign debversion "$dver"
+      now=$(date -R)
+    fi
     ;;
 esac
 
 ## Maybe refresh the build machinery.
 case "$setup" in
   yes)
-    run mdw-setup
+    case $setupcmd in
+      !guess)
+       if [ -f .links ]; then setupcmd=mdw-setup
+       elif [ -x autogen.sh ]; then setupcmd=./autogen.sh
+       elif [ -x setup ]; then setupcmd=./setup
+       elif [ -f configure.ac ]; then setupcmd="autoreconf -is"
+       else setupcmd=mdw-setup
+       fi
+       ;;
+    esac
+    run $setupcmd
     ;;
 esac
 
 ## Initialize the build directory.
-if [ -e $srcpath/configure ]; then
-  assign buildpath $releasepath/_build
-  mkdir $buildpath
-  cd $buildpath
-  run $srcpath/configure
-else
-  info "no configure script"
-  assign buildpath $srcpath
-  cd $srcpath
-fi
+case "$vpath,$(yesno [ -e $srcpath/configure ])" in
+  yes,yes)
+    assign buildpath $releasepath/_build
+    mkdir $buildpath
+    cd $buildpath
+    run $srcpath/configure
+    ;;
+  no,yes)
+    info "VPATH build disabled"
+    assign buildpath $srcpath
+    distcheck=no
+    cd $srcpath
+    run ./configure
+    ;;
+  *,no)
+    info "no configure script"
+    assign buildpath $srcpath
+    cd $srcpath
+    ;;
+esac
 
 ## Discover the release name.
 cat >find-distdir.mk <<'EOF'
 include Makefile
 print-distdir:
-       @echo $(distdir)
+       @bash -c 'echo >&$(fd) $(distdir)'
 EOF
-assign distdir $(make -f find-distdir.mk print-distdir)
+assign distdir \
+  $({ $make -f find-distdir.mk print-distdir fd=$t >/dev/null 2>&1; } {t}>&1)
 
 ## Get a tarball distribution.
 case "$distcheck" in
   yes)
-    run make distcheck
+    run $make $makeopts distcheck
     ;;
   no)
-    run make dist
+    run $make $makeopts dist
     ;;
 esac
 
 cd $releasepath
 
-if ! tar tfz $buildpath/$distdir.tar.gz | grep -q RELEASE; then
-  fail "missing RELEASE file in distribution"
-fi
+case $native in
+  yes)
+    if ! tar tf $buildpath/$distdir.tar.gz 2>/dev/null | grep -q RELEASE
+    then
+      fail "missing RELEASE file in distribution"
+    fi
+    ;;
+esac
 
 run mv $buildpath/$distdir.tar.gz .
+case $build,$sign in
+  release,yes)
+    run gpg -u$signkey -ab -o$distdir.tar.gz.gpg $distdir.tar.gz
+    ;;
+esac
 
 ## Maybe build the Debian packages.
 case "$debian" in
   yes)
     run tar xvfz $distdir.tar.gz
     cd $distdir
-    run dpkg-buildpackage -k$(mdw-conf releasekey)
+    case $hack_dch in
+      yes)
+       cat - debian/changelog >debian/changelog.new <<EOF
+$debsrc ($debversion) experimental; urgency=low
+
+  * Hacking in process, not intended for release.
+
+ -- $debname <$debemail>  $now
+
+EOF
+       mv debian/changelog.new debian/changelog
+       ;;
+    esac
+    sbuildargs=$sbuildsrv
+    case $sbuild,$build in
+      yes,release)
+       case $sign in yes) sbuildargs="-k$signkey $sbuildargs" ;; esac
+       ;;
+      yes,*)
+       if [ -d $toppath/dist-$build.pkgs ]; then
+         sbuildargs="-p$toppath/dist-$build.pkgs $sbuildargs"
+       fi
+       ;;
+    esac
+    case "$sbuild,$test, $DEB_BUILD_OPTIONS " in
+      yes,no,*) sbuildargs="-T $sbuildargs" ;;
+      *" nocheck "*) ;;
+      no,no,*)
+       DEB_BUILD_OPTIONS=${DEB_BUILD_OPTIONS+"$DEB_BUILD_OPTIONS nocheck"}
+       ;;
+    esac
+    case $sbuild,$build,$sign in
+      yes,*) run mdw-sbuild $sbuildargs ;;
+      no,release,yes) run dpkg-buildpackage -k$signkey ;;
+      no,*) run dpkg-buildpackage -us -uc ;;
+    esac
     ;;
 esac
 
 ## Maybe upload Debian packages.
 cd $releasepath
 case "$upload,$build" in
-  yes,test)
-    info "Test build: not uploading."
-    ;;
-  yes,release)
-    run rsync $distdir.tar.gz \
-      $(mdw-conf upload-target metalzone.distorted.org.uk:/home/ftp/pub/mdw/)
-    case "$debian" in
-      yes)
-       run dput -f $(mdw-conf dput-target metalzone) *.changes
-       ;;
-    esac
+  yes,test) info "Test build: not uploading." ;;
+  yes,release) run rsync $distdir.tar.gz $distdir.tar.gz.gpg $uploadpath ;;
+esac
+case "$debian,$upload,$dput,$build" in
+  yes,yes,yes,release) run dput -f $dputtarget *.changes ;;
 esac
 
 ## Tidy up.