File Coverage

blib/lib/App/Rakubrew/Update.pm
Criterion Covered Total %
statement 47 144 32.6
branch 0 38 0.0
condition n/a
subroutine 16 19 84.2
pod 0 2 0.0
total 63 203 31.0


line stmt bran cond sub pod time code
1             package App::Rakubrew::Update;
2             require Exporter;
3             our @ISA = qw(Exporter);
4             our @EXPORT = qw();
5              
6 2     2   13 use strict;
  2         4  
  2         57  
7 2     2   9 use warnings;
  2         4  
  2         47  
8 2     2   30 use 5.010;
  2         9  
9 2     2   14 use HTTP::Tinyish;
  2         4  
  2         36  
10 2     2   8 use JSON;
  2         6  
  2         11  
11 2     2   293 use FindBin qw( $RealBin $RealScript );
  2         3  
  2         201  
12 2     2   14 use File::Copy;
  2         2  
  2         99  
13 2     2   11 use File::Spec::Functions qw( catfile catdir );
  2         4  
  2         97  
14 2     2   13 use Fcntl;
  2         3  
  2         609  
15 2     2   1275 use if scalar ($^O =~ /win32/i), 'Win32';
  2         39  
  2         17  
16 2     2   115 use if scalar ($^O =~ /win32/i), 'Win32::Process';
  2         4  
  2         9  
17 2     2   89 use if scalar ($^O =~ /win32/i), 'Win32::ShellQuote';
  2         4  
  2         10  
18              
19 2     2   59 use App::Rakubrew;
  2         5  
  2         45  
20 2     2   11 use App::Rakubrew::Variables;
  2         4  
  2         283  
21 2     2   25 use App::Rakubrew::Config;
  2         4  
  2         185  
22 2     2   13 use App::Rakubrew::Tools;
  2         4  
  2         2513  
23              
24             my $release_index_url = 'https://rakubrew.org/releases';
25             my $download_url_prefix = 'https://rakubrew.org';
26              
27             my %dl_urls = (
28             fatpack => "$download_url_prefix/perl/rakubrew",
29             win => "$download_url_prefix/win/rakubrew.exe",
30             macos => "$download_url_prefix/macos/rakubrew",
31             macos_arm => "$download_url_prefix/macos_arm/rakubrew",
32             );
33              
34             sub update {
35 0     0 0   my $quiet = shift;
36              
37             # For par packaged executables the following returns the path and name of
38             # the par packaged file.
39 0           my $current_rakubrew_file = catfile($RealBin, $RealScript);
40              
41             # check whether this is a CPAN installation. Abort if yes.
42 0 0         if ($distro_format eq 'cpan') {
43 0           say STDERR 'Rakubrew was installed via CPAN, use your CPAN client to update.';
44 0           exit 1;
45             }
46              
47 0           my $ht = HTTP::Tinyish->new();
48 0           my $release_index = _download_release_index($ht);
49              
50             # check version
51 0 0         if ($App::Rakubrew::VERSION >= $release_index->{latest}) {
52 0           say 'Rakubrew is up-to-date!';
53 0           exit 0;
54             }
55              
56             # Display changes
57 0 0         if (!$quiet) {
58 0           say "Changes\n";
59 0           say "=======\n";
60 0           for my $change (@{$release_index->{releases}}) {
  0            
61 0 0         next if $change->{version} <= $App::Rakubrew::VERSION;
62 0           say $change->{version} . ':';
63 0           say " $_" for split(/^/, $change->{changes});
64 0           say '';
65             }
66 0           print 'Shall we do the update? [y|N] ';
67 0           my $reply = ;
68 0           chomp $reply;
69 0 0         exit 0 if $reply ne 'y';
70 0           say '';
71             }
72              
73 0 0         mkdir catdir($prefix, 'update') unless (-d catdir($prefix, 'update'));
74 0           my $update_file = catfile($prefix, 'update', $RealScript);
75              
76             # delete RAKUBREW_HOME/update/rakubrew
77 0           unlink $update_file;
78              
79             # download latest to RAKUBREW_HOME/update/rakubrew
80 0           my $res = $ht->get($dl_urls{$distro_format});
81 0 0         unless ($res->{success}) {
82 0           say STDERR "Couldn\'t download update. Error: $res->{status} $res->{reason}";
83 0           exit 1;
84             }
85 0           my $fh;
86 0 0         if (!sysopen($fh, $update_file, O_WRONLY|O_CREAT|O_EXCL, 0777)) {
87 0           say STDERR "Couldn't write update file to $update_file. Aborting update.";
88 0           exit 1;
89             }
90 0           binmode $fh;
91 0           print $fh $res->{content};
92 0           close $fh;
93              
94 0 0         if ($^O =~ /win32/i) {
95             # Windows has no real exec(). In addition all the standard perl
96             # utilities to start processes automatically make the started process
97             # inherit all handles of the parent. This has the effect that it's
98             # impossible in the child to delete the parents executable file even
99             # when the parent has already exited. So we use the lower level
100             # Win32::Process::Create with the 4th argument (inheritHandles) set to 0
101             # to get rid of the handles preventing the deletion of the parent
102             # executable.
103              
104 0           say 'You will now see a command prompt, even though the update process is still running.';
105 0           say 'This is caused by a quirk in Windows\' process handling.';
106 0           say 'Just wait a few seconds until an "Update successful!" message shows up';
107 0           my $ProcessObj;
108 0 0         if (!Win32::Process::Create(
109             $ProcessObj,
110             $update_file,
111             Win32::ShellQuote::quote_native(
112             $update_file,
113             'internal_update',
114             $App::Rakubrew::VERSION,
115             $current_rakubrew_file),
116             0,
117             Win32::Process::NORMAL_PRIORITY_CLASS(),
118             "."
119             )) {
120 0           say STDERR 'Failed to call the downloaded rakubrew executable! Aborting update.';
121 0           exit 1;
122             };
123 0           exit 0;
124             }
125             else {
126 0           { exec($update_file, 'internal_update', $App::Rakubrew::VERSION, $current_rakubrew_file) };
  0            
127 0           say STDERR 'Failed to call the downloaded rakubrew executable! Aborting update.';
128 0           exit 1;
129             }
130             }
131              
132             sub internal_update {
133 0     0 0   my ($old_version, $old_rakubrew_file) = @_;
134              
135 0           my $current_script = catfile($RealBin, $RealScript);
136 0           my $update_file = catfile($prefix, 'update', $RealScript);
137 0 0         if ($update_file ne $current_script) {
138 0           say STDERR "'internal_update' was called on a rakubrew ($current_script) that's not $update_file. That's probably wrong and dangerous. Aborting update.";
139 0           exit 1;
140             }
141              
142             # custom update procedures
143 0 0         if ($old_version < 29) {
144             # Change Github URLs to use the https instead of the git protocol.
145 0           my @repos;
146              
147 0           for my $dir ($git_reference, $versions_dir) {
148 0           opendir(my $dh, $dir);
149 0           push @repos, map({ catdir($dir, $_) } grep({ /^[^.]/ } readdir($dh)));
  0            
  0            
150 0           closedir($dh);
151             }
152 0           push @repos, $zef_dir;
153              
154 0           for my $repo (@repos) {
155 0 0         $repo = catdir($repo, '.git') if -d catdir($repo, '.git');
156 0           my $config_file = catfile($repo, 'config');
157 0 0         if (-f $config_file) {
158 0           print "Updating Github repository URLs in $config_file...";
159 0           my $content = slurp($config_file);
160 0           my $replaced = ($content =~ s|^(\s* url \s* = \s*) git (://github\.com/)|$1https$2|gmx);
161 0 0         if ($replaced) {
162 0           spurt($config_file, $content);
163 0           say "done";
164             }
165             else {
166 0           say "nothing to be done";
167             }
168             }
169             }
170             }
171             #if ($old_version < 2) {
172             # Do update stuff for version 2.
173             #}
174              
175             # copy RAKUBREW_HOME/update/rakubrew to 'path/to/rakubrew'
176 0 0         unlink $old_rakubrew_file or die "Failed to unlink old file: $old_rakubrew_file. Error: $!";
177 0           my $fh;
178 0 0         if (!sysopen($fh, $old_rakubrew_file, O_WRONLY|O_CREAT|O_EXCL, 0777)) {
179 0           say STDERR "Couldn't copy update file to $old_rakubrew_file. Rakubrew is broken now. Try manually copying '$update_file' to '$old_rakubrew_file' to get it fixed again.";
180 0           exit 1;
181             }
182 0           binmode $fh;
183 0 0         if (!copy($update_file, $fh)) {
184 0           close $fh;
185 0           unlink $old_rakubrew_file;
186 0           say STDERR "Couldn't copy update file to $old_rakubrew_file. Rakubrew is broken now. Try manually copying '$update_file' to '$old_rakubrew_file' to get it fixed again.";
187 0           exit 1;
188             }
189 0           close $fh;
190 0           unlink $update_file;
191              
192 0           say 'Update successful!';
193             }
194              
195             sub _download_release_index {
196 0     0     my $ht = shift;
197 0           my $res = $ht->get($release_index_url);
198 0 0         unless ($res->{success}) {
199 0           say STDERR "Couldn\'t fetch release index at $release_index_url. Error: $res->{status} $res->{reason}";
200             ;
201 0           exit 1;
202             }
203 0           return decode_json($res->{content});
204             }
205