#!/bin/sh
# This purports to allow distributions to be built with shared libraries.
# 
# I wrote it for Kpathsea and friends, but I don't think there's
# anything TeX-specific in here.
# 
# There is a much fancier libtool project underway by
# <gord@enci.ucalgary.ca>, but I did not want to wait for that to be
# completed, stable, and portable before releasing Web2c.  The ideas are
# based on Gord's Libtool, though, and you may find its documentation
# interesting/useful reading.
# 
# Porting this to other systems shouldn't be too hard, mostly because I
# don't try to take advantage of all the fancy features offered by some
# os's (like multiple version compatibility, encoding directory paths in
# the binary, etc.)  See the configure mode.  I can send you the
# hello,world Makefile I used for testing if you want it.
# 
rcs_version='$Id: klibtool,v 1.10 1999/01/14 18:56:54 olaf Exp $'
version=0.1
maint=tex-k@mail.tug.org
help="Usage: $0 [OPTION]... MODE [ARG]...
Help for building and linking with shared libraries.

Modes:                                 Environment variables used:
configure [HOSTTYPE]                   RANLIB, LIBTOOL_OBJTYPES
compile CC SOURCEFILE ARG...
archive AR ARFLAGS LIBNAME ARG...
link CC ARG...
install-lib DIR LIBNAME...             INSTALL_DATA
install-prog DIR PROGNAME...           INSTALL_PROGRAM
version

Options:
    --source-dir DIR
    --config-dir DIR
-n, --dry-run
    --help
    --quiet, --silent
-v, --verbose
    --version

Email bugs to $maint.
"

bug_report="$0: Please report this bug to $maint.
Please mention this is Klibtool version $version ($rcs_version),
and your hardware/operating system (`uname -a`, at least).

Running this same command ($0 $*) with --verbose and including the
resulting output would be nice, but is not required."

verbose=:
chicken=
show=echo
config_dir=
source_dir=

# Yes, this option parsing is imperfect, e.g., -xcruddy will be
# recognized as --config-dir.  I don't think it's worth the trouble to
# program correctly until somebody besides me uses this.
while test $# -gt 0; do
  case "$1" in
    configure|compile|archive|link|install-lib|install-prog|version)
      mode=$1; break;;
    --source-dir) # --source-dir
      shift; source_dir=$1;;
    -*c*)         # --config-dir
      shift; config_dir=$1;;
    -n|-*d*)      # -n, --dry-run
      chicken=echo;;
    -*help)       # --help
      echo "$help"; exit 0;;
    -*q|-*s)      # --quiet, --silent
      show=:; verbose=:;;
    -v|-*verb*)   # --verbose
      verbose=echo;;
    -*version)    # --version
      echo "$0 version $version ($rcs_version)"; exit 0;;
    -*)
      echo "$0: Unknown option \`$1'. Try --help for info." >&2; exit 1;;
    *)
      echo "$0: Unknown mode \`$1'. Try --help for info." >&2; exit 1;;
  esac
  shift
done

# Read all the arguments.  Peel off the mode.
shift

