#!/usr/bin/env bash
#$Id: mops,v 1.12 2004/10/25 00:55:31 hchen Exp $

# This script assumes the following programs exist under the top MOPS directory
# 1. This script
# bin/mops
# lib/Util.pm
#
# 2. Used by gcc
# bin/cc1
# bin/cc1plus
# bin/as
# bin/collect2
#
# 3. Used by the programs in 2.
# rc/rc1
# src/class
# lib/java-getopt-1.0.9.jar

absolutify () {
    if [ $(expr "$1" : '/.*') = 0 ]; then
        echo `pwd`/"$1"
    else
        echo "$1"
    fi
}

usage() {
    echo "Usage: $progname [-b build_type] [-h] [-l] [-m mfsa]+ [-o output_dir] [-p dir_prefix_replacement] [-r run_level] [-t temp_dir] [-v debug_level] [-- arguments]
  -b <build_type>     Set build type.  Options are: shell rpm cfg rpmcfg
                      shell (the default option): 
                        If the user provides commands in [arguments], then run
                        these commands.  Otherwise, run \$SHELL
                      rpm: the user should provide SRPMs in [arguments]
                      cfg: the user should provide CFGs in [arguments]
                      rpmcfg: the user should provide directories, where SRPM
                        packages have been built, in [arguments]
  -h                  Display help
  -l                  Scan /usr/lib for libraries that contain CFGs
  -m <mfsa>           Specify a property in <mfsa>
  -o <output_dir>     Write traces to <output_dir>
  -p <old_prefix:new_prefix>  Replace directory prefix <old_prefix> with 
                      <new_prefix> in trace files
  -r <run_level>      Set run level.  Options are: cfg trace html
                      Default: html
  -t <temp_dir>       Use <temp_dir> for writing CFGs and building RPM
  -v <debug_devel>    Set debug level.  Options are: error warning info debug.
                      Default: warning

Example: to launch a shell for bulding packages manually
  $progname -m property.mfsa -o outputdir -t tempdir

Example: to build SRPMs
  $progname -m property.mfsa -o outputdir -t tempdir -b rpm -- *.src.rpm

Example: to model check CFGs that have been built
  $progname -m property.mfsa -o outputdir -b cfg -- foo.cfg

Example: to model check CFGs that have been built from SRPMs
  $progname -m property.mfsa -o outputdir -b rpmcfg -- at-xxx lsof-xxx"
}

find_backend()
{
  local cmd
  cmd=$(PATH=/bin:/usr/bin:$PATH which `gcc --print-prog-name=$1` 2>/dev/null)
  if [ $? != 0 ]; then
    cmd=$(PATH=/bin:/usr/bin:$PATH which $2 2>/dev/null)
    if [ $? != 0 ]; then
      echo "Cannot find $1 or $2.  MOPS cannot continue."
      exit 1
    fi
  fi
  echo $cmd
}

# user unconfigurable variables
setup_env() 
{
  # MOPS executable locations
  export MOPS_UI_GEN=$mops_bindir/trace_parse.pl
  export MOPS_CC1=$mops_bindir/cc1-bin
  #export MOPS_COLLECT2=$mops_bindir/collect2-bin
  mops_collect2=$mops_bindir/collect2

  # Real compiler component locations
  export MOPS_GCC_CPP0=$(find_backend cpp0 cpp)
  export MOPS_GCC_CC1=$(find_backend cc1 cc)
  export MOPS_GCC_CC1PLUS=$(find_backend cc1plus c++)
  export MOPS_GCC_AS=$(find_backend as as)
  export MOPS_GCC_COLLECT2=$(find_backend collect2 ld)

  # System utility locations
  export MOPS_AR=`which ar`
  export MOPS_JAVA=`which java`
  export MOPS_OBJCOPY=`which objcopy`
  export MOPS_MV=`which mv`

  # These should not need to be changed
  export MOPS_CLASSPATH=:$mops_classes:$mops_jgetopt
  export GCC_EXEC_PREFIX=$mops_bindir/
  export COMPILER_PATH=$mops_bindir/
  export PATH=$PATH:$mops_bindir

  for cmd in $MOPS_CC1 $mops_collect2 \
             $MOPS_GCC_CPP0 $MOPS_GCC_CC1 $MOPS_GCC_CC1PLUS $MOPS_GCC_AS \
             $MOPS_GCC_COLLECT2 $MOPS_AR $MOPS_JAVA $MOPS_OBJCOPY $MOPS_MV; do
    if ! [ -x $cmd ]; then
      echo $cmd is not executable
      exit 1
    fi
  done

  # Mark setup phase as complete
  export MOPS_SETUP=1
}

