Add --use option to use extra modules when tracing
[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);
4d5603b7 13use File::Path qw(mkpath rmtree);
48af1939 14use B qw(perlstring);
15
4d5603b7 16our $VERSION = '0.009006'; # 0.9.6
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,
3fdf85ca 68 'use=s' => \my @additional_use
48af1939 69 ];
70
71 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
72
3fdf85ca 73 $file ||= 'fatpacker.trace';
74 if (!$to_stderr and -e $file) {
75 unlink $file or die "Couldn't remove old trace file: $!";
48af1939 76 }
77 my $arg = do {
3fdf85ca 78 if ($to_stderr) {
48af1939 79 "=>&STDERR"
3fdf85ca 80 } elsif ($file) {
81 "=>>${file}"
48af1939 82 }
83 };
3fdf85ca 84
85 if(@additional_use) {
86 $arg .= "," . join ",", @additional_use;
87 }
88
48af1939 89 {
90 local $ENV{PERL5OPT} = '-MApp::FatPacker::Trace'.$arg;
91 system $^X, @$args;
92 }
93}
94
95sub script_command_packlists_for {
96 my ($self, $args) = @_;
97 foreach my $pl ($self->packlists_containing($args)) {
98 print "${pl}\n";
99 }
100}
101
102sub packlists_containing {
103 my ($self, $targets) = @_;
104 my @targets = @$targets;
105 require $_ for @targets;
106 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
107 my %pack_rev;
108 my $cwd = cwd;
109 find(sub {
110 return unless $_ eq '.packlist' && -f $_;
111 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
112 }, @search);
113 chdir($cwd) or die "Couldn't chdir back to ${cwd} after find: $!";
f5a54fa1 114 my %found; @found{map +($pack_rev{$INC{$_}}||()), @targets} = ();
48af1939 115 sort keys %found;
116}
117
118sub script_command_tree {
119 my ($self, $args) = @_;
120 my $base = catdir(cwd,'fatlib');
121 $self->packlists_to_tree($base, $args);
122}
123
124sub packlists_to_tree {
125 my ($self, $where, $packlists) = @_;
4d5603b7 126 rmtree $where;
127 mkpath $where;
48af1939 128 foreach my $pl (@$packlists) {
129 my ($vol, $dirs, $file) = splitpath $pl;
130 my @dir_parts = splitdir $dirs;
131 my $pack_base;
132 PART: foreach my $p (0 .. $#dir_parts) {
133 if ($dir_parts[$p] eq 'auto') {
134 # $p-2 since it's <wanted path>/$Config{archname}/auto
135 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
136 last PART;
137 }
138 }
139 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
140 foreach my $source (lines_of $pl) {
141 # there is presumably a better way to do "is this under this base?"
142 # but if so, it's not obvious to me in File::Spec
143 next unless substr($source,0,length $pack_base) eq $pack_base;
144 my $target = rel2abs( abs2rel($source, $pack_base), $where );
145 my $target_dir = catpath((splitpath $target)[0,1]);
4d5603b7 146 mkpath $target_dir;
48af1939 147 copy $source => $target;
148 }
149 }
150}
151
152sub script_command_file {
153 my ($self, $args) = @_;
154 my $file = shift @$args;
155 my $cwd = cwd;
156 my @dirs = map rel2abs($_, $cwd), ('lib','fatlib');
157 my %files;
158 foreach my $dir (@dirs) {
159 find(sub {
160 return unless -f $_;
161 !/\.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;
162 $files{abs2rel($File::Find::name,$dir)} = do {
163 local (@ARGV, $/) = ($File::Find::name); <>
164 };
165 }, $dir);
166 }
167 my $start = stripspace <<' END_START';
168 # This chunk of stuff was generated by App::FatPacker. To find the original
169 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
170 BEGIN {
171 my %fatpacked;
172 END_START
173 my $end = stripspace <<' END_END';
174 s/^ //mg for values %fatpacked;
175
176 unshift @INC, sub {
177 if (my $fat = $fatpacked{$_[1]}) {
002ecfea 178 open my $fh, '<', \$fat
179 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
48af1939 180 return $fh;
181 }
182 return
183 };
184
185 } # END OF FATPACK CODE
186 END_END
187 my @segments = map {
188 (my $stub = $_) =~ s/\.pm$//;
189 my $name = uc join '_', split '/', $stub;
cc5db92a 190 my $data = $files{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 191 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
192 .qq!${data}${name}\n!;
193 } sort keys %files;
194 print join "\n", $start, @segments, $end;
195}
f5a54fa1 196
197=head1 NAME
198
199App::FatPacker - pack your dependencies onto your script file
200
201=head1 SYNOPSIS
202
203 $ fatpack trace myscript.pl
62ceea28 204 $ fatpack packlists-for `cat fatpacker.trace` >packlists
205 $ fatpack tree `cat packlists`
f5a54fa1 206 $ (fatpack file; cat myscript.pl) >myscript.packed.pl
207
208See the documentation for the L<fatpack> script itself for more information.
209
3fdf85ca 210The programmatic API for this code is not yet fully decided, hence the 0.9
f5a54fa1 211release version. Expect that to be cleaned up for 1.0.
212
213=head1 SUPPORT
214
215Your current best avenue is to come annoy annoy mst on #toolchain on
216irc.perl.org. There should be a non-IRC means of support by 1.0.
217
218=head1 AUTHOR
219
220Matt S. Trout (mst) <mst@shadowcat.co.uk>
221
222=head2 CONTRIBUTORS
223
224None as yet, though I probably owe lots of people thanks for ideas. Yet
225another doc nit to fix.
226
227=head1 COPYRIGHT
228
229Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
230as listed above.
231
232=head1 LICENSE
233
234This library is free software and may be distributed under the same terms
235as perl itself.
236
237=cut
238
48af1939 2391;