merge PNG branch and some clean-up
[imager.git] / lib / Imager / Probe.pm
CommitLineData
1d7e3124
TC
1package Imager::Probe;
2use strict;
3use File::Spec;
4use Config;
5
6sub probe {
7 my ($class, $req) = @_;
8
9 $req->{verbose} ||= $ENV{IM_VERBOSE};
10
11 my $name = $req->{name};
12 my $result;
13 if ($req->{code}) {
14 $result = _probe_code($req);
15 }
16 if (!$result && $req->{pkg}) {
17 $result = _probe_pkg($req);
18 }
19 if (!$result && $req->{inccheck} && ($req->{libcheck} || $req->{libbase})) {
20 $result = _probe_check($req);
21 }
22
23 if (!$result && $req->{testcode}) {
24 $result = _probe_fake($req);
25 }
26 $result or return;
27
28 if ($req->{testcode}) {
29 $result = _probe_test($req, $result);
30 }
31
32 $result or return;
33
34 return $result;
35}
36
37sub _probe_code {
38 my ($req) = @_;
39
40 my $code = $req->{code};
41 my @probes = ref $code eq "ARRAY" ? @$code : $code;
42
43 my $result;
44 for my $probe (@probes) {
45 $result = $probe->($req)
46 and return $result;
47 }
48
49 return;
50}
51
52sub is_exe {
53 my ($name) = @_;
54
55 my @exe_suffix = $Config{_exe};
56 if ($^O eq 'MSWin32') {
57 push @exe_suffix, qw/.bat .cmd/;
58 }
59
60 for my $dir (File::Spec->path) {
61 for my $suffix (@exe_suffix) {
62 -x File::Spec->catfile($dir, "$name$suffix")
63 and return 1;
64 }
65 }
66
67 return;
68}
69
70sub _probe_pkg {
71 my ($req) = @_;
72
73 is_exe('pkg-config') or return;
74 my $redir = $^O eq 'MSWin32' ? '' : '2>/dev/null';
75
76 my @pkgs = @{$req->{pkg}};
77 for my $pkg (@pkgs) {
78 if (!system("pkg-config $pkg --exists $redir")) {
79 # if we find it, but the following fail, then pkg-config is too
80 # broken to be useful
81 my $cflags = `pkg-config $pkg --cflags`
82 and !$? or return;
83
84 my $lflags = `pkg-config $pkg --libs`
85 and !$? or return;
86
87 chomp $cflags;
88 chomp $lflags;
89 print "$req->{name}: Found via pkg-config $pkg\n";
90 return
91 {
92 INC => $cflags,
93 LIBS => $lflags,
94 };
95 }
96 }
97
98 print "$req->{name}: Not found via pkg-config\n";
99
100 return;
101}
102
103sub _probe_check {
104 my ($req) = @_;
105
106 my $libcheck = $req->{libcheck};
107 my $libbase = $req->{libbase};
108 if (!$libcheck && $req->{libbase}) {
109 # synthesize a libcheck
110 my $lext=$Config{'so'}; # Get extensions of libraries
111 my $aext=$Config{'_a'};
112 $libcheck = sub {
113 -e File::Spec->catfile($_[0], "lib$libbase$aext")
114 || -e File::Spec->catfile($_[0], "lib$libbase.$lext")
115 };
116 }
117
118 my $found_libpath;
119 my @lib_search = _lib_paths($req);
120 print "$req->{name}: Searching directories for libraries:\n"
121 if $req->{verbose};
122 for my $path (@lib_search) {
123 print "$req->{name}: $path\n" if $req->{verbose};
124 if ($libcheck->($path)) {
125 print "$req->{name}: Found!\n" if $req->{verbose};
126 $found_libpath = $path;
127 last;
128 }
129 }
130
131 my $found_incpath;
132 my $inccheck = $req->{inccheck};
133 my @inc_search = _inc_paths($req);
134 print "$req->{name}: Searching directories for headers:\n"
135 if $req->{verbose};
136 for my $path (@inc_search) {
137 print "$req->{name}: $path\n" if $req->{verbose};
138 if ($inccheck->($path)) {
139 print "$req->{name}: Found!\n" if $req->{verbose};
140 $found_incpath = $path;
141 last;
142 }
143 }
144
145 print "$req->{name}: includes ", $found_incpath ? "" : "not ",
146 "found - libraries ", $found_libpath ? "" : "not ", "found\n";
147
148 $found_libpath && $found_incpath
149 or return;
150
151 my @libs = "-L$found_libpath";
152 if ($req->{libopts}) {
153 push @libs, $req->{libopts};
154 }
155 elsif ($libbase) {
156 push @libs, "-l$libbase";
157 }
158 else {
159 die "$req->{name}: inccheck but no libbase or libopts";
160 }
161
162 return
163 {
164 INC => "-I$found_incpath",
165 LIBS => "@libs",
166 };
167}
168
169sub _probe_fake {
170 my ($req) = @_;
171
172 # the caller provided test code, and the compiler may look in
173 # places we don't, see Imager-Screenshot ticket 56793,
174 # so fake up a result so the test code can
175 my $lopts;
176 if ($req->{libopts}) {
177 $lopts = $req->{libopts};
178 }
179 elsif (defined $req->{libbase}) {
180 # might not need extra libraries, eg. Win32 perl already links
181 # everything
182 $lopts = $req->{libbase} ? "-l$req->{libbase}" : "";
183 }
184 if (defined $lopts) {
185 print "$req->{name}: Checking if the compiler can find them on it's own\n";
186 return
187 {
188 INC => "",
189 LIBS => $lopts,
190 };
191 }
192 else {
193 print "$req->{name}: Can't fake it - no libbase or libopts\n"
194 if $req->{verbose};
195 return;
196 }
197}
198
199sub _probe_test {
200 my ($req, $result) = @_;
201
202 require Devel::CheckLib;
203 # setup LD_RUN_PATH to match link time
204 my ($extra, $bs_load, $ld_load, $ld_run_path) =
205 ExtUtils::Liblist->ext($req->{LIBS}, $req->{verbose});
206 local $ENV{LD_RUN_PATH};
207
208 if ($ld_run_path) {
209 print "Setting LD_RUN_PATH=$ld_run_path for TIFF probe\n"
210 if $req->{verbose};
211 $ENV{LD_RUN_PATH} = $ld_run_path;
212 }
213 my $good =
214 Devel::CheckLib::check_lib
215 (
216 debug => $req->{verbose},
217 LIBS => $result->{LIBS},
218 INC => $result->{INC},
219 header => $req->{testcodeheaders},
220 function => $req->{testcode},
221 );
222 unless ($good) {
223 print "$req->{name}: Test code failed checklib probe: $@\n"
224 if $req->{verbose};
225 return;
226 }
227
228 print "$req->{name}: Passed code check\n";
229 return $result;
230}
231
232sub _lib_paths {
233 my ($req) = @_;
234
235 return _paths
236 (
237 $ENV{IM_LIBPATH},
238 $req->{libpath},
239 (
240 map { split ' ' }
241 grep $_,
242 @Config{qw/loclibpath libpth libspath/}
243 ),
244 $^O eq "MSWin32" ? $ENV{LIB} : "",
245 $^O eq "cygwin" ? "/usr/lib/w32api" : "",
246 );
247}
248
249sub _inc_paths {
250 my ($req) = @_;
251
252 return _paths
253 (
254 $ENV{IM_INCPATH},
255 $req->{incpath},
256 $^O eq "MSWin32" ? $ENV{INCLUDE} : "",
257 $^O eq "cygwin" ? "/usr/include/w32api" : "",
258 (
259 map { split ' ' }
260 grep $_,
261 @Config{qw/locincpath incpath/}
262 ),
263 "/usr/include",
264 "/usr/local/include",
265 );
266}
267
268sub _paths {
269 my (@in) = @_;
270
271 my @out;
272
273 for my $path (@in) {
274 $path or next;
275 $path = _tilde_expand($path);
276
277 push @out, grep -d $_, split /\Q$Config{path_sep}/, $path;
278 }
279
280 return @out;
281}
282
283my $home;
284sub _tilde_expand {
285 my ($path) = @_;
286
287 if ($path =~ m!^~[/\\]!) {
288 defined $home or $home = $ENV{HOME};
289 if (!defined $home && $^O eq 'MSWin32'
290 && defined $ENV{HOMEDRIVE} && defined $ENV{HOMEPATH}) {
291 $home = $ENV{HOMEDRIVE} . $ENV{HOMEPATH};
292 }
293 unless (defined $home) {
294 $home = eval { (getpwuid($<))[7] };
295 }
296 defined $home or die "You supplied $path, but I can't find your home directory\n";
297 $path =~ s/^~//;
298 $path = File::Spec->catdir($home, $path);
299 }
300
301 return $path;
302}
303
3041;
305
306__END__
307
308=head1 NAME
309
310Imager::Probe - hot needle of inquiry for libraries
311
312=head1 SYNOPSIS
313
314 require Imager::Probe;
315
316 my %probe =
317 (
318 # short name of what we're looking for (displayed to user)
319 name => "FOO",
320 # pkg-config lookup
321 pkg => [ qw/name1 name2 name3/ ],
322 # perl subs that probe for the library
323 code => [ \&foo_probe1, \&foo_probe2 ],
324 # or just: code => \&foo_probe,
325 inccheck => sub { ... },
326 libcheck => sub { ... },
327 # search for this library if libcheck not supplied
328 libbase => "foo",
329 # library link time options, uses libbase to build options otherwise
330 libopts => "-lfoo",
331 # C code to check the library is sane
332 testcode => "...",
333 # header files needed
334 testcodeheaders => [ "stdio.h", "foo.h" ],
335 );
336 my $result = Imager::Probe->probe(\%probe)
337 or print "Foo library not found: ",Imager::Probe->error;
338
339=head1 DESCRIPTION
340
341Does the probes that were hidden in Imager's F<Makefile.PL>, pulled
342out so the file format libraries can be externalized.
343
344The return value is either nothing if the probe fails, or a hash
345containing:
346
347=over
348
349=item *
350
351C<INC> - C<-I> and other C options
352
353=item *
354
355C<LIBS> - C<-L>, C<-l> and other link-time options
356
357=back
358
359The possible values for the hash supplied to the probe() method are:
360
361=over
362
363=item *
364
365C<pkg> - an array of F<pkg-config> names to probe for. If the
366F<pkg-config> checks pass, C<inccheck> and C<libcheck> aren't used.
367
368=item *
369
370C<inccheck> - a code reference that checks if the supplied include
371directory contains the required header files.
372
373=item *
374
375C<libcheck> - a code reference that checks if the supplied library
376directory contains the required library files. Note: the
377F<Makefile.PL> version of this was supplied all of the library file
378names instead.
379
380=item *
381
382C<libbase> - if C<inccheck> is supplied, but C<libcheck> isn't, then a
383C<libcheck> that checks for C<lib>I<libbase>I<$Config{_a}> and
384C<lib>I<libbase>.I<$Config{so}> is created. If C<libopts> isn't
385supplied then that can be synthesized as C<-l>C<<I<libbase>>>.
386
387=item *
388
389C<libopts> - if the libraries are found via C<inccheck>/C<libcheck>,
390these are the C<-l> options to supply during the link phase.
391
392=item *
393
394C<code> - a code reference to perform custom checks. Returns the
395probe result directly. Can also be an array ref of functions to call.
396
397=item *
398
399C<testcode> - test C code that is run with Devel::CheckLib. You also
400need to set C<testcodeheaders>.
401
402=item *
403
404C<incpath> - C<$Config{path_sep}> separated list of header file
405directories to check.
406
407=item *
408
409C<libpath> - C<$Config{path_sep}> separated list of library file
410directories to check.
411
412=back
413
414=cut