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   2845 use strict;
  6         15  
  6         160  
4 6     6   89 use warnings;
  6         11  
  6         135  
5 6     6   99 use 5.008004;
  6         19  
6 6     6   705 use Alien::Build::Plugin;
  6         11  
  6         90  
7 6     6   35 use Path::Tiny ();
  6         12  
  6         94  
8 6     6   1139 use File::Which ();
  6         2473  
  6         113  
9 6     6   745 use File::chdir;
  6         5060  
  6         712  
10 6     6   42 use File::Temp qw( tempdir );
  6         13  
  6         326  
11 6     6   45 use Capture::Tiny qw( capture_merged );
  6         19  
  6         11802  
12              
13             # ABSTRACT: Plugin to extract an archive using command line tools
14             our $VERSION = '2.47'; # VERSION
15              
16              
17             has '+format' => 'tar';
18              
19              
20             sub gzip_cmd
21             {
22 3 50   3 1 282 _which('gzip') ? 'gzip' : undef;
23             }
24              
25              
26 49     49   4902 sub _which { scalar File::Which::which(@_) }
27              
28             sub bzip2_cmd
29             {
30 2 50   2 1 638 _which('bzip2') ? 'bzip2' : undef;
31             }
32              
33              
34             sub xz_cmd
35             {
36 2 50   2 1 547 _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   3675 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 628 _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 329 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   55 my(undef, $build, @cmd) = @_;
91 6         116 $build->log("+ @cmd");
92 6         51118 system @cmd;
93 6 50       964 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   21 my($self, $src) = @_;
114              
115 6         13 my $name;
116             my $cmd;
117              
118 6 100       114 if($src =~ /\.(gz|tgz|Z|taz)$/)
    100          
    100          
119             {
120 2 50       37 $self->gzip_cmd(_which('gzip')) unless defined $self->gzip_cmd;
121 2 100       507 if($src =~ /\.(gz|tgz)$/)
    50          
122             {
123 1 50       26 $cmd = $self->gzip_cmd unless $self->_tar_can('tar.gz');
124             }
125             elsif($src =~ /\.(Z|taz)$/)
126             {
127 1 50       18 $cmd = $self->gzip_cmd unless $self->_tar_can('tar.Z');
128             }
129             }
130             elsif($src =~ /\.(bz2|tbz)$/)
131             {
132 1 50       20 $self->bzip2_cmd(_which('bzip2')) unless defined $self->bzip2_cmd;
133 1 50       171 $cmd = $self->bzip2_cmd unless $self->_tar_can('tar.bz2');
134             }
135             elsif($src =~ /\.(xz|txz)$/)
136             {
137 1 50       26 $self->xz_cmd(_which('xz')) unless defined $self->xz_cmd;
138 1 50       180 $cmd = $self->xz_cmd unless $self->_tar_can('tar.xz');
139             }
140              
141 6 50 33     293 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         81 ($name,$cmd);
153             }
154              
155              
156             sub handles
157             {
158 8     8 1 33665 my($class, $ext) = @_;
159              
160 8 100       55 my $self = ref $class
161             ? $class
162             : __PACKAGE__->new;
163              
164 8 50       30 $ext = 'tar.Z' if $ext eq 'taz';
165 8 50       19 $ext = 'tar.gz' if $ext eq 'tgz';
166 8 50       20 $ext = 'tar.bz2' if $ext eq 'tbz';
167 8 50       17 $ext = 'tar.xz' if $ext eq 'txz';
168              
169 8 100 66     43 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     53 return 1 if $ext eq 'tar.bz2' && $self->_tar_can('tar.bz2');
172 5 100 66     38 return 1 if $ext eq 'tar.xz' && $self->_tar_can('tar.xz');
173              
174 4 50 33     26 return 0 if $ext =~ s/\.(gz|Z)$// && (!$self->gzip_cmd);
175 4 50 33     15 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     23 return 1 if $ext eq 'tar' && $self->_tar_can('tar');
179 1 50 33     31 return 1 if $ext eq 'zip' && $self->_tar_can('zip');
180              
181 1         53 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 7 my($self, $meta) = @_;
196              
197 2 50 33     14 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   21 my($build, $src) = @_;
217              
218 6         41 my($dcon_name, $dcon_cmd) = _dcon($self, $src);
219              
220 6 50       35 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     121 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         45 $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         19 );
260             }
261              
262             my %tars;
263              
264             sub _tar_can
265             {
266 12     12   55 my($self, $ext) = @_;
267              
268 12 100       43 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       251 if($line =~ /^\[ (.*) \]$/)
275             {
276 12         39 $name = $1;
277             }
278             else
279             {
280 154         365 $tars{$name} .= $line;
281             }
282             }
283              
284 2         13 foreach my $key (keys %tars)
285             {
286 14         98 $tars{$key} = unpack "u", $tars{$key};
287             }
288             }
289              
290 12         35 my $name = "xx.$ext";
291              
292 12 50       78 return 0 unless $tars{$name};
293              
294 12         95 local $CWD = tempdir( CLEANUP => 1 );
295              
296             my $cleanup = sub {
297 12     12   268 my $save = $CWD;
298 12         1166 unlink $name;
299 12         480 unlink 'xx.txt';
300 12         162 $CWD = '..';
301 12         1047 rmdir $save;
302 12         5976 };
303              
304 12         81 Path::Tiny->new($name)->spew_raw($tars{$name});
305              
306 12         6177 my @cmd = ($self->tar_cmd, 'xf', $name);
307 12 100       41 if($ext eq 'zip')
308             {
309 1         11 @cmd = ($self->unzip_cmd, $name);
310             }
311              
312             my(undef, $exit) = capture_merged {
313 12     12   104869 system(@cmd);
314 12         9814 $?;
315 12         846 };
316              
317 12 100       16223 if($exit)
318             {
319 1         19 $cleanup->();
320 1         20 return 0;
321             }
322              
323 11         62 my $content = eval { Path::Tiny->new('xx.txt')->slurp };
  11         312  
324 11         4208 $cleanup->();
325              
326 11   33     401 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.47
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__