s_winmsi.fcn   [plain text]


#	$Id: s_winmsi.fcn,v 1.12 2006/09/14 15:50:51 mjc Exp $
#
# The common functions used by the s_winmsi scripts (both
# for core DB and DB/XML).
#
# This script uses several bash extensions that are convenient
# since we "know" it will always run under Cygwin: shell functions,
# 'return', declaration of 'local' variables, $(command) syntax,
# ${#X} (counting chars), ${X#regexp} (searching) $((expr)) (arithmetic)
#
# These functions use 'global' variables:
#   ERRORLOG             - a filename
#   PRODUCT_NAME         - e.g. "Berkeley DB"
#   PRODUCT_VERSION      - e.g. "4.1.25", derived from dist/RELEASE
#   PRODUCT_MAJOR        - e.g. "4", (for release 4.1.25) from dist/RELEASE
#   PRODUCT_MINOR        - e.g. "1", (for release 4.1.25) from dist/RELEASE
#   PRODUCT_PATCH        - e.g. "25", (for release 4.1.25) from dist/RELEASE
#   PRODUCT_MAJMIN       - e.g. "41", (for release 4.1.25) from dist/RELEASE
#   PRODUCT_STAGE        - the staging directory for temp files and builds
#   PRODUCT_LICENSEDIR   - the tree containing LICENSE and README
#   PRODUCT_SUB_BLDDIR  - top of the subproduct build e.g. "dbxml-2.0.1/dbxml"
#   PRODUCT_BLDDIR       - top of the build tree e.g. "dbxml-2.0.1"
#   PRODUCT_SRCDIR       - the dir we unzip to e.g. "dbxml-2.0.1"
#   PRODUCT_DBBUILDDIR   - where build_unix dir is for Berkeley DB (for Perl)
#   PRODUCT_SHARED_WINMSIDIR   - where the master winmsi directory is
#   PRODUCT_IMAGEDIR     - where the images are (usually winmsi/images)
#   PRODUCT_ZIP_FILEFMT  - what zip file looks like e.g. "db-X.Y.Z.NC.zip"
#   PRODUCT_MSI_FILEFMT  - what msi file looks like e.g. "db-X.Y.Z.NC.msi"
#
# Some of these may seem redundant, but there are options to take
# an already built tree from a different place than where we'll unzip
# to and take our sources from, for example.  This allows a lot of flexibility
# for development and debugging (especially when these trees can be huge).

# This is the magic tag that creates a new unique GUID in Wix.
# GUIDs are needed on every <Component ... > entry to ensure
# that the component can be uninstalled.
GENGUID='Guid="GUID_CREATE_UNIQUE()"'
PERSISTGUID='Guid="WIX_DB_PERSISTENT_GUID()"'

# MakeRtf()
# Standard input is plain text, standard output is RTF.
#
MakeRtf() {
    temp1=/tmp/sbm$$a
    cat > $temp1


# Courier is a good font, but the lines with all caps
# overflows our current dialog size:
#     {\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}
#     \viewkind4\uc1\pard\lang1033\f0\fs16 
#
# Using Small fonts works:
#      {\rtf1\ansi\deff0{\fonttbl{\f0\fswiss\fprq2\fcharset0 Small Fonts;}}
#      {\colortbl ;\red0\green0\blue0;}
#      \viewkind4\uc1\pard\cf1\lang1033\f0\fs14 

# Arial is the best compromise:
    sed -e 's/^ *//' << 'EndOfRTFHeader'
      {\rtf1\ansi\deff0{\fonttbl{\f0\fswiss\fprq2\fcharset0 Arial;}}
      {\colortbl ;\red0\green0\blue0;}
      \viewkind4\uc1\pard\cf1\lang1033\f0\fs16 
EndOfRTFHeader

# Embedded '<' and '>' can cause problems for Wix
    sed -e 's:$:\\par:' -e 's:<: \\lquote :' -e 's:>: \\rquote :' < $temp1
    echo -n '}'
    rm -f $temp1
}

# NextId()
# Get the next available unique id, a simple integer counter.
# We use a file, rather than a shell variable to track the
# number, because this is called from subshells at various
# points, and they cannot affect the variables in the parent shell.
#
ComponentID=component.id
NextId()
{
    local id=`cat $ComponentID 2>/dev/null`
    if [ "$id" = '' ]; then
       id=0
    fi
    id=$(($id + 1))
    echo "$id" > $ComponentID
    echo "$id"
}

# CleanFileName(FILENAME)
# Removes any strange characters in file names,
# returning the new name on standard output.
CleanFileName()
{
    echo "$1" | sed -e 's/[-%@!]//g'
}

# GetShortName(FILENAME)
# Get a Windows short name for the file,
# to fit into the 8.3 name space.
# This is not a great algorithm, but it works.
# The fact is, the names must be unique, but on
# Win2000 and WinXP, we'll never see them.

ShortID=short.id
GetShortName()
{
    local name=`echo "$1" | tr '[a-z]' '[A-Z]'`

    # See if the name fits into 8.3. If so,
    # return it right away.
    #
    case "$name" in
    ?????????*.* )  ;;
    *.????* )   ;;
    *.*.* )    ;;
    *[-%@!]* )    ;;
    *.* ) echo "$name"
          return
          ;;
    * )
         if [ "${#1}" -le 8 ]; then
             echo "$name"
             return
         fi
         ;;
    esac

    # From NAMEISLONG.EXTLONG, build a name
    # like NAME~ZZZ.EXT, where ZZZ is a unique (hex)
    # number we build.  This is 

    local id=`cat $ShortID 2>/dev/null`
    if [ "$id" = '' ]; then
       id=0
    fi
    id=$(($id + 1))
    echo "$id" > $ShortID
    if [ "$id" -ge 4096 ]; then
        echo "BADBADBAD.TXT"  # return something that will give an error
        Error "ShortId overflow"
        exit 1
    fi

    # Convert the id to hex (I ran out of space using decimal)
    # This is too slow: id=`echo 16 o $id p | dc`
    id=`printf "%x" $id`

    # Collect and clean up the part of the name before, and after, the dot
    local before=`CleanFileName "$name" | sed -e 's/^\([^.]*\)[.].*$/\1/'`
    local after=`CleanFileName "$name" | sed -e 's/^[^.]*[.]\(.*\)$/\1/'`

    # Make sure the before part fits in 5 chars (not 8, since
    # we need a few for the unique number).
    if [ "${#before}" -gt 5 ]; then
        before=`echo "$before" | sed -e 's/^\(.....\).*/\1/'`
    fi
    if [ "${#after}" -gt 3 ]; then
        after=`echo "$after" | sed -e 's/^\(...\).*/\1/'`
    fi
    echo "${before}~${id}.${after}"
}

# Progress([OPTION,]STRING...)
# Show a major processing step via echo to stdout and to the error log.
# An OPTION is "-minor", indicating no big banner.
#
Progress()
{
    if [ "$1" = -minor ]; then
       shift
    else
       echo "" >> $ERRORLOG
       echo "============================" >> $ERRORLOG
    fi
    echo "$@" >> $ERRORLOG
    echo "$@" >&15
}

# Error(STRING...)
# Show an error in a standard way.
#
Error()
{
    echo "" >> $ERRORLOG
    echo "****************** FAIL ******************" >> $ERRORLOG
    echo "ERROR: $@" >> $ERRORLOG
    echo "ERROR: $@" >&15
    echo "See $ERRORLOG for details" >&15
    return 1
}

