File Coverage

blib/lib/App/DuckPAN/Perl.pm
Criterion Covered Total %
statement 57 136 41.9
branch 3 48 6.2
condition 0 21 0.0
subroutine 16 22 72.7
pod 0 5 0.0
total 76 232 32.7


line stmt bran cond sub pod time code
1             package App::DuckPAN::Perl;
2             our $AUTHORITY = 'cpan:DDG';
3             # ABSTRACT: Perl related functionality for duckpan
4             $App::DuckPAN::Perl::VERSION = '1018';
5 2     2   2935 use Moo;
  2         5  
  2         13  
6             with 'App::DuckPAN::HasApp';
7              
8 2     2   922 use Config::INI;
  2         88  
  2         41  
9 2     2   715 use Dist::Zilla::Util;
  2         4355  
  2         50  
10 2     2   15 use Path::Tiny;
  2         4  
  2         101  
11 2     2   12 use Config::INI::Reader;
  2         4  
  2         39  
12 2     2   37 use Config::INI::Writer;
  2         4  
  2         38  
13 2     2   1035 use Data::Dumper;
  2         9109  
  2         118  
14 2     2   16 use LWP::UserAgent;
  2         5  
  2         59  
15 2     2   11 use List::MoreUtils qw/ uniq /;
  2         6  
  2         45  
16 2     2   1070 use List::Util qw/ first /;
  2         5  
  2         112  
17 2     2   10 use File::Temp qw/ :POSIX /;
  2         5  
  2         232  
18 2     2   783 use version;
  2         3297  
  2         12  
19 2     2   131 use Parse::CPAN::Packages::Fast;
  2         5  
  2         54  
20 2     2   10 use Class::Load ':all';
  2         5  
  2         215  
21              
22 2     2   14 no warnings 'uninitialized';
  2         3  
  2         1775  
