#!/bin/bash
#
# Script to update/install the latest versions of all the most
# important Common Lisp packages. Uses SBCL but otherwise tries to be
# somewhat independent of your local environment.
#
# Intended to quickly bootstrap a working development environment for
# Lisp free software hackers.
#
# Idea from jhbuild by James Henstridge (a Gnome hacker).
#
# Contributors:
#   Luke Gorrie <luke@member.fsf.org>
#   Anthony Chaumas-Pellet <achaumas@wispery.info>
#   Christophe Rhodes <csr21@cantab.net>
#   David Lichteblau <david@lichteblau.com>
#   Eric Marsden <eric.marsden@free.fr>

set -e
if [ "$CLNET_USER" == "" ]; then
    CLNET_USER=:pserver:anonymous:anonymous
fi

cd $(dirname $0)
system_dir="$(pwd)/systems"
source_dir="$(pwd)/source"
target_dir="$(pwd)/target"
self="$0"
while test -h "$self"; do
	self=`readlink "$self"`
done

if test x`uname -o` = xCygwin; then
	windowsp=1
else
	windowsp=""
fi

source clbuild.conf
if test -n "$windowsp" -a x$USER_INIT = x/dev/null; then
	USER_INIT=NUL
fi

if test -n "$USER_INIT"; then
    common_options="--userinit $USER_INIT"
else
    common_options=""
fi

# SBCL-specific options
build_options="--noinform --noprint --disable-debugger $common_options"
run_options="--disable-debugger $common_options"
quit="(sb-ext:quit)"
eval="--eval"
require_asdf="(require :asdf)"

# CLIM configuration
case x$CLIM_BACKEND in
    xgraphic-forms)
        EXTRA_CLIM_FEATURES="(pushnew :clim-graphic-forms *features*)"
	maybe_load_clx="nil"
        ;;
    xgtkairo)
        EXTRA_CLIM_FEATURES="(pushnew :gtkairo *features*)"
	maybe_load_clx="nil"
        ;;
    x|xclx)
        EXTRA_CLIM_FEATURES="nil"
	maybe_load_clx="(unless (find-package :xlib) (asdf:operate 'asdf:load-op :clx))"
        ;;
    *)
        echo "invalid $CLIM_BACKEND, expected clx, gtkairo, or graphic-forms." 1>&2
        exit 1
        ;;
esac

if test -n "$windowsp"; then
	system_namestring="`cygpath -m $system_dir`/"
	source_namestring="`cygpath -m $source_dir`/"
	target_namestring="`cygpath -m $target_dir`/"
	self="c:/cygwin/bin/bash $self"
else
	system_namestring="$system_dir/"
	source_namestring="$source_dir/"
	target_namestring="$target_dir/"
fi
set_central_registry="(setq asdf:*central-registry* '(#p\"${system_namestring}\"))"

if [ ! -z $CCL ]; then
    # user wants OpenMCL instead of SBCL.  Make it so.
    lisp=$CCL
    build_options="--batch" # fixme, that's not good enough
    run_options="--batch"   # ditto
    quit="(ccl:quit)"
elif [ ! -z $CLISP ]; then
    # user wants CLISP instead of SBCL.  Make it so.
    cat <<EOF
*** clbuild starting up using CLISP
*** Please note that CLISP support is experimental and not all applications
*** are expected to compile properly yet.

EOF
    lisp="$CLISP -repl"
    eval="-x"
    if test -n "$USER_INIT"; then
	common_options="-norc -i $USER_INIT"
    else
	common_options=""
    fi
    build_options="-on-error exit $common_options"
    run_options="-on-error exit $common_options"
    quit="(ext:quit)"
    require_asdf="(load \"source/asdf-for-clisp/asdf.lisp\")"

    if test -d source/asdf-for-clisp; then
	echo "*** asdf checkout found"
	echo
    else
	echo "NEW checking out asdf for use with clisp"
	(cd source && cvs -d :pserver:anonymous:@sbcl.cvs.sourceforge.net:/cvsroot/sbcl co -d asdf-for-clisp sbcl/contrib/asdf)
    fi
elif [ ! -z $SBCL ]; then
    lisp=$SBCL
elif [ -x ${target_dir}/bin/sbcl ]; then
    export SBCL_HOME=${target_namestring}lib/sbcl/
    lisp="${target_dir}/bin/sbcl"
    if ! test -f monster.core; then
	lisp="$lisp --core ${target_namestring}lib/sbcl/sbcl.core"
    fi
else
    lisp=sbcl
fi

[ -d $system_dir ] || mkdir $system_dir
[ -d $source_dir ] || mkdir $source_dir
[ -d $target_dir ] || mkdir $target_dir


