File Coverage

blib/lib/local/lib/deps.pm
Criterion Covered Total %
statement 98 123 79.6
branch 20 36 55.5
condition 2 5 40.0
subroutine 23 24 95.8
pod 8 9 88.8
total 151 197 76.6


line stmt bran cond sub pod time code
1             package local::lib::deps;
2 7     7   11350 use warnings;
  7         16  
  7         279  
3 7     7   40 use strict;
  7         17  
  7         234  
4 7     7   50 use Cwd;
  7         14  
  7         620  
5 7     7   45 use Config;
  7         16  
  7         563  
6 7     7   8157 use Data::Dumper;
  7         53574  
  7         18850  
7              
8             =pod
9              
10             =head1 NAME
11              
12             local::lib::deps - Maintain a module specific lib path for that modules dependencies.
13              
14             =head1 DESCRIPTION
15              
16             Maintaining perl module dependencies through a distributions package manager
17             can be a real pain. This module helps by making it easier to simply bundle all
18             your applications dependencies into one lib path specific to the module. It
19             also makes it easy to tell your application where to look for modules.
20              
21             =head1 SYNOPSYS
22              
23             Bootstrap your modules dependency area in the default location:
24              
25             use local::lib::deps;
26             $local::lib::deps->install_deps( 'My::Module', 'Dep::One', 'Dep::Two' );
27              
28             Bootstrap your modules dependency area in a custom location:
29              
30             use local::lib::deps;
31             $moduledeps = local::lib::deps->new(
32             base_path => '/path/to/dep/storage',
33             );
34             $moduledeps->install_deps( 'My::Module', 'Dep::One', 'Dep::Two', ... );
35              
36             This will create a directory specifically for the My::Module namespace and
37             install the specified dependencies (and local::lib) there.
38              
39             To use those deps in your app:
40              
41             use local::lib::deps qw/ My::Module My::ModuleTwo/;
42             use Dep::One;
43              
44             or
45              
46             use local::lib::deps;
47             BEGIN {
48             $moduledeps = local::lib::deps->new(
49             base_path => '/path/to/dep/storage',
50             );
51             $moduledeps->add_paths( qw/ My::Module My::ModuleTwo/ );
52             }
53             use Dep::One;
54              
55              
56             To initiate local::lib with the destination directory of your module:
57              
58             use local::lib::deps -locallib => 'My::Module';
59              
60             or
61              
62             use local::lib::deps;
63             $moduledeps = local::lib::deps->new(
64             module => 'My::Module',
65             base_path => '/path/to/dep/storage',
66             );
67             $moduledeps->locallib;
68              
69             =head1 USE CASES
70              
71             The primary use case for this module is installing dependencies for an
72             application that would conflict in some way with the systems perl or module
73             configuration. It is also useful for applications that have a lot of
74             dependancies for which no distribution specific packages exist. This becomes
75             even more critical when you have a sysadmin that abhors using cpan instead of
76             distro packages.
77              
78             Using this module as an end user is not very effective. You could potentially
79             rig your application to install all the dependencies necessary to the users
80             home directory every time it is run. This is a bad idea, each user would have
81             their own copy fo the dependencies, and every run it will try to update all the
82             deps.
83              
84             A better idea is to use this module in your build scripts (Module::Build, or
85             Module::Install). The idea would be to have your 'make' or 'build' task
86             bootstrap the deps folder. Then when the application installs the deps will
87             install as well, but in a way that does not interfer with the rest of the
88             system.
89              
90             This is even more useful when you are building a package as you can bundle the
91             dependences in your package.
92              
93             =head1 PUBLIC METHODS
94              
95             =over 4
96              
97             =cut
98              
99             our $VERSION = 0.09;
100             our %PATHS_ADDED;
101             our $START_PATH = getcwd();
102             our %INITIALIZED;
103              
104             sub import {
105 6     6   37917 my ( $package, @params ) = @_;
106 6         69 my @modules = grep { $_ !~ m/^-/ } @params;
  1         25  
107             # Copy $_ so we don't change @params
108 6         14 my %flags = map { my $i = $_; $i =~ s/^-//g; $i => 1 } grep { $_ =~ m/^-/ } @params;
  0         0  
  0         0  
  0         0  
  1         19527  
109 6 100       57 unless ( @modules ) {
110 5         20 my ($module) = caller;
111 5         15 @modules = ( $module );
112             }
113 6 50       29 if ( $flags{locallib} ) {
114 0 0       0 die( "Can only specify one module to use with the -locallib flag.\n" ) if @modules > 1;
115 0         0 $package->locallib( @modules );
116 0         0 return;
117             }
118 6         121 $package->add_paths( @modules );
119             }
120              
121             =item new( module => 'My::Module', base_path => 'path/to/module/libs', cpan_config => {...}, debug => 0 )
122              
123             Create a new local::lib::deps object.
124              
125             =cut
126              
127             sub new {
128 8     8 1 63 my ( $class, %params ) = @_;
129 8   33     69 $class = ref $class || $class;
130 8         86 return bless( { %params }, $class );
131             }
132              
133             =item add_paths( qw/ Module::One Module::Two /)
134              
135             Add the local::lib path for the specified module to @INC;
136              
137             =cut
138              
139             sub add_paths {
140 6     6 1 13 my $self = shift;
141 6         17 my @modules = @_;
142 6         42 $self->_add_path( $_ ) for @modules;
143             }
144              
145             =item locallib( $module )
146              
147             Will get local::lib setup against the local::lib::deps dir. If called as a
148             class method $module is manditory, if called as an object method $module is
149             ignored.
150              
151             This is different from add_paths in that any module you install after this will
152             be installed to the specified modules local-lib dir.
153              
154             =cut
155              
156             sub locallib {
157 0     0 1 0 my ( $self, $module ) = @_;
158 0 0       0 $module = $self->module if $self->is_object;
159 0         0 my $mpath = __absolute_path( $self->_full_module_path( $module ));
160 0         0 $self->_add_path( $module );
161 0         0 eval "use local::lib '$mpath'";
162 0 0       0 die( $@ ) if $@;
163             }
164              
165             =item install_deps( $module, @deps )
166              
167             This will bootstrap local::lib into a local::lib::deps folder for the specified
168             module, it will then continue to install (or update) all the dependency
169             modules.
170              
171             =cut
172              
173             sub install_deps {
174 4     4 1 22 my ( $self, $pkg, @deps) = @_;
175 4         738 print "Forking child process to run cpan...\n";
176 4 100       6210 if ( my $pid = fork ) {
177 2         143330066 waitpid( $pid, 0 );
178             }
179             else {
180 2         328 $self->_install_deps( $pkg, @deps );
181 0         0 exit;
182             }
183             }
184              
185             sub force_deps {
186 2     2 0 20 my ( $self, $pkg, @deps) = @_;
187 2         118 print "Forking child process to run cpan (force)...\n";
188 2 100       2583 if ( my $pid = fork ) {
189 1         53361319 waitpid( $pid, 0 );
190             }
191             else {
192 1         106 $self->_force_deps( $pkg, @deps );
193 0         0 exit;
194             }
195             }
196              
197             =item is_object()
198              
199             Used internally, documented for completeness. Determines if $self is an object,
200             or the package.
201              
202             =cut
203              
204             sub is_object {
205 72     72 1 875 my $self = shift;
206 72 100       545 return unless ref $self;
207 21         3133 return UNIVERSAL::isa( $self, 'UNIVERSAL' );
208             }
209              
210             =back
211              
212             =head1 ACCESSOR METHODS
213              
214             =over 4
215              
216             =item module()
217              
218             =cut
219              
220             sub module {
221 2     2 1 5 my $self = shift;
222 2 100       4 return unless $self->is_object;
223 1         5 return $self->{ module };
224             }
225              
226             =item base_path()
227              
228             Returns the base path that contains the module dependancy areas. This is
229             documented because you may wish to override this in an application specific
230             subclass.
231              
232             =cut
233              
234             sub base_path {
235 39     39 1 127 my $self = shift;
236 39         68 my $class = $self;
237 39 100       5201 if ( $self->is_object ) {
238 15 100       152 return $self->{ base_path } if $self->{ base_path };
239 3         6 $class = ref $self;
240             }
241              
242             # my $llpath = __FILE__;
243             # $llpath =~ s,/[^/]*$,,ig;
244             # $llpath .= '/deps';
245              
246 27   50     357 my $file = $INC{ join("/", split('::', $class)) . ".pm" } || __FILE__;
247 27         50 my $path = $file;
248 27         232 $path =~ s,/[^/]*$,,ig;
249 27         58 $path .= '/deps';
250              
251 27 100       66 $self->{ base_path } = $path if ( $self->is_object );
252              
253 27         118 return $path;
254             }
255              
256             =item cpan_config
257              
258             Get the cpan_config hashref.
259              
260             =cut
261              
262             sub cpan_config {
263 3     3 1 22 my $self = shift;
264 3         1070 return $self->{ cpan_config };
265             }
266              
267             =head1 OTHER METHODS
268              
269             =over 4
270              
271             =cut
272              
273             sub _module_path {
274 32     32   90 my $self = shift;
275 32         56 my ( $module ) = @_;
276 32         43 my $mpath = $module;
277 32         112 $mpath =~ s,::,/,g;
278 32         11711 return $mpath;
279             }
280              
281             sub _full_module_path {
282 28     28   449 my $self = shift;
283 28         101 return join( "/", $self->base_path(), $self->_module_path( @_ ));
284             }
285              
286             sub _add_path {
287 7     7   640 my $self = shift;
288 7         16 my ( $module ) = @_;
289              
290 7         29 for my $path ( $self->_path( $module ), $self->_arch_path( $module )) {
291 14         37 $path = __absolute_path( $path );
292 14 50       66 next if $PATHS_ADDED{ $path }++;
293 14         46 unshift @INC, $path;
294             }
295             #Shamelessly copied from local::lib;
296 7         327 $ENV{PERL5LIB} = join( $Config{path_sep}, @INC );
297             }
298              
299             sub _path {
300 22     22   453 my $self = shift;
301 22         85 return join( "/", $self->_full_module_path( @_ ), "lib/perl5" );
302             }
303              
304             sub _arch_path {
305 10     10   22 my $self = shift;
306 10         23 return join( "/", $self->_path( @_ ), $Config{archname});
307             }
308              
309             sub __absolute_path {
310 17     17   30 my ( $dir ) = @_;
311 17 50       96 if ( $dir !~ m,^/, ) {
312 0         0 return "$START_PATH/$dir";
313             }
314 17         53 return $dir;
315             }
316              
317             sub _force_deps {
318 1     1   7 my $self = shift;
319 1         146 $self->_do_deps( 'force', @_ );
320             }
321              
322             sub _install_deps {
323 2     2   53 my $self = shift;
324 2         292 $self->_do_deps( 'normal', @_ );
325             }
326              
327             #our %INITIALIZED;
328             sub _do_deps {
329 3     3   62 my $self = shift;
330 3         190 my ($mode, $pkg, @deps) = @_;
331 3         204 my $origin = getcwd();
332              
333 3         556908 require CPAN;
334 3         2110285 CPAN::HandleConfig->load();
335 3         36 $CPAN::Config = {
336 3         153 %{ $CPAN::Config },
337 3         2873 %{ $self->cpan_config },
338             };
339 3         78 CPAN::Shell::setup_output();
340 3         162 CPAN::Index->reload();
341              
342 3 50       171238451 unless ( $INITIALIZED{ $pkg }++ ) {
343 3         45 local $CPAN::Config->{makepl_arg} = '--bootstrap=' . __absolute_path( $self->_full_module_path( $pkg ));
344 3         80 CPAN::Shell->install( 'local::lib' );
345             }
346              
347             # We want to install to the locallib.
348 0           chdir( $origin );
349 0           $self->locallib( $pkg );
350 0 0         if ( $self->{ debug } ) {
351 0           require Data::Dumper;
352 0           Data::Dumper->import;
353 0           print Dumper({ INC => \@INC, ENV => \%ENV, Config => $CPAN::Config });
354             }
355              
356 0           foreach my $dep ( @deps ) {
357 0 0         CPAN::Shell->install( $dep ) if $mode eq 'normal';
358 0 0         CPAN::Shell->force( 'install', $dep ) if $mode eq 'force';
359             }
360              
361             # Be kind rewind.
362 0           chdir( $origin );
363             }
364              
365             1;
366              
367             __END__