23              
24             sub get_local_version {
25 2     2 0 1436 my ($self, $module) = @_;
26 2         360 require Module::Data;
27 2         4298 my $v;
28             {
29 2         4 local $@;
  2         4  
30              
31             # ensure $module is installed by trying to load (require) it
32 2 50       4 eval {
33 2         22 my $m = Module::Data->new($module);
34 2         1448 $m->require;
35 2         97 $v = $m->version;
36 2         121 1;
37             } or return;
38             };
39              
40             # $module (e.g. DuckPAN, DDG) has loaded, but no $VERSION exists
41             # This means we're not working with code that was built by DZIL
42             #
43             # Example:
44             # > ./bin/duckpan -I/lib/ -I../duckduckgo/lib server
45 2 50       10 unless (defined $v) {
46 0 0 0     0 if ($module eq 'App::DuckPAN' || $module eq 'DDG'){
47             # When executing code in-place, $VERSION will not be defined.
48             # Only the installed package will have a defined version
49             # thanks to Dist::Zilla::Plugin::PkgVersion
50 0         0 return $self->app->dev_version;
51             }
52 0         0 return;
53             }
54 2 50       34 return version->parse($v) unless ref $v;
55 0           return $v;
56             }
57              
58             sub cpanminus_install_error {
59 0     0 0   shift->app->emit_and_exit(1,
60             "Failure on installation of modules!",
61             "There are several possible explanations and fixes for this error:",
62             "1. The download from CPAN was unsuccessful - Please restart this installer.",
63             "2. Some other error occured - Please read the `build.log` mentioned in the errors and see if you can fix the problem yourself.",
64             "If you are unable to solve the problem, please let us know by making a GitHub Issue in the DuckPAN Repo:",
65             "https://github.com/duckduckgo/p5-app-duckpan/issues",
66             "Make sure to attach the `build.log` file if it exists. Otherwise, copy/paste the output you see."
67             );
68             }
69              
70             sub duckpan_install {
71 0     0 0   my ($self, @modules) = @_;
72 0           my $mirror = $self->app->duckpan;
73 0           my ($reinstall, $latest);
74 0           my $reinstall_latest = $modules[0];
75 0 0         if($reinstall_latest eq 'reinstall') {
    0          
76             # We sent in a signal to force reinstallation
77 0           $reinstall = 1;
78 0           shift @modules;
79             }
80             elsif($reinstall_latest eq 'latest') {
81 0           $latest = 1;
82 0           shift @modules;
83             }
84 0           my $packages = $self->app->duckpan_packages;
85 0           my @to_install;
86 0           for (@modules) {
87 0           my $module = $packages->package($_);
88 0 0         $self->app->emit_and_exit(1, "Can't find package " . $_ . " on " . $self->app->duckpan) unless $module;
89              
90 0           my $package = $module->package; # Probably $_, but maybe they'll normalize or something someday.
91              
92             # see if we have an env variable for this module
93 0           my $sp = $package;
94 0           $sp =~ s/\:\:/_/g;
95              
96             # special case: check for a pinned verison number
97 0           my $pinned_version = $ENV{$sp};
98 0           my $installed_version = $self->get_local_version($package);
99 0           my $latest_version = version->parse($module->version);
100 0           my $duckpan_module_url = $self->app->duckpan . 'authors/id/' . $module->distribution->pathname;
101              
102             # Remind user about having pinned env variables
103 0 0         $self->app->emit_info("$package: $installed_version installed, $pinned_version pinned, $latest_version latest") if $pinned_version;
104              
105             # Could be moved up but want dev to see the version installed and available first
106 0 0         if($installed_version == $self->app->dev_version){
107 0           $self->app->emit_notice("Skipping installation of $package. Development version detected!");
108 0           next;
109             }
110              
111 0           my ($install_it, $message);
112 0 0 0       if($reinstall || $latest || !$installed_version) {
    0 0        
    0          
    0          
113             # Prefer versions in the following order when (re)installing: installed, pinned, latest
114             # Latest ignores the installed version
115 0           my $version;
116 0 0         $version = $installed_version unless $latest;
117 0   0       $version ||= $pinned_version || $latest_version;
      0        
118             # update the url if not the latest
119 0 0         if($version != $latest_version){
120 0 0         unless($duckpan_module_url = $self->find_previous_url($module, $version)){
121 0           $self->app->emit_and_exit(1, "Failed to find version $version of $package");
122             }
123             }
124 0 0         $message = $reinstall ?
125             "Reinstalling $package, version ($version)" :
126             "You don't have $package installed. Installing version ($version)";
127 0           $install_it = 1;
128             }
129             elsif($pinned_version) {
130 0 0         if($pinned_version != $installed_version) {
131             # We continue here, even if the version is larger than latest released,
132             # on the premise that there might exist unreleased development versions.
133 0 0 0       if($pinned_version == $latest_version || ($duckpan_module_url = $self->find_previous_url($module, $pinned_version))) {
134 0           $install_it = 1;
135             }
136             else{
137 0           $message = "Could not locate version $pinned_version of '$package'";
138 0           $self->app->emit_and_exit(1, $message);
139             }
140             }
141             else{
142 0 0         $message = ($pinned_version == $latest_version) ?
143             "You already have the latest version of '$package' installed!" :
144             "A newer version of '$package' exists. Please update your version pin to match the newest version: $latest_version";
145 0           $install_it = 0;
146             }
147             }
148             elsif($installed_version == $latest_version) {
149 0           $message = "You already have latest version ($installed_version) of $package";
150             }
151             elsif($installed_version > $latest_version) {
152 0           $message = "You have a newer version ($installed_version) of $package than duckpan.org ($latest_version)";
153             }
154             else{
155 0           $message = "You have an older version ($installed_version) of $package than duckpan.org. Installing latest version ($latest_version)";
156 0           $install_it = 1;
157             }
158 0           $self->app->emit_notice($message);
159 0 0 0 0     push @to_install, $duckpan_module_url if ($install_it && !(first { $_ eq $duckpan_module_url } @to_install));
  0            
160             }
161              
162 0 0         if(@to_install){
163 0 0         unshift @to_install, '--reinstall' if $reinstall; # cpanm will do the actual forcing.
164 0 0         if(system "cpanm @to_install"){
165 0           my $err = "cpanm failed (err $?) to (re)install the following modules:\n\n\t" .
166             join("\n\t", @to_install);
167 0           $self->app->emit_and_exit(1, $err);
168             }
169             }
170             }
171              
172             my @POTENTIAL_AUTHORS = qw|
173             ALOHAAS
174             ANDREY_P
175             BRAD
176             BRIANS
177             CAINE
178             CRAZEDPSYC
179             DDGC
180             DYLANLL
181             GETTY
182             JAG
183             JBARRETT
184             JDORW
185             MGA
186             MOOLLAZA
187             RSSSSL
188             SDOUGBROWN
189             TOMMYTOMMYTOMMY
190             YEGG
191             ZT
192             |;
193              
194             # We need to derive URLs because duckpan.org doesn't understand module@0.147 syntax
195             sub find_previous_url {
196 0     0 0   my ($self, $module, $desired_version) = @_;
197              
198             # Shaky premise #1: the author of our previous version is a current author.
199 0           my @cpanids = uniq($self->authors_of_latest_dists, @POTENTIAL_AUTHORS);
200             # Shaky premise #2: the directory structure is always like this.
201 0           my @cpan_dirs = map { join('/', substr($_, 0, 1), substr($_, 0, 2), $_) } @cpanids;
  0            
202             # Shaky premise #3: things never change distributions.
203 0           my $dist = $module->distribution;
204 0           my $filename = $dist->filename;
205             # CPAN::DistnameInfo parses the filename incorrectly because PAUSE ids can only
206             # contain [-A-Z0-9] whereas duckpan allows other characters like _
207 0           $filename =~ s|^[A-Z]/[A-Z]{2}/.*?/||;
208             # Shaky premise #4: the distribution version will match package version.
209 0           my $version = $dist->version;
210             # Shaky premise #5: the version for which they are asking is well-formed.
211 0           $filename =~ s/$version/$desired_version/;
212 0           my @urls = map { $self->app->duckpan . 'authors/id/' . $_ . '/' . $filename } @cpan_dirs;
  0            
213 0           $self->app->emit_debug("Checking up to " . scalar @urls . " distributions for pinned version...");
214              
215             # Shaky premise #6: our network works well enough to make this a definitive test
216 0           my $ua = LWP::UserAgent->new(
217             agent => 'DPPF/0.001a',
218             requests_redirectable => []);
219              
220 0     0     return first { $ua->head($_)->is_success } @urls;
  0            
221             }
222              
223             sub authors_of_latest_dists {
224 0     0 0   my ( $self ) = @_;
225 0           my @dists = $self->app->duckpan_packages->distributions;
226             # CPAN::DistnameInfo can parse the cpanid incorrectly because PAUSE ids can
227             # only contain [-A-Z0-9] whereas duckpan allows other characters like _.
228             # When this happens cpanid is undefined. Thats why we grep for defined.
229 0           return grep { defined $_ } map { $_->cpanid } @dists;
  0            
  0            
230             }
231              
232             1;
233              
234             __END__
235              
236             =pod
237              
238             =head1 NAME
239              
240             App::DuckPAN::Perl - Perl related functionality for duckpan
241              
242             =head1 VERSION
243              
244             version 1018
245              
246             =head1 AUTHOR
247              
248             DuckDuckGo <open@duckduckgo.com>, Zach Thompson <zach@duckduckgo.com>, Zaahir Moolla <moollaza@duckduckgo.com>, Torsten Raudssus <torsten@raudss.us> L<https://raudss.us/>
249              
250             =head1 COPYRIGHT AND LICENSE
251              
252             This software is Copyright (c) 2013 by DuckDuckGo, Inc. L<https://duckduckgo.com/>.
253              
254             This is free software, licensed under:
255              
256             The Apache License, Version 2.0, January 2004
257              
258             =cut