File Coverage

blib/lib/Perinci/Sub/DepChecker.pm
Criterion Covered Total %
statement 124 142 87.3
branch 89 108 82.4
condition 10 11 90.9
subroutine 19 25 76.0
pod 3 16 18.7
total 245 302 81.1


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