#!/bin/sh
#
# ${R_HOME}/bin/build

revision='$Revision: 1.20.2.1 $'
version=`set - ${revision}; echo ${2}`
version="R package builder ${version}

Copyright (C) 2000 R Development Core Team.
There is NO warranty.  You may redistribute this software under the
terms of the GNU General Public License.
For more information about these matters, see the files named COPYING."

usage="Usage: R CMD build [options] pkgdirs

Build R packages from package sources in the directories specified by
pkgdirs.  A variety of diagnostic checks and cleanups are performed
prior to building the packages.

If necessary for passing the checks, use the \`--vsize' and \`--nsize'
options to increase R's memory (\`--vanilla' is used by default).

Options:
  -d, --debug		turn on shell debugging (set -x)
  -h, --help		print short help message and exit
  -v, --version		print version info and exit
  --vsize=N		set R's vector heap size to N bytes
  --nsize=N		set R's number of cons cells to N
  --force		force overwriting of (index) files

Email bug reports to <r-bugs@lists.r-project.org>."

R_opts="--vanilla"
debug=false
force=false
makevars=false
pkgs=

## Parse argument command line
while test -n "${1}"; do
  case ${1} in
    -h|--help)
      echo "${usage}"; exit 0 ;;
    -v|--version)
      echo "${version}"; exit 0 ;;
    -d|--debug)
      debug=true ;;
    --makevars)
      makevars=true ;;
    --force)
      force=true ;;
    --nsize=*)
      R_opts="{R_opts} ${1}" ;;
    --vsize=*)
      R_opts="{R_opts} ${1}" ;;
    *)
      pkgs="${pkgs} ${1}" ;;
  esac
  shift
done

start_dir=`pwd`
stars="*"

## Determine whether echo can suppress newlines.
if echo "testing\c" | grep c >/dev/null; then
  if echo -n "testing" | sed s/-n/xn/ | grep xn >/dev/null; then
    ECHO_N= ECHO_C= ECHO_T='	'
  else
    ECHO_N=-n ECHO_C= ECHO_T=
  fi
else
  ECHO_N= ECHO_C='\c' ECHO_T=
fi

## A few useful output functions
checking () {
  if ${debug}; then
    echo "${stars} checking $@ ..."
  else
    echo ${ECHO_N} "${stars} checking $@ ...${ECHO_C}"
  fi
}
creating () { echo ${ECHO_N} "${stars} creating $@ ...${ECHO_C}"; }
message () { echo "${stars} $@"; }
result () { echo "${ECHO_T} $@"; }

## Get one field including all continuation lines from a DCF file
get_dcf_field () {
  ws="[ 	]"		# space and tab
  sed -n "/^${1}:/,/^[^ ]/{p;}" ${2} | \
    sed -n "/^${1}:/{s/^${1}:${ws}*//;p;}
            /^${ws}/{s/^${ws}*//;p;}"
}

## For updating `INDEX' and `data/00Index'
updateIndex () {
  oldindex=${1}
  newindex=${TMPDIR:-/tmp}/Rbuild${$}
  ${R_HOME}/bin/Rdindex ${Rdfiles} > ${newindex}
  if diff -b ${newindex} ${oldindex} > /dev/null; then
    result "OK"
  else
    result "NO"
    if ${force}; then
      echo "overwriting \`${oldindex}' as \`--force' was given"
      cp ${newindex} ${oldindex}
    else
      echo "use \`--force' to overwrite the existing \`${oldindex}'"
    fi
  fi
  rm -f ${newindex}
}

## For updating `TITLE'
updateTitle () {
  oldtitle=TITLE
  newtitle=${TMPDIR:-/tmp}/Rbuild${$}
  ${R_HOME}/bin/maketitle > ${newtitle}
  foo=`cat TITLE`
  package=`set - ${foo}; echo ${1}`
  title=`set - ${foo}; shift; echo ${*}`
  if test "${package}" != "${1}"; then
    if ${force}; then
      result "NO"
      echo "overwriting \`${oldtitle} as \`--force' was given"
      cp ${newtitle} ${oldtitle}
    else
      result "ERROR: package names do not match"
      echo "use \`--force' to overwrite the existing \`${oldtitle}'"
      exit 1
    fi
  elif diff -b ${newtitle} ${oldtitle} > /dev/null; then
    result "OK"
  else
    result "NO"
    if ${force}; then
      echo "overwriting \`${oldtitle} as \`--force' was given"
      cp ${newtitle} ${oldtitle}
    else
      echo "use \`--force' to overwrite the existing \`${oldtitle}'"
    fi
  fi
  rm -f ${newtitle}
}

