Evil PAR tricks, issue 0: Binary including a binary

tsee on 2007-01-14T15:50:00

In my journal entry from December 22, 2006, I said I'd used a few hacks to package parinstallppdgui into an executable binary using PAR::Packer. I'll explore that further here:

parinstallppdgui is mostly a GUI front-end to parinstallppd. It doesn't use PAR::Dist::InstallPPD internally, but uses the system to run parinstallppd so if the child process bombs out, the GUI process can show a friendly warning to the user (comprised of the STDERR and STDOUT of the child process) instead of crashing. That's all very simple if you have perl on your system, of course.

Now, if you want to package parinstallppdgui or any other Perl script that uses another Perl script into an .exe, you'll quickly find out that without a perl on the target system, these two scripts cannot share the same interpreter. The obvious "solution" is to package both of them up into an .exe separately and ship them both. This has several problems. First, you need to ship two executables instead of one. Second, the first executable won't necessarily know where to look for the second if the user doesn't put them in PATH. Adding a couple of FindBin hacks isn't great at solving this, either! Third, these two executables will have a lot in common - starting with all the core Perl modules.

So I took a slightly better, yet more complicated route. The process is as follows:

  1. Add a special case to the parent's source code for execution from inside a PAR binary: if (defined $ENV{PAR_TEMP}) { require Config; require File::Spec; $ENV{PATH} .= (defined $ENV{PATH} ? $Config::Config{path_sep} : '') . File::Spec->catdir($ENV{PAR_TEMP}, 'inc'); $ENV{PAR_GLOBAL_TEMP} = $ENV{PAR_TEMP}; } This forces all PAR'd executables that are started by this process to use the same cache area as this process. This might be a dangerous thing to do if both binaries contain different, incompatible versions of some files. But we control everything here, so it should be okay.
  2. Package parinstallppdgui, the parent script: pp -o parinstallppdgui.exe parinstallppdgui
  3. Package parinstallppd, the child script: pp -o parinstallppd.exe -l expat parinstallppd (We include the expat library.)
  4. Remove all files from the child script that are also available from the parent. This makes the child dependent on the parent. Use the pare utility from the PAR::Packer distribution's contrib/ directory for this: pare -u parinstallppdgui.exe parinstallppd.exe
  5. Package the parent script again, but this time include the child script as an extra file into the executable: pp -o parinstallppdgui.exe -a parinstallppd.exe -l expat parinstallppdgui
  6. Ship the combined binary only.

Cheers,
Steffen

P.S.: A third and even better solution might be to package both of the scripts into the same binary and symlink or copy that binary to parinstallppd.exe and parinstallppdgui.exe and let PAR figure out what to run. This is cool if you have symlinks and sucks if you don't.