Add a gitignore
[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,
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) = @_;
4d5603b7 122 rmtree $where;
123 mkpath $where;
48af1939 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]);
4d5603b7 142 mkpath $target_dir;
48af1939 143 copy $source => $target;
144 }
145 }
146}
147
148sub script_command_file {
149 my ($self, $args) = @_;
150 my $file = shift @$args;
151 my $cwd = cwd;
152 my @dirs = map rel2abs($_, $cwd), ('lib','fatlib');
153 my %files;
154 foreach my $dir (@dirs) {
155 find(sub {
156 return unless -f $_;
157 !/\.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;
158 $files{abs2rel($File::Find::name,$dir)} = do {
159 local (@ARGV, $/) = ($File::Find::name); <>
160 };
161 }, $dir);
162 }
163 my $start = stripspace <<' END_START';
164 # This chunk of stuff was generated by App::FatPacker. To find the original
165 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
166 BEGIN {
167 my %fatpacked;
168 END_START
169 my $end = stripspace <<' END_END';
170 s/^ //mg for values %fatpacked;
171
172 unshift @INC, sub {
173 if (my $fat = $fatpacked{$_[1]}) {
002ecfea 174 open my $fh, '<', \$fat
175 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
48af1939 176 return $fh;
177 }
178 return
179 };
180
181 } # END OF FATPACK CODE
182 END_END
183 my @segments = map {
184 (my $stub = $_) =~ s/\.pm$//;
185 my $name = uc join '_', split '/', $stub;
cc5db92a 186 my $data = $files{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 187 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
188 .qq!${data}${name}\n!;
189 } sort keys %files;
190 print join "\n", $start, @segments, $end;
191}
f5a54fa1 192
193=head1 NAME
194
195App::FatPacker - pack your dependencies onto your script file
196
197=head1 SYNOPSIS
198
199 $ fatpack trace myscript.pl
62ceea28 200 $ fatpack packlists-for `cat fatpacker.trace` >packlists
201 $ fatpack tree `cat packlists`
f5a54fa1 202 $ (fatpack file; cat myscript.pl) >myscript.packed.pl
203
204See the documentation for the L<fatpack> script itself for more information.
205
206The programmatic API for this code is not yet fully decided, hence the 0.9.1
207release version. Expect that to be cleaned up for 1.0.
208
209=head1 SUPPORT
210
211Your current best avenue is to come annoy annoy mst on #toolchain on
212irc.perl.org. There should be a non-IRC means of support by 1.0.
213
214=head1 AUTHOR
215
216Matt S. Trout (mst) <mst@shadowcat.co.uk>
217
218=head2 CONTRIBUTORS
219
220None as yet, though I probably owe lots of people thanks for ideas. Yet
221another doc nit to fix.
222
223=head1 COPYRIGHT
224
225Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
226as listed above.
227
228=head1 LICENSE
229
230This library is free software and may be distributed under the same terms
231as perl itself.
232
233=cut
234
48af1939 2351;