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.010000'; # 0.10.0
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 my @modules = split /\r?\n/, $self->trace(args => $args);
74 my @packlists = $self->packlists_containing(\@modules);
76 my $base = catdir(cwd, 'fatlib');
77 $self->packlists_to_tree($base, \@packlists);
79 my $file = shift @$args;
80 print $self->fatpack_file($file);
83 sub script_command_trace {
84 my ($self, $args) = @_;
86 $args = $self->call_parser($args => [
88 'to-stderr' => \my $to_stderr,
89 'use=s' => \my @additional_use
92 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
94 $file ||= 'fatpacker.trace';
96 if (!$to_stderr and -e $file) {
97 unlink $file or die "Couldn't remove old trace file: $!";
108 use => \@additional_use,
115 my ($self, %opts) = @_;
117 my $output = $opts{output};
118 my $trace_opts = join ',', $output||'>&STDOUT', @{$opts{use}||[]};
120 local $ENV{PERL5OPT} = join ' ',
121 ($ENV{PERL5OPT}||()), '-MApp::FatPacker::Trace='.$trace_opts;
123 my @args = @{$opts{args}||[]};
126 # user specified output target, JFDI
130 # no output target specified, slurp
131 open my $out_fh, "$^X @args |";
132 return do { local $/; <$out_fh> };
136 sub script_command_packlists_for {
137 my ($self, $args) = @_;
138 foreach my $pl ($self->packlists_containing($args)) {
143 sub packlists_containing {
144 my ($self, $targets) = @_;
145 my @targets = @$targets;
146 foreach my $t (@targets) {
149 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
154 return unless /[\\\/]\.packlist$/ && -f $_;
155 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
158 my %found; @found{map +($pack_rev{Cwd::abs_path($INC{$_})}||()), @targets} = ();
162 sub script_command_tree {
163 my ($self, $args) = @_;
164 my $base = catdir(cwd,'fatlib');
165 $self->packlists_to_tree($base, $args);
168 sub packlists_to_tree {
169 my ($self, $where, $packlists) = @_;
172 foreach my $pl (@$packlists) {
173 my ($vol, $dirs, $file) = splitpath $pl;
174 my @dir_parts = splitdir $dirs;
176 PART: foreach my $p (0 .. $#dir_parts) {
177 if ($dir_parts[$p] eq 'auto') {
178 # $p-2 since it's <wanted path>/$Config{archname}/auto
179 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
183 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
184 foreach my $source (lines_of $pl) {
185 # there is presumably a better way to do "is this under this base?"
186 # but if so, it's not obvious to me in File::Spec
187 next unless substr($source,0,length $pack_base) eq $pack_base;
188 my $target = rel2abs( abs2rel($source, $pack_base), $where );
189 my $target_dir = catpath((splitpath $target)[0,1]);
191 copy $source => $target;
196 sub script_command_file {
197 my ($self, $args) = @_;
198 my $file = shift @$args;
199 print $self->fatpack_file($file);
203 my ($self, $file) = @_;
207 if ( defined $file and -r $file ) {
208 ($shebang, $script) = $self->load_main_script($file);
211 my @dirs = $self->collect_dirs();
213 $self->collect_files($_, \%files) for @dirs;
215 return join "\n", $shebang, $self->fatpack_code(\%files), $script;
218 # This method can be overload in sub classes
219 # For example to skip POD
221 my ($self, $file) = @_;
223 local (@ARGV, $/) = ($file);
233 return grep -d, map rel2abs($_, $cwd), ('lib','fatlib');
237 my ($self, $dir, $files) = @_;
240 !/\.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;
241 $files->{File::Spec::Unix->abs2rel($File::Find::name,$dir)} =
242 $self->load_file($File::Find::name);
246 sub load_main_script {
247 my ($self, $file) = @_;
248 open my $fh, "<", $file or die("Can't read $file: $!");
250 my $script = join "", <$fh>;
252 unless ( index($shebang, '#!') == 0 ) {
253 $script = $shebang . $script;
256 return ($shebang, $script);
260 return stripspace <<' END_START';
261 # This chunk of stuff was generated by App::FatPacker. To find the original
262 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
269 return stripspace <<' END_END';
270 s/^ //mg for values %fatpacked;
272 my $class = 'FatPacked::'.(0+\%fatpacked);
274 *{"${class}::files"} = sub { keys %{$_[0]} };
277 *{"${class}::INC"} = sub {
278 if (my $fat = $_[0]{$_[1]}) {
280 return 0 unless length $fat;
281 $fat =~ s/^([^\n]*\n?)//;
291 *{"${class}::INC"} = sub {
292 if (my $fat = $_[0]{$_[1]}) {
293 open my $fh, '<', \$fat
294 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
301 unshift @INC, bless \%fatpacked, $class;
302 } # END OF FATPACK CODE
307 my ($self, $files) = @_;
309 (my $stub = $_) =~ s/\.pm$//;
310 my $name = uc join '_', split '/', $stub;
311 my $data = $files->{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
312 '$fatpacked{'.perlstring($_).qq!} = '#line '.(1+__LINE__).' "'.__FILE__."\\"\\n".<<'${name}';\n!
313 .qq!${data}${name}\n!;
316 return join "\n", $self->fatpack_start, @segments, $self->fatpack_end;
323 App::FatPacker - pack your dependencies onto your script file
327 $ fatpack pack myscript.pl >myscript.packed.pl
329 Or, with more step-by-step control:
331 $ fatpack trace myscript.pl
332 $ fatpack packlists-for `cat fatpacker.trace` >packlists
333 $ fatpack tree `cat packlists`
334 $ fatpack file myscript.pl >myscript.packed.pl
336 See the documentation for the L<fatpack> script itself for more information.
338 The programmatic API for this code is not yet fully decided, hence the 0.x
339 release version. Expect that to be cleaned up for 1.0.
343 L<article for Perl Advent 2012|http://www.perladvent.org/2012/2012-12-14.html>
347 Your current best avenue is to come annoy mst on #toolchain on
348 irc.perl.org. There should be a non-IRC means of support by 1.0.
352 Matt S. Trout (mst) <mst@shadowcat.co.uk>
356 miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) <miyagawa@bulknews.net>
358 tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) <tokuhirom@gmail.com>
360 dg - David Leadbeater (cpan:DGL) <dgl@dgl.cx>
362 gugod - 劉康民 (cpan:GUGOD) <gugod@cpan.org>
364 t0m - Tomas Doran (cpan:BOBTFISH) <bobtfish@bobtfish.net>
366 sawyer - Sawyer X (cpan:XSAWYERX) <xsawyerx@cpan.org>
368 ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
370 Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
372 dolmen - Olivier Mengué (cpan:DOLMEN) <dolmen@cpan.org>
374 djerius - Diab Jerius (cpan:DJERIUS) <djerius@cpan.org>
376 haarg - Graham Knop (cpan:HAARG> <haarg@haarg.org>
378 Many more people are probably owed thanks for ideas. Yet
379 another doc nit to fix.
383 Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
388 This library is free software and may be distributed under the same terms