File Coverage

blib/lib/CPAN/Plugin/Sysdeps.pm
Criterion Covered Total %
statement 1449 1608 90.1
branch 179 322 55.5
condition 51 78 65.3
subroutine 416 425 97.8
pod 2 2 100.0
total 2097 2435 86.1


line stmt bran cond sub pod time code
1             package CPAN::Plugin::Sysdeps;
2              
3 8     8   914847 use strict;
  8         14  
  8         248  
4 8     8   45 use warnings;
  8         15  
  8         519  
5              
6             our $VERSION = '0.81';
7              
8 8     8   43 use List::Util 'first';
  8         18  
  8         677  
9              
10             our $TRAVERSE_ONLY; # only for testing
11              
12 8     8   35 use constant SUPPORTED_NUMERICAL_OPS => ['<','<=','==','>','>='];
  8         32  
  8         1249  
13 8         12 use constant SUPPORTED_NUMERICAL_OPS_RX => do {
14 8         13 my $rx = '^(' . join('|', map { quotemeta } @{SUPPORTED_NUMERICAL_OPS()}) . ')$';
  40         104  
  8         17  
15 8         22256 qr{$rx};
16 8     8   47 };
  8         13  
17              
18             our @OS_RELEASE_PATH_CANDIDATES = ('/etc/os-release', '/usr/lib/os-release');
19              
20             sub new {
21 42     42 1 1300912 my($class, @args) = @_;
22              
23 42         82 my $installer;
24 42         93 my $batch = 0;
25 42         74 my $dryrun = 0;
26 42         66 my $debug = 0;
27 42         136 my @additional_mappings;
28             my @args_errors;
29 42         0 my $options;
30 42         118 for my $arg (@args) {
31 117 100       821 if (ref $arg eq 'HASH') {
    100          
    100          
    100          
    100          
    50          
    0          
32 15 50       35 if ($options) {
33 0         0 die "Cannot handle multiple option hashes";
34             } else {
35 15         32 $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         74 $installer = $1;
39             } elsif ($arg eq 'batch') {
40 31         68 $batch = 1;
41             } elsif ($arg eq 'interactive') {
42 1         2 $batch = 0;
43             } elsif ($arg eq 'dryrun') {
44 30         59 $dryrun = 1;
45             } elsif ($arg =~ m{^mapping=(.*)$}) {
46 16         67 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 42 50       121 if (@args_errors) {
54 0 0       0 die 'Unrecognized ' . __PACKAGE__ . ' argument' . (@args_errors != 1 ? 's' : '') . ": @args_errors\n";
55             }
56              
57 42 50       177 if (exists $ENV{CPAN_PLUGIN_SYSDEPS_DEBUG}) {
58 0         0 $debug = $ENV{CPAN_PLUGIN_SYSDEPS_DEBUG};
59             }
60 42 50       115 if ($debug) {
61 0         0 require Data::Dumper; # we'll need it
62             }
63              
64 42   66     273 my $os = $options->{os} || $^O;
65 42         79 my $osvers = '';
66 42         78 my $linuxdistro = '';
67 42         60 my $linuxdistroversion = 0;
68 42         82 my $linuxdistrocodename = '';
69 42 100 33     141 if ($os eq 'linux') {
    50 33        
70 33         60 my $linux_info;
71             my $get_linux_info = sub {
72 81 100   81   268 return $linux_info if $linux_info;
73 27         69 return $linux_info = _detect_linux_distribution();
74 33         149 };
75 33 100       92 if (defined $options->{linuxdistro}) {
76 6         28 $linuxdistro = $options->{linuxdistro};
77             } else {
78 27         56 $linuxdistro = $get_linux_info->()->{linuxdistro};
79             }
80              
81 33 100       103 if (defined $options->{linuxdistroversion}) {
82 6         12 $linuxdistroversion = $options->{linuxdistroversion};
83             } else {
84 27         61 $linuxdistroversion = $get_linux_info->()->{linuxdistroversion}; # XXX make it a version object? or make sure it's just X.Y?
85             }
86              
87 33 100       77 if (defined $options->{linuxdistrocodename}) {
88 6         30 $linuxdistrocodename = $options->{linuxdistrocodename};
89             } else {
90 27         62 $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       24 if (defined $options->{osvers}) {
97 9         21 $osvers = $options->{osvers};
98             } else {
99 0         0 chomp($osvers = `/sbin/sysctl -n kern.osrelease`);
100             }
101             }
102              
103 42 100       148 if (!$installer) {
104 18 100 66     118 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 9 100       54 if (__PACKAGE__->_is_linux_debian_like($linuxdistro)) {
    50          
    0          
112 8         18 $installer = 'apt-get';
113             } elsif (__PACKAGE__->_is_linux_fedora_like($linuxdistro)) {
114 1 50       3 if (_detect_dnf()) {
115 0         0 $installer = 'dnf';
116             } else {
117 1         18476 $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 42         84 my @mapping;
134 42         97 for my $mapping (@additional_mappings, 'CPAN::Plugin::Sysdeps::Mapping') {
135 58 100       1157 if (-r $mapping) {
136 16 50       825 open my $fh, '<', $mapping
137             or die "Can't load $mapping: $!";
138 16         89 local $/;
139 16         614 my $buf = <$fh>;
140 16         3652 push @mapping, eval $buf;
141 16 50       451 die "Error while loading $mapping: $@" if $@;
142             } else {
143 42 50       3249 eval "require $mapping"; die "Can't load $mapping: $@" if $@;
  42         227  
144 42         262 push @mapping, $mapping->mapping;
145             }
146             }
147              
148 42         649 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             );
161 42         276 my $self = bless \%config, $class;
162 42 50       100 if (eval { require Hash::Util; 1 }) {
  42         5205  
  42         26199  
163 42         165 Hash::Util::lock_keys($self);
164             }
165 42         1030 $self;
166             }
167              
168             # CPAN.pm plugin hook method
169             sub post_get {
170 15     15 1 1528 my($self, $dist) = @_;
171              
172 15         99 my @packages = $self->_map_cpandist($dist);
173 14 100       78 if (@packages) {
174 2         10 my @uninstalled_packages = $self->_filter_uninstalled_packages(@packages);
175 2 50       22 if (@uninstalled_packages) {
176 2         38 my @cmds = $self->_install_packages_commands(@uninstalled_packages);
177 2         18 for my $cmd (@cmds) {
178 2 50       10 if ($self->{dryrun}) {
179 2         506 warn "DRYRUN: @$cmd\n";
180             } else {
181 0         0 warn "INFO: run @$cmd...\n";
182              
183 0         0 system @$cmd;
184 0 0       0 if ($? != 0) {
185 0         0 die "@$cmd failed, stop installation";
186             }
187             }
188             }
189             }
190             }
191             }
192              
193             # Helpers/Internal functions/methods
194             sub _detect_linux_distribution {
195 28     28   767 my $info = _detect_linux_distribution_os_release();
196 28 50       148 return $info if $info;
197 0 0       0 if (-x '/usr/bin/lsb_release') {
198 0         0 _detect_linux_distribution_lsb_release();
199             } else {
200 0         0 _detect_linux_distribution_fallback();
201             }
202             }
203              
204             sub _detect_linux_distribution_os_release {
205 41     41   195052 my %info;
206 41         139 for my $candidate_file (@OS_RELEASE_PATH_CANDIDATES) {
207 41 100       2485 if (open my $fh, '<', $candidate_file) {
208 40         118 my %c;
209 40         2074 while(<$fh>) {
210 478 100       2901 if (my($k,$v) = $_ =~ m{^(.*?)=["']?(.*?)["']?$}) {
211 470         1590 $c{$k} = $v;
212             }
213             }
214 40         140 $info{linuxdistro} = $c{ID};
215 40         114 $info{linuxdistroversion} = $c{VERSION_ID};
216 40         84 $info{linuxdistrocodename} = $c{VERSION_CODENAME};
217             # heuristics
218 40 100 100     197 if (!defined $info{linuxdistrocodename} && $info{linuxdistro} eq 'debian' && $c{VERSION} =~ m{^\d+\s+\((.+)\)$}) {
      66        
219 2         7 $info{linuxdistrocodename} = $1;
220             }
221 40         766 return \%info;
222             }
223             }
224 1         8 undef;
225             }
226              
227             sub _detect_linux_distribution_lsb_release {
228 0     0   0 my %info;
229 0         0 my @cmd = ('lsb_release', '-irc');
230 0 0       0 open my $fh, '-|', @cmd
231             or die "Error while running '@cmd': $!";
232 0         0 while(<$fh>) {
233 0         0 chomp;
234 0 0       0 if (m{^Distributor ID:\s+(.*)}) {
    0          
    0          
235 0         0 $info{linuxdistro} = lc $1;
236             } elsif (m{^Release:\s+(.*)}) {
237 0         0 $info{linuxdistroversion} = $1;
238             } elsif (m{^Codename:\s+(.*)}) {
239 0         0 $info{linuxdistrocodename} = $1;
240             } else {
241 0         0 warn "WARNING: unexpected '@cmd' output '$_'";
242             }
243             }
244 0 0       0 close $fh
245             or die "Error while running '@cmd': $!";
246 0         0 \%info;
247             }
248              
249             sub _detect_linux_distribution_fallback {
250 0 0   0   0 if (open my $fh, '<', '/etc/redhat-release') {
251 0         0 my $contents = <$fh>;
252 0 0       0 if ($contents =~ m{^(CentOS|Rocky|RedHat|Fedora) (?:Linux )?release (\d+)\S*( \((.*?)\))?}) {
253 0 0       0 return {linuxdistro => lc $1, linuxdistroversion => $2, linuxdistrocodename => defined $3 ? $3 : ''};
254             }
255             }
256 0 0       0 if (open my $fh, '<', '/etc/issue') {
257 0         0 chomp(my $line = <$fh>);
258 0 0       0 if ($line =~ m{^Linux Mint (\d+) (\S+)}) {
    0          
    0          
259 0         0 return {linuxdistro => lc('LinuxMint'), linuxdistroversion => $1, linuxdistrocodename => $2};
260             } elsif ($line =~ m{^(Debian) GNU/Linux (\d+)}) {
261 0         0 my %info = (linuxdistro => lc $1, linuxdistroversion => $2);
262             $info{linuxdistrocodename} =
263             {
264             6 => 'squeeze',
265             7 => 'wheezy',
266             8 => 'jessie',
267             9 => 'stretch',
268             10 => 'buster',
269             11 => 'bullseye',
270             12 => 'bookworm',
271             13 => 'trixie',
272 0         0 }->{$info{linuxdistroversion}};
273 0         0 return \%info;
274             } elsif ($line =~ m{^(Ubuntu) (\d+\.\d+)}) {
275 0         0 my %info = (linuxdistro => lc $1, linuxdistroversion => $2);
276             $info{linuxdistrocodename} =
277             {
278             '12.04' => 'precise',
279             '14.04' => 'trusty',
280             '16.04' => 'xenial',
281             '18.04' => 'bionic',
282             '20.04' => 'focal',
283             '22.04' => 'jammy',
284             '24.04' => 'noble',
285 0         0 }->{$info{linuxdistroversion}};
286 0         0 return \%info;
287             } else {
288 0         0 warn "WARNING: don't know how to handle '$line'";
289             }
290             } else {
291 0         0 warn "WARNING: no /etc/issue available";
292             }
293 0         0 return {};
294             }
295              
296             sub _is_linux_debian_like {
297 4318     4318   7343 my(undef, $linuxdistro) = @_;
298 4318         17703 $linuxdistro =~ m{^(debian|ubuntu|linuxmint)$};
299             }
300              
301             sub _is_linux_fedora_like {
302 2713     2713   4526 my(undef, $linuxdistro) = @_;
303 2713         11928 $linuxdistro =~ m{^(fedora|redhat|centos|rocky)$};
304             }
305              
306             sub _is_linux_alpine_like {
307 12     12   33 my(undef, $linuxdistro) = @_;
308 12         62 $linuxdistro =~ m{^(alpine)$};
309             }
310              
311 17     17   160 sub _is_apt_installer { shift->{installer} =~m{^(apt-get|aptitude)$} }
312              
313             # Run a process in an elevated window, wait for its exit
314             sub _win32_run_elevated {
315 0     0   0 my($exe, @args) = @_;
316            
317 0 0       0 my $args = join " ", map { if(/[ "]/) { s!"!\\"!g; qq{"$_"} } else { $_ }} @args;
  0         0  
  0         0  
  0         0  
  0         0  
318              
319 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"},
320             $exe, $args;
321              
322 0         0 $ps1;
323             }
324              
325             sub _debug {
326 94417     94417   108537 my $self = shift;
327 94417 50       155042 if ($self->{debug}) {
328 0         0 print STDERR 'DEBUG: ';
329             print STDERR join('', map {
330 0 0       0 if (ref $_) {
  0         0  
331 0         0 Data::Dumper->new([$_])->Terse(1)->Indent(0)->Dump;
332             } else {
333 0         0 $_;
334             }
335             } @_);
336 0         0 print STDERR "\n";
337             }
338             }
339              
340             sub _map_cpandist {
341 29     29   92 my($self, $dist) = @_;
342              
343             # compat for older CPAN.pm (1.76)
344 29 50       315 if (!$dist->can('base_id')) {
345 8     8   70 no warnings 'once';
  8         13  
  8         1948  
346             *CPAN::Distribution::base_id = sub {
347 0     0   0 my $self = shift;
348 0         0 my $id = $self->id();
349 0         0 my $base_id = File::Basename::basename($id);
350 0         0 $base_id =~ s{\.(?:tar\.(bz2|gz|Z)|t(?:gz|bz)|zip)$}{}i;
351 0         0 return $base_id;
352 0         0 };
353             }
354              
355             # smartmatch for regexp/string/array without ~~, 5.8.x compat!
356             # also add support for numerical comparisons
357             my $smartmatch = sub ($$) {
358 15402     15402   26722 my($left, $right) = @_;
359 8     8   61 no warnings 'uninitialized';
  8         10  
  8         24599  
360 15402 100       32807 if (ref $right eq 'Regexp') {
    100          
    100          
361 180 100       1818 return 1 if $left =~ $right;
362             } elsif (ref $right eq 'ARRAY') {
363 1951 50 66     9783 return 1 if first { (!defined $left && !defined $_) || ($_ eq $left) } @$right;
  6247 100       26124  
364             } elsif (ref $right eq 'HASH') {
365 425         1292 for my $op (keys %$right) {
366 433 50       1868 if ($op !~ SUPPORTED_NUMERICAL_OPS_RX) {
367 0         0 die "Unsupported operator '$op', only supported: @{SUPPORTED_NUMERICAL_OPS()}";
  0         0  
368             }
369 433         731 my $val = $right->{$op};
370 433         691 my $code = 'no warnings q(numeric); $left '.$op.' $val';
371 433     3   39984 my $res = eval $code;
  3     3   19  
  3     3   5  
  3     3   167  
  3     3   16  
  3     3   7  
  3     2   90  
  3     2   29  
  3     2   8  
  3     2   152  
  3     2   18  
  3     2   6  
  3     2   105  
  3     2   22  
  3     2   5  
  3     2   117  
  3     2   16  
  3     2   6  
  3     2   88  
  2     2   15  
  2     2   4  
  2     2   82  
  2     2   20  
  2     2   4  
  2     2   106  
  2     2   19  
  2     2   6  
  2     2   87  
  2     2   14  
  2     2   5  
  2     2   72  
  2     2   14  
  2     2   4  
  2     2   58  
  2     2   18  
  2     2   6  
  2     1   104  
  2     1   14  
  2     1   4  
  2     1   64  
  2     1   13  
  2     1   6  
  2     1   63  
  2     1   11  
  2     1   4  
  2     1   59  
  2     1   17  
  2     1   5  
  2     1   46  
  2     1   10  
  2     1   21  
  2     1   55  
  2     1   10  
  2     1   3  
  2     1   52  
  2     1   10  
  2     1   3  
  2     1   119  
  2     1   10  
  2     1   4  
  2     1   49  
  2     1   11  
  2     1   5  
  2     1   69  
  2     1   9  
  2     1   3  
  2     1   42  
  2     1   9  
  2     1   3  
  2     1   52  
  2     1   13  
  2     1   2  
  2     1   52  
  2     1   11  
  2     1   4  
  2     1   52  
  2     1   10  
  2     1   3  
  2     1   50  
  2     1   11  
  2     1   2  
  2     1   52  
  2     1   14  
  2     1   4  
  2     1   62  
  2     1   13  
  2     1   3  
  2     1   69  
  2     1   11  
  2     1   4  
  2     1   60  
  2     1   13  
  2     1   4  
  2     1   80  
  2     1   16  
  2     1   10  
  2     1   90  
  2     1   13  
  2     1   6  
  2     1   68  
  2     1   14  
  2     1   5  
  2     1   104  
  2     1   13  
  2     1   4  
  2     1   60  
  2     1   14  
  2     1   4  
  2     1   76  
  1     1   8  
  1     1   2  
  1     1   67  
  1     1   11  
  1     1   2  
  1     1   52  
  1     1   7  
  1     1   2  
  1     1   34  
  1     1   10  
  1     1   3  
  1     1   41  
  1     1   8  
  1     1   2  
  1     1   56  
  1     1   8  
  1     1   2  
  1     1   36  
  1     1   11  
  1     1   4  
  1     1   47  
  1     1   10  
  1     1   1  
  1     1   41  
  1     1   22  
  1     1   2  
  1     1   44  
  1     1   5  
  1     1   1  
  1     1   21  
  1     1   7  
  1     1   1  
  1     1   44  
  1     1   6  
  1     1   2  
  1     1   27  
  1     1   6  
  1     1   1  
  1     1   25  
  1     1   7  
  1     1   2  
  1     1   37  
  1     1   5  
  1     1   10  
  1     1   26  
  1     1   5  
  1     1   2  
  1     1   24  
  1     1   4  
  1     1   2  
  1     1   23  
  1     1   5  
  1     1   1  
  1     1   22  
  1     1   5  
  1     1   1  
  1     1   20  
  1     1   6  
  1     1   1  
  1     1   22  
  1     1   3  
  1     1   2  
  1     1   16  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   4  
  1     1   1  
  1     1   19  
  1     1   5  
  1     1   2  
  1     1   28  
  1     1   4  
  1     1   2  
  1     1   29  
  1     1   6  
  1     1   1  
  1     1   22  
  1     1   5  
  1     1   1  
  1     1   22  
  1     1   20  
  1     1   1  
  1     1   37  
  1     1   6  
  1     1   1  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   26  
  1     1   6  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   1  
  1     1   20  
  1     1   4  
  1     1   2  
  1     1   21  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   8  
  1     1   2  
  1     1   42  
  1     1   6  
  1     1   1  
  1     1   26  
  1     1   11  
  1     1   2  
  1     1   47  
  1     1   5  
  1     1   2  
  1     1   22  
  1     1   5  
  1     1   3  
  1     1   38  
  1     1   4  
  1     1   2  
  1     1   20  
  1     1   4  
  1     1   2  
  1     1   19  
  1     1   6  
  1     1   2  
  1     1   52  
  1     1   13  
  1     1   3  
  1     1   56  
  1     1   8  
  1     1   2  
  1     1   35  
  1     1   5  
  1     1   2  
  1     1   21  
  1     1   7  
  1     1   2  
  1     1   33  
  1     1   5  
  1     1   2  
  1     1   24  
  1     1   4  
  1     1   2  
  1     1   21  
  1     1   7  
  1     1   3  
  1     1   32  
  1     1   5  
  1     1   2  
  1     1   21  
  1     1   6  
  1     1   2  
  1     1   27  
  1     1   6  
  1     1   2  
  1     1   24  
  1     1   5  
  1     1   2  
  1     1   20  
  1     1   5  
  1     1   1  
  1     1   24  
  1     1   5  
  1     1   3  
  1     1   21  
  1     1   4  
  1     1   12  
  1     1   35  
  1     1   4  
  1     1   2  
  1     1   19  
  1     1   4  
  1     1   1  
  1     1   36  
  1     1   6  
  1     1   1  
  1     1   21  
  1     1   5  
  1     1   1  
  1     1   22  
  1     1   6  
  1     1   1  
  1     1   21  
  1     1   11  
  1     1   3  
  1     1   80  
  1     1   7  
  1     1   2  
  1     1   35  
  1     1   9  
  1     1   2  
  1     1   40  
  1     1   9  
  1     1   2  
  1     1   41  
  1     1   6  
  1     1   2  
  1     1   139  
  1     1   8  
  1     1   2  
  1     1   44  
  1     1   5  
  1     1   2  
  1     1   29  
  1     1   6  
  1     1   1  
  1     1   27  
  1     1   10  
  1     1   2  
  1     1   52  
  1     1   8  
  1     1   2  
  1     1   37  
  1     1   13  
  1     1   3  
  1     1   59  
  1     1   9  
  1     1   3  
  1     1   69  
  1     1   9  
  1     1   3  
  1     1   40  
  1     1   8  
  1     1   3  
  1     1   32  
  1     1   8  
  1     1   3  
  1     1   46  
  1     1   10  
  1     1   4  
  1     1   45  
  1     1   10  
  1     1   2  
  1     1   90  
  1     1   10  
  1     1   3  
  1     1   45  
  1     1   7  
  1     1   3  
  1     1   29  
  1     1   12  
  1     1   3  
  1     1   54  
  1     1   12  
  1     1   2  
  1     1   42  
  1     1   26  
  1     1   3  
  1     1   40  
  1     1   13  
  1     1   3  
  1     1   73  
  1     1   10  
  1     1   3  
  1     1   45  
  1     1   12  
  1     1   3  
  1     1   42  
  1     1   11  
  1     1   3  
  1     1   50  
  1     1   8  
  1     1   3  
  1     1   62  
  1     1   11  
  1     1   3  
  1     1   40  
  1     1   8  
  1     1   3  
  1     1   37  
  1     1   8  
  1     1   2  
  1     1   31  
  1     1   8  
  1     1   2  
  1     1   35  
  1     1   8  
  1     1   3  
  1     1   32  
  1         7  
  1         2  
  1         49  
  1         10  
  1         2  
  1         37  
  1         9  
  1         2  
  1         37  
  1         8  
  1         3  
  1         35  
  1         9  
  1         1  
  1         40  
  1         5  
  1         1  
  1         22  
  1         7  
  1         3  
  1         28  
  1         7  
  1         3  
  1         29  
  1         5  
  1         2  
  1         21  
  1         4  
  1         2  
  1         22  
  1         5  
  1         2  
  1         29  
  1         5  
  1         1  
  1         21  
  1         7  
  1         3  
  1         42  
  1         5  
  1         3  
  1         25  
  1         9  
  1         3  
  1         39  
  1         7  
  1         2  
  1         27  
  1         6  
  1         3  
  1         28  
  1         4  
  1         2  
  1         21  
  1         4  
  1         2  
  1         21  
  1         7  
  1         2  
  1         47  
  1         14  
  1         2  
  1         64  
  1         11  
  1         2  
  1         43  
  1         11  
  1         2  
  1         38  
  1         10  
  1         3  
  1         49  
  1         7  
  1         3  
  1         66  
  1         8  
  1         3  
  1         33  
  1         11  
  1         3  
  1         51  
  1         8  
  1         3  
  1         37  
  1         9  
  1         3  
  1         40  
  1         10  
  1         3  
  1         52  
  1         8  
  1         3  
  1         48  
  1         9  
  1         3  
  1         39  
  1         7  
  1         3  
  1         39  
  1         9  
  1         2  
  1         62  
  1         8  
  1         2  
  1         33  
  1         8  
  1         3  
  1         37  
  1         9  
  1         3  
  1         36  
  1         9  
  1         3  
  1         38  
  1         9  
  1         3  
  1         36  
  1         8  
  1         4  
  1         41  
  1         5  
  1         2  
  1         24  
  1         5  
  1         2  
  1         22  
  1         6  
  1         2  
  1         28  
  1         5  
  1         1  
  1         22  
  1         6  
  1         2  
  1         23  
  1         6  
  1         2  
  1         21  
  1         5  
  1         1  
  1         29  
  1         5  
  1         2  
  1         46  
  1         5  
  1         1  
  1         25  
  1         5  
  1         2  
  1         37  
  1         11  
  1         2  
  1         22  
  1         4  
  1         2  
  1         21  
  1         4  
  1         1  
  1         19  
  1         4  
  1         2  
  1         35  
  1         4  
  1         2  
  1         26  
  1         6  
  1         2  
  1         24  
  1         5  
  1         2  
  1         24  
  1         5  
  1         1  
  1         20  
  1         5  
  1         2  
  1         22  
  1         4  
  1         2  
  1         21  
  1         5  
  1         2  
  1         20  
  1         5  
  1         3  
  1         22  
  1         5  
  1         3  
  1         21  
  1         5  
  1         2  
  1         21  
  1         10  
  1         3  
  1         45  
  1         9  
  1         3  
  1         51  
  1         9  
  1         4  
  1         40  
  1         9  
  1         4  
  1         79  
  1         8  
  1         3  
  1         33  
  1         27  
  1         3  
  1         42  
  1         9  
  1         3  
  1         34  
  1         8  
  1         3  
  1         36  
  1         8  
  1         5  
  1         39  
  1         39  
  1         4  
  1         41  
  1         8  
  1         4  
  1         36  
  1         12  
  1         4  
  1         55  
  1         9  
  1         3  
  1         69  
  1         7  
  1         3  
  1         33  
  1         10  
  1         3  
  1         61  
  1         7  
  1         2  
  1         62  
  1         8  
  1         2  
  1         35  
  1         7  
  1         2  
  1         30  
  1         6  
  1         2  
  1         28  
  1         6  
  1         1  
  1         42  
  1         8  
  1         3  
  1         36  
  1         9  
  1         2  
  1         41  
  1         5  
  1         2  
  1         25  
  1         6  
  1         1  
  1         28  
  1         4  
  1         2  
  1         20  
  1         4  
  1         2  
  1         21  
  1         8  
  1         1  
  1         37  
  1         8  
  1         4  
  1         54  
  1         6  
  1         3  
  1         28  
  1         5  
  1         1  
  1         22  
  1         19  
  1         6  
  1         32  
  1         6  
  1         2  
  1         24  
  1         6  
  1         2  
  1         22  
  1         6  
  1         2  
  1         30  
  1         5  
  1         8  
  1         24  
  1         6  
  1         1  
  1         25  
  1         4  
  1         2  
  1         23  
  1         6  
  1         2  
  1         22  
  1         6  
  1         2  
  1         26  
  1         5  
  1         2  
  1         23  
  1         4  
  1         1  
  1         19  
  1         5  
  1         1  
  1         22  
  1         4  
  1         1  
  1         19  
  1         6  
  1         2  
  1         27  
  1         5  
  1         9  
  1         25  
  1         9  
  1         3  
  1         46  
  1         7  
  1         3  
  1         27  
  1         8  
  1         2  
  1         41  
  1         5  
  1         2  
  1         27  
  1         8  
  1         2  
  1         33  
  1         9  
  1         4  
  1         60  
  1         7  
  1         2  
  1         31  
  1         4  
  1         2  
  1         25  
  1         12  
  1         2  
  1         27  
  1         10  
  1         3  
  1         39  
  1         8  
  1         3  
  1         60  
  1         6  
  1         3  
  1         26  
  1         8  
  1         1  
  1         37  
  1         6  
  1         2  
  1         23  
  1         6  
  1         2  
  1         24  
  1         5  
  1         1  
  1         21  
  1         4  
  1         2  
  1         19  
  1         7  
  1         2  
  1         32  
  1         27  
  1         3  
  1         56  
  1         7  
  1         2  
  1         35  
  1         4  
  1         3  
  1         23  
  1         7  
  1         3  
  1         38  
  1         7  
  1         2  
  1         27  
  1         6  
  1         2  
  1         27  
  1         8  
  1         3  
  1         65  
  1         8  
  1         4  
  1         34  
  1         7  
  1         3  
  1         27  
  1         9  
  1         7  
  1         48  
  1         10  
  1         4  
  1         83  
  1         6  
  1         3  
  1         27  
  1         11  
  1         1  
  1         49  
  1         4  
  1         3  
  1         18  
  1         5  
  1         3  
  1         37  
  1         8  
  1         211  
  1         36  
  1         10  
  1         2  
  1         35  
  1         5  
  1         2  
  1         26  
  1         6  
  1         3  
  1         27  
  1         4  
  1         3  
  1         30  
  1         8  
  1         2  
  1         41  
  1         6  
  1         2  
  1         42  
  1         5  
  1         2  
  1         31  
  1         6  
  1         2  
  1         30  
  1         6  
  1         2  
  1         26  
  1         5  
  1         182  
  1         37  
  1         5  
  1         2  
  1         22  
  1         5  
  1         1  
  1         21  
  1         6  
  1         2  
  1         96  
  1         5  
  1         2  
  1         25  
  1         9  
  1         1  
  1         35  
  1         6  
  1         1  
  1         25  
  1         5  
  1         2  
  1         24  
  1         8  
  1         3  
  1         31  
  1         6  
  1         3  
  1         26  
  1         7  
  1         2  
  1         27  
  1         7  
  1         1  
  1         29  
  1         6  
  1         1  
  1         24  
  1         5  
  1         2  
  1         23  
  1         7  
  1         2  
  1         29  
  1         5  
  1         4  
  1         24  
  1         5  
  1         1  
  1         20  
  1         9  
  1         4  
  1         36  
  1         5  
  1         2  
  1         22  
  1         6  
  1         2  
  1         30  
  1         6  
  1         2  
  1         26  
  1         5  
  1         1  
  1         23  
  1         5  
  1         2  
  1         25  
  1         6  
  1         2  
  1         22  
  1         6  
  1         48  
  1         25  
  1         5  
  1         2  
  1         20  
  1         7  
  1         2  
  1         25  
  1         8  
  1         2  
  1         32  
  1         5  
  1         2  
  1         25  
  1         5  
  1         2  
  1         27  
  1         11  
  1         1  
  1         39  
  1         5  
  1         2  
  1         24  
  1         6  
  1         2  
  1         26  
  1         5  
  1         3  
  1         26  
  1         5  
  1         2  
  1         21  
  1         5  
  1         2  
  1         20  
  1         5  
  1         2  
  1         21  
  1         5  
  1         2  
  1         23  
  1         6  
  1         2  
  1         40  
  1         9  
  1         2  
  1         41  
  1         8  
  1         2  
  1         37  
  1         6  
  1         2  
  1         64  
  1         5  
  1         3  
  1         25  
  1         4  
  1         2  
  1         55  
  1         6  
  1         2  
  1         21  
  1         6  
  1         2  
  1         45  
  1         11  
  1         2  
  1         47  
  1         6  
  1         2  
  1         26  
  1         5  
  1         2  
  1         22  
  1         10  
  1         3  
  1         49  
  1         9  
  1         2  
  1         43  
  1         9  
  1         4  
  1         39  
  1         11  
  1         3  
  1         52  
  1         12  
  1         3  
  1         26  
  1         11  
  1         3  
  1         77  
  1         9  
  1         3  
  1         45  
  1         10  
  1         3  
  1         41  
  1         10  
  1         4  
  1         36  
  1         6  
  1         3  
  1         29  
  1         6  
  1         2  
  1         25  
  1         5  
  1         3  
  1         23  
  1         6  
  1         2  
  1         25  
  1         6  
  1         2  
  1         25  
  1         15  
  1         4  
  1         48  
  1         7  
  1         1  
  1         28  
  1         12  
  1         3  
  1         56  
  1         10  
  1         4  
  1         39  
  1         10  
  1         2  
  1         40  
  1         11  
  1         4  
  1         51  
  1         11  
  1         4  
  1         41  
  1         8  
  1         2  
  1         39  
  1         11  
  1         3  
  1         40  
  1         20  
  1         3  
  1         42  
  1         10  
  1         3  
  1         59  
  1         8  
  1         3  
  1         41  
  1         15  
  1         3  
  1         61  
  1         5  
  1         3  
  1         26  
  1         7  
  1         2  
  1         31  
  1         8  
  1         3  
  1         41  
  1         8  
  1         1  
  1         26  
  1         7  
  1         2  
  1         35  
  1         7  
  1         2  
  1         36  
  1         6  
  1         3  
  1         36  
  1         5  
  1         1  
  1         38  
  1         5  
  1         2  
  1         30  
  1         5  
  1         2  
  1         27  
  1         6  
  1         2  
  1         26  
  1         7  
  1         2  
  1         32  
  1         5  
  1         3  
  1         25  
  1         5  
  1         2  
  1         29  
  1         6  
  1         2  
  1         27  
  1         5  
  1         2  
  1         26  
  1         9  
  1         3  
  1         51  
  1         6  
  1         2  
  1         26  
  1         4  
  1         1  
  1         18  
  1         4  
  1         1  
  1         21  
  1         5  
  1         12  
  1         24  
  1         9  
  1         4  
  1         31  
  1         6  
  1         1  
  1         42  
  1         6  
  1         2  
  1         29  
  1         5  
  1         2  
  1         24  
  1         9  
  1         22  
  1         42  
  1         5  
  1         4  
  1         27  
  1         9  
  1         4  
  1         42  
  1         8  
  1         3  
  1         33  
  1         5  
  1         2  
  1         24  
  1         5  
  1         2  
  1         22  
  1         5  
  1         2  
  1         25  
  1         20  
  1         2  
  1         30  
372 433 50       1355 die "Evaluation of '$code' failed: $@" if $@;
373 433 100       2830 return 0 if !$res;
374             }
375 166         941 return 1;
376             } else {
377 12846 50 66     21515 return 1 if !defined $left && !defined $right;
378 12846 100       44862 return 1 if $left eq $right;
379             }
380 29         261 };
381              
382 29         75 my $handle_mapping_entry; $handle_mapping_entry = sub {
383 36149     36149   49555 my($entry, $level) = @_;
384 36149         63896 for(my $map_i=0; $map_i <= $#$entry; $map_i++) {
385 65502         99101 my $key_or_subentry = $entry->[$map_i];
386 65502 100       104353 if (ref $key_or_subentry eq 'ARRAY') {
    100          
387 28656         63567 $self->_debug(' ' x $level . ' traverse another tree level');
388 28656         43660 my $res = $handle_mapping_entry->($key_or_subentry, $level+1);
389 28655 100 100     115598 return $res if $res && !$TRAVERSE_ONLY;
390             } elsif (ref $key_or_subentry eq 'CODE') {
391 1         27 my $res = $key_or_subentry->($self, $dist);
392 1 50 33     6 return $res if $res && !$TRAVERSE_ONLY;
393             } else {
394 36845         43766 my $key = $key_or_subentry;
395 36845         48714 my $match = $entry->[++$map_i];
396 36845         87740 $self->_debug(' ' x $level . " match '$key' against '", $match, "'");
397 36845 100       101643 if ($key eq 'cpandist') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
398 19 100 100     127 return 0 if !$smartmatch->($dist->base_id, $match) && !$TRAVERSE_ONLY;
399             } elsif ($key eq 'cpanmod') {
400 7474         10960 my $found = 0;
401 7474         19952 for my $mod ($dist->containsmods) {
402 7474         56149 $self->_debug(' ' x $level . " found module '$mod' in dist, check now against '", $match, "'");
403 7474 100       13717 if ($smartmatch->($mod, $match)) {
404 15         24 $found = 1;
405 15         27 last;
406             }
407             }
408 7474 100 100     27895 return 0 if !$found && !$TRAVERSE_ONLY;
409             } elsif ($key eq 'os') {
410 5271 100 100     8846 return 0 if !$smartmatch->($self->{os}, $match) && !$TRAVERSE_ONLY;
411             } elsif ($key eq 'osvers') {
412 178 100 100     374 return 0 if !$smartmatch->($self->{osvers}, $match) && !$TRAVERSE_ONLY;
413             } elsif ($key eq 'linuxdistro') {
414 7766 100       23798 if ($match =~ m{^~(debian|fedora|alpine)$}) {
    100          
415 7033         15114 my $method = "_is_linux_$1_like";
416 7033         17071 $self->_debug(' ' x $level . " translate $match to $method");
417 7033 50 66     16248 return 0 if !$self->$method($self->{linuxdistro}) && !$TRAVERSE_ONLY;
418             } elsif ($match =~ m{^~}) {
419 1         9 die "'like' matches only for debian, fedora, and alpine";
420             } else {
421 732 50 66     1349 return 0 if !$smartmatch->($self->{linuxdistro}, $match) && !$TRAVERSE_ONLY;
422             }
423             } elsif ($key eq 'linuxdistroversion') {
424 420 50 66     777 return 0 if !$smartmatch->($self->{linuxdistroversion}, $match) && !$TRAVERSE_ONLY;
425             } elsif ($key eq 'linuxdistrocodename') {
426 1308 50 66     2173 return 0 if !$smartmatch->($self->{linuxdistrocodename}, $match) && !$TRAVERSE_ONLY; # XXX should also do a smart codename comparison additionally!
427             } elsif ($key eq 'package') {
428 14409         36550 $self->_debug(' ' x $level . " found $match"); # XXX array?
429 14409         36752 return { package => $match };
430             } else {
431 0         0 die "Invalid key '$key'"; # XXX context/position?
432             }
433             }
434             }
435 29         241 };
436              
437 29 50       55 for my $entry (@{ $self->{mapping} || [] }) {
  29         135  
438 7493         11003 my $res = $handle_mapping_entry->($entry, 0);
439 7492 100 66     17293 if ($res && !$TRAVERSE_ONLY) {
440 10 100       100 return ref $res->{package} eq 'ARRAY' ? @{ $res->{package} } : $res->{package};
  2         31  
441             }
442             }
443              
444 18         112 ();
445             }
446              
447             sub _detect_dnf {
448 1     1   3 my @cmd = ('dnf', '--help');
449 1         489 require IPC::Open3;
450 1         2516 require Symbol;
451 1         5 my $err = Symbol::gensym();
452 1         14 my $fh;
453 1         1 return eval {
454 1 0       3 if (my $pid = IPC::Open3::open3(undef, $fh, $err, @cmd)) {
455 0         0 waitpid $pid, 0;
456 0         0 return $? == 0;
457             }
458             };
459             }
460              
461             sub _find_missing_deb_packages {
462 5     5   13 my($self, @packages) = @_;
463 5 100       35 return () if !@packages;
464              
465             # taken from ~/devel/deb-install.pl
466 4         7 my %seen_packages;
467             my @missing_packages;
468              
469 4         11 my @cmd = ('dpkg-query', '-W', '-f=${Package} ${Status}\n', @packages);
470 4         1567 require IPC::Open3;
471 4         9244 require Symbol;
472 4         22 my $err = Symbol::gensym();
473 4         65 my $fh;
474 4 50       19 my $pid = IPC::Open3::open3(undef, $fh, $err, @cmd)
475             or die "Error running '@cmd': $!";
476 4         45092 while(<$fh>) {
477 0         0 chomp;
478 0 0       0 if (m{^(\S+) (.*)}) {
479 0 0       0 if ($2 ne 'install ok installed') {
480 0         0 push @missing_packages, $1;
481             }
482 0         0 $seen_packages{$1} = 1;
483             } else {
484 0         0 warn "ERROR: cannot parse $_, ignore line...\n";
485             }
486             }
487 4         117 waitpid $pid, 0;
488 4         51 for my $package (@packages) {
489 6 50       90 if (!$seen_packages{$package}) {
490 6         122 push @missing_packages, $package;
491             }
492             }
493 4         320 @missing_packages;
494             }
495              
496             sub _find_missing_rpm_packages {
497 6     6   559630 my($self, @packages) = @_;
498 6 100       137 return () if !@packages;
499              
500 5         57 my @missing_packages;
501             {
502 5         42 my %packages = map{($_,1)} @packages;
  5         48  
  6         68  
503              
504 5         148 local $ENV{LC_ALL} = 'C';
505 5         40 my @cmd = ('rpm', '-q', @packages);
506 5 50       29222 open my $fh, '-|', @cmd
507             or die "Error running '@cmd': $!";
508 5         2107486 while(<$fh>) {
509 8 100       9270 if (m{^package (\S+) is not installed}) {
510 4         109 my $package = $1;
511 4 50       52 if (!exists $packages{$package}) {
512 0         0 die "Unexpected: package $package listed as non-installed, but not queried in '@cmd'?!";
513             }
514 4         19197 push @missing_packages, $package;
515             }
516             }
517             }
518 5 100       156 return () if !@missing_packages;
519              
520             # maybe the packages are just provided by another package?
521 4         13 my @definitively_missing_packages;
522             {
523 4         15 my %packages = map{($_,1)} @missing_packages;
  4         31  
  4         93  
524              
525 4         83 local $ENV{LC_ALL} = 'C';
526 4         26 my @cmd = ('rpm', '-q', '--whatprovides', @missing_packages);
527 4 50       18655 open my $fh, '-|', @cmd
528             or die "Error running '@cmd': $!";
529 4         1785868 while(<$fh>) {
530 5 100       7070 if (m{^no package provides (\S+)}) {
531 3         398 my $package = $1;
532 3 50       85 if (!exists $packages{$package}) {
533 0         0 die "Unexpected: package $package listed as non-installed, but not queried in '@cmd'?!";
534             }
535 3         14822 push @definitively_missing_packages, $package;
536             }
537             }
538             }
539              
540 4         326 @definitively_missing_packages;
541             }
542              
543             sub _find_missing_freebsd_pkg_packages {
544 0     0   0 my($self, @packages) = @_;
545 0 0       0 return () if !@packages;
546              
547 0         0 my @missing_packages;
548 0         0 for my $package (@packages) {
549 0         0 my @cmd = ('pkg', 'info', '--exists', $package);
550 0         0 system @cmd;
551 0 0       0 if ($? != 0) {
552 0         0 push @missing_packages, $package;
553             }
554             }
555              
556 0         0 @missing_packages;
557             }
558              
559             sub _find_missing_openbsd_pkg_packages {
560 0     0   0 my($self, @packages) = @_;
561 0 0       0 return () if !@packages;
562              
563 0         0 require IPC::Open3;
564 0         0 require Symbol;
565              
566 0         0 my @missing_packages;
567 0         0 for my $package (@packages) {
568 0         0 my $err = Symbol::gensym();
569 0         0 my $fh;
570             my $package_in_repository;
571 0         0 eval {
572 0 0       0 if (my $pid = IPC::Open3::open3(undef, $fh, $err, 'pkg_info', $package)) {
573 0         0 waitpid $pid, 0;
574 0 0       0 if ($? == 0) {
575 0         0 $package_in_repository = 1;
576             }
577             }
578             };
579 0 0       0 if ($package_in_repository) {
580 0         0 my @cmd = ('pkg_info', '-q', '-e', "${package}->=0");
581 0         0 system @cmd;
582 0 0       0 if ($? != 0) {
583 0         0 push @missing_packages, $package;
584             }
585             }
586             }
587              
588 0         0 @missing_packages;
589             }
590              
591             sub _find_missing_homebrew_packages {
592 0     0   0 my($self, @packages) = @_;
593 0 0       0 return () if !@packages;
594              
595 0         0 my @missing_packages;
596 0         0 for my $package (@packages) {
597 0         0 my @cmd = ('brew', 'ls', '--versions', $package);
598 0 0       0 open my $fh, '-|', @cmd
599             or die "Error running @cmd: $!";
600 0         0 my $has_package;
601 0         0 while(<$fh>) {
602 0         0 $has_package = 1;
603 0         0 last;
604             }
605 0         0 close $fh; # earlier homebrew versions returned always 0,
606             # newer (since Oct 2016) return 1 if the package is
607             # missing
608 0 0       0 if (!$has_package) {
609 0         0 push @missing_packages, $package;
610             }
611             }
612 0         0 @missing_packages;
613             }
614              
615             sub _find_missing_chocolatey_packages {
616 0     0   0 my($self, @packages) = @_;
617 0 0       0 return () if !@packages;
618              
619             my %installed_packages = map {
620 0 0       0 /^(.*)\|(.*)$/
621             or next;
622 0         0 $1 => $2
623             } grep {
624 0         0 /^(.*)\|(.*)$/
  0         0  
625             } `choco list --localonly --limit-output`;
626 0         0 my @missing_packages = grep { ! $installed_packages{ $_ }} @packages;
  0         0  
627 0         0 @missing_packages;
628             }
629              
630             sub _find_missing_apk_packages {
631 0     0   0 my($self, @packages) = @_;
632 0 0       0 return () if !@packages;
633              
634 0         0 my @missing_packages;
635 0         0 for my $package (@packages) {
636 0         0 my @cmd = ('apk', 'info', '-e', $package);
637 0         0 system @cmd;
638 0 0       0 if ($? != 0) {
639 0         0 push @missing_packages, $package;
640             }
641             }
642              
643 0         0 @missing_packages;
644             }
645              
646             sub _filter_uninstalled_packages {
647 4     4   2143 my($self, @packages) = @_;
648 4         8 my $find_missing_packages;
649 4 50 0     20 if ($self->_is_apt_installer) {
    0 0        
    0          
    0          
    0          
    0          
    0          
650 4         12 $find_missing_packages = '_find_missing_deb_packages';
651             } elsif (($self->{installer} eq 'yum') || ($self->{installer} eq 'dnf')) {
652 0         0 $find_missing_packages = '_find_missing_rpm_packages';
653             } elsif ($self->{os} eq 'freebsd' || $self->{os} eq 'dragonfly') {
654 0         0 $find_missing_packages = '_find_missing_freebsd_pkg_packages';
655             } elsif ($self->{os} eq 'openbsd') {
656 0         0 $find_missing_packages = '_find_missing_openbsd_pkg_packages';
657             } elsif ($self->{os} eq 'MSWin32') {
658 0         0 $find_missing_packages = '_find_missing_chocolatey_packages';
659             } elsif ($self->{installer} eq 'homebrew') {
660 0         0 $find_missing_packages = '_find_missing_homebrew_packages';
661             } elsif ($self->{installer} eq 'apk') {
662 0         0 $find_missing_packages = '_find_missing_apk_packages';
663             } else {
664 0         0 warn "check for installed packages is NYI for $self->{os}/$self->{linuxdistro}";
665             }
666 4 50       25 if ($find_missing_packages) {
667 4         10 my @plain_packages;
668             my @missing_packages;
669 4         9 for my $package_spec (@packages) {
670 5 100       23 if ($package_spec =~ m{\|}) { # has alternatives
671 1         9 my @single_packages = split /\s*\|\s*/, $package_spec;
672 1         10 my @missing_in_packages_spec = $self->$find_missing_packages(@single_packages);
673 1 50       23 if (@missing_in_packages_spec == @single_packages) {
674 1         22 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
675             }
676             } else {
677 4         7 push @plain_packages, $package_spec;
678             }
679             }
680 4         32 push @missing_packages, $self->$find_missing_packages(@plain_packages);
681 4         47 @packages = @missing_packages;
682             }
683 4         70 @packages;
684             }
685              
686             sub _install_packages_commands {
687 13     13   82 my($self, @packages) = @_;
688 13         24 my @pre_cmd;
689             my @install_cmd;
690              
691             # sudo or not?
692 13 100       71 if ($self->{installer} eq 'homebrew') {
    50          
693             # may run as unprivileged user
694             } elsif ($self->{installer} eq 'chocolatey') {
695             # no sudo on Windows systems?
696             } else {
697 12 50       105 if ($< != 0) {
698 0         0 push @install_cmd, 'sudo';
699             }
700             }
701              
702             # the installer executable
703 13 100       52 if ($self->{installer} eq 'homebrew') {
    50          
704 1         2 push @install_cmd, 'brew';
705             } elsif ($self->{installer} eq 'chocolatey') {
706 0         0 push @install_cmd, 'choco';
707             } else {
708 12         38 push @install_cmd, $self->{installer};
709             }
710              
711             # batch, default or interactive
712 13 100       37 if ($self->{batch}) {
713 4 100 33     72 if ($self->_is_apt_installer) {
    50          
    50          
    50          
    50          
714 3         17 push @install_cmd, '-y';
715             } elsif (($self->{installer} eq 'yum') || ($self->{installer} eq 'dnf')) {
716 0         0 push @install_cmd, '-y';
717             } elsif ($self->{installer} eq 'pkg') { # FreeBSD's pkg
718             # see below
719             } elsif ($self->{installer} eq 'homebrew') {
720             # batch by default
721             } elsif ($self->{installer} eq 'apk') {
722             # batch by default
723             } else {
724 0         0 warn "batch=1 NYI for $self->{installer}";
725             }
726             } else {
727 9 100 100     22 if ($self->_is_apt_installer) {
    100          
    100          
    50          
    100          
    100          
728 3         37 @pre_cmd = ('sh', '-c', 'echo -n "Install package(s) '."@packages".'? (y/N) "; read yn; [ "$yn" = "y" ]');
729             } elsif (($self->{installer} eq 'yum') || ($self->{installer} eq 'dnf')) {
730             # interactive by default
731             } elsif ($self->{installer} eq 'pkg') { # FreeBSD's pkg
732             # see below
733             } elsif ($self->{installer} =~ m{^(chocolatey)$}) {
734             # Nothing to do here
735             } elsif ($self->{installer} eq 'homebrew') {
736             # the sh builtin echo does not understand -n -> use printf
737 1         5 @pre_cmd = ('sh', '-c', 'printf %s "Install package(s) '."@packages".'? (y/N) "; read yn; [ "$yn" = "y" ]');
738             } elsif ($self->{installer} eq 'apk') {
739 1         5 @pre_cmd = ('sh', '-c', 'printf %s "Install package(s) '."@packages".'? (y/N) "; read yn; [ "$yn" = "y" ]');
740             } else {
741 1         15 warn "batch=0 NYI for $self->{installer}";
742             }
743             }
744              
745             # special options
746 13 100       69 if ($self->{installer} eq 'pkg') { # FreeBSD's pkg
747 1         3 push @install_cmd, '--option', 'LOCK_RETRIES=86400'; # wait quite long in case there are concurrent pkg runs
748             }
749              
750             # the installer subcommand
751 13 100       63 if ($self->{installer} eq 'apk') {
    100          
752 2         3 push @install_cmd, 'add';
753             } elsif ($self->{installer} ne 'pkg_add') {
754 10         22 push @install_cmd, 'install';
755             }
756              
757             # post options
758 13 50 66     74 if ($self->{batch} && $self->{installer} eq 'pkg') {
759 0         0 push @install_cmd, '-y';
760             }
761 13 50 66     73 if ($self->{batch} && $self->{installer} eq 'chocolatey') {
762 0         0 push @install_cmd, '-y';
763             }
764 13 50 66     54 if ($self->{batch} && $self->{installer} eq 'pkg_add') {
765 0         0 push @install_cmd, '-I';
766             }
767              
768 13         26 push @install_cmd, @packages;
769            
770 13 50       32 if ($self->{os} eq 'MSWin32') {
771             # Wrap the thing in our small powershell program
772 0         0 @install_cmd = _win32_run_elevated(@install_cmd);
773             };
774              
775 13 100       83 ((@pre_cmd ? \@pre_cmd : ()), \@install_cmd);
776             }
777              
778             1;
779              
780             __END__