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/;
31 nsiswrapper - Helper program for making NSIS Windows installers
35 nsiswrapper [options] [roots...]
37 nsiswrapper myprogram.exe anotherprog.exe docs/ > script.nsis
39 nsiswrapper --run myprogram.exe anotherprog.exe docs/
43 nsiswrapper is a helper program for making it easier to create Windows
44 installers in a cross-compiler environment. It still requires NSIS (a
45 Windows installer generator) but cuts out the tedium of writing the
46 NSIS command script, and can even invoke NSIS automatically to
47 generate a final Windows executable.
49 The general way to use it is to list out some files that you want
50 packaged. For example:
52 nsiswrapper myprogram.exe
54 This will search for C<myprogram.exe> and any libraries (C<*.dll>)
55 that it depends upon, and then it will print out an NSIS script.
57 If you want to have it run C<makensis> as well (to automatically
58 create a Windows installer) then do:
60 nsiswrapper --run myprogram.exe
62 which will generate C<installer.exe> output file that contains
63 C<myprogram.exe> plus any dependencies.
65 You can list other files and directories that you want to have
66 contained in your installer. For example:
68 nsiswrapper myprogram.exe anotherprog.exe docs/*.html
70 There are many other command line options which control aspects of the
71 NSIS command script (and hence, the final installer), such as:
77 The name of the final installer.
81 Desktop shortcuts and menu items.
89 It's a good idea to examine the NSIS command script, to check that
90 nsiswrapper is including all the right dependencies.
98 Print brief help message and exit.
102 Print the full manual page for the command and exit.
106 Print verbose messages while running. If this is not given then we
107 try to operate quietly.
111 Normally this program just prints out the NSIS installer command
112 script. However if you supply this option, then we run C<makensis>
113 and attempt to generate an actual Windows installer.
115 =item B<--name "Name">
117 Set the long name of the installer.
119 If not set, the script tries to invent a suitable name based on the
120 first root file given on the command line.
122 See also B<--outfile>.
124 =item B<--outfile myinstaller.exe>
126 Set the output filename for the installer.
128 If not set, this defaults to C<installer.exe>.
130 This is the same as the C<OutFile> option to NSIS.
132 =item B<--installdir 'C:\foo'>
134 Set the default Windows installation directory. If not set, this
135 program will choose a suitable default based on the name.
137 In any case, the end user can override this when they run the
140 Note that since this string will contain backslashes, you should
141 single-quote it to protect it from the shell.
143 This is the same as the C<InstallDir> option to NSIS.
145 =item B<--installdirregkey 'HKLM SOFTWARE\FOO'>
147 Set the name of the registry key used to save the installation
148 directory. This has two purposes: Firstly it is used to automagically
149 remember the installation directory between installs. Secondly your
150 program can use this as one method to find its own installation
151 directory (there are other ways to do this).
153 The default is C<HKLM SOFTWARE\Name> where C<Name> is derived from the
154 name of the installer.
156 Note that since this string will contain backslashes and spaces, you
157 should single-quote it to protect it from the shell.
159 This is the same as the C<InstallDirRegKey> option to NSIS.
173 my $outfile = 'installer.exe';
175 my $installdirregkey = '';
179 my $result = GetOptions (
182 "verbose" => \$verbose,
185 "outfile=s" => \$outfile,
186 "installdir=s" => \$installdir,
187 "installdirregkey=s" => \$installdirregkey,
189 die "nsiswrapper: use --help for information about command line options\n"
192 pod2usage(1) if $help;
193 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
195 # Add the roots to the list of files.
196 die "nsiswrapper: no roots specified: use --help for more help\n"
200 $exec = 1 if m/\.exe$/i;
212 # Massage the first root into a suitable package name.
220 # InstallDir not set?
224 $installdir = "c:\\$_"
227 # InstallDirRegKey not set?
228 if (!$installdirregkey) {
231 $installdirregkey = "HKLM SOFTWARE\\$_"
235 # Check prerequisites.
239 my @paths = split (/:/, $ENV{PATH});
242 $objdump = check_path ("i686-pc-mingw32-objdump", @paths);
243 if (! $objdump || ! -x $objdump) {
244 die "i686-pc-mingw32-objdump: program not found on \$PATH\n"
249 # Check for the existance of a file at the given paths (not
250 # necessarily executable). Returns the pathname of the file or
251 # undefined if not found.
259 foreach my $dir (@paths) {
260 my $file = $dir . "/" . $_;
269 # Print configuration.
273 print "Configuration:\n";
274 print "\t\$PATH = $ENV{PATH}\n";
275 print "\t\$objdump = $objdump\n";
276 print "\t\$verbose = $verbose\n";
277 print "\t\$name = \"$name\"\n";
278 print "\t\$outfile = \"$outfile\"\n";
279 print "\t\$installdir = \"$installdir\"\n";
280 print "\t\$installdirregkey = \"$installdirregkey\"\n";
281 my @roots = keys %files;
282 print "\t\@roots = (", join (", ", @roots), ")\n";
283 print "End of configuration.\n";
286 # Starting at the roots, get the dependencies.
288 my $missing_deps = 0;
296 foreach (keys %files) {
297 my @deps = get_deps_for_file ($_);
299 # Add the deps as separate files.
301 unless (exists $files{$_}) {
314 die "please correct missing dependencies shown above\n"
315 if $missing_deps > 0;
318 my $path_warning = 0;
320 sub get_deps_for_file
323 my @paths = split (/:/, $ENV{PATH});
325 # If we already fetched the dependencies for this file, just
326 # return that list now.
327 if (exists $files{$file}->{deps}) {
328 return @{$files{$file}->{deps}}
333 # We only know how to do this for *.exe and *.dll files.
334 if (m/\.exe$/i || m/\.dll$/i) {
335 my $cmd = "$objdump -p '$file' |
337 grep -Eo '[-._[:alnum:]]+\.dll' |
338 sort -u"; # XXX quoting
339 open DEPS, "$cmd |" or die "$cmd: $!";
343 # Ignore Windows system DLL deps.
344 next if is_windows_system_dll ($_);
346 # Does the file exist on the path?
347 my $found = check_path ($_, @paths);
351 warn "MISSING DEPENDENCY: $_ (for $file)\n";
353 unless ($path_warning) {
354 warn "You may need to add the directory containing this file to your \$PATH\n";
363 print "dependencies found for binary $file:\n\t",
364 join ("\n\t", @deps), "\n";
366 print "no dependencies found for $file\n"
372 # Cache the list of dependencies so we can just return it
373 # immediately next time.
374 $files{$file}->{deps} = \@deps;
378 sub is_windows_system_dll
383 $_ eq 'kernel32.dll' ||
385 $_ eq 'mscoree.dll' ||
386 $_ eq 'msvcrt.dll' ||
390 # Decide how we will name the output files. This removes the
391 # common prefix from filenames, if it can determine one.
395 my @names = keys %files;
397 # Determine if all the names share a common prefix.
398 my @namelens = map { length } @names;
399 my $shortest = min (@namelens);
402 for ($prefixlen = $shortest; $prefixlen >= 0; --$prefixlen) {
403 my @ns = map { $_ = substr $_, 0, $prefixlen } @names;
407 if ($verbose) { print "prefix length = $prefixlen\n" }
409 # Remove the prefix from each name and save the install directory
410 # and install filename separately.
411 foreach my $name (keys %files) {
412 my $install_as = substr $name, $prefixlen;
414 my ($install_dir, $install_name);
416 if ($install_as =~ m{(.*)/(.*)}) {
421 $install_name = $install_as;
424 # Convert / in install_dir into backslashes.
425 $install_dir =~ s{/}{\\}g;
427 $files{$name}->{install_dir} = $install_dir;
428 $files{$name}->{install_name} = $install_name;
436 $max = $_ if $_ > $max;
445 $min = $_ if $_ < $min;
454 return 0 if $_ ne $s;
459 # Print the list of files.
464 foreach (sort keys %files) {
466 if ($files{$_}->{root}) {
469 if ($files{$_}->{dir}) {
472 print STDOUT ("\n\t => ",
473 $files{$_}->{install_dir}, " \\ ", $files{$_}->{install_name},
476 print "End of files.\n";
479 # Write the NSIS script.
486 #!Nsis Installer Command Script
488 # This is an NSIS Installer Command Script generated automatically
489 # by the Fedora nsiswrapper program. For more information see:
491 # http://fedoraproject.org/wiki/MinGW
493 # To build an installer from the script you would normally do:
495 # makensis this_script
497 # which will generate the output file '$outfile' which is a Windows
498 # installer containing your program.
502 InstallDir "$installdir"
503 InstallDirRegKey $installdirregkey "Install_Dir"
506 ShowUninstDetails hide
508 # Uncomment this to enable BZip2 compression, which results in
509 # slightly smaller files but uses more memory at install time.
518 ComponentText "Select which optional components you want to install."
520 DirText "Please select the installation folder."
526 # Set the output files.
528 foreach (sort keys %files) {
529 if (!$olddir || $files{$_}->{install_dir} ne $olddir) {
530 # Moved into a new install directory.
531 my $dir = $files{$_}->{install_dir};
532 print $io "\n SetOutPath \"\$INSTDIR\\$dir\"\n";
536 # If it's a directory, we copy it recursively, otherwise
537 # just copy the single file.
538 if ($files{$_}->{dir}) {
539 print $io " File /r \"$_\"\n";
541 print $io " File \"$_\"\n";
548 Section "Start Menu Shortcuts"
549 CreateDirectory "\$SMPROGRAMS\\$name"
550 CreateShortCut "\$SMPROGRAMS\\$name\\Uninstall $name.lnk" "\$INSTDIR\\Uninstall $name.exe" "" "\$INSTDIR\\Uninstall $name.exe" 0
553 # Start menu entries for each executable.
554 foreach (sort keys %files) {
555 if ($files{$_}->{exec}) {
556 my $install_dir = $files{$_}->{install_dir};
557 my $install_name = $files{$_}->{install_name};
558 print $io " CreateShortCut \"\$SMPROGRAMS\\$name\\$install_name.lnk\" \"\$INSTDIR\\$install_dir\\$install_name\" \"\" \"\$INSTDIR\\$install_dir\\$install_name\" 0\n";
565 Section "Desktop Icons"
568 # Desktop icons for each executable.
569 foreach (sort keys %files) {
570 if ($files{$_}->{exec}) {
571 my $install_dir = $files{$_}->{install_dir};
572 my $install_name = $files{$_}->{install_name};
573 print $io " CreateShortCut \"\$DESKTOP\\$install_name.lnk\" \"\$INSTDIR\\$install_dir\\$install_name\" \"\" \"\$INSTDIR\\$install_dir\\$install_name\" 0\n";
583 # Remove desktop icons and menu shortcuts.
584 foreach (reverse sort keys %files) {
585 if ($files{$_}->{exec}) {
586 my $install_name = $files{$_}->{install_name};
587 print $io " Delete /rebootok \"\$DESKTOP\\$install_name.lnk\"\n";
588 print $io " Delete /rebootok \"\$SMPROGRAMS\\$name\\$install_name.lnk\"\n";
591 print $io " Delete /rebootok \"\$SMPROGRAMS\\$name\\Uninstall $name.lnk\"\n\n";
593 # Remove remaining files.
595 foreach (reverse sort keys %files) {
596 if (!$olddir || $files{$_}->{install_dir} ne $olddir) {
597 # Moved into a new install directory, so delete the previous one.
598 print $io " RMDir \"\$INSTDIR\\$olddir\"\n\n"
600 $olddir = $files{$_}->{install_dir};
603 # If it's a directory, we delete it recursively, otherwise
604 # just delete the single file.
605 my $install_dir = $files{$_}->{install_dir};
606 my $install_name = $files{$_}->{install_name};
607 if ($files{$_}->{dir}) {
608 print $io " RMDir /r \"\$INSTDIR\\$install_dir\"\n\n";
609 $olddir = ''; # Don't double-delete directory.
611 print $io " Delete /rebootok \"\$INSTDIR\\$install_dir\\$install_name\"\n";
615 print $io " RMDir \"\$INSTDIR\\$olddir\"\n" if $olddir;
622 WriteUninstaller "\$INSTDIR\\Uninstall $name.exe"
627 # Run makensis on the named file.
631 my $filename = shift;
633 system ("makensis", $filename) == 0 or die "makensis: $?"
642 print_config () if $verbose;
645 print_files () if $verbose;
647 my ($io, $filename) = tempfile ("nswXXXXXX", UNLINK => 1);
650 run_makensis ($filename);
652 write_script (\*STDOUT);