File Coverage

blib/lib/Perinci/Sub/DepChecker.pm
Criterion Covered Total %
statement 123 141 87.2
branch 89 108 82.4
condition 8 8 100.0
subroutine 19 25 76.0
pod 3 16 18.7
total 242 298 81.2


line stmt bran cond sub pod time code
1             package Perinci::Sub::DepChecker;
2              
3 1     1   50614 use 5.010001;
  1         12  
4 1     1   4 use strict;
  1         2  
  1         23  
5 1     1   5 use warnings;
  1         1  
  1         32  
6 1     1   344 use experimental 'smartmatch';
  1         2484  
  1         5  
7 1     1   1245 use Log::ger;
  1         62  
  1         4  
8              
9             require Exporter;
10             our @ISA = qw(Exporter);
11             our @EXPORT_OK = qw(
12             check_deps
13             dep_satisfy_rel
14             list_mentioned_dep_clauses
15             );
16              
17             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
18             our $DATE = '2022-04-20'; # DATE
19             our $DIST = 'Perinci-Sub-DepChecker'; # DIST
20             our $VERSION = '0.124'; # VERSION
21              
22             my $pa;
23              
24             sub check_deps {
25 78     78 1 22923 my ($val) = @_;
26             #say "D:check: ", dump($val);
27 78         189 for my $dname (keys %$val) {
28 65         167 my $dval = $val->{$dname};
29 65 100       72 unless (defined &{"checkdep_$dname"}) {
  65         214  
30             # give a chance to load from a module first
31 22         30 eval { my $mod_pm = "Perinci/Sub/Dep/$dname.pm"; require $mod_pm };
  22         35  
  22         3117  
32             return "Unknown dependency type: $dname"
33 22 50       92 unless defined &{"checkdep_$dname"};
  22         131  
34             }
35 43         49 my $check = \&{"checkdep_$dname"};
  43         100  
36 43         151 my $res = $check->($dval);
37 43 100       285 if ($res) {
38 22         87 $res = "$dname: $res";
39 22         68 return $res;
40             }
41             }
42 34         95 "";
43             }
44              
45             sub checkdep_all {
46 11     11 0 21 my ($val) = @_;
47             #say "D:check_all: ", dump($val);
48 11         19 for (@$val) {
49 11         147 my $res = check_deps($_);
50 11 100       39 return "Some dependencies not met: $res" if $res;
51             }
52 4         9 "";
53             }
54              
55             sub checkdep_any {
56 10     10 0 19 my ($val) = @_;
57 10         13 my $nfail = 0;
58 10         14 for (@$val) {
59 16 100       23 return "" unless check_deps($_);
60 12         28 $nfail++;
61             }
62 6 100       17 $nfail ? "None of the dependencies are met" : "";
63             }
64              
65             sub checkdep_none {
66 8     8 0 17 my ($val) = @_;
67 8         23 for (@$val) {
68 10         16 my $res = check_deps($_);
69 10 100       30 return "A dependency is met when it shouldn't: $res" unless $res;
70             }
71 4         11 "";
72             }
73              
74             sub checkdep_env {
75 3     3 0 5 my ($cval) = @_;
76 3 100       11 $ENV{$cval} ? "" : "Environment variable $cval not set/true";
77             }
78              
79             sub checkdep_code {
80 2     2 0 3 my ($cval) = @_;
81 2 100       4 $cval->() ? "" : "code doesn't return true value";
82             }
83              
84             sub checkdep_prog {
85 9     9 0 18 my ($cval) = @_;
86              
87 9 100       33 $cval = ref $cval eq 'HASH' ? $cval : {name=>$cval};
88 9 50       26 my $prog_name = $cval->{name} or return "BUG: Program name not specified in dependency";
89              
90 9 100       29 if ($prog_name =~ m!/!) {
91 3 100       52 return "Program $prog_name not executable" unless (-x $prog_name);
92             } else {
93 6         49 require File::Which;
94 6 50       36 return "Program $prog_name not found in PATH (".
95             join(":", File::Spec->path).")"
96             unless File::Which::which($prog_name);
97             }
98              
99 8 100       1317 if (defined $cval->{min_version}) {
100 4         443 require IPC::System::Options;
101 4         3673 require Version::Util;
102              
103 4         974 my ($ver_cmd, $ver_extract);
104 4 100       13 if ($prog_name eq 'git') {
    50          
105 2         11 $ver_cmd = "git --version";
106 2 50   2   16 $ver_extract = sub { $_[0] =~ /git version (.+)/ ? $1 : undef };
  2         69  
107             } elsif ($prog_name eq 'perl') {
108 2         4 $ver_cmd = "perl -v";
109 2 50   2   12 $ver_extract = sub { $_[0] =~ /\(v(.+?)\)/ ? $1 : undef };
  2         63  
110             } else {
111 0         0 return "ERR: Cannot check minimum version for program '$prog_name'";
112             }
113              
114 4         194 my $ver = IPC::System::Options::readpipe({log=>1}, $ver_cmd);
115 4 50       44560 my ($exit_code, $signal, $core_dump) = ($? < 0 ? $? : $? >> 8, $? & 127, $? & 128);
116 4 50       201 return "ERR: Cannot check version with '$ver_cmd': exit_code=$exit_code"
117             if $exit_code;
118 4 50       30 ($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       43 if Version::Util::version_lt($ver, $cval->{min_version});
121             }
122              
123 6         167 "";
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 29 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   126 my ($ary, $el) = @_;
168 99 100       284 (grep {$_ eq $el} @$ary) && !(grep {$_ ne $el} @$ary);
  111         492  
  189         589  
169             }
170              
171             sub _all_nonblank_elems_is {
172 10     10   20 my ($ary, $el) = @_;
173 10 100       13 (grep {$_ eq $el} @$ary) && !(grep {$_ && $_ ne $el} @$ary);
  10 100       120  
  16         45  
174             }
175              
176             sub dep_satisfy_rel {
177 298     298 1 2073 my ($wanted, $deps) = @_;
178             #$log->tracef("=> dep_satisfy_rel(%s, %s)", $wanted, $deps);
179              
180 298         328 my $res;
181 298         910 for my $dname (keys %$deps) {
182 279         313 my $dval = $deps->{$dname};
183              
184 279 100       554 if ($dname eq 'all') {
    100          
    100          
185 42         62 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         106  
186             #$log->tracef("all: %s", \@r);
187 42 100       213 next unless @r;
188 41 100       113 return "impossible" if "impossible" ~~ @r;
189 35 100 100     160 return "impossible" if "must" ~~ @r && "must not" ~~ @r;
190 13 100       38 return "must" if "must" ~~ @r;
191 9 100       25 return "must not" if "must not" ~~ @r;
192 5 100       11 return "might" if _all_nonblank_elems_is(\@r, "might");
193             } elsif ($dname eq 'any') {
194 42         60 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         94  
195             #$log->tracef("any: %s", \@r);
196 42 100       76 next unless @r;
197 41 100       113 return "impossible" if "impossible" ~~ @r;
198 35 100       130 return "must" if _all_elems_is(\@r, "must");
199 33 100       55 return "must not" if _all_elems_is(\@r, "must not");
200 31 100       50 next if _all_elems_is(\@r, "");
201 29         95 return "might";
202             } elsif ($dname eq 'none') {
203 63         123 my @r = map { dep_satisfy_rel($wanted, $_) } @$dval;
  77         100  
204             #$log->tracef("none: %s", \@r);
205 63 100       110 next unless @r;
206 62 100       150 return "impossible" if "impossible" ~~ @r;
207 56 100 100     173 return "impossible" if "must" ~~ @r && "must not" ~~ @r;
208 55 100       186 return "must not" if "must" ~~ @r;
209 9 100       24 return "must" if "must not" ~~ @r;
210 5 100       8 return "might" if _all_nonblank_elems_is(\@r, "might");
211             } else {
212 132 100       425 return "must" if $dname eq $wanted;
213             }
214             }
215 53         155 "";
216             }
217              
218             sub list_mentioned_dep_clauses {
219 3     3 1 1507 my ($deps, $res) = @_;
220 3   100     15 $res //= [];
221 3         8 for my $dname (keys %$deps) {
222 4         6 my $dval = $deps->{$dname};
223 4 100       10 push @$res, $dname unless $dname ~~ @$res;
224 4 100       17 if ($dname =~ /\A(?:all|any|none)\z/) {
225 1         9 list_mentioned_dep_clauses($_, $res) for @$dval;
226             }
227             }
228 3         13 $res;
229             }
230              
231             1;
232             # ABSTRACT: Check dependencies from 'deps' property
233              
234             __END__