# RequireFileInPath(NAME, PATHVAL, FILE)
# Look for FILE in the path that has value PATHVAL.
# The path's name is NAME if it needs to be shown.
#
RequireFileInPath()
{
    local type="$1"
    local origpath="$2"
    local file="$3"
    local upath="$origpath"
    if [ "$1" != PATH ]; then
       upath=`cygpath -up "$origpath"`
    fi

    SAVEIFS="$IFS"
    IFS=":"
    found=no
    for dir in $upath; do
        if [ -f "$dir/$file" ]; then
            IFS="$SAVEIFS"
            return
        fi
    done
    IFS="$SAVEIFS"
    Error "File $file not found in $type path: $origpath"
    exit 1
}

# Rand4X()
# Return 4 random hex digits on output
#
Rand4X() {
    # The sed command pads the front with 0's as needed
    (echo 'obase=16'; echo $RANDOM ) | bc |
        sed -e 's/^/0000/' -e 's/^.*\(....\)$/\1/'
    
}

# RunM4()
# Run M4, making appropriate substitutions.
# This function uses GLOBAL variables: PRODUCT_VERSION (e.g. "4.1.25")
# and PRODUCT_LICENSEDIR, which is where certain text files are found
#
RunM4() {

    # Given a version number, like 2.3.45, we want to
    # create a 8 character name for the directory like db2_3_45.
    # This name is under a "Oracle" directory,
    # so it only needs to be unique within the universe of BDB versions.
    # TODO: instead of using a version number like $DB_VERSION,
    # maybe use $DB_VERSION_UNIQUE_NAME which looks like "_2003"

    local DB_8CHAR_VERSION=`echo $PRODUCT_VERSION | sed -e 's/[.]/_/g'`
    if [ ${#DB_8CHAR_VERSION} -le 6 ]; then
       DB_8CHAR_VERSION="db$DB_8CHAR_VERSION"
    elif [ ${#DB_8CHAR_VERSION} -le 7 ]; then
       DB_8CHAR_VERSION="d$DB_8CHAR_VERSION"
    else
       Error "Version number too large for simple version number algorithm"
       exit 1
    fi

    # Remove leading ./ from PRODUCT_LICENSEDIR if present.
    local licensedir=`cygpath -w "$PRODUCT_LICENSEDIR"`

    # Create a GUID prefix of the form: ????????-????-????-????-????
    # This leaves 8 digits of GUID to be manipulated by m4.
    local GUID_PREFIX="`Rand4X``Rand4X`-`Rand4X`-`Rand4X`-`Rand4X`-`Rand4X`"

    # -P requires that all m4 macros, like define, eval, etc.
    # are prefixed, like m4_define, m4_eval, etc.  This avoids
    # various name conflicts with input files.
    # TODO: rename DB_SRCDIR as DB_LICENSEDIR
    m4 -P \
       -DWIX_DB_VERSION="$PRODUCT_VERSION" \
       -DWIX_DB_8CHAR_VERSION="$DB_8CHAR_VERSION" \
       -DWIX_DB_GUID_PREFIX="$GUID_PREFIX" \
       -DWIX_DB_PRODUCT_NAME="$PRODUCT_NAME" \
       -DWIX_DB_SRCDIR="$licensedir" \
       -DWIX_DB_TOP="`cygpath -w $PRODUCT_BLDDIR`" \
       -DWIX_DB_SHARED_WINMSIDIR="$PRODUCT_SHARED_WINMSIDIR" \
       -DWIX_DB_IMAGEDIR="`cygpath -w $PRODUCT_IMAGEDIR`" \
       -DWIX_DB_FEATURE_STRUCTURE="m4_include(features.wixinc)" \
       -DWIX_DB_DIRECTORY_STRUCTURE="m4_include(directory.wixinc)" \
       -DWIX_DB_LINKS="m4_include(links.wixinc)" \
       -DWIX_DB_LICENSE_RTF="m4_include(license.rtf)" \
       -DWIX_DB_ENV_FEATURE_PROPS="m4_include(envprops.wixinc)" \
       -DWIX_DB_ENV_FEATURE_SET="m4_include(envset.wixinc)" \
       -DWIX_DB_ENV_FEATURE_SHOW="m4_include(envshow.wixinc)"
}

# RunTallow(DIR, OPTIONS)
# Run Tallow, a tool from the WiX distribution
RunTallow() {
    local dir="$1"
    shift

    Id1=`NextId`
    Id2=`NextId`
    Id3=`NextId`

    # Tallow is a tool that walks a tree, producing
    # a WiX directory heirarchy naming the files.
    # The IDs it produces are not unique (between tallow
    # runs), so we must make them so here.  Thus "directory78"
    # becomes "MyFeatureName.123.78" where 123 is an id from NextId.
    # Secondly, instead of using the tallow output as a separately
    # compiled fragment, we want to include it directly, so
    # we need to strip out some extraneous XML entries at the top
    # and bottom of its output.
    #
    # Another thing we do is when we see <Directory></Directory>
    # pairs, we call m4 macros WIX_DB_{BEGIN,END}_SUBDIR because
    # we need to track the current directory to generate 'persistent'
    # GUIDs.  See the discussion about GUIDs in dbwix.m4 .
    #
    # !!! For stripping out the extraneous XML, we rely heavily
    # !!! on the output format, so this is likely to be fragile
    # !!! between versions of tallow.  Fortunately, it should fail hard.
    #
    echo "=============" >> tallow.log
    echo tallow -nologo -d `cygpath -w "$dir"` "$@" >> tallow.log
    echo "  <!-- TALLOW output begins here -->"
         tallow -nologo -d `cygpath -w "$dir"` "$@" > tallow.out || exit 1
    cat tallow.out >> tallow.log
    echo "-------------" >> tallow.log

    sed -e '1,/<DirectoryRef/d' -e '/<\/DirectoryRef/,$d' \
        -e "s/Id=\"directory/Id=\"$feature.$Id1./" \
        -e "s/Id=\"component/Id=\"$feature.$Id2./" \
        -e "s/Id=\"file/Id=\"$feature.$Id3./" \
        -e '/^      <Directory/d' \
        -e '/^      <\/Directory/d' \
        -e '/<Directory/s/Name=\"\([^"]*\)"/Name="\1" WIX_DB_BEGIN_SUBDIR(\1) /' \
        -e '/<\/Directory>/s/$/ WIX_DB_END_SUBDIR()/' \
        -e "/<Component/s/>/ $PERSISTGUID>/" \
        < tallow.out > tallow.postsed || exit 1

    echo 'WIX_DB_SET_CURFILE()'
    echo 'WIX_DB_CLEAR_SUBDIR()'
    cat tallow.postsed
    echo 'WIX_DB_CLEAR_SUBDIR()'

    cat tallow.postsed >> tallow.log
    echo "  <!-- TALLOW output ends here -->"
}

# ProcessFeatures(INFILES, INFEATURES, INENV, OUTDIRECTORIES, OUTFEATURES,
#                 OUTSET)
# Use the files.in and features.in files as
# input to create two output files, one containing a WiX XML
# fragment showing directories and needed files,
# and another containing a WiX XML fragment showing
# the features in a dependency tree.
#
# This creates the heart of the installer flexibility.
#
ProcessFeatures() {
   InFiles="infiles.tmp";  CleanInputFile "$1" "$InFiles" 3 4
   InFeatures="infeatures.tmp"; CleanInputFile "$2" "$InFeatures" 3 4
   InEnv="inenv.tmp"; CleanInputFile "$3" "$InEnv" 3 4
   OutDirs="$4"
   OutFeatures="$5"
   OutSet="$6"

   rm -f $OutDirs; touch $OutDirs
   rm -f $OutFeatures; touch $OutFeatures

   # Initialize the feature list.
   # This will be expanded (per feature) in ProcessOneFeature
   #
   XmlLevel=4
   Xecho "<Publish Property=\"FeatureList\" Value=\"[NULL]\">" >> $OutSet
   Xecho "  <![CDATA[1]]></Publish>" >> $OutSet

   Dirs=`cut -f 3 < $InFiles | sort | uniq`
   Prevdir="/"
   ProcessDirTransition "$Prevdir" "/" >> $OutDirs

   for Dir in $Dirs; do
      ProcessDirTransition "$Prevdir" "$Dir" >> $OutDirs
      ProcessOneDirectory "$Dir" < $InFiles >> $OutDirs || exit 1
      Prevdir="$Dir"
   done
   ProcessDirTransition "$Prevdir" "/" >> $OutDirs

   cat $InEnv | (
      read line
      while [ "$line" != '' ]; do
         local FeatureName=`echo "$line" | cut -f 1`
         local EnvVariable=`echo "$line" | cut -f 2`
         local EnvValue=`echo "$line" | cut -f 3`
         local EnvOption=`echo "$line" | cut -f 4`
         ProcessOneEnv "$FeatureName" "$EnvVariable" "$EnvValue" "$EnvOption" "$OutDirs" "$OutSet"
         read line
      done
      return 0
   ) || Error "Error processing environment" || exit 1

   cat $InFeatures | (
      read line
      while [ "$line" != '' ]; do
         local FeaturePath=`echo "$line" | cut -f 1`
         local ShortName=`echo "$line" | cut -f 2 | StripDoubleQuotes`
         local Description=`echo "$line" | cut -f 3 | StripDoubleQuotes`
         local FeatureOptions=`echo "$line" | cut -f 4 | StripDoubleQuotes`
         ProcessOneFeature "$FeaturePath" "$ShortName" "$Description" "$FeatureOptions" "$OutDirs" "$OutFeatures" "$OutSet"
         read line
      done
      return 0
   ) || Error "Error processing features" || exit 1

# (PBR)
# This test code didn't work. My hope was that I could force INSTALLLEVEL
# to 4 and this would then enable the debug features.
#   Xecho "<Publish Property=\"INSTALLLEVEL\" Value=\"4\" />" >> $OutSet
#   Xecho "<Publish Event=\"SetInstallLevel\" Value=\"4\" />" >> $OutSet

}

# ProcessLinks(INLINKS, OUTFEATURES)
# Process the INLINKS file, and produce XML on stdout.
# Each line of the input file requires the creation
# of a '.URL' file in the installation, and a Shortcut
# in the Windows menu to point to that.
# Also add the components generated to a feature, put in OUTFEATURES.
#
# TODO: We ought to have a Features column in the links.in file,
# otherwise, the local doc link is always installed.
#
ProcessLinks() {
   # Set a var to a carriage return without actually putting one in this file
   local CR=`echo A | tr A '\015'`
   local InLinks="infiles.tmp";  CleanInputFile "$1" "$InLinks" 3 4
   local here_win=`cygpath -w $(pwd)`
   # TODO: maybe get a real modification time, but not sure why we need it.
   local MODTIMEHEX="0000000007DCC301DE"
   XmlLevel=6
   local OutFeatures="$2"

   Xecho + "<Feature Id=\"LinksFeature\" Title=\"Links\"" >> $OutFeatures
   Xecho "   Description=\"Links\" Display=\"hidden\"" >> $OutFeatures
   Xecho "   Level=\"1\" AllowAdvertise=\"no\"" >> $OutFeatures
   Xecho "   ConfigurableDirectory=\"INSTALLUTIL\"" >> $$OUTFeatures
   Xecho "   Absent=\"disallow\">" >> $OutFeatures

   Xecho "<DirectoryRef Id=\"INSTALLUTIL\">"
   Xecho " <Directory Id=\"INSTALLURL\" Name=\"url\">"
   Xecho "WIX_DB_SET_CURDIR(/installutil/url)"
   cat $InLinks | (
      read line
      while [ "$line" != '' ]; do
         local Shortname=`echo "$line" | cut -f 1 | StripDoubleQuotes`
         local Name=`echo "$line" | cut -f 2 | StripDoubleQuotes`
         local Url=`echo "$line" | cut -f 3 | StripDoubleQuotes`
         read line

         # We register the name .bdbsc extension to get the proper icon
         local UrlName="$Shortname.bdbsc"
         local UrlShortName="$Shortname.d1b"
         local TargetFile="[INSTALLDIR]\\installutil\\url\\$UrlName"
         local CreateUrlFile=true
         local CommandShortcut=false
	 local Program=""
	 case "$Url" in
         file:* ) CreateUrlFile=false
                  TargetFile=`echo $Url | sed -e 's/file://'`
                  TargetFile="[INSTALLDIR]"`cygpath -w $TargetFile`;;
         cmd:* )  CreateUrlFile=false
		  UrlName="$Shortname.bat"
                  UrlShortName="$Shortname.bat"
	          TargetFile="[INSTALLDIR]\\installutil\\url\\$UrlName"
                  Program=`echo $Url | sed -e 's/cmd://'`
                  CommandShortcut=true;;
         esac

         Xecho "WIX_DB_SET_CURFILE($Shortname)"
         Xecho + "<Component Id=\"Links.$Shortname\""
         Xecho "   $PERSISTGUID"
         Xecho "   SharedDllRefCount=\"yes\" Location=\"either\">"

         if $CreateUrlFile; then
            echo "[Default]$CR"                 > $UrlName
            echo "BASEURL=$Url$CR" | RunM4     >> $UrlName   || exit 1
            echo "[InternetShortcut]$CR"       >> $UrlName
            echo "URL=$Url$CR"     | RunM4     >> $UrlName   || exit 1
            echo "Modified=$MODTIMEHEX$CR"     >> $UrlName
            # TODO: we could have an Entry for IconFile=oracleweb.ico IconIndex=1?
            echo ''
            Xecho "<File Id=\"File.$Shortname\" "
            Xecho "   LongName=\"$UrlName\" Name=\"$UrlShortName\""
            Xecho "   Compressed=\"yes\" DiskId=\"1\""
            Xecho "   src=\"$here_win\\$UrlName\" />"
         fi

         if $CommandShortcut; then
	   echo "@echo off"                               > $UrlName
           echo "set DBROOTDIR="                         >> $UrlName
           echo "for /F \"tokens=3 delims=	\" %%A in ('REG QUERY \"HKLM\\SOFTWARE\\Oracle\\$PRODUCT_NAME\\$PRODUCT_VERSION\" /v RootDirectory') do set DBROOTDIR=%%A"                                   >> $UrlName
           echo "if ERRORLEVEL 2 goto MISSING"           >> $UrlName
           echo "if not defined DBROOTDIR goto MISSING"  >> $UrlName
           echo "set FN=\"%DBROOTDIR%$Program\""         >> $UrlName
           echo "if not exist %FN% goto NOTFOUND"        >> $UrlName
           echo "cmd /k \"%DBROOTDIR%$Program\"$CR"      >> $UrlName
           echo "goto END"                               >> $UrlName
           echo ":NOTFOUND"                              >> $UrlName
           echo "echo"                                   >> $UrlName
           echo "echo  Error: The program does not appear to be installed." >> $UrlName
           echo "echo"                                   >> $UrlName
           echo "cmd /k"                                 >> $UrlName
           echo "goto END"                               >> $UrlName
           echo ":MISSING"                               >> $UrlName
           echo "echo"                                   >> $UrlName
           echo "echo NOTE:"                             >> $UrlName
           echo "echo   The $PRODUCT_NAME version could not be determined." >> $UrlName
           echo "echo   If you are running on Windows 2000, make sure the" >> $UrlName
           echo "echo   REG.EXE program is installed from the Tools disk" >> $UrlName
           echo "echo"                                   >> $UrlName
           echo "cmd /k"                                 >> $UrlName
           echo ":END"                                   >> $UrlName

           Xecho "<File Id=\"File.$Shortname\" "
           Xecho "   LongName=\"$UrlName\" Name=\"$UrlShortName\""
           Xecho "   Compressed=\"yes\" DiskId=\"1\""
           Xecho "   src=\"$here_win\\$UrlName\" />"

           Xecho "<Shortcut Id=\"Short.$Shortname\" Directory=\"BerkeleyDbMenu\""
           Xecho "   Name=\"$Shortname\" LongName=\"$Name\""
	   Xecho "   WorkingDirectory=\"[INSTALLDIR]\""
           Xecho "   Target='$TargetFile'"
           Xecho "   Show=\"normal\" />"
         else
           Xecho "<Shortcut Id=\"Short.$Shortname\" Directory=\"BerkeleyDbMenu\""
           Xecho "   Name=\"$Shortname\" LongName=\"$Name\""
           Xecho "   Target='$TargetFile'"
           Xecho "   Show=\"normal\" />"
         fi
     

         Xecho - "</Component>"

         Xecho "<ComponentRef Id=\"Links.$Shortname\" />" >> $OutFeatures
      done
      return 0
   ) || Error "Error processing links" || exit 1

   Xecho "</Directory>"
   Xecho "</DirectoryRef>"
   Xecho - "</Feature>"  >> $OutFeatures
}

# ProcessOneDirectory(DIRECTORYNAME)
# Called by ProcessFeatures.
# Argument is the directory name to process
# Standard input is cleaned up files.in (dirname is 3rd column)
# Standard output will be WiX XML Component/File entries
#
ProcessOneDirectory()
{
  Dir="$1"
  grep "	${Dir}	" | (
    read line
    while [ "$line" != '' ]; do
      local feature=`echo "$line" | cut -f 1`
      local srcfile=`echo "$line" | cut -f 2`
      local targetdir=`echo "$line" | cut -f 3`
      local shortname=`echo "$line" | cut -f 4`

      ProcessOneDirectoryFile "$feature" "$srcfile" "$targetdir" "$shortname" || exit 1
      read line
    done
    return 0
  ) || Error "Error processing directory $Dir" || exit 1
}

# ProcessOneDirectoryFile(DIRECTORYNAME)
# Called by ProcessOneDirectory to process a single file in a directory.
# Standard output will be a single WiX XML Component/File entries
#
ProcessOneDirectoryFile()
{
   local feature="$1"
   local srcfile="$2"
   local targetdir="$3"
   local shortname="$4"
   local base=`basename $srcfile`

   #echo "processing file $srcfile in $feature to directory $targetdir..." >&2

   # Prepend the WIX_DB_TOP unless the source file is absolute

   local root=
   local checkfile=
   local wsrcfile=

   case "$srcfile" in
   /* )  root=""
         wsrcfile=`cygpath -w $srcfile`
         checkfile="$srcfile"
         ;;
   * )   root="$PRODUCT_BLDDIR/"
         wsrcfile="WIX_DB_TOP()\\`cygpath -w $srcfile`"
         checkfile="$PRODUCT_BLDDIR/$srcfile"
         ;;
   esac

   # If srcfile ends in / then we'll use tallow to walk the directory
   case "$srcfile" in
    */ ) if [ ! -d "$root$srcfile" ]; then
            Error "$root$srcfile: not a directory"
            exit 1
         fi
         Progress -minor "  expanding $root$srcfile..."
         RunTallow "$root$srcfile"
         return 0
    ;;
    *'*'* )
         local dirname=`dirname "$root$srcfile"`
         RunTallow "$dirname" -df "$base"
         return 0
    ;;
   esac

   if [ "$shortname" = '' ]; then
     shortname=`GetShortName "$base"`
   fi
   ID=`NextId`

   if [ ! -r "$checkfile" ]; then
     Error "$srcfile: file in feature $feature does not exist"
     Error "  curdir=`pwd`, pathname=$checkfile"
     exit 1
   fi

   Xecho "WIX_DB_SET_CURFILE(${base})"
   Xecho + "<Component Id=\"$feature.$ID\" Location=\"either\" $PERSISTGUID>"
   Xecho "<File Id=\"${feature}.${ID}\" Name=\"${shortname}\" LongName=\"${base}\" Compressed=\"yes\" KeyPath=\"yes\" DiskId=\"1\" src=\"${wsrcfile}\" />"
   Xecho - "</Component>"
   return 0
}

