Make data actually work
[p5sagit/App-FatPacker.git] / lib / App / FatPacker.pm
CommitLineData
48af1939 1package App::FatPacker;
2
3use strict;
4use warnings FATAL => 'all';
f5a54fa1 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);
12use File::Copy qw(copy);
13use File::Path qw(make_path remove_tree);
14use B qw(perlstring);
15
2de6023f 16our $VERSION = '0.009003'; # 0.9.3
f5a54fa1 17
18$VERSION = eval $VERSION;
19
48af1939 20my $option_parser = Getopt::Long::Parser->new(
21 config => [ qw(require_order pass_through bundling no_auto_abbrev) ]
22);
23
24sub call_parser {
25 local *ARGV = [ @{$_[0]} ];
26 $option_parser->getoptions(@{$_[1]});
27 [ @ARGV ];
28}
29
30sub lines_of {
31 map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> };
32}
33
34sub stripspace {
35 my ($text) = @_;
36 $text =~ /^(\s+)/ && $text =~ s/^$1//mg;
37 $text;
38}
39
48af1939 40sub import {
41 $_[1] eq '-run_script'
42 and return shift->new->run_script;
43}
44
45sub new { bless({}, $_[0]) }
46
47sub run_script {
48 my ($self, $args) = @_;
49 my @args = $args ? @$args : @ARGV;
3e4dadce 50 (my $cmd = shift @args || 'help') =~ s/-/_/g;
48af1939 51 if (my $meth = $self->can("script_command_${cmd}")) {
52 $self->$meth(\@args);
53 } else {
54 die "No such command ${cmd}";
55 }
56}
57
3e4dadce 58sub script_command_help {
59 print "Try `perldoc fatpack` for how to use me\n";
60}
61
48af1939 62sub script_command_trace {
63 my ($self, $args) = @_;
64
65 $args = call_parser $args => [
66 'to=s' => \my $file,
67 'to-stderr' => \my $to_stderr,
68 ];
69
70 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
71
72 (my $use_file = $file) ||= 'fatpacker.trace';
73 if (!$to_stderr and -e $use_file) {
74 unlink $use_file or die "Couldn't remove old trace file: $!";
75 }
76 my $arg = do {
77 if ($file) {
78 "=>>${file}"
79 } elsif ($to_stderr) {
80 "=>&STDERR"
81 } else {
82 ""
83 }
84 };
85 {
86 local $ENV{PERL5OPT} = '-MApp::FatPacker::Trace'.$arg;
87 system $^X, @$args;
88 }
89}
90
91sub script_command_packlists_for {
92 my ($self, $args) = @_;
93 foreach my $pl ($self->packlists_containing($args)) {
94 print "${pl}\n";
95 }
96}
97
98sub packlists_containing {
99 my ($self, $targets) = @_;
100 my @targets = @$targets;
101 require $_ for @targets;
102 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
103 my %pack_rev;
104 my $cwd = cwd;
105 find(sub {
106 return unless $_ eq '.packlist' && -f $_;
107 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
108 }, @search);
109 chdir($cwd) or die "Couldn't chdir back to ${cwd} after find: $!";
f5a54fa1 110 my %found; @found{map +($pack_rev{$INC{$_}}||()), @targets} = ();
48af1939 111 sort keys %found;
112}
113
114sub script_command_tree {
115 my ($self, $args) = @_;
116 my $base = catdir(cwd,'fatlib');
117 $self->packlists_to_tree($base, $args);
118}
119
120sub packlists_to_tree {
121 my ($self, $where, $packlists) = @_;
122 remove_tree $where;
123 make_path $where;
124 foreach my $pl (@$packlists) {
125 my ($vol, $dirs, $file) = splitpath $pl;
126 my @dir_parts = splitdir $dirs;
127 my $pack_base;
128 PART: foreach my $p (0 .. $#dir_parts) {
129 if ($dir_parts[$p] eq 'auto') {
130 # $p-2 since it's <wanted path>/$Config{archname}/auto
131 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
132 last PART;
133 }
134 }
135 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
136 foreach my $source (lines_of $pl) {
137 # there is presumably a better way to do "is this under this base?"
138 # but if so, it's not obvious to me in File::Spec
139 next unless substr($source,0,length $pack_base) eq $pack_base;
140 my $target = rel2abs( abs2rel($source, $pack_base), $where );
141 my $target_dir = catpath((splitpath $target)[0,1]);
142 make_path $target_dir;
143 copy $source => $target;
144 }
145 }
146}
147
148sub script_command_file {
149 my ($self, $args) = @_;
279ff374 150
151 $args = call_parser $args => [
152 'data-pack' => \my $data_pack,
153 'main=s' => \my $main_file,
154 ];
155
48af1939 156 my $file = shift @$args;
157 my $cwd = cwd;
158 my @dirs = map rel2abs($_, $cwd), ('lib','fatlib');
159 my %files;
160 foreach my $dir (@dirs) {
161 find(sub {
162 return unless -f $_;
163 !/\.pm$/ and warn "File ${File::Find::name} isn't a .pm file - can't pack this and if you hoped we were going to things may not be what you expected later\n" and return;
164 $files{abs2rel($File::Find::name,$dir)} = do {
165 local (@ARGV, $/) = ($File::Find::name); <>
166 };
167 }, $dir);
168 }
279ff374 169
170 if($data_pack) {
171 generate_file_data_style(\%files, $main_file);
172 } else {
173 generate_file_hash_style(\%files);
174 }
175}
176
177sub generate_file_hash_style {
178 my($files) = @_;
179
48af1939 180 my $start = stripspace <<' END_START';
181 # This chunk of stuff was generated by App::FatPacker. To find the original
182 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
183 BEGIN {
184 my %fatpacked;
185 END_START
186 my $end = stripspace <<' END_END';
187 s/^ //mg for values %fatpacked;
188
ad3b2f82 189 unshift @INC, sub {
190 if (my $fat = $fatpacked{$_[1]}) {
191 open my $fh, '<', \$fat
192 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
193 return $fh;
194 }
195 return
196 };
48af1939 197 } # END OF FATPACK CODE
198 END_END
199 my @segments = map {
200 (my $stub = $_) =~ s/\.pm$//;
201 my $name = uc join '_', split '/', $stub;
279ff374 202 my $data = $files->{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 203 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
204 .qq!${data}${name}\n!;
279ff374 205 } sort keys %$files;
48af1939 206 print join "\n", $start, @segments, $end;
207}
f5a54fa1 208
279ff374 209sub generate_file_data_style {
210 my($files, $main) = @_;
211
ad3b2f82 212 {
213 open my $main_fh, '<', $main or die "Can't open '$main': $!";
214 $files->{"fatpacked-main"} = join "", <$main_fh>;
215 }
216
279ff374 217 my $start = stripspace <<' END_START';
218 # This chunk of stuff was generated by App::FatPacker. Do not edit.
ad3b2f82 219 {
220 my %fatpacked = (
279ff374 221 END_START
222
223 my @segments = map {
224 (my $stub = $_) =~ s/\.pm$//;
225 my $data = $files->{$_};
226 $data =~ s/(?<!\n)\z/\n/;
227 [$_, length $data, \$data];
228 } sort keys %$files;
229
230 my $fatpack_data;
231 my $start_idx = 0;
232 for my $segment (@segments) {
ad3b2f82 233 $fatpack_data .= " ".perlstring($segment->[0])." => [$start_idx, $segment->[1]],\n";
279ff374 234 $start_idx += $segment->[1];
235 }
236
237 my $end = stripspace <<' END_END';
ad3b2f82 238 );
279ff374 239
ad3b2f82 240 my $data_pos = tell DATA;
241 unshift @INC, sub {
242 if(my $fat = $fatpacked{$_[1]}) {
243 seek DATA, $data_pos + $fat->[0], 0;
244 local $/ = \$fat->[1];
245 open my $fh, '<', \scalar <DATA>;
246 return $fh;
247 }
248 };
249 }
279ff374 250 # END OF FATPACK CODE
279ff374 251
ad3b2f82 252 do 'fatpacked-main' or die $@;
253 END_END
279ff374 254
255 print join "\n", $start, $fatpack_data, $end;
279ff374 256 print join "\n", "__DATA__", join "", map ${$_->[2]}, @segments;
257}
258
f5a54fa1 259=head1 NAME
260
261App::FatPacker - pack your dependencies onto your script file
262
263=head1 SYNOPSIS
264
265 $ fatpack trace myscript.pl
62ceea28 266 $ fatpack packlists-for `cat fatpacker.trace` >packlists
267 $ fatpack tree `cat packlists`
f5a54fa1 268 $ (fatpack file; cat myscript.pl) >myscript.packed.pl
269
270See the documentation for the L<fatpack> script itself for more information.
271
272The programmatic API for this code is not yet fully decided, hence the 0.9.1
273release version. Expect that to be cleaned up for 1.0.
274
275=head1 SUPPORT
276
277Your current best avenue is to come annoy annoy mst on #toolchain on
278irc.perl.org. There should be a non-IRC means of support by 1.0.
279
280=head1 AUTHOR
281
282Matt S. Trout (mst) <mst@shadowcat.co.uk>
283
284=head2 CONTRIBUTORS
285
286None as yet, though I probably owe lots of people thanks for ideas. Yet
287another doc nit to fix.
288
289=head1 COPYRIGHT
290
291Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
292as listed above.
293
294=head1 LICENSE
295
296This library is free software and may be distributed under the same terms
297as perl itself.
298
299=cut
300
48af1939 3011;