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