File Coverage

blib/lib/Rex/Bundle.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4             # vim: set ts=3 sw=3 tw=0:
5             # vim: set expandtab:
6              
7             =head1 NAME
8              
9             Rex::Bundle - Bundle Perl Libraries
10              
11             =head1 DESCRIPTION
12              
13             Rex::Bundle is a Rex module to install needed perl modules into a private folder separated from the system librarys.
14              
15             =head1 GETTING HELP
16              
17             =over 4
18              
19             =item * IRC: irc.freenode.net #rex
20              
21             =item * Wiki: L
22              
23             =item * Bug Tracker: L
24              
25             =back
26              
27             =head1 USAGE
28              
29             Create a I in your project directory and add the following content to it:
30              
31             install_to 'vendor/perl'
32            
33             desc "Check and install dependencies";
34             task "deps", sub {
35             mod "Mod1", url => "git://...";
36             mod "Foo::Bar";
37             # ...
38             };
39              
40             Now you can check if all dependencies are met (and if not, it will install the needed modules) with I.
41              
42             After you've installed the dependencies you can use them by appending the I directory to @INC.
43              
44             use lib "vendor/perl";
45              
46             =cut
47              
48             package Rex::Bundle;
49              
50 1     1   742 use strict;
  1         2  
  1         38  
51 1     1   6 use warnings;
  1         2  
  1         30  
52 1     1   902 use version;
  1         2208  
  1         4  
53              
54             our $VERSION = '0.5.0';
55              
56             require Exporter;
57 1     1   74 use base qw(Exporter);
  1         1  
  1         83  
58              
59 1     1   4 use vars qw(@EXPORT $install_dir $rex_file_dir);
  1         2  
  1         49  
60 1     1   4 use Cwd qw(getcwd);
  1         2  
  1         48  
61 1     1   5 use File::Basename qw(basename);
  1         1  
  1         53  
62 1     1   787 use YAML;
  1         16409  
  1         69  
63 1     1   1255 use Data::Dumper;
  1         8633  
  1         92  
64              
65 1     1   673 use Rex -base;
  0            
  0            