# ProcessOneFeature(FEATUREPATH, SHORTNAME, DESCRIPTION, OPTS, INDIRECTORYFILE,
#                   OUTFEATURE, OUTSET)
# Called by ProcessFeatures to process a line in the features.in file.
# The first three arguments are the values of the first three columns:
# the feature dependency path (e.g. "Java/JavaExamples"), the short
# name, and a descriptive name.  The last argument is the directory
# file that lists the components.  We use the last name of the feature
# path (e.g. JavaExamples) to locate all components (really directories)
# named accordingly, so they can be listed as needed parts of the Feature.
# Standard output will be WiX XML Feature entries.
#
ProcessOneFeature() {
      local featurename="$1"
      local shortname="$2"
      local opts="$4"
      local dirfile="$5"
      local outfeature="$6"
      local outset="$7"

      XmlLevel=4
      local featcount=0
      local featurestring=""
      if [ $(SlashCount $featurename) -gt 0 ]; then
         local parent=`echo $featurename | sed -e 's:/[^/]*$::' -e 's:.*/::'`
         featurename=`echo $featurename | sed -e 's:^.*/::'`
         featcount=1
         Xecho "<FeatureRef Id=\"$parent\">" >> $outfeature
      fi


      # TODO: how to get +default to work?
      #    have tried messing with level="0" (doesn't show it)
      #    InstallDefault=\"source\" (doesn't make a difference)
      #
      local leveldebug="Level=\"4\""
      local levelparam="Level=\"1\""
      local leveldisable="Level=\"0\""
      local defparam="InstallDefault=\"source\""
      local displayparam="Display=\"expand\""
      local params="AllowAdvertise=\"no\""
      local regfeature=""

      local descparam=""
      if [ "$3" != '' ]; then
         descparam="Description=\"$3\""
      fi

      local opt
      local reqtext=""
      local isdebugFeature=false
      for opt in $opts; do
        case "$opt" in
        +default )         ;;
        +required )        params="$params Absent=\"disallow\""
                           reqtext=" (required)";;
        +invisible )       displayparam="Display=\"hidden\""
                           params="$params Absent=\"disallow\"";;
        +debug )           isdebugFeature=true;;

        * )                Error "features.in: Bad option $opt"
                           exit 1;;
        esac
      done


