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

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

## Author: Glenn Morris <rgm@gnu.org>
## Maintainer: Stefan Kangas <stefankangas@gmail.com>

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

source "${0%/*}/emacs-shell-lib"

## This should be the admin directory.
cd $PD || exit
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] [-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.
-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=

## Parameters.
ldefs_out=lisp/ldefs-boot.el
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"

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

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

tempfile="$(emacs_mktemp)"

while getopts ":hcfqA:CL" 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 ;;

        (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

    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}) || 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"
}


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


[ "$autogendir" ] && {

    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


[ "$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 "$@" ldefs-boot.el EMACS=../src/bootstrap-emacs || die "make src error"


# Refresh the prebuilt grammar-wy.el
grammar_in=lisp/cedet/semantic/grammar-wy.el
grammar_out=lisp/cedet/semantic/grm-wy-boot.el
make -C admin/grammars/ ../../$grammar_in EMACS=../../src/bootstrap-emacs
cp $grammar_in $grammar_out || die "cp grm_wy_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 $grammar_out) || die


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


exit 0