App-FatPacker-0.010008/0000755000372100001440000000000013432533217014077 5ustar matthewtusersApp-FatPacker-0.010008/maint/0000755000372100001440000000000013432533217015207 5ustar matthewtusersApp-FatPacker-0.010008/maint/Makefile.PL.include0000644000372100001440000000046312312041543020576 0ustar matthewtusersBEGIN { -e 'Distar' or system("git clone git://git.shadowcat.co.uk/p5sagit/Distar.git") } use lib 'Distar/lib'; use Distar; author 'mst - Matt S. Trout (cpan:MSTROUT) '; manifest_include 'bin' => qr/.*/; manifest_include 't/mod' => qr/.*/; manifest_include 't/line' => qr/.*\.p[ml]/; App-FatPacker-0.010008/t/0000755000372100001440000000000013432533217014342 5ustar matthewtusersApp-FatPacker-0.010008/t/basic.t0000644000372100001440000000016611650652435015617 0ustar matthewtusersuse strict; use warnings FATAL => 'all'; use Test::More qw(no_plan); require App::FatPacker; pass "Didn't blow up"; App-FatPacker-0.010008/t/mod/0000755000372100001440000000000013432533217015121 5ustar matthewtusersApp-FatPacker-0.010008/t/mod/ModuleA.pm0000644000372100001440000000006113037670613017005 0ustar matthewtuserspackage ModuleA; use ModuleB; $foo = "bar"; 1; App-FatPacker-0.010008/t/mod/ModuleCond.pm0000644000372100001440000000006713037670613017516 0ustar matthewtuserspackage ModuleCond; eval { require ModuleNothere }; 1; App-FatPacker-0.010008/t/mod/ModuleB.pm0000644000372100001440000000004113037670613017004 0ustar matthewtuserspackage ModuleB; use ModuleC; 1; App-FatPacker-0.010008/t/mod/ModuleD.pl0000644000372100001440000000001513037670613017006 0ustar matthewtusersuse ModuleD; App-FatPacker-0.010008/t/mod/ModuleD.pm0000644000372100001440000000007213037670613017012 0ustar matthewtuserspackage ModuleD; $INC{"ModuleForeign.pm"} = __FILE__; 1; App-FatPacker-0.010008/t/mod/ModuleC.pm0000644000372100001440000000010013037670613017001 0ustar matthewtuserspackage ModuleC; sub xyz { shift() ? 666 : 999 } 1; App-FatPacker-0.010008/t/line.t0000644000372100001440000000073513037670613015466 0ustar matthewtusersuse strict; use warnings FATAL => 'all'; use Test::More tests => 2; use File::Temp qw/tempdir/; use File::Spec; use App::FatPacker; chdir 't/line'; my $fp = App::FatPacker->new; my $temp_fh = File::Temp->new; select $temp_fh; $fp->script_command_file([ 'line-test.pl' ]); select STDOUT; close $temp_fh; # make sure we don't pick up things from our created dir chdir File::Spec->tmpdir; # Packed, now try using it. This should run the tests inside t/line/a.pm do $temp_fh; App-FatPacker-0.010008/t/pack.t0000644000372100001440000000337013037670613015453 0ustar matthewtusersuse strict; use warnings FATAL => 'all'; use Test::More qw(no_plan); use File::Basename; use File::Copy; use File::Path; use File::Temp qw/tempdir/; use File::Spec; use Cwd; use App::FatPacker; my $keep = $ENV{'FATPACKER_KEEP_TESTDIR'}; my $cwd = getcwd; my $tempdir = tempdir('fatpacker-XXXXX', DIR => "$cwd/t", $keep ? (CLEANUP => 0) : (CLEANUP => 1)); mkpath([<$tempdir/{lib,fatlib}>]); for () { copy $_, "$tempdir/lib/".basename($_) or die "copy failed: $!"; } chdir $tempdir; my $fp = App::FatPacker->new; my $packed_file = "$tempdir/script"; open my $temp_fh, '>', $packed_file or die "can't write to $packed_file: $!"; select $temp_fh; $fp->script_command_file; print "1;\n"; select STDOUT; close $temp_fh; # make sure we don't pick up things from our created dir chdir File::Spec->tmpdir; # Packed, now try using it: require $packed_file; { require ModuleA; no warnings 'once'; ok $ModuleA::foo eq 'bar', "packed script works"; } { ok ref $INC[0], "\$INC[0] is a reference"; ok $INC[0]->can( "files" ), "\$INC[0] has a files method"; my @files = sort $INC[0]->files; is_deeply( \@files, [ 'ModuleA.pm', 'ModuleB.pm', 'ModuleC.pm', 'ModuleCond.pm', 'ModuleD.pm', ], "\$INC[0]->files returned the files" ); } if (my $testwith = $ENV{'FATPACKER_TESTWITH'}) { for my $perl (split ' ', $testwith) { my $out = system $perl, '-e', q{alarm 5; require $ARGV[0]; require ModuleA; exit($ModuleA::foo eq 'bar' ? 0 : 1)}, $temp_fh; ok !$out, "packed script works with $perl"; $out = system $perl, '-e', q{alarm 5; require $ARGV[0]; exit( (sort $INC[0]->files)[0] eq 'ModuleA.pm' ? 0 : 1 )}, $temp_fh; ok !$out, "\$INC[0]->files works with $perl"; } } App-FatPacker-0.010008/t/line/0000755000372100001440000000000013432533217015271 5ustar matthewtusersApp-FatPacker-0.010008/t/line/line-test.pl0000644000372100001440000000035112312041543017521 0ustar matthewtusers # To run this test manually: # perl -I../../lib ../../bin/fatpack file line-test.pl | perl package OurTest; use Test::More; our $main_file = __FILE__; note "File: $main_file"; # Run the tests in the packed file do 'line/a.pm'; App-FatPacker-0.010008/t/line/lib/0000755000372100001440000000000013432533217016037 5ustar matthewtusersApp-FatPacker-0.010008/t/line/lib/line/0000755000372100001440000000000013432533217016766 5ustar matthewtusersApp-FatPacker-0.010008/t/line/lib/line/a.pm0000644000372100001440000000042312312041543017533 0ustar matthewtusers # This file will be included in the packed file generated by t/line.t # Check that the name is the one of the packed file is __FILE__, $main_file, '__FILE__'; # Check that the line is the one where the module code starts in the packed file is __LINE__, 14, '__LINE__'; 1; App-FatPacker-0.010008/t/trace.t0000644000372100001440000000142513037670613015632 0ustar matthewtusersuse strict; use warnings FATAL => 'all'; use Test::More qw(no_plan); test_trace("t/mod/ModuleA.pm" => ("ModuleB.pm", "ModuleC.pm")); test_trace("t/mod/ModuleB.pm" => ("ModuleC.pm")); test_trace("t/mod/ModuleC.pm" => ()); test_trace("t/mod/ModuleD.pl" => ("ModuleD.pm")); # Attempts to conditionally load a module that isn't present test_trace("t/mod/ModuleCond.pm" => ()); sub test_trace { my($file, @loaded) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; unlink "fatpacker.trace"; system($^X, "-Mblib", '-It/mod', "-MApp::FatPacker::Trace", $file); open my $trace, "<", "fatpacker.trace"; my @traced = sort map { chomp; $_ } <$trace>; close $trace; is_deeply \@traced, \@loaded, "All expected modules loaded for $file"; unlink "fatpacker.trace"; } App-FatPacker-0.010008/bin/0000755000372100001440000000000013432533217014647 5ustar matthewtusersApp-FatPacker-0.010008/bin/fatpack0000755000372100001440000000461613432531745016221 0ustar matthewtusers#!/usr/bin/perl use App::FatPacker -run_script; =head1 NAME fatpack - Command line frontend for App::FatPacker =head1 COMMANDS =head2 pack $ fatpack pack myscript.pl > myscript.packed.pl A shortcut to do all the work of tracing, collecting packlists, extracting modules in fatlib, then concatenating into a packed script - in one shot. If you need more detailed controls for additional modules, use the following commands separately (see L). =head2 trace $ fatpack trace [--to=trace-file|--to-stderr] [--use=MODULE] myscript.pl Compiles myscript.pl (as in "perl -c") and writes out a trace file containing every module require()d during the compilation. The trace file is called 'fatpacker.trace' by default; the --to option overrides this. If you pass --to-stderr fatpack writes the trace to STDERR instead. You cannot pass both --to and --to-stderr. If the --use option specifies a module (or modules, if used multiple times) those modules will be additionally included in the trace output. =head2 packlists-for $ fatpack packlists-for Module1 Module2 Module3 Searches your perl's @INC for .packlist files containing the .pm files for the modules requested and emits a list of unique packlist files to STDOUT. These packlists will, in a pure cpan-installation environment, be all non-core distributions required for those modules. Unfortunately most vendors strip the .packlist files so if you installed modules via e.g. apt-get you may be missing those modules; installing your dependencies into a L first is the preferred workaround. =head2 tree $ fatpack tree fatlib packlist1 packlist2 packlist3 Takes a list of packlist files and copies their contents into a tree at the requested location. This tree should be sufficient to 'use lib' to make available all modules provided by the distributions whose packlists were specified. =head2 file $ fatpack file Recurses into the 'lib' and 'fatlib' directories and bundles all .pm files found into a BEGIN block which adds a virtual @INC entry to load these files from the bundled code rather than disk. =head1 RECIPES Current basic recipe for packing: $ fatpack trace myscript.pl $ fatpack packlists-for `cat fatpacker.trace` >packlists $ fatpack tree `cat packlists` $ fatpack file myscript.pl >myscript.packed.pl =head1 COPYRIGHT, LICENSE, AUTHOR See the corresponding sections in L. =cut App-FatPacker-0.010008/README0000644000372100001440000000557313432533217014771 0ustar matthewtusersNAME App::FatPacker - pack your dependencies onto your script file SYNOPSIS $ fatpack pack myscript.pl >myscript.packed.pl Or, with more step-by-step control: $ fatpack trace myscript.pl $ fatpack packlists-for `cat fatpacker.trace` >packlists $ fatpack tree `cat packlists` $ fatpack file myscript.pl >myscript.packed.pl Each command is designed to be simple and self-contained so that you can modify the input/output of each step as needed. See the documentation for the fatpack script itself for more information. The programmatic API for this code is not yet fully decided, hence the 0.x release version. Expect that to be cleaned up for 1.0. CAVEATS As dependency module code is copied into the resulting file as text, only pure-perl dependencies can be packed, not compiled XS code. The currently-installed dependencies to pack are found via .packlist files, which are generally only included in non-core distributions that were installed by a CPAN installer. This is a feature; see "packlists-for" in fatpack for details. (a notable exception to this is FreeBSD, which, since its packaging system is designed to work equivalently to a source install, does preserve the packlist files) SEE ALSO article for Perl Advent 2012 pp - PAR Packager, a much more complex architecture-dependent packer that can pack compiled code and even a Perl interpreter SUPPORT Bugs may be submitted through the RT bug tracker (or bug-App-FatPacker@rt.cpan.org ). You can normally also obtain assistance on irc, in #toolchain on irc.perl.org. AUTHOR Matt S. Trout (mst) CONTRIBUTORS miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) dg - David Leadbeater (cpan:DGL) gugod - 劉康民 (cpan:GUGOD) t0m - Tomas Doran (cpan:BOBTFISH) sawyer - Sawyer X (cpan:XSAWYERX) ether - Karen Etheridge (cpan:ETHER) Mithaldu - Christian Walde (cpan:MITHALDU) dolmen - Olivier Mengué (cpan:DOLMEN) djerius - Diab Jerius (cpan:DJERIUS) haarg - Graham Knop (cpan:HAARG) grinnz - Dan Book (cpan:DBOOK) Many more people are probably owed thanks for ideas. Yet another doc nit to fix. COPYRIGHT Copyright (c) 2010 the App::FatPacker "AUTHOR" and "CONTRIBUTORS" as listed above. LICENSE This library is free software and may be distributed under the same terms as perl itself. App-FatPacker-0.010008/META.json0000644000372100001440000000350413432533217015522 0ustar matthewtusers{ "abstract" : "pack your dependencies onto your script file", "author" : [ "mst - Matt S. Trout (cpan:MSTROUT) " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "App-FatPacker", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "B" : "1.01", "Cwd" : "0", "File::Copy" : "0", "File::Find" : "0", "File::Path" : "0", "File::Spec::Functions" : "0", "File::Spec::Unix" : "0", "Getopt::Long" : "0", "perl" : "5.008000" } }, "test" : { "requires" : { "File::Basename" : "0", "File::Spec" : "0", "File::Temp" : "0", "Test::More" : "0.82" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-App-FatPacker-Metadata@rt.cpan.org", "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=App-FatPacker" }, "repository" : { "type" : "git", "url" : "git://git.shadowcat.co.uk/p5sagit/App-FatPacker.git", "web" : "http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/App-FatPacker.git" } }, "version" : "0.010008", "x_serialization_backend" : "JSON::PP version 2.27300" } App-FatPacker-0.010008/Makefile.PL0000644000372100001440000000266413037670613016064 0ustar matthewtusersuse strict; use warnings FATAL => 'all'; use ExtUtils::MakeMaker; use 5.008000; (do 'maint/Makefile.PL.include' or die $@) unless -f 'META.yml'; WriteMakefile( NAME => 'App::FatPacker', VERSION_FROM => 'lib/App/FatPacker.pm', EXE_FILES => [ 'bin/fatpack', ], META_MERGE => { 'meta-spec' => { version => '2' }, dynamic_config => 0, resources => { # r/w: p5sagit@git.shadowcat.co.uk:App-FatPacker.git repository => { url => 'git://git.shadowcat.co.uk/p5sagit/App-FatPacker.git', web => 'http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/App-FatPacker.git', type => 'git', }, bugtracker => { mailto => 'bug-App-FatPacker-Metadata@rt.cpan.org', web => 'https://rt.cpan.org/Public/Dist/Display.html?Name=App-FatPacker', }, }, prereqs => { runtime => { requires => { # B::perlstring was added in B 1.01 (perl 5.8.0) perl => '5.008000', 'B' => '1.01', 'Cwd' => '0', 'File::Copy' => '0', 'File::Find' => '0', 'File::Path' => '0', 'File::Spec::Functions' => '0', 'File::Spec::Unix' => '0', 'Getopt::Long' => '0', }, }, test => { requires => { 'File::Basename' => '0', 'File::Spec' => '0', 'File::Temp' => '0', 'Test::More' => '0.82', }, }, }, }, ); App-FatPacker-0.010008/MANIFEST0000644000372100001440000000105713432533217015233 0ustar matthewtusersbin/fatpack Changes lib/App/FatPacker.pm lib/App/FatPacker/Trace.pm maint/Makefile.PL.include Makefile.PL MANIFEST This list of files t/basic.t t/line.t t/line/lib/line/a.pm t/line/line-test.pl t/mod/ModuleA.pm t/mod/ModuleB.pm t/mod/ModuleC.pm t/mod/ModuleCond.pm t/mod/ModuleD.pl t/mod/ModuleD.pm t/pack.t t/trace.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) README README file (added by Distar) App-FatPacker-0.010008/META.yml0000644000372100001440000000173213432533217015353 0ustar matthewtusers--- abstract: 'pack your dependencies onto your script file' author: - 'mst - Matt S. Trout (cpan:MSTROUT) ' build_requires: ExtUtils::MakeMaker: '0' File::Basename: '0' File::Spec: '0' File::Temp: '0' Test::More: '0.82' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: App-FatPacker no_index: directory: - t - inc requires: B: '1.01' Cwd: '0' File::Copy: '0' File::Find: '0' File::Path: '0' File::Spec::Functions: '0' File::Spec::Unix: '0' Getopt::Long: '0' perl: '5.008000' resources: bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=App-FatPacker repository: git://git.shadowcat.co.uk/p5sagit/App-FatPacker.git version: '0.010008' x_serialization_backend: 'CPAN::Meta::YAML version 0.012' App-FatPacker-0.010008/Changes0000644000372100001440000000630213432533166015376 0ustar matthewtusersRevision history for App-FatPacker 0.010008 - 2019-02-18 - documentation improvements - switch to letting EUMM rewrite fatpack's #! 0.010007 - 2017-01-22 - don't fail packlist finding if one module fails to load - don't fail tracing w/undef INC values due to failed optional module load 0.010_006 - 2017-01-20 - detect and handle packing from within inc dirs w/version part - fix tests to no longer rely on . being in @INC 0.010005 - 2015-12-14 - fix fatpacked code to work properly on perl 5.6 in all cases - removed x_static_install metadata (RT#110299) 0.010004 - 2015-06-28 - fix tests to work reliably on win32 0.010003 - 2015-04-17 - exclude virtual %INC entries from trace output 0.010002 - 2014-08-16 - correctly specify perl prerequisite of 5.8 in meta files 0.010001 - 2014-03-18 - include 'lib' in @INC for packlists_containing's require() calls 0.010000 - 2013-11-27 - use object for @INC hook instead of subref, including a 'files' method to return a list of files provided - generate #line relative to the whole fatpacked script so die() reports and caller() now point directly to the right line in the script (RT#87118, DOLMEN) 0.009018 - 2013-07-26 - deal with relative paths better by changing options passed to File::Find (RT#84414, miyagawa) 0.009017 - 2013-05-29 - fix fatal "List form of piped open not implemented" on MSWin32 (RT#85712, ether) 0.009016 - 2013-04-07 - 'file' command now takes care of keeping its original shebang line when given the original file as its argument. The 'pack' command makes use of that too. (Robin Smidsrod, miyagawa) 0.009015 - 2013-04-07 - new 'pack' command, to do everything in one command (thanks, miyagawa!) (RT#84415) 0.009014 - 2013-04-03 - avoid fatal error when lib/ doesn't exist (now it is simply skipped) (RT#84413, miyagawa) 0.009013 - 2013-01-22 - fix to support for < 5.8 perls (haarg) 0.009012 - 2013-01-18 - support for < 5.8 perls (RT#57811, haarg) 0.009011 - 2012-09-24 - RT #79835: install bin/fatpack (lost in 0.009009 in the Module::Install -> Distar conversion) 0.009010 - 2012-09-19 - enable temp file deletion in pack.t on Win32 - RT #79489: %fatpacked keys need to be unix paths 0.009009 - 2012-08-03 - RT #78724: fix trace --to-stderr (ether) 0.009008 - 2012-07-16 - RT #78391: Avoid using $_ when requiring modules 0.009007 - 2012-07-12 - Allow capturing of trace information into return value - Add repsoitory metadata to META.YML 0.009006 - 2011-01-18 - Use File::Path's "legacy" mkpath and rmtree functions. No longer needs File::Path >= 2.07. 0.009005 - 2011-01-12 - Avoid using done_testing so this works on older systems 0.009004 - 2011-01-12 - Die if open fails in @INC handler, reduces confusing errors (DGL) - RT #63466: Add ending newline if input lacks one (GUGOD) - RT #60622: Fix conditional loading, only fatpack loaded modules (DGL) 0.009003 - 2010-07-22 - fixed dependency for File::Path(remove_tree/make_path was first appeared at 2.07) 0.009002 - 2010-07-07 - fixed typo and doc bug on command usage.(miyagawa) - added `help' command for slightly better user experience(miyagawa) 0.009001 - 2010-03-25 - Initial release App-FatPacker-0.010008/lib/0000755000372100001440000000000013432533217014645 5ustar matthewtusersApp-FatPacker-0.010008/lib/App/0000755000372100001440000000000013432533217015365 5ustar matthewtusersApp-FatPacker-0.010008/lib/App/FatPacker/0000755000372100001440000000000013432533217017225 5ustar matthewtusersApp-FatPacker-0.010008/lib/App/FatPacker/Trace.pm0000644000372100001440000000633213041125733020621 0ustar matthewtuserspackage App::FatPacker::Trace; use strict; use warnings FATAL => 'all'; use B (); my $trace_file; my %initial_inc; sub import { my (undef, $file, @extras) = @_; $trace_file = $file || '>>fatpacker.trace'; # For filtering out our own deps later. # (Not strictly required as these are core only and won't have packlists, but # looks neater.) %initial_inc = %INC; # Use any extra modules specified eval "use $_" for @extras; B::minus_c; } CHECK { return unless $trace_file; # not imported open my $trace, $trace_file or die "Couldn't open $trace_file to trace to: $!"; for my $inc (keys %INC) { next if exists $initial_inc{$inc}; next unless defined($INC{$inc}) and $INC{$inc} =~ /\Q${inc}\E\Z/; print $trace "$inc\n"; } } 1; __END__ =head1 NAME App::FatPacker::Trace - Tracing module usage using compilation checking =head1 SYNOPSIS # open STDERR for writing # will be like: open my $fh, '>', '&STDERR'... perl -MApp::FatPacker::Trace=>&STDERR myscript.pl # open a file for writing # will be like: open my $fh, '>>', 'fatpacker.trace' perl -MApp::FatPacker::Trace=>>fatpacker.trace myscript.pl =head1 DESCRIPTION This module allows tracing the modules being used by your code. It does that using clever trickery using the C method, the C block and L's C function. When App::FatPacker::Trace is being used, the import() method will call C in order to set up the global compilation-only flag perl (the interpreter) has. This will prevent any other code from being run. Then in the C block which is reached at the end of the compilation phase (see L), it will gather all modules that have been loaded, using C<%INC>, and will write it to a file or to STDERR, determined by parameters sent to the C method. =head1 METHODS =head2 import This method gets run when you just load L. It will note the current C<%INC> and will set up the output to be written to, and raise the compilation-only flag, which will prevent anything from being run past that point. This flag cannot be unset, so this is most easily run from the command line as such: perl -MApp::FatPacker::Trace [...] You can control the parameters to the import using an equal sign, as such: # send the parameter "hello" perl -MApp::FatPacker::Trace=hello [...] # send the parameter ">&STDERR" perl -MApp::FatPacker::Trace=>&STDERR [...] The import method accepts a first parameter telling it which output to open and how. These are both sent in a single parameter. # append to mytrace.txt perl -MApp::FatPacker::Trace=>>mytrace.txt myscript.pl # write to STDERR perl -MApp::FatPacker::Trace=>&STDERR myscript.pl The import method accepts additional parameters of extra modules to load. It will then add these modules to the trace. This is helpful if you want to explicitly indicate additional modules to trace, even if they aren't used in your script. Perhaps you're conditionally using them, perhaps they're for additional features, perhaps they're loaded lazily, whatever the reason. # Add Moo to the trace, even if you don't trace it in myscript.pl perl -MApp::FatPacker::Trace=>&STDERR,Moo myscript.pl App-FatPacker-0.010008/lib/App/FatPacker.pm0000644000372100001440000002534713432533133017573 0ustar matthewtuserspackage App::FatPacker; use strict; use warnings FATAL => 'all'; use 5.008001; use Getopt::Long; use Cwd qw(cwd); use File::Find qw(find); use File::Spec::Functions qw( catdir splitpath splitdir catpath rel2abs abs2rel ); use File::Spec::Unix; use File::Copy qw(copy); use File::Path qw(mkpath rmtree); use B qw(perlstring); our $VERSION = '0.010008'; # v0.10.8 $VERSION = eval $VERSION; sub call_parser { my $self = shift; my ($args, $options) = @_; local *ARGV = [ @{$args} ]; $self->{option_parser}->getoptions(@$options); return [ @ARGV ]; } sub lines_of { map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> }; } sub stripspace { my ($text) = @_; $text =~ /^(\s+)/ && $text =~ s/^$1//mg; $text; } sub import { $_[1] && $_[1] eq '-run_script' and return shift->new->run_script; } sub new { bless { option_parser => Getopt::Long::Parser->new( config => [ qw(require_order pass_through bundling no_auto_abbrev) ] ), }, $_[0]; } sub run_script { my ($self, $args) = @_; my @args = $args ? @$args : @ARGV; (my $cmd = shift @args || 'help') =~ s/-/_/g; if (my $meth = $self->can("script_command_${cmd}")) { $self->$meth(\@args); } else { die "No such command ${cmd}"; } } sub script_command_help { print "Try `perldoc fatpack` for how to use me\n"; } sub script_command_pack { my ($self, $args) = @_; my @modules = split /\r?\n/, $self->trace(args => $args); my @packlists = $self->packlists_containing(\@modules); my $base = catdir(cwd, 'fatlib'); $self->packlists_to_tree($base, \@packlists); my $file = shift @$args; print $self->fatpack_file($file); } sub script_command_trace { my ($self, $args) = @_; $args = $self->call_parser($args => [ 'to=s' => \my $file, 'to-stderr' => \my $to_stderr, 'use=s' => \my @additional_use ]); die "Can't use to and to-stderr on same call" if $file && $to_stderr; $file ||= 'fatpacker.trace'; if (!$to_stderr and -e $file) { unlink $file or die "Couldn't remove old trace file: $!"; } my $arg = do { if ($to_stderr) { ">&STDERR" } elsif ($file) { ">>${file}" } }; $self->trace( use => \@additional_use, args => $args, output => $arg, ); } sub trace { my ($self, %opts) = @_; my $output = $opts{output}; my $trace_opts = join ',', $output||'>&STDOUT', @{$opts{use}||[]}; local $ENV{PERL5OPT} = join ' ', ($ENV{PERL5OPT}||()), '-MApp::FatPacker::Trace='.$trace_opts; my @args = @{$opts{args}||[]}; if ($output) { # user specified output target, JFDI system $^X, @args; return; } else { # no output target specified, slurp open my $out_fh, "$^X @args |"; return do { local $/; <$out_fh> }; } } sub script_command_packlists_for { my ($self, $args) = @_; foreach my $pl ($self->packlists_containing($args)) { print "${pl}\n"; } } sub packlists_containing { my ($self, $targets) = @_; my @targets; { local @INC = ('lib', @INC); foreach my $t (@$targets) { unless (eval { require $t; 1}) { warn "Failed to load ${t}: $@\n" ."Make sure you're not missing a packlist as a result\n"; next; } push @targets, $t; } } my @search = grep -d $_, map catdir($_, 'auto'), @INC; my %pack_rev; find({ no_chdir => 1, wanted => sub { return unless /[\\\/]\.packlist$/ && -f $_; $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name; }, }, @search); my %found; @found{map +($pack_rev{Cwd::abs_path($INC{$_})}||()), @targets} = (); sort keys %found; } sub script_command_tree { my ($self, $args) = @_; my $base = catdir(cwd,'fatlib'); $self->packlists_to_tree($base, $args); } sub packlists_to_tree { my ($self, $where, $packlists) = @_; rmtree $where; mkpath $where; foreach my $pl (@$packlists) { my ($vol, $dirs, $file) = splitpath $pl; my @dir_parts = splitdir $dirs; my $pack_base; PART: foreach my $p (0 .. $#dir_parts) { if ($dir_parts[$p] eq 'auto') { # $p-2 normally since it's /$Config{archname}/auto but # if the last bit is a number it's $Config{archname}/$version/auto # so use $p-3 in that case my $version_lib = 0+!!($dir_parts[$p-1] =~ /^[0-9.]+$/); $pack_base = catpath $vol, catdir @dir_parts[0..$p-(2+$version_lib)]; last PART; } } die "Couldn't figure out base path of packlist ${pl}" unless $pack_base; foreach my $source (lines_of $pl) { # there is presumably a better way to do "is this under this base?" # but if so, it's not obvious to me in File::Spec next unless substr($source,0,length $pack_base) eq $pack_base; my $target = rel2abs( abs2rel($source, $pack_base), $where ); my $target_dir = catpath((splitpath $target)[0,1]); mkpath $target_dir; copy $source => $target; } } } sub script_command_file { my ($self, $args) = @_; my $file = shift @$args; print $self->fatpack_file($file); } sub fatpack_file { my ($self, $file) = @_; my $shebang = ""; my $script = ""; if ( defined $file and -r $file ) { ($shebang, $script) = $self->load_main_script($file); } my @dirs = $self->collect_dirs(); my %files; $self->collect_files($_, \%files) for @dirs; return join "\n", $shebang, $self->fatpack_code(\%files), $script; } # This method can be overload in sub classes # For example to skip POD sub load_file { my ($self, $file) = @_; my $content = do { local (@ARGV, $/) = ($file); <> }; close ARGV; return $content; } sub collect_dirs { my ($self) = @_; my $cwd = cwd; return grep -d, map rel2abs($_, $cwd), ('lib','fatlib'); } sub collect_files { my ($self, $dir, $files) = @_; find(sub { return unless -f $_; !/\.pm$/ and warn "File ${File::Find::name} isn't a .pm file - can't pack this -- if you hoped we were going to, things may not be what you expected later\n" and return; $files->{File::Spec::Unix->abs2rel($File::Find::name,$dir)} = $self->load_file($File::Find::name); }, $dir); } sub load_main_script { my ($self, $file) = @_; open my $fh, "<", $file or die("Can't read $file: $!"); my $shebang = <$fh>; my $script = join "", <$fh>; close $fh; unless ( index($shebang, '#!') == 0 ) { $script = $shebang . $script; $shebang = ""; } return ($shebang, $script); } sub fatpack_start { return stripspace <<' END_START'; # This chunk of stuff was generated by App::FatPacker. To find the original # file's code, look for the end of this BEGIN block or the string 'FATPACK' BEGIN { my %fatpacked; END_START } sub fatpack_end { return stripspace <<' END_END'; s/^ //mg for values %fatpacked; my $class = 'FatPacked::'.(0+\%fatpacked); no strict 'refs'; *{"${class}::files"} = sub { keys %{$_[0]} }; if ($] < 5.008) { *{"${class}::INC"} = sub { if (my $fat = $_[0]{$_[1]}) { my $pos = 0; my $last = length $fat; return (sub { return 0 if $pos == $last; my $next = (1 + index $fat, "\n", $pos) || $last; $_ .= substr $fat, $pos, $next - $pos; $pos = $next; return 1; }); } }; } else { *{"${class}::INC"} = sub { if (my $fat = $_[0]{$_[1]}) { open my $fh, '<', \$fat or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; return $fh; } return; }; } unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE END_END } sub fatpack_code { my ($self, $files) = @_; my @segments = map { (my $stub = $_) =~ s/\.pm$//; my $name = uc join '_', split '/', $stub; my $data = $files->{$_}; $data =~ s/^/ /mg; $data =~ s/(?fatpack_start, @segments, $self->fatpack_end; } =encoding UTF-8 =head1 NAME App::FatPacker - pack your dependencies onto your script file =head1 SYNOPSIS $ fatpack pack myscript.pl >myscript.packed.pl Or, with more step-by-step control: $ fatpack trace myscript.pl $ fatpack packlists-for `cat fatpacker.trace` >packlists $ fatpack tree `cat packlists` $ fatpack file myscript.pl >myscript.packed.pl Each command is designed to be simple and self-contained so that you can modify the input/output of each step as needed. See the documentation for the L script itself for more information. The programmatic API for this code is not yet fully decided, hence the 0.x release version. Expect that to be cleaned up for 1.0. =head1 CAVEATS As dependency module code is copied into the resulting file as text, only pure-perl dependencies can be packed, not compiled XS code. The currently-installed dependencies to pack are found via F<.packlist> files, which are generally only included in non-core distributions that were installed by a CPAN installer. This is a feature; see L for details. (a notable exception to this is FreeBSD, which, since its packaging system is designed to work equivalently to a source install, does preserve the packlist files) =head1 SEE ALSO L
L - PAR Packager, a much more complex architecture-dependent packer that can pack compiled code and even a Perl interpreter =head1 SUPPORT Bugs may be submitted through L (or L). You can normally also obtain assistance on irc, in #toolchain on irc.perl.org. =head1 AUTHOR Matt S. Trout (mst) =head2 CONTRIBUTORS miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) dg - David Leadbeater (cpan:DGL) gugod - 劉康民 (cpan:GUGOD) t0m - Tomas Doran (cpan:BOBTFISH) sawyer - Sawyer X (cpan:XSAWYERX) ether - Karen Etheridge (cpan:ETHER) Mithaldu - Christian Walde (cpan:MITHALDU) dolmen - Olivier Mengué (cpan:DOLMEN) djerius - Diab Jerius (cpan:DJERIUS) haarg - Graham Knop (cpan:HAARG) grinnz - Dan Book (cpan:DBOOK) Many more people are probably owed thanks for ideas. Yet another doc nit to fix. =head1 COPYRIGHT Copyright (c) 2010 the App::FatPacker L and L as listed above. =head1 LICENSE This library is free software and may be distributed under the same terms as perl itself. =cut 1;