File Coverage

blib/lib/Alien/Build/Plugin/Extract/CommandLine.pm
Criterion Covered Total %
statement 108 141 76.6
branch 64 114 56.1
condition 26 63 41.2
subroutine 24 28 85.7
pod 8 8 100.0
total 230 354 64.9


line stmt bran cond sub pod time code
1             package Alien::Build::Plugin::Extract::CommandLine;
2              
3 6     6   2901 use strict;
  6         17  
  6         180  
4 6     6   32 use warnings;
  6         13  
  6         158  
5 6     6   109 use 5.008004;
  6         20  
6 6     6   746 use Alien::Build::Plugin;
  6         14  
  6         39  
7 6     6   37 use Path::Tiny ();
  6         12  
  6         109  
8 6     6   1356 use File::Which ();
  6         2807  
  6         141  
9 6     6   810 use File::chdir;
  6         5202  
  6         794  
10 6     6   45 use File::Temp qw( tempdir );
  6         12  
  6         324  
11 6     6   39 use Capture::Tiny qw( capture_merged );
  6         14  
  6         12762  
12              
13             # ABSTRACT: Plugin to extract an archive using command line tools
14             our $VERSION = '2.45'; # VERSION
15              
16              
17             has '+format' => 'tar';
18              
19              
20             sub gzip_cmd
21             {
22 3 50   3 1 272 _which('gzip') ? 'gzip' : undef;
23             }
24              
25              
26 49     49   4293 sub _which { scalar File::Which::which(@_) }
27              
28             sub bzip2_cmd
29             {
30 2 50   2 1 654 _which('bzip2') ? 'bzip2' : undef;
31             }
32              
33              
34             sub xz_cmd
35             {
36 2 50   2 1 578 _which('xz') ? 'xz' : undef;
37             }
38              
39              
40             {
41             my $bsd_tar;
42              
43             # Note: GNU tar can be iffy to very bad on windows, where absolute
44             # paths get confused with remote tars. We used to assume that 'tar.exe'
45             # is borked on Windows, but recent versions of Windows 10 come bundled
46             # with bsdtar (libarchive) named 'tar.exe', and we should definitely
47             # prefer that to ptar.
48             sub _windows_tar_is_bsdtar
49             {
50 20 50   20   3437 return 1 if $^O ne 'MSWin32';
51 0 0       0 return $bsd_tar if defined $bsd_tar;
52             my($out) = capture_merged {
53 0     0   0 system 'tar', '--version';
54 0         0 };
55 0 0       0 return $bsd_tar = $out =~ /bsdtar/ ? 1 : 0
56             }
57             }
58              
59             sub tar_cmd
60             {
61 20 0 33 20 1 564 _which('bsdtar')
    50 33        
    50          
    50          
62             ? 'bsdtar'
63             # Slowlaris /usr/bin/tar doesn't seem to like pax global header
64             # but seems to have gtar in the path by default, which is okay with it
65             : $^O eq 'solaris' && _which('gtar')
66             ? 'gtar'
67             # See note above for Windows logic.
68             : _which('tar') && _windows_tar_is_bsdtar()
69             ? 'tar'
70             : _which('ptar')
71             ? 'ptar'
72             : undef;
73             };
74              
75              
76             sub unzip_cmd
77             {
78 2 50 33 2 1 307 if($^O eq 'MSWin32' && _which('tar') && _windows_tar_is_bsdtar())
      33        
79             {
80 0         0 (_which('tar'), 'xf');
81             }
82             else
83             {
84 2 50       5 _which('unzip') ? 'unzip' : undef;
85             }
86             }
87              
88             sub _run
89             {
90 6     6   45 my(undef, $build, @cmd) = @_;
91 6         100 $build->log("+ @cmd");
92 6         35185 system @cmd;
93 6 50       550 die "execute failed" if $?;
94             }
95              
96             sub _cp
97             {
98 0     0   0 my(undef, $build, $from, $to) = @_;
99 0         0 require File::Copy;
100 0         0 $build->log("copy $from => $to");
101 0 0       0 File::Copy::cp($from, $to) || die "unable to copy: $!";
102             }
103              
104             sub _mv
105             {
106 0     0   0 my(undef, $build, $from, $to) = @_;
107 0         0 $build->log("move $from => $to");
108 0 0       0 rename($from, $to) || die "unable to rename: $!";
109             }
110              
111             sub _dcon
112             {
113 6     6   17 my($self, $src) = @_;
114              
115 6         16 my $name;
116             my $cmd;
117              
118 6 100       124 if($src =~ /\.(gz|tgz|Z|taz)$/)
    100          
    100          
119             {
120 2 50       23 $self->gzip_cmd(_which('gzip')) unless defined $self->gzip_cmd;
121 2 100       388 if($src =~ /\.(gz|tgz)$/)
    50          
122             {
123 1 50       18 $cmd = $self->gzip_cmd unless $self->_tar_can('tar.gz');
124             }
125             elsif($src =~ /\.(Z|taz)$/)
126             {
127 1 50       11 $cmd = $self->gzip_cmd unless $self->_tar_can('tar.Z');
128             }
129             }
130             elsif($src =~ /\.(bz2|tbz)$/)
131             {
132 1 50       24 $self->bzip2_cmd(_which('bzip2')) unless defined $self->bzip2_cmd;
133 1 50       219 $cmd = $self->bzip2_cmd unless $self->_tar_can('tar.bz2');
134             }
135             elsif($src =~ /\.(xz|txz)$/)
136             {
137 1 50       21 $self->xz_cmd(_which('xz')) unless defined $self->xz_cmd;
138 1 50       181 $cmd = $self->xz_cmd unless $self->_tar_can('tar.xz');
139             }
140              
141 6 50 33     258 if($cmd && $src =~ /\.(gz|bz2|xz|Z)$/)
    50 33        
142             {
143 0         0 $name = $src;
144 0         0 $name =~ s/\.(gz|bz2|xz|Z)$//g;
145             }
146             elsif($cmd && $src =~ /\.(tgz|tbz|txz|taz)$/)
147             {
148 0         0 $name = $src;
149 0         0 $name =~ s/\.(tgz|tbz|txz|taz)$/.tar/;
150             }
151              
152 6         48 ($name,$cmd);
153             }
154              
155              
156             sub handles
157             {
158 8     8 1 30226 my($class, $ext) = @_;
159              
160 8 100       52 my $self = ref $class
161             ? $class
162             : __PACKAGE__->new;
163              
164 8 50       33 $ext = 'tar.Z' if $ext eq 'taz';
165 8 50       24 $ext = 'tar.gz' if $ext eq 'tgz';
166 8 50       29 $ext = 'tar.bz2' if $ext eq 'tbz';
167 8 50       23 $ext = 'tar.xz' if $ext eq 'txz';
168              
169 8 100 66     33 return 1 if $ext eq 'tar.gz' && $self->_tar_can('tar.gz');
170 7 100 66     27 return 1 if $ext eq 'tar.Z' && $self->_tar_can('tar.Z');
171 6 100 66     32 return 1 if $ext eq 'tar.bz2' && $self->_tar_can('tar.bz2');
172 5 100 66     28 return 1 if $ext eq 'tar.xz' && $self->_tar_can('tar.xz');
173              
174 4 50 33     41 return 0 if $ext =~ s/\.(gz|Z)$// && (!$self->gzip_cmd);
175 4 50 33     17 return 0 if $ext =~ s/\.bz2$// && (!$self->bzip2_cmd);
176 4 50 33     16 return 0 if $ext =~ s/\.xz$// && (!$self->xz_cmd);
177              
178 4 100 66     24 return 1 if $ext eq 'tar' && $self->_tar_can('tar');
179 1 50 33     34 return 1 if $ext eq 'zip' && $self->_tar_can('zip');
180              
181 1         51 return 0;
182             }
183              
184              
185             sub available
186             {
187 0     0 1 0 my(undef, $ext) = @_;
188              
189             # this is actually the same as handles
190 0         0 __PACKAGE__->handles($ext);
191             }
192              
193             sub init
194             {
195 2     2 1 8 my($self, $meta) = @_;
196              
197 2 50 33     11 if($self->format eq 'tar.xz' && !$self->handles('tar.xz'))
    50 33        
    50 33        
    50 33        
198             {
199 0         0 $meta->add_requires('share' => 'Alien::xz' => '0.06');
200             }
201             elsif($self->format eq 'tar.bz2' && !$self->handles('tar.bz2'))
202             {
203 0         0 $meta->add_requires('share' => 'Alien::Libbz2' => '0.22');
204             }
205             elsif($self->format =~ /^tar\.(gz|Z)$/ && !$self->handles($self->format))
206             {
207 0         0 $meta->add_requires('share' => 'Alien::gzip' => '0.03');
208             }
209             elsif($self->format eq 'zip' && !$self->handles('zip'))
210             {
211 0         0 $meta->add_requires('share' => 'Alien::unzip' => '0');
212             }
213              
214             $meta->register_hook(
215             extract => sub {
216 6     6   22 my($build, $src) = @_;
217              
218 6         43 my($dcon_name, $dcon_cmd) = _dcon($self, $src);
219              
220 6 50       21 if($dcon_name)
221             {
222 0 0       0 unless($dcon_cmd)
223             {
224 0         0 die "unable to decompress $src";
225             }
226             # if we have already decompressed, then keep it.
227 0 0       0 unless(-f $dcon_name)
228             {
229             # we don't use pipes, because that may not work on Windows.
230             # keep the original archive, in case another extract
231             # plugin needs it. keep the decompressed archive
232             # in case WE need it again.
233 0         0 my $src_tmp = Path::Tiny::path($src)
234             ->parent
235             ->child('x'.Path::Tiny::path($src)->basename);
236 0         0 my $dcon_tmp = Path::Tiny::path($dcon_name)
237             ->parent
238             ->child('x'.Path::Tiny::path($dcon_name)->basename);
239 0         0 $self->_cp($build, $src, $src_tmp);
240 0         0 $self->_run($build, $dcon_cmd, "-d", $src_tmp);
241 0         0 $self->_mv($build, $dcon_tmp, $dcon_name);
242             }
243 0         0 $src = $dcon_name;
244             }
245              
246 6 50 33     101 if($src =~ /\.zip$/i)
    50          
247             {
248 0         0 $self->_run($build, $self->unzip_cmd, $src);
249             }
250             elsif($src =~ /\.tar/ || $src =~ /(\.tgz|\.tbz|\.txz|\.taz)$/i)
251             {
252 6         41 $self->_run($build, $self->tar_cmd, '-xf', $src);
253             }
254             else
255             {
256 0         0 die "not sure of archive type from extension";
257             }
258             }
259 2         25 );
260             }
261              
262             my %tars;
263              
264             sub _tar_can
265             {
266 12     12   56 my($self, $ext) = @_;
267              
268 12 100       42 unless(%tars)
269             {
270 2         4 my $name = '';
271 2         4 local $_; # to avoid dynamically scoped read-only $_ from upper scopes
272 2         9 while(my $line = )
273             {
274 166 100       283 if($line =~ /^\[ (.*) \]$/)
275             {
276 12         37 $name = $1;
277             }
278             else
279             {
280 154         382 $tars{$name} .= $line;
281             }
282             }
283              
284 2         18 foreach my $key (keys %tars)
285             {
286 14         95 $tars{$key} = unpack "u", $tars{$key};
287             }
288             }
289              
290 12         61 my $name = "xx.$ext";
291              
292 12 50       104 return 0 unless $tars{$name};
293              
294 12         110 local $CWD = tempdir( CLEANUP => 1 );
295              
296             my $cleanup = sub {
297 12     12   203 my $save = $CWD;
298 12         15589 unlink $name;
299 12         394 unlink 'xx.txt';
300 12         163 $CWD = '..';
301 12         890 rmdir $save;
302 12         5743 };
303              
304 12         97 Path::Tiny->new($name)->spew_raw($tars{$name});
305              
306 12         5848 my @cmd = ($self->tar_cmd, 'xf', $name);
307 12 100       53 if($ext eq 'zip')
308             {
309 1         8 @cmd = ($self->unzip_cmd, $name);
310             }
311              
312             my(undef, $exit) = capture_merged {
313 12     12   80299 system(@cmd);
314 12         7881 $?;
315 12         745 };
316              
317 12 100       13925 if($exit)
318             {
319 1         21 $cleanup->();
320 1         30 return 0;
321             }
322              
323 11         45 my $content = eval { Path::Tiny->new('xx.txt')->slurp };
  11         286  
324 11         3640 $cleanup->();
325              
326 11   33     409 return defined $content && $content eq "xx\n";
327             }
328              
329             1;
330              
331             =pod
332              
333             =encoding UTF-8
334              
335             =head1 NAME
336              
337             Alien::Build::Plugin::Extract::CommandLine - Plugin to extract an archive using command line tools
338              
339             =head1 VERSION
340              
341             version 2.45
342              
343             =head1 SYNOPSIS
344              
345             use alienfile;
346             plugin 'Extract::CommandLine' => (
347             format => 'tar.gz',
348             );
349              
350             =head1 DESCRIPTION
351              
352             Note: in most case you will want to use L
353             instead. It picks the appropriate Extract plugin based on your platform and environment.
354             In some cases you may need to use this plugin directly instead.
355              
356             This plugin extracts from an archive in various formats using command line tools.
357              
358             =head1 PROPERTIES
359              
360             =head2 format
361              
362             Gives a hint as to the expected format.
363              
364             =head2 gzip_cmd
365              
366             The C command, if available. C if not available.
367              
368             =head2 bzip2_cmd
369              
370             The C command, if available. C if not available.
371              
372             =head2 xz_cmd
373              
374             The C command, if available. C if not available.
375              
376             =head2 tar_cmd
377              
378             The C command, if available. C if not available.
379              
380             =head2 unzip_cmd
381              
382             The C command, if available. C if not available.
383              
384             =head1 METHODS
385              
386             =head2 handles
387              
388             Alien::Build::Plugin::Extract::CommandLine->handles($ext);
389             $plugin->handles($ext);
390              
391             Returns true if the plugin is able to handle the archive of the
392             given format.
393              
394             =head2 available
395              
396             Alien::Build::Plugin::Extract::CommandLine->available($ext);
397              
398             Returns true if the plugin is available to extract without
399             installing anything new.
400              
401             =head1 SEE ALSO
402              
403             L, L, L, L, L
404              
405             =head1 AUTHOR
406              
407             Author: Graham Ollis Eplicease@cpan.orgE
408              
409             Contributors:
410              
411             Diab Jerius (DJERIUS)
412              
413             Roy Storey (KIWIROY)
414              
415             Ilya Pavlov
416              
417             David Mertens (run4flat)
418              
419             Mark Nunberg (mordy, mnunberg)
420              
421             Christian Walde (Mithaldu)
422              
423             Brian Wightman (MidLifeXis)
424              
425             Zaki Mughal (zmughal)
426              
427             mohawk (mohawk2, ETJ)
428              
429             Vikas N Kumar (vikasnkumar)
430              
431             Flavio Poletti (polettix)
432              
433             Salvador Fandiño (salva)
434              
435             Gianni Ceccarelli (dakkar)
436              
437             Pavel Shaydo (zwon, trinitum)
438              
439             Kang-min Liu (劉康民, gugod)
440              
441             Nicholas Shipp (nshp)
442              
443             Juan Julián Merelo Guervós (JJ)
444              
445             Joel Berger (JBERGER)
446              
447             Petr Písař (ppisar)
448              
449             Lance Wicks (LANCEW)
450              
451             Ahmad Fatoum (a3f, ATHREEF)
452              
453             José Joaquín Atria (JJATRIA)
454              
455             Duke Leto (LETO)
456              
457             Shoichi Kaji (SKAJI)
458              
459             Shawn Laffan (SLAFFAN)
460              
461             Paul Evans (leonerd, PEVANS)
462              
463             Håkon Hægland (hakonhagland, HAKONH)
464              
465             nick nauwelaerts (INPHOBIA)
466              
467             =head1 COPYRIGHT AND LICENSE
468              
469             This software is copyright (c) 2011-2020 by Graham Ollis.
470              
471             This is free software; you can redistribute it and/or modify it under
472             the same terms as the Perl 5 programming language system itself.
473              
474             =cut
475              
476             __DATA__