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.
294 foreach (keys %files) {
295 my @deps = get_deps_for_file ($_);
297 # Add the deps as separate files.
299 unless (exists $files{$_}) {
313 my $path_warning = 0;
315 sub get_deps_for_file
318 my @paths = split (/:/, $ENV{PATH});
320 # If we already fetched the dependencies for this file, just
321 # return that list now.
322 if (exists $files{$file}->{deps}) {
323 return @{$files{$file}->{deps}}
328 # We only know how to do this for *.exe and *.dll files.
329 if (m/\.exe$/i || m/\.dll$/i) {
330 my $cmd = "$objdump -p '$file' |
332 grep -Eo '[-._[:alnum:]]+\.dll' |
333 sort -u"; # XXX quoting
334 open DEPS, "$cmd |" or die "$cmd: $!";
338 # Ignore Windows system DLL deps.
339 next if is_windows_system_dll ($_);
341 # Does the file exist on the path?
342 my $found = check_path ($_, @paths);
346 warn "MISSING DEPENDENCY: $_ (for $file)\n";
347 unless ($path_warning) {
348 warn "You may need to add the directory containing this file to your \$PATH\n";
357 print "dependencies found for binary $file:\n\t",
358 join ("\n\t", @deps), "\n";
360 print "no dependencies found for $file\n"
366 # Cache the list of dependencies so we can just return it
367 # immediately next time.
368 $files{$file}->{deps} = \@deps;
372 sub is_windows_system_dll
377 $_ eq 'kernel32.dll' ||
379 $_ eq 'mscoree.dll' ||
380 $_ eq 'msvcrt.dll' ||
384 # Decide how we will name the output files. This removes the
385 # common prefix from filenames, if it can determine one.
389 my @names = keys %files;
391 # Determine if all the names share a common prefix.
392 my @namelens = map { length } @names;
393 my $shortest = min (@namelens);
396 for ($prefixlen = $shortest; $prefixlen >= 0; --$prefixlen) {
397 my @ns = map { $_ = substr $_, 0, $prefixlen } @names;
401 if ($verbose) { print "prefix length = $prefixlen\n" }
403 # Remove the prefix from each name and save the install directory
404 # and install filename separately.
405 foreach my $name (keys %files) {
406 my $install_as = substr $name, $prefixlen;
408 my ($install_dir, $install_name);
410 if ($install_as =~ m{(.*)/(.*)}) {
415 $install_name = $install_as;
418 # Convert / in install_dir into backslashes.
419 $install_dir =~ s{/}{\\}g;
421 $files{$name}->{install_dir} = $install_dir;
422 $files{$name}->{install_name} = $install_name;
430 $max = $_ if $_ > $max;
439 $min = $_ if $_ < $min;
448 return 0 if $_ ne $s;
453 # Print the list of files.
458 foreach (sort keys %files) {
460 if ($files{$_}->{root}) {
463 if ($files{$_}->{dir}) {
466 print STDOUT ("\n\t => ",
467 $files{$_}->{install_dir}, " \\ ", $files{$_}->{install_name},
470 print "End of files.\n";
473 # Write the NSIS script.
480 #!Nsis Installer Command Script
482 # This is an NSIS Installer Command Script generated automatically
483 # by the Fedora nsiswrapper program. For more information see:
485 # http://fedoraproject.org/wiki/MinGW
487 # To build an installer from the script you would normally do:
489 # makensis this_script
491 # which will generate the output file '$outfile' which is a Windows
492 # installer containing your program.
496 InstallDir "$installdir"
497 InstallDirRegKey $installdirregkey "Install_Dir"
500 ShowUninstDetails hide
502 # Uncomment this to enable BZip2 compression, which results in
503 # slightly smaller files but uses more memory at install time.
512 ComponentText "Select which optional components you want to install."
514 DirText "Please select the installation folder."
520 # Set the output files.
522 foreach (sort keys %files) {
523 if (!$olddir || $files{$_}->{install_dir} ne $olddir) {
524 # Moved into a new install directory.
525 my $dir = $files{$_}->{install_dir};
526 print $io "\n SetOutPath \"\$INSTDIR\\$dir\"\n";
530 # If it's a directory, we copy it recursively, otherwise
531 # just copy the single file.
532 if ($files{$_}->{dir}) {
533 print $io " File /r \"$_\"\n";
535 print $io " File \"$_\"\n";
542 Section "Start Menu Shortcuts"
543 CreateDirectory "\$SMPROGRAMS\\$name"
544 CreateShortCut "\$SMPROGRAMS\\$name\\Uninstall $name.lnk" "\$INSTDIR\\Uninstall $name.exe" "" "\$INSTDIR\\Uninstall $name.exe" 0
547 # Start menu entries for each executable.
548 foreach (sort keys %files) {
549 if ($files{$_}->{exec}) {
550 my $install_dir = $files{$_}->{install_dir};
551 my $install_name = $files{$_}->{install_name};
552 print $io " CreateShortCut \"\$SMPROGRAMS\\$name\\$install_name.lnk\" \"\$INSTDIR\\$install_dir\\$install_name\" \"\" \"\$INSTDIR\\$install_dir\\$install_name\" 0\n";
559 Section "Desktop Icons"
562 # Desktop icons for each executable.
563 foreach (sort keys %files) {
564 if ($files{$_}->{exec}) {
565 my $install_dir = $files{$_}->{install_dir};
566 my $install_name = $files{$_}->{install_name};
567 print $io " CreateShortCut \"\$DESKTOP\\$install_name.lnk\" \"\$INSTDIR\\$install_dir\\$install_name\" \"\" \"\$INSTDIR\\$install_dir\\$install_name\" 0\n";
577 # Remove desktop icons and menu shortcuts.
578 foreach (reverse sort keys %files) {
579 if ($files{$_}->{exec}) {
580 my $install_name = $files{$_}->{install_name};
581 print $io " Delete /rebootok \"\$DESKTOP\\$install_name.lnk\"\n";
582 print $io " Delete /rebootok \"\$SMPROGRAMS\\$name\\$install_name.lnk\"\n";
585 print $io " Delete /rebootok \"\$SMPROGRAMS\\$name\\Uninstall $name.lnk\"\n\n";
587 # Remove remaining files.
589 foreach (reverse sort keys %files) {
590 if (!$olddir || $files{$_}->{install_dir} ne $olddir) {
591 # Moved into a new install directory, so delete the previous one.
592 print $io " RMDir \"\$INSTDIR\\$olddir\"\n\n"
594 $olddir = $files{$_}->{install_dir};
597 # If it's a directory, we delete it recursively, otherwise
598 # just delete the single file.
599 my $install_dir = $files{$_}->{install_dir};
600 my $install_name = $files{$_}->{install_name};
601 if ($files{$_}->{dir}) {
602 print $io " RMDir /r \"\$INSTDIR\\$install_dir\"\n\n";
603 $olddir = ''; # Don't double-delete directory.
605 print $io " Delete /rebootok \"\$INSTDIR\\$install_dir\\$install_name\"\n";
609 print $io " RMDir \"\$INSTDIR\\$olddir\"\n" if $olddir;
616 WriteUninstaller "\$INSTDIR\\Uninstall $name.exe"
621 # Run makensis on the named file.
625 my $filename = shift;
627 system ("makensis", $filename) == 0 or die "makensis: $?"
636 print_config () if $verbose;
639 print_files () if $verbose;
641 my ($io, $filename) = tempfile ("nswXXXXXX", UNLINK => 1);
644 run_makensis ($filename);
646 write_script (\*STDOUT);