noted sponsorship of certain fixes
[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
14our $VERSION = '1.000000'; # 1.0.0
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
4c375968 41=for test pipeline
b5cc15f7 42
43package local::lib;
44
45{ package Foo; sub foo { -$_[1] } sub bar { $_[1]+2 } sub baz { $_[1]+3 } }
46my $foo = bless({}, 'Foo');
4c375968 47Test::More::ok($foo->${pipeline qw(foo bar baz)}(10) == -15);
b5cc15f7 48
49=cut
50
51sub resolve_path {
52 my ($class, $path) = @_;
5b94dce5 53 $class->${pipeline qw(
b5cc15f7 54 resolve_relative_path
55 resolve_home_path
56 resolve_empty_path
57 )}($path);
58}
59
60sub resolve_empty_path {
61 my ($class, $path) = @_;
62 if (defined $path) {
63 $path;
64 } else {
65 '~/perl5';
66 }
67}
68
69=for test classmethod setup
70
71my $c = 'local::lib';
72
73=cut
74
75=for test classmethod
76
77is($c->resolve_empty_path, '~/perl5');
78is($c->resolve_empty_path('foo'), 'foo');
79
80=cut
81
82sub resolve_home_path {
83 my ($class, $path) = @_;
84 return $path unless ($path =~ /^~/);
85 my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
86 my $tried_file_homedir;
87 my $homedir = do {
88 if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
89 $tried_file_homedir = 1;
90 if (defined $user) {
91 File::HomeDir->users_home($user);
92 } else {
dc8ddd06 93 File::HomeDir->my_home;
b5cc15f7 94 }
95 } else {
96 if (defined $user) {
97 (getpwnam $user)[7];
98 } else {
99 if (defined $ENV{HOME}) {
100 $ENV{HOME};
101 } else {
102 (getpwuid $<)[7];
103 }
104 }
105 }
106 };
107 unless (defined $homedir) {
108 Carp::croak(
109 "Couldn't resolve homedir for "
110 .(defined $user ? $user : 'current user')
111 .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
112 );
113 }
114 $path =~ s/^~[^\/]*/$homedir/;
115 $path;
116}
117
118sub resolve_relative_path {
119 my ($class, $path) = @_;
120 File::Spec->rel2abs($path);
121}
122
123=for test classmethod
124
125local *File::Spec::rel2abs = sub { shift; 'FOO'.shift; };
126is($c->resolve_relative_path('bar'),'FOObar');
127
128=cut
129
130sub setup_local_lib_for {
131 my ($class, $path) = @_;
132 $class->ensure_dir_structure_for($path);
133 if ($0 eq '-') {
134 $class->print_environment_vars_for($path);
135 exit 0;
136 } else {
137 $class->setup_env_hash_for($path);
f9c6b7ff 138 unshift(@INC, split(':', $ENV{PERL5LIB}));
b5cc15f7 139 }
140}
141
142sub modulebuildrc_path {
143 my ($class, $path) = @_;
144 File::Spec->catfile($path, '.modulebuildrc');
145}
146
147sub install_base_bin_path {
148 my ($class, $path) = @_;
149 File::Spec->catdir($path, 'bin');
150}
151
152sub install_base_perl_path {
153 my ($class, $path) = @_;
154 File::Spec->catdir($path, 'lib', 'perl5');
155}
156
157sub install_base_arch_path {
158 my ($class, $path) = @_;
159 File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
160}
161
162sub ensure_dir_structure_for {
163 my ($class, $path) = @_;
164 unless (-d $path) {
165 warn "Attempting to create directory ${path}\n";
166 }
167 File::Path::mkpath($path);
168 my $modulebuildrc_path = $class->modulebuildrc_path($path);
169 if (-e $modulebuildrc_path) {
170 unless (-f _) {
171 Carp::croak("${modulebuildrc_path} exists but is not a plain file");
172 }
173 } else {
174 warn "Attempting to create file ${modulebuildrc_path}\n";
175 open MODULEBUILDRC, '>', $modulebuildrc_path
176 || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
18bb63e0 177 print MODULEBUILDRC qq{install --install_base ${path}\n}
b5cc15f7 178 || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
179 close MODULEBUILDRC
180 || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
181 }
182}
183
184sub INTERPOLATE_PATH () { 1 }
185sub LITERAL_PATH () { 0 }
186
187sub print_environment_vars_for {
188 my ($class, $path) = @_;
189 my @envs = $class->build_environment_vars_for($path, LITERAL_PATH);
190 my $out = '';
0353dbc0 191 # rather basic csh detection, goes on the assumption that something won't
192 # call itself csh unless it really is. also, default to bourne in the
193 # pathological situation where a user doesn't have $ENV{SHELL} defined.
194 # note also that shells with funny names, like zoid, are assumed to be
195 # bourne.
196 my $shellbin = 'sh';
197 if(defined $ENV{'SHELL'}) {
198 my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'});
199 $shellbin = $shell_bin_path_parts[-1];
200 }
b5cc15f7 201 while (@envs) {
202 my ($name, $value) = (shift(@envs), shift(@envs));
203 $value =~ s/(\\")/\\$1/g;
618272fe 204
618272fe 205 if($shellbin =~ /csh/) {
206 $out .= qq{setenv ${name} "${value}"\n};
207 }
208 else {
209 $out .= qq{export ${name}="${value}"\n};
210 }
b5cc15f7 211 }
212 print $out;
213}
214
215sub setup_env_hash_for {
216 my ($class, $path) = @_;
217 my %envs = $class->build_environment_vars_for($path, INTERPOLATE_PATH);
218 @ENV{keys %envs} = values %envs;
219}
220
221sub build_environment_vars_for {
222 my ($class, $path, $interpolate) = @_;
223 return (
224 MODULEBUILDRC => $class->modulebuildrc_path($path),
225 PERL_MM_OPT => "INSTALL_BASE=${path}",
226 PERL5LIB => join(':',
227 $class->install_base_perl_path($path),
228 $class->install_base_arch_path($path),
229 ),
230 PATH => join(':',
231 $class->install_base_bin_path($path),
232 ($interpolate == INTERPOLATE_PATH
233 ? $ENV{PATH}
234 : '$PATH')
235 ),
236 )
237}
238
239=for test classmethod
240
241File::Path::rmtree('t/var/splat');
242
4c375968 243$c->ensure_dir_structure_for('t/var/splat');
b5cc15f7 244
245ok(-d 't/var/splat');
246
247ok(-f 't/var/splat/.modulebuildrc');
248
249=head1 NAME
250
251local::lib - create and use a local lib/ for perl modules with PERL5LIB
252
253=head1 SYNOPSIS
254
255In code -
256
257 use local::lib; # sets up a local lib at ~/perl5
258
259 use local::lib '~/foo'; # same, but ~/foo
260
261From the shell -
262
263 $ perl -Mlocal::lib
264 export MODULEBUILDRC=/home/username/perl/.modulebuildrc
265 export PERL_MM_OPT='INSTALL_BASE=/home/username/perl'
266 export PERL5LIB='/home/username/perl/lib/perl5:/home/username/perl/lib/perl5/i386-linux'
267 export PATH="/home/username/perl/bin:$PATH"
268
bc30e1d5 269To bootstrap if you don't have local::lib itself installed -
270
e423efce 271 $ perl -MCPAN -eshell # you only need to do this if you don't have a ~/.cpan
272 cpan> exit
273 <download local::lib tarball from CPAN, unpack and cd into dir>
bc30e1d5 274 $ perl Makefile.PL --bootstrap
275 $ make test && make install
dc8ddd06 276 $ echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
618272fe 277 # Or for C shells...
278 $ /bin/csh
279 % echo $SHELL
280 /bin/csh
281 % perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc
dc8ddd06 282
283You can also pass --boostrap=~/foo to get a different location (adjust the
618272fe 284bashrc / cshrc line appropriately)
285
286=head1 DESCRIPTION
287
288This module provides a quick, convenient way of bootstrapping a user-local Perl
289module library located within the user's home directory. It also constructs and
290prints out for the user the list of environment variables using the syntax
291appropriate for the user's current shell (as specified by the C<SHELL>
292environment variable), suitable for directly adding to one's shell configuration
293file.
dc8ddd06 294
295=head1 LIMITATIONS
296
618272fe 297Rather basic shell detection. Right now anything with csh in its name is
298assumed to be a C shell or something compatible, and everything else is assumed
299to be Bourne.
dc8ddd06 300
301Bootstrap is a hack and will use CPAN.pm for ExtUtils::MakeMaker even if you
302have CPANPLUS installed.
303
304Kills any existing PERL5LIB, PERL_MM_OPT or MODULEBUILDRC.
305
e423efce 306Should probably auto-fixup CPAN config if not already done.
307
dc8ddd06 308Patches very much welcome for any of the above.
bc30e1d5 309
618272fe 310=head1 ENVIRONMENT
311
312=over 4
313
314=item SHELL
315
316local::lib looks at the user's C<SHELL> environment variable when printing out
317commands to add to the shell configuration file.
318
319=back
320
b5cc15f7 321=head1 AUTHOR
322
323Matt S Trout <mst@shadowcat.co.uk> http://www.shadowcat.co.uk/
324
d6b71a2d 325auto_install fixes kindly sponsored by http://www.takkle.com/
326
b5cc15f7 327=head1 LICENSE
328
329This library is free software under the same license as perl itself
330
331=cut
332
3331;