File Coverage

blib/lib/Ruby/VersionManager.pm
Criterion Covered Total %
statement 54 328 16.4
branch 0 92 0.0
condition 0 5 0.0
subroutine 18 41 43.9
pod 7 8 87.5
total 79 474 16.6


line stmt bran cond sub pod time code
1             package Ruby::VersionManager;
2              
3 1     1   21906 use 5.010;
  1         4  
  1         35  
4 1     1   5 use strict;
  1         2  
  1         34  
5 1     1   5 use feature 'say';
  1         6  
  1         95  
6 1     1   6 use warnings;
  1         2  
  1         50  
7 1     1   2156 use autodie;
  1         29092  
  1         7  
8              
9 1     1   15013 use Moo;
  1         15993  
  1         6  
10 1     1   2523 use YAML;
  1         9921  
  1         62  
11 1     1   12715 use LWP::UserAgent;
  1         97429  
  1         46  
12 1     1   13 use HTTP::Request;
  1         3  
  1         28  
13 1     1   1023 use LWP::Simple;
  1         23020  
  1         11  
14 1     1   435 use File::Path;
  1         2  
  1         49  
15 1     1   4 use File::Spec;
  1         2  
  1         29  
16 1     1   6 use Cwd qw'abs_path cwd';
  1         10  
  1         61  
17              
18 1     1   573 use Ruby::VersionManager::Gem;
  1         4  
  1         966  
19              
20             has rootdir => ( is => 'rw' );
21             has ruby_version => ( is => 'rw' );
22             has major_version => ( is => 'rw' );
23             has rubygems_version => ( is => 'rw' );
24             has available_rubies => ( is => 'rw' );
25             has agent_string => ( is => 'rw' );
26             has archive_type => ( is => 'rw' );
27             has gemset => ( is => 'rw' );
28             has installed_rubies => ( is => 'rw' );
29             has version => ( is => 'rw' );
30              
31             our $VERSION = 0.004004;
32              
33             sub BUILD {
34 0     0 0   my ($self) = @_;
35              
36 0           $self->version($VERSION);
37              
38 0           $self->agent_string( 'Ruby::VersionManager/' . $self->version );
39 0 0         $self->archive_type('.tar.bz2') unless $self->archive_type;
40 0 0         $self->rootdir( abs_path( $self->rootdir ) ) if $self->rootdir;
41 0 0         $self->_make_base or die;
42 0 0         $self->_check_db or die;
43 0           $self->_check_installed;
44 0 0         $self->gemset('default') unless $self->gemset;
45             }
46              
47             sub _make_base {
48 0     0     my ($self) = @_;
49              
50 0 0         $self->rootdir( File::Spec->catdir($ENV{'HOME'}, '.ruby_vmanager' )) unless $self->rootdir;
51              
52 0 0         if ( not -d $self->rootdir ) {
53 0           say "root directory for installation not found.\nbootstraping to " . $self->rootdir;
54 0           mkdir $self->rootdir;
55 0           mkdir File::Spec->catdir($self->rootdir, 'bin');
56 0           mkdir File::Spec->catdir($self->rootdir, 'source');
57 0           mkdir File::Spec->catdir($self->rootdir, 'var');
58 0           mkdir File::Spec->catdir($self->rootdir, 'gemsets');
59 0           mkdir File::Spec->catdir($self->rootdir, 'rubies');
60             }
61              
62 0           return 1;
63              
64             }
65              
66             sub _check_db {
67 0     0     my ($self) = @_;
68              
69 0 0         $self->updatedb unless -f File::Spec->catfile($self->rootdir, 'var', 'db.yml');
70 0           $self->available_rubies( YAML::LoadFile( File::Spec->catfile($self->rootdir, 'var', 'db.yml')));
71              
72             }
73              
74             sub _check_installed {
75 0     0     my ($self) = @_;
76              
77 0           my $rubies = {};
78 0           my $checked_rubies = {};
79              
80 0 0         if ( -f File::Spec->catfile($self->rootdir, 'var', 'installed.yml' )) {
81 0           $rubies = YAML::LoadFile( File::Spec->catfile($self->rootdir, 'var', 'installed.yml' ));
82             }
83              
84 0           for my $major ( keys %$rubies ) {
85 0           for my $ruby ( @{ $rubies->{$major} } ) {
  0            
86 0 0         if ( -x File::Spec->catfile($self->rootdir, 'rubies', $major, $ruby, 'bin', 'ruby' )) {
87 0           push @{ $checked_rubies->{$major} }, $ruby;
  0            
88             }
89             }
90             }
91              
92 0           $self->installed_rubies($checked_rubies);
93              
94             }
95              
96             sub updatedb {
97 1     1   1245 no if $] >= 5.018, warnings => "experimental::smartmatch";
  1         13  
  1         8  
