d52c4dec56e8d4ba918045e5368529e8636f62a2
[p5sagit/local-lib.git] / lib / local / lib.pm
1 use strict;
2 use warnings;
3
4 package local::lib;
5
6 use 5.008001; # 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_ENV () { 1 }
199 sub LITERAL_ENV     () { 0 }
200
201 sub print_environment_vars_for {
202   my ($class, $path) = @_;
203   my @envs = $class->build_environment_vars_for($path, LITERAL_ENV);
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_ENV);
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                   ($ENV{PERL5LIB} ?
263                     ($interpolate == INTERPOLATE_ENV
264                       ? ($ENV{PERL5LIB})
265                       : ('$PERL5LIB'))
266                     : ())
267                 ),
268     PATH => join(':',
269               $class->install_base_bin_path($path),
270               ($interpolate == INTERPOLATE_ENV
271                 ? $ENV{PATH}
272                 : '$PATH')
273              ),
274   )
275 }
276
277 =begin testing
278
279 #:: test classmethod
280
281 File::Path::rmtree('t/var/splat');
282
283 $c->ensure_dir_structure_for('t/var/splat');
284
285 ok(-d 't/var/splat');
286
287 ok(-f 't/var/splat/.modulebuildrc');
288
289 =end testing
290
291 =head1 NAME
292
293 local::lib - create and use a local lib/ for perl modules with PERL5LIB
294
295 =head1 SYNOPSIS
296
297 In code -
298
299   use local::lib; # sets up a local lib at ~/perl5
300
301   use local::lib '~/foo'; # same, but ~/foo
302
303   # Or...
304   use FindBin;
305   use local::lib "$FindBin::Bin/../support";  # app-local support library
306
307 From the shell -
308
309   $ perl -Mlocal::lib
310   export MODULEBUILDRC=/home/username/perl/.modulebuildrc
311   export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
312   export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
313   export PATH="/home/username/perl/bin:$PATH"
314
315 To bootstrap if you don't have local::lib itself installed -
316
317   <download local::lib tarball from CPAN, unpack and cd into dir>
318
319   $ perl Makefile.PL --bootstrap
320   $ make test && make install
321
322   $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
323
324   # Or for C shells...
325
326   $ /bin/csh
327   % echo $SHELL
328   /bin/csh
329   % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
330
331 You can also pass --boostrap=~/foo to get a different location (adjust the
332 bashrc / cshrc line appropriately)
333
334 =head1 DESCRIPTION
335
336 This module provides a quick, convenient way of bootstrapping a user-local Perl
337 module library located within the user's home directory. It also constructs and
338 prints out for the user the list of environment variables using the syntax
339 appropriate for the user's current shell (as specified by the C<SHELL>
340 environment variable), suitable for directly adding to one's shell configuration
341 file.
342
343 More generally, local::lib allows for the bootstrapping and usage of a directory
344 containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship
345 an application with an app-specific copy of a Perl module, or collection of
346 modules. Useful in cases like when an upstream maintainer hasn't applied a patch
347 to a module of theirs that you need for your application.
348
349 On import, local::lib sets the following environment variables to appropriate
350 values:
351
352 =over 4
353
354 =item MODULEBUILDRC
355
356 =item PERL_MM_OPT
357
358 =item PERL5LIB
359
360 =item PATH
361
362 PATH is appended to, rather than clobbered.
363
364 =back
365
366 These values are then available for reference by any code after import.
367
368 =head1 LIMITATIONS
369
370 Rather basic shell detection. Right now anything with csh in its name is
371 assumed to be a C shell or something compatible, and everything else is assumed
372 to be Bourne. If the C<SHELL> environment variable is not set, a
373 Bourne-compatible shell is assumed.
374
375 Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
376 have CPANPLUS installed.
377
378 Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
379
380 Should probably auto-fixup CPAN config if not already done.
381
382 Patches very much welcome for any of the above.
383
384 =head1 ENVIRONMENT
385
386 =over 4
387
388 =item SHELL
389
390 local::lib looks at the user's C<SHELL> environment variable when printing out
391 commands to add to the shell configuration file.
392
393 =back
394
395 =head1 AUTHOR
396
397 Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
398
399 auto_install fixes kindly sponsored by http://www.takkle.com/
400
401 =head1 CONTRIBUTORS
402
403 Patches to correctly output commands for csh style shells, as well as some
404 documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>.
405
406 =head1 LICENSE
407
408 This library is free software under the same license as perl itself
409
410 =cut
411
412 1;