add test code for the JPEG probe
[imager.git] / inc / Devel / CheckLib.pm
1 # $Id: CheckLib.pm,v 1.25 2008/10/27 12:16:23 drhyde Exp $
2
3 package #
4 Devel::CheckLib;
5
6 use strict;
7 use vars qw($VERSION @ISA @EXPORT);
8 $VERSION = '0.699_002';
9 use Config;
10
11 use File::Spec;
12 use File::Temp;
13
14 require Exporter;
15 @ISA = qw(Exporter);
16 @EXPORT = qw(assert_lib check_lib_or_exit check_lib);
17
18 # localising prevents the warningness leaking out of this module
19 local $^W = 1;    # use warnings is a 5.6-ism
20
21 _findcc(); # bomb out early if there's no compiler
22
23 =head1 NAME
24
25 Devel::CheckLib - check that a library is available
26
27 =head1 DESCRIPTION
28
29 Devel::CheckLib is a perl module that checks whether a particular C
30 library and its headers are available.
31
32 =head1 SYNOPSIS
33
34     use Devel::CheckLib;
35
36     check_lib_or_exit( lib => 'jpeg', header => 'jpeglib.h' );
37     check_lib_or_exit( lib => [ 'iconv', 'jpeg' ] );
38   
39     # or prompt for path to library and then do this:
40     check_lib_or_exit( lib => 'jpeg', libpath => $additional_path );
41
42 =head1 USING IT IN Makefile.PL or Build.PL
43
44 If you want to use this from Makefile.PL or Build.PL, do
45 not simply copy the module into your distribution as this may cause
46 problems when PAUSE and search.cpan.org index the distro.  Instead, use
47 the use-devel-checklib script.
48
49 =head1 HOW IT WORKS
50
51 You pass named parameters to a function, describing to it how to build
52 and link to the libraries.
53
54 It works by trying to compile some code - which defaults to this:
55
56     int main(void) { return 0; }
57
58 and linking it to the specified libraries.  If something pops out the end
59 which looks executable, it gets executed, and if main() returns 0 we know
60 that it worked.  That tiny program is
61 built once for each library that you specify, and (without linking) once
62 for each header file.
63
64 If you want to check for the presence of particular functions in a
65 library, or even that those functions return particular results, then
66 you can pass your own function body for main() thus:
67
68     check_lib_or_exit(
69         function => 'foo();if(libversion() > 5) return 0; else return 1;'
70         incpath  => ...
71         libpath  => ...
72         lib      => ...
73         header   => ...
74     );
75
76 In that case, it will fail to build if either foo() or libversion() don't
77 exist, and main() will return the wrong value if libversion()'s return
78 value isn't what you want.
79
80 =head1 FUNCTIONS
81
82 All of these take the same named parameters and are exported by default.
83 To avoid exporting them, C<use Devel::CheckLib ()>.
84
85 =head2 assert_lib
86
87 This takes several named parameters, all of which are optional, and dies
88 with an error message if any of the libraries listed can
89 not be found.  B<Note>: dying in a Makefile.PL or Build.PL may provoke
90 a 'FAIL' report from CPAN Testers' automated smoke testers.  Use 
91 C<check_lib_or_exit> instead.
92
93 The named parameters are:
94
95 =over
96
97 =item lib
98
99 Must be either a string with the name of a single 
100 library or a reference to an array of strings of library names.  Depending
101 on the compiler found, library names will be fed to the compiler either as
102 C<-l> arguments or as C<.lib> file names.  (E.g. C<-ljpeg> or C<jpeg.lib>)
103
104 =item libpath
105
106 a string or an array of strings
107 representing additional paths to search for libraries.
108
109 =item LIBS
110
111 a C<ExtUtils::MakeMaker>-style space-seperated list of
112 libraries (each preceded by '-l') and directories (preceded by '-L').
113
114 This can also be supplied on the command-line.
115
116 =back
117
118 And libraries are no use without header files, so ...
119
120 =over
121
122 =item header
123
124 Must be either a string with the name of a single 
125 header file or a reference to an array of strings of header file names.
126
127 =item incpath
128
129 a string or an array of strings
130 representing additional paths to search for headers.
131
132 =item INC
133
134 a C<ExtUtils::MakeMaker>-style space-seperated list of
135 incpaths, each preceded by '-I'.
136
137 This can also be supplied on the command-line.
138
139 =back
140
141 =head2 check_lib_or_exit
142
143 This behaves exactly the same as C<assert_lib()> except that instead of
144 dieing, it warns (with exactly the same error message) and exits.
145 This is intended for use in Makefile.PL / Build.PL
146 when you might want to prompt the user for various paths and
147 things before checking that what they've told you is sane.
148
149 If any library or header is missing, it exits with an exit value of 0 to avoid
150 causing a CPAN Testers 'FAIL' report.  CPAN Testers should ignore this
151 result -- which is what you want if an external library dependency is not
152 available.
153
154 =head2 check_lib
155
156 This behaves exactly the same as C<assert_lib()> except that it is silent,
157 returning false instead of dieing, or true otherwise.
158
159 =cut
160
161 sub check_lib_or_exit {
162     eval 'assert_lib(@_)';
163     if($@) {
164         warn $@;
165         exit;
166     }
167 }
168
169 sub check_lib {
170     eval 'assert_lib(@_)';
171     return $@ ? 0 : 1;
172 }
173
174 sub assert_lib {
175     my %args = @_;
176     my (@libs, @libpaths, @headers, @incpaths);
177
178     # FIXME: these four just SCREAM "refactor" at me
179     @libs = (ref($args{lib}) ? @{$args{lib}} : $args{lib}) 
180         if $args{lib};
181     @libpaths = (ref($args{libpath}) ? @{$args{libpath}} : $args{libpath}) 
182         if $args{libpath};
183     @headers = (ref($args{header}) ? @{$args{header}} : $args{header}) 
184         if $args{header};
185     @incpaths = (ref($args{incpath}) ? @{$args{incpath}} : $args{incpath}) 
186         if $args{incpath};
187
188     # each entry is an arrayref containing:
189     # 0: arrayref of library search paths
190     # 1: arrayref of library names
191     my @link_cfgs = map [ \@libpaths, [ $_ ] ], @libs;
192
193     # work-a-like for Makefile.PL's LIBS and INC arguments
194     # if given as command-line argument, append to %args
195     for my $arg (@ARGV) {
196         for my $mm_attr_key qw(LIBS INC) {
197             if (my ($mm_attr_value) = $arg =~ /\A $mm_attr_key = (.*)/x) {
198             # it is tempting to put some \s* into the expression, but the
199             # MM command-line parser only accepts LIBS etc. followed by =,
200             # so we should not be any more lenient with whitespace than that
201                 $args{$mm_attr_key} .= " $mm_attr_value";
202             }
203         }
204     }
205     # using special form of split to trim whitespace
206     if(defined($args{LIBS})) {
207         if (ref $args{LIBS}) {
208             foreach my $arg (@{$args{LIBS}}) {
209                 my @sep = split ' ', $arg;
210                 my @libs = map { /^-l(.+)$/ ? $1 : () } @sep;
211                 my @paths = map { /^-L(.+)$/ ? $1 : () } @sep;
212                 push @link_cfgs, [ \@paths, \@libs ];
213             }
214         }
215         else {
216             my @libs;
217             my @paths;
218             foreach my $arg (split(' ', $args{LIBS})) {
219                 die("LIBS argument badly-formed: $arg\n") unless($arg =~ /^-l/i);
220                 push @{$arg =~ /^-l/ ? \@libs : \@paths}, substr($arg, 2);
221             }
222             push @link_cfgs, map [ \@paths, [ $_ ] ], @libs;
223         }
224     }
225     if(defined($args{INC})) {
226         foreach my $arg (split(' ', $args{INC})) {
227             die("INC argument badly-formed: $arg\n") unless($arg =~ /^-I/);
228             push @incpaths, substr($arg, 2);
229         }
230     }
231
232     my @cc = _findcc();
233     my @missing;
234     my @wrongresult;
235
236     # first figure out which headers we can't find ...
237     my @use_headers;
238     for my $header (@headers) {
239         push @use_headers, $header;
240         my($ch, $cfile) = File::Temp::tempfile(
241             'assertlibXXXXXXXX', SUFFIX => '.c'
242         );
243         print $ch qq{#include <$_>\n} for @use_headers;
244         print $ch qq{int main(void) { return 0; }\n};
245         close($ch);
246         my $exefile = File::Temp::mktemp( 'assertlibXXXXXXXX' ) . $Config{_exe};
247         my @sys_cmd;
248         # FIXME: re-factor - almost identical code later when linking
249         if ( $Config{cc} eq 'cl' ) {                 # Microsoft compiler
250             require Win32;
251             @sys_cmd = (
252                 @cc,
253                 $cfile,
254                 "/Fe$exefile",
255                 (map { '/I'.Win32::GetShortPathName($_) } @incpaths)
256             );
257         } elsif($Config{cc} =~ /bcc32(\.exe)?/) {    # Borland
258             @sys_cmd = (
259                 @cc,
260                 (map { "-I$_" } @incpaths),
261                 "-o$exefile",
262                 $cfile
263             );
264         } else { # Unix-ish: gcc, Sun, AIX (gcc, cc), ...
265             @sys_cmd = (
266                 @cc,
267                 $cfile,
268                 (map { "-I$_" } @incpaths),
269                 "-o", "$exefile"
270             );
271         }
272         warn "# @sys_cmd\n" if $args{debug};
273         my $rv = $args{debug} ? system(@sys_cmd) : _quiet_system(@sys_cmd);
274         push @missing, $header if $rv != 0 || ! -x $exefile; 
275         _cleanup_exe($exefile);
276         unlink $cfile;
277     } 
278
279     # now do each library in turn with headers
280     my($ch, $cfile) = File::Temp::tempfile(
281         'assertlibXXXXXXXX', SUFFIX => '.c'
282     );
283     print $ch qq{#include <$_>\n} foreach (@headers);
284     print $ch "int main(void) { ".($args{function} || 'return 0;')." }\n";
285     close($ch);
286     for my $link_cfg ( @link_cfgs ) {
287         my ($paths, $libs) = @$link_cfg;
288         my $exefile = File::Temp::mktemp( 'assertlibXXXXXXXX' ) . $Config{_exe};
289         my @sys_cmd;
290         if ( $Config{cc} eq 'cl' ) {                 # Microsoft compiler
291             require Win32;
292             my @libpath = map { 
293                 q{/libpath:} . Win32::GetShortPathName($_)
294             } @libpaths; 
295             # this is horribly sensitive to the order of arguments
296             @sys_cmd = (
297                 @cc,
298                 $cfile,
299                 ( map { "$_.lib" } @$libs ),
300                 "/Fe$exefile", 
301                 (map { '/I'.Win32::GetShortPathName($_) } @incpaths),
302                 "/link",
303                 (map {'/libpath:'.Win32::GetShortPathName($_)} @$paths),
304             );
305         } elsif($Config{cc} eq 'CC/DECC') {          # VMS
306         } elsif($Config{cc} =~ /bcc32(\.exe)?/) {    # Borland
307             @sys_cmd = (
308                 @cc,
309                 "-o$exefile",
310                 (map { "-l$_" } @$libs ),
311                 (map { "-I$_" } @incpaths),
312                 (map { "-L$_" } @$paths),
313                 $cfile);
314         } else {                                     # Unix-ish
315                                                      # gcc, Sun, AIX (gcc, cc)
316             @sys_cmd = (
317                 @cc,
318                 $cfile,
319                 "-o", "$exefile",
320                 (map { "-l$_" } @$libs ),
321                 (map { "-I$_" } @incpaths),
322                 (map { "-L$_" } @$paths)
323             );
324         }
325         warn "# @sys_cmd\n" if $args{debug};
326         my $rv = $args{debug} ? system(@sys_cmd) : _quiet_system(@sys_cmd);
327         push @missing, @$libs if $rv != 0 || ! -x $exefile;
328         push @wrongresult, @$libs if $rv == 0 && -x $exefile && system(File::Spec->rel2abs($exefile)) != 0; 
329         _cleanup_exe($exefile);
330     } 
331     unlink $cfile;
332
333     my $miss_string = join( q{, }, map { qq{'$_'} } @missing );
334     die("Can't link/include $miss_string\n") if @missing;
335     my $wrong_string = join( q{, }, map { qq{'$_'} } @wrongresult);
336     die("wrong result: $wrong_string\n") if @wrongresult;
337 }
338
339 sub _cleanup_exe {
340     my ($exefile) = @_;
341     my $ofile = $exefile;
342     $ofile =~ s/$Config{_exe}$/$Config{_o}/;
343     unlink $exefile if -f $exefile;
344     unlink $ofile if -f $ofile;
345     unlink "$exefile\.manifest" if -f "$exefile\.manifest";
346     return
347 }
348     
349 sub _findcc {
350     my @paths = split(/$Config{path_sep}/, $ENV{PATH});
351     my @cc = split(/\s+/, $Config{cc});
352     return @cc if -x $cc[0];
353     foreach my $path (@paths) {
354         my $compiler = File::Spec->catfile($path, $cc[0]) . $Config{_exe};
355         return ($compiler, @cc[1 .. $#cc]) if -x $compiler;
356     }
357     die("Couldn't find your C compiler\n");
358 }
359
360 # code substantially borrowed from IPC::Run3
361 sub _quiet_system {
362     my (@cmd) = @_;
363
364     # save handles
365     local *STDOUT_SAVE;
366     local *STDERR_SAVE;
367     open STDOUT_SAVE, ">&STDOUT" or die "CheckLib: $! saving STDOUT";
368     open STDERR_SAVE, ">&STDERR" or die "CheckLib: $! saving STDERR";
369     
370     # redirect to nowhere
371     local *DEV_NULL;
372     open DEV_NULL, ">" . File::Spec->devnull 
373         or die "CheckLib: $! opening handle to null device";
374     open STDOUT, ">&" . fileno DEV_NULL
375         or die "CheckLib: $! redirecting STDOUT to null handle";
376     open STDERR, ">&" . fileno DEV_NULL
377         or die "CheckLib: $! redirecting STDERR to null handle";
378
379     # run system command
380     my $rv = system(@cmd);
381
382     # restore handles
383     open STDOUT, ">&" . fileno STDOUT_SAVE
384         or die "CheckLib: $! restoring STDOUT handle";
385     open STDERR, ">&" . fileno STDERR_SAVE
386         or die "CheckLib: $! restoring STDERR handle";
387
388     return $rv;
389 }
390
391 =head1 PLATFORMS SUPPORTED
392
393 You must have a C compiler installed.  We check for C<$Config{cc}>,
394 both literally as it is in Config.pm and also in the $PATH.
395
396 It has been tested with varying degrees on rigourousness on:
397
398 =over
399
400 =item gcc (on Linux, *BSD, Mac OS X, Solaris, Cygwin)
401
402 =item Sun's compiler tools on Solaris
403
404 =item IBM's tools on AIX
405
406 =item SGI's tools on Irix 6.5
407
408 =item Microsoft's tools on Windows
409
410 =item MinGW on Windows (with Strawberry Perl)
411
412 =item Borland's tools on Windows
413
414 =item QNX
415
416 =back
417
418 =head1 WARNINGS, BUGS and FEEDBACK
419
420 This is a very early release intended primarily for feedback from
421 people who have discussed it.  The interface may change and it has
422 not been adequately tested.
423
424 Feedback is most welcome, including constructive criticism.
425 Bug reports should be made using L<http://rt.cpan.org/> or by email.
426
427 When submitting a bug report, please include the output from running:
428
429     perl -V
430     perl -MDevel::CheckLib -e0
431
432 =head1 SEE ALSO
433
434 L<Devel::CheckOS>
435
436 L<Probe::Perl>
437
438 =head1 AUTHORS
439
440 David Cantrell E<lt>david@cantrell.org.ukE<gt>
441
442 David Golden E<lt>dagolden@cpan.orgE<gt>
443
444 Thanks to the cpan-testers-discuss mailing list for prompting us to write it
445 in the first place;
446
447 to Chris Williams for help with Borland support.
448
449 =head1 COPYRIGHT and LICENCE
450
451 Copyright 2007 David Cantrell. Portions copyright 2007 David Golden.
452
453 This module is free-as-in-speech software, and may be used, distributed,
454 and modified under the same conditions as perl itself.
455
456 =head1 CONSPIRACY
457
458 This module is also free-as-in-mason software.
459
460 =cut
461
462 1;