# $Id$ # # 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_MSVC_VERSION - e.g. "71" for Visual Studio 7, "80" for VS 8 # 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 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 # 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 -nologo -d `cygpath -w "$dir"` "$@" > tallow.out || exit 1 cat tallow.out >> tallow.log echo "-------------" >> tallow.log sed -e '1,//s/$/ WIX_DB_END_SUBDIR()/' \ -e "// $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 " " } # 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 "" >> $OutSet Xecho " " >> $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 "" >> $OutSet # Xecho "" >> $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 + "> $OutFeatures Xecho " Description=\"Links\" Display=\"hidden\"" >> $OutFeatures Xecho " Level=\"1\" AllowAdvertise=\"no\"" >> $OutFeatures Xecho " ConfigurableDirectory=\"INSTALLUTIL\"" >> $$OUTFeatures Xecho " Absent=\"disallow\">" >> $OutFeatures Xecho "" Xecho " " 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 + "" 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 "" 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 "" Xecho "" else Xecho "" fi Xecho - "" Xecho "" >> $OutFeatures done return 0 ) || Error "Error processing links" || exit 1 Xecho "" Xecho "" Xecho - "" >> $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 + "" Xecho "" Xecho - "" 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 "" >> $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 + "" >> $outfeature else Xecho + "" >> $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 stuff is the magic that enables/disables # setting the environment variables depending on the check box property. Xecho + "" >> $outfeature Xecho "1]]>" >> $outfeature Xecho "" >> $outfeature grep 'Component.*Id="env\.'$featurename'[.0-9]*"' "$dirfile" | sed -e 's/\(Id="[^"]*"\).*/\1 \/>/' -e 's/Component /ComponentRef /' >> $outfeature Xecho - "" >> $outfeature Xecho - "" >> $outfeature while [ "$featcount" -gt 0 ]; do Xecho - "" >> $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 "" >> $outset # Xecho " NULL]]>" >> $outset # Xecho "" >> $outset # Xecho " " >> $outset Xecho "" >> $outset Xecho " " >> $outset else Xecho "" >> $outset Xecho " " >> $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: # # # # # # 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 + "" >> $outdirs Id=`NextId` Xecho "> $outdirs Xecho " Permanent=\"no\" Part=\"$part\" Value=\"$path\" />" >> $outdirs Xecho "" >> $outdirs # Generate the OUTSET fragment # This looks like: # # # # # # # This is equivalent to pseudocode: # if (InstallFeature(JavaAPI)) { # Prepend CLASSPATHValue with "Lib/db.jar;" # Prepend CLASSPATHEscValue with "Lib/db.jar;" # } # XmlLevel=4 Xecho "" >> $outset Xecho " " >> $outset Xecho "" >> $outset Xecho " " >> $outset } # CreateProperty(ID, VALUE) # Generate a tag on the stdout CreateProperty() { Xecho "" } # 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: # # # # # And create lines in the envset.wixinc file like: # # # # # # # More will be added to that file later. # Then, create lines in the envshow.wixinc file like: # # # # for envvar in $envvars; do XmlLevel=4 CreateProperty "${envvar}Value" "" >> $outprops CreateProperty "${envvar}EscValue" "" >> $outprops XmlLevel=4 Xecho "" >> $outset Xecho " " >> $outset Xecho "" >> $outset Xecho " " >> $outset XmlLevel=4 Xecho "> $outshow Xecho " X=\"23\" Width=\"316\" PARTIALHEIGHT(10, 2)" >> $outshow Xecho " TabSkip=\"no\" Text=\"${envvar}:\" />" >> $outshow Xecho "> $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_MSVC_VERSION}:'"${PRODUCT_MSVC_VERSION}":g \ -e 's:\${PRODUCT_STAGE}:'"${PRODUCT_STAGE}":g \ -e 's:\${PRODUCT_SHARED_WINMSIDIR}:'"${PRODUCT_SHARED_WINMSIDIR}":g \ -e 's/^[\r \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 heirarchy (on stdout) needed to # transition from directory PREVDIR to NEXTDIR. # This may include any needed entries as well. # For example, ProcessDirTransition /Bin/Stuff /Bin/Foo/Bar # produces: # ...to go up one from 'Stuff' # # # 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 - "" 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 + "" 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/include" BERKELEYDB_LIB="$insdir/lib" \ perl Makefile.PL >>$ERRORLOG || exit 1 make >>$ERRORLOG cd $here } # CreateWindowsSystem() # Copy Windows system files # CreateWindowsSystem() { local here=`pwd` Progress "Copy Windows system files..." cd "${PRODUCT_SUB_BLDDIR}" if [ $PRODUCT_MSVC_VERSION = "80" ]; then cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/msvcm80.dll" build_windows/Win32/Release/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/msvcp80.dll" build_windows/Win32/Release/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/msvcr80.dll" build_windows/Win32/Release/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/Microsoft.VC80.CRT.manifest" build_windows/Win32/Release/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/msvcm80.dll" build_windows/Win32/Debug/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/msvcp80.dll" build_windows/Win32/Debug/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/msvcr80.dll" build_windows/Win32/Debug/ || exit 1 cp -f "$MSVC_ROOT_DIR/VC/redist/x86/Microsoft.VC80.CRT/Microsoft.VC80.CRT.manifest" build_windows/Win32/Debug/ || exit 1 else cp -f $SYSTEMROOT/system32/msvcr$PRODUCT_MSVC_VERSION.dll build_windows/Win32/Release/ || exit 1 cp -f $SYSTEMROOT/system32/msvcp$PRODUCT_MSVC_VERSION.dll build_windows/Win32/Release/ || exit 1 cp -f $SYSTEMROOT/system32/msvcr$PRODUCT_MSVC_VERSION.dll build_windows/Win32/Debug/ || exit 1 cp -f $SYSTEMROOT/system32/msvcp$PRODUCT_MSVC_VERSION.dll build_windows/Win32/Debug/ || exit 1 fi 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 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" "$sources/../docs/" unzip -q -u ../../$OPT_INFILE -d tmp_dir || exit 1 mv ./tmp_dir/${dbver}/docs "$sources/../" || exit 1 mv ./tmp_dir/${dbver} "$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_CONTINUE=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;; -continue ) OPT_CONTINUE=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 if ! $OPT_CONTINUE; then rm -rf ${PRODUCT_STAGE} || exit 1 else # Cleanup files that would break :) rm -f ${PRODUCT_STAGE}/*wxs || exit 1 rm -f ${PRODUCT_STAGE}/*wixinc || exit 1 rm -f ${PRODUCT_STAGE}/*tmp || exit 1 rm -f ${PRODUCT_STAGE}/*bdbsc || exit 1 rm -f ${PRODUCT_STAGE}/tallow* || exit 1 fi if [ ! -d ${PRODUCT_STAGE} ]; then mkdir ${PRODUCT_STAGE} || exit 1 fi cd ${PRODUCT_STAGE} Progress "extracting $OPT_INFILE..." unzip -q -u ../../$OPT_INFILE || exit 1 if [ ! -d $PRODUCT_LICENSEDIR ]; then Error "$OPT_INFILE: no top level $PRODUCT_LICENSEDIR directory" exit 1 fi # TODO: In the 4.8.12 package this file was not writable, it needs to # be for the build. Make it so. Remove me. chmod 664 ${PRODUCT_SRCDIR}/csharp/doc/libdb_dotnet${PRODUCT_MAJMIN}.XML } # 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/'` local tmpfile="dbcore.wxs.tmp" 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 # Remove any stray "PUT-GUID-HERE" tags. # They are added by some recent versions of WiX, including 2.0.5805.0 # The scripts already insert valid GUIDs. sed -e 's/ Guid="PUT-GUID-HERE" / /' < "$wxs" > "$tmpfile" rm -f "$wxs" mv "$tmpfile" "$wxs" 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 }