RT#87352: fix .packlist path abs2rel
[p5sagit/App-FatPacker.git] / lib / App / FatPacker.pm
CommitLineData
48af1939 1package App::FatPacker;
2
3use strict;
4use warnings FATAL => 'all';
8572221e 5use 5.008001;
48af1939 6use Getopt::Long;
7use Cwd qw(cwd);
8use File::Find qw(find);
9use File::Spec::Functions qw(
db590368 10 catdir catfile splitpath splitdir catpath rel2abs abs2rel
48af1939 11);
cbd99f49 12use File::Spec::Unix;
48af1939 13use File::Copy qw(copy);
4d5603b7 14use File::Path qw(mkpath rmtree);
48af1939 15use B qw(perlstring);
16
4987f035 17our $VERSION = '0.009018'; # 0.009.017
f5a54fa1 18
19$VERSION = eval $VERSION;
20
48af1939 21sub call_parser {
24d68aa2 22 my $self = shift;
69667cc8 23 my ($args, $options) = @_;
24d68aa2 24
25 local *ARGV = [ @{$args} ];
69667cc8 26 $self->{option_parser}->getoptions(@$options);
24d68aa2 27
28 return [ @ARGV ];
48af1939 29}
30
31sub lines_of {
32 map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> };
33}
34
35sub stripspace {
36 my ($text) = @_;
37 $text =~ /^(\s+)/ && $text =~ s/^$1//mg;
38 $text;
39}
40
48af1939 41sub import {
24d68aa2 42 $_[1] && $_[1] eq '-run_script'
48af1939 43 and return shift->new->run_script;
44}
45
24d68aa2 46sub new {
47 bless {
48 option_parser => Getopt::Long::Parser->new(
49 config => [ qw(require_order pass_through bundling no_auto_abbrev) ]
50 ),
51 }, $_[0];
52}
48af1939 53
54sub run_script {
55 my ($self, $args) = @_;
56 my @args = $args ? @$args : @ARGV;
3e4dadce 57 (my $cmd = shift @args || 'help') =~ s/-/_/g;
24d68aa2 58
48af1939 59 if (my $meth = $self->can("script_command_${cmd}")) {
60 $self->$meth(\@args);
61 } else {
62 die "No such command ${cmd}";
63 }
64}
65
3e4dadce 66sub script_command_help {
67 print "Try `perldoc fatpack` for how to use me\n";
68}
69
cb3e6884 70sub script_command_pack {
71 my ($self, $args) = @_;
72
73 my @modules = split /\r?\n/, $self->trace(args => $args);
74 my @packlists = $self->packlists_containing(\@modules);
75
76 my $base = catdir(cwd, 'fatlib');
77 $self->packlists_to_tree($base, \@packlists);
78
79 my $file = shift @$args;
7dabafaa 80 print $self->fatpack_file($file);
cb3e6884 81}
82
48af1939 83sub script_command_trace {
84 my ($self, $args) = @_;
24d68aa2 85
69667cc8 86 $args = $self->call_parser($args => [
48af1939 87 'to=s' => \my $file,
88 'to-stderr' => \my $to_stderr,
3fdf85ca 89 'use=s' => \my @additional_use
69667cc8 90 ]);
48af1939 91
92 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
93
3fdf85ca 94 $file ||= 'fatpacker.trace';
69667cc8 95
3fdf85ca 96 if (!$to_stderr and -e $file) {
97 unlink $file or die "Couldn't remove old trace file: $!";
48af1939 98 }
99 my $arg = do {
3fdf85ca 100 if ($to_stderr) {
020d9b76 101 ">&STDERR"
3fdf85ca 102 } elsif ($file) {
276a30c9 103 ">>${file}"
48af1939 104 }
105 };
3fdf85ca 106
b4704b1a 107 $self->trace(
5e1de95d 108 use => \@additional_use,
109 args => $args,
7a3662c8 110 output => $arg,
b4704b1a 111 );
abd7cf01 112}
113
114sub trace {
b4704b1a 115 my ($self, %opts) = @_;
69667cc8 116
1ea23474 117 my $output = $opts{output};
69667cc8 118 my $trace_opts = join ',', $output||'>&STDOUT', @{$opts{use}||[]};
7a3662c8 119
69667cc8 120 local $ENV{PERL5OPT} = '-MApp::FatPacker::Trace='.$trace_opts;
abd7cf01 121
69667cc8 122 my @args = @{$opts{args}||[]};
e0cd89ab 123
69667cc8 124 if ($output) {
125 # user specified output target, JFDI
126 system $^X, @args;
127 return;
e0cd89ab 128 } else {
69667cc8 129 # no output target specified, slurp
9317190f 130 open my $out_fh, "$^X @args |";
69667cc8 131 return do { local $/; <$out_fh> };
132 }
48af1939 133}
134
135sub script_command_packlists_for {
136 my ($self, $args) = @_;
137 foreach my $pl ($self->packlists_containing($args)) {
138 print "${pl}\n";
139 }
140}
141
142sub packlists_containing {
143 my ($self, $targets) = @_;
144 my @targets = @$targets;
a976705b 145 foreach my $t (@targets) {
146 require $t;
147 }
48af1939 148 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
149 my %pack_rev;
0612cca1 150 find({
151 no_chdir => 1,
152 wanted => sub {
153 return unless /[\\\/]\.packlist$/ && -f $_;
154 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
155 },
48af1939 156 }, @search);
0612cca1 157 my %found; @found{map +($pack_rev{Cwd::abs_path($INC{$_})}||()), @targets} = ();
48af1939 158 sort keys %found;
159}
160
161sub script_command_tree {
162 my ($self, $args) = @_;
163 my $base = catdir(cwd,'fatlib');
164 $self->packlists_to_tree($base, $args);
165}
166
167sub packlists_to_tree {
168 my ($self, $where, $packlists) = @_;
4d5603b7 169 rmtree $where;
170 mkpath $where;
db590368 171 # Build a copy of @INC with dir separator added after each path
172 my @inc = map
173 { catfile($_, '') }
174 @INC;
48af1939 175 foreach my $pl (@$packlists) {
48af1939 176 foreach my $source (lines_of $pl) {
db590368 177 my $base;
178 foreach my $inc_base (@inc) {
179 # XXX Not case-proof (for case ignorant filesystems)
180 if (substr($source,0,length $inc_base) eq $inc_base) {
181 $base = $inc_base;
182 last;
183 }
184 }
185 unless ($base) {
186 die "Couldn't figure out \@INC path of ${source}" if substr($source, -3) eq '.pm';
187 next;
188 }
189
48af1939 190 # there is presumably a better way to do "is this under this base?"
191 # but if so, it's not obvious to me in File::Spec
db590368 192 my $target = rel2abs( abs2rel($source, $base), $where );
48af1939 193 my $target_dir = catpath((splitpath $target)[0,1]);
4d5603b7 194 mkpath $target_dir;
48af1939 195 copy $source => $target;
196 }
197 }
198}
199
200sub script_command_file {
201 my ($self, $args) = @_;
202 my $file = shift @$args;
9be5f3c0 203 print $self->fatpack_file($file);
204}
205
206sub fatpack_file {
207 my ($self, $file) = @_;
48af1939 208 my $cwd = cwd;
0c46b17a 209 my @dirs = grep -d, map rel2abs($_, $cwd), ('lib','fatlib');
48af1939 210 my %files;
211 foreach my $dir (@dirs) {
212 find(sub {
213 return unless -f $_;
1f8ac8bd 214 !/\.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;
cbd99f49 215 $files{File::Spec::Unix->abs2rel($File::Find::name,$dir)} = do {
48af1939 216 local (@ARGV, $/) = ($File::Find::name); <>
217 };
0de38c11 218 close ARGV;
48af1939 219 }, $dir);
220 }
221 my $start = stripspace <<' END_START';
222 # This chunk of stuff was generated by App::FatPacker. To find the original
223 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
224 BEGIN {
225 my %fatpacked;
226 END_START
227 my $end = stripspace <<' END_END';
228 s/^ //mg for values %fatpacked;
229
230 unshift @INC, sub {
231 if (my $fat = $fatpacked{$_[1]}) {
7bf1d4f2 232 if ($] < 5.008) {
233 return sub {
234 return 0 unless length $fat;
87108237 235 $fat =~ s/^([^\n]*\n?)//;
7bf1d4f2 236 $_ = $1;
237 return 1;
238 };
239 }
002ecfea 240 open my $fh, '<', \$fat
241 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
48af1939 242 return $fh;
243 }
244 return
245 };
246
247 } # END OF FATPACK CODE
248 END_END
249 my @segments = map {
250 (my $stub = $_) =~ s/\.pm$//;
251 my $name = uc join '_', split '/', $stub;
cc5db92a 252 my $data = $files{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 253 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
254 .qq!${data}${name}\n!;
255 } sort keys %files;
7dabafaa 256 my $shebang = "";
257 my $script = "";
258 if ( defined $file and -r $file ) {
259 open my $fh, "<", $file or die("Can't read $file: $!");
260 $shebang = <$fh>;
261 $script = join "", <$fh>;
262 close $fh;
263 unless ( index($shebang, '#!') == 0 ) {
264 $script = $shebang . $script;
265 $shebang = "";
266 }
267 }
268 return join "\n", $shebang, $start, @segments, $end, $script;
48af1939 269}
f5a54fa1 270
bb49414d 271=encoding UTF-8
272
f5a54fa1 273=head1 NAME
274
275App::FatPacker - pack your dependencies onto your script file
276
277=head1 SYNOPSIS
278
6da38a2d 279 $ fatpack pack myscript.pl >myscript.packed.pl
280
281Or, with more step-by-step control:
282
f5a54fa1 283 $ fatpack trace myscript.pl
62ceea28 284 $ fatpack packlists-for `cat fatpacker.trace` >packlists
285 $ fatpack tree `cat packlists`
7dabafaa 286 $ fatpack file myscript.pl >myscript.packed.pl
cb50b68f 287
f5a54fa1 288See the documentation for the L<fatpack> script itself for more information.
289
3fdf85ca 290The programmatic API for this code is not yet fully decided, hence the 0.9
f5a54fa1 291release version. Expect that to be cleaned up for 1.0.
292
66a19d01 293=head1 SEE ALSO
294
295L<article for Perl Advent 2012|http://www.perladvent.org/2012/2012-12-14.html>
296
f5a54fa1 297=head1 SUPPORT
298
299Your current best avenue is to come annoy annoy mst on #toolchain on
300irc.perl.org. There should be a non-IRC means of support by 1.0.
301
302=head1 AUTHOR
303
304Matt S. Trout (mst) <mst@shadowcat.co.uk>
305
306=head2 CONTRIBUTORS
307
99b15200 308miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) <miyagawa@bulknews.net>
309
310tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) <tokuhirom@gmail.com>
311
312dg - David Leadbeater (cpan:DGL) <dgl@dgl.cx>
313
314gugod - 劉康民 (cpan:GUGOD) <gugod@cpan.org>
315
316t0m - Tomas Doran (cpan:BOBTFISH) <bobtfish@bobtfish.net>
317
318sawyer - Sawyer X (cpan:XSAWYERX) <xsawyerx@cpan.org>
319
320ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
321
cbd99f49 322Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
323
99b15200 324Many more people are probably owed thanks for ideas. Yet
f5a54fa1 325another doc nit to fix.
326
327=head1 COPYRIGHT
328
329Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
330as listed above.
331
332=head1 LICENSE
333
334This library is free software and may be distributed under the same terms
335as perl itself.
336
337=cut
338
48af1939 3391;
24d68aa2 340