File Coverage

blib/lib/App/Rakubrew.pm
Criterion Covered Total %
statement 44 349 12.6
branch 0 200 0.0
condition 0 131 0.0
subroutine 15 29 51.7
pod 0 9 0.0
total 59 718 8.2


line stmt bran cond sub pod time code
1             package App::Rakubrew;
2 2     2   133961 use strict;
  2         25  
  2         60  
3 2     2   10 use warnings;
  2         4  
  2         60  
4 2     2   76 use 5.010;
  2         7  
5             our $VERSION = '36';
6              
7 2     2   961 use Encode::Locale qw(env);
  2         28319  
  2         190  
8             if (-t) {
9             binmode(STDIN, ":encoding(console_in)");
10             binmode(STDOUT, ":encoding(console_out)");
11             binmode(STDERR, ":encoding(console_out)");
12             }
13 2     2   872 use FindBin qw($RealBin);
  2         2158  
  2         285  
14 2     2   17 use File::Path qw(remove_tree);
  2         4  
  2         125  
15 2     2   864 use File::Spec::Functions qw(catfile catdir splitpath updir rel2abs);
  2         1721  
  2         139  
16              
17 2     2   978 use App::Rakubrew::Build;
  2         7  
  2         114  
18 2     2   13 use App::Rakubrew::Config;
  2         4  
  2         174  
19 2     2   980 use App::Rakubrew::Download;
  2         7  
  2         124  
20 2     2   991 use App::Rakubrew::Shell;
  2         4  
  2         61  
21 2     2   13 use App::Rakubrew::Tools;
  2         14  
  2         181  
22 2     2   943 use App::Rakubrew::Update;
  2         6  
  2         103  
23 2     2   13 use App::Rakubrew::Variables;
  2         5  
  2         348  
24 2     2   16 use App::Rakubrew::VersionHandling;
  2         14  
  2         7999  
