3 # NSISWrapper - a helper program for making Windows installers.
4 # Copyright (C) 2008 Red Hat Inc.
5 # Written by Richard W.M. Jones <rjones@redhat.com>,
6 # http://fedoraproject.org/wiki/MinGW
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or (at
11 # your option) any later version.
13 # This program is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 use File::Temp qw/tempfile/;
32 nsiswrapper - Helper program for making NSIS Windows installers
36 nsiswrapper [options] [roots...]
38 nsiswrapper myprogram.exe anotherprog.exe docs/ > script.nsis
40 nsiswrapper --run myprogram.exe anotherprog.exe docs/
44 nsiswrapper is a helper program for making it easier to create Windows
45 installers in a cross-compiler environment. It still requires NSIS (a
46 Windows installer generator) but cuts out the tedium of writing the
47 NSIS command script, and can even invoke NSIS automatically to
48 generate a final Windows executable.
50 The general way to use it is to list out some files that you want
51 packaged. For example:
53 nsiswrapper myprogram.exe
55 This will search for C<myprogram.exe> and any libraries (C<*.dll>)
56 that it depends upon, and then it will print out an NSIS script.
58 If you want to have it run C<makensis> as well (to automatically
59 create a Windows installer) then do:
61 nsiswrapper --run myprogram.exe
63 which will generate C<installer.exe> output file that contains
64 C<myprogram.exe> plus any dependencies.
66 You can list other files and directories that you want to have
67 contained in your installer. For example:
69 nsiswrapper myprogram.exe anotherprog.exe docs/*.html
71 There are many other command line options which control aspects of the
72 NSIS command script (and hence, the final installer), such as:
78 The name of the final installer.
82 Desktop shortcuts and menu items.
90 It's a good idea to examine the NSIS command script, to check that
91 nsiswrapper is including all the right dependencies.
93 =head1 ROOTS (FILES AND DIRECTORIES)
95 Each parameter should refer to a file or directory which is to be
96 included in the installer.
98 These are known as "roots" because we also automatically add any
99 dependencies to the list of files. Thus if a Windows executable
100 requires any DLLs, those are added automatically. DLLs are searched
101 for on the current C<$PATH> (environment variable).
103 We choose the install location by removing any common prefix from the
104 names of roots, which generally ensures that the original directory
105 structure is preserved. Thus for example if the original roots (and
106 any dependencies) are:
108 /usr/i686-pc-mingw32/sys-root/mingw/bin/program.exe
109 /usr/i686-pc-mingw32/sys-root/mingw/bin/library.dll
110 /usr/i686-pc-mingw32/sys-root/mingw/etc/config
112 then the install directory will look like this:
114 $INSTDIR/bin/program.exe
115 $INSTDIR/bin/library.dll
118 (C<$INSTDIR> is the installation directory chosen by the user at
121 You can also specify the install location (relative to C<$INSTDIR>) by
122 adding roots of the form:
128 /usr/i686-pc-mingw32/sys-root/mingw/bin/program.exe=program.exe
129 /usr/i686-pc-mingw32/sys-root/mingw/bin/library.dll=library.dll
130 /usr/i686-pc-mingw32/sys-root/mingw/etc/config=conf/config
144 Print brief help message and exit.
148 Print the full manual page for the command and exit.
152 Print verbose messages while running. If this is not given then we
153 try to operate quietly.
157 Normally this program just prints out the NSIS installer command
158 script. However if you supply this option, then we run C<makensis>
159 and attempt to generate an actual Windows installer.
163 GTK programs should normally supply this option. It ensures that the
164 correct files are copied and/or created by the installer for GTK
167 =item B<--name "Name">
169 Set the long name of the installer.
171 If not set, the script tries to invent a suitable name based on the
172 first root file given on the command line.
174 See also B<--outfile>.
176 =item B<--outfile myinstaller.exe>
178 Set the output filename for the installer.
180 If not set, this defaults to C<installer.exe>.
182 This is the same as the C<OutFile> option to NSIS.
184 =item B<--installdir 'C:\foo'>
186 Set the default Windows installation directory. If not set, this
187 program will choose a suitable default based on the name.
189 In any case, the end user can override this when they run the
192 Note that since this string will contain backslashes, you should
193 single-quote it to protect it from the shell.
195 This is the same as the C<InstallDir> option to NSIS.
197 =item B<--installdirregkey 'HKLM SOFTWARE\FOO'>
199 Set the name of the registry key used to save the installation
200 directory. This has two purposes: Firstly it is used to automagically
201 remember the installation directory between installs. Secondly your
202 program can use this as one method to find its own installation
203 directory (there are other ways to do this).
205 The default is C<HKLM SOFTWARE\Name> where C<Name> is derived from the
206 name of the installer.
208 Note that since this string will contain backslashes and spaces, you
209 should single-quote it to protect it from the shell.
211 This is the same as the C<InstallDirRegKey> option to NSIS.
226 my $outfile = 'installer.exe';
228 my $installdirregkey = '';
230 # XXX Should make these configurable.
231 my $mingw32_prefix = '/usr/i686-pc-mingw32/sys-root/mingw';
232 my $mingw32_bindir = $mingw32_prefix . '/bin';
233 my $mingw32_libdir = $mingw32_prefix . '/lib';
234 my $mingw32_sysconfdir = $mingw32_prefix . '/etc';
238 my $result = GetOptions (
241 "verbose" => \$verbose,
243 "with-gtk" => \$with_gtk,
245 "outfile=s" => \$outfile,
246 "installdir=s" => \$installdir,
247 "installdirregkey=s" => \$installdirregkey,
249 die "nsiswrapper: use --help for information about command line options\n"
252 pod2usage(1) if $help;
253 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
255 # Add the roots to the list of files.
256 die "nsiswrapper: no roots specified: use --help for more help\n"
258 foreach my $file (@ARGV) {
259 my ($dir, $exec) = (0, 0);
261 # Is it source=destination?
263 if ($file =~ /^(.*)=(.*)$/) {
270 die "$source: not a file or directory\n"
271 unless -f $source || -d $source;
273 $exec = 1 if $source =~ m/\.exe$/i;
274 $dir = 1 if -d $source;
283 # Deal with explicit destination.
285 my ($install_dir, $install_name);
287 if ($dest =~ m{(.*)/(.*)}) {
292 $install_name = $dest;
295 # Convert / in install_dir into backslashes.
296 $install_dir =~ s{/}{\\}g;
298 $files{$source}->{install_dir} = $install_dir;
299 $files{$source}->{install_name} = $install_name;
305 # Massage the first root into a suitable package name.
314 # InstallDir not set?
318 $installdir = "c:\\$_"
321 # InstallDirRegKey not set?
322 if (!$installdirregkey) {
325 $installdirregkey = "HKLM SOFTWARE\\$_"
329 # Check prerequisites.
333 my @paths = split (/:/, $ENV{PATH});
336 $objdump = check_path ("i686-pc-mingw32-objdump", @paths);
337 if (! $objdump || ! -x $objdump) {
338 die "i686-pc-mingw32-objdump: program not found on \$PATH\n"
343 # Check for the existance of a file at the given paths (not
344 # necessarily executable). Returns the pathname of the file or
345 # undefined if not found.
353 foreach my $dir (@paths) {
354 my $file = $dir . "/" . $_;
363 # Print configuration.
367 print "Configuration:\n";
368 print "\t\$PATH = $ENV{PATH}\n";
369 print "\t\$objdump = $objdump\n";
370 print "\t\$verbose = $verbose\n";
371 print "\t\$name = \"$name\"\n";
372 print "\t\$outfile = \"$outfile\"\n";
373 print "\t\$installdir = \"$installdir\"\n";
374 print "\t\$installdirregkey = \"$installdirregkey\"\n";
375 my @roots = keys %files;
376 print "\t\@roots = (", join (", ", @roots), ")\n";
377 print "End of configuration.\n";
380 # Starting at the roots, get the dependencies.
382 my $missing_deps = 0;
388 while ($deps_added > 0) {
391 foreach (keys %files) {
392 my @deps = get_deps_for_file ($_);
394 # Add the deps as separate files.
396 $deps_added += add_file_unless_exists (
406 die "please correct missing dependencies shown above\n"
407 if $missing_deps > 0;
410 sub add_file_unless_exists
415 unless (exists $files{$name}) {
416 die "$name: not a file or directory\n" unless -f $name || -d $name;
418 $details{name} = $name;
419 $files{$name} = \%details;
427 my $path_warning = 0;
429 sub get_deps_for_file
432 my @paths = split (/:/, $ENV{PATH});
434 # If we already fetched the dependencies for this file, just
435 # return that list now.
436 if (exists $files{$file}->{deps}) {
437 return @{$files{$file}->{deps}}
442 # We only know how to do this for *.exe and *.dll files.
443 if (m/\.exe$/i || m/\.dll$/i) {
444 my $cmd = "$objdump -p '$file' |
446 grep -Eo '[-._[:alnum:]]+\.dll' |
447 sort -u"; # XXX quoting
448 open DEPS, "$cmd |" or die "$cmd: $!";
452 # Ignore Windows system DLL deps.
453 next if is_windows_system_dll ($_);
455 # Does the file exist on the path?
456 my $found = check_path ($_, @paths);
460 warn "MISSING DEPENDENCY: $_ (for $file)\n";
462 unless ($path_warning) {
463 warn "You may need to add the directory containing this file to your \$PATH\n";
472 print "dependencies found for binary $file:\n\t",
473 join ("\n\t", @deps), "\n";
475 print "no dependencies found for $file\n"
481 # Cache the list of dependencies so we can just return it
482 # immediately next time.
483 $files{$file}->{deps} = \@deps;
487 sub is_windows_system_dll
492 $_ eq 'kernel32.dll' ||
494 $_ eq 'mscoree.dll' ||
495 $_ eq 'msvcrt.dll' ||
499 # Add Gtk dependencies, if --with-gtk.
503 add_file_unless_exists (
504 "$mingw32_libdir/gtk-2.0",
508 install_dir => "lib",
509 install_name => "gtk-2.0"
512 add_file_unless_exists (
513 "$mingw32_libdir/pango",
517 install_dir => "lib",
518 install_name => "pango"
521 add_file_unless_exists (
522 "$mingw32_sysconfdir/fonts",
526 install_dir => "etc",
527 install_name => "fonts"
530 add_file_unless_exists (
531 "$mingw32_sysconfdir/gtk-2.0",
535 install_dir => "etc",
536 install_name => "gtk-2.0"
539 add_file_unless_exists (
540 "$mingw32_sysconfdir/pango",
545 install_dir => "etc",
546 install_name => "pango"
549 # We need to run pango-querymodules after installation to
550 # rebuild etc\pango\pango.modules.
551 add_file_unless_exists (
552 "$mingw32_bindir/pango-querymodules.exe",
559 # Decide how we will name the output files. This removes the
560 # common prefix from filenames, if it can determine one.
564 my @names = keys %files;
566 # Don't care about files that already have an install
567 # directory/name defined, ie. they were specified as source=dest
568 # on the command line.
569 @names = grep { ! exists $files{$_}->{install_name} } @names;
571 # Determine if all the names share a common prefix.
572 my @namelens = map { length } @names;
573 my $shortest = min (@namelens);
576 for ($prefixlen = $shortest; $prefixlen >= 0; --$prefixlen) {
577 my @ns = map { substr $_, 0, $prefixlen } @names;
581 if ($verbose) { print "prefix length = $prefixlen\n" }
583 # Remove the prefix from each name and save the install directory
584 # and install filename separately.
585 foreach my $name (@names) {
586 my $install_as = substr $name, $prefixlen;
588 my ($install_dir, $install_name);
590 if ($install_as =~ m{(.*)/(.*)}) {
595 $install_name = $install_as;
598 # Convert / in install_dir into backslashes.
599 $install_dir =~ s{/}{\\}g;
601 $files{$name}->{install_dir} = $install_dir;
602 $files{$name}->{install_name} = $install_name;
610 $max = $_ if $_ > $max;
619 $min = $_ if $_ < $min;
628 return 0 if $_ ne $s;
633 # Print the list of files.
638 foreach (sort keys %files) {
640 if ($files{$_}->{root}) {
643 if ($files{$_}->{dir}) {
646 print STDOUT ("\n\t => ",
647 $files{$_}->{install_dir}, " \\ ", $files{$_}->{install_name},
650 print "End of files.\n";
653 # Write the NSIS script.
660 #!Nsis Installer Command Script
662 # This is an NSIS Installer Command Script generated automatically
663 # by the Fedora nsiswrapper program. For more information see:
665 # http://fedoraproject.org/wiki/MinGW
667 # To build an installer from the script you would normally do:
669 # makensis this_script
671 # which will generate the output file '$outfile' which is a Windows
672 # installer containing your program.
676 InstallDir "$installdir"
677 InstallDirRegKey $installdirregkey "Install_Dir"
680 ShowUninstDetails hide
682 # Uncomment this to enable BZip2 compression, which results in
683 # slightly smaller files but uses more memory at install time.
692 ComponentText "Select which optional components you want to install."
694 DirText "Please select the installation folder."
700 # Set the output files.
702 foreach (sort keys %files) {
703 if (!$olddir || $files{$_}->{install_dir} ne $olddir) {
704 # Moved into a new install directory.
705 my $dir = $files{$_}->{install_dir};
706 print $io "\n SetOutPath \"\$INSTDIR\\$dir\"\n";
710 # If it's a directory, we copy it recursively, otherwise
711 # just copy the single file.
712 if ($files{$_}->{dir}) {
713 if ($files{$_}->{only_mkdir}) {
714 # This is a hack to allow us to create empty directories.
715 my $install_dir = $files{$_}->{install_dir};
716 my $install_name = $files{$_}->{install_name};
717 print $io " CreateDirectory \"\$INSTDIR\\$install_dir\\$install_name\"\n";
719 print $io " File /r \"$_\"\n";
722 print $io " File \"$_\"\n";
728 my $install_dir = $files{"$mingw32_bindir/pango-querymodules.exe"}->{install_dir};
729 my $install_name = $files{"$mingw32_bindir/pango-querymodules.exe"}->{install_name};
731 # This particular piece of Windows stupidity is documented here:
732 # http://forums.winamp.com/showthread.php?postid=438771#post438771
733 # http://forums.winamp.com/printthread.php?s=53c76b4ae4221ff1d9dc361fc5bf7ea2&threadid=231797
735 print $io " ReadEnvStr \$0 COMSPEC\n";
736 print $io " SetOutPath \"\$INSTDIR\"\n";
737 print $io " nsExec::ExecToLog '\$0 /C $install_dir\\$install_name > etc\\pango\\pango.modules'\n"
743 Section "Start Menu Shortcuts"
744 CreateDirectory "\$SMPROGRAMS\\$name"
745 CreateShortCut "\$SMPROGRAMS\\$name\\Uninstall $name.lnk" "\$INSTDIR\\Uninstall $name.exe" "" "\$INSTDIR\\Uninstall $name.exe" 0
748 # Start menu entries for each executable.
749 foreach (sort keys %files) {
750 if ($files{$_}->{exec}) {
751 my $install_dir = $files{$_}->{install_dir};
752 my $install_name = $files{$_}->{install_name};
753 print $io " CreateShortCut \"\$SMPROGRAMS\\$name\\$install_name.lnk\" \"\$INSTDIR\\$install_dir\\$install_name\" \"\" \"\$INSTDIR\\$install_dir\\$install_name\" 0\n";
760 Section "Desktop Icons"
763 # Desktop icons for each executable.
764 foreach (sort keys %files) {
765 if ($files{$_}->{exec}) {
766 my $install_dir = $files{$_}->{install_dir};
767 my $install_name = $files{$_}->{install_name};
768 print $io " CreateShortCut \"\$DESKTOP\\$install_name.lnk\" \"\$INSTDIR\\$install_dir\\$install_name\" \"\" \"\$INSTDIR\\$install_dir\\$install_name\" 0\n";
778 # Remove desktop icons and menu shortcuts.
779 foreach (reverse sort keys %files) {
780 if ($files{$_}->{exec}) {
781 my $install_name = $files{$_}->{install_name};
782 print $io " Delete /rebootok \"\$DESKTOP\\$install_name.lnk\"\n";
783 print $io " Delete /rebootok \"\$SMPROGRAMS\\$name\\$install_name.lnk\"\n";
786 print $io " Delete /rebootok \"\$SMPROGRAMS\\$name\\Uninstall $name.lnk\"\n";
787 print $io " RMDir \"\$SMPROGRAMS\\$name\"\n\n";
789 # Remove remaining files.
791 foreach (reverse sort keys %files) {
792 if (!$olddir || $files{$_}->{install_dir} ne $olddir) {
793 # Moved into a new install directory, so delete the previous one.
794 print $io " RMDir \"\$INSTDIR\\$olddir\"\n\n"
796 $olddir = $files{$_}->{install_dir};
799 # If it's a directory, we delete it recursively, otherwise
800 # just delete the single file.
801 my $install_dir = $files{$_}->{install_dir};
802 my $install_name = $files{$_}->{install_name};
803 if ($files{$_}->{dir}) {
804 print $io " RMDir /r \"\$INSTDIR\\$install_dir\\$install_name\"\n\n";
805 $olddir = ''; # Don't double-delete directory.
807 print $io " Delete /rebootok \"\$INSTDIR\\$install_dir\\$install_name\"\n";
811 print $io " RMDir \"\$INSTDIR\\$olddir\"\n" if $olddir;
818 WriteUninstaller "\$INSTDIR\\Uninstall $name.exe"
823 # Run makensis on the named file.
827 my $filename = shift;
829 system ("makensis", $filename) == 0 or die "makensis: $?"
838 print_config () if $verbose;
839 do_gtk () if $with_gtk;
842 print_files () if $verbose;
844 my ($io, $filename) = tempfile ("nswXXXXXX", UNLINK => 1);
847 run_makensis ($filename);
849 write_script (\*STDOUT);