Building Perl stand-alone applications with a GUI using Mojolicious and PAR-Packer



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);
}

6 Replies to “Building Perl stand-alone applications with a GUI using Mojolicious and PAR-Packer”

  1. Nice post. I created a Hello World Mojolicious Web App following your tips, but I needed the following pp options: $ pp -c -o hello.exe -M Mojolicious::Plugin::HeaderCondition -M Mojolicious::Plugin::DefaultHelpers -M Mojolicious::Plugin::TagHelpers -M Mojolicious::Plugin::EPLRenderer -M Mojolicious::Plugin::EPRenderer -M Mojolicious::Command::daemon hello.pl
    1. Thanks for the hint! As the original post is more than six years old, it's very well possible that some things have changed. :) Iirc, -x was enough to take care of the dependencies for me at the time.
  2. Hi, i try using pp -B -c -x -o myapp.exe myapp.pl but when execute it got error /myapp.exe Invalid path at Mojo/IOLoop/TLS.pm line 20. Compilation failed in require at Mojo/IOLoop/Client.pm line 8. BEGIN failed--compilation aborted at Mojo/IOLoop/Client.pm line 8. Compilation failed in require at Mojo/IOLoop.pm line 7. BEGIN failed--compilation aborted at Mojo/IOLoop.pm line 7. Compilation failed in require at Mojo/UserAgent.pm line 6. BEGIN failed--compilation aborted at Mojo/UserAgent.pm line 6. Compilation failed in require at Mojolicious.pm line 11. BEGIN failed--compilation aborted at Mojolicious.pm line 11. Compilation failed in require at Mojo/Base.pm line 133. BEGIN failed--compilation aborted at Mojolicious/Lite.pm line 2. Compilation failed in require at script/myapp.pl line 2. BEGIN failed--compilation aborted at script/myapp.pl line 2.
  3. Hi guys, firstly, that post was really informative and helped me to move on with my project but i still can't make an executable of Mojolicious app. Secondly, i am sorry to renew this but i would really need some help with my problem. So, i am trying to make a functional executable but from full Mojolicious app and not Mojolicious::Lite. It is frustrating hahaha Currently, the project seems to get compiled to executable but it the app does not start. It complains that can not find the configuration file. I am using this cmd for now: pp -o my_mojo_app -x -a /usr/local/share/perl5/Mojolicious/Commands.pm -a /usr/local/share/perl5/Mojo/File.pm -a dpath/lib -a dpath/public -a dpath/templates -a ext_data -a dpath/d_p_a_t_h.conf dpath/script/dpath But when i run the app the program is looking for the conf file at /tmp/xxxxx/inc. But originally it is locate in dir 'dpath'. Thank you very much for your time and help and/or any hints on how to attack that problem!
    1. Hi again, for whoever find him/herself in similar situation aka to make executable from FULL APP, i figured it out. Firstly the starting script file( yourapp/script/yourapp): -------------------------------- #!/usr/bin/env perl use strict; use warnings; use FindBin qw($Bin); my ($parLibDir,$appLibDir,$pidfile,$ipaddress); $ipaddress='http://127.0.0.1:3000'; BEGIN{ $Bin=~s//script//; $parLibDir=$Bin.'/lib/inc/lib'; $appLibDir=$Bin.'/lib'; $pidfile=$Bin.'/yourapp/script/pidfile'; # print "n $Bin : $parLibDir : $appLibDir : $pidfilen"; } if(exists $ENV{PAR_TEMP}){ use if (exists $ENV{PAR_TEMP}), lib => "$parLibDir"; push @ARGV, ('prefork', '-m', 'production', '-l', "$ipaddress", '-P', "$pidfile"); system ("xdg-open", "$ipaddress"); }else{ use if (!exists $ENV{PAR_TEMP}), lib => "$appLibDir"; } use Mojolicious::Commands; # Start command line interface for application Mojolicious::Commands->start_app('YOURAPP'); -------------------------------- Secondly the ''pp" command: pp -x -o $APP_NAME -a 'yourapp/yourapp.conf;yourapp.conf' -a 'yourapp/templates;templates' -a 'yourapp/public;public' -a 'yourapp/lib;lib' dpath/script/dpath Thirdly, i wrote a script which first sets the $ENV{PAR_GLOBAL_TEMP} to the lib directory in my app and then just executes the executable: -------------------------------- #!/usr/bin/env perl use strict; use warnings; use FindBin qw($Bin); use Getopt::Long; my($APP_NAME); GetOptions( 'i=s' => $APP_NAME ); $ENV{PAR_GLOBAL_TEMP}=$Bin.'/yourapp/lib'; print "ENV:1: $ENV{PAR_GLOBAL_TEMP} :n"; print "n Starting the app: $APP_NAME ..n"; system("./$APP_NAME"); -------------------------------- That's it. Very convoluted but works. Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *