File Coverage

blib/lib/App/Rakubrew/Download.pm
Criterion Covered Total %
statement 44 158 27.8
branch 0 72 0.0
condition 0 35 0.0
subroutine 15 23 65.2
pod 0 2 0.0
total 59 290 20.3


line stmt bran cond sub pod time code
1             package App::Rakubrew::Download;
2             require Exporter;
3             our @ISA = qw(Exporter);
4             our @EXPORT = qw();
5              
6 14     14   108 use strict;
  14         28  
  14         506  
7 14     14   56 use warnings;
  14         24  
  14         750  
8 14     14   214 use 5.010;
  14         47  
9 14     14   6636 use HTTP::Tinyish;
  14         15195  
  14         461  
10 14     14   9022 use JSON;
  14         210263  
  14         107  
11 14     14   2575 use Config;
  14         33  
  14         824  
12 14     14   81 use Cwd qw(cwd);
  14         29  
  14         1043  
13 14     14   9495 use IO::Uncompress::Unzip qw( $UnzipError );
  14         842948  
  14         2058  
14 14     14   133 use File::Path qw( make_path remove_tree );
  14         30  
  14         954  
15 14     14   12955 use File::Copy::Recursive qw( dirmove );
  14         135456  
  14         1397  
16 14     14   124 use File::Spec::Functions qw( updir splitpath catfile catdir );
  14         30  
  14         916  
17 14     14   78 use File::Which qw();
  14         30  
  14         297  
18 14     14   63 use App::Rakubrew::Variables;
  14         30  
  14         2666  
19 14     14   94 use App::Rakubrew::Tools;
  14         30  
  14         1336  
20 14     14   109 use App::Rakubrew::VersionHandling;
  14         28  
  14         32307  
