Add a doc patch from Torsten Raudssus <torsten@raudssus.de> that clarifies how
[p5sagit/local-lib.git] / lib / local / lib.pm
CommitLineData
b5cc15f7 1use strict;
2use warnings;
3
4package local::lib;
5
c1441fb6 6use 5.008001; # probably works with earlier versions but I'm not supporting them
7 # (patches would, of course, be welcome)
b5cc15f7 8
9use File::Spec ();
10use File::Path ();
11use Carp ();
12use Config;
13
8b1e8e69 14our $VERSION = '1.003002'; # 1.3.2
b5cc15f7 15
16sub import {
0fb70b9a 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;
b5cc15f7 21 $path = $class->resolve_path($path);
22 $class->setup_local_lib_for($path);
0fb70b9a 23
24 # Handle the '--self-contained' option
25 my $flag = shift @args;
26 no warnings 'uninitialized'; # the flag is optional
d4dbe584 27 # make sure fancy dashes cause an error
28 if ($flag =~ /−/) {
29 die <<'DEATH';
30WHOA THERE! It looks like you've got some fancy dashes in your commandline!
31These are *not* the traditional -- dashes that software recognizes. You
32probably got these by copy-pasting from the perldoc for this module as
33rendered by a UTF8-capable formatter. This most typically happens on an OS X
34terminal, but can happen elsewhere too. Please try again after replacing the
35dashes with normal minus signs.
36DEATH
37 }
0fb70b9a 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
b5cc15f7 46}
47
5b94dce5 48sub pipeline;
b5cc15f7 49
5b94dce5 50sub pipeline {
b5cc15f7 51 my @methods = @_;
52 my $last = pop(@methods);
53 if (@methods) {
54 \sub {
55 my ($obj, @args) = @_;
5b94dce5 56 $obj->${pipeline @methods}(
b5cc15f7 57 $obj->$last(@args)
58 );
59 };
60 } else {
61 \sub {
62 shift->$last(@_);
63 };
64 }
65}
66
275c9dae 67=begin testing
68
69#:: test pipeline
b5cc15f7 70
71package local::lib;
72
73{ package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
74my $foo = bless({}, 'Foo');
4c375968 75Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
b5cc15f7 76
275c9dae 77=end testing
78
b5cc15f7 79=cut
80
81sub resolve_path {
82 my ($class, $path) = @_;
5b94dce5 83 $class->${pipeline qw(
b5cc15f7 84 resolve_relative_path
85 resolve_home_path
86 resolve_empty_path
87 )}($path);
88}
89
90sub resolve_empty_path {
91 my ($class, $path) = @_;
92 if (defined $path) {
93 $path;
94 } else {
95 '~/perl5';
96 }
97}
98
275c9dae 99=begin testing
100
101#:: test classmethod setup
b5cc15f7 102
103my $c = 'local::lib';
104
275c9dae 105=end testing
106
107=begin testing
b5cc15f7 108
275c9dae 109#:: test classmethod
b5cc15f7 110
111is($c->resolve_empty_path, '~/perl5');
112is($c->resolve_empty_path('foo'), 'foo');
113
275c9dae 114=end testing
115
b5cc15f7 116=cut
117
118sub 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 {
dc8ddd06 129 File::HomeDir->my_home;
b5cc15f7 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
154sub resolve_relative_path {
155 my ($class, $path) = @_;
156 File::Spec->rel2abs($path);
157}
158
275c9dae 159=begin testing
160
161#:: test classmethod
b5cc15f7 162
163local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
164is($c->resolve_relative_path('bar'),'FOObar');
165
275c9dae 166=end testing
167
b5cc15f7 168=cut
169
170sub 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);
f9c6b7ff 178 unshift(@INC, split(':', $ENV{PERL5LIB}));
b5cc15f7 179 }
180}
181
182sub modulebuildrc_path {
183 my ($class, $path) = @_;
184 File::Spec->catfile($path, '.modulebuildrc');
185}
186
187sub install_base_bin_path {
188 my ($class, $path) = @_;
189 File::Spec->catdir($path, 'bin');
190}
191
192sub install_base_perl_path {
193 my ($class, $path) = @_;
194 File::Spec->catdir($path, 'lib', 'perl5');
195}
196
197sub install_base_arch_path {
198 my ($class, $path) = @_;
199 File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
200}
201
202sub 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: $!");
18bb63e0 217 print MODULEBUILDRC qq{install --install_base ${path}\n}
b5cc15f7 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
c2447f35 224sub INTERPOLATE_ENV () { 1 }
225sub LITERAL_ENV () { 0 }
b5cc15f7 226
227sub print_environment_vars_for {
228 my ($class, $path) = @_;
c2447f35 229 my @envs = $class->build_environment_vars_for($path, LITERAL_ENV);
b5cc15f7 230 my $out = '';
1bc71e56 231
0353dbc0 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 }
1bc71e56 242 my $shelltype = do {
243 local $_ = $shellbin;
b42496e0 244 if(/csh/) {
1bc71e56 245 'csh'
b42496e0 246 } else {
1bc71e56 247 'bourne'
248 }
249 };
250
b5cc15f7 251 while (@envs) {
252 my ($name, $value) = (shift(@envs), shift(@envs));
253 $value =~ s/(\\")/\\$1/g;
1bc71e56 254 $out .= $class->${\"build_${shelltype}_env_declaration"}($name, $value);
b5cc15f7 255 }
256 print $out;
257}
258
1bc71e56 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.
262sub build_bourne_env_declaration {
263 my $class = shift;
264 my($name, $value) = @_;
265 return qq{export ${name}="${value}"\n};
266}
267
268sub build_csh_env_declaration {
269 my $class = shift;
270 my($name, $value) = @_;
271 return qq{setenv ${name} "${value}"\n};
272}
273
b5cc15f7 274sub setup_env_hash_for {
275 my ($class, $path) = @_;
c2447f35 276 my %envs = $class->build_environment_vars_for($path, INTERPOLATE_ENV);
b5cc15f7 277 @ENV{keys %envs} = values %envs;
278}
279
280sub 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),
c2447f35 288 ($ENV{PERL5LIB} ?
289 ($interpolate == INTERPOLATE_ENV
290 ? ($ENV{PERL5LIB})
291 : ('$PERL5LIB'))
292 : ())
b5cc15f7 293 ),
294 PATH => join(':',
295 $class->install_base_bin_path($path),
c2447f35 296 ($interpolate == INTERPOLATE_ENV
b5cc15f7 297 ? $ENV{PATH}
298 : '$PATH')
299 ),
300 )
301}
302
275c9dae 303=begin testing
304
305#:: test classmethod
b5cc15f7 306
307File::Path::rmtree('t/var/splat');
308
4c375968 309$c->ensure_dir_structure_for('t/var/splat');
b5cc15f7 310
311ok(-d 't/var/splat');
312
313ok(-f 't/var/splat/.modulebuildrc');
314
275c9dae 315=end testing
316
b5cc15f7 317=head1 NAME
318
319local::lib - create and use a local lib/ for perl modules with PERL5LIB
320
321=head1 SYNOPSIS
322
323In code -
324
325 use local::lib; # sets up a local lib at ~/perl5
326
327 use local::lib '~/foo'; # same, but ~/foo
328
1bc71e56 329 # Or...
330 use FindBin;
331 use local::lib "$FindBin::Bin/../support"; # app-local support library
332
b5cc15f7 333From the shell -
334
0fb70b9a 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
b5cc15f7 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
bc30e1d5 348To bootstrap if you don't have local::lib itself installed -
349
e423efce 350 <download local::lib tarball from CPAN, unpack and cd into dir>
715c31a0 351
bc30e1d5 352 $ perl Makefile.PL --bootstrap
353 $ make test && make install
715c31a0 354
dc8ddd06 355 $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
715c31a0 356
618272fe 357 # Or for C shells...
715c31a0 358
618272fe 359 $ /bin/csh
360 % echo $SHELL
361 /bin/csh
362 % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
dc8ddd06 363
8b1e8e69 364You 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
618272fe 370
371=head1 DESCRIPTION
372
373This module provides a quick, convenient way of bootstrapping a user-local Perl
374module library located within the user's home directory. It also constructs and
375prints out for the user the list of environment variables using the syntax
376appropriate for the user's current shell (as specified by the C<SHELL>
377environment variable), suitable for directly adding to one's shell configuration
378file.
dc8ddd06 379
1bc71e56 380More generally, local::lib allows for the bootstrapping and usage of a directory
381containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship
382an application with an app-specific copy of a Perl module, or collection of
383modules. Useful in cases like when an upstream maintainer hasn't applied a patch
384to a module of theirs that you need for your application.
385
386On import, local::lib sets the following environment variables to appropriate
387values:
388
389=over 4
390
391=item MODULEBUILDRC
392
393=item PERL_MM_OPT
394
395=item PERL5LIB
396
397=item PATH
398
399PATH is appended to, rather than clobbered.
400
401=back
402
403These values are then available for reference by any code after import.
404
0fb70b9a 405=head1 A WARNING ABOUT UNINST=1
406
407Be careful about using local::lib in combination with "make install UNINST=1".
408The idea of this feature is that will uninstall an old version of a module
409before installing a new one. However it lacks a safety check that the old
410version and the new version will go in the same directory. Used in combination
411with local::lib, you can potentially delete a globally accessible version of a
381738d7 412module while installing the new version in a local place. Only combine "make
0fb70b9a 413install UNINST=1" and local::lib if you understand these possible consequences.
414
dc8ddd06 415=head1 LIMITATIONS
416
618272fe 417Rather basic shell detection. Right now anything with csh in its name is
418assumed to be a C shell or something compatible, and everything else is assumed
1bc71e56 419to be Bourne. If the C<SHELL> environment variable is not set, a
420Bourne-compatible shell is assumed.
dc8ddd06 421
422Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
423have CPANPLUS installed.
424
425Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
426
e423efce 427Should probably auto-fixup CPAN config if not already done.
428
dc8ddd06 429Patches very much welcome for any of the above.
bc30e1d5 430
618272fe 431=head1 ENVIRONMENT
432
433=over 4
434
435=item SHELL
436
437local::lib looks at the user's C<SHELL> environment variable when printing out
438commands to add to the shell configuration file.
439
440=back
441
b5cc15f7 442=head1 AUTHOR
443
444Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
445
d6b71a2d 446auto_install fixes kindly sponsored by http://www.takkle.com/
447
b5c1154d 448=head1 CONTRIBUTORS
449
450Patches to correctly output commands for csh style shells, as well as some
451documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>.
452
0fb70b9a 453'--self-contained' feature contributed by Mark Stosberg <mark@summersault.com>.
454
8b1e8e69 455Doc patches for a custom local::lib patch contributed by Torsten Raudssus
456<torsten@raudssus.de>.
457
b5cc15f7 458=head1 LICENSE
459
460This library is free software under the same license as perl itself
461
462=cut
463
4641;