1#	$Id: s_winmsi.fcn,v 1.15 2008/04/17 01:59:10 alexg Exp $
2#
3# The common functions used by the s_winmsi scripts (both
4# for core DB and DB/XML).
5#
6# This script uses several bash extensions that are convenient
7# since we "know" it will always run under Cygwin: shell functions,
8# 'return', declaration of 'local' variables, $(command) syntax,
9# ${#X} (counting chars), ${X#regexp} (searching) $((expr)) (arithmetic)
10#
11# These functions use 'global' variables:
12#   ERRORLOG             - a filename
13#   PRODUCT_NAME         - e.g. "Berkeley DB"
14#   PRODUCT_VERSION      - e.g. "4.1.25", derived from dist/RELEASE
15#   PRODUCT_MAJOR        - e.g. "4", (for release 4.1.25) from dist/RELEASE
16#   PRODUCT_MINOR        - e.g. "1", (for release 4.1.25) from dist/RELEASE
17#   PRODUCT_PATCH        - e.g. "25", (for release 4.1.25) from dist/RELEASE
18#   PRODUCT_MAJMIN       - e.g. "41", (for release 4.1.25) from dist/RELEASE
19#   PRODUCT_STAGE        - the staging directory for temp files and builds
20#   PRODUCT_LICENSEDIR   - the tree containing LICENSE and README
21#   PRODUCT_SUB_BLDDIR  - top of the subproduct build e.g. "dbxml-2.0.1/dbxml"
22#   PRODUCT_BLDDIR       - top of the build tree e.g. "dbxml-2.0.1"
23#   PRODUCT_SRCDIR       - the dir we unzip to e.g. "dbxml-2.0.1"
24#   PRODUCT_DBBUILDDIR   - where build_unix dir is for Berkeley DB (for Perl)
25#   PRODUCT_SHARED_WINMSIDIR   - where the master winmsi directory is
26#   PRODUCT_IMAGEDIR     - where the images are (usually winmsi/images)
27#   PRODUCT_ZIP_FILEFMT  - what zip file looks like e.g. "db-X.Y.Z.NC.zip"
28#   PRODUCT_MSI_FILEFMT  - what msi file looks like e.g. "db-X.Y.Z.NC.msi"
29#
30# Some of these may seem redundant, but there are options to take
31# an already built tree from a different place than where we'll unzip
32# to and take our sources from, for example.  This allows a lot of flexibility
33# for development and debugging (especially when these trees can be huge).
34
35# This is the magic tag that creates a new unique GUID in Wix.
36# GUIDs are needed on every <Component ... > entry to ensure
37# that the component can be uninstalled.
38GENGUID='Guid="GUID_CREATE_UNIQUE()"'
39PERSISTGUID='Guid="WIX_DB_PERSISTENT_GUID()"'
40
41# MakeRtf()
42# Standard input is plain text, standard output is RTF.
43#
44MakeRtf() {
45    temp1=/tmp/sbm$$a
46    cat > $temp1
47
48
49# Courier is a good font, but the lines with all caps
50# overflows our current dialog size:
51#     {\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}
52#     \viewkind4\uc1\pard\lang1033\f0\fs16 
53#
54# Using Small fonts works:
55#      {\rtf1\ansi\deff0{\fonttbl{\f0\fswiss\fprq2\fcharset0 Small Fonts;}}
56#      {\colortbl ;\red0\green0\blue0;}
57#      \viewkind4\uc1\pard\cf1\lang1033\f0\fs14 
58
59# Arial is the best compromise:
60    sed -e 's/^ *//' << 'EndOfRTFHeader'
61      {\rtf1\ansi\deff0{\fonttbl{\f0\fswiss\fprq2\fcharset0 Arial;}}
62      {\colortbl ;\red0\green0\blue0;}
63      \viewkind4\uc1\pard\cf1\lang1033\f0\fs16 
64EndOfRTFHeader
65
66# Embedded '<' and '>' can cause problems for Wix
67    sed -e 's:$:\\par:' -e 's:<: \\lquote :' -e 's:>: \\rquote :' < $temp1
68    echo -n '}'
69    rm -f $temp1
70}
71
72# NextId()
73# Get the next available unique id, a simple integer counter.
74# We use a file, rather than a shell variable to track the
75# number, because this is called from subshells at various
76# points, and they cannot affect the variables in the parent shell.
77#
78ComponentID=component.id
79NextId()
80{
81    local id=`cat $ComponentID 2>/dev/null`
82    if [ "$id" = '' ]; then
83       id=0
84    fi
85    id=$(($id + 1))
86    echo "$id" > $ComponentID
87    echo "$id"
88}
89
90# CleanFileName(FILENAME)
91# Removes any strange characters in file names,
92# returning the new name on standard output.
93CleanFileName()
94{
95    echo "$1" | sed -e 's/[-%@!]//g'
96}
97
98# GetShortName(FILENAME)
99# Get a Windows short name for the file,
100# to fit into the 8.3 name space.
101# This is not a great algorithm, but it works.
102# The fact is, the names must be unique, but on
103# Win2000 and WinXP, we'll never see them.
104
105ShortID=short.id
106GetShortName()
107{
108    local name=`echo "$1" | tr '[a-z]' '[A-Z]'`
109
110    # See if the name fits into 8.3. If so,
111    # return it right away.
112    #
113    case "$name" in
114    ?????????*.* )  ;;
115    *.????* )   ;;
116    *.*.* )    ;;
117    *[-%@!]* )    ;;
118    *.* ) echo "$name"
119          return
120          ;;
121    * )
122         if [ "${#1}" -le 8 ]; then
123             echo "$name"
124             return
125         fi
126         ;;
127    esac
128
129    # From NAMEISLONG.EXTLONG, build a name
130    # like NAME~ZZZ.EXT, where ZZZ is a unique (hex)
131    # number we build.  This is 
132
133    local id=`cat $ShortID 2>/dev/null`
134    if [ "$id" = '' ]; then
135       id=0
136    fi
137    id=$(($id + 1))
138    echo "$id" > $ShortID
139    if [ "$id" -ge 4096 ]; then
140        echo "BADBADBAD.TXT"  # return something that will give an error
141        Error "ShortId overflow"
142        exit 1
143    fi
144
145    # Convert the id to hex (I ran out of space using decimal)
146    # This is too slow: id=`echo 16 o $id p | dc`
147    id=`printf "%x" $id`
148
149    # Collect and clean up the part of the name before, and after, the dot
150    local before=`CleanFileName "$name" | sed -e 's/^\([^.]*\)[.].*$/\1/'`
151    local after=`CleanFileName "$name" | sed -e 's/^[^.]*[.]\(.*\)$/\1/'`
152
153    # Make sure the before part fits in 5 chars (not 8, since
154    # we need a few for the unique number).
155    if [ "${#before}" -gt 5 ]; then
156        before=`echo "$before" | sed -e 's/^\(.....\).*/\1/'`
157    fi
158    if [ "${#after}" -gt 3 ]; then
159        after=`echo "$after" | sed -e 's/^\(...\).*/\1/'`
160    fi
161    echo "${before}~${id}.${after}"
162}
163
164# Progress([OPTION,]STRING...)
165# Show a major processing step via echo to stdout and to the error log.
166# An OPTION is "-minor", indicating no big banner.
167#
168Progress()
169{
170    if [ "$1" = -minor ]; then
171       shift
172    else
173       echo "" >> $ERRORLOG
174       echo "============================" >> $ERRORLOG
175    fi
176    echo "$@" >> $ERRORLOG
177    echo "$@" >&15
178}
179
180# Error(STRING...)
181# Show an error in a standard way.
182#
183Error()
184{
185    echo "" >> $ERRORLOG
186    echo "****************** FAIL ******************" >> $ERRORLOG
187    echo "ERROR: $@" >> $ERRORLOG
188    echo "ERROR: $@" >&15
189    echo "See $ERRORLOG for details" >&15
190    return 1
191}
192
193# RequireFileInPath(NAME, PATHVAL, FILE)
194# Look for FILE in the path that has value PATHVAL.
195# The path's name is NAME if it needs to be shown.
196#
197RequireFileInPath()
198{
199    local type="$1"
200    local origpath="$2"
201    local file="$3"
202    local upath="$origpath"
203    if [ "$1" != PATH ]; then
204       upath=`cygpath -up "$origpath"`
205    fi
206
207    SAVEIFS="$IFS"
208    IFS=":"
209    found=no
210    for dir in $upath; do
211        if [ -f "$dir/$file" ]; then
212            IFS="$SAVEIFS"
213            return
214        fi
215    done
216    IFS="$SAVEIFS"
217    Error "File $file not found in $type path: $origpath"
218    exit 1
219}
220
221# Rand4X()
222# Return 4 random hex digits on output
223#
224Rand4X() {
225    # The sed command pads the front with 0's as needed
226    (echo 'obase=16'; echo $RANDOM ) | bc |
227        sed -e 's/^/0000/' -e 's/^.*\(....\)$/\1/'
228    
229}
230
231# RunM4()
232# Run M4, making appropriate substitutions.
233# This function uses GLOBAL variables: PRODUCT_VERSION (e.g. "4.1.25")
234# and PRODUCT_LICENSEDIR, which is where certain text files are found
235#
236RunM4() {
237
238    # Given a version number, like 2.3.45, we want to
239    # create a 8 character name for the directory like db2_3_45.
240    # This name is under a "Oracle" directory,
241    # so it only needs to be unique within the universe of BDB versions.
242    # TODO: instead of using a version number like $DB_VERSION,
243    # maybe use $DB_VERSION_UNIQUE_NAME which looks like "_2003"
244
245    local DB_8CHAR_VERSION=`echo $PRODUCT_VERSION | sed -e 's/[.]/_/g'`
246    if [ ${#DB_8CHAR_VERSION} -le 6 ]; then
247       DB_8CHAR_VERSION="db$DB_8CHAR_VERSION"
248    elif [ ${#DB_8CHAR_VERSION} -le 7 ]; then
249       DB_8CHAR_VERSION="d$DB_8CHAR_VERSION"
250    else
251       Error "Version number too large for simple version number algorithm"
252       exit 1
253    fi
254
255    # Remove leading ./ from PRODUCT_LICENSEDIR if present.
256    local licensedir=`cygpath -w "$PRODUCT_LICENSEDIR"`
257
258    # Create a GUID prefix of the form: ????????-????-????-????-????
259    # This leaves 8 digits of GUID to be manipulated by m4.
260    local GUID_PREFIX="`Rand4X``Rand4X`-`Rand4X`-`Rand4X`-`Rand4X`-`Rand4X`"
261
262    # -P requires that all m4 macros, like define, eval, etc.
263    # are prefixed, like m4_define, m4_eval, etc.  This avoids
264    # various name conflicts with input files.
265    # TODO: rename DB_SRCDIR as DB_LICENSEDIR
266    m4 -P \
267       -DWIX_DB_VERSION="$PRODUCT_VERSION" \
268       -DWIX_DB_8CHAR_VERSION="$DB_8CHAR_VERSION" \
269       -DWIX_DB_GUID_PREFIX="$GUID_PREFIX" \
270       -DWIX_DB_PRODUCT_NAME="$PRODUCT_NAME" \
271       -DWIX_DB_SRCDIR="$licensedir" \
272       -DWIX_DB_TOP="`cygpath -w $PRODUCT_BLDDIR`" \
273       -DWIX_DB_SHARED_WINMSIDIR="$PRODUCT_SHARED_WINMSIDIR" \
274       -DWIX_DB_IMAGEDIR="`cygpath -w $PRODUCT_IMAGEDIR`" \
275       -DWIX_DB_FEATURE_STRUCTURE="m4_include(features.wixinc)" \
276       -DWIX_DB_DIRECTORY_STRUCTURE="m4_include(directory.wixinc)" \
277       -DWIX_DB_LINKS="m4_include(links.wixinc)" \
278       -DWIX_DB_LICENSE_RTF="m4_include(license.rtf)" \
279       -DWIX_DB_ENV_FEATURE_PROPS="m4_include(envprops.wixinc)" \
280       -DWIX_DB_ENV_FEATURE_SET="m4_include(envset.wixinc)" \
281       -DWIX_DB_ENV_FEATURE_SHOW="m4_include(envshow.wixinc)"
282}
283
284# RunTallow(DIR, OPTIONS)
285# Run Tallow, a tool from the WiX distribution
286RunTallow() {
287    local dir="$1"
288    shift
289
290    Id1=`NextId`
291    Id2=`NextId`
292    Id3=`NextId`
293
294    # Tallow is a tool that walks a tree, producing
295    # a WiX directory heirarchy naming the files.
296    # The IDs it produces are not unique (between tallow
297    # runs), so we must make them so here.  Thus "directory78"
298    # becomes "MyFeatureName.123.78" where 123 is an id from NextId.
299    # Secondly, instead of using the tallow output as a separately
300    # compiled fragment, we want to include it directly, so
301    # we need to strip out some extraneous XML entries at the top
302    # and bottom of its output.
303    #
304    # Another thing we do is when we see <Directory></Directory>
305    # pairs, we call m4 macros WIX_DB_{BEGIN,END}_SUBDIR because
306    # we need to track the current directory to generate 'persistent'
307    # GUIDs.  See the discussion about GUIDs in dbwix.m4 .
308    #
309    # !!! For stripping out the extraneous XML, we rely heavily
310    # !!! on the output format, so this is likely to be fragile
311    # !!! between versions of tallow.  Fortunately, it should fail hard.
312    #
313    echo "=============" >> tallow.log
314    echo tallow -nologo -d `cygpath -w "$dir"` "$@" >> tallow.log
315    echo "  <!-- TALLOW output begins here -->"
316         tallow -nologo -d `cygpath -w "$dir"` "$@" > tallow.out || exit 1
317    cat tallow.out >> tallow.log
318    echo "-------------" >> tallow.log
319
320    sed -e '1,/<DirectoryRef/d' -e '/<\/DirectoryRef/,$d' \
321        -e "s/Id=\"directory/Id=\"$feature.$Id1./" \
322        -e "s/Id=\"component/Id=\"$feature.$Id2./" \
323        -e "s/Id=\"file/Id=\"$feature.$Id3./" \
324        -e '/^      <Directory/d' \
325        -e '/^      <\/Directory/d' \
326        -e '/<Directory/s/Name=\"\([^"]*\)"/Name="\1" WIX_DB_BEGIN_SUBDIR(\1) /' \
327        -e '/<\/Directory>/s/$/ WIX_DB_END_SUBDIR()/' \
328        -e "/<Component/s/>/ $PERSISTGUID>/" \
329        < tallow.out > tallow.postsed || exit 1
330
331    echo 'WIX_DB_SET_CURFILE()'
332    echo 'WIX_DB_CLEAR_SUBDIR()'
333    cat tallow.postsed
334    echo 'WIX_DB_CLEAR_SUBDIR()'
335
336    cat tallow.postsed >> tallow.log
337    echo "  <!-- TALLOW output ends here -->"
338}
339
340# ProcessFeatures(INFILES, INFEATURES, INENV, OUTDIRECTORIES, OUTFEATURES,
341#                 OUTSET)
342# Use the files.in and features.in files as
343# input to create two output files, one containing a WiX XML
344# fragment showing directories and needed files,
345# and another containing a WiX XML fragment showing
346# the features in a dependency tree.
347#
348# This creates the heart of the installer flexibility.
349#
350ProcessFeatures() {
351   InFiles="infiles.tmp";  CleanInputFile "$1" "$InFiles" 3 4
352   InFeatures="infeatures.tmp"; CleanInputFile "$2" "$InFeatures" 3 4
353   InEnv="inenv.tmp"; CleanInputFile "$3" "$InEnv" 3 4
354   OutDirs="$4"
355   OutFeatures="$5"
356   OutSet="$6"
357
358   rm -f $OutDirs; touch $OutDirs
359   rm -f $OutFeatures; touch $OutFeatures
360
361   # Initialize the feature list.
362   # This will be expanded (per feature) in ProcessOneFeature
363   #
364   XmlLevel=4
365   Xecho "<Publish Property=\"FeatureList\" Value=\"[NULL]\">" >> $OutSet
366   Xecho "  <![CDATA[1]]></Publish>" >> $OutSet
367
368   Dirs=`cut -f 3 < $InFiles | sort | uniq`
369   Prevdir="/"
370   ProcessDirTransition "$Prevdir" "/" >> $OutDirs
371
372   for Dir in $Dirs; do
373      ProcessDirTransition "$Prevdir" "$Dir" >> $OutDirs
374      ProcessOneDirectory "$Dir" < $InFiles >> $OutDirs || exit 1
375      Prevdir="$Dir"
376   done
377   ProcessDirTransition "$Prevdir" "/" >> $OutDirs
378
379   cat $InEnv | (
380      read line
381      while [ "$line" != '' ]; do
382         local FeatureName=`echo "$line" | cut -f 1`
383         local EnvVariable=`echo "$line" | cut -f 2`
384         local EnvValue=`echo "$line" | cut -f 3`
385         local EnvOption=`echo "$line" | cut -f 4`
386         ProcessOneEnv "$FeatureName" "$EnvVariable" "$EnvValue" "$EnvOption" "$OutDirs" "$OutSet"
387         read line
388      done
389      return 0
390   ) || Error "Error processing environment" || exit 1
391
392   cat $InFeatures | (
393      read line
394      while [ "$line" != '' ]; do
395         local FeaturePath=`echo "$line" | cut -f 1`
396         local ShortName=`echo "$line" | cut -f 2 | StripDoubleQuotes`
397         local Description=`echo "$line" | cut -f 3 | StripDoubleQuotes`
398         local FeatureOptions=`echo "$line" | cut -f 4 | StripDoubleQuotes`
399         ProcessOneFeature "$FeaturePath" "$ShortName" "$Description" "$FeatureOptions" "$OutDirs" "$OutFeatures" "$OutSet"
400         read line
401      done
402      return 0
403   ) || Error "Error processing features" || exit 1
404
405# (PBR)
406# This test code didn't work. My hope was that I could force INSTALLLEVEL
407# to 4 and this would then enable the debug features.
408#   Xecho "<Publish Property=\"INSTALLLEVEL\" Value=\"4\" />" >> $OutSet
409#   Xecho "<Publish Event=\"SetInstallLevel\" Value=\"4\" />" >> $OutSet
410
411}
412
413# ProcessLinks(INLINKS, OUTFEATURES)
414# Process the INLINKS file, and produce XML on stdout.
415# Each line of the input file requires the creation
416# of a '.URL' file in the installation, and a Shortcut
417# in the Windows menu to point to that.
418# Also add the components generated to a feature, put in OUTFEATURES.
419#
420# TODO: We ought to have a Features column in the links.in file,
421# otherwise, the local doc link is always installed.
422#
423ProcessLinks() {
424   # Set a var to a carriage return without actually putting one in this file
425   local CR=`echo A | tr A '\015'`
426   local InLinks="infiles.tmp";  CleanInputFile "$1" "$InLinks" 3 4
427   local here_win=`cygpath -w $(pwd)`
428   # TODO: maybe get a real modification time, but not sure why we need it.
429   local MODTIMEHEX="0000000007DCC301DE"
430   XmlLevel=6
431   local OutFeatures="$2"
432
433   Xecho + "<Feature Id=\"LinksFeature\" Title=\"Links\"" >> $OutFeatures
434   Xecho "   Description=\"Links\" Display=\"hidden\"" >> $OutFeatures
435   Xecho "   Level=\"1\" AllowAdvertise=\"no\"" >> $OutFeatures
436   Xecho "   ConfigurableDirectory=\"INSTALLUTIL\"" >> $$OUTFeatures
437   Xecho "   Absent=\"disallow\">" >> $OutFeatures
438
439   Xecho "<DirectoryRef Id=\"INSTALLUTIL\">"
440   Xecho " <Directory Id=\"INSTALLURL\" Name=\"url\">"
441   Xecho "WIX_DB_SET_CURDIR(/installutil/url)"
442   cat $InLinks | (
443      read line
444      while [ "$line" != '' ]; do
445         local Shortname=`echo "$line" | cut -f 1 | StripDoubleQuotes`
446         local Name=`echo "$line" | cut -f 2 | StripDoubleQuotes`
447         local Url=`echo "$line" | cut -f 3 | StripDoubleQuotes`
448         read line
449
450         # We register the name .bdbsc extension to get the proper icon
451         local UrlName="$Shortname.bdbsc"
452         local UrlShortName="$Shortname.d1b"
453         local TargetFile="[INSTALLDIR]\\installutil\\url\\$UrlName"
454         local CreateUrlFile=true
455         local CommandShortcut=false
456	 local Program=""
457	 case "$Url" in
458         file:* ) CreateUrlFile=false
459                  TargetFile=`echo $Url | sed -e 's/file://'`
460                  TargetFile="[INSTALLDIR]"`cygpath -w $TargetFile`;;
461         cmd:* )  CreateUrlFile=false
462		  UrlName="$Shortname.bat"
463                  UrlShortName="$Shortname.bat"
464	          TargetFile="[INSTALLDIR]\\installutil\\url\\$UrlName"
465                  Program=`echo $Url | sed -e 's/cmd://'`
466                  CommandShortcut=true;;
467         esac
468
469         Xecho "WIX_DB_SET_CURFILE($Shortname)"
470         Xecho + "<Component Id=\"Links.$Shortname\""
471         Xecho "   $PERSISTGUID"
472         Xecho "   SharedDllRefCount=\"yes\" Location=\"either\">"
473
474         if $CreateUrlFile; then
475            echo "[Default]$CR"                 > $UrlName
476            echo "BASEURL=$Url$CR" | RunM4     >> $UrlName   || exit 1
477            echo "[InternetShortcut]$CR"       >> $UrlName
478            echo "URL=$Url$CR"     | RunM4     >> $UrlName   || exit 1
479            echo "Modified=$MODTIMEHEX$CR"     >> $UrlName
480            # TODO: we could have an Entry for IconFile=oracleweb.ico IconIndex=1?
481            echo ''
482            Xecho "<File Id=\"File.$Shortname\" "
483            Xecho "   LongName=\"$UrlName\" Name=\"$UrlShortName\""
484            Xecho "   Compressed=\"yes\" DiskId=\"1\""
485            Xecho "   src=\"$here_win\\$UrlName\" />"
486         fi
487
488         if $CommandShortcut; then
489	   echo "@echo off"                               > $UrlName
490           echo "set DBROOTDIR="                         >> $UrlName
491           echo "for /F \"tokens=3 delims=	\" %%A in ('REG QUERY \"HKLM\\SOFTWARE\\Oracle\\$PRODUCT_NAME\\$PRODUCT_VERSION\" /v RootDirectory') do set DBROOTDIR=%%A"                                   >> $UrlName
492           echo "if ERRORLEVEL 2 goto MISSING"           >> $UrlName
493           echo "if not defined DBROOTDIR goto MISSING"  >> $UrlName
494           echo "set FN=\"%DBROOTDIR%$Program\""         >> $UrlName
495           echo "if not exist %FN% goto NOTFOUND"        >> $UrlName
496           echo "cmd /k \"%DBROOTDIR%$Program\"$CR"      >> $UrlName
497           echo "goto END"                               >> $UrlName
498           echo ":NOTFOUND"                              >> $UrlName
499           echo "echo"                                   >> $UrlName
500           echo "echo  Error: The program does not appear to be installed." >> $UrlName
501           echo "echo"                                   >> $UrlName
502           echo "cmd /k"                                 >> $UrlName
503           echo "goto END"                               >> $UrlName
504           echo ":MISSING"                               >> $UrlName
505           echo "echo"                                   >> $UrlName
506           echo "echo NOTE:"                             >> $UrlName
507           echo "echo   The $PRODUCT_NAME version could not be determined." >> $UrlName
508           echo "echo   If you are running on Windows 2000, make sure the" >> $UrlName
509           echo "echo   REG.EXE program is installed from the Tools disk" >> $UrlName
510           echo "echo"                                   >> $UrlName
511           echo "cmd /k"                                 >> $UrlName
512           echo ":END"                                   >> $UrlName
513
514           Xecho "<File Id=\"File.$Shortname\" "
515           Xecho "   LongName=\"$UrlName\" Name=\"$UrlShortName\""
516           Xecho "   Compressed=\"yes\" DiskId=\"1\""
517           Xecho "   src=\"$here_win\\$UrlName\" />"
518
519           Xecho "<Shortcut Id=\"Short.$Shortname\" Directory=\"BerkeleyDbMenu\""
520           Xecho "   Name=\"$Shortname\" LongName=\"$Name\""
521           Xecho "   WorkingDirectory=\"INSTALLDIR\""
522           Xecho "   Target='$TargetFile'"
523           Xecho "   Show=\"normal\" />"
524         else
525           Xecho "<Shortcut Id=\"Short.$Shortname\" Directory=\"BerkeleyDbMenu\""
526           Xecho "   Name=\"$Shortname\" LongName=\"$Name\""
527           Xecho "   Target='$TargetFile'"
528           Xecho "   Show=\"normal\" />"
529         fi
530     
531
532         Xecho - "</Component>"
533
534         Xecho "<ComponentRef Id=\"Links.$Shortname\" />" >> $OutFeatures
535      done
536      return 0
537   ) || Error "Error processing links" || exit 1
538
539   Xecho "</Directory>"
540   Xecho "</DirectoryRef>"
541   Xecho - "</Feature>"  >> $OutFeatures
542}
543
544# ProcessOneDirectory(DIRECTORYNAME)
545# Called by ProcessFeatures.
546# Argument is the directory name to process
547# Standard input is cleaned up files.in (dirname is 3rd column)
548# Standard output will be WiX XML Component/File entries
549#
550ProcessOneDirectory()
551{
552  Dir="$1"
553  grep "	${Dir}	" | (
554    read line
555    while [ "$line" != '' ]; do
556      local feature=`echo "$line" | cut -f 1`
557      local srcfile=`echo "$line" | cut -f 2`
558      local targetdir=`echo "$line" | cut -f 3`
559      local shortname=`echo "$line" | cut -f 4`
560
561      ProcessOneDirectoryFile "$feature" "$srcfile" "$targetdir" "$shortname" || exit 1
562      read line
563    done
564    return 0
565  ) || Error "Error processing directory $Dir" || exit 1
566}
567
568# ProcessOneDirectoryFile(DIRECTORYNAME)
569# Called by ProcessOneDirectory to process a single file in a directory.
570# Standard output will be a single WiX XML Component/File entries
571#
572ProcessOneDirectoryFile()
573{
574   local feature="$1"
575   local srcfile="$2"
576   local targetdir="$3"
577   local shortname="$4"
578   local base=`basename $srcfile`
579
580   #echo "processing file $srcfile in $feature to directory $targetdir..." >&2
581
582   # Prepend the WIX_DB_TOP unless the source file is absolute
583
584   local root=
585   local checkfile=
586   local wsrcfile=
587
588   case "$srcfile" in
589   /* )  root=""
590         wsrcfile=`cygpath -w $srcfile`
591         checkfile="$srcfile"
592         ;;
593   * )   root="$PRODUCT_BLDDIR/"
594         wsrcfile="WIX_DB_TOP()\\`cygpath -w $srcfile`"
595         checkfile="$PRODUCT_BLDDIR/$srcfile"
596         ;;
597   esac
598
599   # If srcfile ends in / then we'll use tallow to walk the directory
600   case "$srcfile" in
601    */ ) if [ ! -d "$root$srcfile" ]; then
602            Error "$root$srcfile: not a directory"
603            exit 1
604         fi
605         Progress -minor "  expanding $root$srcfile..."
606         RunTallow "$root$srcfile"
607         return 0
608    ;;
609    *'*'* )
610         local dirname=`dirname "$root$srcfile"`
611         RunTallow "$dirname" -df "$base"
612         return 0
613    ;;
614   esac
615
616   if [ "$shortname" = '' ]; then
617     shortname=`GetShortName "$base"`
618   fi
619   ID=`NextId`
620
621   if [ ! -r "$checkfile" ]; then
622     Error "$srcfile: file in feature $feature does not exist"
623     Error "  curdir=`pwd`, pathname=$checkfile"
624     exit 1
625   fi
626
627   Xecho "WIX_DB_SET_CURFILE(${base})"
628   Xecho + "<Component Id=\"$feature.$ID\" Location=\"either\" $PERSISTGUID>"
629   Xecho "<File Id=\"${feature}.${ID}\" Name=\"${shortname}\" LongName=\"${base}\" Compressed=\"yes\" KeyPath=\"yes\" DiskId=\"1\" src=\"${wsrcfile}\" />"
630   Xecho - "</Component>"
631   return 0
632}
633
634# ProcessOneFeature(FEATUREPATH, SHORTNAME, DESCRIPTION, OPTS, INDIRECTORYFILE,
635#                   OUTFEATURE, OUTSET)
636# Called by ProcessFeatures to process a line in the features.in file.
637# The first three arguments are the values of the first three columns:
638# the feature dependency path (e.g. "Java/JavaExamples"), the short
639# name, and a descriptive name.  The last argument is the directory
640# file that lists the components.  We use the last name of the feature
641# path (e.g. JavaExamples) to locate all components (really directories)
642# named accordingly, so they can be listed as needed parts of the Feature.
643# Standard output will be WiX XML Feature entries.
644#
645ProcessOneFeature() {
646      local featurename="$1"
647      local shortname="$2"
648      local opts="$4"
649      local dirfile="$5"
650      local outfeature="$6"
651      local outset="$7"
652
653      XmlLevel=4
654      local featcount=0
655      local featurestring=""
656      if [ $(SlashCount $featurename) -gt 0 ]; then
657         local parent=`echo $featurename | sed -e 's:/[^/]*$::' -e 's:.*/::'`
658         featurename=`echo $featurename | sed -e 's:^.*/::'`
659         featcount=1
660         Xecho "<FeatureRef Id=\"$parent\">" >> $outfeature
661      fi
662
663
664      # TODO: how to get +default to work?
665      #    have tried messing with level="0" (doesn't show it)
666      #    InstallDefault=\"source\" (doesn't make a difference)
667      #
668      local leveldebug="Level=\"4\""
669      local levelparam="Level=\"1\""
670      local leveldisable="Level=\"0\""
671      local defparam="InstallDefault=\"source\""
672      local displayparam="Display=\"expand\""
673      local params="AllowAdvertise=\"no\""
674      local regfeature=""
675
676      local descparam=""
677      if [ "$3" != '' ]; then
678         descparam="Description=\"$3\""
679      fi
680
681      local opt
682      local reqtext=""
683      local isdebugFeature=false
684      for opt in $opts; do
685        case "$opt" in
686        +default )         ;;
687        +required )        params="$params Absent=\"disallow\""
688                           reqtext=" (required)";;
689        +invisible )       displayparam="Display=\"hidden\""
690                           params="$params Absent=\"disallow\"";;
691        +debug )           isdebugFeature=true;;
692
693        * )                Error "features.in: Bad option $opt"
694                           exit 1;;
695        esac
696      done
697
698
699# (PBR)
700# I tried to get debugging features to work but I could not. The last thing I
701# tried was to set either ADDSOURCE or INSTALLLEVEL. Neither of these solutions
702# will cause the feature conditions to rerun. The only thing I've found to do
703# that is to rerun CostFinalize. The problem with this is it will re-enable all
704# the options again. I'm keeping the basic framework for +debug support
705      if "$isdebugFeature"; then
706        regfeature="${featurename:1}"
707        echo "regfeature = $regfeature"
708
709        Xecho + "<Feature Id=\"$featurename\" Title=\"$shortname$reqtext\" $descparam $params $displayparam $leveldebug $defparam>" >> $outfeature
710      else
711        Xecho + "<Feature Id=\"$featurename\" Title=\"$shortname$reqtext\" $descparam $params $displayparam $levelparam $defparam>" >> $outfeature
712      fi
713
714
715      grep 'Component.*Id="'$featurename'[.0-9]*"' "$dirfile" | sed -e 's/\(Id="[^"]*"\).*/\1 \/>/' -e 's/Component /ComponentRef /' >> $outfeature
716
717      # Create a separate subfeature for any environment variables
718      # associated with the main feature.
719      # The <Condition> stuff is the magic that enables/disables
720      # setting the environment variables depending on the check box property.
721
722      Xecho + "<Feature Id=\"env.$featurename\" Title=\"env vars\" $params Display=\"hidden\" $levelparam $defparam>" >> $outfeature
723
724      Xecho "<Condition $leveldisable><![CDATA[EnvironmentSetCheck<>1]]></Condition>" >> $outfeature
725      Xecho "<Condition $levelparam><![CDATA[EnvironmentSetCheck=1]]></Condition>" >> $outfeature
726      grep 'Component.*Id="env\.'$featurename'[.0-9]*"' "$dirfile" | sed -e 's/\(Id="[^"]*"\).*/\1 \/>/' -e 's/Component /ComponentRef /' >> $outfeature
727
728      Xecho - "</Feature>" >> $outfeature
729      Xecho - "</Feature>" >> $outfeature
730
731      while [ "$featcount" -gt 0 ]; do
732         Xecho - "</FeatureRef>"  >> $outfeature
733         featcount=$(($featcount - 1))
734      done
735
736      # Append the name to the feature list if it is to be installed.
737      # This publish fragment gets 'executed' when leaving the
738      # dialog to select features.  Note that we have to quote
739      # the comma for m4 (`,') since this appears in a macro usage.
740      #
741
742# (PBR)
743# This code sets ADDSOURCE to show which debug options to include. This does not work
744      # If this is a debug feature, only turn on the value if the parent is on
745      if "$isdebugFeature"; then
746 	  regfeature="${featurename:1}"
747
748#          Xecho "<Publish Property=\"ADDSOURCE\" Value=\"[ADDSOURCE]\`,'\">" >> $outset
749#          Xecho "  <![CDATA[&${regfeature} = 3 AND DebugCheck=\"yes\" AND ADDSOURCE <> NULL]]></Publish>" >> $outset
750
751#          Xecho "<Publish Property=\"ADDSOURCE\" Value=\"[ADDSOURCE]${featurename}\">" >> $outset
752#          Xecho "  <![CDATA[&${regfeature} = 3 AND DebugCheck=\"yes\"]]></Publish>" >> $outset
753
754          Xecho "<Publish Property=\"FeatureList\" Value=\"[FeatureList]\`,' ${shortname}\">" >> $outset
755          Xecho "  <![CDATA[&${regfeature} = 3 AND DebugCheck=\"yes\"]]></Publish>" >> $outset
756
757      else
758        Xecho "<Publish Property=\"FeatureList\" Value=\"[FeatureList]\`,' ${shortname}\">" >> $outset
759        Xecho "  <![CDATA[&${featurename} = 3]]></Publish>" >> $outset
760      fi
761}
762
763# ProcessOneEnv(FEATURE, ENVNAME, ENVVALUE, OPTS, OUTDIRS, OUTSET)
764# Called by ProcessFeatures to process a line in the environment.in file.
765# The four arguments are the values of the four columns.
766# The output will be into two files:
767#   OUTDIRS: WiX XML Component entries, that contain environment values.
768#            This controls the actual setting of the variables.
769#   OUTSET:   WiX XML to set the installer variables if an env variable
770#             is set or a feature selected.
771#
772ProcessOneEnv() {
773      local feature="$1"
774      local envname="$2"
775      local envvalue="$3"
776      local opts="$4"
777      local outdirs="$5"
778      local outset="$6"
779
780      # Make the path uniform.
781      # echo "c:\Program Files\...\/Lib/Hello" | sed -e 's:\\/:\\:' -e 's:/:\\:g`
782      # This produces c:\Program Files\...\Lib\Hello
783      case "$envvalue" in
784      /* ) envvalue=`echo "$envvalue" | sed -e 's:^/::'`
785      esac
786
787      local path="[INSTALLDIR]$envvalue"
788
789      local opt
790      part="last"
791      for opt in $opts; do
792        case "$opt" in
793        +first )         part="first";;
794        +last )          part="last";;
795        * )                Error "environment.in: Bad option $opt"
796                           exit 1;;
797        esac
798      done
799
800      # Generate the OUTDIRS fragment
801      # This looks like:
802      #
803      # <Component Id="env.CoreAPI.43" Guid="4B75755F-1129-292C-3434-238410000247">
804      #   <Environment Id="env.44" Name="+-LIB" Action="set"
805      #     Permanent="no" Part="first" Value="[INSTALLDIR]Lib" />
806      # </Component>
807      #
808      # Having a unique guid makes uninstall work.
809      # Note: We really want these installed as System rather than
810      # User vars (using the System="yes" tag), but only if user
811      # installs for *all* users.  There is no convenient way to
812      # do that, so we leave them as default (User variables).
813
814
815      XmlLevel=4
816      local Id=`NextId`
817      Xecho "WIX_DB_SET_CURFILE(${envname})" >> $outdirs
818      Xecho + "<Component Id=\"env.$feature.$Id\" $PERSISTGUID>"    >> $outdirs
819      Id=`NextId`
820
821      Xecho "<Environment Id=\"env.$Id\" Name=\"+-$envname\" Action=\"set\""  >> $outdirs
822      Xecho "  Permanent=\"no\" Part=\"$part\" Value=\"$path\" />"  >> $outdirs
823
824      Xecho "</Component>"   >> $outdirs
825
826      # Generate the OUTSET fragment
827      # This looks like:
828      #
829      #    <Publish Property="CLASSPATHValue" Value="[INSTALLDIR]Lib/db.jar;[CLASSPATHValue]">
830      #      <![CDATA[&JavaAPI = 3]]></Publish>
831      #    <Publish Property="CLASSPATHEscValue" Value="[INSTALLDIR]Lib/db.jar;[CLASSPATHEscValue]">
832      #      <![CDATA[&JavaAPI = 3]]></Publish>
833      #
834      # This is equivalent to pseudocode:
835      #      if (InstallFeature(JavaAPI)) {
836      #           Prepend CLASSPATHValue with "Lib/db.jar;"
837      #           Prepend CLASSPATHEscValue with "Lib/db.jar;"
838      #      }
839      #
840      XmlLevel=4
841      Xecho "<Publish Property=\"${envname}Value\" Value=\"[INSTALLDIR]${envvalue};[${envname}Value]\">" >> $outset
842      Xecho "   <![CDATA[&${feature} = 3]]></Publish>" >> $outset
843
844      Xecho "<Publish Property=\"${envname}EscValue\" Value=\"[INSTALLDIR]${envvalue};[${envname}EscValue]\">" >> $outset
845      Xecho "   <![CDATA[&${feature} = 3]]></Publish>" >> $outset
846
847      
848}
849
850# CreateProperty(ID, VALUE)
851# Generate a <Property...> tag on the stdout
852CreateProperty() {
853   Xecho "<Property Id=\"$1\" Hidden=\"yes\"><![CDATA[$2]]></Property>"
854}
855
856# ProcessTagProperties(OUTPROPS)
857# Generate some identification tags as properties.
858# This will let us look at an installer and figure out
859# when it was built, etc.
860ProcessTagProperties() {
861   local outprops="$1"
862   local insdate=`date`
863   XmlLevel=4
864
865   CreateProperty _DB_MSI_INSTALLER_DATE "$insdate"   >> $outprops
866   CreateProperty _DB_MSI_PRODUCT_NAME "$PRODUCT_NAME" >> $outprops
867   CreateProperty _DB_MSI_PRODUCT_VERSION "$PRODUCT_VERSION" >> $outprops
868   CreateProperty ARPCOMMENTS "Installer for $PRODUCT_NAME $PRODUCT_VERSION built on $insdate" >> $outprops
869}
870    
871# ProcessEnv(INENVFILE, INBATFILE, OUTPROPS, OUTSET, OUTSHOW)
872# We generate some Property magic to show the user what is set.
873#
874ProcessEnv() {
875   InEnv="inenv.tmp"; CleanInputFile "$1" "$InEnv" 3 4
876   inbat="$2"
877   outprops="$3"
878   outset="$4"
879   outshow="$5"
880
881   # Get a list of the environment variables
882   local envvar
883   local envvars=`cut -f 2 < $InEnv | sort | uniq`
884
885   # For each environment var, create lines that declare
886   # a pair of properties in the envprops.wixinc file like:
887   #
888   #  <Property Id="CLASSPATHValue" Hidden="yes"></Property>
889   #  <Property Id="CLASSPATHEscValue" Hidden="yes"></Property>
890   #
891   # And create lines in the envset.wixinc file like:
892   #
893   #   <Publish Property="CLASSPATHValue" Value="%CLASSPATH%">
894   #        <![CDATA[1]]></Publish>
895   #   <Publish Property="CLASSPATHEscValue" Value="\\%CLASSPATH\\%">
896   #        <![CDATA[1]]></Publish>
897   #
898   # More will be added to that file later.
899   # Then, create lines in the envshow.wixinc file like:
900   #
901   #  <Control Id="CLASSPATHText" Type="Text"
902   #               X="23" Width="316" PARTIALHEIGHT(10, 2)
903   #               TabSkip="no" Text="CLASSPATH:" />
904   #
905   #  <Control Id="CLASSPATHValueText" Type="Text"
906   #               X="37" Width="316" PARTIALHEIGHT(20, 7)
907   #               TabSkip="no" Text="[CLASSPATHValue]" />
908
909   for envvar in $envvars; do
910      XmlLevel=4
911      CreateProperty "${envvar}Value" "" >> $outprops
912      CreateProperty "${envvar}EscValue" "" >> $outprops
913
914      XmlLevel=4
915      Xecho "<Publish Property=\"${envvar}Value\" Value=\"%${envvar}%\">" >> $outset
916      Xecho "      <![CDATA[1]]></Publish>" >> $outset
917      Xecho "<Publish Property=\"${envvar}EscValue\" Value=\"\\%${envvar}\\%\">" >> $outset
918      Xecho "      <![CDATA[1]]></Publish>" >> $outset
919
920      XmlLevel=4
921      Xecho "<Control Id=\"${envvar}Text\" Type=\"Text\""  >> $outshow
922      Xecho "         X=\"23\" Width=\"316\" PARTIALHEIGHT(10, 2)" >> $outshow
923      Xecho "         TabSkip=\"no\" Text=\"${envvar}:\" />"  >> $outshow
924
925      Xecho "<Control Id=\"${envvar}ValueText\" Type=\"Text\"" >> $outshow
926      Xecho "         X=\"37\" Width=\"316\" PARTIALHEIGHT(20, 7)" >> $outshow
927      Xecho "         TabSkip=\"no\" Text=\"[${envvar}Value]\" />" >> $outshow
928
929   done
930
931   # Create the dbvars.bat file from the .bat template file
932   # TODO: the bat template file currently knows the variables
933   #       and their values, it should get them from the environment.in
934
935   RunM4 <"$inbat" >"$PRODUCT_STAGE/dbvars.bat" || Error "m4 failed" || exit 1
936}
937
938
939# CleanInputFile(INFILENAME, OUTFILENAME, MINELEMENTS, MAXELEMENTS)
940# A filter to preprocess and validate input files.
941# We end up without comment lines, a single tab between elements,
942# and a trailing tab.
943# Also some selected shell variables are expanded for convenience.
944# We verify that each line has the number of elements that fall within
945# the given min and max.
946#
947CleanInputFile() {
948   sed \
949        -e 's/#.*//' \
950        -e 's/ *	/	/g' \
951        -e 's/	 */	/g' \
952        -e '/^[ 	]*$/d' \
953        -e 's/$/	/' \
954        -e 's/		*/	/g'  \
955        -e 's:\${PRODUCT_VERSION}:'"${PRODUCT_VERSION}":g \
956        -e 's:\${PRODUCT_MAJOR}:'"${PRODUCT_MAJOR}":g \
957        -e 's:\${PRODUCT_MINOR}:'"${PRODUCT_MINOR}":g \
958        -e 's:\${PRODUCT_PATCH}:'"${PRODUCT_PATCH}":g \
959        -e 's:\${PRODUCT_MAJMIN}:'"${PRODUCT_MAJMIN}":g \
960        -e 's:\${PRODUCT_STAGE}:'"${PRODUCT_STAGE}":g \
961	-e 's:\${PRODUCT_SHARED_WINMSIDIR}:'"${PRODUCT_SHARED_WINMSIDIR}":g \
962        -e 's/^[\r \t]*$//' \
963          < "$1" > "$2"
964
965   # count tabs on each line
966   sed -e 's/[^\t]//g' -e 's/[\t]/x/g' < "$2" | (
967        read line
968        linecount=1
969        while [ "$line" != '' ]; do
970            chars=`echo "$line" | wc -c`
971            chars=$(($chars - 1))  # Remove newline
972            if [ "$chars" -lt "$3" -o "$chars" -gt "$4" ]; then
973               Error "$1: Input file error on or after line $linecount"
974            fi
975            read line
976            linecount=$(($linecount + 1))
977        done
978   )
979}
980
981# StripDoubleQuotes()
982# In some input files, we allow double quotes around
983# multi-word strings for readability.  We strip them
984# here from standard input and write to standard output.
985# We only expect them at the beginning and end.
986#
987StripDoubleQuotes() {
988   sed -e 's/^"//' -e 's/"$//'
989}
990
991# IndentXml(PLUSMINUS_ARG)
992# A global variable $XmlLevel is kept for the indent level.
993# Every call creates blank output that matches the indent level.
994# In addition, with a '-' argument, the indent level
995# decrements by one before printing.
996# With a '+', the indent level increments after printing.
997# This is generally just used by Xecho
998#
999XmlLevel=0
1000IndentXml() {
1001   if [ "$1" = '-' -a $XmlLevel != 0 ]; then
1002     XmlLevel=$(($XmlLevel - 1))
1003   fi
1004   local idx=0
1005   while [ "$idx" != "$XmlLevel" ]; do
1006     echo -n '  '
1007     idx=$(($idx + 1))
1008   done
1009   if [ "$1" = '+' ]; then
1010     XmlLevel=$(($XmlLevel + 1))
1011   fi
1012}
1013
1014# Xecho [ - | + ] ...
1015# echoes arguments (like) echo, except that the output
1016# is indented for XML first.  If +, the indentation changes
1017# after printing, if -, the indentation changes before printing.
1018#
1019Xecho()
1020{
1021   local xarg=
1022   if [ "$1" = '-' -o "$1" = '+' ]; then
1023      xarg="$1"
1024      shift
1025   fi
1026   IndentXml $xarg
1027   echo "$@"
1028}
1029
1030# SlashCount(PATH)
1031# Returns the number of slashes in its argument
1032# Note, we are relying on some advanced
1033# features of bash shell substitution
1034#
1035SlashCount()
1036{
1037   local allslash=`echo "$1" | sed -e 's:[^/]*::g'`
1038   echo "${#allslash}"
1039}
1040
1041# ProcessDirTransition(PREVDIR, NEXTDIR)
1042# Used by ProcessFeatures to create the parts
1043# of an WiX <Directory> heirarchy (on stdout) needed to
1044# transition from directory PREVDIR to NEXTDIR.
1045# This may include any needed </Directory> entries as well.
1046# For example, ProcessDirTransition /Bin/Stuff /Bin/Foo/Bar
1047# produces:
1048#    </Directory>      ...to go up one from 'Stuff'
1049#    <Directory Foo>
1050#      <Directory Bar>
1051#
1052ProcessDirTransition() {
1053   local p="$1"
1054   local n="$2"
1055   if [ "$p" = '' ]; then p=/; fi
1056   if [ "$n" = '' ]; then n=/; fi
1057   local nextdir="$2"
1058
1059   # The number of slashes in $p is the current directory level.
1060   XmlLevel=$(($(SlashCount $p) + 4))
1061
1062   while [ "$p" != / ]; do
1063      if [ "${n#${p}}" != "$n" ]; then
1064         break
1065      fi
1066
1067      # go up one level, and keep $p terminated with a /
1068      p=`dirname $p`
1069      case "$p" in
1070      */ )   ;;
1071      * )    p=$p/;;
1072      esac
1073      Xecho - "</Directory>"
1074   done
1075   n=${n#${p}}
1076   while [ "$n" != '' ]; do
1077      local dirname=`echo $n | sed -e 's:/.*::'`
1078      local cleanname=`CleanFileName "$dirname"`
1079      local shortname=`GetShortName "$cleanname"`
1080      local dirid=`NextId`
1081
1082      local larg=""
1083      if [ "${shortname}" != "${dirname}" ]; then
1084          larg="LongName=\"${dirname}\""
1085      fi
1086      Xecho + "<Directory Id=\"${cleanname}Dir.$dirid\" Name=\"${shortname}\" $larg>"
1087
1088      n=`echo $n | sed -e 's:^[^/]*/::'`
1089   done
1090
1091   Xecho "WIX_DB_SET_CURDIR($nextdir)"      # Tell the m4 macro what the current dir is
1092}
1093
1094# SetupErrorLog()
1095# Given the global variable ERRORLOG for the name of the
1096# error output file, do any setup required to make that happen.
1097#
1098SetupErrorLog() {
1099
1100    # Before we start to use ERRORLOG, we get a full pathname,
1101    # since the caller may change directories at times.
1102    case "$ERRORLOG" in
1103    /* ) ;;
1104    *)   ERRORLOG=`pwd`"/$ERRORLOG" ;;
1105    esac
1106
1107    rm -f $ERRORLOG
1108
1109    # File descriptor tricks.
1110    # Duplicate current stderr to 15, as we'll occasionally
1111    # need to report progress to it.  Then, redirect all
1112    # stderr from now on to the ERRORLOG.
1113    # 
1114    exec 15>&2
1115    exec 2>>$ERRORLOG
1116}
1117
1118# RequireCygwin
1119# Cygwin does not install certain needed components by default.
1120# Check to make sure that everything needed by the script
1121# and functions is here.
1122#
1123RequireCygwin() {
1124    Progress -minor "checking for Cygwin..."
1125    RequireFileInPath PATH "$PATH" m4
1126    RequireFileInPath PATH "$PATH" gcc
1127    RequireFileInPath PATH "$PATH" make
1128    RequireFileInPath PATH "$PATH" unzip
1129    RequireFileInPath PATH "$PATH" bc
1130    RequireFileInPath PATH "$PATH" openssl    # needed for MD5 hashing
1131}
1132
1133# RequireJava()
1134# A java SDK (with include files) must be installed
1135#
1136RequireJava() {
1137    Progress -minor "checking for Java..."
1138    RequireFileInPath INCLUDE "$INCLUDE" jni.h
1139    RequireFileInPath INCLUDE "$INCLUDE" jni_md.h
1140    RequireFileInPath PATH "$PATH" jar.exe
1141    RequireFileInPath PATH "$PATH" javac.exe
1142}
1143
1144# RequireTcl()
1145# A Tcl SDK (with compatible .lib files) must be installed
1146#
1147RequireTcl() {
1148    Progress -minor "checking for Tcl..."
1149    RequireFileInPath INCLUDE "$INCLUDE" tcl.h
1150    RequireFileInPath LIB "$LIB" tcl84g.lib
1151    RequireFileInPath LIB "$LIB" tcl84.lib
1152}
1153
1154# RequireWix()
1155# WiX must be installed
1156#
1157RequireWix() {
1158    Progress -minor "checking for WiX..."
1159    RequireFileInPath PATH "$PATH" candle.exe
1160    RequireFileInPath PATH "$PATH" light.exe
1161    RequireFileInPath PATH "$PATH" tallow.exe
1162}
1163
1164# RequirePerl()
1165# Perl must be installed
1166#
1167RequirePerl() {
1168    Progress -minor "checking for Perl..."
1169    RequireFileInPath PATH "$PATH" perl.exe
1170}
1171
1172# RequirePython()
1173# Python (and include files) must be installed
1174#
1175RequirePython() {
1176    Progress -minor "checking for Python..."
1177    RequireFileInPath INCLUDE "$INCLUDE" Python.h
1178    RequireFileInPath PATH "$PATH" python.exe
1179}
1180
1181# CreateDbPerl()
1182# Build Perl interface (for Berkeley DB only).
1183#
1184CreateDbPerl() {
1185
1186    # First build Berkeley DB using cygwin, as that version is
1187    # needed for the Perl build
1188    local here=`pwd`
1189    Progress "building using Cygwin tools (needed for perl)"
1190    cd "${PRODUCT_DBBUILDDIR}"
1191    insdir="${PRODUCT_STAGE}/install_unix"
1192    ../dist/configure --prefix="$insdir" >>$ERRORLOG  || exit 1
1193    make install   >>$ERRORLOG  || exit 1
1194
1195    Progress "building perl"
1196    cd ../perl/BerkeleyDB
1197    BERKELEYDB_INCLUDE="$insdir/installed_include" BERKELEYDB_LIB="$insdir/lib" \
1198         perl Makefile.PL >>$ERRORLOG   || exit 1
1199    make >>$ERRORLOG
1200    cd $here
1201}
1202
1203# CreateWindowsSystem()
1204# Copy Window system files
1205#
1206CreateWindowsSystem() {
1207    local here=`pwd`
1208    Progress "Copy Window system files..."
1209    cd "${PRODUCT_SUB_BLDDIR}"
1210    cp -f $SYSTEMROOT/system32/msvcr71.dll  build_windows/Release/ || exit 1
1211    cp -f $SYSTEMROOT/system32/msvcp71.dll  build_windows/Release/ || exit 1
1212    cp -f $SYSTEMROOT/system32/msvcr71.dll build_windows/Debug/ || exit 1
1213    cp -f $SYSTEMROOT/system32/msvcp71.dll build_windows/Debug/ || exit 1
1214    cd $here
1215}
1216
1217# CreateInclude(DIR, FILES)
1218# Create an include directory populated with the files given
1219#
1220CreateInclude() {
1221
1222    local incdir="$1"
1223    shift
1224
1225    Progress "creating the "$incdir" directory..."
1226    rm -rf "$incdir"
1227    mkdir "$incdir"  || exit 1
1228    cp -r "$@" "$incdir"
1229}
1230
1231# CreateWindowsBuild()
1232# Do the windows build as defined by the winbuild.bat file
1233#
1234CreateWindowsBuild() {
1235    local here=`pwd`
1236    Progress "building using Windows tools..."
1237    cd "${PRODUCT_SUB_BLDDIR}"  || exit 1
1238
1239    # Before starting, copy any installer tools here.
1240    # This makes building these tools straightforward
1241    # and the results are left in the build directory.
1242    #
1243    cp -r ${PRODUCT_SHARED_WINMSIDIR}/instenv .
1244
1245    # We create a wbuild.bat file, which is essentially
1246    # identical, except it has the carriage returns added.
1247    # This allows us to use our favorite editors on winbuild.bat .
1248    #
1249    sed -e 's/$//' < ${PRODUCT_STAGE}/../winbuild.bat | tr '\001' '\015'  > wbuild.bat
1250    # TODO: Needed?
1251    rm -f build_windows/Berkeley_DB.sln
1252    rm -f winbld.out winbld.err
1253    touch winbld.out winbld.err
1254    echo "Build output and errors are collected in" >> $ERRORLOG
1255    echo "  winbld.{out,err} until the build has completed." >> $ERRORLOG
1256    cmd.exe /x /c call wbuild.bat
1257    status=$?
1258    cat winbld.out >> $ERRORLOG
1259    if [ -s winbld.err -o "$status" != 0 ]; then
1260       cat winbld.err >> $ERRORLOG
1261       Error "Errors during windows build"
1262       exit 1
1263    fi
1264    cd $here
1265}
1266
1267# CreateSources(SOURCESDIR,DOCDIR...)
1268# Create the sources directory, ignoring things in the docdirs
1269#
1270CreateSources() {
1271    local sources="$1"
1272
1273    Progress "creating the Sources directory in $sources..."
1274    rm -rf "$sources"
1275    cp -r ${PRODUCT_SRCDIR} "$sources"  || exit 1
1276}
1277
1278# Usage()
1279# Show the usage for this script.
1280#
1281Usage()
1282{
1283    echo "Usage:  s_winmsi [ options ]" >&2
1284    echo "Options: " >&2
1285    echo "   -input file       use file rather than ${PRODUCT_ZIP_FILEFMT}" >&2
1286    echo "                     where X.Y.Z is defined by ../RELEASE" >&2
1287    echo "   -output file      use file rather than ${PRODUCT_MSI_FILEFMT}" >&2
1288    echo "                     where X.Y.Z is defined by ../RELEASE" >&2
1289    echo "   -usebuild DIR     use DIR for exes, DLLs, etc. " >&2
1290    echo "                     rather than building from scratch" >&2
1291    echo "   -preserve         preserve the winmsi/msi_staging directory" >&2
1292    echo "   -skipgen          skip generating m4 include files" >&2
1293}
1294
1295# SetupOptions()
1296# Parse command line options and set global variables as indicated below.
1297#
1298SetupOptions() {
1299    OPT_USEBUILD=
1300    OPT_PRESERVE=false
1301    OPT_INFILE=
1302    OPT_OUTFILE=
1303    OPT_SKIPGEN=false
1304    while [ "$#" -gt 0 ]; do
1305        arg="$1"; shift
1306        case "$arg" in
1307        -usebuild )  OPT_USEBUILD="$1"; shift ;;
1308        -skipgen )   OPT_SKIPGEN=true ;;
1309        -preserve )  OPT_PRESERVE=true;;
1310        -input )     OPT_INFILE="$1"; shift ;;
1311        -output )    OPT_OUTFILE="$1"; shift ;;
1312        * )
1313            echo "ERROR: Unknown argument '$arg' to s_winmsi" >&2
1314            Usage
1315            exit 1
1316            ;;
1317        esac
1318    done
1319    if [ "$OPT_INFILE" = '' -o ! -f "$OPT_INFILE" ]; then
1320        echo "$OPT_INFILE: not found"
1321        exit 1
1322    fi
1323}
1324
1325# CreateStage()
1326# Create the staging area
1327#
1328CreateStage() {
1329    Progress "creating staging area..."
1330    if [ "$PRODUCT_STAGE" = '' ]; then
1331        Error "PRODUCT_STAGE not set"
1332        exit 1
1333    fi
1334    if ! $OPT_PRESERVE; then
1335        trap 'rm -rf ${PRODUCT_STAGE} ; exit 0' 0 1 2 3 13 15
1336    fi
1337    rm -rf ${PRODUCT_STAGE}   || exit 1
1338    mkdir ${PRODUCT_STAGE}    || exit 1
1339
1340    cd ${PRODUCT_STAGE}
1341    
1342    Progress "extracting $OPT_INFILE..."
1343    unzip -q ../../$OPT_INFILE  || exit 1
1344    
1345    if [ ! -d $PRODUCT_LICENSEDIR ]; then
1346        Error "$OPT_INFILE: no top level $PRODUCT_LICENSEDIR directory"
1347        exit 1
1348    fi
1349}
1350
1351# CreateLicenseRtf(LICENSEIN, LICENSERTF)
1352# From a text LICENSE file, create the equivalent in .rtf format.
1353#
1354CreateLicenseRtf() {
1355    local licensein="$1"
1356    local licensertf="$2"
1357
1358    if [ ! -f "$licensein" ]; then
1359        Error "License file $licensein: does not exist"
1360        exit 1
1361    fi
1362    Progress "creating ${licensertf}..."
1363    
1364    # Build a list of references to components ids (i.e. directories)
1365    # that are listed in the .wxs file.  This is needed to refer to
1366    # all of the source (sadly it appears there is no better way!)
1367    #
1368    if ! grep '^=-=-=-=' $licensein > /dev/null; then
1369        Error "LICENSE has changed format, this script must be adapted"
1370        exit 1
1371    fi
1372    
1373    sed -e '1,/^=-=-=-=-=/d' < $licensein | MakeRtf > $licensertf
1374}
1375
1376    
1377# CreateMsi(INFILE,WXSFILE,MSIFILE)
1378# Do the final creation of the output .MSI file.
1379# It is assumed that all *.wixinc files are now in place.
1380# INFILE is an absolute name of the m4 input WiX file.
1381# WXSFILE is a short (basename) of the postprocessed WiX file,
1382#       after macro expansion, it will be left in staging directory.
1383# MSIFILE is a short (basename) of the output .MSI name
1384#
1385CreateMsi() {
1386    local infile="$1"
1387    local wxs="$2"
1388    local msifile="$3"
1389    local o=`echo "$wxs" | sed -e 's/[.]wxs$//' -e 's/$/.wixobj/'`
1390    local tmpfile="dbcore.wxs.tmp"
1391
1392    rm -f $o $wxs 
1393
1394    # Preprocess the ${PROD}wix.in file, adding the things we need
1395    #
1396    Progress "Running m4 to create $wxs..."
1397    RunM4 < "$infile" > "$PRODUCT_STAGE/$wxs"  || Error "m4 failed" || exit 1
1398    # Remove any stray "PUT-GUID-HERE" tags.
1399    # They are added by some recent versions of WiX, including 2.0.5805.0
1400    # The scripts already insert valid GUIDs.
1401    sed -e 's/ Guid="PUT-GUID-HERE" / /' < "$wxs" > "$tmpfile"
1402    rm -f "$wxs"
1403    mv "$tmpfile" "$wxs"
1404
1405    local here=`pwd`
1406    cd "$PRODUCT_STAGE"
1407    rm -f "$o" "$msifile"
1408    Progress "compiling $wxs..."
1409    candle -w0 $wxs  >> $ERRORLOG || Error "candle (compiler) failed" || exit 1
1410
1411    Progress "linking .msi file..."
1412    light -o "$msifile" $o  >> $ERRORLOG  || Error "light (linker) failed" || exit 1
1413    (rm -f "../../$msifile" && mv "$msifile" ../..)  >> $ERRORLOG  || exit 1
1414    cd $here
1415}
1416
1417# CreateWixIncludeFiles()
1418# Do all processing of input files to produce
1419# the include files that we need to process the Wix input file.
1420#
1421CreateWixIncludeFiles() {
1422    local here=`pwd`
1423    cd "$PRODUCT_STAGE"
1424    # Touch all the wix include files in case any end up empty.
1425    touch directory.wixinc features.wixinc envprops.wixinc \
1426          envset.wixinc envshow.wixinc links.wixinc
1427
1428    Progress "tagging the installer..."
1429    ProcessTagProperties envprops.wixinc
1430    
1431    Progress "processing environment..."
1432    ProcessEnv ../environment.in ../dbvarsbat.in envprops.wixinc envset.wixinc envshow.wixinc
1433    
1434    Progress "processing features and files..."
1435    ProcessFeatures ../files.in ../features.in ../environment.in \
1436                   directory.wixinc features.wixinc \
1437                   envset.wixinc
1438    
1439    Progress "processing links..."
1440    ProcessLinks ../links.in features.wixinc > links.wixinc
1441    cd $here
1442}
1443