# (PBR)
# I tried to get debugging features to work but I could not. The last thing I
# tried was to set either ADDSOURCE or INSTALLLEVEL. Neither of these solutions
# will cause the feature conditions to rerun. The only thing I've found to do
# that is to rerun CostFinalize. The problem with this is it will re-enable all
# the options again. I'm keeping the basic framework for +debug support
      if "$isdebugFeature"; then
        regfeature="${featurename:1}"
        echo "regfeature = $regfeature"

        Xecho + "<Feature Id=\"$featurename\" Title=\"$shortname$reqtext\" $descparam $params $displayparam $leveldebug $defparam>" >> $outfeature
      else
        Xecho + "<Feature Id=\"$featurename\" Title=\"$shortname$reqtext\" $descparam $params $displayparam $levelparam $defparam>" >> $outfeature
      fi


      grep 'Component.*Id="'$featurename'[.0-9]*"' "$dirfile" | sed -e 's/\(Id="[^"]*"\).*/\1 \/>/' -e 's/Component /ComponentRef /' >> $outfeature

      # Create a separate subfeature for any environment variables
      # associated with the main feature.
      # The <Condition> stuff is the magic that enables/disables
      # setting the environment variables depending on the check box property.

      Xecho + "<Feature Id=\"env.$featurename\" Title=\"env vars\" $params Display=\"hidden\" $levelparam $defparam>" >> $outfeature

      Xecho "<Condition $leveldisable><![CDATA[EnvironmentSetCheck<>1]]></Condition>" >> $outfeature
      Xecho "<Condition $levelparam><![CDATA[EnvironmentSetCheck=1]]></Condition>" >> $outfeature
      grep 'Component.*Id="env\.'$featurename'[.0-9]*"' "$dirfile" | sed -e 's/\(Id="[^"]*"\).*/\1 \/>/' -e 's/Component /ComponentRef /' >> $outfeature

      Xecho - "</Feature>" >> $outfeature
      Xecho - "</Feature>" >> $outfeature

      while [ "$featcount" -gt 0 ]; do
         Xecho - "</FeatureRef>"  >> $outfeature
         featcount=$(($featcount - 1))
      done

      # Append the name to the feature list if it is to be installed.
      # This publish fragment gets 'executed' when leaving the
      # dialog to select features.  Note that we have to quote
      # the comma for m4 (`,') since this appears in a macro usage.
      #