98 0     0 1   my ($self) = @_;
99              
100 0           my @versions = qw( 1.8 1.9 2.0 2.1 );
101              
102 0           my $rubies = {};
103              
104 0           for my $version (@versions) {
105 0           my $ruby_ftp = 'ftp://ftp.ruby-lang.org/pub/ruby/' . $version;
106 0           my $req = HTTP::Request->new( GET => $ruby_ftp );
107              
108 0           my $ua = LWP::UserAgent->new;
109 0           $ua->agent( $self->agent_string );
110              
111 0           my $res = $ua->request($req);
112              
113 0 0         if ( $res->is_success ) {
114 0           $rubies->{$version} = [];
115 0           for ( grep { $_ ~~ /ruby.*\.tar\.bz2/ } split '\n', $res->content ) {
  0            
116 0           my $at = $self->archive_type;
117 0           ( my $ruby = $_ ) =~ s/(.*)$at/$1/;
118 0           push @{ $rubies->{$version} }, ( split ' ', $ruby )[-1];
  0            
119             }
120             }
121             }
122              
123 0 0         die "Did not get any data from ftp.ruby-lang.org" unless %$rubies;
124              
125 0           YAML::DumpFile( File::Spec->catfile($self->rootdir, 'var', 'db.yml'), $rubies );
126              
127 0           $self->_check_db;
128              
129             }
130              
131             sub uninstall {
132 0     0 1   my ($self) = @_;
133              
134 0 0         return 0 unless $self->ruby_version;
135              
136 0           for my $major ( keys %{ $self->installed_rubies } ) {
  0            
137 0           for my $ruby ( @{ $self->installed_rubies->{$major} } ) {
  0            
138 0 0         if ( $ruby eq $self->ruby_version ) {
139 0           $self->major_version($major);
140 0           $self->_remove_ruby;
141 0           $self->_remove_source;
142 0           $self->_check_installed;
143 0           $self->_update_installed;
144             }
145             }
146             }
147              
148 0           return 1;
149             }
150              
151             sub _remove_ruby {
152 0     0     my ($self) = @_;
153              
154 0           my $dir_to_remove = File::Spec->catdir($self->rootdir, 'rubies', $self->major_version, $self->ruby_version);
155              
156 0 0         File::Path::rmtree($dir_to_remove) if -d $dir_to_remove;
157             }
158              
159             sub _remove_source {
160 0     0     my ($self) = @_;
161              
162 0           return 1;
163             }
164              
165             sub list {
166 0     0 1   my ($self) = @_;
167              
168 0 0         $self->_check_db or die;
169 0           my %rubies = %{ $self->available_rubies };
  0            
170 0           my %installed = %{ $self->installed_rubies };
  0            
171              
172 0           say "Available ruby versions";
173 0           for ( keys %rubies ) {
174 0           say "\tVersion $_:";
175 0           my @rubies = $self->_sort_rubies( $rubies{$_} );
176 0           for (@rubies) {
177 0           my $at = $self->archive_type;
178 0           ( my $ruby = $_ ) =~ s/(.*)$at/$1/;
179 0           say "\t\t$ruby";
180             }
181             }
182              
183 0           say "Installed ruby versions";
184 0           for ( keys %installed ) {
185 0           say "\tVersion: $_";
186 0           for ( @{ $installed{$_} } ) {
  0            
187 0           say "\t\t$_";
188             }
189             }
190             }
191              
192             sub gem {
193 0     0 1   my ( $self, $action, @args ) = @_;
194              
195 0           my $gem = Ruby::VersionManager::Gem->new;
196 0           $gem->run_action( $action, @args );
197              
198 0           return 1;
199             }
200              
201             sub switch_gemset {
202 1     1   1047 no if $] >= 5.018, warnings => "experimental::smartmatch";
  1         2  
  1         5  