25              
26             sub new {
27 0     0 0   my ($class, @argv) = @_;
28 0           my %opt = (
29             args => \@argv,
30             );
31 0           my $self = bless \%opt, $class;
32 0           return $self;
33             }
34              
35             sub run_script {
36 0     0 0   my ($self) = @_;
37 0           my @args = @{$self->{args}};
  0            
38              
39             sub _cant_access_home {
40 0     0     say STDERR "Can't create rakubrew home directory in $prefix";
41 0           say STDERR "Probably rakubrew was denied access. You can either change that folder to be writable";
42 0           say STDERR "or set a different rakubrew home directory by setting the `\$RAKUBREW_HOME` environment";
43 0           say STDERR "prior to calling the rakubrew shell hook. ";
44 0           exit 1;
45             }
46              
47 0 0         unless (-d $prefix) {
48 0 0         _cant_access_home() unless mkdir $prefix;
49             }
50              
51 0 0 0       mkdir(catdir($prefix, 'bin')) || _cant_access_home() unless (-d catdir($prefix, 'bin'));
52 0 0 0       mkdir(catdir($prefix, 'update')) || _cant_access_home() unless (-d catdir($prefix, 'update'));
53 0 0 0       mkdir(catdir($prefix, 'repos')) || _cant_access_home() unless (-d catdir($prefix, 'repos'));
54 0 0 0       mkdir $shim_dir || _cant_access_home() unless (-d $shim_dir);
55 0 0 0       mkdir $versions_dir || _cant_access_home() unless (-d $versions_dir);
56 0 0 0       mkdir $git_reference || _cant_access_home() unless (-d $git_reference);
57              
58             { # Check whether we are called as a shim and forward if yes.
59 0           my (undef, undef, $prog_name) = splitpath($0);
60              
61             # TODO: Mac is also case insensitive. Is this way to compensate for insensitivity safe?
62 0 0 0       if ($prog_name ne $brew_name &&
      0        
63             ($^O !~ /win32/i || $prog_name =~ /^\Q$brew_name\E\z/i)) {
64 0           $self->do_exec($prog_name, \@args);
65             }
66             }
67              
68             { # Detect shell environment and initialize the shell object.
69 0           my $shell = '';
  0            
  0            
70 0 0 0       $shell = $args[1] if @args >= 2 && $args[0] eq 'internal_shell_hook';
71 0 0 0       $shell = $args[1] if @args >= 2 && $args[0] eq 'internal_hooked';
72 0 0 0       $shell = $args[1] if @args >= 2 && $args[0] eq 'init';
73 0           $self->{hook} = App::Rakubrew::Shell->initialize($shell);
74             }
75              
76 0 0 0       if (@args >= 2 && $args[0] eq 'internal_hooked') { # The hook is there, all good!
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
77 0           shift @args; # Remove the hook so processing code below doesn't need to care about it.
78 0           shift @args; # Remove the shell parameter for the same reason.
79             }
80             elsif (
81             get_brew_mode() eq 'env'
82             && !(@args && $args[0] eq 'mode' && $args[1] eq 'shim')
83             && !(@args && $args[0] eq 'init')
84             && !(@args && $args[0] eq 'home')
85             && !(@args && $args[0] =~ /^internal_/)
86             || @args && $args[0] eq 'shell'
87             || @args >= 2 && $args[0] eq 'mode' && $args[1] eq 'env') {
88 0           say STDERR << "EOL";
89             The shell hook required to run rakubrew in either 'env' mode or with the 'shell' command seems not to be installed.
90             Run '$brew_name init' for installation instructions if you want to use those features,
91             or run '$brew_name mode shim' to use 'shim' mode which doesn't require a shell hook.
92             EOL
93 0           exit 1;
94             }
95              
96 0   0       my $arg = shift(@args) // 'help';
97              
98 0 0 0       if ($arg eq 'version' || $arg eq 'current') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
99 0 0         if (my $c = get_version()) {
100 0           say "Currently running $c"
101             } else {
102 0           say STDERR "Not running anything at the moment. Use '$brew_name switch' to set a version";
103 0           exit 1;
104             }
105              
106             } elsif ($arg eq 'versions' || $arg eq 'list') {
107 0   0       my $cur = get_version() // '';
108             map {
109 0           my $version_line = '';
  0            
110 0 0         $version_line .= 'BROKEN ' if is_version_broken($_);
111 0 0         $version_line .= $_ eq $cur ? '* ' : ' ';
112 0           $version_line .= $_;
113 0 0 0       $version_line .= ' -> ' . (get_version_path($_, 1) || '') if is_registered_version($_);
114 0           say $version_line;
115             } get_versions();
116              
117             } elsif ($arg eq 'global' || $arg eq 'switch') {
118 0 0         if (!@args) {
119 0           my $version = get_global_version();
120 0 0         if ($version) {
121 0           say $version;
122             }
123             else {
124 0           say "$brew_name: no global version configured";
125             }
126             }
127             else {
128             $self->match_and_run($args[0], sub {
129 0     0     set_global_version(shift);
130 0           });
131             }
132              
133             } elsif ($arg eq 'shell') {
134 0 0         if (!@args) {
135 0           my $shell_version = get_shell_version();
136 0 0         if (defined $shell_version) {
137 0           say "$shell_version";
138             }
139             else {
140 0           say "$brew_name: no shell-specific version configured";
141             }
142             }
143             else {
144 0           my $version = shift @args;
145 0 0         if ($version ne '--unset') {
146 0           verify_version($version);
147             }
148             }
149              
150             } elsif ($arg eq 'local') {
151 0           validate_brew_mode();
152 0 0         if (!@args) {
153 0           my $version = get_local_version();
154 0 0         if ($version) {
155 0           say $version;
156             }
157             else {
158 0           say "$brew_name: no local version configured for this directory";
159             }
160             }
161             else {
162 0           my $version = shift @args;
163 0 0         if ($version eq '--unset') {
164 0           set_local_version(undef);
165             }
166             else {
167             $self->match_and_run($version, sub {
168 0     0     set_local_version(shift);
169 0           });
170             }
171             }
172              
173             } elsif ($arg eq 'nuke' || $arg eq 'unregister') {
174 0           my $version = shift @args;
175 0           $self->nuke($version);
176              
177             } elsif ($arg eq 'rehash') {
178 0           validate_brew_mode();
179 0           rehash();
180              
181             } elsif ($arg eq 'list-available' || $arg eq 'available') {
182 0   0       my ($cur_backend, $cur_rakudo) = split '-', (get_version() // ''), 2;
183 0   0       $cur_backend //= '';
184 0   0       $cur_rakudo //= '';
185              
186 0           my @downloadables = App::Rakubrew::Download::available_precomp_archives();
187 0           say "Available Rakudo versions:";
188             map {
189 0           my $ver = $_;
  0            
190 0 0         my $d = (grep {$_->{ver} eq $ver} @downloadables) ? 'D' : ' ';
  0            
191 0 0         my $s = $cur_rakudo eq $ver ? '*' : ' ';
192 0           say "$s$d $ver";
193             } App::Rakubrew::Build::available_rakudos();
194 0           say '';
195 0           $cur_backend |= '';
196 0           $cur_rakudo |= '';
197 0           say "Available backends:";
198 0 0         map { say $cur_backend eq $_ ? "* $_" : " $_" } App::Rakubrew::Variables::available_backends();
  0            
199              
200             } elsif ($arg eq 'build-rakudo' || $arg eq 'build') {
201 0           my ($impl, $ver, @args) =
202             App::Rakubrew::VersionHandling::match_version(@args);
203 0 0         if (!$ver) {
204 0           my @versions = App::Rakubrew::Build::available_rakudos();
205 0           @versions = grep { /^\d\d\d\d\.\d\d/ } @versions;
  0            
206 0           $ver = $versions[-1];
207             }
208              
209 0 0         if ($impl eq "panda") {
    0          
    0          
210 0           say "panda is discontinued; please use zef (rakubrew build-zef) instead";
211             } elsif ($impl eq "zef") {
212 0           my $version = get_version();
213 0 0         if (!$version) {
214 0           say STDERR "$brew_name: No version set.";
215 0           exit 1;
216             }
217 0           App::Rakubrew::Build::build_zef($version);
218             # Might have new executables now -> rehash.
219 0           rehash();
220 0           say "Done, built zef for $version";
221             } elsif (!exists $impls{$impl}) {
222 0           my $warning = "Cannot build Rakudo with backend '$impl': this backend ";
223 0 0         if ($impl eq "parrot") {
224 0           $warning .= "is no longer supported.";
225             } else {
226 0           $warning .= "does not exist.";
227             }
228 0           say $warning;
229 0           exit 1;
230             }
231             else {
232 0           my $configure_opts = '';
233 0 0 0       if (@args && $args[0] =~ /^--configure-opts=/) {
234 0           $configure_opts = shift @args;
235 0           $configure_opts =~ s/^\-\-configure-opts=//;
236 0           $configure_opts =~ s/^'//;
237 0           $configure_opts =~ s/'$//;
238             }
239              
240 0           my $name = "$impl-$ver";
241 0 0 0       $name = $impl if $impl eq 'moar-blead' && $ver eq 'main';
242              
243 0 0 0       if ($impl && $impl eq 'all') {
244 0           for (App::Rakubrew::Variables::available_backends()) {
245 0           App::Rakubrew::Build::build_impl($_, $ver, $configure_opts);
246             }
247             } else {
248 0           App::Rakubrew::Build::build_impl($impl, $ver, $configure_opts);
249             }
250              
251             # Might have new executables now -> rehash.
252 0           rehash();
253 0 0         if (get_version() eq 'system') {
254 0           set_global_version($name);
255             }
256 0           say "Done, $name built";
257             }
258              
259             } elsif ($arg eq 'triple') {
260 0           my ($rakudo_ver, $nqp_ver, $moar_ver) = @args[0 .. 2];
261 0           my $name = App::Rakubrew::Build::build_triple($rakudo_ver, $nqp_ver, $moar_ver);
262              
263             # Might have new executables now -> rehash
264 0           rehash();
265 0 0         if (get_version() eq 'system') {
266 0           set_global_version($name);
267             }
268 0           say "Done, $name built";
269              
270             } elsif ($arg eq 'download-rakudo' || $arg eq 'download') {
271 0           my ($impl, $ver, @args) =
272             App::Rakubrew::VersionHandling::match_version(@args);
273              
274 0 0         if (!exists $impls{$impl}) {
275 0           say STDERR "Cannot download Rakudo on '$impl': this backend does not exist.";
276 0           exit 1;
277             }
278              
279 0           my $name = App::Rakubrew::Download::download_precomp_archive($impl, $ver);
280              
281             # Might have new executables now -> rehash
282 0           rehash();
283 0 0         if (get_version() eq 'system') {
284 0           set_global_version("$name");
285             }
286 0           say "Done, $name installed";
287             } elsif ($arg eq 'register') {
288 0           my ($name, $path) = @args[0 .. 1];
289 0 0 0       if (!$name || !$path) {
290 0           say STDERR "$brew_name: Need a version name and rakudo installation path";
291 0           exit 1;
292             }
293 0 0         if (version_exists($name)) {
294 0           say STDERR "$brew_name: Version $name already exists";
295 0           exit 1;
296             }
297              
298             sub invalid {
299 0     0 0   my $path = shift;
300 0           say STDERR "$brew_name: No valid rakudo installation found at '$path'";
301 0           exit 1;
302             }
303 0           $path = rel2abs($path);
304 0 0         invalid($path) if is_version_path_broken($path);
305 0           $path = clean_version_path($path);
306              
307 0           spurt(catfile($versions_dir, $name), $path);
308              
309             } elsif ($arg eq 'build-zef') {
310 0           my $version = get_version();
311 0           my $zef_version = shift(@args);
312 0 0         if (!$version) {
313 0           say STDERR "$brew_name: No version set.";
314 0           exit 1;
315             }
316 0   0       say("Building zef ", $zef_version || "latest");
317 0           App::Rakubrew::Build::build_zef($version, $zef_version);
318             # Might have new executables now -> rehash
319 0           rehash();
320 0           say "Done, built zef for $version";
321              
322             } elsif ($arg eq 'build-panda') {
323 0           say "panda is discontinued; please use zef (rakubrew build-zef) instead";
324              
325             } elsif ($arg eq 'exec') {
326 0           my $prog_name = shift @args;
327 0           $self->do_exec($prog_name, \@args);
328              
329             } elsif ($arg eq 'which') {
330 0 0         if (!@args) {
331 0           say STDERR "Usage: $brew_name which ";
332             }
333             else {
334 0           my $version = get_version();
335 0 0         if (!$version) {
336 0           say STDERR "$brew_name: No version set.";
337 0           exit 1;
338             }
339 0           map {say $_} which($args[0], $version);
  0            
340             }
341              
342             } elsif ($arg eq 'whence') {
343 0 0         if (!@args) {
344 0           say STDERR "Usage: $brew_name whence [--path] ";
345             }
346             else {
347 0           my $param = shift @args;
348 0           my $pathmode = $param eq '--path';
349 0 0         my $prog = $pathmode ? shift(@args) : $param;
350 0           map {say $_} whence($prog, $pathmode);
  0            
351             }
352              
353             } elsif ($arg eq 'mode') {
354 0 0         if (!@args) {
355 0           say get_brew_mode();
356             }
357             else {
358 0           set_brew_mode($args[0]);
359             }
360              
361             } elsif ($arg eq 'self-upgrade') {
362 0           App::Rakubrew::Update::update();
363              
364             } elsif ($arg eq 'init') {
365 0           $self->init(@args);
366              
367             } elsif ($arg eq 'home') {
368 0           say $prefix;
369              
370             } elsif ($arg eq 'test') {
371 0           my $version = shift @args;
372 0 0         if (!$version) {
    0          
373 0           $self->test(get_version());
374             }
375             elsif ($version eq 'all') {
376 0           for (get_versions()) {
377 0           $self->test($_);
378             }
379             } else {
380 0           $self->test($version);
381             }
382             } elsif ($arg eq 'internal_shell_hook') {
383 0           my $shell = shift @args;
384 0           my $sub = shift @args;
385 0 0         if (my $ref = $self->{hook}->can($sub)) {
386 0           $self->{hook}->$sub(@args);
387             }
388              
389             } elsif ($arg eq 'internal_win_run') {
390 0           my $prog_name = shift @args;
391 0           my $path = which($prog_name, get_version());
392             # Do some filetype detection:
393             # - .exe/.bat/.cmd -> return "filename"
394             # - .nqp -> return "nqp filename"
395             # - shebang contains raku|perl6 -> return "raku|perl6 filename"
396             # - shebang contains perl -> return "perl filename"
397             # - nothing of the above -> return "filename" # if we can't
398             # figure out what to do with this
399             # filename, let Windows have a try.
400             # The first line is potentially the shebang. Thus the search for "perl" and/or perl6/raku.
401 0           my ($basename, undef, $suffix) = my_fileparse($prog_name);
402 0 0         if($suffix =~ /^\Q\.(exe|bat|cmd)\E\z/i) {
    0          
403 0           say $path;
404             }
405             elsif($suffix =~ /^\Q\.nqp\E\z/i) {
406 0           say which('nqp', get_version()).' '.$path;
407             }
408             else {
409 0           open(my $fh, '<', $path);
410 0           my $first_line = <$fh>;
411 0           close($fh);
412 0 0         if($first_line =~ /#!.*(perl6|raku)/) {
    0          
413 0           say get_raku(get_version()) . ' ' . $path;
414             }
415             elsif($first_line =~ /#!.*perl/) {
416 0           say 'perl '.$path;
417             }
418             else {
419 0           say $path;
420             }
421             }
422              
423             } elsif ($arg eq 'internal_update') {
424 0           App::Rakubrew::Update::internal_update(@args);
425              
426             } elsif ($arg eq 'rakubrew-version') {
427 0           say "rakubrew v$VERSION Build type: $distro_format OS: $^O";
428              
429             } else {
430 0           require Pod::Usage;
431 0           my $help_text = "";
432 0           open my $pod_fh, ">", \$help_text;
433              
434 0           my $verbose = 0;
435             @args = grep {
436 0 0 0       if ($_ eq '-v' || $_ eq '--verbose') {
  0            
437 0           $verbose = 1;
438 0           0;
439             }
440 0           else { 1; }
441             } @args;
442              
443 0 0 0       if ($arg eq 'help' && @args) {
444             # the user wants help for a specific command
445             # e.g., rakubrew help list
446 0           my $command = $args[ 0 ];
447 0 0         $command = 'download-rakudo' if $command eq 'download';
448 0 0         $command = 'build-rakudo' if $command eq 'build';
449              
450 0           Pod::Usage::pod2usage(
451             -exitval => "NOEXIT", # do not terminate this script!
452             -verbose => 99, # 99 = indicate the sections
453             -sections => "COMMAND: " . lc( $command ), # e.g.: COMMAND: list
454             -output => $pod_fh, # filehandle reference
455             -noperldoc => 1 # do not call perldoc
456             );
457              
458             # some cleanup
459 0           $help_text =~ s/\A[^\n]+\n//s;
460 0           $help_text =~ s/^ //gm;
461              
462 0 0         $help_text = "Cannot find documentation for [$command]!" if ($help_text =~ /\A\s*\Z/);
463             }
464             else {
465             # Generic help or unknown command
466 0 0         Pod::Usage::pod2usage(
467             -exitval => "NOEXIT", # do not terminate this script!
468             -verbose => $verbose ? 2 : 1, # 1 = only SYNOPSIS, 2 = print everything
469             -output => $pod_fh, # filehandle reference
470             -noperldoc => 1 # do not call perldoc
471             );
472             }
473              
474 0           close $pod_fh;
475              
476 0           my $backends = join '|', App::Rakubrew::Variables::available_backends(), 'all';
477              
478 0           say $help_text;
479             }
480             }
481              
482             sub match_and_run {
483 0     0 0   my ($self, $version, $action) = @_;
484 0 0         if (!$version) {
485 0           say "Which version do you mean?";
486 0           say "Available builds:";
487 0           map {say} get_versions();
  0            
488 0           return;
489             }
490 0 0         if (grep { $_ eq $version } get_versions()) {
  0            
491 0           $action->($version);
492             }
493             else {
494 0           say "Sorry, '$version' not found.";
495 0           my @match = grep { /\Q$version/ } get_versions();
  0            
496 0 0         if (@match) {
497 0           say "Did you mean:";
498 0           say $_ for @match;
499             }
500             }
501             }
502              
503             sub test {
504 0     0 0   my ($self, $version) = @_;
505             $self->match_and_run($version, sub {
506 0     0     my $matched = shift;
507 0           verify_version($matched);
508 0           my $v_dir = catdir($versions_dir, $matched);
509 0 0         if (!-d $v_dir) {
510 0           say STDERR "Version $matched was not built by rakubrew.";
511 0           say STDERR "Refusing to try running spectest there.";
512 0           exit 1;
513             }
514 0           chdir catdir($versions_dir, $matched);
515 0           say "Spectesting $matched";
516 0 0         if (!-f 'Makefile') {
517 0           say STDERR "Can only run spectest in self built Rakudos.";
518 0           say STDERR "This Rakudo is not self built.";
519 0           exit 1;
520             }
521 0           run(App::Rakubrew::Build::determine_make($matched), 'spectest');
522 0           });
523             }
524              
525             sub nuke {
526 0     0 0   my ($self, $version) = @_;
527             $self->match_and_run($version, sub {
528 0     0     my $matched = shift;
529 0 0         if (is_registered_version($matched)) {
    0          
    0          
530 0           say "Unregistering $matched";
531 0           unlink(catfile($versions_dir, $matched));
532             }
533             elsif ($matched eq 'system') {
534 0           say 'I refuse to nuke system Raku!';
535 0           exit 1;
536             }
537             elsif ($matched eq get_version()) {
538 0           say "$matched is currently active. I refuse to nuke.";
539 0           exit 1;
540             }
541             else {
542 0           say "Nuking $matched";
543 0           remove_tree(catdir($versions_dir, $matched));
544             }
545 0           });
546             # Might have lost executables -> rehash
547 0           rehash();
548             }
549              
550             sub init {
551 0     0 0   my $self = shift;
552 0           my $brew_exec = catfile($RealBin, $brew_name);
553 0 0         if (@_) {
554             # We have an argument. That has to be the shell.
555             # We already retrieved the shell above, so no need to look at the passed argument here again.
556 0           say $self->{hook}->get_init_code;
557             }
558             else {
559 0           say $self->{hook}->install_note;
560             }
561             }
562              
563             sub de_par_environment {
564             # The PAR packager modifies the environment.
565             # We undo those modifications here.
566              
567             # The following code was kindly provided by Roderich Schupp
568             # via email.
569 0     0 0   my $ldlibpthname = $Config::Config{ldlibpthname};
570 0           my $path_sep = $Config::Config{path_sep};
571 0           $ENV{$ldlibpthname} =~ s/^ \Q$ENV{PAR_TEMP}\E $path_sep? //x;
572              
573 0           delete $ENV{PAR_0};
574 0           delete $ENV{PAR_INITIALIZED};
575 0           delete $ENV{PAR_PROGNAME};
576 0           delete $ENV{PAR_TEMP};
577             }
578              
579             sub do_exec {
580 0     0 0   my ($self, $program, $args) = @_;
581              
582 0           my $target = which($program, get_version());
583              
584             # Undo PAR env modifications.
585             # Only need to do this on MacOS, as only there
586             # PAR is used and rakubrew itself does the `exec`.
587             # (Windows also uses PAR, but has a .bat shim that
588             # does the `exec`.)
589 0 0 0       if ($distro_format eq 'macos' || $distro_format eq 'macos_arm') {
590 0           de_par_environment;
591             }
592            
593             # Run.
594 0           exec { $target } ($target, @$args);
  0            
595 0           die "Executing $target failed with: $!";
596             }
597              
598             1;
599              
600             __END__