# 
# Read the configuration file unless we're configuring.
# 
if test $mode != configure; then
  # Read configuration file.  If we have it in the current directory, or
  # the user told us where it is, great.  More likely, though, it's only
  # in the directory with the library that we're eventually going to
  # link with.  We have no way of knowing what that is, so let's use the
  # location of this script itself as the default if not in `.', `..',
  # or `../..'.
  test -z "$config_dir" && config_dir=$KLIBTOOL_CONFIG_DIR
  if test -z "$config_dir"; then
    if test -r ./klibtool.config; then
      config_dir=.
    elif test -r ../klibtool.config; then
      config_dir=..
    elif test -r ../../klibtool.config; then
      config_dir=../..
    else
      dir=`echo $0 | sed 's,/[^/]*$,,'`
      test -r $dir/klibtool.config && config_dir=$dir
    fi
  fi
  if test -z "$config_dir"; then
    echo "$0: Cannot find klibtool.config in . or .. or $dir," >&2
    echo "$0: and no --config-dir option specified" >&2
    echo "$0: or KLIBTOOL_CONFIG_DIR environment variable set." >&2
    exit 1
  fi
  # Read the file.
  . $config_dir/klibtool.config

  if test -z "$LIBTOOL_OBJTYPES"; then
    echo "$0: Impossibly empty LIBTOOL_OBJTYPES!" >&2
    echo "$bug_report" >&2
    exit 1
  fi
  # Copy the valid object type names from LIBTOOL_OBJTYPES into objtypes.
  $verbose "$0: checking LIBTOOL_OBJTYPES = $LIBTOOL_OBJTYPES."
  objtypes=
  for ot in `echo $LIBTOOL_OBJTYPES | tr : " "`; do
    case $ot in
      SHARED)
        if $shared_support; then 
          objtypes=$objtypes:$ot
        else
          echo "$0: Shared libraries not supported on this system." >&2
        fi
        ;;
      STATIC)
        objtypes=$objtypes:$ot;;
      "") true;; # don't worry about empty object types.
      *)
        echo "$0: Ignoring unknown libtool object type $objtype." >&2;;
     esac
  done
  # Remove the extra trailing colon from our list-building.
  objtypes=`echo $objtypes | sed 's/^://'`
  if test -z $objtypes; then
    # If they just took the default, we started with STATIC and so
    # wouldn't be here.
    echo "$0: No valid object types in $LIBTOOL_OBJTYPES, quitting." >&2
    exit 1
  fi
  $verbose "$0: final objtypes = $objtypes."
fi


