Prep for releasing 1.001000.
[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.001000'; # 1.1.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 =begin testing
42
43 #:: test pipeline
44
45 package local::lib;
46
47 { package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
48 my $foo = bless({}, 'Foo');                                                 
49 Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
50
51 =end testing
52
53 =cut
54
55 sub resolve_path {
56   my ($class, $path) = @_;
57   $class->${pipeline qw(
58     resolve_relative_path
59     resolve_home_path
60     resolve_empty_path
61   )}($path);
62 }
63
64 sub resolve_empty_path {
65   my ($class, $path) = @_;
66   if (defined $path) {
67     $path;
68   } else {
69     '~/perl5';
70   }
71 }
72
73 =begin testing
74
75 #:: test classmethod setup
76
77 my $c = 'local::lib';
78
79 =end testing
80
81 =begin testing
82
83 #:: test classmethod
84
85 is($c->resolve_empty_path, '~/perl5');
86 is($c->resolve_empty_path('foo'), 'foo');
87
88 =end testing
89
90 =cut
91
92 sub resolve_home_path {
93   my ($class, $path) = @_;
94   return $path unless ($path =~ /^~/);
95   my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
96   my $tried_file_homedir;
97   my $homedir = do {
98     if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
99       $tried_file_homedir = 1;
100       if (defined $user) {
101         File::HomeDir->users_home($user);
102       } else {
103         File::HomeDir->my_home;
104       }
105     } else {
106       if (defined $user) {
107         (getpwnam $user)[7];
108       } else {
109         if (defined $ENV{HOME}) {
110           $ENV{HOME};
111         } else {
112           (getpwuid $<)[7];
113         }
114       }
115     }
116   };
117   unless (defined $homedir) {
118     Carp::croak(
119       "Couldn't resolve homedir for "
120       .(defined $user ? $user : 'current user')
121       .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
122     );
123   }
124   $path =~ s/^~[^\/]*/$homedir/;
125   $path;
126 }
127
128 sub resolve_relative_path {
129   my ($class, $path) = @_;
130   File::Spec->rel2abs($path);
131 }
132
133 =begin testing
134
135 #:: test classmethod
136
137 local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
138 is($c->resolve_relative_path('bar'),'FOObar');
139
140 =end testing
141
142 =cut
143
144 sub setup_local_lib_for {
145   my ($class, $path) = @_;
146   $class->ensure_dir_structure_for($path);
147   if ($0 eq '-') {
148     $class->print_environment_vars_for($path);
149     exit 0;
150   } else {
151     $class->setup_env_hash_for($path);
152     unshift(@INC, split(':', $ENV{PERL5LIB}));
153   }
154 }
155
156 sub modulebuildrc_path {
157   my ($class, $path) = @_;
158   File::Spec->catfile($path, '.modulebuildrc');
159 }
160
161 sub install_base_bin_path {
162   my ($class, $path) = @_;
163   File::Spec->catdir($path, 'bin');
164 }
165
166 sub install_base_perl_path {
167   my ($class, $path) = @_;
168   File::Spec->catdir($path, 'lib', 'perl5');
169 }
170
171 sub install_base_arch_path {
172   my ($class, $path) = @_;
173   File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
174 }
175
176 sub ensure_dir_structure_for {
177   my ($class, $path) = @_;
178   unless (-d $path) {
179     warn "Attempting to create directory ${path}\n";
180   }
181   File::Path::mkpath($path);
182   my $modulebuildrc_path = $class->modulebuildrc_path($path);
183   if (-e $modulebuildrc_path) {
184     unless (-f _) {
185       Carp::croak("${modulebuildrc_path} exists but is not a plain file");
186     }
187   } else {
188     warn "Attempting to create file ${modulebuildrc_path}\n";
189     open MODULEBUILDRC, '>', $modulebuildrc_path
190       || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
191     print MODULEBUILDRC qq{install  --install_base  ${path}\n}
192       || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
193     close MODULEBUILDRC
194       || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
195   }
196 }
197
198 sub INTERPOLATE_PATH () { 1 }
199 sub LITERAL_PATH     () { 0 }
200
201 sub print_environment_vars_for {
202   my ($class, $path) = @_;
203   my @envs = $class->build_environment_vars_for($path, LITERAL_PATH);
204   my $out = '';
205
206   # rather basic csh detection, goes on the assumption that something won't
207   # call itself csh unless it really is. also, default to bourne in the
208   # pathological situation where a user doesn't have $ENV{SHELL} defined.
209   # note also that shells with funny names, like zoid, are assumed to be
210   # bourne.
211   my $shellbin = 'sh';
212   if(defined $ENV{'SHELL'}) {
213       my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'});
214       $shellbin = $shell_bin_path_parts[-1];
215   }
216   my $shelltype = do {
217       local $_ = $shellbin;
218       if(/csh/) {
219           'csh'
220       } else {
221           'bourne'
222       }
223   };
224
225   while (@envs) {
226     my ($name, $value) = (shift(@envs), shift(@envs));
227     $value =~ s/(\\")/\\$1/g;
228     $out .= $class->${\"build_${shelltype}_env_declaration"}($name, $value);
229   }
230   print $out;
231 }
232
233 # simple routines that take two arguments: an %ENV key and a value. return
234 # strings that are suitable for passing directly to the relevant shell to set
235 # said key to said value.
236 sub build_bourne_env_declaration {
237   my $class = shift;
238   my($name, $value) = @_;
239   return qq{export ${name}="${value}"\n};
240 }
241
242 sub build_csh_env_declaration {
243   my $class = shift;
244   my($name, $value) = @_;
245   return qq{setenv ${name} "${value}"\n};
246 }
247
248 sub setup_env_hash_for {
249   my ($class, $path) = @_;
250   my %envs = $class->build_environment_vars_for($path, INTERPOLATE_PATH);
251   @ENV{keys %envs} = values %envs;
252 }
253
254 sub build_environment_vars_for {
255   my ($class, $path, $interpolate) = @_;
256   return (
257     MODULEBUILDRC => $class->modulebuildrc_path($path),
258     PERL_MM_OPT => "INSTALL_BASE=${path}",
259     PERL5LIB => join(':',
260                   $class->install_base_perl_path($path),
261                   $class->install_base_arch_path($path),
262                 ),
263     PATH => join(':',
264               $class->install_base_bin_path($path),
265               ($interpolate == INTERPOLATE_PATH
266                 ? $ENV{PATH}
267                 : '$PATH')
268              ),
269   )
270 }
271
272 =begin testing
273
274 #:: test classmethod
275
276 File::Path::rmtree('t/var/splat');
277
278 $c->ensure_dir_structure_for('t/var/splat');
279
280 ok(-d 't/var/splat');
281
282 ok(-f 't/var/splat/.modulebuildrc');
283
284 =end testing
285
286 =head1 NAME
287
288 local::lib - create and use a local lib/ for perl modules with PERL5LIB
289
290 =head1 SYNOPSIS
291
292 In code -
293
294   use local::lib; # sets up a local lib at ~/perl5
295
296   use local::lib '~/foo'; # same, but ~/foo
297
298   # Or...
299   use FindBin;
300   use local::lib "$FindBin::Bin/../support";  # app-local support library
301
302 From the shell -
303
304   $ perl -Mlocal::lib
305   export MODULEBUILDRC=/home/username/perl/.modulebuildrc
306   export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
307   export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
308   export PATH="/home/username/perl/bin:$PATH"
309
310 To bootstrap if you don't have local::lib itself installed -
311
312   $ perl -MCPAN -eshell # you only need to do this if you don't have a ~/.cpan
313   cpan> exit
314   <download local::lib tarball from CPAN, unpack and cd into dir>
315   $ perl Makefile.PL --bootstrap
316   $ make test && make install
317   $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
318   # Or for C shells...
319   $ /bin/csh
320   % echo $SHELL
321   /bin/csh
322   % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
323
324 You can also pass --boostrap=~/foo to get a different location (adjust the
325 bashrc / cshrc line appropriately)
326
327 =head1 DESCRIPTION
328
329 This module provides a quick, convenient way of bootstrapping a user-local Perl
330 module library located within the user's home directory. It also constructs and
331 prints out for the user the list of environment variables using the syntax
332 appropriate for the user's current shell (as specified by the C<SHELL>
333 environment variable), suitable for directly adding to one's shell configuration
334 file.
335
336 More generally, local::lib allows for the bootstrapping and usage of a directory
337 containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship
338 an application with an app-specific copy of a Perl module, or collection of
339 modules. Useful in cases like when an upstream maintainer hasn't applied a patch
340 to a module of theirs that you need for your application.
341
342 On import, local::lib sets the following environment variables to appropriate
343 values:
344
345 =over 4
346
347 =item MODULEBUILDRC
348
349 =item PERL_MM_OPT
350
351 =item PERL5LIB
352
353 =item PATH
354
355 PATH is appended to, rather than clobbered.
356
357 =back
358
359 These values are then available for reference by any code after import.
360
361 =head1 LIMITATIONS
362
363 Rather basic shell detection. Right now anything with csh in its name is
364 assumed to be a C shell or something compatible, and everything else is assumed
365 to be Bourne. If the C<SHELL> environment variable is not set, a
366 Bourne-compatible shell is assumed.
367
368 Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
369 have CPANPLUS installed.
370
371 Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
372
373 Should probably auto-fixup CPAN config if not already done.
374
375 Patches very much welcome for any of the above.
376
377 =head1 ENVIRONMENT
378
379 =over 4
380
381 =item SHELL
382
383 local::lib looks at the user's C<SHELL> environment variable when printing out
384 commands to add to the shell configuration file.
385
386 =back
387
388 =head1 AUTHOR
389
390 Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
391
392 auto_install fixes kindly sponsored by http://www.takkle.com/
393
394 =head1 LICENSE
395
396 This library is free software under the same license as perl itself
397
398 =cut
399
400 1;