minor wording improvement
[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(
10 catdir splitpath splitdir catpath rel2abs abs2rel
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
3b444fd0 17our $VERSION = '0.009016'; # 0.009.016
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
130 open my $out_fh, '-|', $^X, @args;
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;
150 my $cwd = cwd;
151 find(sub {
152 return unless $_ eq '.packlist' && -f $_;
153 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
154 }, @search);
155 chdir($cwd) or die "Couldn't chdir back to ${cwd} after find: $!";
f5a54fa1 156 my %found; @found{map +($pack_rev{$INC{$_}}||()), @targets} = ();
48af1939 157 sort keys %found;
158}
159
160sub script_command_tree {
161 my ($self, $args) = @_;
162 my $base = catdir(cwd,'fatlib');
163 $self->packlists_to_tree($base, $args);
164}
165
166sub packlists_to_tree {
167 my ($self, $where, $packlists) = @_;
4d5603b7 168 rmtree $where;
169 mkpath $where;
48af1939 170 foreach my $pl (@$packlists) {
171 my ($vol, $dirs, $file) = splitpath $pl;
172 my @dir_parts = splitdir $dirs;
173 my $pack_base;
174 PART: foreach my $p (0 .. $#dir_parts) {
175 if ($dir_parts[$p] eq 'auto') {
176 # $p-2 since it's <wanted path>/$Config{archname}/auto
177 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
178 last PART;
179 }
180 }
181 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
182 foreach my $source (lines_of $pl) {
183 # there is presumably a better way to do "is this under this base?"
184 # but if so, it's not obvious to me in File::Spec
185 next unless substr($source,0,length $pack_base) eq $pack_base;
186 my $target = rel2abs( abs2rel($source, $pack_base), $where );
187 my $target_dir = catpath((splitpath $target)[0,1]);
4d5603b7 188 mkpath $target_dir;
48af1939 189 copy $source => $target;
190 }
191 }
192}
193
194sub script_command_file {
195 my ($self, $args) = @_;
196 my $file = shift @$args;
9be5f3c0 197 print $self->fatpack_file($file);
198}
199
200sub fatpack_file {
201 my ($self, $file) = @_;
48af1939 202 my $cwd = cwd;
0c46b17a 203 my @dirs = grep -d, map rel2abs($_, $cwd), ('lib','fatlib');
48af1939 204 my %files;
205 foreach my $dir (@dirs) {
206 find(sub {
207 return unless -f $_;
1f8ac8bd 208 !/\.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 209 $files{File::Spec::Unix->abs2rel($File::Find::name,$dir)} = do {
48af1939 210 local (@ARGV, $/) = ($File::Find::name); <>
211 };
0de38c11 212 close ARGV;
48af1939 213 }, $dir);
214 }
215 my $start = stripspace <<' END_START';
216 # This chunk of stuff was generated by App::FatPacker. To find the original
217 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
218 BEGIN {
219 my %fatpacked;
220 END_START
221 my $end = stripspace <<' END_END';
222 s/^ //mg for values %fatpacked;
223
224 unshift @INC, sub {
225 if (my $fat = $fatpacked{$_[1]}) {
7bf1d4f2 226 if ($] < 5.008) {
227 return sub {
228 return 0 unless length $fat;
87108237 229 $fat =~ s/^([^\n]*\n?)//;
7bf1d4f2 230 $_ = $1;
231 return 1;
232 };
233 }
002ecfea 234 open my $fh, '<', \$fat
235 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
48af1939 236 return $fh;
237 }
238 return
239 };
240
241 } # END OF FATPACK CODE
242 END_END
243 my @segments = map {
244 (my $stub = $_) =~ s/\.pm$//;
245 my $name = uc join '_', split '/', $stub;
cc5db92a 246 my $data = $files{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 247 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
248 .qq!${data}${name}\n!;
249 } sort keys %files;
7dabafaa 250 my $shebang = "";
251 my $script = "";
252 if ( defined $file and -r $file ) {
253 open my $fh, "<", $file or die("Can't read $file: $!");
254 $shebang = <$fh>;
255 $script = join "", <$fh>;
256 close $fh;
257 unless ( index($shebang, '#!') == 0 ) {
258 $script = $shebang . $script;
259 $shebang = "";
260 }
261 }
262 return join "\n", $shebang, $start, @segments, $end, $script;
48af1939 263}
f5a54fa1 264
bb49414d 265=encoding UTF-8
266
f5a54fa1 267=head1 NAME
268
269App::FatPacker - pack your dependencies onto your script file
270
271=head1 SYNOPSIS
272
6da38a2d 273 $ fatpack pack myscript.pl >myscript.packed.pl
274
275Or, with more step-by-step control:
276
f5a54fa1 277 $ fatpack trace myscript.pl
62ceea28 278 $ fatpack packlists-for `cat fatpacker.trace` >packlists
279 $ fatpack tree `cat packlists`
7dabafaa 280 $ fatpack file myscript.pl >myscript.packed.pl
cb50b68f 281
f5a54fa1 282See the documentation for the L<fatpack> script itself for more information.
283
3fdf85ca 284The programmatic API for this code is not yet fully decided, hence the 0.9
f5a54fa1 285release version. Expect that to be cleaned up for 1.0.
286
66a19d01 287=head1 SEE ALSO
288
289L<article for Perl Advent 2012|http://www.perladvent.org/2012/2012-12-14.html>
290
f5a54fa1 291=head1 SUPPORT
292
293Your current best avenue is to come annoy annoy mst on #toolchain on
294irc.perl.org. There should be a non-IRC means of support by 1.0.
295
296=head1 AUTHOR
297
298Matt S. Trout (mst) <mst@shadowcat.co.uk>
299
300=head2 CONTRIBUTORS
301
99b15200 302miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) <miyagawa@bulknews.net>
303
304tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) <tokuhirom@gmail.com>
305
306dg - David Leadbeater (cpan:DGL) <dgl@dgl.cx>
307
308gugod - 劉康民 (cpan:GUGOD) <gugod@cpan.org>
309
310t0m - Tomas Doran (cpan:BOBTFISH) <bobtfish@bobtfish.net>
311
312sawyer - Sawyer X (cpan:XSAWYERX) <xsawyerx@cpan.org>
313
314ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
315
cbd99f49 316Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
317
99b15200 318Many more people are probably owed thanks for ideas. Yet
f5a54fa1 319another doc nit to fix.
320
321=head1 COPYRIGHT
322
323Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
324as listed above.
325
326=head1 LICENSE
327
328This library is free software and may be distributed under the same terms
329as perl itself.
330
331=cut
332
48af1939 3331;
24d68aa2 334