66              
67             my $has_lwp = 0;
68             my $has_curl = 0;
69             my $has_wget = 0;
70              
71             system("which wget >/dev/null 2>&1");
72             $has_wget = !$?;
73              
74             system("which curl >/dev/null 2>&1");
75             $has_curl = !$?;
76              
77             eval {
78             require LWP::Simple;
79             $has_lwp = 1;
80             };
81              
82             @EXPORT = qw(mod install_to perl);
83              
84             # currently only supports $name
85             sub mod {
86             my $name = shift;
87             return if $name eq "perl";
88             my $opts = { @_ };
89            
90             $rex_file_dir = getcwd;
91              
92             if(!$install_dir) {
93             print STDERR "You have to define install_to in your Rexfile\n";
94             exit 1;
95             }
96              
97             unless(exists $opts->{'force'}) {
98              
99             eval { my $m = $name; $m =~ s{::}{/}g; require "$m.pm"; };
100              
101             unless ($@) {
102              
103             my $installed_version = $name->VERSION;
104              
105             if(exists $opts->{"version"}) {
106              
107             if( version->parse($installed_version) >= version->parse($opts->{"version"}) && ! $@) {
108             print STDERR "$name is already installed.\n";
109             return;
110             }
111              
112             } elsif(! $@) {
113              
114             print STDERR "$name is already installed.\n";
115             return;
116              
117             }
118              
119             }
120              
121             }
122              
123             my $rnd = _gen_rnd();
124              
125             my($file_name, $dir_name, $new_dir);
126             if(defined $opts->{'url'}) {
127             $new_dir = $name;
128             $new_dir =~ s{::}{-}g;
129             $new_dir .= "-$rnd";
130             _clone_repo($opts->{'url'}, $new_dir);
131             } else {
132             my $version_to_check = $opts->{"version"};
133             my $mod_url;
134             for (1..2) {
135             $mod_url = _lookup_module_url($name, $opts->{"version"});
136             if(_download($mod_url)) {
137             last;
138             }
139             $version_to_check = 0;
140             }
141              
142             ($file_name) = $mod_url =~ m{/CPAN/authors/id/.*/(.*?\.(?:tar\.gz|tgz|tar\.bz2|zip))};
143             ($dir_name) = $mod_url =~ m{/CPAN/authors/id/.*/(.*?)\.(?:tar\.gz|tgz|tar\.bz2|zip)};
144             $new_dir = $dir_name . "-" . $rnd;
145              
146             _extract_file($file_name);
147             if(! -d _work_dir() . "/" . $dir_name) {
148             my $dir_wout_version = $dir_name;
149             $dir_wout_version =~ s/\-[\d\.]+$//;
150             if(-d _work_dir() . "/" . $dir_wout_version) {
151             $dir_name = $dir_wout_version;
152             }
153             }
154             _rename_dir($dir_name, $new_dir);
155             }
156              
157             _install_deps($new_dir);
158             _configure($new_dir);
159             _install_deps($new_dir);
160             _configure($new_dir);
161              
162             _make($new_dir);
163             unless(exists $opts->{'notest'}) {
164             _test($new_dir);
165             }
166             _install($new_dir);
167             }
168              
169             sub _install_deps {
170             my ($new_dir) = @_;
171             for my $mod_info (_get_deps($new_dir)) {
172             for my $mod (keys %$mod_info) {
173             unless ($mod_info->{$mod}) {
174             mod($mod);
175             }
176             else {
177             mod($mod, version => $mod_info->{$mod});
178             }
179             }
180             }
181              
182              
183             }
184              
185             sub install_to {
186             $install_dir = shift;
187             lib->import(getcwd . '/' . $install_dir);
188             $ENV{'PATH'} = $install_dir . '/bin:' . $ENV{'PATH'};
189             $ENV{'PERL5LIB'} = $install_dir . ':' . ( $ENV{'PERL5LIB'} || '' );
190             $ENV{'PERLLIB'} = $install_dir . ':' . ( $ENV{'PERLLIB'} || '' );
191              
192             my @new_path = split(/:/, $ENV{PATH});
193              
194             Rex::Config->set_path(\@new_path);
195             }
196              
197             sub perl {
198             my $cmd = "";
199              
200             $cmd .= "PERL5LIB=$install_dir" . ':' . ( $ENV{'PERL5LIB'} || '' );
201             $cmd .= "PERLLIB=$install_dir" . ':' . ( $ENV{'PERLLIB'} || '' );
202              
203             $cmd .= " " . join(" ", @_);
204              
205             Rex::Logger::debug("executing: $cmd");
206              
207             system $cmd;
208             }
209              
210             # private functions
211             sub _lookup_module_url {
212             my ($name, $version) = @_;
213             my $url = 'http://search.cpan.org/perldoc?' . $name;
214             my $html = _get_http($url);
215             my ($dl_url) = $html =~ m{};
216              
217             if($version) {
218             my ($path, $format) = ($dl_url =~ m{(/CPAN/authors/id/./../[^/]+/).*?\.(tar\.gz|tgz|tar\.bz2|zip)$});
219             my $file_name = $name;
220             $file_name =~ s/::/-/g;
221             my $tmp_dl_url = $path . $file_name . "-$version.$format";
222             }
223              
224             if($dl_url) {
225             return $dl_url;
226             } else {
227             die("module not found ($url).");
228             }
229             }
230              
231             sub _get_http {
232             my ($url) = @_;
233              
234             my $html;
235             if($has_curl) {
236             $html = qx{curl -# -L '$url' 2>/dev/null};
237             }
238             elsif($has_wget) {
239             $html = qx{wget -O - '$url' 2>/dev/null};
240             }
241             elsif($has_lwp) {
242             $html = LWP::Simple::get($url);
243             }
244             else {
245             die("No tool found to download something. (curl, wget, LWP::Simple)");
246             }
247              
248             return $html;
249             }
250              
251             sub _download {
252             my ($url) = @_;
253              
254             my $cwd = getcwd;
255             chdir(_work_dir());
256             if($has_wget) {
257             _call("wget http://search.cpan.org$url >/dev/null 2>&1");
258             unless($? == 0) {
259             print "Failed downloading http://search.cpan.org$url\n";
260             return 0;
261             }
262             }
263             elsif($has_curl) {
264             _call("curl -L -O -# http://search.cpan.org$url >/dev/null 2>&1");
265             unless($? == 0) {
266             print "Failed downloading http://search.cpan.org$url\n";
267             return 0;
268             }
269             }
270             elsif($has_lwp) {
271             my $data = LWP::Simple::get("http://search.cpan.org$url");
272             unless($data) {
273             print "Failed downloading http://search.cpan.org$url\n";
274             return 0;
275             }
276             open(my $fh, '>', basename($url)) or die($!);
277             binmode $fh;
278             print $fh $data;
279             close($fh);
280             }
281             else {
282             die("No tool found to download something. (curl, wget, LWP::Simple)");
283             }
284             chdir($cwd);
285              
286             return 1;
287             }
288              
289             sub _extract_file {
290             my($file) = @_;
291              
292             my $cwd = getcwd;
293             chdir(_work_dir());
294              
295             my $cmd;
296             if($file =~ m/\.tar\.gz$/) {
297             $cmd = 'tar -xvzf %s';
298             } elsif($file =~ m/\.tar\.bz2/) {
299             $cmd = 'tar -xjvf %s';
300             }
301              
302             _call(sprintf($cmd, $file));
303             chdir($cwd);
304             }
305              
306             sub _rename_dir {
307             my($old, $new) = @_;
308            
309             my $cwd = getcwd;
310             chdir(_work_dir());
311              
312             rename($old, $new);
313              
314             chdir($cwd);
315             }
316              
317             sub _configure {
318             my($dir) = @_;
319              
320             my $cwd = getcwd;
321             chdir(_work_dir() . '/' . $dir);
322              
323             my $cmd;
324             if(-f "Build.PL") {
325             $cmd = 'yes "" | perl Build.PL';
326             } elsif(-f "Makefile.PL") {
327             $cmd = "yes '' | perl Makefile.PL PREFIX=$cwd/$install_dir INSTALLSITEARCH=$cwd/$install_dir INSTALLPRIVLIB=$cwd/$install_dir INSTALLSITELIB=$cwd/$install_dir INSTALLARCHLIB=$cwd/$install_dir INSTALLVENDORARCH=$cwd/$install_dir";
328             } else {
329             die("not supported");
330             }
331              
332             _call($cmd);
333             die("Error $cmd") if($? != 0);
334             chdir($cwd);
335             }
336              
337             sub _make {
338             my($dir) = @_;
339            
340             my $cwd = getcwd;
341             chdir(_work_dir() . '/' . $dir);
342              
343             my $cmd;
344             if(-f "Build") {
345             $cmd = './Build';
346             } elsif(-f "Makefile") {
347             $cmd = "make";
348             } else {
349             die("not supported");
350             }
351              
352             _call($cmd);
353             die("Error $cmd") if($? != 0);
354             chdir($cwd);
355             }
356              
357             sub _test {
358             my($dir) = @_;
359            
360             my $cwd = getcwd;
361             chdir(_work_dir() . '/' . $dir);
362              
363             my $cmd;
364             if(-f "Build") {
365             $cmd = "./Build test";
366             } elsif(-f "Makefile") {
367             $cmd = "make test";
368             } else {
369             die("not supported");
370             }
371              
372             _call($cmd);
373             die("Error $cmd") if($? != 0);
374             chdir($cwd);
375             }
376              
377             sub _install {
378             my($dir) = @_;
379            
380             my $cwd = getcwd;
381             chdir(_work_dir() . '/' . $dir);
382              
383             my $cmd;
384             if(-f "Build") {
385             $cmd = "./Build install --install_path lib=$cwd/$install_dir --install_path arch=$cwd/$install_dir --install_path script=$cwd/$install_dir/bin --install_path bin=$cwd/$install_dir/bin --install_path bindoc=$cwd/$install_dir/man --install_path libdoc=$cwd/$install_dir/man --install_path libhtml=$cwd/$install_dir/html --install_path binhtml=$cwd/$install_dir/html";
386             } elsif(-f "Makefile") {
387             $cmd = "make install";
388             } else {
389             die("not supported");
390             }
391              
392             _call($cmd);
393             die("Error $cmd") if($? != 0);
394             chdir($cwd);
395             }
396              
397             sub _gen_rnd {
398             my @chars = qw(a b c d e f g h i j k l m n o p u q s t u v w x y z 0 1 2 3 4 5 6 7 8 9);
399             my $ret = '';
400              
401             for (0..4) {
402             $ret .= $chars[int(rand(scalar(@chars)))];
403             }
404              
405             $ret;
406             }
407              
408             sub _work_dir {
409             return $ENV{'HOME'} . '/.rexbundle';
410             }
411              
412             sub _get_deps {
413             my ($dir) = @_;
414              
415            
416             my $cwd = getcwd;
417             chdir(_work_dir() . '/' . $dir);
418             my @ret;
419              
420             my $found=0;
421              
422             my $meta_file = "META.yml";
423             if(-f "MYMETA.yml") { $meta_file = "MYMETA.yml"; }
424              
425             if(-f $meta_file) {
426             my $yaml = eval { local(@ARGV, $/) = ($meta_file); $_=<>; $_; };
427             eval {
428             my $struct = Load($yaml);
429             push(@ret, $struct->{'configure_requires'});
430             push(@ret, $struct->{'build_requires'});
431             push(@ret, $struct->{'requires'});
432             $found=1;
433             };
434              
435             if($@) {
436             print STDERR "Error parseing META.yml :(\n";
437             # fallback and try Makefile.PL
438             }
439             } else {
440             # no meta.yml found :(
441             print STDERR "No META.yml found :(\n";
442             @ret = ();
443             }
444              
445             if(!$found) {
446             if(-f "Makefile.PL") {
447             no strict;
448             no warnings 'all';
449             my $makefile = eval { local(@ARGV, $/) = ("Makefile.PL"); <>; };
450             my ($hash_string) = ($makefile =~ m/WriteMakefile\((.*?)\);/ms);
451             my $make_hash = eval "{$hash_string}";
452             if(exists $make_hash->{"PREREQ_PM"}) {
453             push @ret, $make_hash->{"PREREQ_PM"};
454             }
455             use strict;
456             use warnings;
457             }
458             }
459              
460             chdir($cwd);
461              
462             my @needed = grep { ! /^perl$/ } grep { ! eval { my $m = $_; $m =~ s{::}{/}g; require "$m.pm"; 1;} } @ret;
463             print "Found following dependencies: \n";
464             print Dumper(\@needed);
465              
466             @needed;
467             }
468              
469             sub _clone_repo {
470             my($repo, $path) = @_;
471              
472             my $cmd = "%s %s %s %s";
473             my @p = ();
474              
475             if($repo =~ m/^git/) {
476             @p = qw(git clone);
477             push @p, $repo, $path;
478             } elsif($repo =~ m/^svn/) {
479             @p = qw(svn export);
480             push @p, $repo, $path;
481             } else {
482             die("Repositoryformat not supported: $repo");
483             }
484              
485             my $cwd = getcwd;
486             chdir(_work_dir());
487              
488             _call(sprintf($cmd, @p));
489              
490             chdir($cwd);
491             }
492              
493             sub _call {
494             my ($cmd) = @_;
495              
496             $ENV{'PERL5LIB'} .= ":$rex_file_dir/$install_dir";
497             $ENV{'PERLLIB'} .= ":$rex_file_dir/$install_dir";
498             system($cmd);
499             }
500              
501             if( ! -d _work_dir() ) {
502             mkdir (_work_dir(), 0755);
503             }
504              
505             srand;
506              
507             1;