]> git.imager.perl.org - imager.git/blobdiff - lib/Imager/Probe.pm
1.009 release
[imager.git] / lib / Imager / Probe.pm
index dd8e8f65e09adf286532f964d3da62d53c12c569..ac6d49bee37f5df53690539474071a728afdef0a 100644 (file)
@@ -2,6 +2,9 @@ package Imager::Probe;
 use strict;
 use File::Spec;
 use Config;
+use Cwd ();
+
+our $VERSION = "1.006";
 
 my @alt_transfer = qw/altname incsuffix libbase/;
 
@@ -22,6 +25,11 @@ sub probe {
     $req->{altname} ||= "main";
     $result = _probe_check($req);
   }
+
+  if ($result && $req->{testcode}) {
+    $result = _probe_test($req, $result);
+  }
+
   if (!$result && $req->{alternatives}) {
   ALTCHECK:
     my $index = 1;
@@ -33,18 +41,24 @@ sub probe {
       for my $key (@alt_transfer) {
        exists $alt->{$key} and $work{$key} = $alt->{$key};
       }
-      $result = _probe_check(\%work)
+      $result = _probe_check(\%work);
+
+      if ($result && $req->{testcode}) {
+       $result = _probe_test(\%work, $result);
+      }
+
+      $result
        and last;
+
       ++$index;
     }
   }
 
   if (!$result && $req->{testcode}) {
     $result = _probe_fake($req);
-  }
-  $result or return;
 
-  if ($req->{testcode}) {
+    $result or return;
+
     $result = _probe_test($req, $result);
   }
 
