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   3176 use strict;
  6         12  
  6         196  
4 6     6   33 use warnings;
  6         15  
  6         170  
5 6     6   120 use 5.008004;
  6         24  
6 6     6   930 use Alien::Build::Plugin;
  6         17  
  6         54  
7 6     6   43 use Path::Tiny ();
  6         14  
  6         125  
8 6     6   1598 use File::Which ();
  6         3251  
  6         156  
9 6     6   959 use File::chdir;
  6         6564  
  6         942  
10 6     6   52 use File::Temp qw( tempdir );
  6         17  
  6         368  
11 6     6   44 use Capture::Tiny qw( capture_merged );
  6         13  
  6         13791  
12              
13             # ABSTRACT: Plugin to extract an archive using command line tools
14             our $VERSION = '2.46'; # VERSION
15              
16              
17             has '+format' => 'tar';
18              
19              
20             sub gzip_cmd
21             {
22 3 50   3 1 458 _which('gzip') ? 'gzip' : undef;
23             }
24              
25              
26 49     49   5359 sub _which { scalar File::Which::which(@_) }
27              
28             sub bzip2_cmd
29             {
30 2 50   2 1 963 _which('bzip2') ? 'bzip2' : undef;
31             }
32              
33              
34             sub xz_cmd
35             {
36 2 50   2 1 692 _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   4191 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 780 _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 479 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       7 _which('unzip') ? 'unzip' : undef;
85             }
86             }
87              
88             sub _run
89             {
90 6     6   56 my(undef, $build, @cmd) = @_;
91 6         124 $build->log("+ @cmd");
92 6         46491 system @cmd;
93 6 50       696 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   35 my($self, $src) = @_;
114              
115 6         23 my $name;
116             my $cmd;
117              
118 6 100       155 if($src =~ /\.(gz|tgz|Z|taz)$/)
    100          
    100          
119             {
120 2 50       39 $self->gzip_cmd(_which('gzip')) unless defined $self->gzip_cmd;
121 2 100       612 if($src =~ /\.(gz|tgz)$/)
    50          
122             {
123 1 50       22 $cmd = $self->gzip_cmd unless $self->_tar_can('tar.gz');
124             }
125             elsif($src =~ /\.(Z|taz)$/)
126             {
127 1 50       14 $cmd = $self->gzip_cmd unless $self->_tar_can('tar.Z');
128             }
129             }
130             elsif($src =~ /\.(bz2|tbz)$/)
131             {
132 1 50       27 $self->bzip2_cmd(_which('bzip2')) unless defined $self->bzip2_cmd;
133 1 50       247 $cmd = $self->bzip2_cmd unless $self->_tar_can('tar.bz2');
134             }
135             elsif($src =~ /\.(xz|txz)$/)
136             {
137 1 50       25 $self->xz_cmd(_which('xz')) unless defined $self->xz_cmd;
138 1 50       224 $cmd = $self->xz_cmd unless $self->_tar_can('tar.xz');
139             }
140              
141 6 50 33     347 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         77 ($name,$cmd);
153             }
154              
155              
156             sub handles
157             {
158 8     8 1 39003 my($class, $ext) = @_;
159              
160 8 100       72 my $self = ref $class
161             ? $class
162             : __PACKAGE__->new;
163              
164 8 50       36 $ext = 'tar.Z' if $ext eq 'taz';
165 8 50       25 $ext = 'tar.gz' if $ext eq 'tgz';
166 8 50       30 $ext = 'tar.bz2' if $ext eq 'tbz';
167 8 50       24 $ext = 'tar.xz' if $ext eq 'txz';
168              
169 8 100 66     58 return 1 if $ext eq 'tar.gz' && $self->_tar_can('tar.gz');
170 7 100 66     37 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     33 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     23 return 0 if $ext =~ s/\.bz2$// && (!$self->bzip2_cmd);
176 4 50 33     18 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     32 return 1 if $ext eq 'zip' && $self->_tar_can('zip');
180              
181 1         67 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 13 my($self, $meta) = @_;
196              
197 2 50 33     16 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   31 my($build, $src) = @_;
217              
218 6         65 my($dcon_name, $dcon_cmd) = _dcon($self, $src);
219              
220 6 50       33 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     135 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         47 $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         33 );
260             }
261              
262             my %tars;
263              
264             sub _tar_can
265             {
266 12     12   68 my($self, $ext) = @_;
267              
268 12 100       52 unless(%tars)
269             {
270 2         5 my $name = '';
271 2         5 local $_; # to avoid dynamically scoped read-only $_ from upper scopes
272 2         12 while(my $line = )
273             {
274 166 100       319 if($line =~ /^\[ (.*) \]$/)
275             {
276 12         41 $name = $1;
277             }
278             else
279             {
280 154         508 $tars{$name} .= $line;
281             }
282             }
283              
284 2         20 foreach my $key (keys %tars)
285             {
286 14         117 $tars{$key} = unpack "u", $tars{$key};
287             }
288             }
289              
290 12         109 my $name = "xx.$ext";
291              
292 12 50       73 return 0 unless $tars{$name};
293              
294 12         125 local $CWD = tempdir( CLEANUP => 1 );
295              
296             my $cleanup = sub {
297 12     12   244 my $save = $CWD;
298 12         1200 unlink $name;
299 12         442 unlink 'xx.txt';
300 12         223 $CWD = '..';
301 12         1102 rmdir $save;
302 12         7258 };
303              
304 12         102 Path::Tiny->new($name)->spew_raw($tars{$name});
305              
306 12         7018 my @cmd = ($self->tar_cmd, 'xf', $name);
307 12 100       68 if($ext eq 'zip')
308             {
309 1         13 @cmd = ($self->unzip_cmd, $name);
310             }
311              
312             my(undef, $exit) = capture_merged {
313 12     12   105251 system(@cmd);
314 12         7122 $?;
315 12         964 };
316              
317 12 100       17216 if($exit)
318             {
319 1         28 $cleanup->();
320 1         35 return 0;
321             }
322              
323 11         61 my $content = eval { Path::Tiny->new('xx.txt')->slurp };
  11         354  
324 11         4476 $cleanup->();
325              
326 11   33     497 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.46
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__