blank_line="                                                                  "
tail_last() {
    if tty 0>&1 >/dev/null; then
	while read line; do
	    echo -e '\r\c'
	    echo -n "$blank_line"
	    echo -e '\r\c'
	    echo -n $line | cut -b 1-65 | tr -d '\n'
	done
	echo -e '\r\c'
	echo -n "$blank_line"
	echo -e '\r\c'
    else
	while read line; do
	    echo $line
	done
    fi
}

clbuild_lisp() {
    ${lisp} $common_options \
	$eval "$require_asdf" \
	$eval "$set_central_registry" \
	$eval "$EXTRA_CLIM_FEATURES" \
	"$@"
}

dribble_get() {
    label="$1"
    name="$2"

    if test -d `echo ${name}*/ | awk '{print $1;}'`; then
	echo -n "UPDATE "
    else
	echo -n "NEW "
    fi
    echo "$label $name"
}

get_darcs() {
    name="$1"
    url="$2"

    # don't use tail_last, since darcs already has this kind of progress bar
    if [ -d $name ]; then
	dribble_get "darcs pull" $name
	(cd $name; darcs pull --all)
    else
	dribble_get "darcs get" $name
	darcs get $url $name
    fi
    ln -f -s $(pwd)/$name/*.asd ${system_dir}
}

get_svn() {
    name="$1"
    url="$2"

    dribble_get "svn co" $name

    svn co $url $name | tail_last
    ln -f -s $(pwd)/$name/*.asd ${system_dir}/
}

get_cvs() {
    module="$1"
    repository="$2"

    dribble_get "cvs co" $module

    cvs -d $repository co $module | tail_last
    ln -f -s $(pwd)/$module/*.asd ${system_dir}/ || true
}

# zzz I don't like the way the existing directory is removed completely.
get_tarball() {
    name="$1"
    url="$2"
    flags="${3:-z}"

    tmp=$TMPDIR/${name}.tar.gz

    dribble_get wget $name

    [ -d ${name}*/ ] && rm -rf ${name}*/
    wget \
	--no-check-certificate \
	--progress=dot \
	-O "$tmp" \
	$url \
	2>&1 | tail_last
    tar v${flags}xf "$tmp" | tail_last
    rm "$tmp"
    ln -f -s $(pwd)/${name}*/*.asd ${system_dir}/
}

get_svn_clnet() {
    name="$1"
    path="$2"

    get_svn $name svn://common-lisp.net/project/$name/svn/$2
}

get_cvs_clnet() {
    module="$1"
    project="${2:-$1}"

    get_cvs $module ${CLNET_USER}@common-lisp.net:/project/$project/cvsroot
}

get_ediware() {
    name="$1"

    get_tarball $name http://weitz.de/files/${name}.tar.gz
}

get_tarball_bz2() {
    get_tarball "$1" "$2" j
}

recompile() {
	rm -f monster.core
	touch source/eclipse/config.lisp

        cmd="${1:-$quit}"
	${lisp} $build_options \
	    $eval "$require_asdf" \
	    $eval "$set_central_registry" \
            $eval "$EXTRA_CLIM_FEATURES" \
	    $eval "$maybe_load_clx" \
	    $eval "(asdf:oos 'asdf:load-op :mcclim)" \
	    $eval "(asdf:oos 'asdf:load-op :cl-ppcre)" \
	    $eval "(asdf:oos 'asdf:load-op :clim-listener)" \
	    $eval "(asdf:oos 'asdf:load-op :skippy)" \
	    $eval "(asdf:oos 'asdf:load-op :gsharp)" \
	    $eval "(asdf:oos 'asdf:load-op :clim-examples)" \
	    $eval "(asdf:oos 'asdf:load-op :closure)" \
	    $eval "(asdf:oos 'asdf:load-op :beirc)" \
	    $eval "(asdf:oos 'asdf:load-op :climacs)" \
	    $eval "(asdf:oos 'asdf:load-op :climplayer)" \
	    $eval "(asdf:oos 'asdf:load-op :hunchentoot-test)" \
	    $eval "(asdf:oos 'asdf:load-op :cl-webdav)" \
	    $eval "(asdf:oos 'asdf:load-op :eclipse)" \
	    $eval "(eclipse-system:compile-themes \"source/eclipse/themes/microGUI/\" \"source/eclipse/themes/Step/\" \"source/eclipse/themes/brushed-metal/\" \"source/eclipse/themes/CoolClean/\")" \
	    $eval "$cmd"
	echo "$0 ok"
}

count_systems() {
	n_asd=`ls -1 $system_dir/*.asd | wc -l`
	echo "$n_asd systems definition files registered"
}

update() {
	rm -f monster.core

        TMPDIR=`mktemp -d /tmp/clbuild.XXXXXXXXXX`

        cleanup() {
            rm -rf $TMPDIR
        }
        trap cleanup exit

	cd $source_dir
	source ../update.sh

	echo "update complete"
	count_systems
}

help() {
	cat <<EOF
Usage:
  check         check availability of all necessary helper applications

  update        download/update all applications
  recompile     recompile all applications
  dumpcore      recompile and dump a core file for faster startup
  build         update && dumpcore

  buildsbcl     download/update and compile SBCL
  world         buildsbcl && build

  clean         delete all compiled object files
  mrproper      delete all downloaded source and fasl files

  slime         run the Superior Lisp Interaction Mode in a fresh Emacs
  lisp          run Lisp in the terminal
  sbcl          alias for "lisp" (with all packages available to REQUIRE)
  openmcl       alias for "lisp"

  listener      run the McCLIM listener
  gsharp        run the Gsharp score editor 
  climacs       run the Climacs text editor
  closure       run the CLOSURE web browser
                (required Debian packages: gif2png,libjpeg-progs)
  beirc         run the Beirc IRC client
  climplayer    run the CLIMPlayer music player
                (required Debian packages: mplayer, fileschanged, fam)
  demodemo      run some random CLIM examples
  eclipse [DPY] run the eclipse window manager

  hunchentoot   run the Hunchentoot web server test
  webdav DIR    run the CL-WEBDAV server, serving directory DIR
                (required Debian packages: libssl-dev)

  dialog        show an interactive menu 

Default when started without arguments is 'dialog'.

If you do 'world' or 'buildsbcl' then SBCL will be installed in
target/ and used for future commands. If you don't run these commands
(or you remove target/) then clbuild uses the 'sbcl' in your PATH.

Set CCL to your OpenMCL binary to use OpenMCL instead of SBCL. 
Example: CCL=~/ccl/lx86cl64 ./clbuild build
(Not supported with targets 'buildsbcl' and 'sbcl'.)

Set CLIM_BACKEND=gtkairo to enable clim-gtkairo instead of clim-clx.
(Requires GTK+ >= 2.8.)

EOF
}

check_program() {
    if ! "$1" --help >/dev/null; then
	echo Error: Cannot find a working installation of "$1"
	exit 1
    fi
    echo "found `which $1`"
}

# for programs that don't understand --help, or (like cvs) are stupid enough
# to return a failure code when invoked using a correct --help option... 
check_misdesigned_program() {
    if ! which "$1" >/dev/null; then
	echo Error: Cannot find a working installation of "$1"
	exit 1
    fi
    echo "found `which $1`"
}

start_clim() {
    system=$1
    cmd="$2"

    ${lisp} $run_options \
	$eval "$require_asdf" \
	$eval "$set_central_registry" \
	$eval "$EXTRA_CLIM_FEATURES" \
	$eval "$maybe_load_clx" \
	$eval "(asdf:operate 'asdf:load-op :mcclim)" \
	$eval "(asdf:operate 'asdf:load-op $system)" \
	$eval "$cmd" \
	$eval "$quit"
}

interactive_menu() {
    if ! dialog --clear --title "clbuild" \
	--menu "Welcome to clbuild's interactive menu.\n\n\
If unsure, choose 'update' to download the userland, then try\n\
running one of applications." \
	0 0 0 \
	"update" "download/update all applications" \
	"recompile" "recompile all applications" \
	"dumpcore" "recompile and dump a core file for faster startup" \
	"" "" \
	"buildsbcl" "download/update and compile SBCL" \
	"world" "buildsbcl && update && dumpcore" \
	"" "" \
	"slime" "run the Superior Lisp Interaction Mode in a fresh Emacs" \
	"slime -nw" "run the Superior Lisp Interaction Mode (in this TTY)" \
	"lisp" "run Lisp in the terminal" \
	"" "" \
	"gsharp" "run the Gsharp score editor" \
	"climacs" "run the Climacs text editor" \
	"closure" "run the CLOSURE web browser" \
	"beirc" "run the Beirc IRC client" \
	"climplayer" "run the CLIMPlayer music player" \
	"demodemo" "run some random CLIM examples" \
	"hunchentoot" "run the Hunchentoot web server test" \
	"webdav" "run the CL-WEBDAV server" \
	"eclipse" "run the eclipse window manager" \
	"" "" \
	"clear" "Delete all fasls" \
	"mrproper" "Delete all downloaded source and fasls" \
	"" "" \
	"quit" "Exit clbuild" \
	2>$TMPDIR/choice
    then
	echo menu aborted
	exit 0
    fi
    choice="`cat $TMPDIR/choice`"
    case $choice in
	webdav)
	    if dialog --title "clbuild webdav" --clear --inputbox \
		"A WebDAV server will be started on http://localhost:4242.\n\n\
Files will be served for read and write operations without authentication.\n\n\
Please choose a directory to be exported." \
		16 72 /tmp 2>$TMPDIR/choice
	    then
		directory=`cat $TMPDIR/choice`
		if test -d $directory; then
		    choice="webdav $directory"
		else
		    $DIALOG --title "clbuild webdav" --clear \
			--msgbox "Error: Directory not found: $directory" \
			10 41
		    choice=""
		fi
	    else
		choice=""
	    fi
	    ;;
	eclipse)
	    if dialog --title "clbuild webdav" --clear --inputbox \
		"Please enter the X11 display to run eclipse on." \
		16 72 ${DISPLAY:-:0} 2>$TMPDIR/choice
	    then
		choice="eclipse `cat $TMPDIR/choice`"
	    else
		choice=""
	    fi
	    ;;
    esac
    case $choice in
	"")
	    ;;
	quit)
	    exit 0
	    ;;
	*)
	    echo
	    "$0" $choice || echo clbuild: $choice failed
	    echo
	    echo Type RET to continue
	    read
	    ;;
    esac
}

check() {
    echo "Checking for helper applications..."
    check_misdesigned_program cvs
    check_program svn
    check_program darcs
    check_program wget
    check_program tar
    check_misdesigned_program mktemp
    echo "Success: All helper applications found."
	
    echo
    echo "Checking Lisp startup..."
    if ${lisp} $run_options $eval $quit >/dev/null; then
	echo "Success: Lisp starts up using \"$lisp\""
    else
	echo "Error: Cannot run Lisp using \"$lisp\""
	exit 1
    fi

    echo
    echo "Looking for installable systems..."
    count_systems
}

dumpcore() {
    recompile '(sb-ext:save-lisp-and-die "monster.core")'
    echo "monster.core dumped"
}

if test -f monster.core; then
    # echo "using `pwd`/monster.core"
    run_options="--core monster.core $run_options"
fi

case $1 in
    check)
	check
	;;
    world)
	"$0" clean && "$0" buildsbcl && "$0" build
	;;
    clean)
	rm -f monster.core
	cd $source_dir
	find . -name "*.fasl" -exec rm {} \;
	find . -name "*.lx64fsl" -exec rm {} \;
	;;
    mrproper)
	rm -rf monster.core ${source_dir} ${target_dir}
	;;
    update)
	update
	;;
    recompile)
	recompile
	;;
    build)
	update
	dumpcore
	;;
    dumpcore)
        if [ -n "$CCL" ]; then
            echo "Sorry, dumpcore not implemented for OpenMCL yet." 1>&2
            exit 1
        fi
	dumpcore
	;;
    buildsbcl)
        if [ -n "$CCL" ]; then
            echo "Cowardly refusing to build SBCL when \$CCL is set." 1>&2
            exit 1
        fi
	cd ${source_dir}
	cvs -d :pserver:anonymous:@sbcl.cvs.sourceforge.net:/cvsroot/sbcl co sbcl
	# Enable threads
	if test -z "$windowsp"; then
	    echo '(lambda (list) (pushnew :sb-thread list) list)' > sbcl/customize-target-features.lisp
	fi
	(cd sbcl; sh make.sh; SBCL_HOME= INSTALL_ROOT=${target_dir} sh install.sh)
	;;
    lisp)
	shift;
        clbuild_lisp "$@"
        ;;
    sbcl)
        if [ -n "$CCL" ]; then
            echo "Cowardly refusing to run SBCL when \$CCL is set." 1>&2
            exit 1
        fi
	shift;
        clbuild_lisp "$@"
	;;
    openmcl)
        if [ -z "$CCL" ]; then
            echo "Cannot run OpenMCL because \$CCL is not set." 1>&2
            exit 1
        fi
	shift;
        clbuild_lisp "$@"
	;;
    slime)
	shift
	emacs_args="$@"
	emacs=${EMACS-emacs}
	$emacs \
	    -eval "(setq load-path (cons \"${source_namestring}slime\" load-path))" \
	    -eval "(setq inhibit-splash-screen t)" \
	    -eval "(load \"${source_namestring}slime/slime\")" \
	    -eval "(setq inferior-lisp-program \"$self lisp\")" \
	    -eval "(slime-setup)" \
	    -eval '(slime)' \
	    ${emacs_args}
	;;
    listener)
	start_clim :clim-listener "(clim-listener:run-listener)"
	;; 
    gsharp)
	start_clim :gsharp "(gsharp::gsharp)"
	;;
    demodemo)
	start_clim :clim-examples "(clim-demo::demodemo)"
	;;
    climacs)
	start_clim :climacs "(climacs:climacs)"
	;;
    closure)
	start_clim :closure "(clim-user::run-closure :new-process nil)"
	;;
    beirc)
	start_clim :beirc "(beirc:beirc :new-process nil)"
	;;
    climplayer)
	check_program fileschanged
	check_misdesigned_program mplayer
	start_clim :climplayer "(climplayer:climplayer)"
	;;
    hunchentoot)
	if ! test -f /usr/lib/libssl.so; then
	    echo "WARNING: /usr/lib/libssl.so not found, did you install OpenSSL?"
	    echo "(type RET to continue anyway)"
	    read
	fi
	${lisp} $run_options \
	    $eval "$require_asdf" \
	    $eval "$set_central_registry" \
	    $eval "(asdf:operate 'asdf:load-op :hunchentoot-test)" \
	    $eval "(setf tbnl:*catch-errors-p* nil)" \
	    $eval "(hunchentoot:start-server :port 4242)" \
	    $eval '(format t "~%Webserver is running at http://localhost:4242/hunchentoot/test~%")' \
	    $eval '(format t "Type RET to quit~%")' \
	    $eval '(read-line)' \
	    $eval "$quit"
	;;
    webdav)
	if ! test -f /usr/lib/libssl.so; then
	    echo "WARNING: /usr/lib/libssl.so not found, did you install OpenSSL?"
	    echo "(type RET to continue anyway)"
	    read
	fi
	directory="$2/"
	if ! test -d "$directory"; then
	    echo Error: Expected directory as an argument.
	    echo usage: clbuild webdav DIRECTORY
	    exit 1
	fi
	${lisp} $run_options \
	    $eval "$require_asdf" \
	    $eval "$set_central_registry" \
	    $eval "(asdf:operate 'asdf:load-op :cl-webdav)" \
	    $eval "(setf tbnl:*catch-errors-p* nil)" \
	    $eval "(setf dav:*file-resource-base-path-namestring* \"$directory\")" \
	    $eval "(push (dav:create-dav-dispatcher 'dav:file-resource) tbnl:*dispatch-table*)" \
	    $eval "(hunchentoot:start-server :port 4242)" \
	    $eval "(format t \"~%WebDAV server for $directory is running at http://localhost:4242/~%\")" \
	    $eval '(format t "Type RET to quit~%")' \
	    $eval '(read-line)' \
	    $eval "$quit"
	;;
    eclipse)
	options=""
	if test -n "$2"; then
	    options=":display \"$2\""
	fi
	touch source/eclipse/config.lisp
	# zzz cl-user::*eclipse-initfile*?!
	# zzz cl-user::*eclipse-eclipsedir*?!
	${lisp} $run_options \
	    $eval "$require_asdf" \
	    $eval "$set_central_registry" \
	    $eval "(unless (find-package :eclipse) (asdf:operate 'asdf:load-op :eclipse))" \
	    $eval "(defparameter cl-user::*eclipse-initfile* \".eclipse.lisp\")" \
	    $eval "(defparameter cl-user::*eclipse-eclipsedir* nil)" \
	    $eval "(unless (find-package :eclipse) (eclipse-system:compile-themes \"source/eclipse/themes/microGUI/\" \"source/eclipse/themes/Step/\" \"source/eclipse/themes/brushed-metal/\" \"source/eclipse/themes/CoolClean/\"))" \
	    $eval "(load (compile-file \"source/eclipse/lib/clx-ext/event.lisp\"))" \
	    $eval "(eclipse:eclipse $options)" \
	    $eval "$quit"
	;;
    help|--help|-h|-H)
        help
	;;
    ""|dialog)
        if ! dialog --help >/dev/null; then
	    cat <<EOF

error: dialog(1) not found.  Cannot start interactive menu.
Install dialog or try clbuild --help instead.
EOF
	    exit 1
	fi

	# always run check first to avoid unpleasant surprises
	check


        TMPDIR=`mktemp -d /tmp/clbuild.XXXXXXXXXX`

        cleanup() {
            rm -rf $TMPDIR
        }
        trap cleanup exit

	while :; do
	    interactive_menu
	done
	;;
    *)
        help
	exit 1
esac
