File Coverage

blib/lib/Alien/Libarchive/Installer.pm
Criterion Covered Total %
statement 14 306 4.5
branch 1 148 0.6
condition 0 61 0.0
subroutine 5 20 25.0
pod 13 13 100.0
total 33 548 6.0


line stmt bran cond sub pod time code
1             package Alien::Libarchive::Installer;
2              
3 8     8   254129 use strict;
  8         9  
  8         184  
4 8     8   23 use warnings;
  8         8  
  8         9203  
5              
6             # ABSTRACT: Installer for libarchive
7             our $VERSION = '0.14'; # VERSION
8              
9              
10             sub versions_available
11             {
12 0     0 1 0 require HTTP::Tiny;
13 0         0 my $url = "http://www.libarchive.org/downloads/";
14 0         0 my $response = HTTP::Tiny->new->get($url);
15            
16             die sprintf("%s %s %s", $response->{status}, $response->{reason}, $url)
17 0 0       0 unless $response->{success};
18              
19 0         0 my @versions;
20 0         0 push @versions, [$1,$2,$3] while $response->{content} =~ /libarchive-([1-9][0-9]*)\.([0-9]+)\.([0-9]+)\.tar.gz/g;
21 0 0 0     0 @versions = map { join '.', @$_ } sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] } @versions;
  0         0  
  0         0  
