#!/bin/bash
### update_autogen - update the generated files in Emacs autogen/ directory

## Copyright (C) 2011-2013 Free Software Foundation, Inc.

## Author: Glenn Morris <rgm@gnu.org>

## This file is part of GNU Emacs.

## GNU Emacs 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 3 of the License, or
## (at your option) any later version.

## GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

### Commentary:

## This is a helper script to update the pre-built generated files in
## the autogen/ directory.  This is suitable for running from cron.
## Only Emacs maintainers need use this, so it uses bash features.
##
## With the -l option, it also updates the versioned loaddefs-like
## files in lisp/.  These include ldefs-boot, cl-loaddefs, rmail, etc.

### Code:

die ()                 # write error to stderr and exit
{
    [ $# -gt 0 ] && echo "$PN: $@" >&2
    exit 1
}

PN=${0##*/}                     # basename of script
PD=${0%/*}

[ "$PD" = "$0" ] && PD=.        # if PATH includes PWD

## This should be the autogen directory.
cd $PD
cd ../
[ -d autogen ] || die "Could not locate autogen directory"


usage ()
{
    cat 1>&2 <<EOF
Usage: ${PN} [-f] [-c] [-q] [-l [-L]] [-C] [-- make-flags]
Update the generated files in the Emacs autogen/ directory.
Options:
-f: force an update even if the source files are locally modified.
-c: if the update succeeds and the generated files are modified,
    commit them (caution).
-q: be quiet; only give error messages, not status messages.
-l: also update the versioned loaddefs-like files in lisp/.
This requires a build.  Passes any non-option args to make (eg -- -j2).
-L: also update ldefs-boot.el.
-C: start from a clean state.  Slower, but more correct.
EOF
    exit 1
}


## Defaults.

force=
commit=
quiet=
clean=
ldefs_flag=
lboot_flag=

## Parameters.
ldefs_in=lisp/loaddefs.el
ldefs_out=lisp/ldefs-boot.el
sources="configure.ac lib/Makefile.am"
genfiles="
  configure aclocal.m4 src/config.in lib/Makefile.in
  build-aux/compile build-aux/config.guess build-aux/config.sub
  build-aux/depcomp build-aux/install-sh build-aux/missing
"

for g in $genfiles; do
    basegen="$basegen ${g##*/}"
done

[ "$basegen" ] || die "internal error"

tempfile=/tmp/$PN.$$

trap "rm -f $tempfile 2> /dev/null" EXIT


while getopts ":hcflqCL" option ; do
    case $option in
        (h) usage ;;

        (c) commit=1 ;;

        (f) force=1 ;;

        (l) ldefs_flag=1 ;;

        (q) quiet=1 ;;

        (C) clean=1 ;;

        (L) lboot_flag=1 ;;

        (\?) die "Bad option -$OPTARG" ;;

        (:) die "Option -$OPTARG requires an argument" ;;

        (*) die "getopts error" ;;
    esac
done
shift $(( --OPTIND ))
OPTIND=1


## Does not work 100% because a lot of Emacs batch output comes on stderr (?).
[ "$quiet" ] && exec 1> /dev/null


echo "Running bzr status..."

bzr status -S $sources ${ldefs_flag:+lisp} >| $tempfile || \
    die "bzr status error for sources"

## The lisp portion could be more permissive, eg only care about .el files.
while read stat file; do

    case $stat in
        M)
            echo "Locally modified: $file"
            [ "$force" ] || die "There are local modifications"
            ;;

        *) die "Unexpected status ($stat) for $file" ;;
    esac
done < $tempfile


## Probably this is overkill, and there's no need to "bootstrap" just
## for making autoloads.
[ "$clean" ] && {

    echo "Running 'make maintainer-clean'..."

    make maintainer-clean #|| die "Cleaning error"

    rm -f $ldefs_in
}


echo "Running autoreconf..."

autoreconf ${clean:+-f} -i -I m4 2>| $tempfile

retval=$?

## Annoyingly, autoreconf puts the "installing `./foo' messages on stderr.
if [ "$quiet" ]; then
    grep -v 'installing `\.' $tempfile 1>&2
else
    cat "$tempfile" 1>&2
fi

[ $retval -ne 0 ] && die "autoreconf error"


cp $genfiles autogen/


cd autogen

echo "Checking status of generated files..."

bzr status -S $basegen >| $tempfile || \
    die "bzr status error for generated files"


modified=

while read stat file; do

    [ "$stat" != "M" ] && die "Unexpected status ($stat) for generated $file"

    modified="$modified $file"

done < $tempfile


cd ../


## Uses global $commit.
commit ()
{
    local type=$1
    shift

    [ $# -gt 0 ] || {
        echo "No files were modified"
        return 0
    }

    echo "Modified file(s): $@"

    [ "$commit" ] || return 0

    echo "Committing..."

    ## bzr status output is always relative to top-level, not PWD.
    bzr commit -m "Auto-commit of $type files." "$@" || return $?

    echo "Committed files: $@"
}                               # function commit


commit "generated" $modified || die "bzr commit error"


[ "$ldefs_flag" ] || exit 0


echo "Finding loaddef targets..."

sed -n -e '/^AUTOGEN_VCS/,/^$/ s/\\//p' lisp/Makefile.in | \
    sed '/AUTOGEN_VCS/d' >| $tempfile || die "sed error"

genfiles=

while read genfile; do

    [ -r lisp/$genfile ] || die "Unable to read $genfile"

    genfiles="$genfiles $genfile"
done < $tempfile


[ "$genfiles" ] || die "Error setting genfiles"


[ -e Makefile ] || {
    echo "Running ./configure..."

    ## Minimize required packages.
    ./configure --without-x || die "configure error"
}


## Build the minimum needed to get the autoloads.
echo "Running lib/ make..."

make -C lib "$@" all || die "make lib error"


echo "Running src/ make..."

make -C src "$@" bootstrap-emacs || die "make src error"


echo "Running lisp/ make..."

make -C lisp "$@" autoloads EMACS=../src/bootstrap-emacs || die "make src error"


## Ignore comment differences.
[ ! "$lboot_flag" ] || \
    diff -q -I '^;' $ldefs_in $ldefs_out || \
    cp $ldefs_in $ldefs_out || die "cp ldefs_boot error"


cd lisp

echo "Checking status of loaddef files..."

## It probably would be fine to just check+commit lisp/, since
## making autoloads should not effect any other files.  But better
## safe than sorry.
bzr status -S $genfiles ${ldefs_out#lisp/} >| $tempfile || \
    die "bzr status error for generated files"


modified=

while read stat file; do

    [ "$stat" != "M" ] && die "Unexpected status ($stat) for generated $file"
    modified="$modified $file"

done < $tempfile


cd ../


commit "loaddefs" $modified || die "bzr commit error"


exit 0

### update_autogen ends here