# (PBR)
# This code sets ADDSOURCE to show which debug options to include. This does not work
      # If this is a debug feature, only turn on the value if the parent is on
      if "$isdebugFeature"; then
 	  regfeature="${featurename:1}"

#          Xecho "<Publish Property=\"ADDSOURCE\" Value=\"[ADDSOURCE]\`,'\">" >> $outset
#          Xecho "  <![CDATA[&${regfeature} = 3 AND DebugCheck=\"yes\" AND ADDSOURCE <> NULL]]></Publish>" >> $outset

#          Xecho "<Publish Property=\"ADDSOURCE\" Value=\"[ADDSOURCE]${featurename}\">" >> $outset
#          Xecho "  <![CDATA[&${regfeature} = 3 AND DebugCheck=\"yes\"]]></Publish>" >> $outset

          Xecho "<Publish Property=\"FeatureList\" Value=\"[FeatureList]\`,' ${shortname}\">" >> $outset
          Xecho "  <![CDATA[&${regfeature} = 3 AND DebugCheck=\"yes\"]]></Publish>" >> $outset

      else
        Xecho "<Publish Property=\"FeatureList\" Value=\"[FeatureList]\`,' ${shortname}\">" >> $outset
        Xecho "  <![CDATA[&${featurename} = 3]]></Publish>" >> $outset
      fi
}

