bump version for release
[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
f66e06d3 14our $VERSION = '1.002000'; # 1.2.0
b5cc15f7 15
16sub import {
17 my ($class, $path) = @_;
18 $path = $class->resolve_path($path);
19 $class->setup_local_lib_for($path);
20}
21
5b94dce5 22sub pipeline;
b5cc15f7 23
5b94dce5 24sub pipeline {
b5cc15f7 25 my @methods = @_;
26 my $last = pop(@methods);
27 if (@methods) {
28 \sub {
29 my ($obj, @args) = @_;
5b94dce5 30 $obj->${pipeline @methods}(
b5cc15f7 31 $obj->$last(@args)
32 );
33 };
34 } else {
35 \sub {
36 shift->$last(@_);
37 };
38 }
39}
40
275c9dae 41=begin testing
42
43#:: test pipeline
b5cc15f7 44
45package local::lib;
46
47{ package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
48my $foo = bless({}, 'Foo');
4c375968 49Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
b5cc15f7 50
275c9dae 51=end testing
52
b5cc15f7 53=cut
54
55sub resolve_path {
56 my ($class, $path) = @_;
5b94dce5 57 $class->${pipeline qw(
b5cc15f7 58 resolve_relative_path
59 resolve_home_path
60 resolve_empty_path
61 )}($path);
62}
63
64sub resolve_empty_path {
65 my ($class, $path) = @_;
66 if (defined $path) {
67 $path;
68 } else {
69 '~/perl5';
70 }
71}
72
275c9dae 73=begin testing
74
75#:: test classmethod setup
b5cc15f7 76
77my $c = 'local::lib';
78
275c9dae 79=end testing
80
81=begin testing
b5cc15f7 82
275c9dae 83#:: test classmethod
b5cc15f7 84
85is($c->resolve_empty_path, '~/perl5');
86is($c->resolve_empty_path('foo'), 'foo');
87
275c9dae 88=end testing
89
b5cc15f7 90=cut
91
92sub 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 {
dc8ddd06 103 File::HomeDir->my_home;
b5cc15f7 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
128sub resolve_relative_path {
129 my ($class, $path) = @_;
130 File::Spec->rel2abs($path);
131}
132
275c9dae 133=begin testing
134
135#:: test classmethod
b5cc15f7 136
137local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
138is($c->resolve_relative_path('bar'),'FOObar');
139
275c9dae 140=end testing
141
b5cc15f7 142=cut
143
144sub 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);
f9c6b7ff 152 unshift(@INC, split(':', $ENV{PERL5LIB}));
b5cc15f7 153 }
154}
155
156sub modulebuildrc_path {
157 my ($class, $path) = @_;
158 File::Spec->catfile($path, '.modulebuildrc');
159}
160
161sub install_base_bin_path {
162 my ($class, $path) = @_;
163 File::Spec->catdir($path, 'bin');
164}
165
166sub install_base_perl_path {
167 my ($class, $path) = @_;
168 File::Spec->catdir($path, 'lib', 'perl5');
169}
170
171sub install_base_arch_path {
172 my ($class, $path) = @_;
173 File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
174}
175
176sub 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: $!");
18bb63e0 191 print MODULEBUILDRC qq{install --install_base ${path}\n}
b5cc15f7 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
c2447f35 198sub INTERPOLATE_ENV () { 1 }
199sub LITERAL_ENV () { 0 }
b5cc15f7 200
201sub print_environment_vars_for {
202 my ($class, $path) = @_;
c2447f35 203 my @envs = $class->build_environment_vars_for($path, LITERAL_ENV);
b5cc15f7 204 my $out = '';
1bc71e56 205
0353dbc0 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 }
1bc71e56 216 my $shelltype = do {
217 local $_ = $shellbin;
b42496e0 218 if(/csh/) {
1bc71e56 219 'csh'
b42496e0 220 } else {
1bc71e56 221 'bourne'
222 }
223 };
224
b5cc15f7 225 while (@envs) {
226 my ($name, $value) = (shift(@envs), shift(@envs));
227 $value =~ s/(\\")/\\$1/g;
1bc71e56 228 $out .= $class->${\"build_${shelltype}_env_declaration"}($name, $value);
b5cc15f7 229 }
230 print $out;
231}
232
1bc71e56 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.
236sub build_bourne_env_declaration {
237 my $class = shift;
238 my($name, $value) = @_;
239 return qq{export ${name}="${value}"\n};
240}
241
242sub build_csh_env_declaration {
243 my $class = shift;
244 my($name, $value) = @_;
245 return qq{setenv ${name} "${value}"\n};
246}
247
b5cc15f7 248sub setup_env_hash_for {
249 my ($class, $path) = @_;
c2447f35 250 my %envs = $class->build_environment_vars_for($path, INTERPOLATE_ENV);
b5cc15f7 251 @ENV{keys %envs} = values %envs;
252}
253
254sub 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),
c2447f35 262 ($ENV{PERL5LIB} ?
263 ($interpolate == INTERPOLATE_ENV
264 ? ($ENV{PERL5LIB})
265 : ('$PERL5LIB'))
266 : ())
b5cc15f7 267 ),
268 PATH => join(':',
269 $class->install_base_bin_path($path),
c2447f35 270 ($interpolate == INTERPOLATE_ENV
b5cc15f7 271 ? $ENV{PATH}
272 : '$PATH')
273 ),
274 )
275}
276
275c9dae 277=begin testing
278
279#:: test classmethod
b5cc15f7 280
281File::Path::rmtree('t/var/splat');
282
4c375968 283$c->ensure_dir_structure_for('t/var/splat');
b5cc15f7 284
285ok(-d 't/var/splat');
286
287ok(-f 't/var/splat/.modulebuildrc');
288
275c9dae 289=end testing
290
b5cc15f7 291=head1 NAME
292
293local::lib - create and use a local lib/ for perl modules with PERL5LIB
294
295=head1 SYNOPSIS
296
297In code -
298
299 use local::lib; # sets up a local lib at ~/perl5
300
301 use local::lib '~/foo'; # same, but ~/foo
302
1bc71e56 303 # Or...
304 use FindBin;
305 use local::lib "$FindBin::Bin/../support"; # app-local support library
306
b5cc15f7 307From 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
bc30e1d5 315To bootstrap if you don't have local::lib itself installed -
316
e423efce 317 <download local::lib tarball from CPAN, unpack and cd into dir>
715c31a0 318
bc30e1d5 319 $ perl Makefile.PL --bootstrap
320 $ make test && make install
715c31a0 321
dc8ddd06 322 $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
715c31a0 323
618272fe 324 # Or for C shells...
715c31a0 325
618272fe 326 $ /bin/csh
327 % echo $SHELL
328 /bin/csh
329 % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
dc8ddd06 330
331You can also pass --boostrap=~/foo to get a different location (adjust the
618272fe 332bashrc / cshrc line appropriately)
333
334=head1 DESCRIPTION
335
336This module provides a quick, convenient way of bootstrapping a user-local Perl
337module library located within the user's home directory. It also constructs and
338prints out for the user the list of environment variables using the syntax
339appropriate for the user's current shell (as specified by the C<SHELL>
340environment variable), suitable for directly adding to one's shell configuration
341file.
dc8ddd06 342
1bc71e56 343More generally, local::lib allows for the bootstrapping and usage of a directory
344containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship
345an application with an app-specific copy of a Perl module, or collection of
346modules. Useful in cases like when an upstream maintainer hasn't applied a patch
347to a module of theirs that you need for your application.
348
349On import, local::lib sets the following environment variables to appropriate
350values:
351
352=over 4
353
354=item MODULEBUILDRC
355
356=item PERL_MM_OPT
357
358=item PERL5LIB
359
360=item PATH
361
362PATH is appended to, rather than clobbered.
363
364=back
365
366These values are then available for reference by any code after import.
367
dc8ddd06 368=head1 LIMITATIONS
369
618272fe 370Rather basic shell detection. Right now anything with csh in its name is
371assumed to be a C shell or something compatible, and everything else is assumed
1bc71e56 372to be Bourne. If the C<SHELL> environment variable is not set, a
373Bourne-compatible shell is assumed.
dc8ddd06 374
375Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
376have CPANPLUS installed.
377
378Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
379
e423efce 380Should probably auto-fixup CPAN config if not already done.
381
dc8ddd06 382Patches very much welcome for any of the above.
bc30e1d5 383
618272fe 384=head1 ENVIRONMENT
385
386=over 4
387
388=item SHELL
389
390local::lib looks at the user's C<SHELL> environment variable when printing out
391commands to add to the shell configuration file.
392
393=back
394
b5cc15f7 395=head1 AUTHOR
396
397Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
398
d6b71a2d 399auto_install fixes kindly sponsored by http://www.takkle.com/
400
b5c1154d 401=head1 CONTRIBUTORS
402
403Patches to correctly output commands for csh style shells, as well as some
404documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>.
405
b5cc15f7 406=head1 LICENSE
407
408This library is free software under the same license as perl itself
409
410=cut
411
4121;