#!/usr/bin/env bash
### update_autogen - update some auto-generated files in the Emacs tree

## Copyright (C) 2011-2021 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 <https://www.gnu.org/licenses/>.

### Commentary:

## This is a helper script to update some generated files in the Emacs
## repository.  This is suitable for running from cron.
## Only Emacs maintainers need use this, so it uses bash features.
##
## By default, it updates the versioned loaddefs-like files in lisp,
## except ldefs-boot.el.

### 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 admin directory.
cd $PD
cd ../
[ -d admin ] || die "Could not locate admin directory"

[ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1 || die "Not in a git repository"

usage ()
{
    cat 1>&2 <<EOF
Usage: ${PN} [-f] [-c] [-q] [-A dir] [-I] [-L] [-C] [-- make-flags]
Update some auto-generated files in the Emacs tree.
By default, only does the versioned loaddefs-like files in lisp/.
This requires a build.  Passes any non-option args to make (eg -- -j2).
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.
-A: only update autotools files, copying into specified dir.
-H: also update ChangeLog.${changelog_n}
-I: also update info/dir.
-L: also update ldefs-boot.el.
-C: start from a clean state.  Slower, but more correct.
EOF
    exit 1
}


## Defaults.

force=
commit=
quiet=
clean=
autogendir=                     # was "autogen"
ldefs_flag=1
lboot_flag=
info_flag=
changelog_flag=

## Parameters.
ldefs_in=lisp/loaddefs.el
ldefs_out=lisp/ldefs-boot.el
changelog_n=$(sed -n 's/CHANGELOG_HISTORY_INDEX_MAX *= *//p' Makefile.in)
changelog_files="ChangeLog.$changelog_n"
sources="configure.ac lib/Makefile.am"
## Files to copy into autogendir.
## Everything:
genfiles="
  configure aclocal.m4 src/config.in
  build-aux/config.guess build-aux/config.sub
  build-aux/install-sh
"
## msdos-only:
genfiles="src/config.in"

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 ":hcfqA:HCIL" option ; do
    case $option in
        (h) usage ;;

        (c) commit=1 ;;

        (f) force=1 ;;

        (q) quiet=1 ;;

        (A) autogendir=$OPTARG
            [ -d "$autogendir" ] || die "No autogen directory: $autogendir"
            ;;

        (C) clean=1 ;;

        (H) changelog_flag=1 ;;

        (I) info_flag=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


## Run status on inputs, list modified files on stdout.
status ()
{
    git status -s "$@" >| $tempfile || die "git status error for $@"

    local stat file modified

    while read stat file; do

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

    done < $tempfile

    echo "$modified"

    return 0
}                               # function status


echo "Checking input file status..."

## The lisp portion could be more permissive, eg only care about .el files.
modified=$(status ${autogendir:+$sources} ${ldefs_flag:+lisp} ${info_flag:+doc}) || die

[ "$modified" ] && {
    echo "Locally modified: $modified"
    [ "$force" ] || die "There are local modifications"
}


## 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"


## 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..."

    git commit -m "; Auto-commit of $type files." "$@" || return $?

    ## In case someone else pushed something while we were working.
    git pull --rebase || return $?
    git push || return $?

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


## No longer used since info/dir is now generated at install time if needed,
## and is not in the repository any more.
info_dir ()
{
    local basefile=build-aux/dir_top outfile=info/dir

    echo "Regenerating info/dir..."

    ## Header contains non-printing characters, so this is more
    ## reliable than using echo.
    rm -f $outfile
    cp $basefile $outfile

    local topic file dircat dirent

    ## FIXME inefficient looping.
    for topic in "Texinfo documentation system" "Emacs" "GNU Emacs Lisp" \
        "Emacs editing modes" "Emacs network features" "Emacs misc features" \
        "Emacs lisp libraries"; do

        cat - <<EOF >> $outfile

$topic
EOF
        ## Bit faster than doc/*/*.texi.
        for file in doc/emacs/emacs.texi doc/lispintro/*.texi \
            doc/lispref/elisp.texi doc/misc/*.texi; do

            ## FIXME do not ignore w32 if OS is w32.
            case $file in
                *-xtra.texi|*efaq-w32.texi) continue ;;
            esac

            dircat=$(sed -n -e 's/@value{emacsname}/Emacs/' -e 's/^@dircategory //p' $file)

            ## TODO warn about unknown topics (check-info in top-level
            ## Makefile does this).
            [ "$dircat" = "$topic" ] || continue

            sed -n -e 's/@value{emacsname}/Emacs/' \
                -e 's/@acronym{\([A-Z]*\)}/\1/' \
                -e '/^@direntry/,/^@end direntry/ s/^\([^@]\)/\1/p' \
                $file >> $outfile

        done
    done

    local modified

    modified=$(status $outfile) || die

    commit "info/dir" $modified || die "commit error"
}                               # function info_dir


[ "$autogendir" ] && {

    oldpwd=$PWD

    cp $genfiles $autogendir/

    cd $autogendir || die "cd error for $autogendir"

    echo "Checking status of generated files..."

    modified=$(status $basegen) || die

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

    exit 0
}                               # $autogendir


[ "$info_flag" ] && info_dir


[ "$ldefs_flag" ] || exit 0


echo "Finding loaddef targets..."

find lisp -name '*.el' -exec grep '^;.*generated-autoload-file:' {} + | \
    sed -e '/loaddefs\|esh-groups/d' -e 's|/[^/]*: "|/|' -e 's/"//g' \
    >| $tempfile || die "Error finding targets"

genfiles=

while read genfile; do

    ## Or we can just use sort -u when making tempfile...
    case " $genfiles " in
        *" $genfile "*) continue ;;
    esac

    [ -r $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"


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.
modified=$(status $genfiles $ldefs_out) || die


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


## Less important than the other stuff, so do it last.
[ ! "$changelog_flag" ] || {
    make change-history-nocommit || die "make change-history error"
    modified=$(status $changelog_files) || die
    commit "ChangeLog" $modified || die "commit error"
}


exit 0

### update_autogen ends here
