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 2     2   456802 use 5.010001;
  2         10  
4 2     2   12 use strict;
  2         5  
  2         62  
5 2     2   10 use warnings;
  2         4  
  2         125  
6 2     2   4063 use Log::ger;
  2         111  
  2         16  
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-11-16'; # DATE
18             our $DIST = 'Perinci-Sub-DepChecker'; # DIST
19             our $VERSION = '0.128'; # VERSION
20              
21             my $pa;
22              
23             sub check_deps {
24 78     78 1 320772 my ($val) = @_;
25             #say "D:check: ", dump($val);
26 78         209 for my $dname (keys %$val) {
27 65         146 my $dval = $val->{$dname};
28 65 100       92 unless (defined &{"checkdep_$dname"}) {
  65         335  
29             # give a chance to load from a module first
30 22         39 eval { my $mod_pm = "Perinci/Sub/Dep/$dname.pm"; require $mod_pm };
  22         32  
  22         2496  
31             return "Unknown dependency type: $dname"
32 22 50       46 unless defined &{"checkdep_$dname"};
  22         1082  
33             }
34 43         59 my $check = \&{"checkdep_$dname"};
  43         105  
35 43         92 my $res = $check->($dval);
36 43 100       292 if ($res) {
37 22         67 $res = "$dname: $res";
38 22         76 return $res;
39             }
40             }
41 34         208 "";
42             }
43              
44             sub checkdep_all {
45 11     11 0 23 my ($val) = @_;
46             #say "D:check_all: ", dump($val);
47 11         39 for (@$val) {
48 11         978 my $res = check_deps($_);
49 11 100       40 return "Some dependencies not met: $res" if $res;
50             }
51 4         11 "";
52             }
53              
54             sub checkdep_any {
55 10     10 0 19 my ($val) = @_;
56 10         19 my $nfail = 0;
57 10         22 for (@$val) {
58 16 100       31 return "" unless check_deps($_);
59 12         34 $nfail++;
60             }
61 6 100       24 $nfail ? "None of the dependencies are met" : "";
62             }
63              
64             sub checkdep_none {
65 8     8 0 17 my ($val) = @_;
66 8         15 for (@$val) {
67 10         30 my $res = check_deps($_);
68 10 100       37 return "A dependency is met when it shouldn't: $res" unless $res;
69             }
70 4         11 "";
71             }
72              
73             sub checkdep_env {
74 3     3 0 7 my ($cval) = @_;
75 3 100       15 $ENV{$cval} ? "" : "Environment variable $cval not set/true";
76             }
77              
78             sub checkdep_code {
79 2     2 0 5 my ($cval) = @_;
80 2 100       4 $cval->() ? "" : "code doesn't return true value";
81             }
82              
83             sub checkdep_prog {
84 9     9 0 20 my ($cval) = @_;
85              
86 9 100       48 $cval = ref $cval eq 'HASH' ? $cval : {name=>$cval};
87 9 50       29 my $prog_name = $cval->{name} or return "BUG: Program name not specified in dependency";
88              
89 9 100       36 if ($prog_name =~ m!/!) {
90 3 100       252 return "Program $prog_name not executable" unless (-x $prog_name);
91             } else {
92 6         76 require File::Which;
93 6 50       43 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       1103 if (defined $cval->{min_version}) {
99 4         1913 require IPC::System::Options;
100 4         5028 require Version::Util;
101              
102 4         1798 my (@ver_cmd, $ver_extract);
103 4   66     48 my $prog_path = $cval->{path} // $prog_name;
104 4 100       25 if ($prog_name eq 'git') {
    50          
105 2         15 @ver_cmd = ($prog_path, "--version");
106 2 50   2   22 $ver_extract = sub { $_[0] =~ /git version (.+)/ ? $1 : undef };
  2         116  
107             } elsif ($prog_name eq 'perl') {
108 2         7 @ver_cmd = ($prog_path, "-v");
109 2 50   2   19 $ver_extract = sub { $_[0] =~ /\s\(?v([\.\d]+)\*?\)?\s/ ? $1 : undef };
  2         88  
110             } else {
111 0         0 return "ERR: Cannot check minimum version for program '$prog_name'";
112             }
113              
114 4         41 my $ver = IPC::System::Options::readpipe({log=>1, shell=>0}, @ver_cmd);
115 4 50       114497 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       34 ($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       52 if Version::Util::version_lt($ver, $cval->{min_version});
121             }
122              
123 6         233 "";
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 40 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   199 my ($ary, $el) = @_;
168 99 100       141 (grep {$_ eq $el} @$ary) && !(grep {$_ ne $el} @$ary);
  111         300  
  189         447  
169             }
170              
171             sub _all_nonblank_elems_is {
172 10     10   21 my ($ary, $el) = @_;
173 10 100       19 (grep {$_ eq $el} @$ary) && !(grep {$_ && $_ ne $el} @$ary);
  10 100       65  
  16         48  
174             }
175              
176             sub dep_satisfy_rel {
177 298     298 1 3670 my ($wanted, $deps) = @_;
178             #$log->tracef("=> dep_satisfy_rel(%s, %s)", $wanted, $deps);
179              
180 298         479 my $res;
181 298         634 for my $dname (keys %$deps) {
182 279         493 my $dval = $deps->{$dname};
183              
184 279 100       766 if ($dname eq 'all') {
    100          
    100          
185 42         82 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         159  
186             #$log->tracef("all: %s", \@r);
187 42 100       92 next unless @r;
188 41 100       69 return "impossible" if grep { $_ eq "impossible" } @r;
  77         188  
189 35 100 100     57 return "impossible" if (grep { $_ eq "must" } @r) && (grep {$_ eq "must not"} @r);
  66         146  
  51         147  
190 13 100       29 return "must" if grep { $_ eq "must" } @r;
  22         61  
191 9 100       13 return "must not" if grep { $_ eq "must not" } @r;
  15         64  
192 5 100       15 return "might" if _all_nonblank_elems_is(\@r, "might");
193             } elsif ($dname eq 'any') {
194 42         86 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         154  
195             #$log->tracef("any: %s", \@r);
196 42 100       89 next unless @r;
197 41 100       63 return "impossible" if grep { $_ eq "impossible" } @r;
  77         186  
198 35 100       69 return "must" if _all_elems_is(\@r, "must");
199 33 100       60 return "must not" if _all_elems_is(\@r, "must not");
200 31 100       53 next if _all_elems_is(\@r, "");
201 29         117 return "might";
202             } elsif ($dname eq 'none') {
203 63         146 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         136  
204             #$log->tracef("none: %s", \@r);
205 63 100       135 next unless @r;
206 62 100       80 return "impossible" if grep { $_ eq "impossible" } @r;
  77         201  
207 56 100 100     80 return "impossible" if (grep { $_ eq "must" } @r) && (grep {$_ eq "must not"} @r);
  66         165  
  51         148  
208 55 100       92 return "must not" if grep { $_ eq "must" } @r;
  64         220  
209 9 100       14 return "must" if grep { $_ eq "must not" } @r;
  15         45  
210 5 100       13 return "might" if _all_nonblank_elems_is(\@r, "might");
211             } else {
212 132 100       478 return "must" if $dname eq $wanted;
213             }
214             }
215 53         167 "";
216             }
217              
218             sub list_mentioned_dep_clauses {
219 3     3 1 6242 my ($deps, $res) = @_;
220 3   100     19 $res //= [];
221 3         10 for my $dname (keys %$deps) {
222 4         7 my $dval = $deps->{$dname};
223 4 100       9 push @$res, $dname unless grep { $_ eq $dname } @$res;
  5         13  
224 4 100       15 if ($dname =~ /\A(?:all|any|none)\z/) {
225 1         6 list_mentioned_dep_clauses($_, $res) for @$dval;
226             }
227             }
228 3         19 $res;
229             }
230              
231             1;
232             # ABSTRACT: Check dependencies from 'deps' property
233              
234             __END__