# 
# Do the deed.
# 
# I wish we had subroutines so we could make this readable, but shell
# functions aren't portable enough, even nowadays.
# 
$verbose "$0: mode = $mode."
case $mode in


  # configure mode: [HOSTTYPE]
  configure)
    # If no config dir specified, use the script location.
    if test -z "$config_dir"; then
      if echo $0 | grep / >/dev/null; then
        config_dir=`echo $0 | sed 's,/[^/]*$,,'`
      else
        config_dir=.  # $0 is just the script name, no directory part.
      fi
    fi
    if test -z "$source_dir"; then
      source_dir=$config_dir
    fi
    config_file=$config_dir/klibtool.config
    config_tmp=$config_dir/klt$$.tmp
    $verbose "$0: writing to config_file = $config_file."
    
    # If no specified HOSTTYPE, guess it.
    if test $# -eq 0; then
      config_guess=$source_dir/config.guess
      if test ! -r $config_guess; then
        echo "$0: config.guess not in $source_dir." >&2
        echo "$0: Either specify a host type or get the scripts." >&2
        exit 1
      fi
      host_alias=`$config_guess`
    else
      test $# -eq 1 \
       || echo "$0: Using $1 as host alias, ignoring other arguments ($*)." >&2
      host_alias=$1
    fi
    
    # Convert the original host type to canonical form.
    config_sub=$source_dir/config.sub
    if test ! -r $config_sub; then
      echo "$0: config.sub missing from $source_dir; it's required." >&2
      exit 1
    fi
    host_type=`$config_sub $host_alias`
    if test -z "$host_type"; then
      echo "$0: $host_alias not a recognizable host type." >&2
      exit 1
    fi
    
    # Define defaults, to be overridden in the system-specific cases.
    config_vars="LIBTOOL_OBJTYPES shared_support shared_ext libpath_var CC
      args_STATIC_compile args_SHARED_compile
      args_STATIC_archive STATIC_ranlib args_SHARED_archive
      args_SHARED_link
      SHARED_postinstall"
    for v in $config_vars; do
      # Preserve existing value of a couple variables.
      case $v in
        LIBTOOL_OBJTYPES|CC) true;;
        *) eval $v=;;
      esac
    done
    test -z "$LIBTOOL_OBJTYPES" && LIBTOOL_OBJTYPES=STATIC
    shared_ext=so
    libpath_var=LD_LIBRARY_PATH
    STATIC_ranlib=$RANLIB

    # The compiler.  If the user set CC, take that, else use gcc if we
    # can find it, else use cc.  Up to the user to avoid /usr/ucb/cc.
    if test -z "$CC"; then
      for dir in `echo $PATH | tr : ' '`; do
        test -z "$dir" && dir=.
        if test -f $dir/gcc; then
          CC=gcc
          break
        elif test -f $dir/gcc.exe; then
          CC=gcc
          break
        fi
      done
    fi
    test -z "$CC" && CC=cc
    #
    # But the real question is not the name of the command, it's whether
    # it is GNU C.  We only distinguish gcc and system cc.  We have to
    # assume that they use the same compiler at `klibtool compile' time
    # as we determine here; the whole point is that we don't want to do
    # this check before compiling every file (for speed).
    rm -f conftest.c
    (
      echo "#ifdef __GNUC__"
      echo "yes;"
      echo "#endif"
    ) >conftest.c
    if eval "$CC -E conftest.c" | grep yes >/dev/null 2>&1; then
      compiler=gcc
      args_SHARED_compile=-fPIC # should we have an option for -fpic?
      args_SHARED_archive=-shared
    else
      compiler=cc
    fi
    rm -f conftest.c

    # Override defaults for this system.
    case $host_type in
      *-*-linux*)
        shared_support=true
        SHARED_postinstall='ldconfig $libdir'
        ;;
        
      *-*-solaris2*)
        shared_support=true
        if test $compiler = cc; then # /opt/SUNWspro/cc, that is.
          args_SHARED_compile=-KPIC
          args_SHARED_archive="-G -z text" # Perhaps should have -h.
        fi
        ;;

      *-*-sysv4.2*)
        shared_support=true
        if test $compiler = cc; then # /opt/ccs/bin/cc, that is.
          args_SHARED_compile=-KPIC
          args_SHARED_archive="-G -z text"
        fi
        ;;

      *-*-sunos4*)
        shared_support=true
        STATIC_ranlib=ranlib
        SHARED_postinstall='ldconfig $libdir'
        if test $compiler = cc; then
          args_SHARED_compile=-PIC
          prog_SHARED_archive=ld
          args_SHARED_archive="-assert pure-text"    # gord has -Bstatic?
        fi
        ;;

      *-*-irix5*|*-*-irix6*)
        shared_support=true
        args_SHARED_compile=
        ;;

      *-*-*djgpp* | *-*-*go32* | *-*-msdos*)
        shared_support=false
        libpath_var=LIBRARY_PATH
        ;;

      *-*-freebsd*)
        shared_support=true
        SHARED_postinstall='ldconfig -m $libdir'
        ;;

      hppa*-*-hpux*)
        if test $compiler = gcc; then
          shared_support=true
          args_SHARED_archive='-shared -fPIC'
          shared_ext='sl'
        fi
        ;;

      *)
        echo "$0: $host_type not explicitly supported, using defaults." >&2
        ;;
    esac

    # Finally, we'll be creating something.
    rm -f $config_tmp    

    # Output values.  
    for v in $config_vars; do
      config_line=$v=\'`eval echo '$'$v`\'
      $verbose "$0: writing config line $config_line."
      echo $config_line >>$config_tmp
    done

    # Check if this changed anything.
    if cmp -s $config_file $config_tmp 2>/dev/null; then
      $verbose "$0: $config_file is unchanged"
      rm -f $config_tmp
    else
      rm -f $config_file
      mv $config_tmp $config_file
    fi
    ;;


  # compile mode: CC SOURCEFILE [ARG]...
  compile)
    compiler=$1; shift   # must assume it's what configure found
    sourcefile=$1
    objname=`basename $sourcefile | sed 's/\.[^./]*$//'`.o
    $verbose "$0: object basename for source file $sourcefile = $objname."
    #
    for ot in `echo $objtypes | tr : " "`; do
      # Snarf arguments for this object type.
      ot_args=`eval echo '$'args_${ot}_${mode}`
      $verbose "$0: args_${ot}_${mode} = $ot_args."
      
      # Have to output into a subdirectory of current directory (not
      # source directory, which might be read-only).
      output_arg="-o $ot/$objname"
      if test ! -d $ot; then
        $show mkdir $ot
        $chicken mkdir $ot
      fi
      
      # Construct and run the cmd.
      cmd="$compiler ""$@"" $ot_args $output_arg"
      $show $cmd
      $chicken eval "$cmd"
      status=$?
      test $status -eq 0 || break
    done # end of objtypes loop for compile mode
    test $status -eq 0 && date >./"`echo $objname | sed 's/o$/lo/'`"
    exit $status
    ;;


  # archive mode
  archive)
    cmdname=$1; shift             # the archiver
    for ot in `echo $objtypes | tr : " "`; do
      libname=
      args=
      if test $ot = SHARED; then
        # Can't generally use ar to make a shared library.
        old_ar="$cmdname $1"
        shift
        ot_args=`eval echo '$'args_SHARED_archive`
        ot_prog=`eval echo '$'prog_SHARED_archive`
        test -z "$ot_prog" && ot_prog=$CC
        cmdname="$ot_prog $ot_args -o"
        $verbose "$0: replaced $old_ar with $cmdname."
      fi
      
      # Now transform remaining arguments (presumably filenames).
      for arg in "$@"; do
        case "$arg" in
          *.l[ao]) # Remove the `l' from a .la or .lo filename.
            newarg=`echo $arg | sed 's/l\(.\)$/\1/'`
            $verbose "$0: transformed arg $arg to $newarg."
            # First .la file is the output library.
            if test -z "$libname" && echo $newarg | grep '\.a$'>/dev/null; then
              if test $ot = SHARED; then
                $verbose "$0: running $0 version $newarg."
                verstring=`$0 version $newarg`
                $verbose "$0: got version $verstring."
                libname=`echo $newarg | sed 's/\.a$/\.'$shared_ext$verstring/`
              else
                libname=$newarg
              fi
              if echo $libname | grep / >/dev/null; then
                lib_dir=`echo $libname | sed 's,/[^/]*$,,'`
              else
                lib_dir=.
              fi
              lib_basename=`basename $libname`
              lib_base=`echo $lib_basename | sed 's/[.0-9]*$//'`
              
              # We might have to run a command after making the library.
              post=
              if test $ot = SHARED; then
                # If it supports shared libraries, it supports symlinks.
                # Although this is unnecessary on (e.g.) SunOS, it
                # doesn't hurt, and it is necessary on (e.g.) Solaris.
                post="&& rm -f $lib_base && ln -s $lib_basename $lib_base"
              elif test $ot = STATIC; then
                ranlib=`eval echo '$'${ot}_ranlib`
                $verbose "${ot}_ranlib = $ranlib."
                test -n "$ranlib" && post="&& $ranlib $lib_basename"
              fi

              $verbose "$0: output library dir = $lib_dir."
              $verbose "$0: output library basename = $lib_basename."
              $verbose "$0: output library base = $lib_base."
              newarg=$lib_basename
            fi
            arg=$newarg
            ;;
        esac
        args="$args $arg"
      done
      if test -z "$libname"; then
        # FIXME: should check that the .la file was second arg.
        echo "$0 archive: .la (libtool archive) argument missing." >&2
        exit 1
      fi
      
      # Remove old archive file because we recommend `ar q', not `r',
      # and the user can't necessarily know the library name.  We remove
      # both $lib_base and $lib_base* because on MS-DOS the latter may not
      # match the former.
      cmd="cd $lib_dir/$ot && rm -f $lib_base $lib_base* && $cmdname $args $post"
      $show $cmd
      $chicken eval "($cmd)"
      status=$?
      test $status -eq 0 || break
      
      # If making both objtypes, need original arguments next time through.
      if test $ot = SHARED; then
        cmdname=$old_ar
      else
        true # Don't think we failed to make the library just because
             # the last objtype was not SHARED.
      fi
    done # end of objtypes loop for archive mode
    #
    # Create the .la file we claimed that we would.
    test $status -eq 0 && date >./"`echo $libname | sed 's/\.[^/]*$/.la/'`"
    exit $status
    ;;


  # link mode
  link)
    cmdname=$1; shift             # the linker
    # Do links using the first object type.
    linktype=`echo $objtypes | sed 's/:.*//'`
    $verbose "$0: linktype = $linktype."
    if test -z "$linktype"; then
      echo "$0: Impossibly empty linktype?!" >&2
      exit 1
    fi
    #
    # Need to know the output name if we generate the wrapper.
    looking_for_output=false
    real_output_name=
    libpath=
    #
    for arg in "$@"; do
      if $looking_for_output; then
        real_output_name=$arg
        arg=$real_output_name.exe
        looking_for_output=false
      fi
      case "$arg" in
        -o)
          test $linktype = SHARED && looking_for_output=true
          ;;
        *.l[ao]) # Find .la files in the linktype subdir
                 # of the given directory, e.g., if the user says
                 # ../foo/libfoo.la, transform into
                 # ../foo/$linktype/libfoo.{a,so...}.  We do the same for
                 # libtool archive, although it's not as often necessary.
          newarg=`echo $arg | sed -e 's,\([^/]*\)$,'$linktype'/\1,' \
                                  -e 's/l\(.\)$/\1/'`
          if test $linktype = SHARED \
             && echo $newarg | grep '\.a$' >/dev/null; then
            # If shared, transform dir/foo.la into -Ldir -lfoo.
            dir=`echo $newarg | sed 's,/[^/]*$,,'`
            lib=`echo $newarg | sed -e 's,.*/\([^/]*\),\1,' \
                                    -e 's/^lib//' -e 's/\.a$//'`
            newarg="-L$dir -l$lib"
            # Remember we will need this directory in LD_LIBRARY_PATH.
            if echo $dir | grep -v '^/' >/dev/null; then
              dir=`pwd`/$dir
            fi
            # Maybe had previous directories.
            test -n "$libpath" && libpath=$libpath:
            libpath=$libpath$dir
          fi
          $verbose "$0: transformed .la arg $arg to $newarg."
          arg=$newarg
          ;;
        *.lo) # .lo files have no directory stripping or suffix changing.
          newarg=`echo $arg | sed -e 's,\([^/]*\)$,'$linktype'/\1,' \
                                  -e 's/l\(.\)$/\1/'`
          $verbose "$0: transformed .lo arg $arg to $newarg."
          arg=$newarg
          ;;
      esac
      args="$args $arg"
    done
    
    # Set up to generate wrapper shell script if shared.
    if test $linktype = SHARED; then
      if $looking_for_output; then
        echo "$0 link: -o requires an argument." >&2
        exit 1
      fi
      if test -z "$real_output_name"; then
        # If they didn't give -o at all, default to a.out.
        real_output_name=a.out
        args="$args -o $real_output_name.exe"
      fi
    fi
    
    # Do the link.
    cmd="$cmdname $args"
    $show $cmd
    $chicken eval "$cmd"
    
    status=$?
    if test $status -eq 0 && test -n "$real_output_name"; then
      $verbose "$0: creating wrapper $real_output_name."
      # We created the binary, so create the wrapper script.
      # Use as short a temporary suffix as we can to minimize the chance
      # of collisions on deficient systems.
      rm -f ${real_output_name} ${real_output_name}T
      (
        libpath_var_val='$'$libpath_var
        echo "#! /bin/sh"
        echo "# Generated `date` by $0."
        echo "# Do not install this wrapper script."
        echo "# It's only here to enable running $real_output_name"
        echo "# before it's installed.  (Use $0 install-progs to install it.)"
        echo
        echo "if test -z \"$libpath_var_val\"; then"
        echo "  $libpath_var=\"$libpath\""
        echo "else"
        echo "  $libpath_var=\"$libpath:$libpath_var_val\""
        echo "fi"
        echo "export $libpath_var"
        echo
        echo "thisdir=\`echo $""0 | sed 's%/[^/]*$%%'\`"
        echo 'test "x$thisdir" = "x$0" && thisdir=.'
        echo
        echo "exec \$thisdir/$real_output_name.exe \"\$@\""
      ) >${real_output_name}T
      chmod +x ${real_output_name}T
      mv ${real_output_name}T ${real_output_name}
    fi
    exit $status
    ;;


  # install-lib mode: DIR LIBNAME...
  install-lib)
    if test $# -lt 2; then
      echo "$0 install-lib: Need directory and at least one library." >&2
      exit 1
    fi
    libdir=$1; shift
    if test ! -d $libdir; then
      echo "$0 install-lib: $1 not a directory." >&2
      exit 1
    fi
    for arg in "$@"; do # for each library...
      # Always having a directory part simplifies the code below.
      echo $arg | grep / >/dev/null || arg="./$arg"
      for ot in `echo $objtypes | tr : " "`; do # for each object type...
        case $ot in
          SHARED) # needs shared extension and version number.
            verstring=`$0 version $arg`
            libname=`echo $arg | sed 's/\.la$/\.'$shared_ext$verstring/`
            ;;
          STATIC) # just get rid of the `l'.
            libname=`echo $arg | sed 's/l\(.\)$/\1/'`
            ;;
          *)
            echo "$0: Impossible object type $ot!" >&2
            echo "$bug_report" >&2
            exit 1;;
        esac
        # Have to insert the object type directory.
        libname=`echo $libname | sed 's,\(/[^/]*\)$,/'$ot'\1,'`
        lib_basename=`basename $libname`
        $verbose "$0: library name = $libname."
        $verbose "$0: installation name = $libdir/$lib_basename."
        cmd="${INSTALL_DATA-cp} $libname $libdir/$lib_basename"
        #
        if test $ot = SHARED; then
          # Link libfoo.so to libfoo.so.1.2.3.
          lib_base=`echo $lib_basename | sed 's/[.0-9]*$//'`          
          $verbose "$0: linking $libdir/$lib_base to $lib_basename"
          (cd $libdir && rm -f $lib_base && ln -s $lib_basename $lib_base)
        fi
        #
        # Run ldconfig, etc.
        postinstall=`eval echo '$'${ot}_postinstall`
        test -n "$postinstall" && cmd="$cmd && $postinstall"
        $show $cmd
        $chicken eval "$cmd"
      done
    done
    ;;


  # install-prog mode: DIR PROGNAME...
  install-prog)
    if test $# -lt 2; then
      echo "$0 install-prog: Need directory and at least one program." >&2
      exit 1
    fi
    dir=$1; shift
    if test ! -d $dir; then
      echo "$0 install-prog: $1 not a directory." >&2
      exit 1
    fi
    # The program got linked using the first object type, so
    # installation of the program will proceed accordingly.
    linktype=`echo $objtypes | sed 's/:.*//'`
    $verbose "$0: linktype = $linktype."
    if test -z "$linktype"; then
      echo "$0: Impossibly empty linktype?!" >&2
      exit 1
    fi
    if test $linktype = SHARED; then
      # Install the binary, not the wrapper script.
      suffix=.exe
    else
      suffix=
    fi
    for arg in "$@"; do # for each program...
      # FIXME: On SunOS, AIX, and HP-UX, relink to avoid hardwired libdirs.
      cmd="${INSTALL_PROGRAM-cp} $arg$suffix $dir/$arg"
      $show $cmd
      $chicken eval "$cmd"
    done
    ;;


  # version mode
  version)
    if test $# -ne 1; then
      echo "$0 version: Exactly one argument needed, not $# ($*)." >&2
      exit 1
    fi
    # Strip directory name, `lib' prefix, and any .suffix.
    dir=`echo $1 | sed 's,/[^/]*$,,'`
    test "x$dir" = "x$1" && dir=.
    libname=`basename $1 | sed -e 's,^lib,,' -e s',\..*$,,'`
    verfile=$dir/klibtool.version
    if test ! -r $verfile; then
      echo "$0 version: No file $verfile for $libname version info." >&2
      echo "$0 version: Original argument was $1." >&2
      exit 1
    fi
    $verbose "$0: dir = $dir, libname = $libname."
    version=`awk '$1 == "'$libname'" { print "." $2 "." $3 "." $4 }' $verfile`
    $verbose "$0: version for $libname = $version."
    echo $version
    ;;
    
  
  # unknown mode
  *)
    echo "$0: Impossible mode \`$mode'!" >&2
    echo "$bug_report" >&2
    exit 1
    ;;
esac