203 0     0 1   my ($self, $gemset) = @_;
204              
205 0 0 0       if ($ENV{RUBY_VERSION} && $gemset){
206 0           $self->ruby_version($ENV{RUBY_VERSION});
207 0           ( my $major_version = $self->ruby_version ) =~ s/ruby-(\d\.\d).*/$1/;
208 0           $self->major_version($major_version);
209 0           $self->_check_installed;
210              
211 0           my $installed = $self->installed_rubies->{$major_version};
212              
213 0 0         if ($self->ruby_version ~~ @$installed){
214 0           $self->gemset($gemset);
215 0           $self->_setup_environment;
216              
217 0           $self->_sub_shell;
218             }
219             }
220              
221 0           return 0;
222             }
223              
224             sub gemsets {
225 1     1   290 no if $] >= 5.018, warnings => "experimental::smartmatch";
  1         1  
  1         6  
226 0     0 1   my $self = shift;
227              
228 0           my @gemsets = ();
229              
230 0 0         if ($ENV{RUBY_VERSION}) {
231 0           $self->ruby_version($ENV{RUBY_VERSION});
232 0           ( my $major_version = $self->ruby_version ) =~ s/ruby-(\d\.\d).*/$1/;
233 0           $self->major_version($major_version);
234 0           $self->_check_installed;
235              
236 0           my $installed = $self->installed_rubies->{$major_version};
237              
238 0 0         if ($self->ruby_version ~~ @$installed){
239 0           my $dir = File::Spec->catdir($self->rootdir, 'gemsets', $self->major_version, $self->ruby_version);
240 0   0       opendir my $dh, $dir || die "Could not open $dir.";
241             # filter . and .. and mark current gemset with a *
242 0 0         @gemsets = map { $ENV{GEM_PATH} =~ /$_/ ? "$_ *" : $_ } grep { !/^\.\.?$/ } readdir $dh;
  0            
  0            
243             }
244             }
245              
246 0 0         return wantarray ? @gemsets : [@gemsets];
247             }
248              
249             sub _sort_rubies {
250 0     0     my ( $self, $rubies ) = @_;
251              
252 0           my @sorted = ();
253 0           my $major_versions;
254              
255 0           for (@$rubies) {
256 0           my ( undef, $major, $patchlevel ) = split '-', $_;
257 0 0         $major_versions->{$major} = [] unless $major_versions->{$major};
258 0 0         $patchlevel = 'x' if !defined($patchlevel);
259 0           push @{ $major_versions->{$major} }, $patchlevel;
  0            
260             }
261              
262 0           for my $version ( sort { $a cmp $b } keys %{$major_versions} ) {
  0            
  0            
263 0 0         my @patchlevels = grep { defined $_ && $_ =~ /p\d{1,3}/ } @{ $major_versions->{$version} };
  0            
  0            
264 0 0         my @pre = grep { defined $_ && $_ =~ /preview\d{0,1}|rc\d{0,1}/ } @{ $major_versions->{$version} };
  0            
  0            
265 0 0         my @old = grep { defined $_ && $_ =~ /^\d/ } @{ $major_versions->{$version} };
  0            
  0            
266 0 0         my @no_plevel = grep { defined $_ && $_ =~ 'x' } @{ $major_versions->{$version} };
  0            
  0            
267              
268 0           my @numeric_levels;
269 0           for my $level (@patchlevels) {
270 0           ( my $num = $level ) =~ s/p(\d+)/$1/;
271 0           push @numeric_levels, $num;
272             }
273              
274 0           @patchlevels = ();
275 0           for ( sort { $a <=> $b } @numeric_levels ) {
  0            
276 0           push @patchlevels, 'p' . $_;
277             }
278              
279 0           for ( ( sort { $a cmp $b } @old ), @patchlevels, ( sort { $a cmp $b } @pre ) ) {
  0            
  0            
280 0           push @sorted, "ruby-$version-$_";
281             }
282              
283 0           for ( ( sort { $a cmp $b } @no_plevel ) ) {
  0            
284 0           push @sorted, "ruby-$version";
285             }
286             }
287              
288 0           return @sorted;
289             }
290              
291             sub _guess_version {
292 0     0     my ($self) = @_;
293              
294 0           my @rubies = ();
295 0           my $req_version = $self->ruby_version;
296              
297             # 1.8 or 1.9?
298 0           for my $major_version ( keys %{ $self->available_rubies } ) {
  0            
299 0 0         if ( $req_version =~ /$major_version/ ) {
300 0           for my $ruby ( @{ $self->available_rubies->{$major_version} } ) {
  0            
301 0 0         if ( $ruby =~ /$req_version/ ) {
302              
303 0           my $at = $self->archive_type;
304 0           ( $ruby = $ruby ) =~ s/(.*)$at/$1/;
305              
306 0 0         if ( $ruby eq $req_version ) {
    0          
307 0           push @rubies, $ruby;
308 0           last;
309             }
310             elsif ( $ruby =~ /preview|rc\d?+/ ) {
311 0           next;
312             }
313              
314 0           push @rubies, $ruby;
315             }
316             }
317             }
318             }
319              
320 0           my $guess = ( $self->_sort_rubies( [@rubies] ) )[-1];
321              
322 0 0         if ( not $guess ) {
323 0           say "No matching version found. Valid versions:";
324 0           $self->list;
325              
326 0           exit 1;
327             }
328              
329 0           return $guess;
330             }
331              
332             sub install {
333 1     1   1383 no if $] >= 5.018, warnings => "experimental::smartmatch";
  1         2  
  1         4  
