[Gpg4win-commits] r598 - in trunk: . doc src

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Mon Nov 26 02:50:27 CET 2007


Author: marcus
Date: 2007-11-26 02:50:25 +0100 (Mon, 26 Nov 2007)
New Revision: 598

Modified:
   trunk/ChangeLog
   trunk/doc/README.de.txt
   trunk/doc/README.en.txt
   trunk/src/Makefile.am
   trunk/src/README-msi.txt
   trunk/src/gpg4win.nsi
   trunk/src/inst-gpg4win.nsi
   trunk/src/make-msi.bat
   trunk/src/make-msi.guids
   trunk/src/make-msi.pl
Log:
2007-11-26  Marcus Brinkmann  <marcus at g10code.de>

	* src/Makefile.am (msi, gpg4win-$(VERSION).wix,
	(gpg4win-light-$(VERSION).wix): New targets.
	* src/make-msi.pl: Rewritten.
	* src/make-msi.guids: Include missing files.
	* src/inst-gpg4win.nsi: Give section an identifier.
	* src/README-msi.txt: Update.
	* src/gpg4win.nsi: Do not include pinentry in the light installer.
	* doc/README.de.txt, doc/README.en.txt: Document MSI package.
	* src/make-msi.bat: Support other languages and light installer.


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/ChangeLog	2007-11-26 01:50:25 UTC (rev 598)
@@ -1,3 +1,15 @@
+2007-11-26  Marcus Brinkmann  <marcus at g10code.de>
+
+	* src/Makefile.am (msi, gpg4win-$(VERSION).wix,
+	(gpg4win-light-$(VERSION).wix): New targets.
+	* src/make-msi.pl: Rewritten.
+	* src/make-msi.guids: Include missing files.
+	* src/inst-gpg4win.nsi: Give section an identifier.
+	* src/README-msi.txt: Update.
+	* src/gpg4win.nsi: Do not include pinentry in the light installer.
+	* doc/README.de.txt, doc/README.en.txt: Document MSI package.
+	* src/make-msi.bat: Support other languages and light installer.
+
 2007-11-23  Marcus Brinkmann  <marcus at g10code.de>
 
 	* packages/packages.current: Update gpgme to 1.1.6-svn1279.

Modified: trunk/doc/README.de.txt
===================================================================
--- trunk/doc/README.de.txt	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/doc/README.de.txt	2007-11-26 01:50:25 UTC (rev 598)
@@ -120,7 +120,18 @@
 
 gpg4win.exe /S /C=C:\TEMP\gpg4win.ini /D=D:\Programme\Gpg4win
 
+Für den MSI installer gilt entsprechendes, mit folgenden Änderungen:
+Automatischer Ablauf wird wie üblich mit der Option /qb- zu msiexec
+erreicht.  Die Steuerungsdatei muss gpg4win.ini heißen und im
+Systemverzeichnis (C:\WINDOWS) vorliegen.  Der Installationspfad kann
+mit dem Eintrag "instdir" festgelegt werden.  Ausserdem können die
+Einstellungen auch auf der Kommandozeile mittels INSTDIR=... und
+INST_GPA=FALSE etc. angegeben werden (die Steuerungsdatei geht
+allerdings vor).  Die Angabe von Standard-Konfigurationsdateien, sowie
+die Angabe des Start Menu Verzeichnisses und die optionale Auswahl der
+Verknüpfungen wird vom MSI installer momentan nicht unterstützt.
 
+
 5. Rechtliche Hinweise zu den einzelnen Bestandteilen der Software
 ==================================================================
 

Modified: trunk/doc/README.en.txt
===================================================================
--- trunk/doc/README.en.txt	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/doc/README.en.txt	2007-11-26 01:50:25 UTC (rev 598)
@@ -95,10 +95,23 @@
   scdaemon.conf = D:\config\scdaemon-site.txt
   gpa.conf = D:\config\gpa-site.conf
 
-  
+An example command for unattended installation could look like this:
 
+gpg4win.exe /S /C=C:\TEMP\gpg4win.ini /D=D:\Programme\Gpg4win
 
+For the MSI installer, the above also holds, with the following
+changes: Unattended installation is achieved as usual with the /qb-
+option to msiexec.  The control file must be called gpg4win.ini and
+reside in the system directory (C:\WINDOWS).  It is found
+automatically by the installer.  The installation directory can be
+specified with an entry for "instdir".  Also, each entry can be given
+in uppercase at the command line through INSTDIR=... and
+INST_GPA=FALSE etc. (the control file takes precedence, though).  The
+default config files as well as start menu directory and optional
+installation of short cuts are currently not supported through the MSI
+installer.
 
+
 5. Legal notices pertaining to the individual packets
 =====================================================
 

Modified: trunk/src/Makefile.am
===================================================================
--- trunk/src/Makefile.am	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/Makefile.am	2007-11-26 01:50:25 UTC (rev 598)
@@ -411,6 +411,24 @@
 	$(MAKENSIS) -V3 -DBUILD_DIR=`pwd` -DTOP_SRCDIR=$(top_srcdir) \
 		-DSRCDIR=$(srcdir) -DGPG4WIN_LIGHT=1 $(srcdir)/gpg4win.nsi
 
+gpg4win-$(VERSION).wix: gpg4win.nsi $(common_nsi) stamps/stamp-final \
+			gpgwrap.exe README.en.txt README.de.txt versioninfo.txt
+	perl make-msi.pl --guids $(srcdir)/make-msi.guids \
+		--manifest gpg4win-$(VERSION).files \
+		-DBUILD_DIR=. -DTOP_SRCDIR=$(top_srcdir) \
+		-DSRCDIR=$(srcdir) $(srcdir)/gpg4win.nsi > $@
+
+gpg4win-light-$(VERSION).wix: gpg4win.nsi $(common_nsi) stamps/stamp-final \
+			gpgwrap.exe README.en.txt README.de.txt versioninfo.txt
+	perl make-msi.pl --guids $(srcdir)/make-msi.guids \
+		--manifest gpg4win-light-$(VERSION).files \
+		-DBUILD_DIR=. -DTOP_SRCDIR=$(top_srcdir) \
+		-DSRCDIR=$(srcdir) -DGPG4WIN_LIGHT=1 $(srcdir)/gpg4win.nsi > $@
+
+.PHONY: msi
+msi: gpg4win-$(VERSION).wix gpg4win-light-$(VERSION).wix
+
+
 stamps/stamp-dist-self: versioninfo.txt
 	(set -e; cd ..; make dist-bzip2)
 	touch stamps/stamp-dist-self

Modified: trunk/src/README-msi.txt
===================================================================
--- trunk/src/README-msi.txt	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/README-msi.txt	2007-11-26 01:50:25 UTC (rev 598)
@@ -28,19 +28,23 @@
 
 $ cd gpg4win/src
 
-2) Run make-msi.pl to generate the required WiX source file:
+2) Create the required WiX source file:
 
-$ perl make-msi.pl < ../include/config.nsi > gpg4win.wix
+$ make msi
 
 Maintainer note: The program might output the message "GUID list
 stored in make-msi.guids changed, please commit!".  In this case, the
-file make-msi.guids should be committed to the repository.
+modified source file make-msi.guids should be committed to the repository.
 
 3) Now switch to the same directory on the Windows machine, and run
 the script make-msi.bat to create the MSI package:
 
-> make-msi.bat
+> make-msi.bat gpg4win-VERSION.wix
+> make-msi.bat gpg4win-light-VERSION.wix
 
+where VERSION is the full version number of the build (for example,
+1.9.0-svn595).
+
 The batch file assumes that WiX is installed in the canonical
 location.  If that is not the case, you might need to adjust the PATH
 in the file.
@@ -81,11 +85,8 @@
 they can be grouped in an archive using tar:
 
 $ cd gpg4win
-$ tar -T src/make-msi.files cjf gpg4win-msi.tar.bz2
+$ tar -T src/gpg4win-VERSION.files cjf gpg4win-msi.tar.bz2
 
-Beside the files in gpg4win-msi.tar.bz, you also need src/gpg4win.wix
+Beside the files in gpg4win-msi.tar.bz, you also need src/gpg4win-VERSION.wix
 and src/make-msi.bat on the Windows computer, which should be put into
 the src/ subdirectory of the archive.
