.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "PAR::Tutorial 3pm" .TH PAR::Tutorial 3pm "2021-01-16" "perl v5.32.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" PAR::Tutorial \- Cross\-Platform Packaging and Deployment with PAR .SH "SYNOPSIS" .IX Header "SYNOPSIS" This is a tutorial on \s-1PAR,\s0 first appeared at the 7th Perl Conference. The \s-1HTML\s0 version of this tutorial is available online as .SH "DESCRIPTION" .IX Header "DESCRIPTION" .SS "On Deploying Perl Applications" .IX Subsection "On Deploying Perl Applications" .Vb 3 \& % sshnuke.pl 10.2.2.2 \-rootpw="Z1ON0101" \& Perl v5.6.1 required\-\-this is only v5.6.0, stopped at sshnuke.pl line 1. \& BEGIN failed\-\-compilation aborted at sshnuke.pl line 1. .Ve .IP "\(bu" 4 Q: \*(L"Help! I can't run your program!\*(R" .IP "\(bu" 4 A1: Install Perl & \f(CW\*(C`perl \-MCPAN \-e\*(Aqinstall(...)\*(Aq\*(C'\fR .RS 4 .IP "\(bu" 4 How do we know which modules are needed? .IP "\(bu" 4 New versions of \s-1CPAN\s0 modules may break \f(CW\*(C`sshnuke.pl\*(C'\fR .RE .RS 4 .RE .IP "\(bu" 4 A2: Install Perl & \f(CW\*(C`tar zxf my_perllib.tgz\*(C'\fR .RS 4 .IP "\(bu" 4 Possibly overwriting existing modules; not cross-platform at all .RE .RS 4 .RE .IP "\(bu" 4 A3: Use the executable generated by \f(CW\*(C`perlcc sshnuke.pl\*(C'\fR .RS 4 .IP "\(bu" 4 Impossible to debug; \f(CW\*(C`perlcc\*(C'\fR usually does not work anyway .RE .RS 4 .RE .SS "\s-1PAR,\s0 the Perl Archive Toolkit" .IX Subsection "PAR, the Perl Archive Toolkit" .IP "\(bu" 4 Do what \s-1JAR\s0 (Java Archive) does for Perl .RS 4 .IP "\(bu" 4 Aggregates modules, scripts and other files into a Zip file .IP "\(bu" 4 Easy to generate, update and extract .IP "\(bu" 4 Version consistency: solves forward-compatibility problems .IP "\(bu" 4 Developed by community: \f(CW\*(C`par@perl.org\*(C'\fR .RE .RS 4 .RE .IP "\(bu" 4 \&\s-1PAR\s0 files can be packed into self-contained scripts .RS 4 .IP "\(bu" 4 Automatically scans perl script for dependencies .IP "\(bu" 4 Bundles all necessary 3rd\-party modules with it .IP "\(bu" 4 Requires only core Perl to run on the target machine .IP "\(bu" 4 \&\s-1PAR\s0 also comes with \f(CW\*(C`pp\*(C'\fR, the Perl Packager: .Sp .Vb 1 \& % pp \-o sshnuke.exe sshnuke.pl # stand\-alone executable! .Ve .RE .RS 4 .RE .SS "Simple Packaging" .IX Subsection "Simple Packaging" .IP "\(bu" 4 \&\s-1PAR\s0 files are just Zip files with modules in it .IP "\(bu" 4 Any Zip tools can generate them: .Sp .Vb 2 \& % zip foo.par Hello.pm World.pm # pack two modules \& % zip \-r bar.par lib/ # grab all modules in lib/ .Ve .IP "\(bu" 4 To load modules from \s-1PAR\s0 files: .Sp .Vb 3 \& use PAR; \& use lib "foo.par"; # the .par part is optional \& use Hello; .Ve .IP "\(bu" 4 This also works: .Sp .Vb 2 \& use PAR "/home/mylibs/*.par"; # put all of them into @INC \& use Hello; .Ve .SS "\s-1PAR\s0 Loaders" .IX Subsection "PAR Loaders" .IP "\(bu" 4 Use \f(CW\*(C`par.pl\*(C'\fR to run files inside a \s-1PAR\s0 archive: .Sp .Vb 2 \& % par.pl foo.par # looks for \*(Aqmain.pl\*(Aq by default \& % par.pl foo.par test.pl # runs script/test.pl in foo.par .Ve .IP "\(bu" 4 Same thing, with the stand-alone \f(CW\*(C`parl\*(C'\fR or \f(CW\*(C`parl.exe\*(C'\fR: .Sp .Vb 2 \& % parl foo.par # no perl or PAR.pm needed! \& % parl foo.par test.pl # ditto .Ve .IP "\(bu" 4 The \s-1PAR\s0 loader can prepend itself to a \s-1PAR\s0 file: .RS 4 .IP "\(bu" 4 \&\f(CW\*(C`\-b\*(C'\fR bundles non-core modules needed by \f(CW\*(C`PAR.pm\*(C'\fR: .Sp .Vb 1 \& % par.pl \-b \-O./foo.pl foo.par # self\-contained script .Ve .IP "\(bu" 4 \&\f(CW\*(C`\-B\*(C'\fR bundles core modules in addition to \f(CW\*(C`\-b\*(C'\fR: .Sp .Vb 1 \& % parl \-B \-O./foo.exe foo.par # self\-contained binary .Ve .RE .RS 4 .RE .SS "Dependency Scanning" .IX Subsection "Dependency Scanning" .IP "\(bu" 4 Recursively scan dependencies with \f(CW\*(C`scandeps.pl\*(C'\fR: .Sp .Vb 7 \& % scandeps.pl sshnuke.pl \& # Legend: [C]ore [X]ternal [S]ubmodule [?]NotOnCPAN \& \*(AqCrypt::SSLeay\*(Aq => \*(Aq0\*(Aq, # X # \& \*(AqNet::HTTP\*(Aq => \*(Aq0\*(Aq, # # \& \*(AqCrypt::SSLeay::X509\*(Aq => \*(Aq0\*(Aq, # S # Crypt::SSLeay \& \*(AqNet::HTTP::Methods\*(Aq => \*(Aq0\*(Aq, # S # Net::HTTP \& \*(AqCompress::Zlib\*(Aq => \*(Aq0\*(Aq, # X # Net::HTTP::Methods .Ve .IP "\(bu" 4 Scan an one-liner, list all involved files: .Sp .Vb 7 \& % scandeps.pl \-V \-e "use Dynaloader;" \& ... \& # auto/DynaLoader/dl_findfile.al [autoload] \& # auto/DynaLoader/extralibs.ld [autoload] \& # auto/File/Glob/Glob.bs [data] \& # auto/File/Glob/Glob.so [shared] \& ... .Ve .ie n .SS "Perl Packager: ""pp""" .el .SS "Perl Packager: \f(CWpp\fP" .IX Subsection "Perl Packager: pp" .IP "\(bu" 4 Combines scanning, zipping and loader-embedding: .Sp .Vb 2 \& % pp \-o out.exe src.pl # self\-contained .exe \& % out.exe # runs anywhere on the same OS .Ve .IP "\(bu" 4 Bundle additional modules: .Sp .Vb 1 \& % pp \-o out.exe \-M CGI src.pl # pack CGI + its dependencies, too .Ve .IP "\(bu" 4 Pack one-liners: .Sp .Vb 1 \& % pp \-o out.exe \-e \*(Aqprint "Hi!"\*(Aq # turns one\-liner into executable .Ve .IP "\(bu" 4 Generate \s-1PAR\s0 files instead of executables: .Sp .Vb 2 \& % pp \-p src.pl # makes \*(Aqsource.par\*(Aq \& % pp \-B \-p src.pl # include core modules .Ve .SS "How it works" .IX Subsection "How it works" .IP "\(bu" 4 Command-line options are almost identical to \f(CW\*(C`perlcc\*(C'\fR's .RS 4 .IP "\(bu" 4 Also supports \f(CW\*(C`gcc\*(C'\fR\-style long options: .Sp .Vb 1 \& % pp \-\-gui \-\-verbose \-\-output=out.exe src.pl .Ve .RE .RS 4 .RE .IP "\(bu" 4 Small initial overhead; no runtime overhead .IP "\(bu" 4 Dependencies are POD-stripped before packing .IP "\(bu" 4 Loads modules directly into memory on demand .IP "\(bu" 4 Shared libraries (DLLs) are extracted with File::Temp .IP "\(bu" 4 Works on Perl 5.6.0 or above .IP "\(bu" 4 Tested on Win32 (\s-1VC++\s0 and MinGW), FreeBSD, NetBSD, Linux, MacOSX, Cygwin, \s-1AIX,\s0 Solaris, HP-UX, Tru64... .SS "Aggregating multiple programs" .IX Subsection "Aggregating multiple programs" .IP "\(bu" 4 A common question: .Sp .Vb 3 \& > I have used pp to make several standalone applications which work \& > great, the only problem is that for each executable that I make, I am \& > assuming the parl.exe is somehow bundled into the resulting exe. .Ve .IP "\(bu" 4 The obvious workaround: .Sp .Vb 2 \& You can ship parl.exe by itself, along with .par files built \& by "pp \-p", and run those PAR files by associating them to parl.exe. .Ve .IP "\(bu" 4 On platforms that have \f(CW\*(C`ln\*(C'\fR, there is a better solution: .Sp .Vb 4 \& % pp \-\-output=a.out a.pl b.pl # two scripts in one! \& % ln a.out b.out # symlink also works \& % ./a.out # runs a.pl \& % ./b.out # runs b.pl .Ve .SS "Cross-platform Packages" .IX Subsection "Cross-platform Packages" .IP "\(bu" 4 Of course, there is no cross-platform binary format .IP "\(bu" 4 Pure-perl \s-1PAR\s0 packages are cross-platform by default .RS 4 .IP "\(bu" 4 However, \s-1XS\s0 modules are specific to Perl version and platform .IP "\(bu" 4 Multiple versions of a \s-1XS\s0 module can co-exist in a \s-1PAR\s0 file .RE .RS 4 .RE .IP "\(bu" 4 Suppose we need \f(CW\*(C`out.par\*(C'\fR on both Win32 and Finix: .Sp .Vb 3 \& C:\e> pp \-\-multiarch \-\-output=out.par src.pl \& ...copy src.pl and out.par to a Finix machine... \& % pp \-\-multiarch \-\-output=out.par src.pl .Ve .IP "\(bu" 4 Now it works on both platforms: .Sp .Vb 2 \& % parl out.par # runs src.pl \& % perl \-MPAR=out.par \-e \*(Aq...\*(Aq # uses modules inside out.par .Ve .SS "The Anatomy of a \s-1PAR\s0 file" .IX Subsection "The Anatomy of a PAR file" .IP "\(bu" 4 Modules can reside in several directories: .Sp .Vb 6 \& / # casual packaging only \& /lib/ # standard location \& /arch/ # for creating from blib/ \& /i386\-freebsd/ # i.e. $Config{archname} \& /5.8.0/ # i.e. Perl version number \& /5.8.0/i386\-freebsd/ # combination of the two above .Ve .IP "\(bu" 4 Scripts are stored in one of the two locations: .Sp .Vb 2 \& / # casual packaging only \& /script/ # standard location .Ve .IP "\(bu" 4 Shared libraries may be architecture\- or perl-version-specific: .Sp .Vb 1 \& /shlib/(5.8.0/)?(i386\-freebsd/)? .Ve .IP "\(bu" 4 \&\s-1PAR\s0 files may recursively contain other \s-1PAR\s0 files: .Sp .Vb 1 \& /par/(5.8.0/)?(i386\-freebsd/)? .Ve .SS "Special files" .IX Subsection "Special files" .IP "\(bu" 4 \&\s-1MANIFEST\s0 .RS 4 .IP "\(bu" 4 Index of all files inside \s-1PAR\s0 .IP "\(bu" 4 Can be parsed with \f(CW\*(C`ExtUtils::Manifest\*(C'\fR .RE .RS 4 .RE .IP "\(bu" 4 \&\s-1META\s0.yml .RS 4 .IP "\(bu" 4 Dependency, license, runtime options .IP "\(bu" 4 Can be parsed with \f(CW\*(C`YAML\*(C'\fR .RE .RS 4 .RE .IP "\(bu" 4 \&\s-1SIGNATURE\s0 .RS 4 .IP "\(bu" 4 OpenPGP-signed digital signature .IP "\(bu" 4 Can be parsed and verified with \f(CW\*(C`Module::Signature\*(C'\fR .RE .RS 4 .RE .SS "Advantages over perlcc, PerlApp and Perl2exe" .IX Subsection "Advantages over perlcc, PerlApp and Perl2exe" .IP "\(bu" 4 This is not meant to be a flame .RS 4 .IP "\(bu" 4 All three maintainers have contributed to \s-1PAR\s0 directly; I'm grateful .RE .RS 4 .RE .IP "\(bu" 4 perlcc .RS 4 .IP "\(bu" 4 \&\*(L"The code generated in this way is not guaranteed to work... Use for production purposes is strongly discouraged.\*(R" (from perldoc perlcc) .IP "\(bu" 4 \&\fIGuaranteed to not work\fR is more like it .RE .RS 4 .RE .IP "\(bu" 4 PerlApp / Perl2exe .RS 4 .IP "\(bu" 4 Expensive: Need to pay for each upgrade .IP "\(bu" 4 Non-portable: Only available for limited platforms .IP "\(bu" 4 Proprietary: Cannot extend its features or fix bugs .IP "\(bu" 4 Obfuscated: Vendor and black-hats can see your code, but you can't .IP "\(bu" 4 Inflexible: Does not work with existing Perl installations .RE .RS 4 .RE .SS "\s-1MANIFEST:\s0 Best viewed with Mozilla" .IX Subsection "MANIFEST: Best viewed with Mozilla" .IP "\(bu" 4 The \s-1URL\s0 of \f(CW\*(C`MANIFEST\*(C'\fR inside \f(CW\*(C`/home/autrijus/foo.par\*(C'\fR: .Sp .Vb 1 \& jar:file:///home/autrijus/foo.par!/MANIFEST .Ve .IP "\(bu" 4 Open it in a Gecko browser (e.g. Netscape 6+) with Javascript enabled: .IP "\(bu" 4 No needed to unzip anything; just click on files to view them .SS "\s-1META\s0.yml: Metadata galore" .IX Subsection "META.yml: Metadata galore" .IP "\(bu" 4 Static, machine-readable distribution metadata .RS 4 .IP "\(bu" 4 Supported by \f(CW\*(C`Module::Build\*(C'\fR, \f(CW\*(C`ExtUtils::MakeMaker\*(C'\fR, \f(CW\*(C`Module::Install\*(C'\fR .RE .RS 4 .RE .IP "\(bu" 4 A typical \f(CW\*(C`pp\*(C'\fR\-generated \f(CW\*(C`META.yml\*(C'\fR looks like this: .Sp .Vb 12 \& build_requires: {} \& conflicts: {} \& dist_name: out.par \& distribution_type: par \& dynamic_config: 0 \& generated_by: \*(AqPerl Packager version 0.03\*(Aq \& license: unknown \& par: \& clean: 0 \& signature: \*(Aq\*(Aq \& verbatim: 0 \& version: 0.68 .Ve .IP "\(bu" 4 The \f(CW\*(C`par:\*(C'\fR settings controls its runtime behavior .SS "\s-1SIGNATURE:\s0 Signing and verifying packages" .IX Subsection "SIGNATURE: Signing and verifying packages" .IP "\(bu" 4 OpenPGP clear-signed manifest with \s-1SHA1\s0 digests .RS 4 .IP "\(bu" 4 Supported by \f(CW\*(C`Module::Signature\*(C'\fR, \f(CW\*(C`CPANPLUS\*(C'\fR and \f(CW\*(C`Module::Build\*(C'\fR .RE .RS 4 .RE .IP "\(bu" 4 A typical \f(CW\*(C`SIGNATURE\*(C'\fR looks like this: .Sp .Vb 2 \& \-\-\-\-\-BEGIN PGP SIGNED MESSAGE\-\-\-\-\- \& Hash: SHA1 \& \& SHA1 8a014cd6d0f6775552a01d1e6354a69eb6826046 AUTHORS \& ... \& \-\-\-\-\-BEGIN PGP SIGNATURE\-\-\-\-\- \& ... \& \-\-\-\-\-END PGP SIGNATURE\-\-\-\-\- .Ve .IP "\(bu" 4 Use \f(CW\*(C`pp\*(C'\fR and \f(CW\*(C`cpansign\*(C'\fR to work with signatures: .Sp .Vb 3 \& % pp \-s \-o foo.par bar.pl # make and sign foo.par from bar.pl \& % cpansign \-s foo.par # sign this PAR file \& % cpansign \-v foo.par # verify this PAR file .Ve .SS "Perl Servlets with Apache::PAR" .IX Subsection "Perl Servlets with Apache::PAR" .IP "\(bu" 4 Framework for self-contained Web applications .RS 4 .IP "\(bu" 4 Similar to Java's \*(L"Web Application Archive\*(R" (\s-1WAR\s0) files .IP "\(bu" 4 Works with mod_perl 1.x or 2.x .RE .RS 4 .RE .IP "\(bu" 4 A complete web application inside a \f(CW\*(C`.par\*(C'\fR file .RS 4 .IP "\(bu" 4 Apache configuration, static files, Perl modules... .IP "\(bu" 4 Supports Static, Registry and PerlRun handlers .IP "\(bu" 4 Can also load all PARs under a directory .RE .RS 4 .RE .IP "\(bu" 4 One additional special file: \f(CW\*(C`web.conf\*(C'\fR .Sp .Vb 6 \& Alias /myapp/cgi\-perl/ ##PARFILE##/ \& \& Options +ExecCGI \& SetHandler perl\-script \& PerlHandler Apache::PAR::Registry \& .Ve .SS "Hon Dah, A\-par-che!" .IX Subsection "Hon Dah, A-par-che!" .IP "\(bu" 4 First, make a \f(CW\*(C`hondah.par\*(C'\fR from an one-liner: .Sp .Vb 4 \& # use the "web.conf" from the previous slide \& % pp \-p \-o hondah.par \-e \*(Aqprint "Hon Dah!\en"\*(Aq \e \& \-\-add web.conf \& % chmod a+x hondah.par .Ve .IP "\(bu" 4 Add this to \f(CW\*(C`httpd.conf\*(C'\fR, then restart apache: .Sp .Vb 5 \& \& PerlModule Apache2 \& \& PerlAddVar PARInclude /home/autrijus/hondah.par \& PerlModule Apache::PAR .Ve .IP "\(bu" 4 Test it out: .Sp .Vb 2 \& % GET http://localhost/myapp/cgi\-perl/main.pl \& Hon Dah! .Ve .IP "\(bu" 4 Instant one-liner web application that works! .SS "On-demand library fetching" .IX Subsection "On-demand library fetching" .IP "\(bu" 4 With \s-1LWP\s0 installed, your can use remote \s-1PAR\s0 files: .Sp .Vb 3 \& use PAR; \& use lib \*(Aqhttp://aut.dyndns.org/par/DBI\-latest.par\*(Aq; \& use DBI; # always up to date! .Ve .IP "\(bu" 4 Modules are now cached under \f(CW$ENV{PAR_GLOBAL_TEMP}\fR .IP "\(bu" 4 Auto-updates with \f(CW\*(C`LWP::Simple::mirror\*(C'\fR .RS 4 .IP "\(bu" 4 Download only if modified .IP "\(bu" 4 Safe for offline use after the first time .IP "\(bu" 4 May use \f(CW\*(C`SIGNATURE\*(C'\fR to prevent DNS-spoofing .RE .RS 4 .RE .IP "\(bu" 4 Makes large-scale deployment a breeze .RS 4 .IP "\(bu" 4 Upgrades from a central location .IP "\(bu" 4 No installers needed .RE .RS 4 .RE .SS "Code Obfuscation" .IX Subsection "Code Obfuscation" .IP "\(bu" 4 Also known as \fIsource-hiding\fR techniques .RS 4 .IP "\(bu" 4 It is \fInot\fR encryption .IP "\(bu" 4 Offered by PerlApp, Perl2Exe, Stunnix... .RE .RS 4 .RE .IP "\(bu" 4 Usually easy to defeat .RS 4 .IP "\(bu" 4 Take optree dump from memory, feed to \f(CW\*(C`B::Deparse\*(C'\fR .IP "\(bu" 4 If you just want to stop a casual \f(CW\*(C`grep\*(C'\fR, \*(L"deflate\*(R" already works .RE .RS 4 .RE .IP "\(bu" 4 \&\s-1PAR\s0 now supports pluggable \fIinput filters\fR with \f(CW\*(C`pp \-f\*(C'\fR .RS 4 .IP "\(bu" 4 Bundled examples: Bleach, PodStrip and PatchContent .IP "\(bu" 4 True encryption using \f(CW\*(C`Crypt::*\*(C'\fR .IP "\(bu" 4 Or even _product activation_ over the internet .RE .RS 4 .RE .IP "\(bu" 4 Alternatively, just keep core logic in your server and use \s-1RPC\s0 .SS "Accessing packed files" .IX Subsection "Accessing packed files" .IP "\(bu" 4 To get the host archive from a packed program: .Sp .Vb 2 \& my $zip = PAR::par_handle($0); # an Archive::Zip object \& my $content = $zip\->contents(\*(AqMANIFEST\*(Aq); .Ve .IP "\(bu" 4 Same thing, but with \f(CW\*(C`read_file()\*(C'\fR: .Sp .Vb 1 \& my $content = PAR::read_file(\*(AqMANIFEST\*(Aq); .Ve .IP "\(bu" 4 Loaded \s-1PAR\s0 files are stored in \f(CW%PAR::LibCache\fR: .Sp .Vb 5 \& use PAR \*(Aq/home/mylibs/*.par\*(Aq; \& while (my ($filename, $zip) = each %PAR::LibCache) { \& print "[$filename \- MANIFEST]\en"; \& print $zip\->contents(\*(AqMANIFEST\*(Aq); \& } .Ve .SS "Packing \s-1GUI\s0 applications" .IX Subsection "Packing GUI applications" .IP "\(bu" 4 \&\s-1GUI\s0 toolkits often need to link with shared libraries: .Sp .Vb 2 \& # search for libncurses under library paths and pack it \& % pp \-l ncurses curses_app.pl # same for Tk, Wx, Gtk, Qt... .Ve .IP "\(bu" 4 Use \f(CW\*(C`pp \-\-gui\*(C'\fR on Win32 to eliminate the console window: .Sp .Vb 2 \& # pack \*(Aqsrc.pl\*(Aq into a console\-less \*(Aqout.exe\*(Aq (Win32 only) \& % pp \-\-gui \-o out.exe src.pl .Ve .IP "\(bu" 4 \&\*(L"Can't locate Foo/Widget/Bar.pm in \f(CW@INC\fR\*(R"? .RS 4 .IP "\(bu" 4 Some toolkits (notably Tk) autoloads modules without \f(CW\*(C`use\*(C'\fR or \f(CW\*(C`require\*(C'\fR .IP "\(bu" 4 Hence \f(CW\*(C`pp\*(C'\fR and \f(CW\*(C`Module::ScanDeps\*(C'\fR may fail to detect them .IP "\(bu" 4 Tk problems mostly fixed by now, but other toolkits may still break .IP "\(bu" 4 You can work around it with \f(CW\*(C`pp \-M\*(C'\fR or an explicit \f(CW\*(C`require\*(C'\fR .IP "\(bu" 4 Or better, send a short test-case to \f(CW\*(C`par@perl.org\*(C'\fR so we can fix it .RE .RS 4 .RE .SS "Precompiled \s-1CPAN\s0 distributions" .IX Subsection "Precompiled CPAN distributions" .IP "\(bu" 4 Installing \s-1XS\s0 extensions from \s-1CPAN\s0 was difficult .RS 4 .IP "\(bu" 4 Some platforms do not come with a compiler (Win32, MacOSX...) .IP "\(bu" 4 Some headers or libraries may be missing .IP "\(bu" 4 \&\s-1PAR\s0.pm itself used to suffer from both problems .RE .RS 4 .RE .IP "\(bu" 4 \&...but not anymore \*(-- \f(CW\*(C`Module::Install\*(C'\fR to the rescue! .Sp .Vb 6 \& # same old Makefile.PL, with a few changes \& use inc::Module::Install; # was "use ExtUtils::MakeMaker;" \& WriteMakefile( ... ); # same as the original \& check_nmake(); # make sure the user have nmake \& par_base(\*(AqAUTRIJUS\*(Aq); # your CPAN ID or a URL \& fetch_par() unless can_cc(); # use precompiled PAR only if necessary .Ve .IP "\(bu" 4 Users will not notice anything, except now it works .RS 4 .IP "\(bu" 4 Of course, you still need to type \f(CW\*(C`make par\*(C'\fR and upload the precompiled package .IP "\(bu" 4 \&\s-1PAR\s0 users can also install it directly with \f(CW\*(C`parl \-i\*(C'\fR .RE .RS 4 .RE .SS "Thank you!" .IX Subsection "Thank you!" .IP "\(bu" 4 Additional resources .RS 4 .IP "\(bu" 4 Mailing list: \f(CW\*(C`par@perl.org\*(C'\fR .IP "\(bu" 4 Subscribe: Send a blank email to \f(CW\*(C`par\-subscribe@perl.org\*(C'\fR .IP "\(bu" 4 List archive: .IP "\(bu" 4 PAR::Intro: .IP "\(bu" 4 Apache::PAR: .IP "\(bu" 4 Module::Install: .RE .RS 4 .RE .IP "\(bu" 4 Any questions? .SS "Overview of \s-1PAR\s0.pm's Implementation" .IX Subsection "Overview of PAR.pm's Implementation" .IP "\(bu" 4 Here begins the scary part .RS 4 .IP "\(bu" 4 Grues, Dragons and Jabberwocks abound... .IP "\(bu" 4 You are going to learn weird things about Perl internals .RE .RS 4 .RE .IP "\(bu" 4 \&\s-1PAR\s0 invokes four areas of Perl arcana: .RS 4 .IP "\(bu" 4 \&\f(CW@INC\fR code references .IP "\(bu" 4 On-the-fly source filtering .IP "\(bu" 4 Overriding \f(CW\*(C`DynaLoader::bootstrap()\*(C'\fR to handle \s-1XS\s0 modules .IP "\(bu" 4 Making self-bootstrapping binary executables .RE .RS 4 .RE .IP "\(bu" 4 The first two only works on 5.6 or later .RS 4 .IP "\(bu" 4 DynaLoader and \f(CW%INC\fR are there since Perl 5 was born .IP "\(bu" 4 \&\s-1PAR\s0 currently needs 5.6, but a 5.005 port is possible .RE .RS 4 .RE .ie n .SS "Code References in @INC" .el .SS "Code References in \f(CW@INC\fP" .IX Subsection "Code References in @INC" .IP "\(bu" 4 On 1999\-07\-19, Ken Fox submitted a patch to P5P .RS 4 .IP "\(bu" 4 To _enable using remote modules_ by putting hooks in \f(CW@INC\fR .IP "\(bu" 4 It's accepted to come in Perl 5.6, but undocumented until 5.8 .IP "\(bu" 4 Type \f(CW\*(C`perldoc \-f require\*(C'\fR to read the nitty-gritty details .RE .RS 4 .RE .IP "\(bu" 4 Coderefs in \f(CW@INC\fR may return a fh, or undef to 'pass': .Sp .Vb 5 \& push @INC, sub { \& my ($coderef, $filename) = @_; # $coderef is \e&my_sub \& open my $fh, "wget ftp://example.com/$filename |"; \& return $fh; # using remote modules, indeed! \& }; .Ve .IP "\(bu" 4 Perl 5.8 let you open a file handle to a string, so we just use that: .Sp .Vb 2 \& open my $fh, \*(Aq<\*(Aq, \e($zip\->memberNamed($filename)\->contents); \& return $fh; .Ve .IP "\(bu" 4 But Perl 5.6 does not have that, and I don't want to use temp files... .SS "Source Filtering without Filter::* Modules" .IX Subsection "Source Filtering without Filter::* Modules" .IP "\(bu" 4 \&... Undocumented features to the rescue! .RS 4 .IP "\(bu" 4 It turns out that \f(CW@INC\fR hooks can return \fBtwo\fR values .IP "\(bu" 4 The first is still the file handle .IP "\(bu" 4 The second is a code reference for line-by-line source filtering! .RE .RS 4 .RE .IP "\(bu" 4 This is how \f(CW\*(C`Acme::use::strict::with::pride\*(C'\fR works: .Sp .Vb 7 \& # Force all modules used to use strict and warnings \& open my $fh, "<", $filename or return; \& my @lines = ("use strict; use warnings;\en", "#line 1 \e"$full\e"\en"); \& return ($fh, sub { \& return 0 unless @lines; \& push @lines, $_; $_ = shift @lines; return length $_; \& }); .Ve .SS "Source Filtering without Filter::* Modules (cont.)" .IX Subsection "Source Filtering without Filter::* Modules (cont.)" .IP "\(bu" 4 But we don't really have a filehandle for anything .IP "\(bu" 4 Another undocumented feature saves the day! .IP "\(bu" 4 We can actually omit the first return value altogether: .Sp .Vb 9 \& # Return all contents line\-by\-line from the file inside PAR \& my @lines = split( \& /(?<=\en)/, \& $zip\->memberNamed($filename)\->contents \& ); \& return (sub { \& $_ = shift(@lines); \& return length $_; \& }); .Ve .SS "Overriding DynaLoader::bootstrap" .IX Subsection "Overriding DynaLoader::bootstrap" .IP "\(bu" 4 \&\s-1XS\s0 modules have dynamically loaded libraries .RS 4 .IP "\(bu" 4 They cannot be loaded as part of a zip file, so we extract them out .IP "\(bu" 4 Must intercept DynaLoader's library-finding process .RE .RS 4 .RE .IP "\(bu" 4 Module names are passed to \f(CW\*(C`bootstrap\*(C'\fR for \s-1XS\s0 loading .RS 4 .IP "\(bu" 4 During the process, it calls \f(CW\*(C`dl_findfile\*(C'\fR to locate the file .IP "\(bu" 4 So we install pre-hooks around both functions .RE .RS 4 .RE .IP "\(bu" 4 Our \f(CW\*(C`_bootstrap\*(C'\fR just checks if the library is in PARs .RS 4 .IP "\(bu" 4 If yes, extract it to a \f(CW\*(C`File::Temp\*(C'\fR temp file .RS 4 .IP "\(bu" 4 The file will be automatically cleaned up when the program ends .RE .RS 4 .RE .IP "\(bu" 4 It then pass the arguments to the original \f(CW\*(C`bootstrap\*(C'\fR .IP "\(bu" 4 Finally, our \f(CW\*(C`dl_findfile\*(C'\fR intercepts known filenames and return it .RE .RS 4 .RE .SS "Anatomy of a Self-Contained \s-1PAR\s0 executable" .IX Subsection "Anatomy of a Self-Contained PAR executable" .IP "\(bu" 4 The par script ($0) itself .RS 4 .IP "\(bu" 4 May be in plain-text or native executable format .RE .RS 4 .RE .IP "\(bu" 4 Any number of embedded files .RS 4 .IP "\(bu" 4 Typically used to bootstrap \s-1PAR\s0's various dependencies .IP "\(bu" 4 Each section begins with the magic string \*(L"\s-1FILE\*(R"\s0 .IP "\(bu" 4 Length of filename in pack('N') format and the filename (auto/.../) .IP "\(bu" 4 File length in pack('N') and the file's content (not compressed) .RE .RS 4 .RE .IP "\(bu" 4 One \s-1PAR\s0 file .RS 4 .IP "\(bu" 4 Just a regular zip file with the magic string \f(CW"PK\e003\e004"\fR .RE .RS 4 .RE .IP "\(bu" 4 Ending section .RS 4 .IP "\(bu" 4 A pack('N') number of the total length of \s-1FILE\s0 and \s-1PAR\s0 sections .IP "\(bu" 4 Finally, there must be a 8\-bytes magic string: \f(CW"\e012PAR.pm\e012"\fR .RE .RS 4 .RE .SS "Self-Bootstrapping Tricks" .IX Subsection "Self-Bootstrapping Tricks" .IP "\(bu" 4 All we can expect is a working perl interpreter .RS 4 .IP "\(bu" 4 The self-contained script *must not* use any modules at all .IP "\(bu" 4 But to process \s-1PAR\s0 files, we need \s-1XS\s0 modules like Compress::Zlib .RE .RS 4 .RE .IP "\(bu" 4 Answer: bundle all modules + libraries used by \s-1PAR\s0.pm .RS 4 .IP "\(bu" 4 That's what the \f(CW\*(C`FILE\*(C'\fR section in the previous slide is for .IP "\(bu" 4 Load modules to memory, and write object files to disk .IP "\(bu" 4 Then use a local \f(CW@INC\fR hook to load them on demand .RE .RS 4 .RE .IP "\(bu" 4 Minimizing the amount of temporary files .RS 4 .IP "\(bu" 4 First, try to load PerlIO::scalar and File::Temp .IP "\(bu" 4 Set up an \s-1END\s0 hook to unlink all temp files up to this point .IP "\(bu" 4 Load other bundled files, and look in the compressed \s-1PAR\s0 section .IP "\(bu" 4 This can be much easier with a pure-perl \f(CW\*(C`inflate()\*(C'\fR; patches welcome! .RE .RS 4 .RE .SS "Thank you (again)!" .IX Subsection "Thank you (again)!" .IP "\(bu" 4 Any questions, \fIplease\fR? .SH "SEE ALSO" .IX Header "SEE ALSO" \&\s-1PAR\s0, pp, par.pl, parl .PP ex::lib::zip, Acme::use::strict::with::pride .PP App::Packer, Apache::PAR, \s-1CPANPLUS\s0, Module::Install .SH "AUTHORS" .IX Header "AUTHORS" Audrey Tang .PP You can write to the mailing list at , or send an empty mail to to participate in the discussion. .PP Please submit bug reports to . .SH "COPYRIGHT" .IX Header "COPYRIGHT" Copyright 2003, 2004, 2005, 2006 by Audrey Tang . .PP This document is free documentation; you can redistribute it and/or modify it under the same terms as Perl itself. .PP See \fI\s-1LICENSE\s0\fR.