File Coverage

blib/lib/Perinci/Sub/DepChecker.pm
Criterion Covered Total %
statement 133 151 88.0
branch 89 108 82.4
condition 10 11 90.9
subroutine 18 24 75.0
pod 3 16 18.7
total 253 310 81.6


line stmt bran cond sub pod time code
1             package Perinci::Sub::DepChecker;
2              
3 1     1   74137 use 5.010001;
  1         14  
4 1     1   5 use strict;
  1         2  
  1         36  
5 1     1   7 use warnings;
  1         2  
  1         45  
6 1     1   1924 use Log::ger;
  1         68  
  1         5  
7              
8             require Exporter;
9             our @ISA = qw(Exporter);
10             our @EXPORT_OK = qw(
11             check_deps
12             dep_satisfy_rel
13             list_mentioned_dep_clauses
14             );
15              
16             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
17             our $DATE = '2023-07-08'; # DATE
18             our $DIST = 'Perinci-Sub-DepChecker'; # DIST
19             our $VERSION = '0.126'; # VERSION
20              
21             my $pa;
22              
23             sub check_deps {
24 78     78 1 22814 my ($val) = @_;
25             #say "D:check: ", dump($val);
26 78         237 for my $dname (keys %$val) {
27 65         130 my $dval = $val->{$dname};
28 65 100       94 unless (defined &{"checkdep_$dname"}) {
  65         268  
29             # give a chance to load from a module first
30 22         37 eval { my $mod_pm = "Perinci/Sub/Dep/$dname.pm"; require $mod_pm };
  22         50  
  22         3123  
31             return "Unknown dependency type: $dname"
32 22 50       115 unless defined &{"checkdep_$dname"};
  22         181  
33             }
34 43         77 my $check = \&{"checkdep_$dname"};
  43         132  
35 43         108 my $res = $check->($dval);
36 43 100       326 if ($res) {
37 22         68 $res = "$dname: $res";
38 22         95 return $res;
39             }
40             }
41 34         132 "";
42             }
43              
44             sub checkdep_all {
45 11     11 0 27 my ($val) = @_;
46             #say "D:check_all: ", dump($val);
47 11         30 for (@$val) {
48 11         83 my $res = check_deps($_);
49 11 100       47 return "Some dependencies not met: $res" if $res;
50             }
51 4         13 "";
52             }
53              
54             sub checkdep_any {
55 10     10 0 18 my ($val) = @_;
56 10         25 my $nfail = 0;
57 10         28 for (@$val) {
58 16 100       43 return "" unless check_deps($_);
59 12         32 $nfail++;
60             }
61 6 100       30 $nfail ? "None of the dependencies are met" : "";
62             }
63              
64             sub checkdep_none {
65 8     8 0 18 my ($val) = @_;
66 8         17 for (@$val) {
67 10         25 my $res = check_deps($_);
68 10 100       34 return "A dependency is met when it shouldn't: $res" unless $res;
69             }
70 4         14 "";
71             }
72              
73             sub checkdep_env {
74 3     3 0 6 my ($cval) = @_;
75 3 100       13 $ENV{$cval} ? "" : "Environment variable $cval not set/true";
76             }
77              
78             sub checkdep_code {
79 2     2 0 4 my ($cval) = @_;
80 2 100       6 $cval->() ? "" : "code doesn't return true value";
81             }
82              
83             sub checkdep_prog {
84 9     9 0 24 my ($cval) = @_;
85              
86 9 100       34 $cval = ref $cval eq 'HASH' ? $cval : {name=>$cval};
87 9 50       31 my $prog_name = $cval->{name} or return "BUG: Program name not specified in dependency";
88              
89 9 100       32 if ($prog_name =~ m!/!) {
90 3 100       101 return "Program $prog_name not executable" unless (-x $prog_name);
91             } else {
92 6         89 require File::Which;
93 6 50       42 return "Program $prog_name not found in PATH (".
94             join(":", File::Spec->path).")"
95             unless File::Which::which($prog_name);
96             }
97              
98 8 100       1185 if (defined $cval->{min_version}) {
99 4         569 require IPC::System::Options;
100 4         4770 require Version::Util;
101              
102 4         4351 my (@ver_cmd, $ver_extract);
103 4   66     46 my $prog_path = $cval->{path} // $prog_name;
104 4 100       24 if ($prog_name eq 'git') {
    50          
105 2         19 @ver_cmd = ($prog_path, "--version");
106 2 50   2   28 $ver_extract = sub { $_[0] =~ /git version (.+)/ ? $1 : undef };
  2         111  
107             } elsif ($prog_name eq 'perl') {
108 2         8 @ver_cmd = ($prog_path, "-v");
109 2 50   2   14 $ver_extract = sub { $_[0] =~ /\(v(.+?)\)/ ? $1 : undef };
  2         101  
110             } else {
111 0         0 return "ERR: Cannot check minimum version for program '$prog_name'";
112             }
113              
114 4         35 my $ver = IPC::System::Options::readpipe({log=>1, shell=>0}, @ver_cmd);
115 4 50       96955 my ($exit_code, $signal, $core_dump) = ($? < 0 ? $? : $? >> 8, $? & 127, $? & 128);
116 4 50       43 return "ERR: Cannot check version with '".join(" ", @ver_cmd)."': exit_code=$exit_code"
117             if $exit_code;
118 4 50       21 ($ver) = $ver_extract->($ver) or return "ERR: Cannot extract version from response '$ver'";
119             return "Program '$prog_name' version ($ver) is less than required ($cval->{min_version})"
120 4 100       58 if Version::Util::version_lt($ver, $cval->{min_version});
121             }
122              
123 6         212 "";
124             }
125              
126             sub riap_client {
127 0 0   0 0 0 return $pa if $pa;
128 0         0 require Perinci::Access;
129 0         0 $pa = Perinci::Access->new;
130 0         0 $pa;
131             }
132              
133             sub checkdep_pkg {
134 0     0 0 0 my ($cval) = @_;
135 0         0 my $res = riap_client->request(info => $cval);
136 0 0       0 $res->[0] == 200 or return "Can't perform 'info' Riap request on '$cval': ".
137             "$res->[0] $res->[1]";
138 0 0       0 $res->[2]{type} eq 'package' or return "$cval is not a Riap package";
139 0         0 "";
140             }
141              
142             sub checkdep_func {
143 0     0 0 0 my ($cval) = @_;
144 0         0 my $res = riap_client->request(info => $cval);
145 0 0       0 $res->[0] == 200 or return "Can't perform 'info' Riap request on '$cval': ".
146             "$res->[0] $res->[1]";
147 0 0       0 $res->[2]{type} eq 'function' or return "$cval is not a Riap function";
148 0         0 "";
149             }
150              
151             # for backward-compatibility
152 9     9 0 39 sub checkdep_exec { checkdep_prog(@_) }
153              
154             # we check this dep by checking arguments, so we'll let something like
155             # Perinci::Sub::Wrapper to do it
156 0     0 0 0 sub checkdep_tmp_dir { "" }
157              
158             # we check this dep by checking arguments, so we'll let something like
159             # Perinci::Sub::Wrapper to do it
160 0     0 0 0 sub checkdep_trash_dir { "" }
161              
162             # we check this dep by checking arguments, so we'll let something like
163             # Perinci::Sub::Wrapper to do it
164 0     0 0 0 sub checkdep_undo_trash_dir { "" }
165              
166             sub _all_elems_is {
167 99     99   182 my ($ary, $el) = @_;
168 99 100       136 (grep {$_ eq $el} @$ary) && !(grep {$_ ne $el} @$ary);
  111         321  
  189         438  
169             }
170              
171             sub _all_nonblank_elems_is {
172 10     10   22 my ($ary, $el) = @_;
173 10 100       22 (grep {$_ eq $el} @$ary) && !(grep {$_ && $_ ne $el} @$ary);
  10 100       74  
  16         55  
174             }
175              
176             sub dep_satisfy_rel {
177 298     298 1 2009 my ($wanted, $deps) = @_;
178             #$log->tracef("=> dep_satisfy_rel(%s, %s)", $wanted, $deps);
179              
180 298         419 my $res;
181 298         621 for my $dname (keys %$deps) {
182 280         434 my $dval = $deps->{$dname};
183              
184 280 100       694 if ($dname eq 'all') {
    100          
    100          
185 42         76 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         160  
186             #$log->tracef("all: %s", \@r);
187 42 100       111 next unless @r;
188 41 100       67 return "impossible" if grep { $_ eq "impossible" } @r;
  77         196  
189 35 100 100     47 return "impossible" if (grep { $_ eq "must" } @r) && (grep {$_ eq "must not"} @r);
  66         162  
  51         167  
190 13 100       22 return "must" if grep { $_ eq "must" } @r;
  22         62  
191 9 100       20 return "must not" if grep { $_ eq "must not" } @r;
  15         48  
192 5 100       13 return "might" if _all_nonblank_elems_is(\@r, "might");
193             } elsif ($dname eq 'any') {
194 42         75 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         140  
195             #$log->tracef("any: %s", \@r);
196 42 100       94 next unless @r;
197 41 100       67 return "impossible" if grep { $_ eq "impossible" } @r;
  77         200  
198 35 100       74 return "must" if _all_elems_is(\@r, "must");
199 33 100       80 return "must not" if _all_elems_is(\@r, "must not");
200 31 100       59 next if _all_elems_is(\@r, "");
201 29         117 return "might";
202             } elsif ($dname eq 'none') {
203 63         107 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         139  
204             #$log->tracef("none: %s", \@r);
205 63 100       142 next unless @r;
206 62 100       125 return "impossible" if grep { $_ eq "impossible" } @r;
  77         228  
207 56 100 100     92 return "impossible" if (grep { $_ eq "must" } @r) && (grep {$_ eq "must not"} @r);
  66         171  
  51         164  
208 55 100       88 return "must not" if grep { $_ eq "must" } @r;
  64         214  
209 9 100       14 return "must" if grep { $_ eq "must not" } @r;
  15         52  
210 5 100       11 return "might" if _all_nonblank_elems_is(\@r, "might");
211             } else {
212 133 100       458 return "must" if $dname eq $wanted;
213             }
214             }
215 53         189 "";
216             }
217              
218             sub list_mentioned_dep_clauses {
219 3     3 1 1911 my ($deps, $res) = @_;
220 3   100     35 $res //= [];
221 3         16 for my $dname (keys %$deps) {
222 4         9 my $dval = $deps->{$dname};
223 4 100       10 push @$res, $dname unless grep { $_ eq $dname } @$res;
  6         16  
224 4 100       21 if ($dname =~ /\A(?:all|any|none)\z/) {
225 1         6 list_mentioned_dep_clauses($_, $res) for @$dval;
226             }
227             }
228 3         20 $res;
229             }
230              
231             1;
232             # ABSTRACT: Check dependencies from 'deps' property
233              
234             __END__