print_message()
{
  type=$1
  shift
  echo $type: $progname: $* >&2
}

error()
{
  print_message ERROR $*
}

warn()
{
  print_message WARNING $*
}

info()
{
  print_message INFO $*
}

debug()
{
  print_message DEBUG $*
}

self_test()
{
  return 1
}

# For each argument "foo" to the "-b" option, you should have a 
# build_foo function here
build_shell()
{
  if [ -z "$mops_build_dir" ]; then
    echo "Please specify a build directory using the -t option"
    exit 1
  fi
  MOPS_LOG=$mops_build_dir/log
  MOPS_PROFILE=$mops_build_dir/profile
  MOPS_CFGDIR=$mops_build_dir/CFG
  MOPS_TEMPDIR=$mops_build_dir/temp
  mkdir -p $MOPS_CFGDIR $MOPS_TEMPDIR
  if [ $MOPS_RUN_LEVEL -gt $mops_run_level_cfg ]; then
    if [ -z "$mops_check_dir" ]; then
      echo "Please specify an output directory using the -o option"
      exit 1
    fi
    if [ -z "$MOPS_MFSAS" ]; then
      echo "Please specify an MFSA using the -m option"
      exit 1
    fi
    MOPS_TEXTTRACEDIR=$mops_check_dir/texttrace
    MOPS_HTMLTRACEDIR=$mops_check_dir/HTMLtrace
    mkdir -p $MOPS_TEXTTRACEDIR $MOPS_HTMLTRACEDIR
  fi
  commands=$*
  if [ -z "$commands" ]; then
    commands=$SHELL
  fi
  info "Running $commands"
  eval $commands
}

