o Add a =head1 DESCRIPTION. This fixes installation using FreeBSD's BSDPAN
[p5sagit/local-lib.git] / lib / local / lib.pm
1 use strict;
2 use warnings;
3
4 package local::lib;
5
6 use 5.8.1; # probably works with earlier versions but I'm not supporting them
7            # (patches would, of course, be welcome)
8
9 use File::Spec ();
10 use File::Path ();
11 use Carp ();
12 use Config;
13
14 our $VERSION = '1.000000'; # 1.0.0
15
16 sub import {
17   my ($class, $path) = @_;
18   $path = $class->resolve_path($path);
19   $class->setup_local_lib_for($path);
20 }
21
22 sub pipeline;
23
24 sub pipeline {
25   my @methods = @_;
26   my $last = pop(@methods);
27   if (@methods) {
28     \sub {
29       my ($obj, @args) = @_;
30       $obj->${pipeline @methods}(
31         $obj->$last(@args)
32       );
33     };
34   } else {
35     \sub {
36       shift->$last(@_);
37     };
38   }
39 }
40
41 =for test pipeline
42
43 package local::lib;
44
45 { package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
46 my $foo = bless({}, 'Foo');                                                 
47 Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
48
49 =cut
50
51 sub resolve_path {
52   my ($class, $path) = @_;
53   $class->${pipeline qw(
54     resolve_relative_path
55     resolve_home_path
56     resolve_empty_path
57   )}($path);
58 }
59
60 sub resolve_empty_path {
61   my ($class, $path) = @_;
62   if (defined $path) {
63     $path;
64   } else {
65     '~/perl5';
66   }
67 }
68
69 =for test classmethod setup
70
71 my $c = 'local::lib';
72
73 =cut
74
75 =for test classmethod
76
77 is($c->resolve_empty_path, '~/perl5');
78 is($c->resolve_empty_path('foo'), 'foo');
79
80 =cut
81
82 sub resolve_home_path {
83   my ($class, $path) = @_;
84   return $path unless ($path =~ /^~/);
85   my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
86   my $tried_file_homedir;
87   my $homedir = do {
88     if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
89       $tried_file_homedir = 1;
90       if (defined $user) {
91         File::HomeDir->users_home($user);
92       } else {
93         File::HomeDir->my_home;
94       }
95     } else {
96       if (defined $user) {
97         (getpwnam $user)[7];
98       } else {
99         if (defined $ENV{HOME}) {
100           $ENV{HOME};
101         } else {
102           (getpwuid $<)[7];
103         }
104       }
105     }
106   };
107   unless (defined $homedir) {
108     Carp::croak(
109       "Couldn't resolve homedir for "
110       .(defined $user ? $user : 'current user')
111       .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
112     );
113   }
114   $path =~ s/^~[^\/]*/$homedir/;
115   $path;
116 }
117
118 sub resolve_relative_path {
119   my ($class, $path) = @_;
120   File::Spec->rel2abs($path);
121 }
122
123 =for test classmethod
124
125 local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
126 is($c->resolve_relative_path('bar'),'FOObar');
127
128 =cut
129
130 sub setup_local_lib_for {
131   my ($class, $path) = @_;
132   $class->ensure_dir_structure_for($path);
133   if ($0 eq '-') {
134     $class->print_environment_vars_for($path);
135     exit 0;
136   } else {
137     $class->setup_env_hash_for($path);
138     unshift(@INC, split(':', $ENV{PERL5LIB}));
139   }
140 }
141
142 sub modulebuildrc_path {
143   my ($class, $path) = @_;
144   File::Spec->catfile($path, '.modulebuildrc');
145 }
146
147 sub install_base_bin_path {
148   my ($class, $path) = @_;
149   File::Spec->catdir($path, 'bin');
150 }
151
152 sub install_base_perl_path {
153   my ($class, $path) = @_;
154   File::Spec->catdir($path, 'lib', 'perl5');
155 }
156
157 sub install_base_arch_path {
158   my ($class, $path) = @_;
159   File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
160 }
161
162 sub ensure_dir_structure_for {
163   my ($class, $path) = @_;
164   unless (-d $path) {
165     warn "Attempting to create directory ${path}\n";
166   }
167   File::Path::mkpath($path);
168   my $modulebuildrc_path = $class->modulebuildrc_path($path);
169   if (-e $modulebuildrc_path) {
170     unless (-f _) {
171       Carp::croak("${modulebuildrc_path} exists but is not a plain file");
172     }
173   } else {
174     warn "Attempting to create file ${modulebuildrc_path}\n";
175     open MODULEBUILDRC, '>', $modulebuildrc_path
176       || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
177     print MODULEBUILDRC qq{install  --install_base  ${path}\n}
178       || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
179     close MODULEBUILDRC
180       || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
181   }
182 }
183
184 sub INTERPOLATE_PATH () { 1 }
185 sub LITERAL_PATH     () { 0 }
186
187 sub print_environment_vars_for {
188   my ($class, $path) = @_;
189   my @envs = $class->build_environment_vars_for($path, LITERAL_PATH);
190   my $out = '';
191   while (@envs) {
192     my ($name, $value) = (shift(@envs), shift(@envs));
193     $value =~ s/(\\")/\\$1/g;
194
195     # rather basic csh detection, goes on the assumption that something won't
196     # call itself csh unless it really is. also, default to bourne in the
197     # pathological situation where a user doesn't have $ENV{SHELL} defined.
198     # note also that shells with funny names, like zoid, are assumed to be
199     # bourne.
200     my $shellbin = 'sh';
201     if(defined $ENV{'SHELL'}) {
202       my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'});
203       $shellbin = $shell_bin_path_parts[-1];
204     }
205     if($shellbin =~ /csh/) {
206       $out .= qq{setenv ${name} "${value}"\n};
207     }
208     else {
209       $out .= qq{export ${name}="${value}"\n};
210     }
211   }
212   print $out;
213 }
214
215 sub setup_env_hash_for {
216   my ($class, $path) = @_;
217   my %envs = $class->build_environment_vars_for($path, INTERPOLATE_PATH);
218   @ENV{keys %envs} = values %envs;
219 }
220
221 sub build_environment_vars_for {
222   my ($class, $path, $interpolate) = @_;
223   return (
224     MODULEBUILDRC => $class->modulebuildrc_path($path),
225     PERL_MM_OPT => "INSTALL_BASE=${path}",
226     PERL5LIB => join(':',
227                   $class->install_base_perl_path($path),
228                   $class->install_base_arch_path($path),
229                 ),
230     PATH => join(':',
231               $class->install_base_bin_path($path),
232               ($interpolate == INTERPOLATE_PATH
233                 ? $ENV{PATH}
234                 : '$PATH')
235              ),
236   )
237 }
238
239 =for test classmethod
240
241 File::Path::rmtree('t/var/splat');
242
243 $c->ensure_dir_structure_for('t/var/splat');
244
245 ok(-d 't/var/splat');
246
247 ok(-f 't/var/splat/.modulebuildrc');
248
249 =head1 NAME
250
251 local::lib - create and use a local lib/ for perl modules with PERL5LIB
252
253 =head1 SYNOPSIS
254
255 In code -
256
257   use local::lib; # sets up a local lib at ~/perl5
258
259   use local::lib '~/foo'; # same, but ~/foo
260
261 From the shell -
262
263   $ perl -Mlocal::lib
264   export MODULEBUILDRC=/home/username/perl/.modulebuildrc
265   export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
266   export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
267   export PATH="/home/username/perl/bin:$PATH"
268
269 To bootstrap if you don't have local::lib itself installed -
270
271   $ perl -MCPAN -eshell # you only need to do this if you don't have a ~/.cpan
272   cpan> exit
273   <download local::lib tarball from CPAN, unpack and cd into dir>
274   $ perl Makefile.PL --bootstrap
275   $ make test && make install
276   $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
277   # Or for C shells...
278   $ /bin/csh
279   % echo $SHELL
280   /bin/csh
281   % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
282
283 You can also pass --boostrap=~/foo to get a different location (adjust the
284 bashrc / cshrc line appropriately)
285
286 =head1 DESCRIPTION
287
288 This module provides a quick, convenient way of bootstrapping a user-local Perl
289 module library located within the user's home directory. It also constructs and
290 prints out for the user the list of environment variables using the syntax
291 appropriate for the user's current shell (as specified by the C<SHELL>
292 environment variable), suitable for directly adding to one's shell configuration
293 file.
294
295 =head1 LIMITATIONS
296
297 Rather basic shell detection. Right now anything with csh in its name is
298 assumed to be a C shell or something compatible, and everything else is assumed
299 to be Bourne.
300
301 Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
302 have CPANPLUS installed.
303
304 Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
305
306 Should probably auto-fixup CPAN config if not already done.
307
308 Patches very much welcome for any of the above.
309
310 =head1 ENVIRONMENT
311
312 =over 4
313
314 =item SHELL
315
316 local::lib looks at the user's C<SHELL> environment variable when printing out
317 commands to add to the shell configuration file.
318
319 =back
320
321 =head1 AUTHOR
322
323 Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
324
325 =head1 LICENSE
326
327 This library is free software under the same license as perl itself
328
329 =cut
330
331 1;