You might have come across the same problem I have faced pretty often: You want to write a small snippet of code for a friend who's not into programming to solve some task. You want to use the scripting language of your choice (yeah, [Perl](http://www.perl.org/)). But for many people, especially Windows users, explaining them how to [install perl](http://www.perl.org/get.html), install some modules from [CPAN](http://cpan.org), and finally how to use the script from the command line is tedious and often takes more time than writing it in the first place. And sometimes it even takes more time than solving the task by hand which is quite frustrating.
So I always wanted to build stand-alone applications with a GUI for those cases. But building GUIs is usually a huge pain in the ass, so I always avoided it; until I got the idea to build web applications with [Mojolicious](http://mojolicio.us/) as GUI. Building stand alone executables without the need of installing perl, modules, or libraries can be solved with [PAR-Packer](http://search.cpan.org/dist/PAR-Packer/). So far, that was just a thought.
A few days ago I got a small task: My brother wanted an application to automatically correct one kind of systemic error in large data sets. So I wanted to put that idea to the test. It worked out quite well!
First, building a Mojolicious application is fun in its own. Don't forget to write portable code if you're developing on Linux for Windows. The problem itself also was quite easy to solve. What took more time was getting PAR-Packer to work on Windows.
I tried it in [cygwin](http://www.cygwin.com/) first. I had to solve some problems in
myldr/Makefile.PL
where paths were created using \
instead of /
(don't make that mistake, just use [File::Spec
](http://perldoc.perl.org/File/Spec.html)). Alas, that wasn't the end of it, as PAR failed most of its tests and I couldn't find a solution for that in short order.
Next in line was [ActiveState Perl](http://www.activestate.com/activeperl). Using ppm was easy, but the [pp](http://search.cpan.org/perldoc?pp) executable was created using perl 5.16.0, while the perl version I got with ActivePerl was 5.16.3. That's not compatible. And old versions of ActivePerl aren't available for free. So I tried to install it using cpan, but the executable compiled during the installation just didn't work.
Finally I tried [Strawberry Perl](http://strawberryperl.com/). The installation of PAR-Packer (and Mojolicious) using cpan went smoothly on the first try.
Okay, great. Now I could work out the details. First, your script might want to read some files that have to be packed into the executable. To do that conveniently you want to change in the directory PAR extracted the files packed within your executable. Also, you don't want the user to have to pass parameters to your Mojolicious start script (*"just double click it"*). So I added the following lines:
use File::Spec;
BEGIN {
if(exists $ENV{PAR_TEMP}) {
my $dir = File::Spec->catfile($ENV{PAR_TEMP}, 'inc');
chdir $dir or die "chdir `$dir' failed: $!";
push @ARGV, qw(
daemon -m production -l http://127.0.0.1:3000
);
}
}
Second, how to pack the executable with all dependencies? The following things are needed:
1. The Mojo and Mojolicious modules
2. Some Mojo(licious) data files, namely the folders Mojolicious/public
, Mojolicious/templates
and the file Mojo/entities.txt
3. The shared libraries libeay32__.dll
, zlib1__.dll
and ssleay32__.dll
4. The folders containing your Mojolicious application: lib
, public
and templates
To pack all necessary Mojo and Mojolicious modules, just pass -x
to pp
. That executes the program one time to collect run-time dependencies.
Putting all together, the batch file
pp -o my_mojo_app.exe ^
-x ^
-l libeay32__.dll ^
-l zlib1__.dll ^
-l ssleay32__.dll ^
-a lib ^
-a public ^
-a templates ^
-a "C:\strawberry\perl\site\lib\Mojo\entities.txt;Mojo\entities.txt" ^
-a "C:\strawberry\perl\site\lib\Mojolicious\public;Mojolicious\public" ^
-a "C:\strawberry\perl\site\lib\Mojolicious\templates;Mojolicious\templates" ^
script\my_mojo_app
does the trick.
One last thing: To make the "just double click" experience work, I wanted the browser window to open automatically. That's easy, on Windows you can call "start" to open the default browser. I added the following lines to the end of my startup routine (make sure you do that *after* setting up the routes :).
if(exists $ENV{PAR_TEMP} && $^O eq "MSWin32") {
system qw(start http://localhost:3000);
}
-x
was enough to take care of the dependencies for me at the time.