21              
22             my $release_index_url = 'https://rakudo.org/dl/rakudo';
23             my $download_url_prefix = 'https://rakudo.org/dl/rakudo/';
24              
25             sub download_precomp_archive {
26 0     0 0   my ($impl, $ver) = @_;
27              
28 0           my $ht = HTTP::Tinyish->new();
29              
30             my @matching_releases = grep {
31 0 0         $_->{backend} eq $impl && ($ver ? $_->{ver} eq $ver : 1)
  0 0          
32             } _retrieve_releases($ht);
33              
34 0 0         if (!@matching_releases) {
35 0 0         say STDERR 'Couldn\'t find a precomp release for OS: "' . _my_platform() . '", architecture: "' . _my_arch() . '"' . ($ver ? (', version: "' . $ver . '"') : '');
36 0           say STDERR 'You can try building yourself. Use the `rakubrew build` command to do so.';
37 0           exit 1;
38             }
39 0 0 0       if ($ver && @matching_releases > 1) {
40 0           say STDERR 'Multiple releases found for your architecture. Don\'t know what to install. This shouldn\'t happen.';
41 0           exit 1;
42             }
43              
44 0 0         if (!$ver) {
45 0           $ver = $matching_releases[0]->{ver};
46             }
47              
48 0           my $name = "$impl-$ver";
49              
50 0           chdir $versions_dir;
51 0 0         if (-d $name) {
52 0           say STDERR "$name is already installed.";
53 0           exit 1;
54             }
55              
56 0           say 'Downloading ' . $matching_releases[0]->{url};
57 0           my $res = $ht->get($matching_releases[0]->{url});
58 0 0         unless ($res->{success}) {
59 0           say STDERR "Couldn\'t download release. Error: $res->{status} $res->{reason}";
60 0           exit 1;
61             }
62              
63 0           mkdir $name;
64 0           say 'Extracting';
65 0 0         if (_my_platform() eq 'win') {
66 0           _unzip(\($res->{content}), $name);
67             }
68             else {
69 0           _untar($res->{content}, $name);
70             }
71              
72             # Remove top-level rakudo-2020.01 folder and move all files one level up.
73 0           my $back = cwd();
74 0           chdir $name;
75 0           my $rakudo_dir;
76 0 0         opendir(DIR, '.') || die "Can't open directory: $!\n";
77 0           while (my $file = readdir(DIR)) {
78 0 0 0       if (-d $file && $file =~ /^rakudo-/) {
79 0           $rakudo_dir = $file;
80 0           last;
81             }
82             }
83 0           closedir(DIR);
84 0 0         unless ($rakudo_dir) {
85 0           say STDERR "Archive didn't look as expected, aborting. Extracted to: $name";
86 0           exit 1;
87             }
88 0           dirmove($rakudo_dir, '.');
89 0           rmdir($rakudo_dir);
90 0           chdir $back;
91              
92 0           return $name;
93             }
94              
95             sub available_precomp_archives {
96 0     0 0   return _retrieve_releases(HTTP::Tinyish->new());
97             }
98              
99             sub _retrieve_releases {
100 0     0     my $ht = shift;
101 0           my $release_index = _download_release_index($ht);
102             my @matching_releases =
103 0           sort { $b->{ver} cmp $a->{ver} }
104             grep {
105 0           $_->{name} eq 'rakudo'
106             && $_->{type} eq 'archive'
107             && $_->{platform} eq _my_platform()
108             && $_->{arch} eq _my_arch()
109 0 0 0       && $_->{format} eq (_my_platform() eq 'win' ? 'zip' : 'tar.gz')
    0 0        
      0        
110             } @$release_index;
111              
112             # Filter out older build revisions
113             @matching_releases = grep {
114 0           my $this = $_;
  0            
115             not grep {
116 0           +($_->{build_rev}) > +($this->{build_rev})
117             && $_->{name} eq $this->{name}
118             && $_->{type} eq $this->{type}
119             && $_->{platform} eq $this->{platform}
120             && $_->{arch} eq $this->{arch}
121             && $_->{format} eq $this->{format}
122 0 0 0       && $_->{ver} eq $this->{ver};
      0        
      0        
      0        
      0        
123             } @matching_releases;
124             } @matching_releases;
125              
126 0           return @matching_releases;
127             }
128              
129             sub _my_platform {
130 0     0     my %oses = (
131             MSWin32 => 'win',
132             darwin => 'macos',
133             linux => 'linux',
134             openbsd => 'openbsd',
135             );
136 0   0       return $oses{$^O} // $^O;
137             }
138              
139             sub _my_arch {
140 0     0     my $arch;
141             my $mac_brand_string;
142 0 0         if ($Config{archname} =~ /darwin/i) {
143             # Some MacOS' have sysctl in /usr/sbin/ and that dir not in path.
144             # Seems to be the case at least on Ventura 13.5.1
145             # See https://github.com/Raku/App-Rakubrew/issues/77
146 0           my @mac_sysctls = File::Which::which('sysctl');
147 0   0       my $mac_sysctl = $mac_sysctls[0] // '/usr/sbin/sysctl';
148 0           $mac_brand_string = `$mac_sysctl -n machdep.cpu.brand_string`;
149 0 0         $arch =
    0          
150             $mac_brand_string =~ /Apple/i ? 'arm64' : # MacOS M1 / Apple Silicon
151             $mac_brand_string =~ /Intel/i ? 'x86_64' : # MacOS Intel
152             '';
153             }
154             else {
155             $arch =
156             $Config{archname} =~ /x64/i ? 'x86_64' :
157             $Config{archname} =~ /x86_64/i ? 'x86_64' :
158             $Config{archname} =~ /amd64/i ? 'x86_64' :
159             $Config{archname} =~ /x86/i ? 'x86' :
160             $Config{archname} =~ /i686/i ? 'x86' :
161             $Config{archname} =~ /aarch64/i ? 'arm64' : # e.g. Raspi >= 2.1 with 64bit OS
162             $Config{archname} =~ /arm-linux-gnueabihf/i ? 'armhf' : # e.g. Raspi >= 2, with 32bit OS
163 0 0         $Config{archname} =~ /s390x-linux/i ? 's390x' :
    0          
    0          
    0          
    0          
    0          
    0          
    0          
164             '';
165             }
166              
167 0 0         unless ($arch) {
168 0           say STDERR 'Couldn\'t detect system architecture. Current arch is: ' . $Config{archname};
169 0           say STDERR 'Current uname -a is: ' . `uname -a`;
170 0 0         say STDERR 'Current machdep.cpu.brand_string is: ' . $mac_brand_string if $mac_brand_string;
171 0           exit 1;
172             }
173 0           return $arch;
174             }
175              
176             sub _download_release_index {
177 0     0     my $ht = shift;
178 0           my $res = $ht->get($release_index_url);
179 0 0         unless ($res->{success}) {
180 0           say STDERR "Couldn\'t fetch release index at $release_index_url. Error: $res->{status} $res->{reason}";
181 0           exit 1;
182             }
183 0           return decode_json($res->{content});
184             }
185              
186             sub _untar {
187 0     0     my ($data, $target) = @_;
188 0           my $back = cwd();
189 0           chdir $target;
190 0           open (TAR, '| tar -xz');
191 0           binmode(TAR);
192 0           print TAR $data;
193 0           close TAR;
194 0           chdir $back;
195             }
196              
197             sub _unzip {
198 0     0     my ($data_ref, $target) = @_;
199              
200 0           my $zip = IO::Uncompress::Unzip->new($data_ref);
201 0 0         unless ($zip) {
202 0           say STDERR "Reading zip file failed. Error: $UnzipError";
203 0           exit 1;
204             }
205              
206 0           my $status;
207 0           for ($status = 1; $status > 0; $status = $zip->nextStream()) {
208 0           my $header = $zip->getHeaderInfo();
209              
210 0           my ($vol, $path, $file) = splitpath($header->{Name});
211              
212 0 0         if (index($path, updir()) != -1) {
213 0           say STDERR 'Found updirs in zip file, this is bad. Aborting.';
214 0           exit 1;
215             }
216              
217 0           my $target_dir = catdir($target, $path);
218              
219 0 0         unless (-d $target_dir) {
220 0 0         unless (make_path($target_dir)) {
221 0           say STDERR "Failed to create directory $target_dir. Error: $!";
222 0           exit 1;
223             }
224             }
225              
226 0 0         next unless $file;
227              
228 0           my $target_file = catfile($target, $path, $file);
229              
230 0 0         unless (open(FH, '>', $target_file)) {
231 0           say STDERR "Failed to write $target_file. Error: $!";
232 0           exit 1;
233             }
234 0           binmode(FH);
235              
236 0           my $buf;
237 0           while (($status = $zip->read($buf)) > 0) {
238 0           print FH $buf;
239             }
240 0           close FH;
241             }
242              
243 0 0         if ($status < 0) {
244 0           say STDERR "Failed to extract archive. Error: $UnzipError";
245 0           exit 1;
246             }
247             }
248