# ProcessOneEnv(FEATURE, ENVNAME, ENVVALUE, OPTS, OUTDIRS, OUTSET)
# Called by ProcessFeatures to process a line in the environment.in file.
# The four arguments are the values of the four columns.
# The output will be into two files:
#   OUTDIRS: WiX XML Component entries, that contain environment values.
#            This controls the actual setting of the variables.
#   OUTSET:   WiX XML to set the installer variables if an env variable
#             is set or a feature selected.
#
ProcessOneEnv() {
      local feature="$1"
      local envname="$2"
      local envvalue="$3"
      local opts="$4"
      local outdirs="$5"
      local outset="$6"

      # Make the path uniform.
      # echo "c:\Program Files\...\/Lib/Hello" | sed -e 's:\\/:\\:' -e 's:/:\\:g`
      # This produces c:\Program Files\...\Lib\Hello
      case "$envvalue" in
      /* ) envvalue=`echo "$envvalue" | sed -e 's:^/::'`
      esac

      local path="[INSTALLDIR]$envvalue"

      local opt
      part="last"
      for opt in $opts; do
        case "$opt" in
        +first )         part="first";;
        +last )          part="last";;
        * )                Error "environment.in: Bad option $opt"
                           exit 1;;
        esac
      done

      # Generate the OUTDIRS fragment
      # This looks like:
      #
      # <Component Id="env.CoreAPI.43" Guid="4B75755F-1129-292C-3434-238410000247">
      #   <Environment Id="env.44" Name="+-LIB" Action="set"
      #     Permanent="no" Part="first" Value="[INSTALLDIR]Lib" />
      # </Component>
      #
      # Having a unique guid makes uninstall work.
      # Note: We really want these installed as System rather than
      # User vars (using the System="yes" tag), but only if user
      # installs for *all* users.  There is no convenient way to
      # do that, so we leave them as default (User variables).


      XmlLevel=4
      local Id=`NextId`
      Xecho "WIX_DB_SET_CURFILE(${envname})" >> $outdirs
      Xecho + "<Component Id=\"env.$feature.$Id\" $PERSISTGUID>"    >> $outdirs
      Id=`NextId`

      Xecho "<Environment Id=\"env.$Id\" Name=\"+-$envname\" Action=\"set\""  >> $outdirs
      Xecho "  Permanent=\"no\" Part=\"$part\" Value=\"$path\" />"  >> $outdirs

      Xecho "</Component>"   >> $outdirs

      # Generate the OUTSET fragment
      # This looks like:
      #
      #    <Publish Property="CLASSPATHValue" Value="[INSTALLDIR]Lib/db.jar;[CLASSPATHValue]">
      #      <![CDATA[&JavaAPI = 3]]></Publish>
      #    <Publish Property="CLASSPATHEscValue" Value="[INSTALLDIR]Lib/db.jar;[CLASSPATHEscValue]">
      #      <![CDATA[&JavaAPI = 3]]></Publish>
      #
      # This is equivalent to pseudocode:
      #      if (InstallFeature(JavaAPI)) {
      #           Prepend CLASSPATHValue with "Lib/db.jar;"
      #           Prepend CLASSPATHEscValue with "Lib/db.jar;"
      #      }
      #
      XmlLevel=4
      Xecho "<Publish Property=\"${envname}Value\" Value=\"[INSTALLDIR]${envvalue};[${envname}Value]\">" >> $outset
      Xecho "   <![CDATA[&${feature} = 3]]></Publish>" >> $outset

      Xecho "<Publish Property=\"${envname}EscValue\" Value=\"[INSTALLDIR]${envvalue};[${envname}EscValue]\">" >> $outset
      Xecho "   <![CDATA[&${feature} = 3]]></Publish>" >> $outset

      
}

# CreateProperty(ID, VALUE)
# Generate a <Property...> tag on the stdout
CreateProperty() {
   Xecho "<Property Id=\"$1\" Hidden=\"yes\"><![CDATA[$2]]></Property>"
}

# ProcessTagProperties(OUTPROPS)
# Generate some identification tags as properties.
# This will let us look at an installer and figure out
# when it was built, etc.
ProcessTagProperties() {
   local outprops="$1"
   local insdate=`date`
   XmlLevel=4

   CreateProperty _DB_MSI_INSTALLER_DATE "$insdate"   >> $outprops
   CreateProperty _DB_MSI_PRODUCT_NAME "$PRODUCT_NAME" >> $outprops
   CreateProperty _DB_MSI_PRODUCT_VERSION "$PRODUCT_VERSION" >> $outprops
   CreateProperty ARPCOMMENTS "Installer for $PRODUCT_NAME $PRODUCT_VERSION built on $insdate" >> $outprops
}
    
# ProcessEnv(INENVFILE, INBATFILE, OUTPROPS, OUTSET, OUTSHOW)
# We generate some Property magic to show the user what is set.
#
ProcessEnv() {
   InEnv="inenv.tmp"; CleanInputFile "$1" "$InEnv" 3 4
   inbat="$2"
   outprops="$3"
   outset="$4"
   outshow="$5"

   # Get a list of the environment variables
   local envvar
   local envvars=`cut -f 2 < $InEnv | sort | uniq`

   # For each environment var, create lines that declare
   # a pair of properties in the envprops.wixinc file like:
   #
   #  <Property Id="CLASSPATHValue" Hidden="yes"></Property>
   #  <Property Id="CLASSPATHEscValue" Hidden="yes"></Property>
   #
   # And create lines in the envset.wixinc file like:
   #
   #   <Publish Property="CLASSPATHValue" Value="%CLASSPATH%">
   #        <![CDATA[1]]></Publish>
   #   <Publish Property="CLASSPATHEscValue" Value="\\%CLASSPATH\\%">
   #        <![CDATA[1]]></Publish>
   #
   # More will be added to that file later.
   # Then, create lines in the envshow.wixinc file like:
   #
   #  <Control Id="CLASSPATHText" Type="Text"
   #               X="23" Width="316" PARTIALHEIGHT(10, 2)
   #               TabSkip="no" Text="CLASSPATH:" />
   #
   #  <Control Id="CLASSPATHValueText" Type="Text"
   #               X="37" Width="316" PARTIALHEIGHT(20, 7)
   #               TabSkip="no" Text="[CLASSPATHValue]" />

   for envvar in $envvars; do
      XmlLevel=4
      CreateProperty "${envvar}Value" "" >> $outprops
      CreateProperty "${envvar}EscValue" "" >> $outprops

      XmlLevel=4
      Xecho "<Publish Property=\"${envvar}Value\" Value=\"%${envvar}%\">" >> $outset
      Xecho "      <![CDATA[1]]></Publish>" >> $outset
      Xecho "<Publish Property=\"${envvar}EscValue\" Value=\"\\%${envvar}\\%\">" >> $outset
      Xecho "      <![CDATA[1]]></Publish>" >> $outset

      XmlLevel=4
      Xecho "<Control Id=\"${envvar}Text\" Type=\"Text\""  >> $outshow
      Xecho "         X=\"23\" Width=\"316\" PARTIALHEIGHT(10, 2)" >> $outshow
      Xecho "         TabSkip=\"no\" Text=\"${envvar}:\" />"  >> $outshow

      Xecho "<Control Id=\"${envvar}ValueText\" Type=\"Text\"" >> $outshow
      Xecho "         X=\"37\" Width=\"316\" PARTIALHEIGHT(20, 7)" >> $outshow
      Xecho "         TabSkip=\"no\" Text=\"[${envvar}Value]\" />" >> $outshow

   done

   # Create the dbvars.bat file from the .bat template file
   # TODO: the bat template file currently knows the variables
   #       and their values, it should get them from the environment.in

   RunM4 <"$inbat" >"$PRODUCT_STAGE/dbvars.bat" || Error "m4 failed" || exit 1
}


# CleanInputFile(INFILENAME, OUTFILENAME, MINELEMENTS, MAXELEMENTS)
# A filter to preprocess and validate input files.
# We end up without comment lines, a single tab between elements,
# and a trailing tab.
# Also some selected shell variables are expanded for convenience.
# We verify that each line has the number of elements that fall within
# the given min and max.
#
CleanInputFile() {
   sed \
        -e 's/#.*//' \
        -e 's/ *	/	/g' \
        -e 's/	 */	/g' \
        -e '/^[ 	]*$/d' \
        -e 's/$/	/' \
        -e 's/		*/	/g'  \
        -e 's:\${PRODUCT_VERSION}:'"${PRODUCT_VERSION}":g \
        -e 's:\${PRODUCT_MAJOR}:'"${PRODUCT_MAJOR}":g \
        -e 's:\${PRODUCT_MINOR}:'"${PRODUCT_MINOR}":g \
        -e 's:\${PRODUCT_PATCH}:'"${PRODUCT_PATCH}":g \
        -e 's:\${PRODUCT_MAJMIN}:'"${PRODUCT_MAJMIN}":g \
        -e 's:\${PRODUCT_STAGE}:'"${PRODUCT_STAGE}":g \
	-e 's:\${PRODUCT_SHARED_WINMSIDIR}:'"${PRODUCT_SHARED_WINMSIDIR}":g \
        -e 's/^[
 \t]*$//'	\
          < "$1" > "$2"

   # count tabs on each line
   sed -e 's/[^\t]//g' -e 's/[\t]/x/g' < "$2" | (
        read line
        linecount=1
        while [ "$line" != '' ]; do
            chars=`echo "$line" | wc -c`
            chars=$(($chars - 1))  # Remove newline
            if [ "$chars" -lt "$3" -o "$chars" -gt "$4" ]; then
               Error "$1: Input file error on or after line $linecount"
            fi
            read line
            linecount=$(($linecount + 1))
        done
   )
}

# StripDoubleQuotes()
# In some input files, we allow double quotes around
# multi-word strings for readability.  We strip them
# here from standard input and write to standard output.
# We only expect them at the beginning and end.
#
StripDoubleQuotes() {
   sed -e 's/^"//' -e 's/"$//'
}

# IndentXml(PLUSMINUS_ARG)
# A global variable $XmlLevel is kept for the indent level.
# Every call creates blank output that matches the indent level.
# In addition, with a '-' argument, the indent level
# decrements by one before printing.
# With a '+', the indent level increments after printing.
# This is generally just used by Xecho
#
XmlLevel=0
IndentXml() {
   if [ "$1" = '-' -a $XmlLevel != 0 ]; then
     XmlLevel=$(($XmlLevel - 1))
   fi
   local idx=0
   while [ "$idx" != "$XmlLevel" ]; do
     echo -n '  '
     idx=$(($idx + 1))
   done
   if [ "$1" = '+' ]; then
     XmlLevel=$(($XmlLevel + 1))
   fi
}

