Commit | Line | Data |
b5cc15f7 |
1 | use strict; |
2 | use warnings; |
3 | |
4 | package local::lib; |
5 | |
c1441fb6 |
6 | use 5.008001; # probably works with earlier versions but I'm not supporting them |
7 | # (patches would, of course, be welcome) |
b5cc15f7 |
8 | |
9 | use File::Spec (); |
10 | use File::Path (); |
11 | use Carp (); |
12 | use Config; |
13 | |
8b1e8e69 |
14 | our $VERSION = '1.003002'; # 1.3.2 |
b5cc15f7 |
15 | |
16 | sub 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'; |
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 | } |
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 |
48 | sub pipeline; |
b5cc15f7 |
49 | |
5b94dce5 |
50 | sub 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 | |
71 | package local::lib; |
72 | |
73 | { package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } } |
74 | my $foo = bless({}, 'Foo'); |
4c375968 |
75 | Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15); |
b5cc15f7 |
76 | |
275c9dae |
77 | =end testing |
78 | |
b5cc15f7 |
79 | =cut |
80 | |
81 | sub 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 | |
90 | sub 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 | |
103 | my $c = 'local::lib'; |
104 | |
275c9dae |
105 | =end testing |
106 | |
107 | =begin testing |
b5cc15f7 |
108 | |
275c9dae |
109 | #:: test classmethod |
b5cc15f7 |
110 | |
111 | is($c->resolve_empty_path, '~/perl5'); |
112 | is($c->resolve_empty_path('foo'), 'foo'); |
113 | |
275c9dae |
114 | =end testing |
115 | |
b5cc15f7 |
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 { |
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 | |
154 | sub resolve_relative_path { |
155 | my ($class, $path) = @_; |
156 | File::Spec->rel2abs($path); |
157 | } |
158 | |
275c9dae |
159 | =begin testing |
160 | |
161 | #:: test classmethod |
b5cc15f7 |
162 | |
163 | local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; }; |
164 | is($c->resolve_relative_path('bar'),'FOObar'); |
165 | |
275c9dae |
166 | =end testing |
167 | |
b5cc15f7 |
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); |
f9c6b7ff |
178 | unshift(@INC, split(':', $ENV{PERL5LIB})); |
b5cc15f7 |
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: $!"); |
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 |
224 | sub INTERPOLATE_ENV () { 1 } |
225 | sub LITERAL_ENV () { 0 } |
b5cc15f7 |
226 | |
227 | sub 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. |
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 | |
b5cc15f7 |
274 | sub 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 | |
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), |
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 | |
307 | File::Path::rmtree('t/var/splat'); |
308 | |
4c375968 |
309 | $c->ensure_dir_structure_for('t/var/splat'); |
b5cc15f7 |
310 | |
311 | ok(-d 't/var/splat'); |
312 | |
313 | ok(-f 't/var/splat/.modulebuildrc'); |
314 | |
275c9dae |
315 | =end testing |
316 | |
b5cc15f7 |
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 | |
1bc71e56 |
329 | # Or... |
330 | use FindBin; |
331 | use local::lib "$FindBin::Bin/../support"; # app-local support library |
332 | |
b5cc15f7 |
333 | From 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 |
348 | To 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 |
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 |
618272fe |
370 | |
371 | =head1 DESCRIPTION |
372 | |
373 | This module provides a quick, convenient way of bootstrapping a user-local Perl |
374 | module library located within the user's home directory. It also constructs and |
375 | prints out for the user the list of environment variables using the syntax |
376 | appropriate for the user's current shell (as specified by the C<SHELL> |
377 | environment variable), suitable for directly adding to one's shell configuration |
378 | file. |
dc8ddd06 |
379 | |
1bc71e56 |
380 | More generally, local::lib allows for the bootstrapping and usage of a directory |
381 | containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship |
382 | an application with an app-specific copy of a Perl module, or collection of |
383 | modules. Useful in cases like when an upstream maintainer hasn't applied a patch |
384 | to a module of theirs that you need for your application. |
385 | |
386 | On import, local::lib sets the following environment variables to appropriate |
387 | values: |
388 | |
389 | =over 4 |
390 | |
391 | =item MODULEBUILDRC |
392 | |
393 | =item PERL_MM_OPT |
394 | |
395 | =item PERL5LIB |
396 | |
397 | =item PATH |
398 | |
399 | PATH is appended to, rather than clobbered. |
400 | |
401 | =back |
402 | |
403 | These values are then available for reference by any code after import. |
404 | |
0fb70b9a |
405 | =head1 A WARNING ABOUT UNINST=1 |
406 | |
407 | Be careful about using local::lib in combination with "make install UNINST=1". |
408 | The idea of this feature is that will uninstall an old version of a module |
409 | before installing a new one. However it lacks a safety check that the old |
410 | version and the new version will go in the same directory. Used in combination |
411 | with local::lib, you can potentially delete a globally accessible version of a |
381738d7 |
412 | module while installing the new version in a local place. Only combine "make |
0fb70b9a |
413 | install UNINST=1" and local::lib if you understand these possible consequences. |
414 | |
dc8ddd06 |
415 | =head1 LIMITATIONS |
416 | |
618272fe |
417 | Rather basic shell detection. Right now anything with csh in its name is |
418 | assumed to be a C shell or something compatible, and everything else is assumed |
1bc71e56 |
419 | to be Bourne. If the C<SHELL> environment variable is not set, a |
420 | Bourne-compatible shell is assumed. |
dc8ddd06 |
421 | |
422 | Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you |
423 | have CPANPLUS installed. |
424 | |
425 | Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC. |
426 | |
e423efce |
427 | Should probably auto-fixup CPAN config if not already done. |
428 | |
dc8ddd06 |
429 | Patches very much welcome for any of the above. |
bc30e1d5 |
430 | |
618272fe |
431 | =head1 ENVIRONMENT |
432 | |
433 | =over 4 |
434 | |
435 | =item SHELL |
436 | |
437 | local::lib looks at the user's C<SHELL> environment variable when printing out |
438 | commands to add to the shell configuration file. |
439 | |
440 | =back |
441 | |
b5cc15f7 |
442 | =head1 AUTHOR |
443 | |
444 | Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/ |
445 | |
d6b71a2d |
446 | auto_install fixes kindly sponsored by http://www.takkle.com/ |
447 | |
b5c1154d |
448 | =head1 CONTRIBUTORS |
449 | |
450 | Patches to correctly output commands for csh style shells, as well as some |
451 | documentation additions, contributed by Christopher Nehren <apeiron@cpan.org>. |
452 | |
0fb70b9a |
453 | '--self-contained' feature contributed by Mark Stosberg <mark@summersault.com>. |
454 | |
8b1e8e69 |
455 | Doc patches for a custom local::lib patch contributed by Torsten Raudssus |
456 | <torsten@raudssus.de>. |
457 | |
b5cc15f7 |
458 | =head1 LICENSE |
459 | |
460 | This library is free software under the same license as perl itself |
461 | |
462 | =cut |
463 | |
464 | 1; |