2007-11-26 Marcus Brinkmann <marcus@g10code.de>
[gpg4win.git] / src / make-msi.pl
1 #! /usr/bin/perl -w
2 # make-msi.pl - MSI Installer for GnuPG 4 Windows.
3 # Copyright (C) 2007 g10 Code GmbH
4
5 # This file is part of Gpg4win.
6
7 # Gpg4win is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # Gpg4win is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20
21 use strict;
22 use warnings;
23 use diagnostics;
24
25 \f
26 # Default language.
27 $::lang = 'en';
28
29 \f
30 sub fail
31 {
32     print STDERR $_[0] . "\n";
33     exit 1;
34 }
35     
36 # We use a new product and package code for every build (using pseudo
37 # components), but a single constant upgrade code for all versions.
38 # Note that Windows installer ignores the build part of the version
39 # number (only the first three components are used).
40 #
41 # FIXME: Build upgrade table.
42 #
43 # We take a simplified view: Each file corresponds to exactly one
44 # component.  This means we need to generate GUIDs for these
45 # components and remember them from one installer version to the next.
46 # We do this automatically by means of a support file, make-msi.guids.
47
48 %::guid = ();
49 $::guid_file = 'make-msi.guids';
50 $::guid_changed = 0;
51
52 sub fetch_guids
53 {
54     # FIXME: Check if file exists.
55     open (FILE, "<$::guid_file") or return;
56     while (<FILE>)
57     {
58         next if (/^#/);
59         if (/(\S+)\s+(.+)\s*\r?\n$/)
60         {
61             $::guid{$2} = $1;
62         }
63     }
64     close (FILE);
65 }
66
67
68 sub store_guids
69 {
70     # FIXME: Maybe allow to forget unused GUIDs.
71
72     return if (not $::guid_changed);
73     print STDERR "GUID list stored in $::guid_file changed, please commit!\n";
74     open (FILE, ">$::guid_file.bak") or die;
75     print FILE "# This is an automatically generated file.  DO NOT EDIT.\n";
76     foreach my $file (sort keys %::guid)
77     {
78         print FILE "$::guid{$file} $file\n";
79     }
80     close FILE;
81     rename "$::guid_file.bak", $::guid_file or die;
82 }
83
84
85 sub get_guid
86 {
87     my ($file) = @_;
88     my $guid;
89
90     if (defined $::guid{$file})
91     {
92         return $::guid{$file};
93     }
94     # Need to generate a new GUID.
95     $::guid_changed = 1;
96     $guid = `uuidgen`;
97     chomp $guid;
98     $::guid{$file} = $guid;
99     return $guid;
100 }
101
102 \f
103 $::files_file = '';
104
105 # We store the list of included files for temporary packaging, in case
106 # WiX needs to be run on a different system.
107 sub store_files
108 {
109     my ($parser) = @_;
110
111     return if ($::files_file eq '');
112     open (FILE, ">$::files_file") or die;
113     foreach my $name (@{$parser->{pkg_list}})
114     {
115         my $pkg = $parser->{pkgs}->{$name};
116
117         next if ($#{$pkg->{files}} == -1);
118         print FILE (join ("\n", map { "src/" . ($_->{source}) }
119                           @{$pkg->{files}})). "\n";
120     }
121     close FILE;
122 }
123
124 \f
125 sub lang_to_lcid
126 {
127     my ($lang) = @_;
128
129     if ($lang eq 'en')
130     {
131         return 1033;
132     }
133     elsif ($lang eq 'de')
134     {
135         return 1031;
136     }
137     else
138     {
139         fail "language $lang not supported";
140     }
141 }
142         
143 \f
144 # NSIS parser
145
146 # The parser data structure contains the following members:
147 #
148 # pre_depth: The current nesting depth of preprocessor conditionals.
149 # pre_true:  Depth of the last preprocessor conditional that was true.
150 # pre_symbols: A hash of defined preprocessor symbols.
151 # po: A hash of languages, each a hash of translated strings.
152 # outpath: the current output path.
153 # includedirs: An array of include directories to search through.
154
155 # A couple of variables you can set:
156 $::nsis_parser_warn = 0;
157 $::nsis_parser_debug = 0;
158
159 $::nsis_level_default = 1;
160 $::nsis_level_optional = 1000;
161 $::nsis_level_hidden = 2000;
162
163 # Evaluate an expression.
164 sub nsis_eval
165 {
166     my ($parser, $file, $expr) = @_;
167     my $val = $expr;
168
169     # Resolve outer double quotes, if any.
170     if ($val =~ m/^"/)
171     {
172         if (not $val =~ s/^"(.*)"$/$1/)
173         {
174             fail "$file:$.: unmatched quote in expression: $expr";
175         }
176     }
177     
178     my $iter = 0;
179     while ($val =~ m/\${([^}]*)}/)
180     {
181         my $varname = $1;
182         my $varvalue;
183
184         if (exists $parser->{pre_symbols}->{$varname})
185         {
186             $varvalue = $parser->{pre_symbols}->{$varname};
187         }
188         else
189         {
190             fail "$file:$.: undefined variable $varname in expression: $expr";
191         }
192         $val =~ s/\${$varname}/$varvalue/g;
193
194         $iter++;
195         if ($iter > 100)
196         {
197             fail "$file:$.: too many variable expansions in expression: $expr";
198         }
199     }
200     
201 #    # FIXME: For now.
202 #    if ($expr =~ m/\$/ or $expr !~ m/^\"/)
203 #    {
204 #       return $expr;
205 #    }
206 #    $val = eval $expr;
207     return $val;
208 }
209
210
211 # Retrieve an evaluated symbol
212 sub nsis_fetch
213 {
214     my ($parser, $symname) = @_;
215
216     return undef if (not exists $parser->{pre_symbols}->{$symname});
217
218     return nsis_eval ($parser, '', $parser->{pre_symbols}->{$symname});
219 }
220
221
222 # Evaluate an expression.
223 sub nsis_translate
224 {
225     my ($parser, $file, $expr) = @_;
226     my $val = $expr;
227     my $iter = 0;
228
229     while ($val =~ m/\$\((.*)\)/)
230     {
231         my $var = $1;
232
233         if (exists $parser->{po}->{$::lang}->{$1})
234         {
235             my $subst = $parser->{po}->{$::lang}->{$1};
236             $val =~ s/\$\($var\)/$subst/g;
237         }
238         else
239         {
240             fail "$file:$.: no translation for $val to language $::lang";
241         }
242         $iter++;
243         if ($iter > 100)
244         {
245             fail "$file:$.: too deep nesting of translations";
246         }
247     }
248
249     # Resolve outer double quotes, if any.
250     $val =~ s/^"(.*)"$/$1/;
251     $val =~ s/\$\r/\r/g;
252     $val =~ s/\$\n/\n/g;
253     $val =~ s/\$\"/"/g;
254
255     return $val;
256 }
257
258
259 # Low level line input.
260 sub nsis_get_line
261 {
262     my ($file) = @_;
263     my $line = '';
264
265     while (<$file>)
266     {
267         $line = $line . $_;
268
269         # Strip leading whitespace.
270         $line =~ s/^\s*//;
271
272         # Strip newline and trailing whitespace.
273         $line =~ s/\s*\r?\n$//;
274
275         # Combine multiple lines connected with backslashes.
276         if ($line =~ m/^(.*)\\$/)
277         {
278             $line = $1 . ' ';
279             next;
280         }
281
282         $_ = $line;
283         last;
284     }
285
286     # Now break up the line into 
287     return $_;
288 }
289
290
291 # Tokenize the NSIS line.
292 sub nsis_tokenize
293 {
294     my ($file, $line) = @_;
295     my @tokens;
296
297     my @line = split ('', $line);
298     my $idx = 0;
299
300     while ($idx <= $#line)
301     {
302         # The beginning of the current partial token.
303         my $token = $idx;
304
305         if ($line[$idx] eq '"')
306         {
307             $idx++;
308             # Skip until end of string, indicated by double quote that
309             # is not part of the $\" string.
310             while ($idx <= $#line)
311             {
312                 if (substr ($line, $idx, 3) eq '$\\"')
313                 {
314                     $idx += 3;
315                 }
316                 else
317                 {
318                     last if ($line[$idx] eq '"');
319                     $idx++;
320                 }
321             }
322             fail "$file:$.:$idx: unterminated string from position $token"
323                 if ($idx > $#line);
324             $idx++;
325             fail "$file:$.:$idx: strings not separated"
326                 if ($idx <= $#line and $line[$idx] !~ m/\s/);
327         }
328         elsif ($line[$idx] eq '\'')
329         {
330             $idx++;
331             # Skip until end of string, indicated by a single quote.
332             while ($idx <= $#line)
333             {
334                 last if ($line[$idx] eq '\'');
335                 $idx++;
336             }
337             fail "$file:$.:$idx: unterminated string from position $token"
338                 if ($idx > $#line);
339             $idx++;
340             fail "$file:$.:$idx: strings not separated"
341                 if ($idx <= $#line and $line[$idx] !~ m/\s/);
342         }
343         else
344         {
345             # Skip until end of token indicated by whitespace.
346             while ($idx <= $#line)
347             {
348                 fail "$file:$.:$idx: invalid character"
349                     if ($line[$idx] eq '"');
350
351                 last if ($line[$idx] =~ m/\s/);
352                 $idx++;
353             }
354         }
355
356         push @tokens, substr ($line, $token, $idx - $token);
357
358         # Skip white space between arguments.
359         while ($idx <= $#line and $line[$idx] =~ m/\s/)
360         {
361             $idx++;
362         }
363     }
364     
365     return @tokens;
366 }
367
368
369 # We suppress some warnings after first time.
370 %::warn = ();
371
372 # Parse the NSIS line.
373 sub nsis_parse_line
374 {
375     my ($parser, $file, $line) = @_;
376
377     # We first tokenize the line.
378     my @tokens = nsis_tokenize ($file, $line); 
379
380     # We handle preprocessing directives here.
381         
382     print STDERR "Tokens: " . join (" AND ", @tokens) . "\n"
383         if $::nsis_parser_debug;
384
385     # We have special code dealing with ignored areas.
386     if ($parser->{pre_depth} > $parser->{pre_true})
387     {
388         if ($tokens[0] eq '!ifdef' or $tokens[0] eq '!ifndef')
389         {
390             fail "$file:$.: syntax error" if $#tokens != 1;
391             $parser->{pre_depth}++;
392         }
393         elsif ($tokens[0] eq '!else')
394         {
395             fail "$file:$.: stray !else" if $parser->{pre_depth} == 0;
396
397             if ($parser->{pre_depth} == $parser->{pre_true} + 1)
398             {
399                 $parser->{pre_true}++;
400             }
401         }
402         elsif ($tokens[0] eq '!endif')
403         {
404             fail "$file:$.: syntax error" if $#tokens != 0;
405
406             fail "$file:$.: stray !endif" if $parser->{pre_depth} == 0;
407
408             $parser->{pre_depth}--;
409         }
410         elsif ($tokens[0] eq '!macro')
411         {
412             fail "$file:$.: syntax error" if $#tokens < 1;
413
414             # FIXME: We do not support macros at this point, although
415             # support would not be too hard to add.  Instead, we just
416             # ignore their definition so it does not throw us off.
417
418             print STDERR
419                 "$file:$.: warning: ignoring macro $tokens[1]\n"
420                 if $::nsis_parser_warn;
421
422             $parser->{pre_depth}++;
423         }
424         elsif ($tokens[0] eq '!macroend')
425         {
426             # FIXME: See !macro.
427             fail "$file:$.: stray !macroend" if $parser->{pre_depth} == 0;
428             $parser->{pre_depth}--;
429         }
430     }
431     else
432     {
433         # This is the parser for areas not ignored.
434         if ($tokens[0] eq '!define')
435         {
436             if ($#tokens == 1)
437             {
438                 # FIXME: Maybe define to 1?
439                 $parser->{pre_symbols}->{$tokens[1]} = '';
440             }
441             elsif ($#tokens == 2)
442             {
443                 $parser->{pre_symbols}->{$tokens[1]} =
444                     nsis_eval ($parser, $file, $tokens[2]);
445             }
446             else
447             {
448                 fail "$file:$.: syntax error";
449             }
450
451         }
452         elsif ($tokens[0] eq '!undef')
453         {
454             fail "$file:$.: syntax error" if $#tokens != 1;
455             delete $parser->{pre_symbols}->{$tokens[1]};
456         }
457         elsif ($tokens[0] eq '!ifdef')
458         {
459             fail "$file:$.: syntax error" if $#tokens != 1;
460
461             if (exists $parser->{pre_symbols}->{$tokens[1]})
462             {
463                 $parser->{pre_true}++;
464             }
465             $parser->{pre_depth}++;
466         }
467         elsif ($tokens[0] eq '!ifndef')
468         {
469             fail "$file:$.: syntax error" if $#tokens != 1;
470
471             if (not exists $parser->{pre_symbols}->{$tokens[1]})
472             {
473                 $parser->{pre_true}++;
474             }
475             $parser->{pre_depth}++;
476         }
477         elsif ($tokens[0] eq '!else')
478         {
479             fail "$file:$.: stray !else" if $parser->{pre_depth} == 0;
480
481             if ($parser->{pre_depth} == $parser->{pre_true})
482             {
483                 $parser->{pre_true}--;
484             }
485             elsif ($parser->{pre_depth} == $parser->{pre_true} + 1)
486             {
487                 $parser->{pre_true}++;
488             }
489         }
490         elsif ($tokens[0] eq '!endif')
491         {
492             fail "$file:$.: syntax error" if $#tokens != 0;
493
494             fail "$file:$.: stray !endif" if $parser->{pre_depth} == 0;
495
496             if ($parser->{pre_depth} == $parser->{pre_true})
497             {
498                 $parser->{pre_true}--;
499             }
500             $parser->{pre_depth}--;
501         }
502         elsif ($tokens[0] eq '!include')
503         {
504             fail "$file:$.: syntax error" if $#tokens != 1;
505
506             print STDERR "Including $tokens[1]\n"
507                 if $::nsis_parser_debug;
508
509             my $filename = nsis_eval ($parser, $file, $tokens[1]);
510
511             # Recursion.
512             nsis_parse_file ($parser, $filename);
513         }
514         elsif ($tokens[0] eq '!macro')
515         {
516             fail "$file:$.: syntax error" if $#tokens < 1;
517
518             # FIXME: We do not support macros at this point, although
519             # support would not be too hard to add.  Instead, we just
520             # ignore their definition so it does not throw us off.
521
522             print STDERR
523                 "$file:$.: warning: ignoring macro $tokens[1]\n"
524                 if $::nsis_parser_warn;
525
526             $parser->{pre_depth}++;
527         }
528         elsif ($tokens[0] eq '!macroend')
529         {
530             # FIXME: See !macro.
531             fail "$file:$.: stray !macroend" if $parser->{pre_depth} == 0;
532             $parser->{pre_depth}--;
533         }
534         elsif ($tokens[0] eq '!cd' or $tokens[0] eq '!addplugindir')
535         {
536             if (not exists $::warn{"directive-$tokens[0]"})
537             {
538                 print STDERR
539                     "$file:$.: warning: ignoring $tokens[0] directive\n"
540                 if $::nsis_parser_warn;
541             }
542             $::warn{"directive-$tokens[0]"}++;
543         }
544         elsif ($tokens[0] eq '!addincludedir')
545         {
546             fail "$file:$.: syntax error" if $#tokens != 1;
547
548             my $dir = nsis_eval ($parser, $file, $tokens[1]);
549
550             unshift @{$parser->{includedirs}}, $dir;
551         }
552         elsif ($tokens[0] =~ m/^\!/ and $tokens[0] ne '!insertmacro')
553         {
554             # Note: It is essential that some !insertmacro invocations are
555             # not expanded, namely those of SelectSection and UnselectSection,
556             # which are used to track dependencies in Gpg4win.
557
558             fail "$file:$.: compiler directive $tokens[0] not implemented";
559         }
560         else
561         {
562             # Main processing routine.  This is specific to the backend
563             # and probably package.
564             gpg4win_nsis_stubs ($parser, $file, @tokens);
565         }
566     }    
567 }
568
569
570 # Parse the NSIS file.
571 sub nsis_parse_file
572 {
573     my ($parser, $file) = @_;
574     my $handle;
575
576     if ($file eq '-')
577     {
578         $. = 0;
579         $handle = *STDIN;
580     }
581     else
582     {
583         if (not -e $file and 1)
584         {
585             # Search for include file.  Note: We do not change
586             # directories, but that is OK for us.  Also, we want to
587             # avoid the system header files, as we don't control what
588             # constructs they use, and in fact we want to treat their
589             # macros and functions as atoms.
590
591             my @includedirs = @{$parser->{includedirs}};
592             my $dir;
593
594             foreach $dir (@includedirs)
595             {
596                 if (-e $dir . '/' . $file)
597                 {
598                     $file = $dir . '/' . $file;
599                     last;
600                 }
601             }
602         }
603
604         if (not open ($handle, "<$file"))
605         {
606             print STDERR "$file:$.: warning: "
607                 . "can not open include file $file: $!\n"
608                 if $::nsis_parser_warn;
609             return;
610         }
611     }
612
613     while (defined nsis_get_line ($handle))
614     {
615         $.++ if ($file eq '-');
616
617         # Skip comment lines.
618         next if $_ =~ m/^#/;
619
620         # Skip empty lines.
621         next if $_ =~ m/^$/;
622
623         nsis_parse_line ($parser, $file, $_);
624     }
625
626     close $handle if ($file ne '-');
627 }
628
629 \f
630 # The Gpg4win stubs for the MSI backend to the NSIS converter.
631
632 # Gpg4win specific state in $parser:
633 # pkg: the current package (a hash reference), corresponds to certain sections.
634 # pkgs: a hash ref of all packages encountered indexed by their frobbed name.
635 # pkg_list: the order of packages (as frobbed names).
636 # state: specifies a state for special parsing of certain parts.
637 # dep_name: the current package for which we list dependencies (- for none)
638
639 sub gpg4win_nsis_stubs
640 {
641     my ($parser, $file, $command, @args) = @_;
642
643     $parser->{state} = "" if not defined $parser->{state};
644     
645     if ($parser->{state} =~ m/^ignore-until-(.*)$/)
646     {
647         undef $parser->{state} if ($command eq $1);
648     }
649
650     # Section support.
651     #
652     # We parse SetOutPath and File directives in sections.
653     # Everything else is ignored.
654
655     elsif ($parser->{state} eq '' and $command eq 'Section')
656     {
657         my $idx = 0;
658         # Default install level for MSI is 3.
659         my $level = $::nsis_level_default;
660         my $hidden = 0;
661         
662         # Check for options first.
663         return if ($idx > $#args);
664         if ($args[$idx] eq '/o')
665         {
666             # Default install level for MSI is 3.
667             $level = $::nsis_level_optional;
668             $idx++;
669         }
670
671         return if ($idx > $#args);
672
673         my $title = nsis_eval ($parser, $file, $args[$idx++]);
674
675         # Check for hidden flag.
676         if (substr ($title, 0, 1) eq '-')
677         {
678             # Hidden packages are dependency tracked and never
679             # installed by default unless required.
680             $level = $::nsis_level_hidden;
681             $hidden = 1;
682             substr ($title, 0, 1) = '';
683         }
684                 
685         # We only pay attention to special sections and those which
686         # have a section index defined.
687         if ($title eq 'startmenu')
688         {
689             # The special startmenu section contains all our shortcuts.\
690             $parser->{state} = 'section-startmenu';
691             return;
692         }
693         elsif ($idx > $#args)
694         {
695             return;
696         }
697
698         # Finally we can get the frobbed name of the package.
699         my $name = $args[$idx++];
700         $name =~ s/^SEC_//;
701         
702         my $pkg = \%{$parser->{pkgs}->{$name}};
703
704         $pkg->{name} = $name;
705         $pkg->{title} = $title;
706         $pkg->{level} = $level;
707         $pkg->{hidden} = $hidden;
708         $pkg->{features} = '';
709
710         # Remember the order of sections included.
711         push @{$parser->{pkg_list}}, $name;
712
713         $parser->{pkg} = $pkg;
714         $parser->{state} = 'in-section';
715     }
716     elsif ($parser->{state} eq 'in-section')
717     {
718         if ($command eq 'SectionEnd')
719         {
720             delete $parser->{pkg};
721             undef $parser->{state};
722         }
723         elsif ($command eq 'SetOutPath')
724         {
725             fail "$file:$.: syntax error" if ($#args != 0);
726
727             my $outpath = $args[0];
728             if (not $outpath =~ s/^"\$INSTDIR\\?(.*)"$/$1/)
729             {
730                 fail "$file:$.: unsupported out path: $args[0]";
731             }
732             $parser->{outpath} = $outpath;
733         }
734         elsif ($command eq 'File')
735         {
736             my $idx = 0;
737             my $target;
738             
739             fail "$file:$.: not supported" if ($#args < 0 || $#args > 1);
740             
741             if ($#args == 1)
742             {
743                 if ($args[0] eq '/nonfatal')
744                 {
745                     print STDERR "$file:$.: warning: skipping non-fatal file $args[1]\n"
746                         if $::nsis_parser_warn;
747                     return;
748                 }
749                 
750                 $target = $args[0];
751                 if (not $target =~ s,^/oname=(.*)$,$1,)
752                 {
753                     fail "$file:$.: syntax error";
754                 }
755                 
756                 # Temp files are due to overwrite attempts, which are
757                 # handled automatically by the Windows Installer.  Ignore
758                 # them here.
759                 return if $target =~ m/\.tmp$/;
760                 $idx++;
761             }
762             
763             my $source = nsis_eval ($parser, $file, $args[$idx]);
764             if (not defined $target)
765             {
766                 $target = $source;
767                 $target =~ s,^.*/([^/\\]+)$,$1,;
768             }
769
770             push @{$parser->{pkg}->{files}}, { source => $source,
771                                                dir => $parser->{outpath},
772                                                target => $target };
773         }
774         elsif ($command eq 'WriteRegStr')
775         {
776             fail "$file:$.: not supported" if ($#args != 3);
777
778             my $root = $args[0];
779
780             my $key = $args[1];
781             $key =~ s/^"(.*)"$/$1/;
782
783             my $name = $args[2];
784             $name =~ s/^"(.*)"$/$1/;
785
786             my $value = $args[3];
787             $value =~ s/^"(.*)"$/$1/;
788             $value =~ s/\$INSTDIR\\?/\[INSTDIR\]/g;
789
790             push (@{$parser->{pkg}->{registry}},
791                   { root => $root, key => $key, name => $name,
792                     value => $value, type => 'string' });
793         }
794     }
795
796     # Start menu shortcuts support.
797
798     elsif ($parser->{state} eq 'section-startmenu')
799     {
800         if ($command eq 'SectionEnd')
801         {
802             undef $parser->{state};
803         }
804         elsif ($command eq 'CreateShortCut')
805         {
806             fail "$file:$.: not supported" if ($#args != 7);
807
808             # The link may contains a translatable string.
809             my $link = $args[0];
810
811             # We filter for startmenu shortcuts, as the others are
812             # just more of the same.  Equivalently, we could filter
813             # for a block between two labels.
814             return if ($link !~ m/STARTMENU_FOLDER/);
815
816             # Take the base name of the link.  */
817             $link =~ s/^.*\\([^\\]*)\"$/$1/;
818             $link =~ s/\.lnk$//;
819
820             my $target = nsis_eval ($parser, $file, $args[1]);
821             $target =~ s/^\$INSTDIR\\//;
822
823             my $icon = $args[3];
824             $icon =~ s/^"(.*)"$/$1/;
825             $icon =~ s/^\$INSTDIR\\/[INSTDIR]/;
826             $icon = nsis_eval ($parser, $file, $icon);
827
828             my $icon_idx = nsis_eval ($parser, $file, $args[4]);
829             fail "$file:$.: not supported" if ($icon_idx ne '');
830
831             # The description contains a translatable string.
832             my $description = $args[7];
833
834             $parser->{shortcuts}->{$target} = { link => $link,
835                                                 target => $target,
836                                                 icon => $icon,
837                                                 description => $description };
838         }
839     }
840
841     # LangString support.
842     #
843     # LangString directives must be stated at the top-level of the file.
844
845     elsif ($parser->{state} eq '' and $command eq 'LangString')
846     {
847         fail "$file:$.: syntax error" if ($#args != 2);
848
849         my $lang = $args[1];
850         $lang =~ s/^\$\{LANG_(\w*)\}$/$1/;
851         if ($lang eq 'ENGLISH')
852         {
853             $lang = 'en';
854         }
855         elsif ($lang eq 'GERMAN')
856         {
857             $lang = 'de';
858         }
859         else
860         {
861             fail "$file:$.: unsupported language ID $args[1]";
862         }
863         $parser->{po}->{$lang}->{$args[0]} = $args[2];
864     }
865
866     # Function support.
867     #
868     # Most functions are ignored.  Some are of special interest and
869     # are parsed separately.
870
871     elsif ($parser->{state} eq '' and $command eq 'Function')
872     {
873         fail "$file:$.: syntax error" if ($#args != 0);
874
875         if ($args[0] eq 'CalcDepends')
876         {
877             $parser->{state} = 'function-calc-depends';
878         }
879         elsif ($args[0] eq 'CalcDefaults')
880         {
881             $parser->{state} = 'function-calc-defaults';
882         }
883         else
884         {
885             # Functions we do not find interesting are skipped.
886             print STDERR
887                 "$file:$.: warning: ignoring function $args[0]\n"
888                 if $::nsis_parser_warn;
889             delete $parser->{dep_name};
890             $parser->{state} = 'ignore-until-FunctionEnd';
891         }
892     }
893
894     # Function calc-depends.
895     #
896     # This function gathers information about dependencies between
897     # features.  Features are identified by their frobbed names.  The
898     # format is as such: First, a couple of UnselectSection macros,
899     # one for each dependency.  Then SelectSection invocations for all
900     # packages which should always be installed (mandatory), followed
901     # by one block for each feature, consisting of a label "have_FOO:"
902     # where FOO is the frobbed package name (in lowercase, usually),
903     # followed by SelectSection invocations, one for each dependency,
904     # and finally a "skip_FOO:" label to finish the block.
905     #
906     # The order of these statements and blocks must be so that a single pass
907     # through the list is sufficient to resolve all dependencies, that means
908     # in pre-fix order.
909
910     elsif ($parser->{state} eq 'function-calc-depends')
911     {
912         if ($command eq 'FunctionEnd')
913         {
914             undef $parser->{state};
915         }
916         elsif ($command =~ m/^have_(.*):$/)
917         {
918             $parser->{dep_name} = $1;
919             $parser->{pkgs}->{$1}->{deps} = {};
920         }
921         elsif ($command eq '!insertmacro')
922         {
923             fail "$file:$.: syntax error" if $#args < 0;
924             if ($args[0] eq 'SelectSection')
925             {
926                 fail "$file:$.: syntax error" if $#args != 1;
927                 my $name = $args[1];
928                 $name =~ s/^\$\{SEC_(.*)\}$/$1/;
929
930                 if (not exists $parser->{dep_name})
931                 {
932                     # A stray SelectSection chooses defaults.
933                     $parser->{pkgs}->{$name}->{features} .=
934                         " Absent='disallow'";
935                 }
936                 else
937                 {
938                     my $dep_name = $parser->{dep_name};
939
940                     # Add $name as a dependency for $dep_name.
941                     $parser->{pkgs}->{$dep_name}->{deps}->{$name} = 1;
942                 }
943             }
944         }
945         elsif ($command =~ m/^skip_(.*):$/)
946         {
947             fail "$file:$.: stray skip_FOO label"
948                 if not exists $parser->{dep_name};
949
950             my $dep_name = $parser->{dep_name};
951             my $dep_pkg = $parser->{pkgs}->{$dep_name};
952
953             # We resolve indirect dependencies right now.  This works
954             # because dependencies are required to be listed in
955             # pre-fix order.
956
957             foreach my $name (keys %{$parser->{pkgs}})
958             {
959                 my $pkg = $parser->{pkgs}->{$name};
960
961                 # Check if $dep_name is a dependency for $name.
962                 if (exists $pkg->{deps}->{$dep_name})
963                 {
964                     # Add all dependencies of $dep_name to $name.
965                     foreach my $dep (keys %{$dep_pkg->{deps}})
966                     {
967                         $pkg->{deps}->{$dep} = $pkg->{deps}->{$dep_name} + 1
968                             if (not defined $pkg->{deps}->{$dep});
969                     }
970                 }
971             }
972             delete $parser->{dep_name};
973         }
974     }
975
976     # Function calc-depends.
977     #
978     # Format:
979     # g4wihelp::config_fetch_bool "inst_FOO"
980
981     elsif ($parser->{state} eq 'function-calc-defaults')
982     {
983         if ($command eq 'FunctionEnd')
984         {
985             undef $parser->{state};
986         }
987         elsif ($command eq 'g4wihelp::config_fetch_bool')
988         {
989             fail "$file:$.: syntax error" if $#args != 0;
990
991             if ($args[0] !~ m/^"inst_(.*)"$/)
992             {
993                 fail "$file:$.: syntax error";
994             }
995
996             $parser->{pkgs}->{$1}->{ini_inst} = 1;
997         }
998     }
999 }
1000
1001 \f
1002 # MSI generator.
1003
1004 # Simple indentation tracking, for pretty printing.
1005 $::level = 0;
1006
1007
1008 sub dump_all
1009 {
1010     my ($parser) = @_;
1011
1012     my $pkgname;
1013     # A running count for files within each feature.
1014     my $fileidx;
1015     # A running count for registry settings within each feature.
1016     my $regidx;
1017     # A running count for directories throughout the whole file.
1018     my $diridx = 0;
1019     # The current directory.
1020     my $cdir = '';
1021
1022     foreach $pkgname (@{$parser->{pkg_list}})
1023     {
1024         my $pkg = $parser->{pkgs}->{$pkgname};
1025
1026         $fileidx = 0;
1027         foreach my $file (@{$pkg->{files}})
1028         {
1029             if ($cdir ne $file->{dir})
1030             {
1031                 # We need to change the directory.  We weed out empty
1032                 # path elements, which also takes care of leading slashes.
1033                 my @cdir = grep (!/^$/, split (/\\/, $cdir));
1034                 my @ndir = grep (!/^$/, split (/\\/, $file->{dir}));
1035                 my $min;
1036                 my $i;
1037                 $min = $#cdir;
1038                 $min = $#ndir if ($#ndir < $min);
1039                 for ($i = 0; $i <= $min; $i++)
1040                 {
1041                     last if ($cdir[$i] ne $ndir[$i])
1042                 }
1043                 my $j;
1044                 for ($j = $i; $j <= $#cdir; $j++)
1045                 {
1046                     $::level -= 2;
1047                     print ' ' x $::level
1048                         . "</Directory>\n";
1049                 }
1050                 for ($j = $i; $j <= $#ndir; $j++)
1051                 {
1052                     print ' ' x $::level
1053                         . "<Directory Id='d_$diridx' Name='$ndir[$j]'>\n";
1054                     $diridx++;
1055                     $::level += 2;
1056                 }
1057                 $cdir = $file->{dir};
1058             }
1059
1060             my $targetfull;
1061             if ($file->{dir} ne '')
1062             {
1063                 $targetfull = $file->{dir} . '\\' . $file->{target};
1064             }
1065             else
1066             {
1067                 $targetfull = $file->{target};
1068             }
1069
1070             print ' ' x $::level
1071                 . "<Component Id='c_$pkg->{name}_$fileidx' Guid='"
1072                 . get_guid ($targetfull) . "'>\n";
1073             print ' ' x $::level
1074                 . "  <File Id='f_$pkg->{name}_$fileidx' Name='"
1075                 . $file->{target} . "' Source='" . $file->{source} . "'>\n";
1076             # Does not help to avoid the warnings: DefaultLanguage='1033'.
1077
1078             # EXCEPTIONS:
1079             if ($targetfull eq 'gpgol.dll')
1080             {
1081                 print ' ' x $::level
1082                     . "    <Class Id='{42D30988-1A3A-11DA-C687-000D6080E735}' "
1083                     . "Context='InprocServer32' Description='GpgOL - The "
1084                     . "GnuPG Outlook Plugin' ThreadingModel='neutral'/>\n";
1085             }
1086             if ($targetfull eq 'gpgex.dll')
1087             {
1088                 print ' ' x $::level
1089                     . "    <Class Id='{CCD955E4-5C16-4A33-AFDA-A8947A94946B}' "
1090                     . "Context='InprocServer32' Description='GpgEX' "
1091                     . "ThreadingModel='apartment'/>\n";
1092             }
1093             elsif ($targetfull eq 'gpgee.dll')
1094             {
1095                 print STDERR "ERR: run heat.exe on gpgee.dll and add info\n";
1096                 exit 1;
1097             }
1098
1099             # Create shortcuts.
1100             if (defined $parser->{shortcuts}->{$targetfull})
1101             {
1102                 my $shortcut = $parser->{shortcuts}->{$targetfull};
1103                 my $extra = '';
1104
1105                 if (exists $shortcut->{description})
1106                 {
1107                     my $desc = nsis_translate ($parser, '',
1108                                                $shortcut->{description});
1109                     $extra .= " Description='$desc'";
1110                 }
1111 # FIXME: WiX wants the icon to be known at compile time, so it needs a
1112 # source file, not a target file name.
1113 #               if ($shortcut->{icon} ne '')
1114 #               {
1115 #                   $extra .= " Icon='sm_$pkg->{name}_${fileidx}_icon'";
1116 #               }
1117
1118                 # FIXME: Note that the link name should better not
1119                 # change, or it is not correctly replaced on updates.
1120                 my $link = nsis_translate ($parser, '', $shortcut->{link});
1121                 print ' ' x $::level
1122                     . "    <Shortcut Id='sm_$pkg->{name}_$fileidx' "
1123                     . "Directory='ProgramMenuDir' Name='$link'"
1124                     . $extra;
1125
1126 #               if ($shortcut->{icon} eq '')
1127 #               {
1128                     print "/>\n";
1129 #               }
1130 #               else
1131 #               {
1132 #                   print ">\n";
1133 #                   print ' ' x $::level
1134 #                       . "      <Icon Id='sm_$pkg->{name}_${fileidx}_icon' "
1135 #                       . "SourceFile='$shortcut->{icon}'/>\n";
1136 #                   print ' ' x $::level
1137 #                       . "    </Shortcut>\n";
1138 #               }
1139
1140 # Can't make these optional, so we don't do this.
1141 #               print ' ' x $::level
1142 #                   . "    <Shortcut Id='dt_$pkg->{name}_$fileidx' "
1143 #                   . "Directory='DesktopFolder' Name='$file->{target}'/>\n";
1144             }
1145
1146             print ' ' x $::level
1147                 . "  </File>\n";
1148
1149             if (defined $parser->{shortcuts}->{$targetfull})
1150             {
1151                 # http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg02746.html
1152                 # -sice:ICE64
1153                 print ' ' x $::level
1154                     . "  <RemoveFolder Id='rsm_$pkg->{name}_$fileidx' "
1155                     . "Directory='ProgramMenuDir' On='uninstall'/>\n";
1156             }
1157
1158             # EXCEPTIONS:
1159             # We use $targetfull because there is also a gpg.exe in pub\.
1160             if ($targetfull eq 'gpg.exe')
1161             {
1162                 print ' ' x $::level
1163                     . "  <Environment Id='env_path' Name='PATH' Action='set' "
1164                     . "System='yes' Part='last' Value='[INSTDIR]pub'/>\n";
1165             }
1166             elsif ($targetfull eq 'gpgol.dll')
1167             {
1168                 print ' ' x $::level
1169                     . "  <RegistryValue Root='HKLM' Key='Software\\"
1170                     . "Microsoft\\Exchange\\Client\\Extensions' "
1171                     . "Name='GpgOL' "
1172                     . "Value='4.0;[!gpgol.dll];1;11000111111100;11111101' "
1173                     . "Type='string' Action='write'/>\n";
1174                 print ' ' x $::level
1175                     . "  <RegistryValue Root='HKLM' Key='Software\\"
1176                     . "Microsoft\\Exchange\\Client\\Extensions' "
1177                     . "Name='Outlook Setup Extension' "
1178                     . "Value='4.0;Outxxx.dll;7;000000000000000;0000000000;OutXXX' "
1179                     . "Type='string' Action='write'/>\n";
1180             }
1181             elsif ($targetfull eq 'gpgex.dll')
1182             {
1183                 print ' ' x $::level
1184                     . "  <ProgId Id='*'/>\n";
1185                 print ' ' x $::level
1186                     . "  <ProgId Id='Directory'/>\n";
1187                 print ' ' x $::level
1188                     . "  <RegistryValue Root='HKCR' "
1189                     . "Key='*\\ShellEx\\ContextMenuHandlers\\GpgEX' "
1190                     . "Value='{CCD955E4-5C16-4A33-AFDA-A8947A94946B}' "
1191                     . "Type='string' Action='write'/>\n";
1192                 print ' ' x $::level
1193                     . "  <RegistryValue Root='HKCR' "
1194                     . "Key='Directory\\ShellEx\\ContextMenuHandlers\\GpgEX' "
1195                     . "Value='{CCD955E4-5C16-4A33-AFDA-A8947A94946B}' "
1196                     . "Type='string' Action='write'/>\n";
1197             }
1198             elsif ($targetfull eq 'gpgee.dll')
1199             {
1200                 print STDERR "ERR: run heat.exe on gpgee.dll and add info\n";
1201                 exit 1;
1202             }
1203             elsif ($targetfull eq 'dirmngr.exe')
1204             {
1205                 print ' ' x $::level
1206                     . "  <ServiceInstall Id='s_dirmngr' "
1207                     . "DisplayName='Directory Manager' "
1208                     . "Name='DirMngr' ErrorControl='normal' Start='auto' "
1209                     . "Arguments='--service' "
1210                     . "Type='ownProcess' Vital='yes'/>\n";
1211                 print ' ' x $::level
1212                     . "  <ServiceControl Id='s_dirmngr_ctrl' "
1213                     . "Name='DirMngr' Start='install' Stop='uninstall' "
1214                     . "Remove='uninstall'/>\n";
1215             }
1216
1217             print ' ' x $::level
1218                 . "</Component>\n";
1219             $fileidx++;
1220         }
1221
1222         $regidx = 0;
1223         foreach my $reg (@{$pkg->{registry}})
1224         {
1225             my $target;
1226
1227             $target = '/REGISTRY/' . $reg->{root} . '/' . $reg->{key}
1228             . '/' . $reg->{name};
1229
1230             print ' ' x $::level
1231                 . "<Component Id='c_$pkg->{name}_r_$regidx' Guid='"
1232                 . get_guid ($target) . "'>\n";
1233             print ' ' x $::level
1234                 . "  <RegistryValue Id='r_$pkg->{name}_$regidx' Root='"
1235                 . $reg->{root} . "' Key='" . $reg->{key} . "' Name='"
1236                 . $reg->{name} . "' Action='write' Type='" . $reg->{type}
1237                 . "' Value='" . $reg->{value} . "'/>\n";
1238             print ' ' x $::level
1239                 . "</Component>\n";
1240             $regidx++;
1241         }
1242     }
1243
1244     my @cdir = grep (!/^$/, split (/\\/, $cdir));
1245     my $j;
1246     for ($j = 0; $j <= $#cdir; $j++)
1247     {
1248         $::level -= 2;
1249         print ' ' x $::level
1250             . "</Directory>\n";
1251     }
1252 }
1253
1254
1255 sub dump_meat
1256 {
1257     my ($pkg) = @_;
1258     my $fileidx;
1259     my $regidx;
1260
1261     $fileidx = 0;
1262     foreach my $file (@{$pkg->{files}})
1263     {
1264         print ' ' x $::level
1265             . "  <ComponentRef Id='c_$pkg->{name}_$fileidx'/>\n";
1266         $fileidx++;
1267     }
1268     $regidx = 0;
1269     foreach my $reg (@{$pkg->{registry}})
1270     {
1271         print ' ' x $::level
1272             . "  <ComponentRef Id='c_$pkg->{name}_r_$regidx'/>\n";
1273         $regidx++;
1274     }
1275 }
1276
1277
1278 sub dump_all2
1279 {
1280     my ($parser) = @_;
1281
1282     my $pkgname;
1283
1284     foreach $pkgname (@{$parser->{pkg_list}})
1285     {
1286         my $pkg = $parser->{pkgs}->{$pkgname};
1287         my $features;
1288
1289         next if $pkg->{hidden};
1290
1291         $features = $pkg->{features};
1292 #       $features .= " Display='hidden'" if $pkg->{hidden};
1293         $features .= " Description='$pkg->{description}'"
1294             if $pkg->{description};
1295         
1296         my $title = nsis_translate ($parser, '', $pkg->{title});
1297
1298         print ' ' x $::level
1299             . "<Feature Id='p_$pkg->{name}' Level='$pkg->{level}' "
1300             . "Title='$title'" . $features . ">\n";
1301         if ($pkg->{ini_inst})
1302         {
1303             my $uc_pkgname = uc ($pkgname);
1304
1305             print ' ' x $::level
1306                 . "<Condition Level='$::nsis_level_default'>"
1307                 . "INST_$uc_pkgname = \"true\"</Condition>\n";
1308             print ' ' x $::level
1309                 . "<Condition Level='$::nsis_level_optional'>"
1310                 . "INST_$uc_pkgname = \"false\"</Condition>\n";
1311         }
1312
1313         dump_meat ($pkg);
1314
1315         foreach my $dep (keys %{$pkg->{deps}})
1316         {
1317             my $deppkg = $parser->{pkgs}->{$dep};
1318             
1319             print ' ' x $::level
1320                 . "  <Feature Id='p_$pkg->{name}_$dep' "
1321                 . "Title='p_$pkg->{name}_$dep' "
1322                 . "Level='$pkg->{level}' Display='hidden' "
1323                 . "InstallDefault='followParent'>\n";
1324             $::level += 2;
1325             dump_meat ($deppkg);
1326             $::level -= 2;
1327             print ' ' x $::level
1328                 . "  </Feature>\n";
1329         }
1330         print ' ' x $::level
1331             . "</Feature>\n";
1332     }
1333 }
1334
1335 \f
1336 # Just so that it is defined.
1337 $. = 0;
1338
1339 my %parser = ( pre_depth => 0, pre_true => 0 );
1340 my $parser = \%parser;
1341
1342 fetch_guids ();
1343
1344 while ($#ARGV >= 0 and $ARGV[0] =~ m/^-/)
1345 {
1346     my $opt = shift @ARGV;
1347     if ($opt =~ m/^--guids$/)
1348     {
1349         $::guid_file = shift @ARGV;
1350     }
1351     elsif ($opt =~ m/^--manifest$/)
1352     {
1353         $::files_file = shift @ARGV;
1354     }
1355     elsif ($opt =~ m/^-D([^=]*)=(.*)$/)
1356     {
1357         $parser->{pre_symbols}->{$1} = $2;
1358     }
1359     elsif ($opt =~ m/^-L(.*)$/)
1360     {
1361         $::lang = $1;
1362         # Test if it is supported.
1363         lang_to_lcid ($::lang); 
1364     }
1365     elsif ($opt eq '--usage')
1366     {
1367         print STDERR "Usage: $0 [-DNAME=VALUE...] NSIFILE\n";
1368         print STDERR "Use --help or -h for more information.\n";
1369         exit 1;
1370     }
1371     elsif ($opt eq '-h' or $opt eq '--help')
1372     {
1373         print STDERR "Usage: $0 [-DNAME=VALUE...] NSIFILE\n";
1374         print STDERR "Convert the .nsi file NSIFILE to a WiX source file.\n";
1375         print STDERR "Options:\n";
1376         print STDERR "       --guids NAME     Save GUIDs into file NAME (default: $::guid_file)\n";
1377         print STDERR "       --manifest NAME  Save included files into file NAME (default: $::files_file)\n";
1378         print STDERR "       -DNAME=VALUE     Define preprocessor symbol NAME to VALUE\n";
1379         print STDERR "       -LLANG           Build installer for language LANG (default: $::lang)\n";
1380         print STDERR "\n";
1381         print STDERR "       -h|--help        Print this help and exit\n";
1382         exit 0;
1383     }
1384     else
1385     {
1386         print STDERR "$0: unknown option $opt\n";
1387         print STDERR "Usage: $0 [-DNAME=VALUE...] NSIFILE\n";
1388         print STDERR "Use --help or -h for more information.\n";
1389         exit 1;
1390     }
1391 }
1392
1393
1394 if ($#ARGV < 0)
1395 {
1396     nsis_parse_file ($parser, '-');
1397 }
1398 else
1399 {
1400     nsis_parse_file ($parser, $ARGV[0]);
1401 }
1402
1403 # Add exceptions.
1404 # ===============
1405
1406 $parser->{pkgs}->{gnupg}->{deps}->{gpg4win} = 1;
1407
1408 # For debugging:
1409 # use Data::Dumper;
1410 # print Dumper ($parser);
1411 # exit;
1412
1413 # Dump the gathered information.
1414 # ==============================
1415
1416 my $BUILD_FILEVERSION = nsis_fetch ($parser, '_BUILD_FILEVERSION');
1417
1418 my $product_id = get_guid ("/PRODUCT/$BUILD_FILEVERSION");
1419 my $upgrade_code = get_guid ("/UPGRADE/1");
1420
1421 my $INSTALL_DIR = nsis_fetch ($parser, 'INSTALL_DIR');
1422
1423 my $lcid = lang_to_lcid ($::lang);
1424
1425 print <<EOF;
1426 <?xml version='1.0'?>
1427 <Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
1428   <Product Name='Gpg4win'
1429            Id='$product_id'
1430            UpgradeCode='$upgrade_code'
1431            Language='$lcid'
1432            Version='$BUILD_FILEVERSION'
1433            Manufacturer='g10 Code GmbH'>
1434     <Package Description='Gpg4win Installer'
1435              Comments='http://www.gpg4win.org/'
1436              Compressed='yes' 
1437              InstallerVersion='200'
1438              InstallPrivileges='elevated'
1439              Manufacturer='g10 Code GmbH'/>
1440
1441     <Upgrade Id='$upgrade_code'>
1442       <UpgradeVersion Property='UPGRADEPROP'
1443                       IncludeMaximum='no'
1444                       Maximum='$BUILD_FILEVERSION'/>
1445     </Upgrade>
1446
1447     <InstallExecuteSequence>
1448       <RemoveExistingProducts After='InstallFinalize' />
1449     </InstallExecuteSequence>
1450
1451     <Condition
1452      Message="You need to be an administrator to install this product.">
1453       Privileged
1454     </Condition>
1455
1456     <Media Id='1' Cabinet='gpg4win.cab' EmbedCab='yes'/>
1457
1458     <Property Id="INSTDIR">
1459       <RegistrySearch Id='gpg4win_instdir_registry' Type='raw'
1460        Root='HKLM' Key='Software\\GNU\\GnuPG' Name='Install Directory'/>
1461       <IniFileSearch Id='gpg4win_instdir_ini' Type='raw'
1462        Name='gpg4win.ini' Section='gpg4win' Key='instdir'/>
1463     </Property>
1464
1465 EOF
1466
1467 foreach my $pkgname (@{$parser->{pkg_list}})
1468 {
1469     if (exists $parser->{pkgs}->{$pkgname}->{ini_inst})
1470     {
1471         my $uc_pkgname = uc ($pkgname);
1472
1473         print <<EOF;
1474     <Property Id="INST_$uc_pkgname">
1475       <IniFileSearch Id='gpg4win_ini_inst_$pkgname' Type='raw'
1476        Name='gpg4win.ini' Section='gpg4win' Key='inst_$pkgname'/>
1477     </Property>
1478
1479 EOF
1480     }
1481 }
1482
1483 print <<EOF;
1484     <Directory Id='TARGETDIR' Name='SourceDir'>
1485       <Directory Id='ProgramFilesFolder' Name='PFiles'>
1486         <Directory Id='GNU' Name='GNU'>
1487           <Directory Id='INSTDIR' Name='$INSTALL_DIR'>
1488 EOF
1489
1490 $::level = 12;
1491 dump_all ($parser);
1492
1493
1494 print <<EOF;
1495           </Directory>
1496         </Directory>
1497       </Directory>
1498 EOF
1499
1500 if (scalar keys %{$parser->{shortcuts}})
1501 {
1502     my $name = nsis_fetch ($parser, 'PRETTY_PACKAGE');
1503
1504     print <<EOF;
1505       <Directory Id='ProgramMenuFolder' Name='PMenu'>
1506         <Directory Id='ProgramMenuDir' Name='$name'/>
1507       </Directory>
1508 EOF
1509 }
1510
1511 #print <<EOF;
1512 #      <Directory Id="DesktopFolder" Name="Desktop"/>
1513 #EOF
1514
1515
1516 print <<EOF;
1517     </Directory>
1518
1519     <Feature Id='Complete' Title='Gpg4win' Description='All components.'
1520              Display='expand' Level='1' ConfigurableDirectory='INSTDIR'>
1521 EOF
1522
1523 $::level = 6;
1524 dump_all2 ($parser);
1525     
1526 #    <Icon Id="Foobar10.exe" SourceFile="FoobarAppl10.exe"/>
1527
1528 # Removed this, because it is not localized:
1529 #    <UIRef Id='WixUI_ErrorProgressText' />
1530
1531 print <<EOF;
1532     </Feature>
1533
1534     <WixVariable Id='WixUILicenseRtf' Value='gpl.rtf'/>
1535     <UIRef Id='WixUI_Mondo' />
1536
1537   </Product>
1538 </Wix>
1539 EOF
1540
1541 # Post-processing: We need to remember the GUIDs for later reuse, and
1542 # we remember the files we need in case we want to transfer them to a
1543 # different machine for invocation of WiX.
1544
1545 store_guids ();
1546 store_files ($parser);