# Xecho [ - | + ] ...
# echoes arguments (like) echo, except that the output
# is indented for XML first.  If +, the indentation changes
# after printing, if -, the indentation changes before printing.
#
Xecho()
{
   local xarg=
   if [ "$1" = '-' -o "$1" = '+' ]; then
      xarg="$1"
      shift
   fi
   IndentXml $xarg
   echo "$@"
}

# SlashCount(PATH)
# Returns the number of slashes in its argument
# Note, we are relying on some advanced
# features of bash shell substitution
#
SlashCount()
{
   local allslash=`echo "$1" | sed -e 's:[^/]*::g'`
   echo "${#allslash}"
}

# ProcessDirTransition(PREVDIR, NEXTDIR)
# Used by ProcessFeatures to create the parts
# of an WiX <Directory> heirarchy (on stdout) needed to
# transition from directory PREVDIR to NEXTDIR.
# This may include any needed </Directory> entries as well.
# For example, ProcessDirTransition /Bin/Stuff /Bin/Foo/Bar
# produces:
#    </Directory>      ...to go up one from 'Stuff'
#    <Directory Foo>
#      <Directory Bar>
#
ProcessDirTransition() {
   local p="$1"
   local n="$2"
   if [ "$p" = '' ]; then p=/; fi
   if [ "$n" = '' ]; then n=/; fi
   local nextdir="$2"

   # The number of slashes in $p is the current directory level.
   XmlLevel=$(($(SlashCount $p) + 4))

   while [ "$p" != / ]; do
      if [ "${n#${p}}" != "$n" ]; then
         break
      fi

      # go up one level, and keep $p terminated with a /
      p=`dirname $p`
      case "$p" in
      */ )   ;;
      * )    p=$p/;;
      esac
      Xecho - "</Directory>"
   done
   n=${n#${p}}
   while [ "$n" != '' ]; do
      local dirname=`echo $n | sed -e 's:/.*::'`
      local cleanname=`CleanFileName "$dirname"`
      local shortname=`GetShortName "$cleanname"`
      local dirid=`NextId`

      local larg=""
      if [ "${shortname}" != "${dirname}" ]; then
          larg="LongName=\"${dirname}\""
      fi
      Xecho + "<Directory Id=\"${cleanname}Dir.$dirid\" Name=\"${shortname}\" $larg>"

      n=`echo $n | sed -e 's:^[^/]*/::'`
   done

   Xecho "WIX_DB_SET_CURDIR($nextdir)"      # Tell the m4 macro what the current dir is
}

