Bail if given fancy dashes due to copypasting from a UTF8-happy POD formatter.
[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.003001'; # 1.3.1
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 (adjust the
365 bashrc / cshrc line appropriately)
366
367 =head1 DESCRIPTION
368
369 This module provides a quick, convenient way of bootstrapping a user-local Perl
370 module library located within the user's home directory. It also constructs and
371 prints out for the user the list of environment variables using the syntax
372 appropriate for the user's current shell (as specified by the C<SHELL>
373 environment variable), suitable for directly adding to one's shell configuration
374 file.
375
376 More generally, local::lib allows for the bootstrapping and usage of a directory
377 containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship
378 an application with an app-specific copy of a Perl module, or collection of
379 modules. Useful in cases like when an upstream maintainer hasn't applied a patch
380 to a module of theirs that you need for your application.
381
382 On import, local::lib sets the following environment variables to appropriate
383 values:
384
385 =over 4
386
387 =item MODULEBUILDRC
388
389 =item PERL_MM_OPT
390
391 =item PERL5LIB
392
393 =item PATH
394
395 PATH is appended to, rather than clobbered.
396
397 =back
398
399 These values are then available for reference by any code after import.
400
401 =head1 A WARNING ABOUT UNINST=1
402
403 Be careful about using local::lib in combination with "make install UNINST=1".
404 The idea of this feature is that will uninstall an old version of a module
405 before installing a new one. However it lacks a safety check that the old
406 version and the new version will go in the same directory. Used in combination
407 with local::lib, you can potentially delete a globally accessible version of a
408 module while installing the new version in a local place. Only combine "make
409 install UNINST=1" and local::lib if you understand these possible consequences.
410
411 =head1 LIMITATIONS
412
413 Rather basic shell detection. Right now anything with csh in its name is
414 assumed to be a C shell or something compatible, and everything else is assumed
415 to be Bourne. If the C<SHELL> environment variable is not set, a
416 Bourne-compatible shell is assumed.
417
418 Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
419 have CPANPLUS installed.
420
421 Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
422
423 Should probably auto-fixup CPAN config if not already done.
424
425 Patches very much welcome for any of the above.
426
427 =head1 ENVIRONMENT
428
429 =over 4
430
431 =item SHELL
432
433 local::lib looks at the user's C<SHELL> environment variable when printing out
434 commands to add to the shell configuration file.
435
436 =back
437
438 =head1 AUTHOR
439
440 Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
441
442 auto_install fixes kindly sponsored by http://www.takkle.com/
443
444 =head1 CONTRIBUTORS
445
446 Patches to correctly output commands for csh style shells, as well as some
447 documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>.
448
449 '--self-contained' feature contributed by Mark Stosberg <mark@summersault.com>.
450
451 =head1 LICENSE
452
453 This library is free software under the same license as perl itself
454
455 =cut
456
457 1;