## Initial checks
checkdir () {
  cd ${start_dir}

  checking "package dir"
  if test -d ${1}; then
    dir=`cd ${1}; pwd`
  else
    result "ERROR: package dir \`${1}' does not exist"
    exit 1
  fi
  result "OK"

  ## Check whether `DESCRIPTION' exists and contains required fields
  checking "for \`DESCRIPTION'"
  if test -r ${dir}/DESCRIPTION; then
    :
  else
    result "ERROR: file \`DESCRIPTION' not found"
    exit 1
  fi
  result "OK"
  checking "\`DESCRIPTION' license entry"
  license=`get_dcf_field License ${dir}/DESCRIPTION`
  if test -z "${license}"; then
    result "ERROR: no license entry in \`DESCRIPTION'"
    exit 1
  fi
  result "OK"
  checking "\`DESCRIPTION' version entry"
  foo=`grep "^Version" ${dir}/DESCRIPTION`
  version=`set - ${foo}; echo ${2}`
  if test -z "${version}"; then
    result "ERROR: no version entry in \`DESCRIPTION'"
    exit 1
  fi
  result "OK"
}

## Add src/Makevars.in, acinclude.m4 and configure.in
addmakevars () {
  if test -d src; then
    message "Adding configure scripts to library \$1"
    if test -r src/Makefile; then
      echo "src/Makefile exists, not adding src/Makevars.in, acinclude.m4 and"
      echo "configure.in. Remove src/Makefile and try to move the statements"
      echo "from src/Makefile to src/Makevars.in afterwards."
    else
      backup=false
      if test -r configure.in; then
        mv configure.in configure.in.bak
        echo "BACKUP: moved old configure.in to configure.in.bak"
        backup=true
      fi
      src_files_f=`find src -name \*.f`
      src_files_c=`find src -name \*.c`
      src_files="${src_files_f} ${src_files_c}"
      first_src_file=`echo ${src_files} | awk ' {print $1}'`
      cat >configure.in <<EOF
# package/configure.in

AC_INIT(${first_src_file})  # replace with appropriate source file in src

AM_INIT_AUTOMAKE("${package}",${version})

# find installation path for dependency package "somepack" and test for 
# availability of shared or static library libsomepack.{so,a}
# 
# uncomment and repeat the following line for each package from
# which you want to use compiled functions:
#
#R_FIND_LIB(somepack)

AC_OUTPUT(src/Makevars)

EOF
      echo "configure.in written."
      if test -r acinclude.m4; then
        mv acinclude.m4 acinclude.m4.bak
        echo "BACKUP: moved old acinclude.m4 to acinclude.m4.bak"
        backup=true
      fi
      cat >acinclude.m4 <<EOF
dnl acinclude.m4
dnl
dnl autoconf macro to search for an installed contributed R package
dnl
AC_DEFUN(R_FIND_LIB,[
  AC_CACHE_CHECK([for R library \$1],
  r_library_\$1,[
if test -z "\${R_HOME}" ; then
 R_HOME=\`R RHOME\`
fi

if test -z "\${R_HOME}" ; then
  echo "Cannot find the R distribution!"
  exit 1 
fi

dnl identify PATH where the library is installed (multiple paths possible!)
dnl (one  gets eaten by autoconf, thats why the triple brackets below!)
cat >conftest.R <<EOT
paste.libs<-function(){
  out<-"PKG_LIBPATH_\$1="
  outp<-""
  numfound<-0
  for (i in system.file("libs", pkg="\$1")){
    p<-""
    if(nchar(i)!=0){
      pc<-strsplit(i,"/")[[[1]]]
        if(length(pc)>2){
        for (j in pc[[1:(length(pc)-2)]])
          if(nchar(j)>0) p<-paste(p,j,sep="/")
        numfound<-numfound+1
        if(nchar(outp)>0)
          outp<-paste(outp,p,sep=":")
        else
          outp<-p
      }
    }
  }
  out<-paste(out,outp,sep="")
  cat(paste(out,"\n","export PKG_LIBPATH_\$1\nPKG_NUM=",
      as.character(numfound),"\n","export PKG_NUM\n",sep=""), file="./\$1Libs")
}
paste.libs()
EOT

echo "source(\"conftest.R\")" | \$R_HOME/bin/R --vanilla >/dev/null
rm -f conftest.R

eval "\`cat ./\$1Libs\`"
rm -f ./\$1Libs

if test -z "\${PKG_LIBPATH_\$1}" ; then
 echo "Cannot find the \$1 library for R. Please install it,"
 echo "or set the environment R_LIBS so that it can be found in an R session."
 exit 1;
else
 echo "package found"
fi

if test "\${PKG_NUM}" -gt 1; then
  echo "WARNING: Package $1 found more then once!"
fi

dnl choose the first component from the list PKG_LIBPATH_\$1 which contains
dnl a dynamic or static package library in its ./lib subdirectory 
PKG_R_LIB_\$1_found=false
for i in \`echo \${PKG_LIBPATH_\$1}| sed -e 's/:/ /g'\`; do
dnl should use something like SHLIBEXT instead of .so:
  if test -f \${i}/lib/lib\$1.so; then
    echo "found lib\$1.so at: \${i}/lib"
    PKG_LIBPATH="\${PKG_LIBPATH} -L\${i}/lib"
    PKG_R_LIBS="\${PKG_R_LIBS} -l\$1"
    PKG_R_LIB_\$1_found=true
    break
  else
    if test -f \${i}/lib/lib\$1.a; then
      echo "found lib\$1.a at: \${i}/lib"
      PKG_LIBPATH="\${PKG_LIBPATH} -L\${i}/lib"
      PKG_R_LIBS="\${PKG_R_LIBS} -l\$1"
      PKG_R_LIB_\$1_found=true
      break
    fi
  fi
done
if ! \$PKG_R_LIB_\$1_found; then
  echo "Neither dynamic nor static library for \$1 found."
  echo "You may need to reinstall \$1 with --create-lib-so or --create-lib-a"
  echo "as options to R INSTALL"
  exit 1
fi
AC_SUBST(PKG_LIBPATH)
AC_SUBST(PKG_R_LIBS)
    ]
  )
])
EOF
      echo "acinclude.m4 written."
      if test -r src/Makevars.in; then
        mv src/Makevars.in src/Makevars.in.bak
        echo "BACKUP: moved old src/Makevars.in to src/Makevars.in.bak"
        backup=true
      fi
      cat >src/Makevars.in <<EOF
# src/Makevars.in                                                      

PKG_LIBS=@PKG_LIBPATH@ @PKG_R_LIBS@

EOF
      echo "src/Makevars.in written."
      cat >install-sh <<EOF
#/bin/sh
 echo "dummy install script, edit me if necessary"
EOF
      chmod 755 install-sh 
      echo "install-sh written, to make ./configure happy."
      if test -r configure; then
        mv configure configure.bak
        echo "BACKUP: saved old version of configure to configure.bak"
        backup=true
      fi
      if test -r aclocal.m4; then
        mv aclocal.m4 aclocal.m4.bak
        echo "BACKUP: saved old version of aclocal.m4 to aclocal.m4.bak"
        backup=true
      fi
      if test -r src/Makevars; then
        mv src/Makevars src/Makevars.bak
        echo "BACKUP: saved old version of src/Makevars to src/Makevars.bak"
        backup=true
      fi
      aclocal
      autoconf
      echo "You should now edit configure.in and maybe src/Makevars.in and then"
      echo "rerun autoconf and ./configure (and aclocal if acinclude.m4 has been"
      echo "changed)."
      if $backup; then
         echo ""
         echo "WARNING: Please check the backup files, they will be lost at the next run!"
         echo ""
      fi
    fi
  fi
}

## The work horse
checkpkg () {
  cd ${start_dir}

  if ${is_bundle}; then
    ## If this package is part of a bundle, we need to make sure the
    ## directory exists and contains a `DESCRIPTION.in' file.
    ## Note that this code repeats the first two tests in checkdir().
    checking "package dir"
    if test -d ${1}; then
      dir=`cd ${1}; pwd`
    else
      result "ERROR: package dir \`${1}' does not exist"
      exit 1
    fi
    result "OK"
    description=DESCRIPTION.in
    checking "for \`${description}'"
    if test -r ${dir}/${description}; then
      :
    else
      result "ERROR: file \`${description}' not found"
      exit 1
    fi
    result "OK"
  else
    dir=`cd ${1}; pwd`
    description=DESCRIPTION
  fi

  if [ "${dir}" = "lib" ]; then
    result "ERROR: package name \"lib\" resrved for internal use"
    exit 1
  fi 

  cd ${dir}

  checking "\`${description}' package entry"
  package=`get_dcf_field Package ${description}`
  if test -z "${package}"; then
    result "ERROR: no package entry in \`${description}'"
    exit 1
  fi
  if test "${package}" != `basename ${dir}`; then
    result "ERROR: package field in \`${description}' differs from dir name"
    exit 1
  fi
  result "OK"

  ## Check for title entry and/or `TITLE'
  checking "\`${description}' title entry"
  title=`get_dcf_field Title ${description}`
  if test -z "${title}"; then
    result "WARNING: no title entry in \`${description}'"
  else
    result "OK"
  fi
  checking "for \`TITLE'"
  if test -r TITLE; then
    result "OK"
    if test -n "${title}"; then
      checking "whether \`TITLE' is up-to-date"
      updateTitle "${package}"
    fi
  else
    result "NO"
    if test -n "${title}"; then
      creating "\`TITLE' from \`${description}' title entry"
      ${R_HOME}/bin/maketitle > TITLE
      if test ${?} -ne 0; then
	result "ERROR"
	exit 1
      else
	result "OK"
      fi
    else
      result "ERROR: no \`TITLE' and no \`${description}' title entry"
      exit 1
    fi
  fi

  ## Check R documentation files
  if test -d man; then
    checking "Rd files"
    all_file=${TMPDIR:-/tmp}/Rall${$}
    tag_file=${TMPDIR:-/tmp}/Rtag${$}
    rm -f ${all_file} 2>/dev/null
    LC_ALL=C find man -name "*.[Rr]d" -print | sort > ${all_file}
    Rdfiles=`cat ${all_file}`
    if test -n "${Rdfiles}"; then
      badfiles=`grep "<" ${all_file}`
      if test -n "${badfiles}"; then
	result "ERROR"
	echo "Cannot handle Rd file names containing \`<'."
	echo "These are not legal file names on all R platforms."
	echo "Please rename the following files and try again."
	echo "${badfiles}"
	exit 1
      fi
      any=
      for tag in name alias title description keyword; do
	rm -f ${tag_file} 2>/dev/null
	grep -l "^\\\\${tag}" ${Rdfiles} > ${tag_file} 2>/dev/null
	badfiles=`comm -2 -3 ${all_file} ${tag_file}`
	if test -n "${badfiles}"; then
	  if test -z "${any}"; then
	    result "WARNING:"
	  fi
	  any="${any} ${tag}"
	  echo "Rd files without \`${tag}':"
	  echo "${badfiles}"
	fi
      done
      for tag in name title description usage arguments format details \
	         value references source seealso examples note author  \
	         synopsis; do
	rm -f ${tag_file} 2>/dev/null
	pattern="^\\\\${tag}"
	for f in ${Rdfiles}; do
	  count=`grep ${pattern} ${f} | wc -l`
	  if test ${count} -gt 1; then
	    echo ${f} >> ${tag_file}
	  fi
	done
	if test -f ${tag_file}; then
	  if test -z "${any}"; then
	    result "WARNING:"
	  fi
	  any="${any} ${tag}"
	  echo "Rd files with duplicate \`${tag}':"
	  cat ${tag_file}
	fi
      done
      if test -z "${any}"; then
	result "OK"
      fi
    else
      result "WARNING: no Rd files found"
    fi
    rm -f ${all_file} ${tag_file} 2>/dev/null
  fi

  Rdfiles=`echo "${Rdfiles}" | sed -e '/windows\//d'`

  ## Check for `INDEX'
  checking "for \`INDEX'"
  if test -r INDEX; then
    result "OK"
    if test -n "${Rdfiles}"; then
      checking "whether \`INDEX' is up-to-date"
      updateIndex "INDEX"
    fi
  else
    result "NO"
    if test -n "${Rdfiles}"; then
      creating "\`INDEX' from Rd files"
      ${R_HOME}/bin/Rdindex ${Rdfiles} > INDEX
      if test ${?} -ne 0; then
	result "ERROR"
	exit 1
      else
	result "OK"
      fi
    else
      result "ERROR: no \`INDEX' and no Rd files found."
      exit 1
    fi
  fi

  ## Update `data/00Index'
  if test -d data; then
    if test -n "${Rdfiles}"; then
      Rdfiles=`grep -l "\\\\keyword{datasets}" ${Rdfiles}`
    fi
    checking "for \`data/00Index'"
    if test -r data/00Index; then
      result "OK"
      if test -n "${Rdfiles}"; then
	checking "whether \`data/00Index' is up-to-date"
	updateIndex "data/00Index"
      fi
    else
      result "NO"
      if test -n "${Rdfiles}"; then
	creating "\`data/00Index' from Rd files"
	${R_HOME}/bin/Rdindex ${Rdfiles} > data/00Index
	if test ${?} -ne 0; then
	  result "ERROR"
	  exit 1
	else
	  result "OK"
	fi
      else
	result "ERROR: no \`data/00Index' and no Rd files found."
	exit 1
      fi
    fi
  fi

  ## Check for undocumented objects
  if test -d R -a -d man; then
    checking "for undocumented objects"
    out=`echo "undoc(dir = \"${dir}\")" \
      | ${R_HOME}/bin/R.bin ${R_opts}`
    err=`echo "${out}" | grep "^Error"`
    out=`echo "${out}" | grep "^ *\\["`
    if test -z "${err}"; then
      if test -n "${out}"; then
	result "WARNING:"
	echo "${out}"
      else
	result "OK"
      fi
    else
      err=`echo "${err}" | sed -e 's/^Error *//'`
      result "ERROR:"
      echo "${err}"
      exit 1
    fi
  fi

  ## Add local config stuff
  if ${makevars}; then
    addmakevars
  fi

  ## Clean up `src'
  if test -d src; then
    message "cleaning \`src'"
    if test -r src/Makefile; then
      (cd src && make clean)
    else
      rm -f src/*.o src/*.s[lo]
    fi
  fi

  ## Other cleanups
  if test -x cleanup; then
    message "running \`cleanup'"
    ./cleanup
  fi
}

buildpkg () {
  cd ${start_dir}/${1}
  ## Final cleanups
  for f in .Rdata .Rhistory; do
    junk=`find . -name ${f}`
    if test -n "${junk}"; then
      message "removing left-over \`${f}' files"
      rm -f ${junk}
    fi
  done
  ## And finally build the package (bundle)
  cd ..
  package=`basename ${1}`
  exclude_file=${TMPDIR:-/tmp}/Rbuild${$}
  rm -f ${exclude_file} 2>/dev/null \
    || (echo "cannot create file with exclude patterns" && exit 2)
  find ${package} -type d -name check >> ${exclude_file}
  find ${package} -type d -name '*[Oo]ld' >> ${exclude_file}
  find ${package} -name GNUMakefile >> ${exclude_file}
  find ${package} -name CVS >> ${exclude_file}
  find ${package} -name \*~ >> ${exclude_file}
  message "building \`${start_dir}/${package}_${version}.tar.gz'"
  tar chfX - ${exclude_file} ${package} | \
    gzip -c9 > ${start_dir}/${package}_${version}.tar.gz
  rm -f ${exclude_file}
}

## The main loop
if ${debug}; then set -x; fi
for p in ${pkgs}; do
  echo "Building package \`${p}'"
  checkdir ${p}
  if grep "^Contains" ${p}/DESCRIPTION >/dev/null; then
    echo "Looks like \`${p}' is a package bundle"
    is_bundle=true
    bundlepkgs=`get_dcf_field Contains ${p}/DESCRIPTION`
    for pp in ${bundlepkgs}; do
      message "checking package \`${pp}' in bundle \`${p}'"
      stars="**"
      checkpkg ${p}/${pp}
      stars="*"
    done
  else
    is_bundle=false
    checkpkg ${p}
  fi
  buildpkg ${p}
  echo
done

### Local Variables: ***
### mode: sh ***
### sh-indentation: 2 ***
### End: ***