# SetupErrorLog()
# Given the global variable ERRORLOG for the name of the
# error output file, do any setup required to make that happen.
#
SetupErrorLog() {

    # Before we start to use ERRORLOG, we get a full pathname,
    # since the caller may change directories at times.
    case "$ERRORLOG" in
    /* ) ;;
    *)   ERRORLOG=`pwd`"/$ERRORLOG" ;;
    esac

    rm -f $ERRORLOG

    # File descriptor tricks.
    # Duplicate current stderr to 15, as we'll occasionally
    # need to report progress to it.  Then, redirect all
    # stderr from now on to the ERRORLOG.
    # 
    exec 15>&2
    exec 2>>$ERRORLOG
}

# RequireCygwin
# Cygwin does not install certain needed components by default.
# Check to make sure that everything needed by the script
# and functions is here.
#
RequireCygwin() {
    Progress -minor "checking for Cygwin..."
    RequireFileInPath PATH "$PATH" m4
    RequireFileInPath PATH "$PATH" gcc
    RequireFileInPath PATH "$PATH" make
    RequireFileInPath PATH "$PATH" unzip
    RequireFileInPath PATH "$PATH" bc
    RequireFileInPath PATH "$PATH" openssl    # needed for MD5 hashing
}

# RequireJava()
# A java SDK (with include files) must be installed
#
RequireJava() {
    Progress -minor "checking for Java..."
    RequireFileInPath INCLUDE "$INCLUDE" jni.h
    RequireFileInPath INCLUDE "$INCLUDE" jni_md.h
    RequireFileInPath PATH "$PATH" jar.exe
    RequireFileInPath PATH "$PATH" javac.exe
}

# RequireTcl()
# A Tcl SDK (with compatible .lib files) must be installed
#
RequireTcl() {
    Progress -minor "checking for Tcl..."
    RequireFileInPath INCLUDE "$INCLUDE" tcl.h
    RequireFileInPath LIB "$LIB" tcl84g.lib
    RequireFileInPath LIB "$LIB" tcl84.lib
}

# RequireWix()
# WiX must be installed
#
RequireWix() {
    Progress -minor "checking for WiX..."
    RequireFileInPath PATH "$PATH" candle.exe
    RequireFileInPath PATH "$PATH" light.exe
    RequireFileInPath PATH "$PATH" tallow.exe
}

# RequirePerl()
# Perl must be installed
#
RequirePerl() {
    Progress -minor "checking for Perl..."
    RequireFileInPath PATH "$PATH" perl.exe
}

# RequirePython()
# Python (and include files) must be installed
#
RequirePython() {
    Progress -minor "checking for Python..."
    RequireFileInPath INCLUDE "$INCLUDE" Python.h
    RequireFileInPath PATH "$PATH" python.exe
}

# CreateDbPerl()
# Build Perl interface (for Berkeley DB only).
#
CreateDbPerl() {

    # First build Berkeley DB using cygwin, as that version is
    # needed for the Perl build
    local here=`pwd`
    Progress "building using Cygwin tools (needed for perl)"
    cd "${PRODUCT_DBBUILDDIR}"
    insdir="${PRODUCT_STAGE}/install_unix"
    ../dist/configure --prefix="$insdir" >>$ERRORLOG  || exit 1
    make install   >>$ERRORLOG  || exit 1

    Progress "building perl"
    cd ../perl/BerkeleyDB
    BERKELEYDB_INCLUDE="$insdir/installed_include" BERKELEYDB_LIB="$insdir/lib" \
         perl Makefile.PL >>$ERRORLOG   || exit 1
    make >>$ERRORLOG
    cd $here
}

# CreateWindowsSystem()
# Copy Window system files
#
CreateWindowsSystem() {
    local here=`pwd`
    Progress "Copy Window system files..."
    cd "${PRODUCT_SUB_BLDDIR}"
    cp -f $SYSTEMROOT/system32/msvcr71.dll  build_windows/Release/ || exit 1
    cp -f $SYSTEMROOT/system32/msvcp71.dll  build_windows/Release/ || exit 1
    cp -f $SYSTEMROOT/system32/msvcr71d.dll build_windows/Debug/ || exit 1
    cp -f $SYSTEMROOT/system32/msvcp71d.dll build_windows/Debug/ || exit 1
    cd $here
}

# CreateInclude(DIR, FILES)
# Create an include directory populated with the files given
#
CreateInclude() {

    local incdir="$1"
    shift

    Progress "creating the "$incdir" directory..."
    rm -rf "$incdir"
    mkdir "$incdir"  || exit 1
    cp -r "$@" "$incdir"
}

# CreateWindowsBuild()
# Do the windows build as defined by the winbuild.bat file
#
CreateWindowsBuild() {
    local here=`pwd`
    Progress "building using Windows tools..."
    cd "${PRODUCT_SUB_BLDDIR}"  || exit 1

    # Before starting, copy any installer tools here.
    # This makes building these tools straightforward
    # and the results are left in the build directory.
    #
    cp -r ${PRODUCT_SHARED_WINMSIDIR}/instenv .

    # We create a wbuild.bat file, which is essentially
    # identical, except it has the carriage returns added.
    # This allows us to use our favorite editors on winbuild.bat .
    #
    sed -e 's/$//' < ${PRODUCT_STAGE}/../winbuild.bat | tr '\001' '\015'  > wbuild.bat
    # TODO: Needed?
    rm -f build_windows/Berkeley_DB.sln
    rm -f winbld.out winbld.err
    touch winbld.out winbld.err
    echo "Build output and errors are collected in" >> $ERRORLOG
    echo "  winbld.{out,err} until the build has completed." >> $ERRORLOG
    cmd.exe /x /c call wbuild.bat
    status=$?
    cat winbld.out >> $ERRORLOG
    if [ -s winbld.err -o "$status" != 0 ]; then
       cat winbld.err >> $ERRORLOG
       Error "Errors during windows build"
       exit 1
    fi
    cd $here
}

# CreateSources(SOURCESDIR,DOCDIR...)
# Create the sources directory, ignoring things in the docdirs
#
CreateSources() {
    local sources="$1"

    Progress "creating the Sources directory in $sources..."
    rm -rf "$sources"
    cp -r ${PRODUCT_SRCDIR} "$sources"  || exit 1
}

# Usage()
# Show the usage for this script.
#
Usage()
{
    echo "Usage:  s_winmsi [ options ]" >&2
    echo "Options: " >&2
    echo "   -input file       use file rather than ${PRODUCT_ZIP_FILEFMT}" >&2
    echo "                     where X.Y.Z is defined by ../RELEASE" >&2
    echo "   -output file      use file rather than ${PRODUCT_MSI_FILEFMT}" >&2
    echo "                     where X.Y.Z is defined by ../RELEASE" >&2
    echo "   -usebuild DIR     use DIR for exes, DLLs, etc. " >&2
    echo "                     rather than building from scratch" >&2
    echo "   -preserve         preserve the winmsi/msi_staging directory" >&2
    echo "   -skipgen          skip generating m4 include files" >&2
}

# SetupOptions()
# Parse command line options and set global variables as indicated below.
#
SetupOptions() {
    OPT_USEBUILD=
    OPT_PRESERVE=false
    OPT_INFILE=
    OPT_OUTFILE=
    OPT_SKIPGEN=false
    while [ "$#" -gt 0 ]; do
        arg="$1"; shift
        case "$arg" in
        -usebuild )  OPT_USEBUILD="$1"; shift ;;
        -skipgen )   OPT_SKIPGEN=true ;;
        -preserve )  OPT_PRESERVE=true;;
        -input )     OPT_INFILE="$1"; shift ;;
        -output )    OPT_OUTFILE="$1"; shift ;;
        * )
            echo "ERROR: Unknown argument '$arg' to s_winmsi" >&2
            Usage
            exit 1
            ;;
        esac
    done
    if [ "$OPT_INFILE" = '' -o ! -f "$OPT_INFILE" ]; then
        echo "$OPT_INFILE: not found"
        exit 1
    fi
}

# CreateStage()
# Create the staging area
#
CreateStage() {
    Progress "creating staging area..."
    if [ "$PRODUCT_STAGE" = '' ]; then
        Error "PRODUCT_STAGE not set"
        exit 1
    fi
    if ! $OPT_PRESERVE; then
        trap 'rm -rf ${PRODUCT_STAGE} ; exit 0' 0 1 2 3 13 15
    fi
    rm -rf ${PRODUCT_STAGE}   || exit 1
    mkdir ${PRODUCT_STAGE}    || exit 1

    cd ${PRODUCT_STAGE}
    
    Progress "extracting $OPT_INFILE..."
    unzip -q ../../$OPT_INFILE  || exit 1
    
    if [ ! -d $PRODUCT_LICENSEDIR ]; then
        Error "$OPT_INFILE: no top level $PRODUCT_LICENSEDIR directory"
        exit 1
    fi
}

# CreateLicenseRtf(LICENSEIN, LICENSERTF)
# From a text LICENSE file, create the equivalent in .rtf format.
#
CreateLicenseRtf() {
    local licensein="$1"
    local licensertf="$2"

    if [ ! -f "$licensein" ]; then
        Error "License file $licensein: does not exist"
        exit 1
    fi
    Progress "creating ${licensertf}..."
    
    # Build a list of references to components ids (i.e. directories)
    # that are listed in the .wxs file.  This is needed to refer to
    # all of the source (sadly it appears there is no better way!)
    #
    if ! grep '^=-=-=-=' $licensein > /dev/null; then
        Error "LICENSE has changed format, this script must be adapted"
        exit 1
    fi
    
    sed -e '1,/^=-=-=-=-=/d' < $licensein | MakeRtf > $licensertf
}

    
# CreateMsi(INFILE,WXSFILE,MSIFILE)
# Do the final creation of the output .MSI file.
# It is assumed that all *.wixinc files are now in place.
# INFILE is an absolute name of the m4 input WiX file.
# WXSFILE is a short (basename) of the postprocessed WiX file,
#       after macro expansion, it will be left in staging directory.
# MSIFILE is a short (basename) of the output .MSI name
#
CreateMsi() {
    local infile="$1"
    local wxs="$2"
    local msifile="$3"
    local o=`echo "$wxs" | sed -e 's/[.]wxs$//' -e 's/$/.wixobj/'`

    rm -f $o $wxs 

    # Preprocess the ${PROD}wix.in file, adding the things we need
    #
    Progress "Running m4 to create $wxs..."
    RunM4 < "$infile" > "$PRODUCT_STAGE/$wxs"  || Error "m4 failed" || exit 1

    local here=`pwd`
    cd "$PRODUCT_STAGE"
    rm -f "$o" "$msifile"
    Progress "compiling $wxs..."
    candle -w0 $wxs  >> $ERRORLOG || Error "candle (compiler) failed" || exit 1

    Progress "linking .msi file..."
    light -o "$msifile" $o  >> $ERRORLOG  || Error "light (linker) failed" || exit 1
    (rm -f "../../$msifile" && mv "$msifile" ../..)  >> $ERRORLOG  || exit 1
    cd $here
}

# CreateWixIncludeFiles()
# Do all processing of input files to produce
# the include files that we need to process the Wix input file.
#
CreateWixIncludeFiles() {
    local here=`pwd`
    cd "$PRODUCT_STAGE"
    # Touch all the wix include files in case any end up empty.
    touch directory.wixinc features.wixinc envprops.wixinc \
          envset.wixinc envshow.wixinc links.wixinc

    Progress "tagging the installer..."
    ProcessTagProperties envprops.wixinc
    
    Progress "processing environment..."
    ProcessEnv ../environment.in ../dbvarsbat.in envprops.wixinc envset.wixinc envshow.wixinc
    
    Progress "processing features and files..."
    ProcessFeatures ../files.in ../features.in ../environment.in \
                   directory.wixinc features.wixinc \
                   envset.wixinc
    
    Progress "processing links..."
    ProcessLinks ../links.in features.wixinc > links.wixinc
    cd $here
}