22             }
23              
24              
25             sub fetch
26             {
27 0     0 1 0 my($class, %options) = @_;
28            
29 0   0     0 my $dir = $options{dir} || eval { require File::Temp; File::Temp::tempdir( CLEANUP => 1 ) };
30              
31 0         0 require HTTP::Tiny;
32 0   0     0 my $version = $options{version} || do {
33             my @versions = $class->versions_available;
34             die "unable to determine latest version from listing"
35             unless @versions > 0;
36             $versions[-1];
37             };
38              
39 0 0       0 if(defined $ENV{ALIEN_LIBARCHIVE_INSTALL_MIRROR})
40             {
41 0         0 my $fn = File::Spec->catfile($ENV{ALIEN_LIBARCHIVE_INSTALL_MIRROR}, "libarchive-$version.tar.gz");
42 0 0       0 return wantarray ? ($fn, $version) : $fn;
43             }
44              
45 0         0 my $url = "http://www.libarchive.org/downloads/libarchive-$version.tar.gz";
46            
47 0         0 my $response = HTTP::Tiny->new->get($url);
48            
49             die sprintf("%s %s %s", $response->{status}, $response->{reason}, $url)
50 0 0       0 unless $response->{success};
51            
52 0         0 require File::Spec;
53            
54 0         0 my $fn = File::Spec->catfile($dir, "libarchive-$version.tar.gz");
55            
56 0         0 open my $fh, '>', $fn;
57 0         0 binmode $fh;
58 0         0 print $fh $response->{content};
59 0         0 close $fh;
60            
61 0 0       0 wantarray ? ($fn, $version) : $fn;
62             }
63              
64              
65             sub build_requires
66             {
67 1     1 1 11 my %prereqs = (
68             'HTTP::Tiny' => 0,
69             'Archive::Tar' => 0,
70             );
71            
72 1 50       4 if($^O eq 'MSWin32')
73             {
74 0         0 require Config;
75 0 0       0 if($Config::Config{cc} =~ /cl(\.exe)?$/i)
76             {
77 0         0 $prereqs{'Alien::CMake'} = '0.05';
78             }
79             else
80             {
81 0         0 $prereqs{'Alien::MSYS'} = '0.07';
82 0         0 $prereqs{'PkgConfig'} = '0.07620';
83             }
84             }
85            
86 1         5 \%prereqs;
87             }
88              
89              
90             sub system_requires
91             {
92 1     1 1 8 my %prereqs = ();
93 1         4 \%prereqs;
94             }
95              
96              
97             sub system_install
98             {
99 0     0 1   my($class, %options) = @_;
100              
101 0 0         $options{alien} = 1 unless defined $options{alien};
102 0   0       $options{test} ||= 'compile';
103             die "test must be one of compile, ffi or both"
104 0 0         unless $options{test} =~ /^(compile|ffi|both)$/;
105              
106 0 0 0       if($options{alien} && eval q{ use Alien::Libarchive 0.21; 1 })
107             {
108 0           my $alien = Alien::Libarchive->new;
109            
110 0           require File::Spec;
111 0           my $dir;
112             my(@dlls) = map {
113 0           my($v,$d,$f) = File::Spec->splitpath($_);
  0            
114 0           $dir = [$v,File::Spec->splitdir($d)];
115 0           $f;
116             } $alien->dlls;
117            
118 0           my $build = bless {
119             cflags => [$alien->cflags],
120             libs => [$alien->libs],
121             dll_dir => $dir,
122             dlls => \@dlls,
123             prefix => File::Spec->rootdir,
124             }, $class;
125 0           eval {
126 0 0 0       $build->test_compile_run || die $build->error if $options{test} =~ /^(compile|both)$/;
127 0 0 0       $build->test_ffi || die $build->error if $options{test} =~ /^(ffi|both)$/;
128             };
129 0 0         return $build unless $@;
130             }
131              
132 0           my $build = bless {
133             cflags => _try_pkg_config(undef, 'cflags', '', ''),
134             libs => _try_pkg_config(undef, 'libs', '-larchive', ''),
135             }, $class;
136            
137 0 0         if($options{test} =~ /^(ffi|both)$/)
138             {
139 0           my @dir_search_list;
140            
141 0 0         if($^O eq 'MSWin32')
142             {
143             # On MSWin32 the entire path is not included in dl_library_path
144             # but that is the most likely place that we will find dlls.
145 0           @dir_search_list = grep { -d $_ } split /;/, $ENV{PATH};
  0            
146             }
147             else
148             {
149 0           require DynaLoader;
150 0           @dir_search_list = grep { -d $_ } @DynaLoader::dl_library_path
  0            
151             }
152            
153 0           found_dll: foreach my $dir (@dir_search_list)
154             {
155 0           my $dh;
156 0 0         opendir($dh, $dir) || next;
157             # sort by filename length so that libarchive.so.12.0.4
158             # is preferred over libarchive.so.12 or libarchive.so
159             # if only to make diagnostics point to the more specific
160             # version.
161 0           foreach my $file (sort { length $b <=> length $a } readdir $dh)
  0            
162             {
163 0 0         if($^O eq 'MSWin32')
    0          
164             {
165 0 0         next unless $file =~ /^libarchive-[0-9]+\.dll$/i;
166             }
167             elsif($^O eq 'cygwin')
168             {
169 0 0         next unless $file =~ /^cygarchive-[0-9]+\.dll$/i;
170             }
171             else
172             {
173 0 0         next unless $file =~ /^libarchive\.(dylib|so(\.[0-9]+)*)$/;
174             }
175 0           require File::Spec;
176 0           my($v,$d) = File::Spec->splitpath($dir, 1);
177 0           $build->{dll_dir} = [File::Spec->splitdir($d)];
178 0           $build->{prefix} = $v;
179 0           $build->{dlls} = [$file];
180 0           closedir $dh;
181 0           last found_dll;
182             }
183 0           closedir $dh;
184             }
185             }
186              
187 0 0 0       $build->test_compile_run || die $build->error if $options{test} =~ /^(compile|both)$/;
188 0 0 0       $build->test_ffi || die $build->error if $options{test} =~ /^(ffi|both)$/;
189 0           $build;
190             }
191              
192              
193             sub _try_pkg_config
194             {
195 0     0     my($dir, $field, $guess, $extra) = @_;
196            
197 0 0         unless(defined $dir)
198             {
199 0           require File::Temp;
200 0           $dir = File::Temp::tempdir(CLEANUP => 1);
201             }
202            
203 0           require Config;
204 0   0       local $ENV{PKG_CONFIG_PATH} = join $Config::Config{path_sep}, $dir, split /$Config::Config{path_sep}/, ($ENV{PKG_CONFIG_PATH}||'');
205              
206 0           my $value = eval {
207             # you probably think I am crazy...
208 0           eval q{ use PkgConfig 0.07620 };
209 0 0         die $@ if $@;
210 0           my $value = `$^X $INC{'PkgConfig.pm'} --silence-errors libarchive $extra --$field`;
211 0 0         die if $?;
212 0           $value;
213             };
214              
215 0 0         unless(defined $value) {
216 8     8   36 no warnings;
  8         9  
  8         15758  
217 0           $value = `pkg-config --silence-errors libarchive $extra --$field`;
218 0 0         return $guess if $?;
219             }
220            
221 0           chomp $value;
222 0           require Text::ParseWords;
223 0           [Text::ParseWords::shellwords($value)];
224             }
225              
226             sub _msys
227             {
228 0     0     my($sub) = @_;
229 0           require Config;
230 0 0         if($^O eq 'MSWin32')
231             {
232 0 0         if($Config::Config{cc} !~ /cl(\.exe)?$/i)
233             {
234 0           require Alien::MSYS;
235 0     0     return Alien::MSYS::msys(sub{ $sub->('make') });
  0            
236             }
237             }
238 0           $sub->($Config::Config{make});
239             }
240              
241             sub build_install
242             {
243 0     0 1   my($class, $prefix, %options) = @_;
244            
245 0   0       $options{test} ||= 'compile';
246             die "test must be one of compile, ffi or both"
247 0 0         unless $options{test} =~ /^(compile|ffi|both)$/;
248 0 0         die "need an install prefix" unless $prefix;
249            
250 0           $prefix =~ s{\\}{/}g;
251            
252 0   0       my $dir = $options{dir} || do { require File::Temp; File::Temp::tempdir( CLEANUP => 1 ) };
253            
254 0           require Archive::Tar;
255 0           my $tar = Archive::Tar->new;
256 0   0       $tar->read($options{tar} || $class->fetch);
257            
258 0           require Cwd;
259 0           my $save = Cwd::getcwd();
260            
261 0           chdir $dir;
262 0           my $build = eval {
263            
264 0           $tar->extract;
265              
266 0           chdir do {
267 0           opendir my $dh, '.';
268 0           my(@list) = grep !/^\./,readdir $dh;
269 0           close $dh;
270 0 0         die "unable to find source in build root" if @list == 0;
271 0 0         die "confused by multiple entries in the build root" if @list > 1;
272 0           $list[0];
273             };
274            
275             _msys(sub {
276 0     0     my($make) = @_;
277 0           require Config;
278 0 0         if($Config::Config{cc} !~ /cl(\.exe)?$/i)
279             {
280 0           system 'sh', 'configure', "--prefix=$prefix", '--with-pic';
281 0 0         die "configure failed" if $?;
282             }
283             else
284             {
285 0           require Alien::CMake;
286 0           my $cmake = Alien::CMake->config('prefix') . '/bin/cmake.exe';
287 0 0         my $system = $make =~ /nmake(\.exe)?$/ ? 'NMake Makefiles' : 'MinGW Makefiles';
288 0           system $cmake,
289             -G => $system,
290             "-DCMAKE_MAKE_PROGRAM:PATH=$make",
291             "-DCMAKE_INSTALL_PREFIX:PATH=$prefix",
292             "-DENABLE_TEST=OFF",
293             ".";
294 0 0         die "cmake failed" if $?;
295             }
296 0           system $make, 'all';
297 0 0         die "make all failed" if $?;
298 0           system $make, 'install';
299 0 0         die "make install failed" if $?;
300 0           });
301              
302 0           require File::Spec;
303              
304 0 0         foreach my $name ($^O =~ /^(MSWin32|cygwin)$/ ? ('bin','lib') : ('lib'))
305             {
306 0           do {
307 0           my $static_dir = File::Spec->catdir($prefix, $name);
308 0           my $dll_dir = File::Spec->catdir($prefix, 'dll');
309 0           require File::Path;
310 0           File::Path::mkpath($dll_dir, 0, 0755);
311 0           my $dh;
312 0           opendir $dh, $static_dir;
313 0           my @list = readdir $dh;
314 0 0         @list = grep { /\.so/ || /\.(dylib|la|dll|dll\.a)$/ } grep !/^\./, @list;
  0            
315 0           closedir $dh;
316 0           foreach my $basename (@list)
317             {
318 0           my $from = File::Spec->catfile($static_dir, $basename);
319 0           my $to = File::Spec->catfile($dll_dir, $basename);
320 0 0         if(-l $from)
321             {
322 0           symlink(readlink $from, $to);
323 0           unlink($from);
324             }
325             else
326             {
327 0           require File::Copy;
328 0           File::Copy::move($from, $to);
329             }
330             }
331             };
332             }
333              
334 0           my $pkg_config_dir = File::Spec->catdir($prefix, 'lib', 'pkgconfig');
335            
336 0           my $pcfile = File::Spec->catfile($pkg_config_dir, 'libarchive.pc');
337            
338 0           do {
339 0           my @content;
340 0 0         if($Config::Config{cc} !~ /cl(\.exe)?$/i)
341             {
342 0           open my $fh, '<', $pcfile;
343 0           @content = map { s{$prefix}{'${pcfiledir}/../..'}eg; $_ } do { <$fh> };
  0            
  0            
  0            
  0            
344 0           close $fh;
345             }
346             else
347             {
348             # TODO: later when we know the version with more
349             # certainty, we can update this file with the
350             # Version
351 0           @content = join "\n", "prefix=\${pcfiledir}/../..",
352             "exec_prefix=\${prefix}",
353             "libdir=\${exec_prefix}/lib",
354             "includedir=\${prefix}/include",
355             "Name: libarchive",
356             "Description: library that can create and read several streaming archive formats",
357             "Cflags: -I\${includedir}",
358             "Libs: advapi32.lib \${libdir}/archive_static.lib",
359             "Libs.private: ",
360             "";
361 0           require File::Path;
362 0           File::Path::mkpath($pkg_config_dir, 0, 0755);
363             }
364            
365 0           my($version) = map { /^Version:\s*(.*)$/; $1 } grep /^Version: /, @content;
  0            
  0            
366             # older versions apparently didn't include the necessary -I and -L flags
367 0 0 0       if(defined $version && $version =~ /^[12]\./)
368             {
369 0           for(@content)
370             {
371 0           s/^Libs: /Libs: -L\${libdir} /;
372             }
373 0           push @content, "Cflags: -I\${includedir}\n";
374             }
375            
376 0           open my $fh, '>', $pcfile;
377 0           print $fh @content;
378 0           close $fh;
379             };
380            
381             my $build = bless {
382             cflags => _try_pkg_config($pkg_config_dir, 'cflags', '-I' . File::Spec->catdir($prefix, 'include'), '--static'),
383             libs => _try_pkg_config($pkg_config_dir, 'libs', '-L' . File::Spec->catdir($prefix, 'lib'), '--static'),
384             prefix => $prefix,
385             dll_dir => [ 'dll' ],
386 0           dlls => do {
387 0           opendir(my $dh, File::Spec->catdir($prefix, 'dll'));
388 0 0         [grep { ! -l File::Spec->catfile($prefix, 'dll', $_) } grep { /\.so/ || /\.(dll|dylib)$/ } grep !/^\./, readdir $dh];
  0            
  0            
389             },
390             }, $class;
391            
392 0 0 0       if($^O eq 'cygwin' || $^O eq 'MSWin32')
393             {
394             # TODO: should this go in the munged pc file?
395 0           unshift @{ $build->{cflags} }, '-DLIBARCHIVE_STATIC';
  0            
396             }
397              
398 0 0 0       $build->test_compile_run || die $build->error if $options{test} =~ /^(compile|both)$/;
399 0 0 0       $build->test_ffi || die $build->error if $options{test} =~ /^(ffi|both)$/;
400 0           $build;
401             };
402            
403 0           my $error = $@;
404 0           chdir $save;
405 0 0         die $error if $error;
406 0           $build;
407             }
408              
409              
410 0     0 1   sub cflags { shift->{cflags} }
411 0     0 1   sub libs { shift->{libs} }
412 0     0 1   sub version { shift->{version} }
413              
414             sub dlls
415             {
416 0     0 1   my($self, $prefix) = @_;
417            
418 0 0         $prefix = $self->{prefix} unless defined $prefix;
419 0 0 0       $prefix = '' if $^O eq 'MSWin32' && $prefix eq '\\';
420            
421 0 0 0       unless(defined $self->{dlls} && defined $self->{dll_dir})
422             {
423             # Question: is this necessary in light of the better
424             # dll detection now done in system_install ?
425 0 0         if($^O eq 'cygwin')
426             {
427             # /usr/bin/cygarchive-13.dll
428 0           opendir my $dh, '/usr/bin';
429 0           $self->{dlls} = [grep /^cygarchive-[0-9]+.dll$/i, readdir $dh];
430 0           $self->{dll_dir} = [];
431 0           $prefix = '/usr/bin';
432 0           closedir $dh;
433             }
434             else
435             {
436 0           require DynaLoader;
437 0 0         $self->{libs} = [] unless defined $self->{libs};
438 0 0         $self->{libs} = [ $self->{libs} ] unless ref $self->{libs};
439 0           my $path = DynaLoader::dl_findfile(grep /^-l/, @{ $self->libs });
  0            
440 0 0         die "unable to find dynamic library" unless defined $path;
441 0           require File::Spec;
442 0           my($vol, $dirs, $file) = File::Spec->splitpath($path);
443 0 0         if($^O eq 'openbsd')
444             {
445             # on openbsd we get the .a file back, so have to scan
446             # for .so.#.# as there is no .so symlink
447 0           opendir(my $dh, $dirs);
448 0           $self->{dlls} = [grep /^libarchive.so/, readdir $dh];
449 0           closedir $dh;
450             }
451             else
452             {
453 0           $self->{dlls} = [ $file ];
454             }
455 0           $self->{dll_dir} = [];
456 0           $self->{prefix} = $prefix = File::Spec->catpath($vol, $dirs);
457             }
458             }
459            
460 0 0 0       if($prefix eq '' && $self->{dll_dir}->[0] eq '')
461             {
462 0           shift @{ $self->{dll_dir} };
  0            
463             }
464            
465 0           require File::Spec;
466             $^O eq 'MSWin32'
467 0           ? map { File::Spec->catfile( @{ $self->{dll_dir} }, $_ ) } @{ $self->{dlls} }
  0            
  0            
468 0 0         : map { File::Spec->catfile($prefix, @{ $self->{dll_dir} }, $_ ) } @{ $self->{dlls} };
  0            
  0            
  0            
469             }
470              
471              
472             sub test_compile_run
473             {
474 0     0 1   my($self, %opt) = @_;
475 0           delete $self->{error};
476 0 0         $self->{quiet} = 1 unless defined $self->{quiet};
477 0   0       my $cbuilder = $opt{cbuilder} || do { require ExtUtils::CBuilder; ExtUtils::CBuilder->new(quiet => $self->{quiet}) };
478            
479 0 0         unless($cbuilder->have_compiler)
480             {
481 0           $self->{error} = 'no compiler';
482 0           return;
483             }
484            
485 0           require File::Spec;
486 0   0       my $dir = $opt{dir} || do { require File::Temp; File::Temp::tempdir( CLEANUP => 1 ) };
487 0           my $fn = File::Spec->catfile($dir, 'test.c');
488 0           do {
489 0           open my $fh, '>', $fn;
490 0           print $fh "#include \n",
491             "#include \n",
492             "#include \n",
493             "int\n",
494             "main(int argc, char *argv[])\n",
495             "{\n",
496             " printf(\"version = '%d'\\n\", archive_version_number());\n",
497             " return 0;\n",
498             "}\n";
499 0           close $fh;
500             };
501            
502 0           my $test_object = eval {
503             $cbuilder->compile(
504             source => $fn,
505 0   0       extra_compiler_flags => $self->{cflags} || [],
506             );
507             };
508            
509 0 0         if(my $error = $@)
510             {
511 0           $self->{error} = $error;
512 0           return;
513             }
514            
515 0           my $test_exe = eval {
516             $cbuilder->link_executable(
517             objects => $test_object,
518 0   0       extra_linker_flags => $self->{libs} || [],
519             );
520             };
521            
522 0 0         if(my $error = $@)
523             {
524 0           $self->{error} = $error;
525 0           return;
526             }
527            
528 0 0         if($test_exe =~ /\s/)
529             {
530 0 0         $test_exe = Win32::GetShortPathName($test_exe) if $^O eq 'MSWin32';
531 0 0         $test_exe = Cygwin::win_to_posix_path(Win32::GetShortPathName(Cygwin::posix_to_win_path($test_exe))) if $^O eq 'cygwin';
532             }
533            
534 0           my $output = `$test_exe`;
535            
536 0 0         if($? == -1)
    0          
    0          
537             {
538 0           $self->{error} = "failed to execute $!";
539 0           return;
540             }
541             elsif($? & 127)
542             {
543 0           $self->{error} = "child died with siganl " . ($? & 127);
544 0           return;
545             }
546             elsif($?)
547             {
548 0           $self->{error} = "child exited with value " . ($? >> 8);
549 0           return;
550             }
551            
552 0 0         if($output =~ /version = '([0-9]+)([0-9]{3})([0-9]{3})'/)
553             {
554 0           return $self->{version} = join '.', map { int } $1, $2, $3;
  0            
555             }
556             else
557             {
558 0           $self->{error} = "unable to retrieve version from output";
559 0           return;
560             }
561             }
562              
563              
564             sub test_ffi
565             {
566 0     0 1   my($self) = @_;
567 0           require FFI::Raw;
568 0           delete $self->{error};
569              
570 0           foreach my $dll ($self->dlls)
571             {
572 0           my $archive_version_number = eval {
573 0           FFI::Raw->new(
574             $dll, 'archive_version_number',
575             FFI::Raw::int(),
576             );
577             };
578 0 0         next if $@;
579 0 0         if($archive_version_number->() =~ /^([0-9]+)([0-9]{3})([0-9]{3})/)
580             {
581 0           return $self->{version} = join '.', map { int } $1, $2, $3;
  0            
582             }
583             }
584 0           $self->{error} = 'could not find archive_version_number';
585 0           return;
586             }
587              
588              
589 0     0 1   sub error { shift->{error} }
590              
591             1;
592              
593             __END__