334 0     0 1   my ($self) = @_;
335              
336 0           $self->ruby_version( $self->_guess_version );
337 0           ( my $major_version = $self->ruby_version ) =~ s/ruby-(\d\.\d).*/$1/;
338 0           $self->major_version($major_version);
339              
340 0           my $ruby = $self->ruby_version;
341 0           my $installed = 0;
342 0 0         $installed = 1 if join ' ', @{ $self->installed_rubies->{$major_version} } ~~ /$ruby/;
  0            
343              
344 0 0         if ( not $installed ) {
345 0           $self->_fetch_ruby;
346 0           $self->_unpack_ruby;
347 0           $self->_make_install;
348             }
349              
350 0           $self->_setup_environment;
351              
352 0 0         if ( not $installed ) {
353 0           $self->_install_rubygems;
354 0 0         push @{ $self->installed_rubies->{$major_version} }, $ruby unless $installed;
  0            
355 0           $self->_update_installed;
356             }
357              
358 0           $self->_sub_shell;
359             }
360              
361             sub _update_installed {
362 0     0     my ($self) = @_;
363              
364 0           YAML::DumpFile( File::Spec->catfile($self->rootdir, 'var', 'installed.yml'), $self->installed_rubies );
365              
366             }
367              
368             sub _unpack_ruby {
369 0     0     my ($self) = @_;
370              
371 0           system 'tar xf ' . File::Spec->catfile($self->rootdir, 'source', $self->ruby_version . $self->archive_type) . ' -C ' . File::Spec->catdir($self->rootdir, 'source');
372              
373 0           return 1;
374             }
375              
376             sub _make_install {
377 0     0     my ($self) = @_;
378              
379 0           my $prefix = File::Spec->catdir($self->rootdir, 'rubies', $self->major_version, $self->ruby_version);
380              
381 0           my $cwd = cwd();
382              
383 0           chdir File::Spec->catdir($self->rootdir, 'source', $self->ruby_version);
384              
385             # TODO make more portable
386             # TODO make options depend on ruby version
387             # TODO make silent
388 0           my $cores = 1;
389 0           my $nproc = `which nproc`;
390 0           chomp $nproc;
391 0 0         if (-x $nproc){
392 0           $cores = `$nproc`;
393 0           chomp $cores;
394             }
395 0           system "./configure --with-ssl --with-yaml --enable-ipv6 --enable-pthread --enable-shared --prefix=$prefix && make -j$cores && make install";
396              
397 0           chdir $cwd;
398              
399 0           return 1;
400             }
401              
402             sub _setup_environment {
403 0     0     my ($self) = @_;
404              
405 0           $ENV{PATH} = $self->_clean_path;
406              
407 0           $ENV{RUBY_VERSION} = $self->ruby_version;
408 0           $ENV{GEM_PATH} = File::Spec->catdir( abs_path( $self->rootdir ), 'gemsets', $self->major_version, $self->ruby_version, $self->gemset );
409 0           $ENV{GEM_HOME} = File::Spec->catdir( abs_path( $self->rootdir ), 'gemsets', $self->major_version, $self->ruby_version, $self->gemset );
410 0           $ENV{MY_RUBY_HOME} = File::Spec->catdir( abs_path( $self->rootdir ), 'rubies', $self->major_version, $self->ruby_version );
411 0           $ENV{PATH} = File::Spec->catdir( abs_path( $self->rootdir ), 'rubies', $self->major_version, $self->ruby_version, 'bin' )
412             . ':'
413             . File::Spec->catdir( abs_path( $self->rootdir ), 'gemsets', $self->major_version, $self->ruby_version, $self->gemset, 'bin' )
414             . ':'
415             . $ENV{PATH};
416              
417 0           open my $rcfile, '>', File::Spec->catfile($self->rootdir, 'var', 'ruby_vmanager.rc');
418 0           say $rcfile 'export RUBY_VERSION=' . $self->ruby_version;
419 0           say $rcfile 'export GEM_PATH=' . $ENV{GEM_PATH};
420 0           say $rcfile 'export GEM_HOME=' . $ENV{GEM_HOME};
421 0           say $rcfile 'export MY_RUBY_HOME=' . $ENV{MY_RUBY_HOME};
422 0           say $rcfile 'export PATH=' . File::Spec->catdir( abs_path( $self->rootdir ), 'rubies', $self->major_version, $self->ruby_version, 'bin' )
423             . ':'
424             . File::Spec->catdir( abs_path( $self->rootdir ), 'gemsets', $self->major_version, $self->ruby_version, $self->gemset, 'bin' )
425             . ':'
426             . $ENV{PATH};
427              
428 0           close $rcfile;
429              
430 0           return 1;
431             }
432              
433             sub _clean_path {
434 0     0     my $self = shift;
435 0           my $rootdir = $self->rootdir;
436 0           my $seen = {};
437 0           my @path = grep {
438 0           $seen->{$_}++;
439 0 0         $seen->{$_} <= 1 && $_ !~ /$rootdir/
440             } split ':', $ENV{PATH};
441              
442 0           return join ':', @path;
443             }
444              
445             sub _sub_shell {
446             # disable. find better way
447             return
448 0     0     my $self = shift;
449 0           my $shell = $ENV{SHELL};
450              
451 0 0         if ($shell) {
452 0           say "launching subshell with new settings.";
453 0           exec($shell);
454             }
455             }
456              
457             sub _fetch_ruby {
458 0     0     my ($self) = @_;
459              
460 0           my $url = 'ftp://ftp.ruby-lang.org/pub/ruby/' . $self->major_version . '/' . $self->ruby_version . $self->archive_type;
461              
462 0           my $file = File::Spec->catfile($self->rootdir, 'source', $self->ruby_version . $self->archive_type);
463              
464 0 0         if ( -f $file ) {
465 0           return 1;
466             }
467              
468 0           my $result = LWP::Simple::getstore( $url, $file );
469              
470 0 0         die if $result != 200;
471              
472 0           return 1;
473              
474             }
475              
476             sub _install_rubygems {
477 0     0     my ($self) = @_;
478              
479 0 0         if ( -d File::Spec->catdir($self->rootdir, 'source', $self->ruby_version, 'bin') ) {
480 0           my $source_bin_dir = File::Spec->catdir($self->rootdir, 'source', $self->ruby_version, 'bin');
481 0           my $ruby_bin_dir = File::Spec->catdir($ENV{MY_RUBY_HOME}, 'bin');
482 0           system "cp $source_bin_dir/* $ruby_bin_dir";
483             }
484              
485 0 0         unless ( -f $ENV{MY_RUBY_HOME} . '/bin/gem' ) {
486 0           my $url = 'http://rubyforge.org/frs/download.php/70696/rubygems-1.3.7.tgz';
487 0           my $file = File::Spec->catfile($self->rootdir, 'source', 'rubygems-1.3.7.tgz');
488              
489 0 0         unless ( -f $file ) {
490 0           my $result = LWP::Simple::getstore( $url, $file );
491 0 0         die if $result != 200;
492             }
493              
494 0           system 'tar xf ' . $file . ' -C ' . File::Spec->catdir($self->rootdir, 'source');
495              
496 0           my $cwd = cwd();
497              
498 0           chdir File::Spec->catdir($self->rootdir, 'source', 'rubygems-1.3.7');
499 0           system 'ruby setup.rb';
500             }
501              
502 0           return 1;
503             }
504              
505             1;
506              
507             __END__