Additional install tests from Hans Dieter Pearcey (hdp@cpan.org). Thanks!
[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.003004'; # 1.3.4
15
16 sub import {
17   my ($class, @args) = @_;
18
19   # The path is required, but last in the list, so we pop, not shift here. 
20   my $path = pop @args;
21   $path = $class->resolve_path($path);
22   $class->setup_local_lib_for($path);
23
24   # Handle the '--self-contained' option
25   my $flag = shift @args;  
26   no warnings 'uninitialized'; # the flag is optional 
27   # make sure fancy dashes cause an error
28   if ($flag =~ /−/) {
29       die <<'DEATH';
30 WHOA THERE! It looks like you've got some fancy dashes in your commandline!
31 These are *not* the traditional -- dashes that software recognizes. You
32 probably got these by copy-pasting from the perldoc for this module as
33 rendered by a UTF8-capable formatter. This most typically happens on an OS X
34 terminal, but can happen elsewhere too. Please try again after replacing the
35 dashes with normal minus signs.
36 DEATH
37   }
38   if ($flag eq '--self-contained') {
39     # The only directories that remain are those that we just defined and those where core modules are stored. 
40     @INC = ($Config::Config{privlibexp}, $Config::Config{archlibexp}, split ':', $ENV{PERL5LIB});
41   }
42   elsif (defined $flag) {
43       die "unrecognized import argument: $flag";
44   }
45
46 }
47
48 sub pipeline;
49
50 sub pipeline {
51   my @methods = @_;
52   my $last = pop(@methods);
53   if (@methods) {
54     \sub {
55       my ($obj, @args) = @_;
56       $obj->${pipeline @methods}(
57         $obj->$last(@args)
58       );
59     };
60   } else {
61     \sub {
62       shift->$last(@_);
63     };
64   }
65 }
66
67 =begin testing
68
69 #:: test pipeline
70
71 package local::lib;
72
73 { package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
74 my $foo = bless({}, 'Foo');                                                 
75 Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
76
77 =end testing
78
79 =cut
80
81 sub resolve_path {
82   my ($class, $path) = @_;
83   $class->${pipeline qw(
84     resolve_relative_path
85     resolve_home_path
86     resolve_empty_path
87   )}($path);
88 }
89
90 sub resolve_empty_path {
91   my ($class, $path) = @_;
92   if (defined $path) {
93     $path;
94   } else {
95     '~/perl5';
96   }
97 }
98
99 =begin testing
100
101 #:: test classmethod setup
102
103 my $c = 'local::lib';
104
105 =end testing
106
107 =begin testing
108
109 #:: test classmethod
110
111 is($c->resolve_empty_path, '~/perl5');
112 is($c->resolve_empty_path('foo'), 'foo');
113
114 =end testing
115
116 =cut
117
118 sub resolve_home_path {
119   my ($class, $path) = @_;
120   return $path unless ($path =~ /^~/);
121   my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
122   my $tried_file_homedir;
123   my $homedir = do {
124     if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
125       $tried_file_homedir = 1;
126       if (defined $user) {
127         File::HomeDir->users_home($user);
128       } else {
129         File::HomeDir->my_home;
130       }
131     } else {
132       if (defined $user) {
133         (getpwnam $user)[7];
134       } else {
135         if (defined $ENV{HOME}) {
136           $ENV{HOME};
137         } else {
138           (getpwuid $<)[7];
139         }
140       }
141     }
142   };
143   unless (defined $homedir) {
144     Carp::croak(
145       "Couldn't resolve homedir for "
146       .(defined $user ? $user : 'current user')
147       .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
148     );
149   }
150   $path =~ s/^~[^\/]*/$homedir/;
151   $path;
152 }
153
154 sub resolve_relative_path {
155   my ($class, $path) = @_;
156   File::Spec->rel2abs($path);
157 }
158
159 =begin testing
160
161 #:: test classmethod
162
163 local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
164 is($c->resolve_relative_path('bar'),'FOObar');
165
166 =end testing
167
168 =cut
169
170 sub setup_local_lib_for {
171   my ($class, $path) = @_;
172   $class->ensure_dir_structure_for($path);
173   if ($0 eq '-') {
174     $class->print_environment_vars_for($path);
175     exit 0;
176   } else {
177     $class->setup_env_hash_for($path);
178     unshift(@INC, split(':', $ENV{PERL5LIB}));
179   }
180 }
181
182 sub modulebuildrc_path {
183   my ($class, $path) = @_;
184   File::Spec->catfile($path, '.modulebuildrc');
185 }
186
187 sub install_base_bin_path {
188   my ($class, $path) = @_;
189   File::Spec->catdir($path, 'bin');
190 }
191
192 sub install_base_perl_path {
193   my ($class, $path) = @_;
194   File::Spec->catdir($path, 'lib', 'perl5');
195 }
196
197 sub install_base_arch_path {
198   my ($class, $path) = @_;
199   File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
200 }
201
202 sub ensure_dir_structure_for {
203   my ($class, $path) = @_;
204   unless (-d $path) {
205     warn "Attempting to create directory ${path}\n";
206   }
207   File::Path::mkpath($path);
208   my $modulebuildrc_path = $class->modulebuildrc_path($path);
209   if (-e $modulebuildrc_path) {
210     unless (-f _) {
211       Carp::croak("${modulebuildrc_path} exists but is not a plain file");
212     }
213   } else {
214     warn "Attempting to create file ${modulebuildrc_path}\n";
215     open MODULEBUILDRC, '>', $modulebuildrc_path
216       || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
217     print MODULEBUILDRC qq{install  --install_base  ${path}\n}
218       || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
219     close MODULEBUILDRC
220       || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
221   }
222 }
223
224 sub INTERPOLATE_ENV () { 1 }
225 sub LITERAL_ENV     () { 0 }
226
227 sub print_environment_vars_for {
228   my ($class, $path) = @_;
229   my @envs = $class->build_environment_vars_for($path, LITERAL_ENV);
230   my $out = '';
231
232   # rather basic csh detection, goes on the assumption that something won't
233   # call itself csh unless it really is. also, default to bourne in the
234   # pathological situation where a user doesn't have $ENV{SHELL} defined.
235   # note also that shells with funny names, like zoid, are assumed to be
236   # bourne.
237   my $shellbin = 'sh';
238   if(defined $ENV{'SHELL'}) {
239       my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'});
240       $shellbin = $shell_bin_path_parts[-1];
241   }
242   my $shelltype = do {
243       local $_ = $shellbin;
244       if(/csh/) {
245           'csh'
246       } else {
247           'bourne'
248       }
249   };
250
251   while (@envs) {
252     my ($name, $value) = (shift(@envs), shift(@envs));
253     $value =~ s/(\\")/\\$1/g;
254     $out .= $class->${\"build_${shelltype}_env_declaration"}($name, $value);
255   }
256   print $out;
257 }
258
259 # simple routines that take two arguments: an %ENV key and a value. return
260 # strings that are suitable for passing directly to the relevant shell to set
261 # said key to said value.
262 sub build_bourne_env_declaration {
263   my $class = shift;
264   my($name, $value) = @_;
265   return qq{export ${name}="${value}"\n};
266 }
267
268 sub build_csh_env_declaration {
269   my $class = shift;
270   my($name, $value) = @_;
271   return qq{setenv ${name} "${value}"\n};
272 }
273
274 sub setup_env_hash_for {
275   my ($class, $path) = @_;
276   my %envs = $class->build_environment_vars_for($path, INTERPOLATE_ENV);
277   @ENV{keys %envs} = values %envs;
278 }
279
280 sub build_environment_vars_for {
281   my ($class, $path, $interpolate) = @_;
282   return (
283     MODULEBUILDRC => $class->modulebuildrc_path($path),
284     PERL_MM_OPT => "INSTALL_BASE=${path}",
285     PERL5LIB => join(':',
286                   $class->install_base_perl_path($path),
287                   $class->install_base_arch_path($path),
288                   ($ENV{PERL5LIB} ?
289                     ($interpolate == INTERPOLATE_ENV
290                       ? ($ENV{PERL5LIB})
291                       : ('$PERL5LIB'))
292                     : ())
293                 ),
294     PATH => join(':',
295               $class->install_base_bin_path($path),
296               ($interpolate == INTERPOLATE_ENV
297                 ? $ENV{PATH}
298                 : '$PATH')
299              ),
300   )
301 }
302
303 =begin testing
304
305 #:: test classmethod
306
307 File::Path::rmtree('t/var/splat');
308
309 $c->ensure_dir_structure_for('t/var/splat');
310
311 ok(-d 't/var/splat');
312
313 ok(-f 't/var/splat/.modulebuildrc');
314
315 =end testing
316
317 =head1 NAME
318
319 local::lib - create and use a local lib/ for perl modules with PERL5LIB
320
321 =head1 SYNOPSIS
322
323 In code -
324
325   use local::lib; # sets up a local lib at ~/perl5
326
327   use local::lib '~/foo'; # same, but ~/foo
328
329   # Or...
330   use FindBin;
331   use local::lib "$FindBin::Bin/../support";  # app-local support library
332
333 From the shell -
334
335   # Install LWP and it's missing dependencies to the 'my_lwp' directory
336   perl -MCPAN -Mlocal::lib=my_lwp -e 'CPAN::install(LWP)'
337
338   # Install LWP and *all non-core* dependencies to the 'my_lwp' directory 
339   perl -MCPAN -Mlocal::lib=--self-contained,my_lwp -e 'CPAN::install(LWP)'
340
341   # Just print out useful shell commands
342   $ perl -Mlocal::lib
343   export MODULEBUILDRC=/home/username/perl/.modulebuildrc
344   export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
345   export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
346   export PATH="/home/username/perl/bin:$PATH"
347
348 To bootstrap if you don't have local::lib itself installed -
349
350   <download local::lib tarball from CPAN, unpack and cd into dir>
351
352   $ perl Makefile.PL --bootstrap
353   $ make test && make install
354
355   $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
356
357   # Or for C shells...
358
359   $ /bin/csh
360   % echo $SHELL
361   /bin/csh
362   % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
363
364 You can also pass --boostrap=~/foo to get a different location -
365
366   $ perl Makefile.PL --bootstrap=~/foo
367   $ make test && make install
368
369   $ echo 'eval $(perl -I$HOME/foo/lib/perl5 -Mlocal::lib=$HOME/foo)' >>~/.bashrc
370
371 If you want to install multiple Perl module environments, say for application evelopment, 
372 install local::lib globally and then:
373
374     $ cd ~/mydir1
375     $ perl -Mlocal::lib=./
376     $ eval $(perl -Mlocal::lib=./)  ### To set the environment for this shell alone
377     $ printenv  ### You will see that ~/mydir1 is in the PERL5LIB
378     $ perl -MCPAN -e install ...    ### whatever modules you want
379     $ cd ../mydir2
380     ... REPEAT ...
381
382 For multiple environments for multiple apps you may need to include a modified version of 
383 the C<< use FindBin >> instructions in the "In code" sample above. If you did something like
384 the above, you have a set of Perl modules at C<< ~/mydir1/lib >>. If you have a script at
385 C<< ~/mydir1/scripts/myscript.pl >>, you need to tell it where to find the modules you installed 
386 for it at C<< ~/mydir1/lib>>. 
387
388 In C<< ~/mydir1/scripts/myscript.pl >>:
389
390     use strict;
391     use warnings;
392     use local::lib "$FindBin::Bin/..";  ### points to ~/mydir1 and local::lib finds lib
393     use lib "$FindBin::Bin/../lib";     ### points to ~/mydir1/lib
394
395 Put this before any BEGIN { ... } blocks that require the modules you installed.
396
397 =head1 DESCRIPTION
398
399 This module provides a quick, convenient way of bootstrapping a user-local Perl
400 module library located within the user's home directory. It also constructs and
401 prints out for the user the list of environment variables using the syntax
402 appropriate for the user's current shell (as specified by the C<SHELL>
403 environment variable), suitable for directly adding to one's shell configuration
404 file.
405
406 More generally, local::lib allows for the bootstrapping and usage of a directory
407 containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship
408 an application with an app-specific copy of a Perl module, or collection of
409 modules. Useful in cases like when an upstream maintainer hasn't applied a patch
410 to a module of theirs that you need for your application.
411
412 On import, local::lib sets the following environment variables to appropriate
413 values:
414
415 =over 4
416
417 =item MODULEBUILDRC
418
419 =item PERL_MM_OPT
420
421 =item PERL5LIB
422
423 =item PATH
424
425 PATH is appended to, rather than clobbered.
426
427 =back
428
429 These values are then available for reference by any code after import.
430
431 =head1 METHODS
432
433 =head2 ensure_directory_structure_for
434
435 =over 4
436
437 =item Arguments: path
438
439 =back
440
441 Attempts to create the given path, and all required parent directories. Throws
442 an exception on failure.
443
444 =head2 print_environment_vars_for
445
446 =over 4
447
448 =item Arguments: path
449
450 =back
451
452 Prints to standard output the variables listed above, properly set to use the
453 given path as the base directory.
454
455 =head2 setup_env_hash_for
456
457 =over 4
458
459 =item Arguments: path
460
461 =back
462
463 Constructs the C<%ENV> keys for the given path, by calling
464 C<build_environment_vars_for>.
465
466 =head2 install_base_perl_path
467
468 =over 4
469
470 =item Arguments: path
471
472 =back
473
474 Returns a path describing where to install the Perl modules for this local
475 library installation. Appends the directories C<lib> and C<perl5> to the given
476 path.
477
478 =head2 install_base_arch_path
479
480 =over 4
481
482 =item Arguments: path
483
484 =back
485
486 Returns a path describing where to install the architecture-specific Perl
487 modules for this local library installation. Based on the
488 L</install_base_perl_path> method's return value, and appends the value of
489 C<$Config{archname}>.
490
491 =head2 install_base_bin_path
492
493 =over 4
494
495 =item Arguments: path
496
497 =back
498
499 Returns a path describing where to install the executable programs for this
500 local library installation. Based on the L</install_base_perl_path> method's
501 return value, and appends the directory C<bin>.
502
503 =head2 modulebuildrc_path
504
505 =over 4
506
507 =item Arguments: path
508
509 =back
510
511 Returns a path describing where to install the C<.modulebuildrc> file, based on
512 the given path.
513
514 =head2 resolve_empty_path
515
516 =over 4
517
518 =item Arguments: path
519
520 =back
521
522 Builds and returns the base path into which to set up the local module
523 installation. Defaults to C<~/perl5>.
524
525 =head2 resolve_home_path
526
527 =over 4
528
529 =item Arguments: path
530
531 =back
532
533 Attempts to find the user's home directory. If installed, uses C<File::HomeDir>
534 for this purpose. If no definite answer is available, throws an exception.
535
536 =head2 resolve_relative_path
537
538 =over 4
539
540 =item Arguments: path
541
542 =back
543
544 Translates the given path into an absolute path.
545
546 =head2 resolve_path
547
548 =over 4
549
550 =item Arguments: path
551
552 =back
553
554 Calls the following in a pipeline, passing the result from the previous to the
555 next, in an attempt to find where to configure the environment for a local
556 library installation: L</resolve_empty_path>, L</resolve_home_path>,
557 L</resolve_relative_path>. Passes the given path argument to
558 L</resolve_empty_path> which then returns a result that is passed to
559 L</resolve_home_path>, which then has its result passed to
560 L</resolve_relative_path>. The result of this final call is returned from
561 L</resolve_path>.
562
563 =head1 A WARNING ABOUT UNINST=1
564
565 Be careful about using local::lib in combination with "make install UNINST=1".
566 The idea of this feature is that will uninstall an old version of a module
567 before installing a new one. However it lacks a safety check that the old
568 version and the new version will go in the same directory. Used in combination
569 with local::lib, you can potentially delete a globally accessible version of a
570 module while installing the new version in a local place. Only combine "make
571 install UNINST=1" and local::lib if you understand these possible consequences.
572
573 =head1 LIMITATIONS
574
575 Rather basic shell detection. Right now anything with csh in its name is
576 assumed to be a C shell or something compatible, and everything else is assumed
577 to be Bourne. If the C<SHELL> environment variable is not set, a
578 Bourne-compatible shell is assumed.
579
580 Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
581 have CPANPLUS installed.
582
583 Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
584
585 Should probably auto-fixup CPAN config if not already done.
586
587 Patches very much welcome for any of the above.
588
589 =head1 ENVIRONMENT
590
591 =over 4
592
593 =item SHELL
594
595 local::lib looks at the user's C<SHELL> environment variable when printing out
596 commands to add to the shell configuration file.
597
598 =back
599
600 =head1 AUTHOR
601
602 Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
603
604 auto_install fixes kindly sponsored by http://www.takkle.com/
605
606 =head1 CONTRIBUTORS
607
608 Patches to correctly output commands for csh style shells, as well as some
609 documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>.
610
611 '--self-contained' feature contributed by Mark Stosberg <mark@summersault.com>.
612
613 Doc patches for a custom local::lib patch contributed by Torsten Raudssus
614 <torsten@raudssus.de>.
615
616 Hans Dieter Pearcey <hdp@cpan.org> sent in some additional tests for ensuring
617 things will install properly.
618
619 =head1 LICENSE
620
621 This library is free software under the same license as perl itself
622
623 =cut
624
625 1;