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