-
-
-

Modified: trunk/src/gpg4win.nsi
===================================================================
--- trunk/src/gpg4win.nsi	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/gpg4win.nsi	2007-11-26 01:50:25 UTC (rev 598)
@@ -51,6 +51,9 @@
 !ifdef HAVE_PKG_GNUPG2
 !undef HAVE_PKG_GNUPG2
 !endif
+!ifdef HAVE_PKG_PINENTRY
+!undef HAVE_PKG_PINENTRY
+!endif
 !ifdef HAVE_PKG_DIRMNGR
 !undef HAVE_PKG_DIRMNGR
 !endif

Modified: trunk/src/inst-gpg4win.nsi
===================================================================
--- trunk/src/inst-gpg4win.nsi	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/inst-gpg4win.nsi	2007-11-26 01:50:25 UTC (rev 598)
@@ -19,7 +19,7 @@
 
 
 # This is the very first section installed.
-Section "-gpg4win"
+Section "-gpg4win" SEC_gpg4win
 !ifdef SOURCES
   SetOutPath "$INSTDIR"
   File "${BUILD_DIR}/../gpg4win-${VERSION}.tar.bz2"

Modified: trunk/src/make-msi.bat
===================================================================
--- trunk/src/make-msi.bat	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/make-msi.bat	2007-11-26 01:50:25 UTC (rev 598)
@@ -1,4 +1,30 @@
+REM Usage: make-msi.bat [-L{de|en}] [FILE]
+REM Defaults: -Len gpg4win.wix
+REM 
 set WIXPATH=C:\"Program Files"\"Windows Installer XML v3"\bin
 
-%WIXPATH%\candle.exe gpg4win.wix
-%WIXPATH%\light.exe -ext WixUIExtension -cultures:en-us gpg4win.wixobj
+set LANG=en-us
+IF NOT "%1"=="-Lde" GOTO langde
+  shift
+  set LANG=de-de
+  GOTO langset
+:langde
+IF NOT "%1"=="-Len" GOTO langen
+  shift
+  set LANG=en-us
+  GOTO langset
+:langen
+
+:langset
+
+
+set FILE=gpg4win.wix
+IF "%1"=="" GOTO nofile
+  set FILE=%1
+:nofile
+
+%WIXPATH%\candle.exe %FILE%
+%WIXPATH%\light.exe -ext WixUIExtension -cultures:%LANG% %FILE%obj
+
+%WIXPATH%\candle.exe %FILE%
+%WIXPATH%\light.exe -ext WixUIExtension -cultures:%LANG% %FILE%obj

Modified: trunk/src/make-msi.guids
===================================================================
--- trunk/src/make-msi.guids	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/make-msi.guids	2007-11-26 01:50:25 UTC (rev 598)
@@ -1,6 +1,7 @@
 # This is an automatically generated file.  DO NOT EDIT.
 c98dadcc-b796-41cf-aff2-bfa1706a5a6f /PRODUCT/1.9.0.581
 06b9c50c-352a-4fe2-9f47-4f6348d9dda4 /PRODUCT/1.9.0.587
+b3ce3cd9-0436-4348-8c09-106163ad9170 /PRODUCT/1.9.0.595
 fb54db39-2456-4fcc-9550-2790db663015 /REGISTRY/HKLM/Software\GNU\GnuPG/Install Directory
 ad23691b-1734-4dec-b91a-d4ff286190ea /REGISTRY/HKLM/Software\GNU\GnuPG/gpgProgram
 e0807a2f-4e70-48f8-b047-c5d3e219c3e7 /UPGRADE/1
@@ -253,9 +254,12 @@
 01020ce6-6c8c-460f-8f66-a8f734fa3fec share\gpa\gpa_logo.ppm
 fa992dcc-ff89-4a32-9723-0b76bbc1dae4 share\gpa\gpa_tips.de
 1e855765-7624-42bb-b06b-28a0bcfce29b share\gpa\gpa_tips.en
+9433746e-dddf-421c-9dd0-f5b1cefaadb7 share\gpg4win\README.de.txt
+13b17315-1137-401c-8021-020cc0e5db70 share\gpg4win\README.en.txt
 592ab2a3-a681-4baf-80ff-9d8d9ea3ce95 share\gpg4win\durchblicker.pdf
 b1b23af2-9aa8-4055-9ff8-6e0d26c8dd0e share\gpg4win\einsteiger.pdf
 806964aa-cb2a-4d5e-b53c-9525736563f2 share\gpg4win\novices.pdf
+85a68157-9931-4a40-879c-b3f24361930f share\gpg4win\versioninfo.txt
 4ca6e2ae-6987-4a21-9268-dc652b709fe2 share\icons\oxygen\16x16\actions\1day.png
 f244617c-8b96-4f99-aa69-d73a41c287fd share\icons\oxygen\16x16\actions\5days.png
 d38f25e8-1245-4687-bf17-4e5be87a548a share\icons\oxygen\16x16\actions\7days.png

Modified: trunk/src/make-msi.pl
===================================================================
--- trunk/src/make-msi.pl	2007-11-23 17:40:56 UTC (rev 597)
+++ trunk/src/make-msi.pl	2007-11-26 01:50:25 UTC (rev 598)
@@ -18,61 +18,36 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 
-# Invoke like this:
+# TODO
+# ====
 #
-# perl make-msi.pl < ../include/config.nsi
+# 1. The UI extension in WiX 3.0 only supports en-us.
+# Translation to german is desired.
 #
-# Note that this needs to be called from the gpg4win src/ directory, because
-# a number of files (inst-*.nsi for example) are accessed in that directory.
+# 1. Edit license dialog to not require acceptance (see tutorial, lesson 2).
 #
-# This program parses the NSIS source files and creates a .wix file,
-# which needs to be compiled with candle and linked with light, see
-# make-msi.bat.
+# 2. Support .ini file for customization.
 #
-# The file make-msi.guids is read and updated.  It contains GUIDs for all
-# components used by the installer, which are kept from version to version.
-#
-# Also, the output file make-msi.files contains a list of all files
-# that will be accessed by the linker when creating the package.
+# 3. Add README dialog and launch README file:
+# <Property Id='NOTEPAD'>Notepad.exe</Property>
+# <CustomAction Id='LaunchFile' Property='NOTEPAD' ExeCommand='[SourceDir]Readme.txt' Return='asyncNoWait'/>
+# Problems: Dialog is missing.  How to select language to display?
 
 use strict;
+use warnings;
+use diagnostics;
 
-# TODO:
-#
-# DirMngr config files/cache directory?  service start fails!!!
-# desktop and quick launch entries, but optional (also startmenu optional)
-
-# The list of all enabled packages.
-@::pkgs = ();
-# All definitions from config.nsi (the input file)
-%::config = ();
-# A description of the components.  We have one component per package.
-# Creating the data for these components is most of the work.
-@::components = ();
-# A list of all source files included in the package.
-@::sources = ();
-# A hash which maps frobbed package names to a hash of frobbed package
-# names on which they depend.
-%::deps = ();
-# A hash which contains one key for each file that wants a shortcut in the
-# canonical places (start menu, desktop, quick launch).
-%::shortcuts = ();
-
-$::INSTDIR = 'GnuPG';
-$::name = 'GnuPG for Windows';
-
-# Simple indentation tracking, for pretty printing.
-$::level = 0;
-
 
-# FIXME: Some work arounds for the manual.
+# Default language.
+$::lang = 'en';
 
-my $DESC_Name_man_advanced_de = "Advanced Manual (German)";
-my $DESC_Name_man_advanced_en = "Advanced Manual";
-my $DESC_Name_man_novice_de = "Novice Manual (German)";
-my $DESC_Name_man_novice_en = "Novice Manual";
-
 
+sub fail
+{
+    print STDERR $_[0] . "\n";
+    exit 1;
+}
+    
 # We use a new product and package code for every build (using pseudo
 # components), but a single constant upgrade code for all versions.
 # Note that Windows installer ignores the build part of the version
@@ -140,15 +115,20 @@
 }
 
 
