File Coverage

blib/lib/Imager/Probe.pm
Criterion Covered Total %
statement 14 265 5.2
branch 0 188 0.0
condition 0 92 0.0
subroutine 5 26 19.2
pod 0 2 0.0
total 19 573 3.3


line stmt bran cond sub pod time code
1             package Imager::Probe;
2 1     1   908 use 5.006;
  1         3  
3 1     1   5 use strict;
  1         2  
  1         18  
4 1     1   4 use File::Spec;
  1         2  
  1         16  
5 1     1   4 use Config;
  1         2  
  1         30  
6 1     1   4 use Cwd ();
  1         2  
  1         3604  
7              
8             our $VERSION = "1.008";
9              
10             my @alt_transfer = qw/altname incsuffix libbase/;
11              
12             sub probe {
13 0     0 0   my ($class, $req) = @_;
14              
15 0   0       $req->{verbose} ||= $ENV{IM_VERBOSE};
16              
17 0           my $name = $req->{name};
18 0           my $result;
19 0 0         if ($req->{code}) {
20 0           $result = _probe_code($req);
21             }
22 0 0 0       if (!$result && $req->{pkg}) {
23 0           $result = _probe_pkg($req);
24             }
25 0 0 0       if (!$result && $req->{inccheck} && ($req->{libcheck} || $req->{libbase})) {
      0        
      0        
26 0   0       $req->{altname} ||= "main";
27 0           $result = _probe_check($req);
28             }
29              
30 0 0 0       if ($result && $req->{testcode}) {
31 0           $result = _probe_test($req, $result);
32             }
33              
34 0 0 0       if (!$result && $req->{alternatives}) {
35 0           ALTCHECK:
36             my $index = 1;
37 0           for my $alt (@{$req->{alternatives}}) {
  0            
38 0   0       $req->{altname} = $alt->{altname} || "alt $index";
39             $req->{verbose}
40 0 0         and print "$req->{name}: Trying alternative $index\n";
41 0           my %work = %$req;
42 0           for my $key (@alt_transfer) {
43 0 0         exists $alt->{$key} and $work{$key} = $alt->{$key};
44             }
45 0           $result = _probe_check(\%work);
46              
47 0 0 0       if ($result && $req->{testcode}) {
48 0           $result = _probe_test(\%work, $result);
49             }
50              
51             $result
52 0 0         and last;
53              
54 0           ++$index;
55             }
56             }
57              
58 0 0 0       if (!$result && $req->{testcode}) {
59 0           $result = _probe_fake($req);
60              
61 0 0         $result or return;
62              
63 0           $result = _probe_test($req, $result);
64             }
65              
66 0 0         $result or return;
67              
68 0           return $result;
69             }
70              
71             sub _probe_code {
72 0     0     my ($req) = @_;
73              
74 0           my $code = $req->{code};
75 0 0         my @probes = ref $code eq "ARRAY" ? @$code : $code;
76              
77 0           my $result;
78 0           for my $probe (@probes) {
79 0 0         $result = $probe->($req)
80             and return $result;
81             }
82              
83 0           return;
84             }
85              
86             sub is_exe {
87 0     0 0   my ($name) = @_;
88              
89 0           my @exe_suffix = $Config{_exe};
90 0 0         if ($^O eq 'MSWin32') {
    0          
91 0           push @exe_suffix, qw/.bat .cmd/;
92             }
93             elsif ($^O eq 'cygwin') {
94 0           push @exe_suffix, "";
95             }
96              
97 0           for my $dir (File::Spec->path) {
98 0           for my $suffix (@exe_suffix) {
99 0 0         -x File::Spec->catfile($dir, "$name$suffix")
100             and return 1;
101             }
102             }
103              
104 0           return;
105             }
106              
107             sub _probe_pkg {
108 0     0     my ($req) = @_;
109              
110             # Setup pkg-config's environment variable to search non-standard paths
111             # which may be provided by --libdirs.
112 0           my @pkgcfg_paths = map { "$_/pkgconfig" } _lib_paths( $req );
  0            
113 0 0         unshift @pkgcfg_paths, $ENV{ 'PKG_CONFIG_PATH' } if $ENV{ 'PKG_CONFIG_PATH' };
114              
115 0           local $ENV{ 'PKG_CONFIG_PATH' } = join $Config{path_sep}, @pkgcfg_paths;
116 0 0         print "PKG_CONFIG_PATH=$ENV{PKG_CONFIG_PATH}\n" if $req->{verbose};
117              
118 0 0         is_exe('pkg-config') or return;
119 0 0         my $redir = $^O eq 'MSWin32' ? '' : '2>/dev/null';
120              
121 0           my @pkgs = @{$req->{pkg}};
  0            
122 0           for my $pkg (@pkgs) {
123 0 0         if (!system("pkg-config $pkg --exists $redir")) {
124             # if we find it, but the following fail, then pkg-config is too
125             # broken to be useful
126 0 0 0       my $cflags = `pkg-config $pkg --cflags`
127             and !$? or return;
128              
129 0 0 0       my $lflags = `pkg-config $pkg --libs`
130             and !$? or return;
131              
132 0           my $defines = '';
133 0           $cflags =~ s/(-D\S+)/$defines .= " $1"; ''/ge;
  0            
  0            
134              
135 0           chomp $cflags;
136 0           chomp $lflags;
137 0           print "$req->{name}: Found via pkg-config $pkg\n";
138 0 0         print <{verbose};
139             cflags: $cflags
140             defines: $defines
141             lflags: $lflags
142             EOS
143             # rt 75869
144             # if Win32 doesn't provide this information, too bad
145 0 0 0       if (!grep(/^-L/, split " ", $lflags)
146             && $^O ne 'MSWin32') {
147             # pkg-config told us about the library, make sure it's
148             # somewhere EU::MM can find it
149 0 0         print "Checking if EU::MM can find $lflags\n" if $req->{verbose};
150             my ($extra, $bs_load, $ld_load, $ld_run_path) =
151 0           ExtUtils::Liblist->ext($lflags, $req->{verbose});
152 0 0         unless ($ld_run_path) {
153             # search our standard places
154 0           $lflags = _resolve_libs($req, $lflags);
155             }
156             }
157              
158             return
159             {
160 0           INC => $cflags,
161             LIBS => $lflags,
162             DEFINE => $defines,
163             };
164             }
165             }
166              
167 0           print "$req->{name}: Not found via pkg-config\n";
168              
169 0           return;
170             }
171              
172             sub _is_msvc {
173 0     0     return $Config{cc} eq "cl";
174             }
175              
176             sub _lib_basename {
177 0     0     my ($base) = @_;
178              
179 0 0         if (_is_msvc()) {
180 0           return $base;
181             }
182             else {
183 0           return "lib$base";
184             }
185             }
186              
187             sub _lib_option {
188 0     0     my ($base) = @_;
189              
190 0 0         if (_is_msvc()) {
191 0           return $base . $Config{_a};
192             }
193             else {
194 0           return "-l$base";
195             }
196             }
197              
198             sub _quotearg {
199 0     0     my ($opt) = @_;
200              
201 0 0         return $opt =~ /\s/ ? qq("$opt") : $opt;
202             }
203              
204             sub _probe_check {
205 0     0     my ($req) = @_;
206              
207 0           my @libcheck;
208             my @libbase;
209 0 0         if ($req->{libcheck}) {
    0          
210 0 0         if (ref $req->{libcheck} eq "ARRAY") {
211 0           push @libcheck, @{$req->{libcheck}};
  0            
212             }
213             else {
214 0           push @libcheck, $req->{libcheck};
215             }
216             }
217             elsif ($req->{libbase}) {
218 0 0         @libbase = ref $req->{libbase} ? @{$req->{libbase}} : $req->{libbase};
  0            
219              
220 0           my $lext=$Config{'so'}; # Get extensions of libraries
221 0           my $aext=$Config{'_a'};
222              
223 0           for my $libbase (@libbase) {
224 0           my $basename = _lib_basename($libbase);
225             push @libcheck, sub {
226 0 0   0     -e File::Spec->catfile($_[0], "$basename$aext")
227             || -e File::Spec->catfile($_[0], "$basename.$lext")
228 0           };
229             }
230             }
231             else {
232             print "$req->{name}: No libcheck or libbase, nothing to search for\n"
233 0 0         if $req->{verbose};
234 0           return;
235             }
236              
237 0           my @found_libpath;
238 0           my @lib_search = _lib_paths($req);
239             print "$req->{name}: Searching directories for libraries:\n"
240 0 0         if $req->{verbose};
241 0           for my $libcheck (@libcheck) {
242 0           for my $path (@lib_search) {
243 0 0         print "$req->{name}: $path\n" if $req->{verbose};
244 0 0         if ($libcheck->($path)) {
245 0 0         print "$req->{name}: Found!\n" if $req->{verbose};
246 0           push @found_libpath, $path;
247 0           last;
248             }
249             }
250             }
251              
252 0           my $found_incpath;
253 0           my $inccheck = $req->{inccheck};
254 0           my @inc_search = _inc_paths($req);
255             print "$req->{name}: Searching directories for headers:\n"
256 0 0         if $req->{verbose};
257 0           for my $path (@inc_search) {
258 0 0         print "$req->{name}: $path\n" if $req->{verbose};
259 0 0         if ($inccheck->($path)) {
260 0 0         print "$req->{name}: Found!\n" if $req->{verbose};
261 0           $found_incpath = $path;
262 0           last;
263             }
264             }
265              
266 0           my $alt = "";
267 0 0         if ($req->{altname}) {
268 0           $alt = " $req->{altname}:";
269             }
270 0 0         print "$req->{name}:$alt includes ", $found_incpath ? "" : "not ",
    0          
271             "found - libraries ", @found_libpath == @libcheck ? "" : "not ", "found\n";
272              
273 0 0 0       @found_libpath == @libcheck && $found_incpath
274             or return;
275              
276 0           my @libs = map "-L$_", @found_libpath;
277 0 0         if ($req->{libopts}) {
    0          
278 0           push @libs, $req->{libopts};
279             }
280             elsif (@libbase) {
281 0           push @libs, map _lib_option($_), @libbase;
282             }
283             else {
284 0           die "$req->{altname}: inccheck but no libbase or libopts";
285             }
286              
287             return
288             {
289 0           INC => _quotearg("-I$found_incpath"),
290             LIBS => join(" ", map _quotearg($_), @libs),
291             DEFINE => "",
292             };
293             }
294              
295             sub _probe_fake {
296 0     0     my ($req) = @_;
297              
298             # the caller provided test code, and the compiler may look in
299             # places we don't, see Imager-Screenshot ticket 56793,
300             # so fake up a result so the test code can
301 0           my $lopts;
302 0 0         if ($req->{libopts}) {
    0          
303 0           $lopts = $req->{libopts};
304             }
305             elsif (defined $req->{libbase}) {
306             # might not need extra libraries, eg. Win32 perl already links
307             # everything
308             my @libs = $req->{libbase}
309 0           ? ( ref $req->{libbase} ? @{$req->{libbase}} : $req->{libbase} )
310 0 0         : ();
    0          
311 0           $lopts = join " ", map _lib_option($_), @libs;
312             }
313 0 0         if (defined $lopts) {
314 0           print "$req->{name}: Checking if the compiler can find them on its own\n";
315             return
316             {
317 0           INC => "",
318             LIBS => $lopts,
319             DEFINE => "",
320             };
321             }
322             else {
323             print "$req->{name}: Can't fake it - no libbase or libopts\n"
324 0 0         if $req->{verbose};
325 0           return;
326             }
327             }
328              
329             sub _probe_test {
330 0     0     my ($req, $result) = @_;
331              
332 0           require Devel::CheckLib;
333             # setup LD_RUN_PATH to match link time
334 0 0         print "Asking liblist for LD_RUN_PATH:\n" if $req->{verbose};
335             my ($extra, $bs_load, $ld_load, $ld_run_path) =
336 0           ExtUtils::Liblist->ext($result->{LIBS}, $req->{verbose});
337 0           local $ENV{LD_RUN_PATH};
338              
339 0 0         if ($ld_run_path) {
340             print "Setting LD_RUN_PATH=$ld_run_path for $req->{name} probe\n"
341 0 0         if $req->{verbose};
342 0           $ENV{LD_RUN_PATH} = $ld_run_path;
343 0 0 0       if ($Config{lddlflags} =~ /([^ ]*-(?:rpath|R)[,=]?)([^ ]+)/
344             && -d $2) {
345             # hackety, hackety
346             # LD_RUN_PATH is ignored when there's already an -rpath option
347             # so provide one
348 0           my $prefix = $1;
349             $result->{LDDLFLAGS} = $Config{lddlflags} . " " .
350 0           join " ", map "$prefix$_", split $Config{path_sep}, $ld_run_path;
351             }
352             }
353             my $good =
354             Devel::CheckLib::check_lib
355             (
356             debug => $req->{verbose},
357             LIBS => [ $result->{LIBS} ],
358             INC => $result->{INC},
359             header => $req->{testcodeheaders},
360             function => $req->{testcode},
361             prologue => $req->{testcodeprologue},
362 0           );
363 0 0         unless ($good) {
364 0           print "$req->{name}: Test code failed: $@";
365 0           return;
366             }
367              
368 0           print "$req->{name}: Passed code check\n";
369 0           return $result;
370             }
371              
372             sub _resolve_libs {
373 0     0     my ($req, $lflags) = @_;
374              
375 0           my @libs = grep /^-l/, split ' ', $lflags;
376 0           my %paths;
377 0           my @paths = _lib_paths($req);
378 0           my $so = $Config{so};
379 0           my $libext = $Config{_a};
380 0           for my $lib (@libs) {
381 0           $lib =~ s/^-l/lib/;
382              
383 0           for my $path (@paths) {
384 0 0 0       if (-e "$path/$lib.$so" || -e "$path/$lib$libext") {
385 0           $paths{$path} = 1;
386             }
387             }
388             }
389              
390 0           return join(" ", ( map "-L$_", keys %paths ), $lflags );
391             }
392              
393             sub _lib_paths {
394 0     0     my ($req) = @_;
395              
396             print "$req->{name} IM_LIBPATH: $ENV{IM_LIBPATH}\n"
397 0 0 0       if $req->{verbose} && defined $ENV{IM_LIBPATH};
398             print "$req->{name} LIB: $ENV{IM_LIBPATH}\n"
399 0 0 0       if $req->{verbose} && defined $ENV{LIB} && $^O eq "MSWin32";
      0        
400 0           my $lp = $req->{libpath};
401             print "$req->{name} libpath: ", ref $lp ? join($Config{path_sep}, @$lp) : $lp, "\n"
402 0 0 0       if $req->{verbose} && defined $lp;
    0          
403              
404             return _paths
405             (
406             $ENV{IM_LIBPATH},
407             $req->{libpath},
408             (
409 0           map { split ' ' }
410             grep $_,
411             @Config{qw/loclibpth libpth libspath/}
412             ),
413 0 0         $^O eq "MSWin32" ? $ENV{LIB} : "",
    0          
414             $^O eq "cygwin" ? "/usr/lib/w32api" : "",
415             "/usr/lib",
416             "/usr/local/lib",
417             _gcc_lib_paths(),
418             _dyn_lib_paths(),
419             );
420             }
421              
422             sub _gcc_lib_paths {
423             $Config{gccversion}
424 0 0   0     or return;
425              
426 0 0         my ($base_version) = $Config{gccversion} =~ /^([0-9]+)/
427             or return;
428              
429 0 0         $base_version >= 4
430             or return;
431              
432 0           local $ENV{LANG} = "C";
433 0           local $ENV{LC_ALL} = "C";
434 0 0         my ($lib_line) = grep /^libraries:/, `$Config{cc} -print-search-dirs`
435             or return;
436 0           $lib_line =~ s/^libraries: =//;
437 0           chomp $lib_line;
438              
439 0   0       return grep !/gcc/ && -d, split /:/, $lib_line;
440             }
441              
442             sub _dyn_lib_paths {
443 0 0         return map { defined() ? split /\Q$Config{path_sep}/ : () }
444 0     0     map $ENV{$_},
445             qw(LD_RUN_PATH LD_LIBRARY_PATH DYLD_LIBRARY_PATH LIBRARY_PATH);
446             }
447              
448             sub _inc_paths {
449 0     0     my ($req) = @_;
450              
451             print "$req->{name} IM_INCPATH: $ENV{IM_INCPATH}\n"
452 0 0 0       if $req->{verbose} && defined $ENV{IM_INCPATH};
453             print "$req->{name} INCLUDE: $ENV{INCLUDE}\n"
454 0 0 0       if $req->{verbose} && defined $ENV{INCLUDE} && $^O eq "MSWin32";
      0        
455 0           my $ip = $req->{incpath};
456             print "$req->{name} incpath: ", ref $ip ? join($Config{path_sep}, @$ip) : $ip, "\n"
457 0 0 0       if $req->{verbose} && defined $req->{incpath};
    0          
458              
459             my @paths = _paths
460             (
461             $ENV{IM_INCPATH},
462             $req->{incpath},
463             $^O eq "MSWin32" ? $ENV{INCLUDE} : "",
464             $^O eq "cygwin" ? "/usr/include/w32api" : "",
465             (
466 0           map { split ' ' }
467             grep $_,
468 0 0         @Config{qw/locincpth incpath/}
    0          
469             ),
470             "/usr/include",
471             "/usr/local/include",
472             _gcc_inc_paths(),
473             _dyn_inc_paths(),
474             );
475              
476 0 0         if ($req->{incsuffix}) {
477 0           @paths = map File::Spec->catdir($_, $req->{incsuffix}), @paths;
478             }
479              
480 0           return @paths;
481             }
482              
483             sub _gcc_inc_paths {
484             $Config{gccversion}
485 0 0   0     or return;
486              
487 0 0         my ($base_version) = $Config{gccversion} =~ /^([0-9]+)/
488             or return;
489              
490 0 0         $base_version >= 4
491             or return;
492              
493 0           local $ENV{LANG} = "C";
494 0           local $ENV{LC_ALL} = "C";
495 0           my $devnull = File::Spec->devnull;
496 0           my @spam = `$Config{cc} -E -v - <$devnull 2>&1`;
497             # output includes lines like:
498             # ...
499             # ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/include"
500             # #include "..." search starts here:
501             # #include <...> search starts here:
502             # /usr/lib/gcc/x86_64-linux-gnu/4.9/include
503             # /usr/local/include
504             # /usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed
505             # /usr/include/x86_64-linux-gnu
506             # /usr/include
507             # End of search list.
508             # # 1 ""
509             # # 1 ""
510             # ...
511              
512 0   0       while (@spam && $spam[0] !~ /^#include /) {
513 0           shift @spam;
514             }
515 0           my @inc;
516 0   0       while (@spam && $spam[0] !~ /^End of search/) {
517 0           my $line = shift @spam;
518 0           chomp $line;
519 0 0         next if $line =~ /^#include /;
520 0 0         next unless $line =~ s/^\s+//;
521 0           push @inc, $line;
522             }
523 0           return @inc;
524             }
525              
526             sub _dyn_inc_paths {
527             return map {
528 0     0     my $tmp = $_;
  0            
529 0 0         $tmp =~ s/\blib$/include/ ? $tmp : ()
530             } _dyn_lib_paths();
531             }
532              
533             sub _paths {
534 0     0     my (@in) = @_;
535              
536 0           my @out;
537              
538             # expand any array refs
539 0 0         @in = map { ref() ? @$_ : $_ } @in;
  0            
540              
541 0           for my $path (@in) {
542 0 0         $path or next;
543 0           $path = _tilde_expand($path);
544              
545 0           push @out, grep -d $_, split /\Q$Config{path_sep}/, $path;
546             }
547              
548 0           @out = map Cwd::realpath($_), @out;
549              
550 0           my %seen;
551 0           @out = grep !$seen{$_}++, @out;
552              
553 0           return @out;
554             }
555              
556             my $home;
557             sub _tilde_expand {
558 0     0     my ($path) = @_;
559              
560 0 0         if ($path =~ m!^~[/\\]!) {
561 0 0         defined $home or $home = $ENV{HOME};
562 0 0 0       if (!defined $home && $^O eq 'MSWin32'
      0        
      0        
563             && defined $ENV{HOMEDRIVE} && defined $ENV{HOMEPATH}) {
564 0           $home = $ENV{HOMEDRIVE} . $ENV{HOMEPATH};
565             }
566 0 0         unless (defined $home) {
567 0           $home = eval { (getpwuid($<))[7] };
  0            
568             }
569 0 0         defined $home or die "You supplied $path, but I can't find your home directory\n";
570 0           $path =~ s/^~//;
571 0           $path = File::Spec->catdir($home, $path);
572             }
573              
574 0           return $path;
575             }
576              
577             1;
578              
579             __END__