Julius is a systems administrator for Ayala Systems Technology. He can be contacted at [email protected].
Being a Perl programmer, one of my biggest frustrations with using Windows is the absence of a Perl interpreter. On many occasions, I had to install ActivePerl (http://www .activestate.com/) just so I could use my Perl scripts. A number of times, I have asked myself, "Wouldn't it be nice to have a Perl script compiler to convert my scripts into self-contained executables so that I won't have to worry about a Perl interpreter?" Fortunately for me, I discovered LibZip.
This month, I'll discuss LibZip. Next month, I'll follow up with a discussion of PAR, the Perl Archive Toolkit, which is another way to create Perl executables. Obviously, you'll need a working C compiler to convert your Perl scripts into self-contained executables, as discussed here.
LibZip
LibZip is the brainchild of GracilianoMonteiroPassos and is available from http://search.cpan.org/CPAN/authors/id/G/GM/ GMPASSOS. In Graciliano's own words, the goal of LibZip is to "create very low weight executables" out of Perl scripts. To achieve this, LibZip bundles all modules needed by a Perl script into one big file, called lib.zip, and creates a native code equivalent of the script. When the native code is run, it uncompresses lib.zip into a temporary folder and uses the files in this folder, thereby eliminating the need for a Perl interpreter. The downside, of course, is that the native code won't run without lib.zip.
Using LibZip
LibZip needs three other modules before it can be useful: Pod::Stripper, Compress::Zlib, and Archive::Zip, all available from CPAN. Assuming you have successfully installed LibZip, using it is a piece of cake. To use LibZip, follow these simple steps. Let's convert dos2unix.pl (Listing 1) into a Windows native executable, dos2unix.exe:
- Scan all modules that dos2unix.pl needs, including dependencies:
- This will build libzip.modules. LibZip will use this file to create the EXE file in the next step.
- Build the library, lib.zip, and executable, dos2unix.exe:
perl -MLibZip::Scan dos2unix.pl
libzip -o dos2unix.pl -lzw -striplib
The -o option tells the batch file, libzip, to create an EXE file. Note also that the name of the Perl script must immediately follow the option -o. The -lzw option tells libzip to use LZW compression. An interesting benefit of compression, albeit a trivial one, is that you can now obfuscate your code with it! -striplib tells libzip to remove PODs from lib.zip.
That's it! One more thing, though. After the last step, LibZip will import perl56.dll and perl58.dll from your Perl interpreter's installation folder into the folder where you created lib.zip and dos2unix.exe. So, go ahead, run your shiny new executable! Rename your Perl installation folder to something else and see if dos2unix.exe will run without a Perl interpreter.
A Word Of Warning
I usually use diagnostics in my scripts to make debugging easier. But, for some reason, I can't get a functioning executable (both in Windows and Linux) if I use diagnostics. Not using diagnostics solves the problem.
Compiling Perl/Tk Scripts
Compiling console Perl scripts are straightforward, but compiling Perl/Tk scripts is a bit problematic. If you compile perl-tk.pl (Listing 2), for example:
perl -MLibZip::Scan perl-tk.pl libzip -o perl-tk.pl -lzw -striplib
the generated executable won't run. Apparently, Tk performs some magic that LibZip fails to see. Specifically, utf8_heavy.pl and the whole unicore directory were not included in libzip.modules during the scan. So when lib.zip was finally built, some files were missing. The fix, therefore, is to edit libzip.modules manually and add utf8_heavy.pl and unicore. Recompiling perl-tk.pl should now be a breeze.
You may also use the -gui option, aside from -lzw and -striplib, to create a nonconsole executable.
libzip -o perl-tk.pl -lzw -striplib -gui
You are encouraged to use -gui if you're compiling a Perl/Tk script.
UPX
LibZip can also use what is known as UPX, short for "Ultimate Packer for executables" to compress lib.zip and executables even more. UPX's inventors, Markus F.X.J. Oberhumer and László Molnár, claim that UPX is a "free, portable, extendable, high-performance executable packer for several different executable formats. It achieves an excellent compression ratio and offers very fast decompression." http://upx.sourceforge.net/ is UPX's official web site where you'll find versions of UPX for different platforms.
Using UPX within LibZip is also painless:
libzip -o perl-tk.pl -lzw -striplib -gui -upx best -upxlib best
Option -upx will compress the executable, while -upxlib will compress lib.zip. The best argument to -upx and -upxlib tells libzip to call UPX using the best possible compression.
Because UPX is also a standalone program, you may also use it independently from LibZip. For example, you may type Step 2 as:
libzip -o perl-tk.pl -lzw -striplib -gui
and then follow it up with:
upx best perl-tk.exe perl56.dll perl58.dll
perlccPerl's Own Compiler
How about Perl's own compiler, perlcc? Based from my personal experience, I have yet to produce a working executable that is compiled with perlcc. Indeed, the Perl documentation says that perlcc's output is not guaranteed to work. The man page further says "the whole codegen suite (perlcc included) should be considered very experimental. Use for production purposes is strongly discouraged." Aside from that, perlcc takes several minutes to compile even a simple script.
Compiling Scripts for Linux
You may ask, "Can LibZip also create executables for Linux?" The answer is a resounding "Yes." The steps are the same, but without perl56.dll and perl58.dll being generated (for the obvious reason).
PARAnother Executable Maker
What if you dislike having to bundle your executables with a separate library and prefer, instead, to have just a single executable? The LibZip man page mentions a similar tool, PAR, or the Perl Archive Toolkit, from Autrijus Tang. I'll discuss PAR next month.
TPJ
#!/usr/local/bin/perl # Julius C. Duque use strict; use warnings; use Getopt::Long; my ($format, $help) = (); GetOptions( "format=s" => \$format, # =s -> takes mandatory string argument "help" => \$help # optional switch ); if (!$format or $help) { if ($^O eq "MSWin32") { $0 =~ s/.*\\//g; # Windows } else { $0 =~ s/.*\///g; } print "Usage: $0 --format=unix file1 [file2] [file3] ...\n"; print " $0 -f=unix file1 [file2] [file3] ...\n"; print "\n"; print " $0 --format=dos file1 [file2] [file3] ...\n"; print " $0 -f=dos file1 [file2] [file3] ...\n"; print "\nOriginal file(s) will be overwritten\n"; exit 1; } foreach my $infile (@ARGV) { print "Converting $infile to $format format...\n"; open INFILE, $infile; open OUTFILE, ">temp.$infile"; binmode INFILE; binmode OUTFILE; while (<INFILE>) { $_ =~ s/\015\012/\012/g if ($format =~ /u/); $_ =~ s/\012/\015\012/g if ($format =~ /d/); print OUTFILE; } close INFILE; close OUTFILE; rename "temp.$infile", "$infile"; }Back to article
Listing 2
#!/usr/local/bin/perl use strict; use warnings; use Tk; use Tk::Balloon; my $INDENT_DEF = 0; my $LWIDTH_DEF = 70; my $VERSION = "1.4.3"; my $TITLE = "Perl/Tk Example $VERSION"; my $AUTHOR = "Julius C. Duque"; my $indent = $INDENT_DEF; my $newline = 1; my $hyphenate = 1; my $width = $LWIDTH_DEF; my ($BOTH, $LEFT, $RIGHT, $CENTERED) = (1, 2, 3, 4); my $format_choice = $BOTH; my ($infile, $outfile) = (); my $mw = new MainWindow(); drawButtons(); Tk::MainLoop(); sub processfile { open INFILE, $infile; open OUTFILE, "> $outfile"; while (<INFILE>) { print OUTFILE; } close INFILE; close OUTFILE; printMessage("info", "OK", "File was successfully saved."); } sub drawButtons { $mw->title($TITLE); # Status bar widget my $status = $mw->Label(-width => 70, -relief => "sunken", -anchor => "w")->pack(-side => "bottom", -padx => 1, -pady => 1, -fill => "x"); # Create balloon widget my $b = $mw->Balloon(-statusbar => $status); # Create menu bar frame my $menubar = $mw->Frame(-borderwidth => 4, -relief => "ridge")-> pack(-side => "top", -fill => "x"); # Create Open File button my $openfilebutton = $menubar-> Button(-text => "Open File", -relief => "raised", -width => 10, -command => [\&fileDialog, $mw, "open"])->pack(-side => "left"); $b->attach($openfilebutton, -msg => "Open a file"); # Create Save File button my $savefilebutton = $menubar-> Button(-text => "Save To File", -relief => "raised", -width => 10, -command => sub { if (defined $infile and $infile ne "") { fileDialog($mw, "save"); } else { printMessage("warning", "OK", "You must open a file first"); } })->pack(-side => "left"); $b->attach($savefilebutton, -msg => "Proceed with saving a file"); # Create About button my $aboutbutton = $menubar->Button(-text => "About", -relief => "raised", -width => 10, -command => [\&printMessage, "info", "OK", "A Perl/Tk script created by $AUTHOR"])->pack(-side => "left"); $b->attach($aboutbutton, -msg => "$TITLE created by $AUTHOR"); # Create Quit button my $quitbutton = $menubar->Button(-text => "Dismiss", -relief => "raised", -width => 10, -command => sub { exit })-> pack(-side => "right"); $b->attach($quitbutton, -msg => "Quit this Perl/Tk script"); my $both = $mw->Radiobutton(-variable => \$format_choice, -value => $BOTH, -text => "Radio Button 1")-> pack(-side => "top", -anchor => "w"); $b->attach($both, -msg => "Radio Button 1"); my $left = $mw->Radiobutton(-variable => \$format_choice, -value => $LEFT, -text => "Radio Button 2")->pack(-side => "top", -anchor => "w"); $b->attach($left, -msg => "Radio Button 2"); my $right = $mw->Radiobutton(-variable => \$format_choice, -value => $RIGHT, -text => "Radio Button 3")->pack(-side => "top", -anchor => "w"); $b->attach($right, -msg => "Radio Button 3"); my $centered = $mw->Radiobutton(-variable => \$format_choice, -value => $CENTERED, -text => "Radio Button 4")->pack(-side => "top", -anchor => "w"); $b->attach($centered, -msg => "Radio Button 4"); $both->select; # Set default to $both my $chknewline = $mw->Checkbutton(-variable => \$newline, -text => "Check Box 1")-> pack(-side => "top", -anchor => "w"); $b->attach($chknewline, -msg => "Check Box 2"); $chknewline->select; # Set default to $newline my $chkhyphen = $mw->Checkbutton(-variable => \$hyphenate, -text => "Check Box 2")->pack(-side => "top", -anchor => "w"); $b->attach($chkhyphen, -msg => "Check Button 2"); $chknewline->select; # Set default to $hyphenate my $f = $mw->Frame->pack(-side => "left"); my $l = $f->Label(-text => "Indention: ", -justify => "left"); $b->attach($l, -msg => "Blah blah blah..."); Tk::grid($l, -row => 0, -column => 0); my $tindent = $f->Entry(-width => 2, -textvariable => \$indent, -justify => "right"); $b->attach($tindent, -msg => "Number of spaces at the start of every paragraph"); Tk::grid($tindent, -row => 0, -column => 1); $l = $f->Label(-text => "characters (default: $INDENT_DEF) ", -justify => "left"); $b->attach($l, -msg => "Number of spaces at the beginning of each paragraph"); Tk::grid($l, -row => 0, -column => 2); $l = $f->Label(-text => "Line width: ", -justify => "left"); Tk::grid($l, -row => 1, -column => 0); $b->attach($l, -msg => "Maximum length of every line"); my $tlwidth = $f->Entry(-width => 2, -textvariable => \$width, -justify => "right"); $b->attach($tlwidth, -msg => "Maximum length of every line"); Tk::grid($tlwidth, -row => 1, -column => 1); $l = $f->Label(-text => "characters (default: $LWIDTH_DEF)", -justify => "left"); $b->attach($l, -msg => "Maximum length of every line"); Tk::grid($l, -row => 1, -column => 2); } sub printMessage { my ($icon, $type, $outputmsg) = @_; my $msg = $mw->messageBox(-icon => $icon, -type => $type, -title => $TITLE, -message => $outputmsg); } sub fileDialog { my ($w, $operation) = @_; my @types = (["Text files", [qw/.txt .doc/]], ["Text files", "", "TEXT"], ["All files", "*"] ); if ($operation eq "open") { $infile = $w->getOpenFile(-filetypes => \@types); } if ($operation eq "save") { $outfile = $w->getSaveFile(-filetypes => \@types, -initialfile => "Untitled", -defaultextension => ".txt"); processfile() if (defined $outfile and $outfile ne ""); } }Back to article