@@ -116,6 +130,26 @@ sub _probe_pkg {
       chomp $cflags;
       chomp $lflags;
       print "$req->{name}: Found via pkg-config $pkg\n";
+      print <<EOS if $req->{verbose};
+  cflags: $cflags
+  defines: $defines
+  lflags: $lflags
+EOS
+      # rt 75869
+      # if Win32 doesn't provide this information, too bad
+      if (!grep(/^-L/, split " ", $lflags)
+         && $^O ne 'MSWin32') {
+       # pkg-config told us about the library, make sure it's
+       # somewhere EU::MM can find it
+       print "Checking if EU::MM can find $lflags\n" if $req->{verbose};
+       my ($extra, $bs_load, $ld_load, $ld_run_path) =
+         ExtUtils::Liblist->ext($lflags, $req->{verbose});
+       unless ($ld_run_path) {
+         # search our standard places
+         $lflags = _resolve_libs($req, $lflags);
+       }
+      }
+
       return
        {
         INC => $cflags,
@@ -130,31 +164,83 @@ sub _probe_pkg {
   return;
 }
 
+sub _is_msvc {
+  return $Config{cc} eq "cl";
+}
+
+sub _lib_basename {
+  my ($base) = @_;
+
+  if (_is_msvc()) {
+    return $base;
+  }
+  else {
+    return "lib$base";
+  }
+}
+
+sub _lib_option {
+  my ($base) = @_;
+
+  if (_is_msvc()) {
+    return $base . $Config{_a};
+  }
+  else {
+    return "-l$base";
+  }
+}
+
+sub _quotearg {
+  my ($opt) = @_;
+
+  return $opt =~ /\s/ ? qq("$opt") : $opt;
+}
+
 sub _probe_check {
   my ($req) = @_;
 
-  my $libcheck = $req->{libcheck};
-  my $libbase = $req->{libbase};
-  if (!$libcheck && $req->{libbase}) {
-    # synthesize a libcheck
+  my @libcheck;
+  my @libbase;
+  if ($req->{libcheck}) {
+    if (ref $req->{libcheck} eq "ARRAY") {
+      push @libcheck, @{$req->{libcheck}};
+    }
+    else {
+      push @libcheck, $req->{libcheck};
+    }
+  }
+  elsif ($req->{libbase}) {
+    @libbase = ref $req->{libbase} ? @{$req->{libbase}} : $req->{libbase};
+
     my $lext=$Config{'so'};   # Get extensions of libraries
     my $aext=$Config{'_a'};
-    $libcheck = sub {
-      -e File::Spec->catfile($_[0], "lib$libbase$aext")
-       || -e File::Spec->catfile($_[0], "lib$libbase.$lext")
-      };
+
+    for my $libbase (@libbase) {
+      my $basename = _lib_basename($libbase);
+      push @libcheck, sub {
+       -e File::Spec->catfile($_[0], "$basename$aext")
+         || -e File::Spec->catfile($_[0], "$basename.$lext")
+       };
+    }
+  }
+  else {
+    print "$req->{name}: No libcheck or libbase, nothing to search for\n"
+      if $req->{verbose};
+    return;
   }
 
-  my $found_libpath;
+  my @found_libpath;
   my @lib_search = _lib_paths($req);
   print "$req->{name}: Searching directories for libraries:\n"
     if $req->{verbose};
-  for my $path (@lib_search) {
-    print "$req->{name}:   $path\n" if $req->{verbose};
-    if ($libcheck->($path)) {
-      print "$req->{name}: Found!\n" if $req->{verbose};
-      $found_libpath = $path;
-      last;
+  for my $libcheck (@libcheck) {
+    for my $path (@lib_search) {
+      print "$req->{name}:   $path\n" if $req->{verbose};
+      if ($libcheck->($path)) {
+       print "$req->{name}: Found!\n" if $req->{verbose};
+        push @found_libpath, $path;
+       last;
+      }
     }
   }
 
@@ -177,17 +263,17 @@ sub _probe_check {
     $alt = " $req->{altname}:";
   }
   print "$req->{name}:$alt includes ", $found_incpath ? "" : "not ",
-    "found - libraries ", $found_libpath ? "" : "not ", "found\n";
+    "found - libraries ", @found_libpath == @libcheck ? "" : "not ", "found\n";
 
-  $found_libpath && $found_incpath
+  @found_libpath == @libcheck && $found_incpath
     or return;
 
-  my @libs = "-L$found_libpath";
+  my @libs = map "-L$_", @found_libpath;
   if ($req->{libopts}) {
     push @libs, $req->{libopts};
   }
-  elsif ($libbase) {
-    push @libs, "-l$libbase";
+  elsif (@libbase) {
+    push @libs, map _lib_option($_), @libbase;
   }
   else {
     die "$req->{altname}: inccheck but no libbase or libopts";
@@ -195,8 +281,8 @@ sub _probe_check {
 
   return
     {
-     INC => "-I$found_incpath",
-     LIBS => "@libs",
+     INC => _quotearg("-I$found_incpath"),
+     LIBS => join(" ", map _quotearg($_), @libs),
      DEFINE => "",
     };
 }
@@ -214,7 +300,10 @@ sub _probe_fake {
   elsif (defined $req->{libbase}) {
     # might not need extra libraries, eg. Win32 perl already links
     # everything
-    $lopts = $req->{libbase} ? "-l$req->{libbase}" : "";
+    my @libs = $req->{libbase}
+      ? ( ref $req->{libbase} ? @{$req->{libbase}} : $req->{libbase} )
+      : ();
+    $lopts = join " ", map _lib_option($_), @libs;
   }
   if (defined $lopts) {
     print "$req->{name}: Checking if the compiler can find them on its own\n";
@@ -237,6 +326,7 @@ sub _probe_test {
 
   require Devel::CheckLib;
   # setup LD_RUN_PATH to match link time
+  print "Asking liblist for LD_RUN_PATH:\n" if $req->{verbose};
   my ($extra, $bs_load, $ld_load, $ld_run_path) =
     ExtUtils::Liblist->ext($result->{LIBS}, $req->{verbose});
   local $ENV{LD_RUN_PATH};
@@ -245,6 +335,15 @@ sub _probe_test {
     print "Setting LD_RUN_PATH=$ld_run_path for $req->{name} probe\n"
       if $req->{verbose};
     $ENV{LD_RUN_PATH} = $ld_run_path;
+    if ($Config{lddlflags} =~ /([^ ]*-(?:rpath|R)[,=]?)([^ ]+)/
+       && -d $2) {
+      # hackety, hackety
+      # LD_RUN_PATH is ignored when there's already an -rpath option
+      # so provide one
+      my $prefix = $1;
+      $result->{LDDLFLAGS} = $Config{lddlflags} . " " .
+       join " ", map "$prefix$_", split $Config{path_sep}, $ld_run_path;
+    }
   }
   my $good =
     Devel::CheckLib::check_lib
@@ -265,9 +364,38 @@ sub _probe_test {
   return $result;
 }
 
+sub _resolve_libs {
+  my ($req, $lflags) = @_;
+
+  my @libs = grep /^-l/, split ' ', $lflags;
+  my %paths;
+  my @paths = _lib_paths($req);
+  my $so = $Config{so};
+  my $libext = $Config{_a};
+  for my $lib (@libs) {
+    $lib =~ s/^-l/lib/;
+
+    for my $path (@paths) {
+      if (-e "$path/$lib.$so" || -e "$path/$lib$libext") {
+       $paths{$path} = 1;
+      }
+    }
+  }
+
+  return join(" ", ( map "-L$_", keys %paths ), $lflags );
+}
+
 sub _lib_paths {
   my ($req) = @_;
 
+  print "$req->{name} IM_LIBPATH: $ENV{IM_LIBPATH}\n"
+    if $req->{verbose} && defined $ENV{IM_LIBPATH};
+  print "$req->{name} LIB: $ENV{IM_LIBPATH}\n"
+    if $req->{verbose} && defined $ENV{LIB} && $^O eq "MSWin32";
+  my $lp = $req->{libpath};
+  print "$req->{name} libpath: ", ref $lp ? join($Config{path_sep}, @$lp) : $lp, "\n"
+    if $req->{verbose} && defined $lp;
+
   return _paths
     (
      $ENV{IM_LIBPATH},
@@ -281,12 +409,48 @@ sub _lib_paths {
      $^O eq "cygwin" ? "/usr/lib/w32api" : "",
      "/usr/lib",
      "/usr/local/lib",
+     _gcc_lib_paths(),
+     _dyn_lib_paths(),
     );
 }
 
+sub _gcc_lib_paths {
+  $Config{gccversion}
+    or return;
+
+  my ($base_version) = $Config{gccversion} =~ /^([0-9]+)/
+    or return;
+
+  $base_version >= 4
+    or return;
+
+  local $ENV{LANG} = "C";
+  local $ENV{LC_ALL} = "C";
+  my ($lib_line) = grep /^libraries:/, `$Config{cc} -print-search-dirs`
+    or return;
+  $lib_line =~ s/^libraries: =//;
+  chomp $lib_line;
+
+  return grep !/gcc/ && -d, split /:/, $lib_line;
+}
+
+sub _dyn_lib_paths {
+  return map { defined() ? split /\Q$Config{path_sep}/ : () }
+    map $ENV{$_},
+      qw(LD_RUN_PATH LD_LIBRARY_PATH DYLD_LIBRARY_PATH LIBRARY_PATH);
+}
+
 sub _inc_paths {
   my ($req) = @_;
 
+  print "$req->{name} IM_INCPATH: $ENV{IM_INCPATH}\n"
+    if $req->{verbose} && defined $ENV{IM_INCPATH};
+  print "$req->{name} INCLUDE: $ENV{INCLUDE}\n"
+    if $req->{verbose} && defined $ENV{INCLUDE} && $^O eq "MSWin32";
+  my $ip = $req->{incpath};
+  print "$req->{name} incpath: ", ref $ip ? join($Config{path_sep}, @$ip) : $ip, "\n"
+    if $req->{verbose} && defined $req->{incpath};
+
   my @paths = _paths
     (
      $ENV{IM_INCPATH},
@@ -300,6 +464,8 @@ sub _inc_paths {
      ),
      "/usr/include",
      "/usr/local/include",
+     _gcc_inc_paths(),
+     _dyn_inc_paths(),
     );
 
   if ($req->{incsuffix}) {
@@ -309,6 +475,56 @@ sub _inc_paths {
   return @paths;
 }
 
+sub _gcc_inc_paths {
+  $Config{gccversion}
+    or return;
+
+  my ($base_version) = $Config{gccversion} =~ /^([0-9]+)/
+    or return;
+
+  $base_version >= 4
+    or return;
+
+  local $ENV{LANG} = "C";
+  local $ENV{LC_ALL} = "C";
+  my $devnull = File::Spec->devnull;
+  my @spam = `$Config{cc} -E -v - <$devnull 2>&1`;
+  # output includes lines like:
+  # ...
+  # ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/include"
+  # #include "..." search starts here:
+  # #include <...> search starts here:
+  #  /usr/lib/gcc/x86_64-linux-gnu/4.9/include
+  #  /usr/local/include
+  #  /usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed
+  #  /usr/include/x86_64-linux-gnu
+  #  /usr/include
+  # End of search list.
+  # # 1 "<stdin>"
+  # # 1 "<built-in>"
+  # ...
+
+  while (@spam && $spam[0] !~ /^#include /) {
+    shift @spam;
+  }
+  my @inc;
+  while (@spam && $spam[0] !~ /^End of search/) {
+    my $line = shift @spam;
+    chomp $line;
+    next if $line =~ /^#include /;
+    next unless $line =~ s/^\s+//;
+    push @inc, $line;
+  }
+  return @inc;
+}
+
+sub _dyn_inc_paths {
+  return map {
+    my $tmp = $_;
+    $tmp =~ s/\blib$/include/ ? $tmp : ()
+  } _dyn_lib_paths();
+}
+
 sub _paths {
   my (@in) = @_;
 
@@ -324,6 +540,11 @@ sub _paths {
     push @out, grep -d $_, split /\Q$Config{path_sep}/, $path;
   }
 
+  @out = map Cwd::realpath($_), @out;
+
+  my %seen;
+  @out = grep !$seen{$_}++, @out;
+
   return @out;
 }
 
@@ -426,14 +647,19 @@ directory contains the required header files.
 C<libcheck> - a code reference that checks if the supplied library
 directory contains the required library files.  Note: the
 F<Makefile.PL> version of this was supplied all of the library file
-names instead.
+names instead.  C<libcheck> can also be an arrayref of library check
+code references, all of which must find a match for the library to be
+considered "found".
 
 =item *
 
 C<libbase> - if C<inccheck> is supplied, but C<libcheck> isn't, then a
 C<libcheck> that checks for C<lib>I<libbase>I<$Config{_a}> and
 C<lib>I<libbase>.I<$Config{so}> is created.  If C<libopts> isn't
-supplied then that can be synthesized as C<-l>C<<I<libbase>>>.
+supplied then that can be synthesized as C<< -lI<libbase>
+>>. C<libbase> can also be an arrayref of library base names to search
+for, in which case all of the libraries mentioned must be found for
+the probe to succeed.
 
 =item *
 
@@ -465,6 +691,18 @@ directories to check, or a reference to an array of such.
 C<libpath> - C<$Config{path_sep}> separated list of library file
 directories to check, or a reference to an array of such.
 
+=item *
+
+C<alternatives> - an optional array reference of alternate
+configurations (as hash references) to test if the primary
+configuration isn't successful.  Each alternative should include an
+C<altname> key describing the alternative.  Any key not mentioned in
+an alternative defaults to the value from the main configuration.
+
 =back
 
+=head1 AUTHOR
+
+Tony Cook <tonyc@cpan.org>, Arnar M. Hrafnkelsson
+
 =cut