build_rpm() 
{
  rpms=$*
  if [ -z "$rpms" ]; then
    error "Build type is rpm, but no SRPM is given"
    return 1
  fi
  if [ -z "$mops_build_dir" ]; then
    echo "Please specify a build directory using the -t option"
    exit 1
  fi
  if [ $MOPS_RUN_LEVEL -gt $mops_run_level_cfg ]; then
    if [ -z "$mops_check_dir" ]; then
      echo "Please specify an output directory using the -o option"
      exit 1
    fi
    if [ -z "$MOPS_MFSAS" ]; then
      echo "Please specify an MFSA using the -m option"
      exit 1
    fi
  fi
  rpmbuildCmd=rpmbuild
  rpmbuild --showrc > /dev/null || rpmbuildCmd=rpm
  for i in $rpms; do
    package=$(basename $i ".src.rpm")
    package_build_dir=$mops_build_dir/$package
    MOPS_LOG=$package_build_dir/log
    MOPS_PROFILE=$package_build_dir/profile
    MOPS_CFGDIR=$package_build_dir/CFG
    MOPS_TEMPDIR=$package_build_dir/temp
    mkdir -p $MOPS_CFGDIR $MOPS_TEMPDIR
    info "package start: $package: `date +\"%D %H:%M:%S\"`"
    rpm_dir=$package_build_dir/RPM
    mkdir -p $rpm_dir $rpm_dir/BUILD $rpm_dir/SPECS $rpm_dir/SOURCES
    if [ $MOPS_RUN_LEVEL -gt $mops_run_level_cfg ]; then
      package_check_dir=$mops_check_dir/$package
      MOPS_TEXTTRACEDIR=$package_check_dir/texttrace
      MOPS_HTMLTRACEDIR=$package_check_dir/HTMLtrace
      mkdir -p $MOPS_TEXTTRACEDIR $MOPS_HTMLTRACEDIR
    fi
    extractCmd="rpm --define \"_topdir $rpm_dir\" -i $i"
    debug $extractCmd
    eval $extractCmd
    if [ $? != 0 ]; then
      info "====failed: extraction: Package $package: $extractCmd"
    else
      specs=""
      for j in $rpm_dir/SPECS/*.spec; do
        buildCmd="$rpmbuildCmd --define \"_topdir $rpm_dir\" --nodeps -bc $j"
        debug $buildCmd
        eval $buildCmd
        if [ $? != 0 ]; then
          specs="$specs $j "
        fi
      done
      if [ -n "$specs" ]; then
        info "====failed: build: Package $package: $specs"
      else
        info "====succeeded: Package $package"
      fi
    fi
  done
}

build_cfg()
{
  cfgs=$*
  if [ $MOPS_RUN_LEVEL -le $mops_run_level_cfg ]; then
    echo "'-b cfg' is incompatible with '-r cfg'"
    exit 1
  fi
  if [ -z mops_check_dir ]; then
    echo "Please specify an output dir using the -o option"
    exit 1
  fi
  if [ -z MOPS_MFSAS ]; then
    echo "Please specify an MFSA using the -m option"
    exit 1
  fi
  MOPS_LOG=$mops_check_dir/log
  MOPS_PROFILE=$mops_check_dir/profile
  MOPS_TEMPDIR=$mops_check_dir/temp
  MOPS_TEXTTRACEDIR=$mops_check_dir/texttrace
  MOPS_HTMLTRACEDIR=$mops_check_dir/HTMLtrace
  mkdir -p $MOPS_TEMPDIR $MOPS_TEXTTRACEDIR $MOPS_HTMLTRACEDIR 
  export MOPS_LD_MCFG
  for i in $cfgs; do
    MOPS_LD_MCFG=$(absolutify $i)
    $mops_collect2
  done
}

build_rpmcfg()
{
  build_dirs=$*
  if [ $MOPS_RUN_LEVEL -le $mops_run_level_cfg ]; then
    echo "'-b rpmcfg' is incompatible with '-r cfg'"
    exit 1
  fi
  if [ -z $mops_check_dir ]; then
    echo "Please specify an output dir using the -o option"
    exit 1
  fi
  if [ -z $MOPS_MFSAS ]; then
    echo "Please specify an MFSA using the -m option"
    exit 1
  fi
  export MOPS_LD_MCFG
  for i in $build_dirs; do
    package=$(basename $i)
    package_check_dir=$mops_check_dir/$package
    MOPS_LOG=$package_check_dir/log
    MOPS_PROFILE=$package_check_dir/profile
    MOPS_TEMPDIR=$package_check_dir/temp
    MOPS_TEXTTRACEDIR=$package_check_dir/texttrace
    MOPS_HTMLTRACEDIR=$package_check_dir/HTMLtrace
    mkdir -p $MOPS_TEMPDIR $MOPS_TEXTTRACEDIR $MOPS_HTMLTRACEDIR 
    for j in $i/CFG/*.mcfg; do
      MOPS_LD_MCFG=$(absolutify $j)
      $mops_collect2
    done
  done
}

## If you set nothing else, set these!
mops_bindir=$(absolutify $(dirname $(which $0)))
mops_classes=$mops_bindir/../src/class
mops_jgetopt=$mops_bindir/../lib/java-getopt-1.0.9.jar

progname=mops

# These must be kept consistent with ../lib/Util.pm
mops_verbosity_error=0
mops_verbosity_warn=1
mops_verbosity_info=2
mops_verbosity_debug=3

# These must be kept consistent with ld
mops_run_level_cfg=0
mops_run_level_trace=1
mops_run_level_html=2


# If we're already running in a MOPS subshell, all we should be able to
# do is enable or disable MOPS.
if [ "x$MOPS_SETUP" = x1 ]; then
    # If MOPS is already initialized, we don't want to spawn a new
    # subshell; mops should only be called via . (or source), and this
    # mechanism does not change $0
    if [ "x$(basename $0)" = x$progname ]; then
	echo "MOPS is already initialized."
	echo "Usage: . mops [ enable | disable | status ]"
	echo "(Note the leading . character)"
	exit 1
    fi
    if [ -n "$2" ]; then
	echo "MOPS is already initialized."
	echo "Usage: . mops [ enable | disable | status ]"
	return 1
    fi
    case "$1" in
	enable)
	    MOPS_ENABLE=1
	    echo "MOPS enabled"
	    return 0
	    ;;
	disable)
	    MOPS_ENABLE=0
	    echo "MOPS disabled"
	    return 0
	    ;;
	status)
	    if [ "x$MOPS_ENABLE" = x1 ]; then
		echo "MOPS enabled"
	    else
		echo "MOPS disabled"
	    fi
	    return 0
	    ;;
	*)
	    echo "MOPS is already initialized."
	    echo "Usage: . mops [ enable | disable | status ]"
	    return 1
	    ;;
    esac
fi

if [ $(basename $0) != $progname ]; then
    echo "MOPS is not initialized."
    return 1
fi

# User configurable, non-directory environment variables
build_type="shell"
export MOPS_SCAN_USR_LIB=0
#export MOPS_PRECIOUS=1
export MOPS_RUN_LEVEL=$mops_run_level_html
export MOPS_VERBOSITY=$mops_verbosity_warn
export MOPS_MFSAS
# Directory environment variables will be specified in build_xxx
export MOPS_LOG MOPS_PROFILE MOPS_CFGDIR MOPS_TEMPDIR
export MOPS_TEXTTRACEDIR MOPS_HTMLTRACEDIR MOPS_PREFIXSWAP

args=`getopt b:hlm:o:p:r:t:v: $*`
if [ $? != 0 ]; then
    usage
    exit
fi
set -- $args
while [ $# -gt 0 ]; do
    case "$1" in
	-b)
	    if [ -z "$2" ]; then
		echo "Option -b requires an argument"
		exit 1
	    fi
	    build_type=$2
	    shift
	    ;;
	-h)
	    usage
	    exit 0
	    ;;
	-l)
	    MOPS_SCAN_USR_LIB=1
	    ;;
	-m)
	    if [ -z "$2" ]; then
		echo "Option -m requires an argument"
		exit 1
	    fi
	    MOPS_MFSAS="$MOPS_MFSAS:$(absolutify $2)"
	    shift
	    ;;
	-o)
	    if [ -z "$2" ]; then
		echo "Option -o requires an argument"
		exit 1
	    fi
	    mops_check_dir=$(absolutify $2)
	    shift
	    ;;
# 	-p)
# 	    MOPS_PRECIOUS=1
# 	    ;;
	-p)
	    if [ -z "$2" ]; then
		echo "Option -p requires an argument"
		exit 1
	    fi
	    MOPS_PREFIXSWAP="$MOPS_PREFIXSWAP;$2"
	    shift
	    ;;
	-r)
	    if [ -z "$2" ]; then
		echo "Option -r requires an argument"
		exit 1
	    fi
	    case $2 in
		cfg | c)
		    MOPS_RUN_LEVEL=$mops_run_level_cfg;
		    ;;
		trace | t)
		    MOPS_RUN_LEVEL=$mops_run_level_trace;
		    ;;
		html | h)
		    MOPS_RUN_LEVEL=$mops_run_level_html;
		    ;;
		*)
		  echo "Invalid run level $2"
		  echo "Valid options are: c[fg] t[race] h[tml]"
		  exit 1
		  ;;
	    esac
	    shift;
	    ;;
	-t)
	    if [ -z "$2" ]; then
		echo "Option -t requires an argument"
		exit 1
	    fi
	    mops_build_dir=$(absolutify $2)
	    shift
	    ;;
	-v)
	    if [ -z "$2" ]; then
		echo "Option -v requires an argument"
		exit 1
	    fi
	    case $2 in
		error | e)
		    MOPS_VERBOSITY=$mops_verbosity_error;
		    ;;
		warn | w)
		    MOPS_VERBOSITY=$mops_verbosity_warn;
		    ;;
		info | i)
		    MOPS_VERBOSITY=$mops_verbosity_info;
		    ;;
		debug | d)
		    MOPS_VERBOSITY=$mops_verbosity_debug;
		    ;;
		*)
		    echo "Invalid verbosity level $2"
		    echo "Valid options are: e[rror] w[arn] i[nfo] d[ebug]"
		    exit 1
		    ;;
	    esac
	    shift
	    ;;
	--)
	    break
	    ;;
	*)
	    echo "Invalid option $1"
	    usage
	    exit 1
	    ;;
    esac
    shift
done

shift
arguments=$*
# Remove leading :
MOPS_MFSAS=${MOPS_MFSAS#:}
MOPS_PREFIXSWAP=${MOPS_PREFIXSWAP#;}

# if [ -z "$mops_build_dir" ] && [ $build_type != "cfg" ]; then
#     echo "Please specify an output directory using the -o option"
#     usage
#     exit 1
# fi

# if [ $MOPS_RUN_LEVEL -ge $mops_run_level_trace ]; then
#   if [ -z "$MOPS_MFSAS" ]; then
#     echo "Please specify MFSAs using the -m option"
#     usage
#     exit 1
#   fi
# fi

setup_env

eval "
mops_enable() { 
    . ${mops_bindir}/${progname} enable
}
"
eval "
mops_disable() {
    . ${mops_bindir}/${progname} disable
}
"
eval "
mops_status() {
    . ${mops_bindir}/${progname} status
}
"

# This requires bash... ugh
export -f mops_enable mops_disable mops_status

export MOPS_ENABLE=1

self_test

shopt -s nullglob

case $build_type in
  shell | SHELL)
    build_shell $arguments
    ;;
  rpm | RPM)
    build_rpm $arguments
    ;;
  cfg | CFG)
    build_cfg $arguments
    ;;
  rpmcfg | RPMCFG)
    build_rpmcfg $arguments
    ;;
  *)
    echo "Invalid build type $build_type.  Valid options are: shell rpm cfg rpmcfg"
    exit 1
  ;;
esac

# if [ "x$MOPS_PRECIOUS" = x1 ]; then
#     echo "==> MOPS temporary files left in $MOPS_TEMPDIR"
# else
#     echo "==> Cleaning up MOPS temporary files"
#     rm -rf $MOPS_TEMPDIR
# fi
