I discovered that 7-zip files can be converted into installers. Basically, it's a variant of a self-extracting archive that auto-runs a cmd file (like a bat file) after the extraction.
Yes, that's exactly what my old AP+app installer did using WinZIPs SFX function. Perl can quite easily be embedded into a batch script, and as far as I remember, that was how the installer script worked. A batch file with embedded code that first did some minimal sanity checks using the batch interpreter, then executed perl -x -S %0, letting perl search and execute the embedded perl script in the batch file.
This is a quite minimal example showing the trick:
@echo off
echo Hello from the DOS/Windows command interpreter
perl -x -S %0
goto batchend
#!perl
use strict;
use warnings;
print "Hello from Perl\n";
exit;
__END__
:batchend
echo Back from perl in the command interpreter
When installing from an unpacked ZIP, that batch usually runs from a temporary directory (not a big problem), and the perl executable from the ZIP is not in $ENV{PATH}. That can be a problem. Just starting the installer script as shown above will either run some other perl installed by some other application (Oracle delivered REALLY old perls at that time), or perl won't simply run.
You really don't want to mix two or more perls on a Windows machine, so the first thing you do (or better: I did years ago) was to try to run the "resident" perl, expecting it to fail:
rem -- somewhere inside the big batch file --
perl -e "exit 42"
if errorlevel 43 goto noperl
if errorlevel 42 goto badperl
goto noperl
:badperl
echo Sorry, there is a foreign perl installed. Can't install.
goto fail
:noperl
rem ...
rem ... much code omitted, explained in the next step
rem ...
:fail
pause
:end
DOS is strange: if errorlevel 43 does NOT check that the exit code of the last program is exactly 43, but if the exit code is at least 43. So the first if errorlevel 43 catches exit codes from 43 to 255, the next if errorlevel 42 can catch only exit code 42 (all above were alredy handled), and the final goto noperl catches all exit codes below 42. The check for 42 is quite arbitary, usually the exit code is 0, 255, or something much larger on NT. But it is very unlikely that some program named perl.com or perl.exe exits with exit code 42, given the parameters, and can't interpret perl.
Note that the batch doesn't yet use the embedded script trick, because there can be only one embedded script.
Another step is to make sure that the script finds itself and the other files extracted from the ZIP file. SFX modules should run the embedded script with the temporary directory as the current directory.
Quite easy:
rem ...
rem (assuming this script is named setup.bat)
if exists setup.bat goto havesetup
echo Can't find myself. Can't install.
goto fail
:havesetup
rem (assuming our perl.exe is in a bin subdirectory)
if not exists perl.exe goto dirsok
echo ZIP file was unpacked without directories. Can't install.
goto fail
:dirsok
if exists bin\perl.exe goto haveperl
echo Missing my perl.exe. Archive damaged? Can't install.
goto fail
:haveperl
rem ...
rem ... use perl to bootstrap itself
rem ...
:fail
pause
:end
Quite easy: Check that the batch file exists in the current directory, and that perl.exe was extracted to the bin subdirectroy, not to the current directory. At that point, the unpacked perl should be usable.
Perhaps one last paranoia check?
rem ...
bin\perl.exe -e "exit($]!=5.014002)"
if not errorlevel 1 goto perlok
echo Unexpected perl interpreter found. Won't install.
goto fail
:perlok
rem ...
:fail
pause
:end
Just a version check. perl.exe will exit with exit code 0 only if it has exactly the right version. Else, it will exit with exit code 1, maybe larger if something strange has happened (crashing perl).
And now, the embedded script. perl -x will search the entire file for the first line starting with #! and containing the word "perl". From there, it will start interpreting perl. The -S parameter will make perl search $ENV{PATH} for the script. Not strictly required.
Errors should be reported. Perl has die, so that's not a big problem. But the command window should stay open on error. The batch file has a fail label that does exactly that, and die sets the exit code to non-zero. Easily combined:
rem ...
perl -x -S %0
if errorlevel 1 goto fail
goto end
rem -- command shell will never try to interpret this or the following
+ lines up to the fail label.
#!perl
use strict;
use warnings;
use lib 'lib';
use My::Custom::Setup;
My::Custom::Setup->run();
__END__
rem ^-- perl stops interpreting at __END__
:fail
pause
end
The embedded script here is quite minimal, it just loads a custom module that does the actual work. You can also combine a batch skeleton and a larger perl script, perhaps using a primitive template system, or just by concatenating batch header, perl script, and batch footer into the final setup.bat. But that makes it a little bit harder to use an editor with syntax highlighting. Most won't correctly display both languages at the same time.
Another option is to setup or load a custom error handler that displays a message box instead of just writing into the DOS box. Carp can be quite inspiring, but a simple eval may be sufficient. The string eval prevents compile time effects of use, so even compile errors in lib and My::Custom::Setup are trapped by eval.
#!perl
use strict;
use warnings;
if (eval q[
use lib 'lib';
use My::Custom::Setup;
My::Custom::Setup->run();
1;
]) {
exit(0);
} else {
my $err=$@;
Win32::MsgBox($err,MB_ICONSTOP,"Error");
die $err;
}
__END__
Alexander
--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
|