1 package App::FatPacker;
4 use warnings FATAL => 'all';
8 use File::Find qw(find);
9 use File::Spec::Functions qw(
10 catdir splitpath splitdir catpath rel2abs abs2rel
13 use File::Copy qw(copy);
14 use File::Path qw(mkpath rmtree);
17 our $VERSION = '0.010001'; # 0.10.1
19 $VERSION = eval $VERSION;
23 my ($args, $options) = @_;
25 local *ARGV = [ @{$args} ];
26 $self->{option_parser}->getoptions(@$options);
32 map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> };
37 $text =~ /^(\s+)/ && $text =~ s/^$1//mg;
42 $_[1] && $_[1] eq '-run_script'
43 and return shift->new->run_script;
48 option_parser => Getopt::Long::Parser->new(
49 config => [ qw(require_order pass_through bundling no_auto_abbrev) ]
55 my ($self, $args) = @_;
56 my @args = $args ? @$args : @ARGV;
57 (my $cmd = shift @args || 'help') =~ s/-/_/g;
59 if (my $meth = $self->can("script_command_${cmd}")) {
62 die "No such command ${cmd}";
66 sub script_command_help {
67 print "Try `perldoc fatpack` for how to use me\n";
70 sub script_command_pack {
71 my ($self, $args) = @_;
73 $args = $self->call_parser($args => [
74 'core-only' => \my $core_only,
77 my @modules = split /\r?\n/, $self->trace(args => $args);
78 my @packlists = $self->packlists_containing(\@modules);
80 my $base = catdir(cwd, 'fatlib');
81 $self->packlists_to_tree($base, \@packlists);
83 my $file = shift @$args;
84 print $self->fatpack_file($file, $core_only);
87 sub script_command_trace {
88 my ($self, $args) = @_;
90 $args = $self->call_parser($args => [
92 'to-stderr' => \my $to_stderr,
93 'use=s' => \my @additional_use
96 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
98 $file ||= 'fatpacker.trace';
100 if (!$to_stderr and -e $file) {
101 unlink $file or die "Couldn't remove old trace file: $!";
112 use => \@additional_use,
119 my ($self, %opts) = @_;
121 my $output = $opts{output};
122 my $trace_opts = join ',', $output||'>&STDOUT', @{$opts{use}||[]};
124 local $ENV{PERL5OPT} = join ' ',
125 ($ENV{PERL5OPT}||()), '-MApp::FatPacker::Trace='.$trace_opts;
127 my @args = @{$opts{args}||[]};
130 # user specified output target, JFDI
134 # no output target specified, slurp
135 open my $out_fh, "$^X @args |";
136 return do { local $/; <$out_fh> };
140 sub script_command_packlists_for {
141 my ($self, $args) = @_;
142 foreach my $pl ($self->packlists_containing($args)) {
147 sub packlists_containing {
148 my ($self, $targets) = @_;
149 my @targets = @$targets;
151 local @INC = ('lib', @INC);
152 foreach my $t (@targets) {
156 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
161 return unless /[\\\/]\.packlist$/ && -f $_;
162 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
165 my %found; @found{map +($pack_rev{Cwd::abs_path($INC{$_})}||()), @targets} = ();
169 sub script_command_tree {
170 my ($self, $args) = @_;
171 my $base = catdir(cwd,'fatlib');
172 $self->packlists_to_tree($base, $args);
175 sub packlists_to_tree {
176 my ($self, $where, $packlists) = @_;
179 foreach my $pl (@$packlists) {
180 my ($vol, $dirs, $file) = splitpath $pl;
181 my @dir_parts = splitdir $dirs;
183 PART: foreach my $p (0 .. $#dir_parts) {
184 if ($dir_parts[$p] eq 'auto') {
185 # $p-2 since it's <wanted path>/$Config{archname}/auto
186 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
190 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
191 foreach my $source (lines_of $pl) {
192 # there is presumably a better way to do "is this under this base?"
193 # but if so, it's not obvious to me in File::Spec
194 next unless substr($source,0,length $pack_base) eq $pack_base;
195 my $target = rel2abs( abs2rel($source, $pack_base), $where );
196 my $target_dir = catpath((splitpath $target)[0,1]);
198 copy $source => $target;
203 sub script_command_file {
204 my ($self, $args) = @_;
206 $args = $self->call_parser($args => [
207 'core-only' => \my $core_only,
210 my $file = shift @$args;
211 print $self->fatpack_file($file, $core_only);
215 my ($self, $file, $core_only) = @_;
219 if ( defined $file and -r $file ) {
220 ($shebang, $script) = $self->load_main_script($file);
223 my @dirs = $self->collect_dirs();
225 $self->collect_files($_, \%files) for @dirs;
228 "BEGIN { use Config; \@INC = \@Config{qw(privlibexp archlibexp sitelibexp sitearchexp)} }\n";
230 return join "\n", $shebang, ($core_only ? $lib_reset : ()), $self->fatpack_code(\%files), $script;
233 # This method can be overload in sub classes
234 # For example to skip POD
236 my ($self, $file) = @_;
238 local (@ARGV, $/) = ($file);
248 return grep -d, map rel2abs($_, $cwd), ('lib','fatlib');
252 my ($self, $dir, $files) = @_;
255 !/\.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;
256 $files->{File::Spec::Unix->abs2rel($File::Find::name,$dir)} =
257 $self->load_file($File::Find::name);
261 sub load_main_script {
262 my ($self, $file) = @_;
263 open my $fh, "<", $file or die("Can't read $file: $!");
265 my $script = join "", <$fh>;
267 unless ( index($shebang, '#!') == 0 ) {
268 $script = $shebang . $script;
271 return ($shebang, $script);
275 return stripspace <<' END_START';
276 # This chunk of stuff was generated by App::FatPacker. To find the original
277 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
284 return stripspace <<' END_END';
285 s/^ //mg for values %fatpacked;
287 my $class = 'FatPacked::'.(0+\%fatpacked);
289 *{"${class}::files"} = sub { keys %{$_[0]} };
292 *{"${class}::INC"} = sub {
293 if (my $fat = $_[0]{$_[1]}) {
295 return 0 unless length $fat;
296 $fat =~ s/^([^\n]*\n?)//;
306 *{"${class}::INC"} = sub {
307 if (my $fat = $_[0]{$_[1]}) {
308 open my $fh, '<', \$fat
309 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
316 unshift @INC, bless \%fatpacked, $class;
317 } # END OF FATPACK CODE
322 my ($self, $files) = @_;
324 (my $stub = $_) =~ s/\.pm$//;
325 my $name = uc join '_', split '/', $stub;
326 my $data = $files->{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
327 '$fatpacked{'.perlstring($_).qq!} = '#line '.(1+__LINE__).' "'.__FILE__."\\"\\n".<<'${name}';\n!
328 .qq!${data}${name}\n!;
331 return join "\n", $self->fatpack_start, @segments, $self->fatpack_end;
338 App::FatPacker - pack your dependencies onto your script file
342 $ fatpack pack myscript.pl >myscript.packed.pl
344 Or, with more step-by-step control:
346 $ fatpack trace myscript.pl
347 $ fatpack packlists-for `cat fatpacker.trace` >packlists
348 $ fatpack tree `cat packlists`
349 $ fatpack file myscript.pl >myscript.packed.pl
351 See the documentation for the L<fatpack> script itself for more information.
353 The programmatic API for this code is not yet fully decided, hence the 0.x
354 release version. Expect that to be cleaned up for 1.0.
358 L<article for Perl Advent 2012|http://www.perladvent.org/2012/2012-12-14.html>
362 Your current best avenue is to come annoy mst on #toolchain on
363 irc.perl.org. There should be a non-IRC means of support by 1.0.
367 Matt S. Trout (mst) <mst@shadowcat.co.uk>
371 miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) <miyagawa@bulknews.net>
373 tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) <tokuhirom@gmail.com>
375 dg - David Leadbeater (cpan:DGL) <dgl@dgl.cx>
377 gugod - 劉康民 (cpan:GUGOD) <gugod@cpan.org>
379 t0m - Tomas Doran (cpan:BOBTFISH) <bobtfish@bobtfish.net>
381 sawyer - Sawyer X (cpan:XSAWYERX) <xsawyerx@cpan.org>
383 ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
385 Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
387 dolmen - Olivier Mengué (cpan:DOLMEN) <dolmen@cpan.org>
389 djerius - Diab Jerius (cpan:DJERIUS) <djerius@cpan.org>
391 haarg - Graham Knop (cpan:HAARG> <haarg@haarg.org>
393 Many more people are probably owed thanks for ideas. Yet
394 another doc nit to fix.
398 Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
403 This library is free software and may be distributed under the same terms