File Coverage

blib/lib/CPAN/Plugin/Sysdeps.pm
Criterion Covered Total %
statement 1473 1630 90.3
branch 189 334 56.5
condition 53 81 65.4
subroutine 422 430 98.1
pod 2 3 66.6
total 2139 2478 86.3


line stmt bran cond sub pod time code
1             package CPAN::Plugin::Sysdeps;
2              
3 9     9   897010 use strict;
  9         14  
  9         275  
4 9     9   33 use warnings;
  9         33  
  9         611  
5              
6             our $VERSION = '0.83';
7              
8 9     9   40 use List::Util 'first';
  9         17  
  9         672  
9              
10             our $TRAVERSE_ONLY; # only for testing
11              
12 9     9   41 use constant SUPPORTED_NUMERICAL_OPS => ['<','<=','==','>','>='];
  9         19  
  9         1351  
13 9         33 use constant SUPPORTED_NUMERICAL_OPS_RX => do {
14 9         14 my $rx = '^(' . join('|', map { quotemeta } @{SUPPORTED_NUMERICAL_OPS()}) . ')$';
  45         96  
  9         19  
15 9         26550 qr{$rx};
16 9     9   55 };
  9         12  
17              
18             our @OS_RELEASE_PATH_CANDIDATES = ('/etc/os-release', '/usr/lib/os-release');
19              
20             sub new {
21 44     44 1 1324293 my($class, @args) = @_;
22              
23 44         106 my $installer;
24 44         122 my $batch = 0;
25 44         84 my $dryrun = 0;
26 44         86 my $debug = 0;
27 44         141 my @additional_mappings;
28             my @args_errors;
29 44         0 my $options;
30 44         115 for my $arg (@args) {
31 121 100       781 if (ref $arg eq 'HASH') {
    100          
    100          
    100          
    100          
    50          
    0          
32 15 50       37 if ($options) {
33 0         0 die "Cannot handle multiple option hashes";
34             } else {
35 15         31 $options = $arg;
36             }
37             } elsif ($arg =~ m{^(apt-get|aptitude|pkg|pkg_add|yum|dnf|chocolatey|homebrew|apk)$}) { # XXX are there more package installers?
38 24         80 $installer = $1;
39             } elsif ($arg eq 'batch') {
40 33         70 $batch = 1;
41             } elsif ($arg eq 'interactive') {
42 1         3 $batch = 0;
43             } elsif ($arg eq 'dryrun') {
44 32         66 $dryrun = 1;
45             } elsif ($arg =~ m{^mapping=(.*)$}) {
46 16         60 push @additional_mappings, $1;
47             } elsif ($arg =~ m{^debug(?:=(\d+))?$}) {
48 0 0       0 $debug = defined $1 ? $1 : 1;
49             } else {
50 0         0 push @args_errors, $arg;
51             }
52             }
53 44 50       131 if (@args_errors) {
54 0 0       0 die 'Unrecognized ' . __PACKAGE__ . ' argument' . (@args_errors != 1 ? 's' : '') . ": @args_errors\n";
55             }
56              
57 44 50       172 if (exists $ENV{CPAN_PLUGIN_SYSDEPS_DEBUG}) {
58 0         0 $debug = $ENV{CPAN_PLUGIN_SYSDEPS_DEBUG};
59             }
60 44 50       113 if ($debug) {
61 0         0 require Data::Dumper; # we'll need it
62             }
63              
64 44   66     278 my $os = $options->{os} || $^O;
65 44         73 my $osvers = '';
66 44         79 my $linuxdistro = '';
67 44         76 my $linuxdistroversion = 0;
68 44         80 my $linuxdistrocodename = '';
69 44 100 33     178 if ($os eq 'linux') {
    50 33        
70 35         56 my $linux_info;
71             my $get_linux_info = sub {
72 87 100   87   320 return $linux_info if $linux_info;
73 29         107 return $linux_info = _detect_linux_distribution();
74 35         152 };
75 35 100       122 if (defined $options->{linuxdistro}) {
76 6         22 $linuxdistro = $options->{linuxdistro};
77             } else {
78 29         65 $linuxdistro = $get_linux_info->()->{linuxdistro};
79             }
80              
81 35 100       104 if (defined $options->{linuxdistroversion}) {
82 6         12 $linuxdistroversion = $options->{linuxdistroversion};
83             } else {
84 29         77 $linuxdistroversion = $get_linux_info->()->{linuxdistroversion}; # XXX make it a version object? or make sure it's just X.Y?
85             }
86              
87 35 100       84 if (defined $options->{linuxdistrocodename}) {
88 6         30 $linuxdistrocodename = $options->{linuxdistrocodename};
89             } else {
90 29         49 $linuxdistrocodename = $get_linux_info->()->{linuxdistrocodename};
91             }
92             } elsif (($os eq 'freebsd') || ($os eq 'openbsd') || ($os eq 'dragonfly')) {
93             # Note: don't use $Config{osvers}, as this is just the OS
94             # version of the system which built the current perl, not the
95             # actually running OS version.
96 9 50       32 if (defined $options->{osvers}) {
97 9         19 $osvers = $options->{osvers};
98             } else {
99 0         0 chomp($osvers = `/sbin/sysctl -n kern.osrelease`);
100             }
101             }
102              
103 44 100       139 if (!$installer) {
104 20 100 66     124 if ($os eq 'freebsd' || $os eq 'dragonfly') {
    50          
    50          
    50          
    0          
    0          
105 9         19 $installer = 'pkg';
106             } elsif ($os eq 'gnukfreebsd') {
107 0         0 $installer = 'apt-get';
108             } elsif ($os eq 'openbsd') {
109 0         0 $installer = 'pkg_add';
110             } elsif ($os eq 'linux') {
111 11 100       81 if (__PACKAGE__->_is_linux_debian_like($linuxdistro)) {
    50          
    0          
112 10         20 $installer = 'apt-get';
113             } elsif (__PACKAGE__->_is_linux_fedora_like($linuxdistro)) {
114 1 50       14 if (_detect_dnf()) {
115 0         0 $installer = 'dnf';
116             } else {
117 1         25534 $installer = 'yum';
118             }
119             } elsif (__PACKAGE__->_is_linux_alpine_like($linuxdistro)) {
120 0         0 $installer = 'apk';
121             } else {
122 0         0 die __PACKAGE__ . " has no support for linux distribution $linuxdistro $linuxdistroversion\n";
123             }
124             } elsif( $os eq 'MSWin32' ) {
125 0         0 $installer = 'chocolatey';
126             } elsif ($os eq 'darwin') {
127 0         0 $installer = 'homebrew';
128             } else {
129 0         0 die __PACKAGE__ . " has no support for operating system $os\n";
130             }
131             }
132              
133 44         78 my @mapping;
134 44         125 for my $mapping (@additional_mappings, 'CPAN::Plugin::Sysdeps::Mapping') {
135 60 100       1024 if (-r $mapping) {
136 16 50       642 open my $fh, '<', $mapping
137             or die "Can't load $mapping: $!";
138 16         79 local $/;
139 16         489 my $buf = <$fh>;
140 16         3297 push @mapping, eval $buf;
141 16 50       361 die "Error while loading $mapping: $@" if $@;
142             } else {
143 44 50       2916 eval "require $mapping"; die "Can't load $mapping: $@" if $@;
  44         200  
144 44         232 push @mapping, $mapping->mapping;
145             }
146             }
147              
148 44         729 my %config =
149             (
150             installer => $installer,
151             batch => $batch,
152             dryrun => $dryrun,
153             debug => $debug,
154             os => $os,
155             osvers => $osvers,
156             linuxdistro => $linuxdistro,
157             linuxdistroversion => $linuxdistroversion,
158             linuxdistrocodename => $linuxdistrocodename,
159             mapping => \@mapping,
160             _mapper_ran => {},
161             _containsmods_warned => 0,
162             );
163 44         251 my $self = bless \%config, $class;
164 44 50       92 if (eval { require Hash::Util; 1 }) {
  44         5333  
  44         26288  
165 44         172 Hash::Util::lock_keys($self);
166             }
167 44         967 $self;
168             }
169              
170             # CPAN.pm plugin hook method (primary)
171             sub post_get {
172 16     16 1 2752 my($self, $dist) = @_;
173 16         70 $self->_maybe_run_mapper($dist);
174             }
175              
176             # CPAN.pm plugin hook method (secondary, needed only for the "cpan ." workaround)
177             sub pre_make {
178 2     2 0 1857 my($self, $dist) = @_;
179 2         6 $self->_maybe_run_mapper($dist);
180             }
181              
182             # Helpers/Internal functions/methods
183             sub _maybe_run_mapper {
184 18     18   47 my($self, $dist) = @_;
185 18 50       161 if (!$self->{_mapper_ran}{$dist->id}) {
186 18         185 $self->_run_mapper($dist);
187             }
188             }
189              
190             sub _run_mapper {
191 18     18   46 my($self, $dist) = @_;
192              
193 18         107 $self->{_mapper_ran}{$dist->id} = 1;
194              
195 18         140 my @packages = $self->_map_cpandist($dist);
196 17 100       104 if (@packages) {
197 4         18 my @uninstalled_packages = $self->_filter_uninstalled_packages(@packages);
198 4 50       22 if (@uninstalled_packages) {
199 4         54 my @cmds = $self->_install_packages_commands(@uninstalled_packages);
200 4         32 for my $cmd (@cmds) {
201 4 50       19 if ($self->{dryrun}) {
202 4         190 warn "DRYRUN: @$cmd\n";
203             } else {
204 0         0 warn "INFO: run @$cmd...\n";
205              
206 0         0 system @$cmd;
207 0 0       0 if ($? != 0) {
208 0         0 die "@$cmd failed, stop installation";
209             }
210             }
211             }
212             }
213             }
214             }
215              
216             sub _detect_linux_distribution {
217 30     30   822 my $info = _detect_linux_distribution_os_release();
218 30 50       134 return $info if $info;
219 0 0       0 if (-x '/usr/bin/lsb_release') {
220 0         0 _detect_linux_distribution_lsb_release();
221             } else {
222 0         0 _detect_linux_distribution_fallback();
223             }
224             }
225              
226             sub _get_os_release {
227 44     44   417 for my $candidate_file (@OS_RELEASE_PATH_CANDIDATES) {
228 44 100       2395 if (open my $fh, '<', $candidate_file) {
229 43         90 my %c;
230 43         2016 while(<$fh>) {
231 517 100       2296 if (my($k,$v) = $_ =~ m{^(.*?)=["']?(.*?)["']?$}) {
232 509         1505 $c{$k} = $v;
233             }
234             }
235 43         547 return \%c;
236             }
237             }
238             }
239              
240             sub _detect_linux_distribution_os_release {
241 43     43   195072 my $c = _get_os_release();
242 43 100       129 if ($c) {
243 42         67 my %info;
244 42         130 $info{linuxdistro} = $c->{ID};
245 42         89 $info{linuxdistroversion} = $c->{VERSION_ID};
246 42         84 $info{linuxdistrocodename} = $c->{VERSION_CODENAME};
247             # heuristics
248 42 100 100     181 if (!defined $info{linuxdistrocodename} && $info{linuxdistro} eq 'debian' && $c->{VERSION} =~ m{^\d+\s+\((.+)\)$}) {
      66        
249 2         6 $info{linuxdistrocodename} = $1;
250             }
251 42         187 return \%info;
252             }
253 1         4 undef;
254             }
255              
256             sub _detect_linux_distribution_lsb_release {
257 0     0   0 my %info;
258 0         0 my @cmd = ('lsb_release', '-irc');
259 0 0       0 open my $fh, '-|', @cmd
260             or die "Error while running '@cmd': $!";
261 0         0 while(<$fh>) {
262 0         0 chomp;
263 0 0       0 if (m{^Distributor ID:\s+(.*)}) {
    0          
    0          
264 0         0 $info{linuxdistro} = lc $1;
265             } elsif (m{^Release:\s+(.*)}) {
266 0         0 $info{linuxdistroversion} = $1;
267             } elsif (m{^Codename:\s+(.*)}) {
268 0         0 $info{linuxdistrocodename} = $1;
269             } else {
270 0         0 warn "WARNING: unexpected '@cmd' output '$_'";
271             }
272             }
273 0 0       0 close $fh
274             or die "Error while running '@cmd': $!";
275 0         0 \%info;
276             }
277              
278             sub _detect_linux_distribution_fallback {
279 0 0   0   0 if (open my $fh, '<', '/etc/redhat-release') {
280 0         0 my $contents = <$fh>;
281 0 0       0 if ($contents =~ m{^(CentOS|Rocky|RedHat|Fedora) (?:Linux )?release (\d+)\S*( \((.*?)\))?}) {
282 0 0       0 return {linuxdistro => lc $1, linuxdistroversion => $2, linuxdistrocodename => defined $3 ? $3 : ''};
283             }
284             }
285 0 0       0 if (open my $fh, '<', '/etc/issue') {
286 0         0 chomp(my $line = <$fh>);
287 0 0       0 if ($line =~ m{^Linux Mint (\d+) (\S+)}) {
    0          
    0          
288 0         0 return {linuxdistro => lc('LinuxMint'), linuxdistroversion => $1, linuxdistrocodename => $2};
289             } elsif ($line =~ m{^(Debian) GNU/Linux (\d+)}) {
290 0         0 my %info = (linuxdistro => lc $1, linuxdistroversion => $2);
291             $info{linuxdistrocodename} =
292             {
293             6 => 'squeeze',
294             7 => 'wheezy',
295             8 => 'jessie',
296             9 => 'stretch',
297             10 => 'buster',
298             11 => 'bullseye',
299             12 => 'bookworm',
300             13 => 'trixie',
301             14 => 'forky',
302             15 => 'duke',
303 0         0 }->{$info{linuxdistroversion}};
304 0         0 return \%info;
305             } elsif ($line =~ m{^(Ubuntu) (\d+\.\d+)}) {
306 0         0 my %info = (linuxdistro => lc $1, linuxdistroversion => $2);
307             $info{linuxdistrocodename} =
308             {
309             '12.04' => 'precise',
310             '14.04' => 'trusty',
311             '16.04' => 'xenial',
312             '18.04' => 'bionic',
313             '20.04' => 'focal',
314             '22.04' => 'jammy',
315             '24.04' => 'noble',
316             '26.04' => 'resolute',
317 0         0 }->{$info{linuxdistroversion}};
318 0         0 return \%info;
319             } else {
320 0         0 warn "WARNING: don't know how to handle '$line'";
321             }
322             } else {
323 0         0 warn "WARNING: no /etc/issue available";
324             }
325 0         0 return {};
326             }
327              
328             sub _is_linux_debian_like {
329 4322     4322   6358 my(undef, $linuxdistro) = @_;
330 4322         15002 $linuxdistro =~ m{^(debian|ubuntu|linuxmint)$};
331             }
332              
333             sub _is_linux_fedora_like {
334 2713     2713   3960 my(undef, $linuxdistro) = @_;
335 2713         10107 $linuxdistro =~ m{^(fedora|redhat|centos|rocky)$};
336             }
337              
338             sub _is_linux_alpine_like {
339 12     12   35 my(undef, $linuxdistro) = @_;
340 12         59 $linuxdistro =~ m{^(alpine)$};
341             }
342              
343 21     21   217 sub _is_apt_installer { shift->{installer} =~m{^(apt-get|aptitude)$} }
344              
345             # Run a process in an elevated window, wait for its exit
346             sub _win32_run_elevated {
347 0     0   0 my($exe, @args) = @_;
348            
349 0 0       0 my $args = join " ", map { if(/[ "]/) { s!"!\\"!g; qq{"$_"} } else { $_ }} @args;
  0         0  
  0         0  
  0         0  
  0         0  
350              
351 0         0 my $ps1 = sprintf q{powershell -NonInteractive -NoProfile -Command "$process = Start-Process '%s' -PassThru -ErrorAction Stop -ArgumentList '%s' -Verb RunAs -Wait; Exit $process.ExitCode"},
352             $exe, $args;
353              
354 0         0 $ps1;
355             }
356              
357             sub _debug {
358 96478     96478   98837 my $self = shift;
359 96478 50       138055 if ($self->{debug}) {
360 0         0 print STDERR 'DEBUG: ';
361             print STDERR join('', map {
362 0 0       0 if (ref $_) {
  0         0  
363 0         0 Data::Dumper->new([$_])->Terse(1)->Indent(0)->Dump;
364             } else {
365 0         0 $_;
366             }
367             } @_);
368 0         0 print STDERR "\n";
369             }
370             }
371              
372             sub _dist_get_base_id {
373 834     834   4925 my($self, $dist) = @_;
374              
375             # compat for older CPAN.pm (1.76)
376 834 50       2030 if (!$dist->can('base_id')) {
377 0         0 my $id = $dist->id();
378 0         0 my $base_id = File::Basename::basename($id);
379 0         0 $base_id =~ s{\.(?:tar\.(bz2|gz|Z)|t(?:gz|bz)|zip)$}{}i;
380 0         0 return $base_id;
381             }
382              
383 834         1366 $dist->base_id();
384             }
385              
386             sub _dist_containsmods {
387 8608     8608   12161 my($self, $dist) = @_;
388 8608         14901 my @containsmods = $dist->containsmods;
389 8608 100 66     47962 if (!@containsmods && $self->_dist_get_base_id($dist) eq '.') {
390 808 100       22050 if (defined $ENV{CPAN_PLUGIN_SYSDEPS_MODULE}) {
391 397         630 return $ENV{CPAN_PLUGIN_SYSDEPS_MODULE};
392             }
393 411         708 my $id = $dist->id();
394 411 100       1346 if (!$self->{_containsmods_warned}++) {
395 1         20 warn <
396             WARNING: running in local directory '$id'. Please define the environment
397             variable CPAN_PLUGIN_SYSDEPS_MODULE to the primary module of
398             the tested distribution.
399             EOF
400             }
401             }
402 8211         12306 @containsmods;
403             }
404              
405             sub _map_cpandist {
406 32     32   80 my($self, $dist) = @_;
407              
408 32         48 my $get_base_id = do {
409 32         49 my $cached_base_id;
410             sub {
411 22 50   22   65 return $cached_base_id if defined $cached_base_id;
412 22         84 $cached_base_id = $self->_dist_get_base_id($dist);
413 32         166 };
414             };
415              
416             # smartmatch for regexp/string/array without ~~, 5.8.x compat!
417             # also add support for numerical comparisons
418             my $smartmatch = sub ($$) {
419 16169     16169   24859 my($left, $right) = @_;
420 9     9   64 no warnings 'uninitialized';
  9         11  
  9         23139  
421 16169 100       30523 if (ref $right eq 'Regexp') {
    100          
    100          
422 183 100       1610 return 1 if $left =~ $right;
423             } elsif (ref $right eq 'ARRAY') {
424 2029 50 66     8393 return 1 if first { (!defined $left && !defined $_) || ($_ eq $left) } @$right;
  6427 100       22808  
425             } elsif (ref $right eq 'HASH') {
426 425         1067 for my $op (keys %$right) {
427 433 50       1623 if ($op !~ SUPPORTED_NUMERICAL_OPS_RX) {
428 0         0 die "Unsupported operator '$op', only supported: @{SUPPORTED_NUMERICAL_OPS()}";
  0         0  
429             }
430 433         628 my $val = $right->{$op};
431 433         591 my $code = 'no warnings q(numeric); $left '.$op.' $val';
432 433     3   34267 my $res = eval $code;
  3     3   23  
  3     3   6  
  3     3   145  
  3     3   19  
  3     3   6  
  3     3   90  
  3     2   19  
  3     2   6  
  3     2   109  
  3     2   14  
  3     2   5  
  3     2   150  
  3     2   16  
  3     2   4  
  3     2   73  
  3     2   13  
  3     2   6  
  3     2   78  
  3     2   13  
  3     2   6  
  3     2   89  
  2     2   11  
  2     2   2  
  2     2   53  
  2     2   11  
  2     2   3  
  2     2   61  
  2     2   9  
  2     2   4  
  2     2   51  
  2     2   10  
  2     2   2  
  2     2   65  
  2     2   11  
  2     1   4  
  2     1   51  
  2     1   10  
  2     1   4  
  2     1   46  
  2     1   9  
  2     1   4  
  2     1   46  
  2     1   20  
  2     1   4  
  2     1   55  
  2     1   9  
  2     1   4  
  2     1   45  
  2     1   11  
  2     1   3  
  2     1   49  
  2     1   9  
  2     1   3  
  2     1   49  
  2     1   11  
  2     1   4  
  2     1   50  
  2     1   10  
  2     1   3  
  2     1   56  
  2     1   10  
  2     1   3  
  2     1   48  
  2     1   9  
  2     1   3  
  2     1   42  
  2     1   9  
  2     1   3  
  2     1   44  
  2     1   38  
  2     1   5  
  2     1   49  
  2     1   11  
  2     1   2  
  2     1   81  
  2     1   17  
  2     1   3  
  2     1   53  
  2     1   10  
  2     1   4  
  2     1   55  
  2     1   14  
  2     1   4  
  2     1   71  
  2     1   14  
  2     1   4  
  2     1   70  
  2     1   10  
  2     1   3  
  2     1   54  
  2     1   12  
  2     1   4  
  2     1   57  
  2     1   11  
  2     1   4  
  2     1   64  
  2     1   13  
  2     1   4  
  2     1   69  
  2     1   14  
  2     1   6  
  2     1   57  
  2     1   11  
  2     1   3  
  2     1   71  
  1     1   6  
  1     1   1  
  1     1   39  
  1     1   6  
  1     1   1  
  1     1   40  
  1     1   4  
  1     1   3  
  1     1   23  
  1     1   5  
  1     1   2  
  1     1   28  
  1     1   6  
  1     1   3  
  1     1   27  
  1     1   5  
  1     1   2  
  1     1   25  
  1     1   4  
  1     1   1  
  1     1   19  
  1     1   7  
  1     1   2  
  1     1   31  
  1     1   5  
  1     1   2  
  1     1   25  
  1     1   5  
  1     1   2  
  1     1   27  
  1     1   6  
  1     1   2  
  1     1   24  
  1     1   6  
  1     1   1  
  1     1   29  
  1     1   8  
  1     1   14  
  1     1   31  
  1     1   7  
  1     1   3  
  1     1   31  
  1     1   6  
  1     1   2  
  1     1   35  
  1     1   10  
  1     1   3  
  1     1   40  
  1     1   5  
  1     1   2  
  1     1   26  
  1     1   6  
  1     1   2  
  1     1   26  
  1     1   6  
  1     1   2  
  1     1   27  
  1     1   5  
  1     1   1  
  1     1   34  
  1     1   6  
  1     1   2  
  1     1   41  
  1     1   5  
  1     1   2  
  1     1   27  
  1     1   8  
  1     1   2  
  1     1   38  
  1     1   8  
  1     1   4  
  1     1   38  
  1     1   5  
  1     1   3  
  1     1   27  
  1     1   8  
  1     1   3  
  1     1   37  
  1     1   7  
  1     1   2  
  1     1   29  
  1     1   5  
  1     1   2  
  1     1   25  
  1     1   13  
  1     1   3  
  1     1   60  
  1     1   8  
  1     1   4  
  1     1   39  
  1     1   6  
  1     1   2  
  1     1   27  
  1     1   5  
  1     1   2  
  1     1   29  
  1     1   4  
  1     1   2  
  1     1   22  
  1     1   4  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   4  
  1     1   1  
  1     1   21  
  1     1   7  
  1     1   1  
  1     1   41  
  1     1   5  
  1     1   2  
  1     1   22  
  1     1   9  
  1     1   3  
  1     1   36  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   5  
  1     1   2  
  1     1   35  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   3  
  1     1   2  
  1     1   16  
  1     1   5  
  1     1   2  
  1     1   25  
  1     1   5  
  1     1   2  
  1     1   24  
  1     1   4  
  1     1   2  
  1     1   22  
  1     1   4  
  1     1   1  
  1     1   18  
  1     1   7  
  1     1   2  
  1     1   25  
  1     1   4  
  1     1   1  
  1     1   21  
  1     1   22  
  1     1   2  
  1     1   34  
  1     1   6  
  1     1   2  
  1     1   23  
  1     1   6  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   22  
  1     1   5  
  1     1   1  
  1     1   20  
  1     1   4  
  1     1   3  
  1     1   22  
  1     1   4  
  1     1   2  
  1     1   36  
  1     1   4  
  1     1   2  
  1     1   19  
  1     1   4  
  1     1   1  
  1     1   18  
  1     1   4  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   1  
  1     1   22  
  1     1   5  
  1     1   2  
  1     1   23  
  1     1   6  
  1     1   1  
  1     1   22  
  1     1   7  
  1     1   2  
  1     1   32  
  1     1   5  
  1     1   1  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   23  
  1     1   7  
  1     1   1  
  1     1   28  
  1     1   4  
  1     1   2  
  1     1   21  
  1     1   4  
  1     1   2  
  1     1   68  
  1     1   5  
  1     1   1  
  1     1   21  
  1     1   5  
  1     1   1  
  1     1   26  
  1     1   89  
  1     1   3  
  1     1   47  
  1     1   6  
  1     1   2  
  1     1   38  
  1     1   6  
  1     1   2  
  1     1   31  
  1     1   16  
  1     1   2  
  1     1   26  
  1     1   5  
  1     1   2  
  1     1   23  
  1     1   5  
  1     1   3  
  1     1   21  
  1     1   5  
  1     1   1  
  1     1   22  
  1     1   6  
  1     1   2  
  1     1   28  
  1     1   6  
  1     1   3  
  1     1   65  
  1     1   6  
  1     1   2  
  1     1   25  
  1     1   5  
  1     1   2  
  1     1   23  
  1     1   5  
  1     1   2  
  1     1   28  
  1     1   5  
  1     1   1  
  1     1   23  
  1     1   10  
  1     1   2  
  1     1   41  
  1     1   14  
  1     1   2  
  1     1   45  
  1     1   5  
  1     1   2  
  1     1   25  
  1     1   6  
  1     1   2  
  1     1   25  
  1     1   7  
  1     1   2  
  1     1   27  
  1     1   6  
  1     1   2  
  1     1   24  
  1     1   5  
  1     1   2  
  1     1   22  
  1     1   5  
  1     1   1  
  1     1   24  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   5  
  1     1   8  
  1     1   22  
  1         7  
  1         2  
  1         27  
  1         4  
  1         1  
  1         21  
  1         6  
  1         2  
  1         26  
  1         8  
  1         3  
  1         42  
  1         13  
  1         4  
  1         58  
  1         9  
  1         2  
  1         37  
  1         11  
  1         3  
  1         37  
  1         10  
  1         2  
  1         43  
  1         5  
  1         2  
  1         25  
  1         4  
  1         2  
  1         21  
  1         4  
  1         2  
  1         23  
  1         6  
  1         2  
  1         57  
  1         7  
  1         2  
  1         44  
  1         6  
  1         2  
  1         43  
  1         12  
  1         4  
  1         62  
  1         9  
  1         2  
  1         39  
  1         6  
  1         3  
  1         29  
  1         5  
  1         2  
  1         19  
  1         5  
  1         1  
  1         18  
  1         5  
  1         2  
  1         26  
  1         6  
  1         2  
  1         28  
  1         6  
  1         1  
  1         26  
  1         4  
  1         2  
  1         27  
  1         10  
  1         2  
  1         27  
  1         5  
  1         2  
  1         23  
  1         9  
  1         4  
  1         38  
  1         10  
  1         2  
  1         51  
  1         9  
  1         3  
  1         35  
  1         9  
  1         3  
  1         40  
  1         6  
  1         4  
  1         35  
  1         6  
  1         13  
  1         43  
  1         4  
  1         2  
  1         22  
  1         5  
  1         2  
  1         22  
  1         5  
  1         3  
  1         20  
  1         5  
  1         1  
  1         19  
  1         5  
  1         2  
  1         21  
  1         4  
  1         2  
  1         20  
  1         5  
  1         2  
  1         30  
  1         6  
  1         2  
  1         20  
  1         9  
  1         2  
  1         39  
  1         5  
  1         2  
  1         20  
  1         5  
  1         1  
  1         22  
  1         6  
  1         3  
  1         25  
  1         5  
  1         2  
  1         22  
  1         5  
  1         1  
  1         20  
  1         5  
  1         2  
  1         21  
  1         5  
  1         1  
  1         21  
  1         6  
  1         2  
  1         42  
  1         5  
  1         2  
  1         22  
  1         5  
  1         2  
  1         30  
  1         5  
  1         2  
  1         21  
  1         9  
  1         4  
  1         43  
  1         7  
  1         4  
  1         34  
  1         8  
  1         4  
  1         35  
  1         6  
  1         1  
  1         28  
  1         11  
  1         2  
  1         49  
  1         5  
  1         1  
  1         36  
  1         5  
  1         2  
  1         24  
  1         6  
  1         1  
  1         36  
  1         5  
  1         1  
  1         24  
  1         6  
  1         2  
  1         24  
  1         5  
  1         2  
  1         28  
  1         5  
  1         2  
  1         22  
  1         5  
  1         1  
  1         27  
  1         7  
  1         2  
  1         26  
  1         10  
  1         3  
  1         39  
  1         6  
  1         2  
  1         26  
  1         6  
  1         2  
  1         26  
  1         3  
  1         3  
  1         17  
  1         9  
  1         4  
  1         36  
  1         5  
  1         2  
  1         23  
  1         5  
  1         2  
  1         24  
  1         6  
  1         2  
  1         22  
  1         7  
  1         2  
  1         27  
  1         5  
  1         2  
  1         25  
  1         14  
  1         3  
  1         59  
  1         10  
  1         3  
  1         38  
  1         8  
  1         3  
  1         53  
  1         14  
  1         3  
  1         52  
  1         9  
  1         1  
  1         40  
  1         8  
  1         3  
  1         37  
  1         10  
  1         3  
  1         37  
  1         9  
  1         4  
  1         38  
  1         5  
  1         2  
  1         35  
  1         6  
  1         2  
  1         24  
  1         7  
  1         2  
  1         26  
  1         5  
  1         1  
  1         22  
  1         5  
  1         2  
  1         32  
  1         5  
  1         1  
  1         24  
  1         4  
  1         1  
  1         18  
  1         10  
  1         3  
  1         44  
  1         9  
  1         2  
  1         34  
  1         6  
  1         2  
  1         26  
  1         5  
  1         2  
  1         24  
  1         6  
  1         1  
  1         27  
  1         8  
  1         3  
  1         31  
  1         5  
  1         2  
  1         24  
  1         6  
  1         2  
  1         27  
  1         6  
  1         2  
  1         24  
  1         6  
  1         2  
  1         28  
  1         6  
  1         2  
  1         27  
  1         5  
  1         2  
  1         26  
  1         5  
  1         2  
  1         24  
  1         5  
  1         2  
  1         24  
  1         5  
  1         2  
  1         23  
  1         5  
  1         1  
  1         22  
  1         8  
  1         11  
  1         37  
  1         9  
  1         3  
  1         50  
  1         7  
  1         3  
  1         34  
  1         7  
  1         2  
  1         29  
  1         10  
  1         2  
  1         43  
  1         5  
  1         3  
  1         21  
  1         6  
  1         2  
  1         27  
  1         9  
  1         4  
  1         47  
  1         10  
  1         3  
  1         38  
  1         8  
  1         3  
  1         33  
  1         6  
  1         2  
  1         27  
  1         5  
  1         2  
  1         24  
  1         5  
  1         2  
  1         36  
  1         5  
  1         2  
  1         22  
  1         6  
  1         2  
  1         25  
  1         3  
  1         3  
  1         18  
  1         4  
  1         2  
  1         23  
  1         4  
  1         2  
  1         19  
  1         4  
  1         2  
  1         18  
  1         6  
  1         3  
  1         24  
  1         6  
  1         1  
  1         24  
  1         5  
  1         2  
  1         22  
  1         4  
  1         2  
  1         19  
  1         6  
  1         1  
  1         23  
  1         6  
  1         1  
  1         29  
  1         5  
  1         3  
  1         29  
  1         5  
  1         8  
  1         24  
  1         5  
  1         1  
  1         19  
  1         6  
  1         1  
  1         23  
  1         6  
  1         8  
  1         24  
  1         5  
  1         2  
  1         21  
  1         166  
  1         2  
  1         28  
  1         4  
  1         2  
  1         22  
  1         3  
  1         1  
  1         18  
  1         3  
  1         2  
  1         33  
  1         5  
  1         1  
  1         21  
  1         5  
  1         1  
  1         37  
  1         4  
  1         3  
  1         20  
  1         5  
  1         2  
  1         33  
  1         5  
  1         1  
  1         21  
  1         5  
  1         3  
  1         26  
  1         5  
  1         2  
  1         20  
  1         5  
  1         1  
  1         25  
  1         5  
  1         2  
  1         75  
  1         5  
  1         2  
  1         21  
  1         4  
  1         2  
  1         18  
  1         5  
  1         2  
  1         20  
  1         5  
  1         1  
  1         20  
  1         5  
  1         2  
  1         29  
  1         5  
  1         2  
  1         23  
  1         9  
  1         3  
  1         43  
  1         8  
  1         3  
  1         36  
  1         8  
  1         4  
  1         39  
  1         8  
  1         2  
  1         37  
  1         8  
  1         4  
  1         36  
  1         9  
  1         2  
  1         39  
  1         7  
  1         2  
  1         31  
  1         5  
  1         2  
  1         23  
  1         4  
  1         8  
  1         20  
  1         6  
  1         2  
  1         24  
  1         5  
  1         2  
  1         21  
  1         6  
  1         1  
  1         22  
  1         5  
  1         1  
  1         29  
  1         5  
  1         2  
  1         29  
  1         5  
  1         1  
  1         23  
  1         6  
  1         2  
  1         22  
  1         6  
  1         2  
  1         22  
  1         4  
  1         2  
  1         21  
  1         4  
  1         2  
  1         21  
  1         3  
  1         2  
  1         17  
  1         4  
  1         2  
  1         20  
  1         4  
  1         2  
  1         18  
  1         5  
  1         1  
  1         23  
  1         5  
  1         2  
  1         20  
  1         5  
  1         2  
  1         22  
  1         4  
  1         1  
  1         21  
  1         7  
  1         3  
  1         36  
  1         5  
  1         13  
  1         22  
  1         6  
  1         1  
  1         23  
  1         7  
  1         1  
  1         43  
  1         5  
  1         1  
  1         21  
  1         5  
  1         1  
  1         21  
  1         5  
  1         1  
  1         21  
  1         5  
  1         2  
  1         40  
  1         6  
  1         1  
  1         38  
  1         6  
  1         2  
  1         24  
  1         6  
  1         1  
  1         32  
  1         6  
  1         9  
  1         22  
  1         5  
  1         2  
  1         22  
  1         5  
  1         2  
  1         18  
  1         4  
  1         1  
  1         17  
  1         5  
  1         2  
  1         25  
  1         6  
  1         1  
  1         24  
  1         5  
  1         1  
  1         23  
  1         4  
  1         2  
  1         18  
  1         5  
  1         2  
  1         27  
  1         4  
  1         2  
  1         20  
  1         5  
  1         2  
  1         41  
  1         5  
  1         2  
  1         26  
  1         5  
  1         3  
  1         22  
  1         4  
  1         2  
  1         22  
  1         6  
  1         2  
  1         26  
  1         5  
  1         2  
  1         36  
  1         4  
  1         3  
  1         21  
  1         6  
  1         2  
  1         22  
  1         4  
  1         2  
  1         17  
  1         5  
  1         8  
  1         21  
  1         4  
  1         2  
  1         17  
  1         4  
  1         2  
  1         21  
  1         5  
  1         2  
  1         19  
  1         5  
  1         2  
  1         27  
  1         6  
  1         1  
  1         24  
  1         8  
  1         2  
  1         42  
  1         5  
  1         2  
  1         23  
  1         5  
  1         2  
  1         23  
  1         8  
  1         2  
  1         26  
  1         10  
  1         3  
  1         55  
  1         9  
  1         4  
  1         39  
  1         5  
  1         2  
  1         25  
  1         5  
  1         2  
  1         22  
  1         5  
  1         2  
  1         36  
  1         5  
  1         2  
  1         24  
  1         10  
  1         3  
  1         35  
  1         6  
  1         2  
  1         40  
  1         6  
  1         2  
  1         26  
  1         4  
  1         2  
  1         20  
  1         5  
  1         2  
  1         23  
  1         6  
  1         2  
  1         36  
  1         6  
  1         2  
  1         27  
  1         8  
  1         1  
  1         25  
  1         4  
  1         3  
  1         19  
  1         6  
  1         2  
  1         40  
  1         5  
  1         1  
  1         25  
  1         7  
  1         1  
  1         22  
  1         7  
  1         8  
  1         28  
  1         24  
  1         11  
  1         41  
  1         6  
  1         1  
  1         25  
  1         6  
  1         1  
  1         27  
  1         5  
  1         2  
  1         25  
  1         6  
  1         1  
  1         39  
  1         8  
  1         4  
  1         28  
  1         5  
  1         3  
  1         24  
  1         5  
  1         1  
  1         20  
  1         5  
  1         3  
  1         25  
  1         6  
  1         2  
  1         33  
  1         7  
  1         2  
  1         27  
  1         5  
  1         2  
  1         25  
  1         7  
  1         2  
  1         38  
  1         5  
  1         2  
  1         22  
  1         5  
  1         2  
  1         23  
  1         6  
  1         3  
  1         29  
  1         5  
  1         2  
  1         24  
  1         4  
  1         2  
  1         23  
  1         6  
  1         2  
  1         24  
  1         10  
  1         3  
  1         22  
433 433 50       1167 die "Evaluation of '$code' failed: $@" if $@;
434 433 100       2392 return 0 if !$res;
435             }
436 166         780 return 1;
437             } else {
438 13532 50 66     18620 return 1 if !defined $left && !defined $right;
439 13532 100       38479 return 1 if $left eq $right;
440             }
441 32         206 };
442              
443 32         54 my $handle_mapping_entry; $handle_mapping_entry = sub {
444 37366     37366   46029 my($entry, $level) = @_;
445 37366         58015 for(my $map_i=0; $map_i <= $#$entry; $map_i++) {
446 66801         84653 my $key_or_subentry = $entry->[$map_i];
447 66801 100       94910 if (ref $key_or_subentry eq 'ARRAY') {
    100          
448 28738         55090 $self->_debug(' ' x $level . ' traverse another tree level');
449 28738         38252 my $res = $handle_mapping_entry->($key_or_subentry, $level+1);
450 28737 100 100     97792 return $res if $res && !$TRAVERSE_ONLY;
451             } elsif (ref $key_or_subentry eq 'CODE') {
452 1         24 my $res = $key_or_subentry->($self, $dist);
453 1 50 33     6 return $res if $res && !$TRAVERSE_ONLY;
454             } else {
455 38062         39455 my $key = $key_or_subentry;
456 38062         44179 my $match = $entry->[++$map_i];
457 38062         78439 $self->_debug(' ' x $level . " match '$key' against '", $match, "'");
458 38062 100       87943 if ($key eq 'cpandist') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
459 22 100 100     76 return 0 if !$smartmatch->($get_base_id->(), $match) && !$TRAVERSE_ONLY;
460             } elsif ($key eq 'cpanmod') {
461 8606         8936 my $found = 0;
462 8606         12532 for my $mod ($self->_dist_containsmods($dist)) {
463 8196         17492 $self->_debug(' ' x $level . " found module '$mod' in dist, check now against '", $match, "'");
464 8196 100       11315 if ($smartmatch->($mod, $match)) {
465 17         23 $found = 1;
466 17         26 last;
467             }
468             }
469 8606 100 100     24868 return 0 if !$found && !$TRAVERSE_ONLY;
470             } elsif ($key eq 'os') {
471 5313 100 100     7438 return 0 if !$smartmatch->($self->{os}, $match) && !$TRAVERSE_ONLY;
472             } elsif ($key eq 'osvers') {
473 178 100 100     358 return 0 if !$smartmatch->($self->{osvers}, $match) && !$TRAVERSE_ONLY;
474             } elsif ($key eq 'linuxdistro') {
475 7768 100       19887 if ($match =~ m{^~(debian|fedora|alpine)$}) {
    100          
476 7035         12758 my $method = "_is_linux_$1_like";
477 7035         15034 $self->_debug(' ' x $level . " translate $match to $method");
478 7035 50 66     14019 return 0 if !$self->$method($self->{linuxdistro}) && !$TRAVERSE_ONLY;
479             } elsif ($match =~ m{^~}) {
480 1         12 die "'like' matches only for debian, fedora, and alpine";
481             } else {
482 732 50 66     1319 return 0 if !$smartmatch->($self->{linuxdistro}, $match) && !$TRAVERSE_ONLY;
483             }
484             } elsif ($key eq 'linuxdistroversion') {
485 420 50 66     773 return 0 if !$smartmatch->($self->{linuxdistroversion}, $match) && !$TRAVERSE_ONLY;
486             } elsif ($key eq 'linuxdistrocodename') {
487 1308 50 66     1893 return 0 if !$smartmatch->($self->{linuxdistrocodename}, $match) && !$TRAVERSE_ONLY; # XXX should also do a smart codename comparison additionally!
488             } elsif ($key eq 'package') {
489 14447         31836 $self->_debug(' ' x $level . " found $match"); # XXX array?
490 14447         30934 return { package => $match };
491             } else {
492 0         0 die "Invalid key '$key'"; # XXX context/position?
493             }
494             }
495             }
496 32         298 };
497              
498 32 50       79 for my $entry (@{ $self->{mapping} || [] }) {
  32         141  
499 8628         11003 my $res = $handle_mapping_entry->($entry, 0);
500 8627 100 66     16462 if ($res && !$TRAVERSE_ONLY) {
501 12 100       85 return ref $res->{package} eq 'ARRAY' ? @{ $res->{package} } : $res->{package};
  2         29  
502             }
503             }
504              
505 19         110 ();
506             }
507              
508             sub _detect_dnf {
509 1     1   3 my @cmd = ('dnf', '--help');
510 1         535 require IPC::Open3;
511 1         2589 require Symbol;
512 1         4 my $err = Symbol::gensym();
513 1         14 my $fh;
514 1         1 return eval {
515 1 0       4 if (my $pid = IPC::Open3::open3(undef, $fh, $err, @cmd)) {
516 0         0 waitpid $pid, 0;
517 0         0 return $? == 0;
518             }
519             };
520             }
521              
522             sub _find_missing_deb_packages {
523 7     7   23 my($self, @packages) = @_;
524 7 100       23 return () if !@packages;
525              
526             # taken from ~/devel/deb-install.pl
527 6         12 my %seen_packages;
528             my @missing_packages;
529              
530 6         16 my @cmd = ('dpkg-query', '-W', '-f=${Package} ${Status}\n', @packages);
531 6         1910 require IPC::Open3;
532 6         11761 require Symbol;
533 6         39 my $err = Symbol::gensym();
534 6         124 my $fh;
535 6 50       25 my $pid = IPC::Open3::open3(undef, $fh, $err, @cmd)
536             or die "Error running '@cmd': $!";
537 6         63484 while(<$fh>) {
538 0         0 chomp;
539 0 0       0 if (m{^(\S+) (.*)}) {
540 0 0       0 if ($2 ne 'install ok installed') {
541 0         0 push @missing_packages, $1;
542             }
543 0         0 $seen_packages{$1} = 1;
544             } else {
545 0         0 warn "ERROR: cannot parse $_, ignore line...\n";
546             }
547             }
548 6         149 waitpid $pid, 0;
549 6         68 for my $package (@packages) {
550 8 50       83 if (!$seen_packages{$package}) {
551 8         73 push @missing_packages, $package;
552             }
553             }
554 6         335 @missing_packages;
555             }
556              
557             sub _find_missing_rpm_packages {
558 6     6   452055 my($self, @packages) = @_;
559 6 100       79 return () if !@packages;
560              
561 5         48 my @missing_packages;
562             {
563 5         53 my %packages = map{($_,1)} @packages;
  5         149  
  6         124  
564              
565 5         134 local $ENV{LC_ALL} = 'C';
566 5         30 my @cmd = ('rpm', '-q', @packages);
567 5 50       18207 open my $fh, '-|', @cmd
568             or die "Error running '@cmd': $!";
569 5         2009798 while(<$fh>) {
570 8 100       10748 if (m{^package (\S+) is not installed}) {
571 4         85 my $package = $1;
572 4 50       39 if (!exists $packages{$package}) {
573 0         0 die "Unexpected: package $package listed as non-installed, but not queried in '@cmd'?!";
574             }
575 4         16661 push @missing_packages, $package;
576             }
577             }
578             }
579 5 100       77 return () if !@missing_packages;
580              
581             # maybe the packages are just provided by another package?
582 4         16 my @definitively_missing_packages;
583             {
584 4         7 my %packages = map{($_,1)} @missing_packages;
  4         33  
  4         25  
585              
586 4         52 local $ENV{LC_ALL} = 'C';
587 4         22 my @cmd = ('rpm', '-q', '--whatprovides', @missing_packages);
588 4 50       14520 open my $fh, '-|', @cmd
589             or die "Error running '@cmd': $!";
590 4         1509171 while(<$fh>) {
591 5 100       5385 if (m{^no package provides (\S+)}) {
592 3         63 my $package = $1;
593 3 50       31 if (!exists $packages{$package}) {
594 0         0 die "Unexpected: package $package listed as non-installed, but not queried in '@cmd'?!";
595             }
596 3         12768 push @definitively_missing_packages, $package;
597             }
598             }
599             }
600              
601 4         195 @definitively_missing_packages;
602             }
603              
604             sub _find_missing_freebsd_pkg_packages {
605 0     0   0 my($self, @packages) = @_;
606 0 0       0 return () if !@packages;
607              
608 0         0 my @missing_packages;
609 0         0 for my $package (@packages) {
610 0         0 my @cmd = ('pkg', 'info', '--exists', $package);
611 0         0 system @cmd;
612 0 0       0 if ($? != 0) {
613 0         0 push @missing_packages, $package;
614             }
615             }
616              
617 0         0 @missing_packages;
618             }
619              
620             sub _find_missing_openbsd_pkg_packages {
621 0     0   0 my($self, @packages) = @_;
622 0 0       0 return () if !@packages;
623              
624 0         0 require IPC::Open3;
625 0         0 require Symbol;
626              
627 0         0 my @missing_packages;
628 0         0 for my $package (@packages) {
629 0         0 my $err = Symbol::gensym();
630 0         0 my $fh;
631             my $package_in_repository;
632 0         0 eval {
633 0 0       0 if (my $pid = IPC::Open3::open3(undef, $fh, $err, 'pkg_info', $package)) {
634 0         0 waitpid $pid, 0;
635 0 0       0 if ($? == 0) {
636 0         0 $package_in_repository = 1;
637             }
638             }
639             };
640 0 0       0 if ($package_in_repository) {
641 0         0 my @cmd = ('pkg_info', '-q', '-e', "${package}->=0");
642 0         0 system @cmd;
643 0 0       0 if ($? != 0) {
644 0         0 push @missing_packages, $package;
645             }
646             }
647             }
648              
649 0         0 @missing_packages;
650             }
651              
652             sub _find_missing_homebrew_packages {
653 0     0   0 my($self, @packages) = @_;
654 0 0       0 return () if !@packages;
655              
656 0         0 my @missing_packages;
657 0         0 for my $package (@packages) {
658 0         0 my @cmd = ('brew', 'ls', '--versions', $package);
659 0 0       0 open my $fh, '-|', @cmd
660             or die "Error running @cmd: $!";
661 0         0 my $has_package;
662 0         0 while(<$fh>) {
663 0         0 $has_package = 1;
664 0         0 last;
665             }
666 0         0 close $fh; # earlier homebrew versions returned always 0,
667             # newer (since Oct 2016) return 1 if the package is
668             # missing
669 0 0       0 if (!$has_package) {
670 0         0 push @missing_packages, $package;
671             }
672             }
673 0         0 @missing_packages;
674             }
675              
676             sub _find_missing_chocolatey_packages {
677 0     0   0 my($self, @packages) = @_;
678 0 0       0 return () if !@packages;
679              
680             my %installed_packages = map {
681 0 0       0 /^(.*)\|(.*)$/
682             or next;
683 0         0 $1 => $2
684             } grep {
685 0         0 /^(.*)\|(.*)$/
  0         0  
686             } `choco list --localonly --limit-output`;
687 0         0 my @missing_packages = grep { ! $installed_packages{ $_ }} @packages;
  0         0  
688 0         0 @missing_packages;
689             }
690              
691             sub _find_missing_apk_packages {
692 0     0   0 my($self, @packages) = @_;
693 0 0       0 return () if !@packages;
694              
695 0         0 my @missing_packages;
696 0         0 for my $package (@packages) {
697 0         0 my @cmd = ('apk', 'info', '-e', $package);
698 0         0 system @cmd;
699 0 0       0 if ($? != 0) {
700 0         0 push @missing_packages, $package;
701             }
702             }
703              
704 0         0 @missing_packages;
705             }
706              
707             sub _filter_uninstalled_packages {
708 6     6   2092 my($self, @packages) = @_;
709 6         12 my $find_missing_packages;
710 6 50 0     24 if ($self->_is_apt_installer) {
    0 0        
    0          
    0          
    0          
    0          
    0          
711 6         14 $find_missing_packages = '_find_missing_deb_packages';
712             } elsif (($self->{installer} eq 'yum') || ($self->{installer} eq 'dnf')) {
713 0         0 $find_missing_packages = '_find_missing_rpm_packages';
714             } elsif ($self->{os} eq 'freebsd' || $self->{os} eq 'dragonfly') {
715 0         0 $find_missing_packages = '_find_missing_freebsd_pkg_packages';
716             } elsif ($self->{os} eq 'openbsd') {
717 0         0 $find_missing_packages = '_find_missing_openbsd_pkg_packages';
718             } elsif ($self->{os} eq 'MSWin32') {
719 0         0 $find_missing_packages = '_find_missing_chocolatey_packages';
720             } elsif ($self->{installer} eq 'homebrew') {
721 0         0 $find_missing_packages = '_find_missing_homebrew_packages';
722             } elsif ($self->{installer} eq 'apk') {
723 0         0 $find_missing_packages = '_find_missing_apk_packages';
724             } else {
725 0         0 warn "check for installed packages is NYI for $self->{os}/$self->{linuxdistro}";
726             }
727 6 50       21 if ($find_missing_packages) {
728 6         27 my @plain_packages;
729             my @missing_packages;
730 6         11 for my $package_spec (@packages) {
731 7 100       28 if ($package_spec =~ m{\|}) { # has alternatives
732 1         7 my @single_packages = split /\s*\|\s*/, $package_spec;
733 1         11 my @missing_in_packages_spec = $self->$find_missing_packages(@single_packages);
734 1 50       21 if (@missing_in_packages_spec == @single_packages) {
735 1         15 push @missing_packages, $single_packages[0]; # XXX this could go wrong if the first alternative is not available at all; see problem with jpeg|jpeg-turbo in Mapping.pm
736             }
737             } else {
738 6         14 push @plain_packages, $package_spec;
739             }
740             }
741 6         39 push @missing_packages, $self->$find_missing_packages(@plain_packages);
742 6         66 @packages = @missing_packages;
743             }
744 6         68 @packages;
745             }
746              
747             sub _install_packages_commands {
748 15     15   112 my($self, @packages) = @_;
749 15         41 my @pre_cmd;
750             my @install_cmd;
751              
752             # sudo or not?
753 15 100       119 if ($self->{installer} eq 'homebrew') {
    50          
754             # may run as unprivileged user
755             } elsif ($self->{installer} eq 'chocolatey') {
756             # no sudo on Windows systems?
757             } else {
758 14 50       128 if ($< != 0) {
759 0         0 push @install_cmd, 'sudo';
760             }
761             }
762              
763             # the installer executable
764 15 100       110 if ($self->{installer} eq 'homebrew') {
    50          
765 1         3 push @install_cmd, 'brew';
766             } elsif ($self->{installer} eq 'chocolatey') {
767 0         0 push @install_cmd, 'choco';
768             } else {
769 14         59 push @install_cmd, $self->{installer};
770             }
771              
772             # batch, default or interactive
773 15 100       49 if ($self->{batch}) {
774 6 100 33     83 if ($self->_is_apt_installer) {
    50          
    50          
    50          
    50          
775 5         25 push @install_cmd, '-y';
776             } elsif (($self->{installer} eq 'yum') || ($self->{installer} eq 'dnf')) {
777 0         0 push @install_cmd, '-y';
778             } elsif ($self->{installer} eq 'pkg') { # FreeBSD's pkg
779             # see below
780             } elsif ($self->{installer} eq 'homebrew') {
781             # batch by default
782             } elsif ($self->{installer} eq 'apk') {
783             # batch by default
784             } else {
785 0         0 warn "batch=1 NYI for $self->{installer}";
786             }
787             } else {
788 9 100 100     28 if ($self->_is_apt_installer) {
    100          
    100          
    50          
    100          
    100          
789 3         13 @pre_cmd = ('sh', '-c', 'echo -n "Install package(s) '."@packages".'? (y/N) "; read yn; [ "$yn" = "y" ]');
790             } elsif (($self->{installer} eq 'yum') || ($self->{installer} eq 'dnf')) {
791             # interactive by default
792             } elsif ($self->{installer} eq 'pkg') { # FreeBSD's pkg
793             # see below
794             } elsif ($self->{installer} =~ m{^(chocolatey)$}) {
795             # Nothing to do here
796             } elsif ($self->{installer} eq 'homebrew') {
797             # the sh builtin echo does not understand -n -> use printf
798 1         6 @pre_cmd = ('sh', '-c', 'printf %s "Install package(s) '."@packages".'? (y/N) "; read yn; [ "$yn" = "y" ]');
799             } elsif ($self->{installer} eq 'apk') {
800 1         6 @pre_cmd = ('sh', '-c', 'printf %s "Install package(s) '."@packages".'? (y/N) "; read yn; [ "$yn" = "y" ]');
801             } else {
802 1         18 warn "batch=0 NYI for $self->{installer}";
803             }
804             }
805              
806             # special options
807 15 100       81 if ($self->{installer} eq 'pkg') { # FreeBSD's pkg
808 1         3 push @install_cmd, '--option', 'LOCK_RETRIES=86400'; # wait quite long in case there are concurrent pkg runs
809             }
810              
811             # the installer subcommand
812 15 100       78 if ($self->{installer} eq 'apk') {
    100          
813 2         4 push @install_cmd, 'add';
814             } elsif ($self->{installer} ne 'pkg_add') {
815 12         32 push @install_cmd, 'install';
816             }
817              
818             # post options
819 15 50 66     97 if ($self->{batch} && $self->{installer} eq 'pkg') {
820 0         0 push @install_cmd, '-y';
821             }
822 15 50 66     78 if ($self->{batch} && $self->{installer} eq 'chocolatey') {
823 0         0 push @install_cmd, '-y';
824             }
825 15 50 66     143 if ($self->{batch} && $self->{installer} eq 'pkg_add') {
826 0         0 push @install_cmd, '-I';
827             }
828              
829 15         34 push @install_cmd, @packages;
830            
831 15 50       53 if ($self->{os} eq 'MSWin32') {
832             # Wrap the thing in our small powershell program
833 0         0 @install_cmd = _win32_run_elevated(@install_cmd);
834             };
835              
836 15 100       86 ((@pre_cmd ? \@pre_cmd : ()), \@install_cmd);
837             }
838              
839             1;
840              
841             __END__