-$::files_file = 'make-msi.files';
+$::files_file = '';
 
 # We store the list of included files for temporary packaging, in case
 # WiX needs to be run on a different system.
 sub store_files
 {
+    my ($parser) = @_;
+
+    return if ($::files_file eq '');
     open (FILE, ">$::files_file") or die;
-    foreach my $pkg (@::components)
+    foreach my $name (@{$parser->{pkg_list}})
     {
+	my $pkg = $parser->{pkgs}->{$name};
+
 	next if ($#{$pkg->{files}} == -1);
 	print FILE (join ("\n", map { "src/" . ($_->{source}) }
 			  @{$pkg->{files}})). "\n";
@@ -157,248 +137,880 @@
 }
 
 
-sub get_deps
+sub lang_to_lcid
 {
-    my $name = '';
-    my %deps = ();
+    my ($lang) = @_;
 
-    # FIXME: Check if file exists.
-    open (FILE, "<inst-sections.nsi") or return;
-    while (<FILE>)
+    if ($lang eq 'en')
     {
-	my $line = $_;
+	return 1033;
+    }
+    elsif ($lang eq 'de')
+    {
+	return 1031;
+    }
+    else
+    {
+	fail "language $lang not supported";
+    }
+}
+	
+
+# NSIS parser
 
-	if ($name eq '')
+# The parser data structure contains the following members:
+#
+# pre_depth: The current nesting depth of preprocessor conditionals.
+# pre_true:  Depth of the last preprocessor conditional that was true.
+# pre_symbols: A hash of defined preprocessor symbols.
+# po: A hash of languages, each a hash of translated strings.
+# outpath: the current output path.
+# includedirs: An array of include directories to search through.
+
+# A couple of variables you can set:
+$::nsis_parser_warn = 0;
+$::nsis_parser_debug = 0;
+
+$::nsis_level_default = 1;
+$::nsis_level_optional = 1000;
+$::nsis_level_hidden = 2000;
+
+# Evaluate an expression.
+sub nsis_eval
+{
+    my ($parser, $file, $expr) = @_;
+    my $val = $expr;
+
+    # Resolve outer double quotes, if any.
+    if ($val =~ m/^"/)
+    {
+	if (not $val =~ s/^"(.*)"$/$1/)
 	{
-	    if ($line =~ m/^\s*have_(\S+):\s*\r?\n$/)
+	    fail "$file:$.: unmatched quote in expression: $expr";
+	}
+    }
+    
+    my $iter = 0;
+    while ($val =~ m/\${([^}]*)}/)
+    {
+	my $varname = $1;
+	my $varvalue;
+
+	if (exists $parser->{pre_symbols}->{$varname})
+	{
+	    $varvalue = $parser->{pre_symbols}->{$varname};
+	}
+	else
+	{
+	    fail "$file:$.: undefined variable $varname in expression: $expr";
+	}
+	$val =~ s/\${$varname}/$varvalue/g;
+
+	$iter++;
+	if ($iter > 100)
+	{
+	    fail "$file:$.: too many variable expansions in expression: $expr";
+	}
+    }
+    
+#    # FIXME: For now.
+#    if ($expr =~ m/\$/ or $expr !~ m/^\"/)
+#    {
+#	return $expr;
+#    }
+#    $val = eval $expr;
+    return $val;
+}
+
+
+# Retrieve an evaluated symbol
+sub nsis_fetch
+{
+    my ($parser, $symname) = @_;
+
+    return undef if (not exists $parser->{pre_symbols}->{$symname});
+
+    return nsis_eval ($parser, '', $parser->{pre_symbols}->{$symname});
+}
+
+
+# Evaluate an expression.
+sub nsis_translate
+{
+    my ($parser, $file, $expr) = @_;
+    my $val = $expr;
+
+    # Resolve outer double quotes, if any.
+    if ($val =~ m/^\$\((.*)\)$/)
+    {
+	if (exists $parser->{po}->{$::lang}->{$1})
+	{
+	    $val = $parser->{po}->{$::lang}->{$1};
+	}
+	else
+	{
+	    fail "$file:$.: no translation for $val to language $::lang";
+	}
+    }
+
+    $val =~ s/^"(.*)"$/$1/;
+    $val =~ s/\$\r/\r/g;
+    $val =~ s/\$\n/\n/g;
+    $val =~ s/\$\"/"/g;
+
+    return $val;
+}
+
+
+# Low level line input.
+sub nsis_get_line
+{
+    my ($file) = @_;
+    my $line = '';
+
+    while (<$file>)
+    {
+	$line = $line . $_;
+
+	# Strip leading whitespace.
+	$line =~ s/^\s*//;
+
+	# Strip newline and trailing whitespace.
+	$line =~ s/\s*\r?\n$//;
+
+	# Combine multiple lines connected with backslashes.
+	if ($line =~ m/^(.*)\\$/)
+	{
+	    $line = $1 . ' ';
+	    next;
+	}
+
+	$_ = $line;
+	last;
+    }
+
+    # Now break up the line into 
+    return $_;
+}
+
+
+# Tokenize the NSIS line.
+sub nsis_tokenize
+{
+    my ($file, $line) = @_;
+    my @tokens;
+
+    my @line = split ('', $line);
+    my $idx = 0;
+
+    while ($idx <= $#line)
+    {
+	# The beginning of the current partial token.
+	my $token = $idx;
+
+	if ($line[$idx] eq '"')
+	{
+	    $idx++;
+	    # Skip until end of string, indicated by double quote that
+	    # is not part of the $\" string.
+	    while ($idx <= $#line)
 	    {
-		$name = $1;
+		if (substr ($line, $idx, 3) eq '$\\"')
+		{
+		    $idx += 3;
+		}
+		else
+		{
+		    last if ($line[$idx] eq '"');
+		    $idx++;
+		}
 	    }
+	    fail "$file:$.:$idx: unterminated string from position $token"
+		if ($idx > $#line);
+	    $idx++;
+	    fail "$file:$.:$idx: strings not separated"
+		if ($idx <= $#line and $line[$idx] !~ m/\s/);
 	}
+	elsif ($line[$idx] eq '\'')
+	{
+	    $idx++;
+	    # Skip until end of string, indicated by a single quote.
+	    while ($idx <= $#line)
+	    {
+		last if ($line[$idx] eq '\'');
+		$idx++;
+	    }
+	    fail "$file:$.:$idx: unterminated string from position $token"
+		if ($idx > $#line);
+	    $idx++;
+	    fail "$file:$.:$idx: strings not separated"
+		if ($idx <= $#line and $line[$idx] !~ m/\s/);
+	}
 	else
 	{
-	    if ($line =~ m/^\s*!insertmacro\s+SelectSection\s+\$\{SEC_(\S+)\}\s*\r?\n$/)
+	    # Skip until end of token indicated by whitespace.
+	    while ($idx <= $#line)
 	    {
-		$deps{$1} = 1;
+		fail "$file:$.:$idx: invalid character"
+		    if ($line[$idx] eq '"');
+
+		last if ($line[$idx] =~ m/\s/);
+		$idx++;
 	    }
-	    elsif ($line =~ m/^\s*skip_$name:\s*\r?\n$/)
+	}
+
+	push @tokens, substr ($line, $token, $idx - $token);
+
+	# Skip white space between arguments.
+	while ($idx <= $#line and $line[$idx] =~ m/\s/)
+	{
+	    $idx++;
+	}
+    }
+    
+    return @tokens;
+}
+
+
+# We suppress some warnings after first time.
+%::warn = ();
+
+# Parse the NSIS line.
+sub nsis_parse_line
+{
+    my ($parser, $file, $line) = @_;
+
+    # We first tokenize the line.
+    my @tokens = nsis_tokenize ($file, $line); 
+
+    # We handle preprocessing directives here.
+	
+    print STDERR "Tokens: " . join (" AND ", @tokens) . "\n"
+	if $::nsis_parser_debug;
+
+    # We have special code dealing with ignored areas.
+    if ($parser->{pre_depth} > $parser->{pre_true})
+    {
+	if ($tokens[0] eq '!ifdef' or $tokens[0] eq '!ifndef')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 1;
+	    $parser->{pre_depth}++;
+	}
+	elsif ($tokens[0] eq '!else')
+	{
+	    fail "$file:$.: stray !else" if $parser->{pre_depth} == 0;
+
+	    if ($parser->{pre_depth} == $parser->{pre_true} + 1)
 	    {
-		# We resolve indirect dependencies right now.
-		foreach my $pkg (keys %::deps)
+		$parser->{pre_true}++;
+	    }
+	}
+	elsif ($tokens[0] eq '!endif')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 0;
+
+	    fail "$file:$.: stray !endif" if $parser->{pre_depth} == 0;
+
+	    $parser->{pre_depth}--;
+	}
+	elsif ($tokens[0] eq '!macro')
+	{
+	    fail "$file:$.: syntax error" if $#tokens < 1;
+
+	    # FIXME: We do not support macros at this point, although
+	    # support would not be too hard to add.  Instead, we just
+	    # ignore their definition so it does not throw us off.
+
+	    print STDERR
+		"$file:$.: warning: ignoring macro $tokens[1]\n"
+		if $::nsis_parser_warn;
+
+	    $parser->{pre_depth}++;
+	}
+	elsif ($tokens[0] eq '!macroend')
+	{
+	    # FIXME: See !macro.
+	    fail "$file:$.: stray !macroend" if $parser->{pre_depth} == 0;
+	    $parser->{pre_depth}--;
+	}
+    }
+    else
+    {
+	# This is the parser for areas not ignored.
+	if ($tokens[0] eq '!define')
+	{
+	    if ($#tokens == 1)
+	    {
+		# FIXME: Maybe define to 1?
+		$parser->{pre_symbols}->{$tokens[1]} = '';
+	    }
+	    elsif ($#tokens == 2)
+	    {
+		$parser->{pre_symbols}->{$tokens[1]} =
+		    nsis_eval ($parser, $file, $tokens[2]);
+	    }
+	    else
+	    {
+		fail "$file:$.: syntax error";
+	    }
+
+	}
+	elsif ($tokens[0] eq '!undef')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 1;
+	    delete $parser->{pre_symbols}->{$tokens[1]};
+	}
+	elsif ($tokens[0] eq '!ifdef')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 1;
+
+	    if (exists $parser->{pre_symbols}->{$tokens[1]})
+	    {
+		$parser->{pre_true}++;
+	    }
+	    $parser->{pre_depth}++;
+	}
+	elsif ($tokens[0] eq '!ifndef')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 1;
+
+	    if (not exists $parser->{pre_symbols}->{$tokens[1]})
+	    {
+		$parser->{pre_true}++;
+	    }
+	    $parser->{pre_depth}++;
+	}
+	elsif ($tokens[0] eq '!else')
+	{
+	    fail "$file:$.: stray !else" if $parser->{pre_depth} == 0;
+
+	    if ($parser->{pre_depth} == $parser->{pre_true})
+	    {
+		$parser->{pre_true}--;
+	    }
+	    elsif ($parser->{pre_depth} == $parser->{pre_true} + 1)
+	    {
+		$parser->{pre_true}++;
+	    }
+	}
+	elsif ($tokens[0] eq '!endif')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 0;
+
+	    fail "$file:$.: stray !endif" if $parser->{pre_depth} == 0;
+
+	    if ($parser->{pre_depth} == $parser->{pre_true})
+	    {
+		$parser->{pre_true}--;
+	    }
+	    $parser->{pre_depth}--;
+	}
+	elsif ($tokens[0] eq '!include')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 1;
+
+	    print STDERR "Including $tokens[1]\n"
+		if $::nsis_parser_debug;
+
+	    my $filename = nsis_eval ($parser, $file, $tokens[1]);
+
+	    # Recursion.
+	    nsis_parse_file ($parser, $filename);
+	}
+	elsif ($tokens[0] eq '!macro')
+	{
+	    fail "$file:$.: syntax error" if $#tokens < 1;
+
+	    # FIXME: We do not support macros at this point, although
+	    # support would not be too hard to add.  Instead, we just
+	    # ignore their definition so it does not throw us off.
+
+	    print STDERR
+		"$file:$.: warning: ignoring macro $tokens[1]\n"
+		if $::nsis_parser_warn;
+
+	    $parser->{pre_depth}++;
+	}
+	elsif ($tokens[0] eq '!macroend')
+	{
+	    # FIXME: See !macro.
+	    fail "$file:$.: stray !macroend" if $parser->{pre_depth} == 0;
+	    $parser->{pre_depth}--;
+	}
+	elsif ($tokens[0] eq '!cd' or $tokens[0] eq '!addplugindir')
+	{
+	    if (not exists $::warn{"directive-$tokens[0]"})
+	    {
+		print STDERR
+		    "$file:$.: warning: ignoring $tokens[0] directive\n"
+		if $::nsis_parser_warn;
+	    }
+	    $::warn{"directive-$tokens[0]"}++;
+	}
+	elsif ($tokens[0] eq '!addincludedir')
+	{
+	    fail "$file:$.: syntax error" if $#tokens != 1;
+
+	    my $dir = nsis_eval ($parser, $file, $tokens[1]);
+
+	    unshift @{$parser->{includedirs}}, $dir;
+	}
+	elsif ($tokens[0] =~ m/^\!/ and $tokens[0] ne '!insertmacro')
+	{
+	    # Note: It is essential that some !insertmacro invocations are
+	    # not expanded, namely those of SelectSection and UnselectSection,
+	    # which are used to track dependencies in Gpg4win.
+
+	    fail "$file:$.: compiler directive $tokens[0] not implemented";
+	}
+	else
+	{
+	    # Main processing routine.  This is specific to the backend
+	    # and probably package.
+	    gpg4win_nsis_stubs ($parser, $file, @tokens);
+	}
+    }    
+}
+
+
+# Parse the NSIS file.
+sub nsis_parse_file
+{
+    my ($parser, $file) = @_;
+    my $handle;
+
+    if ($file eq '-')
+    {
+	$. = 0;
+	$handle = *STDIN;
+    }
+    else
+    {
+	if (not -e $file and 1)
+	{
+	    # Search for include file.  Note: We do not change
+	    # directories, but that is OK for us.  Also, we want to
+	    # avoid the system header files, as we don't control what
+	    # constructs they use, and in fact we want to treat their
+	    # macros and functions as atoms.
+
+	    my @includedirs = @{$parser->{includedirs}};
+	    my $dir;
+
+	    foreach $dir (@includedirs)
+	    {
+		if (-e $dir . '/' . $file)
 		{
-		    if (defined $::deps{$pkg}->{$name})
-		    {
-			foreach my $dep (keys %deps)
-			{
-			    $::deps{$pkg}->{$dep} = $::deps{$pkg}->{$name} + 1
-				if (not defined $::deps{$pkg}->{$dep})
-			}
-		    }
+		    $file = $dir . '/' . $file;
+		    last;
 		}
-		$::deps{$name} = { %deps };
-		$name = '';
-		%deps = ();
 	    }
 	}
+
+	if (not open ($handle, "<$file"))
+	{
+	    print STDERR "$file:$.: warning: "
+		. "can not open include file $file: $!\n"
+		if $::nsis_parser_warn;
+	    return;
+	}
     }
-    close (FILE);
+
+    while (defined nsis_get_line ($handle))
+    {
+	$.++ if ($file eq '-');
+
+	# Skip comment lines.
+	next if $_ =~ m/^#/;
+
+	# Skip empty lines.
+	next if $_ =~ m/^$/;
+
+	nsis_parse_line ($parser, $file, $_);
+    }
+
+    close $handle if ($file ne '-');
 }
 
 
-sub get_shortcuts
+# The Gpg4win stubs for the MSI backend to the NSIS converter.
+
+# Gpg4win specific state in $parser:
+# pkg: the current package (a hash reference), corresponds to certain sections.
+# pkgs: a hash ref of all packages encountered indexed by their frobbed name.
+# pkg_list: the order of packages (as frobbed names).
+# state: specifies a state for special parsing of certain parts.
+# dep_name: the current package for which we list dependencies (- for none)
+
+sub gpg4win_nsis_stubs
 {
-    my %shortcuts = ();
+    my ($parser, $file, $command, @args) = @_;
 
-    # Pending line.
-    my $line;
+    $parser->{state} = "" if not defined $parser->{state};
+    
+    if ($parser->{state} =~ m/^ignore-until-(.*)$/)
+    {
+	undef $parser->{state} if ($command eq $1);
+    }
 
-    # FIXME: Check if file exists.
-    open (FILE, "<inst-sections.nsi") or return;
-    while (<FILE>)
+    # Section support.
+    #
+    # We parse SetOutPath and File directives in sections.
+    # Everything else is ignored.
+
+    elsif ($parser->{state} eq '' and $command eq 'Section')
     {
-	# Combine multiple lines connected with backslashes.
-	$line = $line . $_;
-	if ($line =~ m/^(.*)\\\s*\r?\n$/)
+	my $idx = 0;
+	# Default install level for MSI is 3.
+	my $level = $::nsis_level_default;
+	my $hidden = 0;
+	
+	# Check for options first.
+	return if ($idx > $#args);
+	if ($args[$idx] eq '/o')
 	{
-	    $line = $1 . ' ';
-	    next;
+	    # Default install level for MSI is 3.
+	    $level = $::nsis_level_optional;
+	    $idx++;
 	}
-	$_ = $line;
-	$line = '';
 
-	if (m,^\s*CreateShortCut\s+\"\$SMPROGRAMS\\\$STARTMENU_FOLDER\\[^.]+\.lnk\"\s+\"\$INSTDIR\\([^"]+)\",)
+	return if ($idx > $#args);
+
+	my $title = nsis_eval ($parser, $file, $args[$idx++]);
+
+	# Check for hidden flag.
+	if (substr ($title, 0, 1) eq '-')
 	{
-	    $shortcuts{$1} = 1;
+	    # Hidden packages are dependency tracked and never
+	    # installed by default unless required.
+	    $level = $::nsis_level_hidden;
+	    $hidden = 1;
+	    substr ($title, 0, 1) = '';
 	}
+		
+	# We only pay attention to special sections and those which
+	# have a section index defined.
+	if ($title eq 'startmenu')
+	{
+	    # The special startmenu section contains all our shortcuts.\
+	    $parser->{state} = 'section-startmenu';
+	    return;
+	}
+	elsif ($idx > $#args)
+	{
+	    return;
+	}
+
+	# Finally we can get the frobbed name of the package.
+	my $name = $args[$idx++];
+	$name =~ s/^SEC_//;
+	
+	my $pkg = \%{$parser->{pkgs}->{$name}};
+
+	$pkg->{name} = $name;
+	$pkg->{title} = $title;
+	$pkg->{level} = $level;
+	$pkg->{hidden} = $hidden;
+	$pkg->{features} = '';
+
+	# Remember the order of sections included.
+	push @{$parser->{pkg_list}}, $name;
+
+	$parser->{pkg} = $pkg;
+	$parser->{state} = 'in-section';
     }
-    close (FILE);
-    %::shortcuts = %shortcuts;
-}
+    elsif ($parser->{state} eq 'in-section')
+    {
+	if ($command eq 'SectionEnd')
+	{
+	    delete $parser->{pkg};
+	    undef $parser->{state};
+	}
+	elsif ($command eq 'SetOutPath')
+	{
+	    fail "$file:$.: syntax error" if ($#args != 0);
 
-
-sub collect_all
-{
-  # Input file is $(top_srcdir)/include/config.nsi
+	    my $outpath = $args[0];
+	    if (not $outpath =~ s/^"\$INSTDIR\\?(.*)"$/$1/)
+	    {
+		fail "$file:$.: unsupported out path: $args[0]";
+	    }
+	    $parser->{outpath} = $outpath;
+	}
+	elsif ($command eq 'File')
+	{
+	    my $idx = 0;
+	    my $target;
+	    
+	    fail "$file:$.: not supported" if ($#args < 0 || $#args > 1);
+	    
+	    if ($#args == 1)
+	    {
+		if ($args[0] eq '/nonfatal')
+		{
+		    print STDERR "$file:$.: warning: skipping non-fatal file $args[1]\n"
+			if $::nsis_parser_warn;
+		    return;
+		}
+		
+		$target = $args[0];
+		if (not $target =~ s,^/oname=(.*)$,$1,)
+		{
+		    fail "$file:$.: syntax error";
+		}
+		
+		# Temp files are due to overwrite attempts, which are
+		# handled automatically by the Windows Installer.  Ignore
+		# them here.
+		return if $target =~ m/\.tmp$/;
+		$idx++;
+	    }
+	    
+	    my $source = nsis_eval ($parser, $file, $args[$idx]);
+	    if (not defined $target)
+	    {
+		$target = $source;
+		$target =~ s,^.*/([^/\\]+)$,$1,;
+	    }
 
-  while (<>)
-  {
-      if (/^!define\s+(\w+)\s+(\S.*\S)\s*\r?\n$/)
-      {
-	  $::config{$1} = $2;
-      }
-  }
-  # gpg4win_build_list is a C-like string, so strip the quotes first.
-  my $pkg_list;
-  eval '$pkg_list = ' . $::config{gpg4win_build_list};
-  @::pkgs = split (' ', $pkg_list);
+	    push @{$parser->{pkg}->{files}}, { source => $source,
+					       dir => $parser->{outpath},
+					       target => $target };
+	}
+	elsif ($command eq 'WriteRegStr')
+	{
+	    fail "$file:$.: not supported" if ($#args != 3);
 
-  # Now we have a list of packages to process.  For each package,
-  # create a nice data structure that captures all the information we
-  # collect.
+	    my $root = $args[0];
 
-  foreach my $pkg (@::pkgs)
-  {
-      my %pkg;
-      $pkg{name} = $pkg;
-      $pkg =~ tr/-+/__/;
-      $pkg{frobbed_name} = $pkg;
-      $pkg{version} = $::config{"gpg4win_pkg_${pkg}_version"};
-      $pkg{source} = $::config{"gpg4win_pkg_${pkg}"};
-      $pkg{features} = '';
-      $pkg{hidden} = 0;
+	    my $key = $args[1];
+	    $key =~ s/^"(.*)"$/$1/;
 
-      # We parse the inst-package file to figure out what to do.  This
-      # is not a full-featured NSIS to MSI converter, but it does the
-      # job for us.
+	    my $name = $args[2];
+	    $name =~ s/^"(.*)"$/$1/;
 
-      my $prefix;
-      if (defined $pkg{version})
-      {
-	  $prefix = "playground/install/pkgs/$pkg{name}-$pkg{version}";
-      }
+	    my $value = $args[3];
+	    $value =~ s/^"(.*)"$/$1/;
+	    $value =~ s/\$INSTDIR\\?/\[INSTDIR\]/g;
 
-      # The list of all files encountered and included in the package.
-      my @files;
-      # The list of all registry settings to write.
-      my @registry;
+	    push (@{$parser->{pkg}->{registry}},
+		  { root => $root, key => $key, name => $name,
+		    value => $value, type => 'string' });
+	}
+    }
 
-      # The current directory.
-      my $dir = '';
+    # Start menu shortcuts support.
 
-      # The pending line.
-      my $line = '';
+    elsif ($parser->{state} eq 'section-startmenu')
+    {
+	if ($command eq 'SectionEnd')
+	{
+	    undef $parser->{state};
+	}
+	elsif ($command eq 'CreateShortCut')
+	{
+	    fail "$file:$.: not supported" if ($#args != 7);
 
-      open (FILE, "<inst-$pkg{name}.nsi") or die;
-      while (<FILE>)
-      {
-	  # Combine multiple lines connected with backslashes.
-	  $line = $line . $_;
-	  if ($line =~ m/^(.*)\\\s*\r?\n$/)
-	  {
-	      $line = $1 . ' ';
-	      next;
-	  }
-	  $_ = $line;
-	  $line = '';
+	    # The link may contains a translatable string.
+	    my $link = $args[0];
 
-	  # FIXME: Handle hidden packages "-foo".
-	  if (m,^\s*Section\s+"-([^"]+)",)
-	  {
-	      # Hidden packages are dependency-tracked.
-	      $pkg{title} = $1;
-	      $pkg{level} = 2000;  # Superfluous.
-	      $pkg{hidden} = 1;
-	  }
-	  elsif (m,^\s*Section\s+"([^"]+)",)
-	  {
-	      # FIXME: Work around for manuals, which have variables
-	      # in this place.
-	      my $title = $1;
-	      $title =~ s/^\$\((.*)\)$/\$$1/;
-	      eval '$pkg{title} = "' . $title . '"';
-	      $pkg{level} = 1;
-	  }
-	  elsif (m,^\s*Section\s+/o\s+"([^"]+)",)
-	  {
-	      # FIXME: Work around for manuals, which have variables
-	      # in this place.
-	      $pkg{title} = $1;
-	      # Default install level is 3.
-	      $pkg{level} = 1000;
-	  }
-	  elsif (m,^\s*LangString\s+DESC_SEC_\S+\s+\$\{LANG_ENGLISH\}\s+\"([^"]+)\"\s*\r?\n,)
-	  {
-	      $pkg{description} = $1;
-	  }
-	  # Special hack for kdesupport.nsi.  FIXME: Could do real
-	  # variable substitution here.
-	  elsif (m,^\s*\!define prefix \${ipdir}/([^\$]+)-\$.*\r?\n$,)
-	  {
-	      $prefix = "playground/install/pkgs/$1-$pkg{version}";
-	  }
-	  elsif (m,^\s*SetOutPath\s+"?\$INSTDIR\\?([^"]*)"?\s*\r?\n$,)
-	  {
-	      $dir = $1;
-	  }
-	  elsif (m,^\s*File\s+"?\$\{(prefix|BUILD_DIR|SRCDIR)\}(?:/(\S*))?/([^/"\s]+)"?\s*\r?\n$,)
-	  {
-	      my $source = $3;
+	    # We filter for startmenu shortcuts, as the others are
+	    # just more of the same.  Equivalently, we could filter
+	    # for a block between two labels.
+	    return if ($link !~ m/STARTMENU_FOLDER/);
 
-	      $source = "$2/$source" if defined $2;
-	      $source = "${prefix}/$source" if $1 eq 'prefix';
-	      # FIXME: We assume that srcdir == build_dir here.
+	    # Take the base name of the link.  */
+	    $link =~ s/^.*\\([^\\]*)\"$/$1/;
 
-	      push @files, { source => $source, dir => $dir, target => $3 };
-	      push @::sources, $source;
-	  }
-	  elsif (m,^\s*File\s+/oname=(\S+)\s+"?\$\{(prefix|BUILD_DIR)\}/([^"\s]+)"?\s*\r?\n$,)
-	  {
-	      my $target = $1;
-	      my $source = $3;
+	    my $target = nsis_eval ($parser, $file, $args[1]);
+	    $target =~ s/^\$INSTDIR\\//;
 
-	      $source = "${prefix}/$source" if $2 eq 'prefix';
+	    my $icon = nsis_eval ($parser, $file, $args[3]);
+	    my $icon_idx = nsis_eval ($parser, $file, $args[4]);
+	    fail "$file:$.: not supported" if ($icon_idx ne '');
 
-	      # Temp files are due to overwrite attempts, which are
-	      # handled automatically by the Windows Installer.
-	      # Ignore them here.
-	      next if $target =~ m/\.tmp$/;
+	    # The description contains a translatable string.
+	    my $description = $args[7];
 
-	      push @files, { source => $source,
-			     dir => $dir, target => $target };
-	      push @::sources, $source;
-	  }
-	  elsif (m,^\s*WriteRegStr\s+(\S+)\s+"([^"]+)"\s+"([^"]+)"\s+"?([^"]+)"?\s*\r?\n$,)
-	  {
-	      my ($root, $key, $name, $value) = ($1, $2, $3, $4);
-	      $value =~ s/\$INSTDIR/\[INSTDIR\]/g;
-	      push (@registry,
-		    { root => $root, key => $key, name => $name,
-		      value => $value, type => 'string' });
-	  }
-      }
-      close (FILE);
-      $pkg{files} = \@files;
-      $pkg{registry} = \@registry;
+	    $parser->{shortcuts}->{$target} = { link => $link,
+						target => $target,
+						icon => $icon,
+						description => $description };
+	}
+    }
 
-      # Some things we can not easily parse from the NSI files.  For
-      # these, we do manual overrides here.
-      if ($pkg{name} eq 'gnupg')
-      {
-	  $pkg{features} .= " Absent='disallow'";
-      }
-      elsif ($pkg{name} eq 'gnupg2')
-      {
-	  $pkg{features} .= " Absent='disallow'";
-      }
+    # LangString support.
+    #
+    # LangString directives must be stated at the top-level of the file.
 
-      push @::components, \%pkg;
-  }
+    elsif ($parser->{state} eq '' and $command eq 'LangString')
+    {
+	fail "$file:$.: syntax error" if ($#args != 2);
+
+	my $lang = $args[1];
+	$lang =~ s/^\$\{LANG_(\w*)\}$/$1/;
+	if ($lang eq 'ENGLISH')
+	{
+	    $lang = 'en';
+	}
+	elsif ($lang eq 'GERMAN')
+	{
+	    $lang = 'de';
+	}
+	else
+	{
+	    fail "$file:$.: unsupported language ID $args[1]";
+	}
+	$parser->{po}->{$lang}->{$args[0]} = $args[2];
+    }
+
+    # Function support.
+    #
+    # Most functions are ignored.  Some are of special interest and
+    # are parsed separately.
+
+    elsif ($parser->{state} eq '' and $command eq 'Function')
+    {
+	fail "$file:$.: syntax error" if ($#args != 0);
+
+	if ($args[0] eq 'CalcDepends')
+	{
+	    $parser->{state} = 'function-calc-depends';
+	}
+	elsif ($args[0] eq 'CalcDefaults')
+	{
+	    $parser->{state} = 'function-calc-defaults';
+	}
+	else
+	{
+	    # Functions we do not find interesting are skipped.
+	    print STDERR
+		"$file:$.: warning: ignoring function $args[0]\n"
+		if $::nsis_parser_warn;
+	    delete $parser->{dep_name};
+	    $parser->{state} = 'ignore-until-FunctionEnd';
+	}
+    }
+
+    # Function calc-depends.
+    #
+    # This function gathers information about dependencies between
+    # features.  Features are identified by their frobbed names.  The
+    # format is as such: First, a couple of UnselectSection macros,
+    # one for each dependency.  Then SelectSection invocations for all
+    # packages which should always be installed (mandatory), followed
+    # by one block for each feature, consisting of a label "have_FOO:"
+    # where FOO is the frobbed package name (in lowercase, usually),
+    # followed by SelectSection invocations, one for each dependency,
+    # and finally a "skip_FOO:" label to finish the block.
+    #
+    # The order of these statements and blocks must be so that a single pass
+    # through the list is sufficient to resolve all dependencies, that means
+    # in pre-fix order.
+
+    elsif ($parser->{state} eq 'function-calc-depends')
+    {
+	if ($command eq 'FunctionEnd')
+	{
+	    undef $parser->{state};
+	}
+	elsif ($command =~ m/^have_(.*):$/)
+	{
+	    $parser->{dep_name} = $1;
+	    $parser->{pkgs}->{$1}->{deps} = {};
+	}
+	elsif ($command eq '!insertmacro')
+	{
+	    fail "$file:$.: syntax error" if $#args < 0;
+	    if ($args[0] eq 'SelectSection')
+	    {
+		fail "$file:$.: syntax error" if $#args != 1;
+		my $name = $args[1];
+		$name =~ s/^\$\{SEC_(.*)\}$/$1/;
+
+		if (not exists $parser->{dep_name})
+		{
+		    # A stray SelectSection chooses defaults.
+		    $parser->{pkgs}->{$name}->{features} .=
+			" Absent='disallow'";
+		}
+		else
+		{
+		    my $dep_name = $parser->{dep_name};
+
+		    # Add $name as a dependency for $dep_name.
+		    $parser->{pkgs}->{$dep_name}->{deps}->{$name} = 1;
+		}
+	    }
+	}
+	elsif ($command =~ m/^skip_(.*):$/)
+	{
+	    fail "$file:$.: stray skip_FOO label"
+		if not exists $parser->{dep_name};
+
+	    my $dep_name = $parser->{dep_name};
+	    my $dep_pkg = $parser->{pkgs}->{$dep_name};
+
+	    # We resolve indirect dependencies right now.  This works
+	    # because dependencies are required to be listed in
+	    # pre-fix order.
+
+	    foreach my $name (keys %{$parser->{pkgs}})
+	    {
+		my $pkg = $parser->{pkgs}->{$name};
+
+		# Check if $dep_name is a dependency for $name.
+		if (exists $pkg->{deps}->{$dep_name})
+		{
+		    # Add all dependencies of $dep_name to $name.
+		    foreach my $dep (keys %{$dep_pkg->{deps}})
+		    {
+			$pkg->{deps}->{$dep} = $pkg->{deps}->{$dep_name} + 1
+			    if (not defined $pkg->{deps}->{$dep});
+		    }
+		}
+	    }
+	    delete $parser->{dep_name};
+	}
+    }
+
+    # Function calc-depends.
+    #
+    # Format:
+    # g4wihelp::config_fetch_bool "inst_FOO"
+
+    elsif ($parser->{state} eq 'function-calc-defaults')
+    {
+	if ($command eq 'FunctionEnd')
+	{
+	    undef $parser->{state};
+	}
+	elsif ($command eq 'g4wihelp::config_fetch_bool')
+	{
+	    fail "$file:$.: syntax error" if $#args != 0;
+
+	    if ($args[0] !~ m/^"inst_(.*)"$/)
+	    {
+		fail "$file:$.: syntax error";
+	    }
+
+	    $parser->{pkgs}->{$1}->{ini_inst} = 1;
+	}
+    }
 }
 
+
+# MSI generator.
 
+# Simple indentation tracking, for pretty printing.
+$::level = 0;
+
+
 sub dump_all
 {
-    my $pkg;
+    my ($parser) = @_;
+
+    my $pkgname;
     # A running count for files within each feature.
     my $fileidx;
     # A running count for registry settings within each feature.
@@ -408,8 +1020,10 @@
     # The current directory.
     my $cdir = '';
 
-    foreach $pkg (@::components)
+    foreach $pkgname (@{$parser->{pkg_list}})
     {
+	my $pkg = $parser->{pkgs}->{$pkgname};
+
 	$fileidx = 0;
 	foreach my $file (@{$pkg->{files}})
 	{
@@ -455,12 +1069,12 @@
 	    }
 
 	    print ' ' x $::level
-		. "<Component Id='c_$pkg->{frobbed_name}_$fileidx' Guid='"
+		. "<Component Id='c_$pkg->{name}_$fileidx' Guid='"
 		. get_guid ($targetfull) . "'>\n";
 	    print ' ' x $::level
-		. "  <File Id='f_$pkg->{frobbed_name}_$fileidx' Name='"
-		. $file->{target} . "' Source='" . $file->{source} . "'"
-		. " DefaultLanguage='1033'>\n";
+		. "  <File Id='f_$pkg->{name}_$fileidx' Name='"
+		. $file->{target} . "' Source='" . $file->{source} . "'>\n";
+	    # Does not help to avoid the warnings: DefaultLanguage='1033'.
 
 	    # EXCEPTIONS:
 	    if ($targetfull eq 'gpgol.dll')
@@ -484,26 +1098,27 @@
 	    }
 
 	    # Create shortcuts.
-	    if (defined $::shortcuts{$targetfull})
+	    if (defined $parser->{shortcuts}->{$targetfull})
 	    {
+		# FIXME: Use shortcut info.
 		print ' ' x $::level
-		    . "    <Shortcut Id='sm_$pkg->{frobbed_name}_$fileidx' "
+		    . "    <Shortcut Id='sm_$pkg->{name}_$fileidx' "
 		    . "Directory='ProgramMenuDir' Name='$file->{target}'/>\n";
 
 #		print ' ' x $::level
-#                   . "    <Shortcut Id='sm_$pkg->{frobbed_name}_$fileidx' "
+#                   . "    <Shortcut Id='sm_$pkg->{name}_$fileidx' "
 #		    . "Directory='DesktopFolder' Name='$file->{target}'/>\n";
 	    }
 
 	    print ' ' x $::level
 		. "  </File>\n";
 
-	    if (defined $::shortcuts{$targetfull})
+	    if (defined $parser->{shortcuts}->{$targetfull})
 	    {
 		# http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg02746.html
 		# -sice:ICE64
 		print ' ' x $::level
-		    . "    <RemoveFolder Id='rsm_$pkg->{frobbed_name}_$fileidx' "
+		    . "  <RemoveFolder Id='rsm_$pkg->{name}_$fileidx' "
 		    . "Directory='ProgramMenuDir' On='uninstall'/>\n";
 	    }
 
@@ -581,10 +1196,10 @@
 	    . '/' . $reg->{name};
 
 	    print ' ' x $::level
-		. "<Component Id='c_$pkg->{frobbed_name}_r_$regidx' Guid='"
+		. "<Component Id='c_$pkg->{name}_r_$regidx' Guid='"
 		. get_guid ($target) . "'>\n";
 	    print ' ' x $::level
-		. "  <RegistryValue Id='r_$pkg->{frobbed_name}_$regidx' Root='"
+		. "  <RegistryValue Id='r_$pkg->{name}_$regidx' Root='"
 		. $reg->{root} . "' Key='" . $reg->{key} . "' Name='"
 		. $reg->{name} . "' Action='write' Type='" . $reg->{type}
 		. "' Value='" . $reg->{value} . "'/>\n";
@@ -593,6 +1208,15 @@
 	    $regidx++;
 	}
     }
+
+    my @cdir = grep (!/^$/, split (/\\/, $cdir));
+    my $j;
+    for ($j = 0; $j <= $#cdir; $j++)
+    {
+	$::level -= 2;
+	print ' ' x $::level
+	    . "</Directory>\n";
+    }
 }
 
 
@@ -606,14 +1230,14 @@
     foreach my $file (@{$pkg->{files}})
     {
 	print ' ' x $::level
-	    . "  <ComponentRef Id='c_$pkg->{frobbed_name}_$fileidx'/>\n";
+	    . "  <ComponentRef Id='c_$pkg->{name}_$fileidx'/>\n";
 	$fileidx++;
     }
     $regidx = 0;
     foreach my $reg (@{$pkg->{registry}})
     {
 	print ' ' x $::level
-	    . "  <ComponentRef Id='c_$pkg->{frobbed_name}_r_$regidx'/>\n";
+	    . "  <ComponentRef Id='c_$pkg->{name}_r_$regidx'/>\n";
 	$regidx++;
     }
 }
@@ -621,10 +1245,13 @@
 
 sub dump_all2
 {
-    my $pkg;
+    my ($parser) = @_;
 
-    foreach $pkg (@::components)
+    my $pkgname;
+
+    foreach $pkgname (@{$parser->{pkg_list}})
     {
+	my $pkg = $parser->{pkgs}->{$pkgname};
 	my $features;
 
 	next if $pkg->{hidden};
@@ -634,24 +1261,32 @@
 	$features .= " Description='$pkg->{description}'"
 	    if $pkg->{description};
 	
+	my $title = nsis_translate ($parser, '', $pkg->{title});
+
 	print ' ' x $::level
-	    . "<Feature Id='p_$pkg->{frobbed_name}' Level='$pkg->{level}' "
-	    . "Title='$pkg->{title}'" . $features . ">\n";
+	    . "<Feature Id='p_$pkg->{name}' Level='$pkg->{level}' "
+	    . "Title='$title'" . $features . ">\n";
+	if ($pkg->{ini_inst})
+	{
+	    my $uc_pkgname = uc ($pkgname);
+
+	    print ' ' x $::level
+		. "<Condition Level='$::nsis_level_default'>"
+		. "INST_$uc_pkgname = \"true\"</Condition>\n";
+	    print ' ' x $::level
+		. "<Condition Level='$::nsis_level_optional'>"
+		. "INST_$uc_pkgname = \"false\"</Condition>\n";
+	}
+
 	dump_meat ($pkg);
 
-	foreach my $dep (keys %{$::deps{$pkg->{frobbed_name}}})
+	foreach my $dep (keys %{$pkg->{deps}})
 	{
-	    my $deppkg;
-
-	    foreach my $_pkg (@::components)
-	    {
-		$deppkg = $_pkg;
- 		last if ($_pkg->{frobbed_name} eq $dep);
-	    }
+	    my $deppkg = $parser->{pkgs}->{$dep};
 	    
 	    print ' ' x $::level
-		. "  <Feature Id='p_$pkg->{frobbed_name}_$dep' "
-		. "Title='p_$pkg->{frobbed_name}_$dep' "
+		. "  <Feature Id='p_$pkg->{name}_$dep' "
+		. "Title='p_$pkg->{name}_$dep' "
 		. "Level='$pkg->{level}' Display='hidden' "
 		. "InstallDefault='followParent'>\n";
 	    $::level += 2;
@@ -666,32 +1301,103 @@
 }
 
 
-# WiX is the Windows Installer XML toolset.  It contains a compiler
-# (candle.exe) and a linker (light.exe) which can assemble an XML file
-# and data files to a Windows installer package (MSI).
-#
-# The following code creates an appropriate XML file for Gpg4win.
+# Just so that it is defined.
+$. = 0;
 
-# We use a single media element, which is also the default for all
-# components and directory elements.
+my %parser = ( pre_depth => 0, pre_true => 0 );
+my $parser = \%parser;
 
-# FIXME: Use Vital for all file attributes?
 fetch_guids ();
-collect_all ();
-get_deps ();
-get_shortcuts ();
 
-$::product_id = get_guid ("/PRODUCT/$::config{_BUILD_FILEVERSION}");
-$::upgrade_code = get_guid ("/UPGRADE/1");
+while ($#ARGV >= 0 and $ARGV[0] =~ m/^-/)
+{
+    my $opt = shift @ARGV;
+    if ($opt =~ m/^--guids$/)
+    {
+	$::guid_file = shift @ARGV;
+    }
+    elsif ($opt =~ m/^--manifest$/)
+    {
+	$::files_file = shift @ARGV;
+    }
+    elsif ($opt =~ m/^-D([^=]*)=(.*)$/)
+    {
+	$parser->{pre_symbols}->{$1} = $2;
+    }
+    elsif ($opt =~ m/^-L(.*)$/)
+    {
+	$::lang = $1;
+	# Test if it is supported.
+	lang_to_lcid ($::lang);	
+    }
+    elsif ($opt eq '--usage')
+    {
+	print STDERR "Usage: $0 [-DNAME=VALUE...] NSIFILE\n";
+	print STDERR "Use --help or -h for more information.\n";
+	exit 1;
+    }
+    elsif ($opt eq '-h' or $opt eq '--help')
+    {
+	print STDERR "Usage: $0 [-DNAME=VALUE...] NSIFILE\n";
+	print STDERR "Convert the .nsi file NSIFILE to a WiX source file.\n";
+	print STDERR "Options:\n";
+        print STDERR "       --guids NAME     Save GUIDs into file NAME (default: $::guid_file)\n";
+        print STDERR "       --manifest NAME  Save included files into file NAME (default: $::files_file)\n";
+        print STDERR "       -DNAME=VALUE     Define preprocessor symbol NAME to VALUE\n";
+        print STDERR "       -LLANG           Build installer for language LANG (default: $::lang)\n";
+	print STDERR "\n";
+        print STDERR "       -h|--help        Print this help and exit\n";
+	exit 0;
+    }
+    else
+    {
+	print STDERR "$0: unknown option $opt\n";
+	print STDERR "Usage: $0 [-DNAME=VALUE...] NSIFILE\n";
+	print STDERR "Use --help or -h for more information.\n";
+	exit 1;
+    }
+}
 
+
+if ($#ARGV < 0)
+{
+    nsis_parse_file ($parser, '-');
+}
+else
+{
+    nsis_parse_file ($parser, $ARGV[0]);
+}
+
+# Add exceptions.
+# ===============
+
+$parser->{pkgs}->{gnupg}->{deps}->{gpg4win} = 1;
+
+# For debugging:
+# use Data::Dumper;
+# print Dumper ($parser);
+# exit;
+
+# Dump the gathered information.
+# ==============================
+
+my $BUILD_FILEVERSION = nsis_fetch ($parser, '_BUILD_FILEVERSION');
+
+my $product_id = get_guid ("/PRODUCT/$BUILD_FILEVERSION");
+my $upgrade_code = get_guid ("/UPGRADE/1");
+
+my $INSTALL_DIR = nsis_fetch ($parser, 'INSTALL_DIR');
+
+my $lcid = lang_to_lcid ($::lang);
+
 print <<EOF;
 <?xml version='1.0'?>
 <Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
   <Product Name='Gpg4win'
-           Id='$::product_id'
-           UpgradeCode='$::upgrade_code'
-           Language='1033'
-           Version='$::config{_BUILD_FILEVERSION}'
+           Id='$product_id'
+           UpgradeCode='$upgrade_code'
+           Language='$lcid'
+           Version='$BUILD_FILEVERSION'
            Manufacturer='g10 Code GmbH'>
     <Package Description='Gpg4win Installer'
              Comments='http://www.gpg4win.org/'
@@ -700,10 +1406,10 @@
              InstallPrivileges='elevated'
              Manufacturer='g10 Code GmbH'/>
 
-    <Upgrade Id='$::upgrade_code'>
+    <Upgrade Id='$upgrade_code'>
       <UpgradeVersion Property='UPGRADEPROP'
                       IncludeMaximum='no'
-                      Maximum='$::config{_BUILD_FILEVERSION}'/>
+                      Maximum='$BUILD_FILEVERSION'/>
     </Upgrade>
 
     <InstallExecuteSequence>
@@ -718,18 +1424,39 @@
     <Media Id='1' Cabinet='gpg4win.cab' EmbedCab='yes'/>
 
     <Property Id="INSTDIR">
-      <RegistrySearch Id='gpg4win_registry' Type='raw'
-      Root='HKLM' Key='Software\\GNU\\GnuPG' Name='Install Directory' />
+      <RegistrySearch Id='gpg4win_instdir_registry' Type='raw'
+       Root='HKLM' Key='Software\\GNU\\GnuPG' Name='Install Directory'/>
+      <IniFileSearch Id='gpg4win_instdir_ini' Type='raw'
+       Name='gpg4win.ini' Section='gpg4win' Key='instdir'/>
     </Property>
 
+EOF
+
+foreach my $pkgname (@{$parser->{pkg_list}})
+{
+    if (exists $parser->{pkgs}->{$pkgname}->{ini_inst})
+    {
+	my $uc_pkgname = uc ($pkgname);
+
+	print <<EOF;
+    <Property Id="INST_$uc_pkgname">
+      <IniFileSearch Id='gpg4win_ini_inst_$pkgname' Type='raw'
+       Name='gpg4win.ini' Section='gpg4win' Key='inst_$pkgname'/>
+    </Property>
+
+EOF
+    }
+}
+
+print <<EOF;
     <Directory Id='TARGETDIR' Name='SourceDir'>
       <Directory Id='ProgramFilesFolder' Name='PFiles'>
         <Directory Id='GNU' Name='GNU'>
-          <Directory Id='INSTDIR' Name='$::INSTDIR'>
+          <Directory Id='INSTDIR' Name='$INSTALL_DIR'>
 EOF
 
 $::level = 12;
-dump_all ();
+dump_all ($parser);
 
 
 print <<EOF;
@@ -738,11 +1465,13 @@
       </Directory>
 EOF
 
-if (scalar keys %::shortcuts)
+if (scalar keys %{$parser->{shortcuts}})
 {
+    my $name = nsis_fetch ($parser, 'PRETTY_PACKAGE');
+
     print <<EOF;
       <Directory Id='ProgramMenuFolder' Name='PMenu'>
-        <Directory Id='ProgramMenuDir' Name='$::name'/>
+        <Directory Id='ProgramMenuDir' Name='$name'/>
       </Directory>
 EOF
 }
@@ -760,16 +1489,18 @@
 EOF
 
 $::level = 6;
-dump_all2 ();
+dump_all2 ($parser);
     
 #    <Icon Id="Foobar10.exe" SourceFile="FoobarAppl10.exe"/>
 
+# Removed this, because it is not localized:
+#    <UIRef Id='WixUI_ErrorProgressText' />
+
 print <<EOF;
     </Feature>
 
     <WixVariable Id='WixUILicenseRtf' Value='gpl.rtf'/>
     <UIRef Id='WixUI_Mondo' />
-    <UIRef Id='WixUI_ErrorProgressText' />
 
   </Product>
 </Wix>
@@ -780,5 +1511,4 @@
 # different machine for invocation of WiX.
 
 store_guids ();
-store_files ();
-
+store_files ($parser);



More information about the Gpg4win-commits mailing list