]> git.imager.perl.org - imager.git/blob - PNG/inc/Devel/CheckLib.pm
57cc8e6dc3d7cfba6f1913d2e1601b76dcb17dba
[imager.git] / PNG / 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 If you need to perform know more than "does it link?" you can provide
142 code to be compiled and run:
143
144 =over
145
146 =item function
147
148 the body of the <main()> function.  If not provided C<return 0;> is
149 used.
150
151 =item prologue
152
153 code to insert between the C<#include> of the headers and the
154 definition of main.
155
156 =back
157
158 =head2 check_lib_or_exit
159
160 This behaves exactly the same as C<assert_lib()> except that instead of
161 dieing, it warns (with exactly the same error message) and exits.
162 This is intended for use in Makefile.PL / Build.PL
163 when you might want to prompt the user for various paths and
164 things before checking that what they've told you is sane.
165
166 If any library or header is missing, it exits with an exit value of 0 to avoid
167 causing a CPAN Testers 'FAIL' report.  CPAN Testers should ignore this
168 result -- which is what you want if an external library dependency is not
169 available.
170
171 =head2 check_lib
172
173 This behaves exactly the same as C<assert_lib()> except that it is silent,
174 returning false instead of dieing, or true otherwise.
175
176 =cut
177
178 sub check_lib_or_exit {
179     eval 'assert_lib(@_)';
180     if($@) {
181         warn $@;
182         exit;
183     }
184 }
185
186 sub check_lib {
187     eval 'assert_lib(@_)';
188     return $@ ? 0 : 1;
189 }
190
191 sub assert_lib {
192     my %args = @_;
193     my (@libs, @libpaths, @headers, @incpaths);
194
195     # FIXME: these four just SCREAM "refactor" at me
196     @libs = (ref($args{lib}) ? @{$args{lib}} : $args{lib}) 
197         if $args{lib};
198     @libpaths = (ref($args{libpath}) ? @{$args{libpath}} : $args{libpath}) 
199         if $args{libpath};
200     @headers = (ref($args{header}) ? @{$args{header}} : $args{header}) 
201         if $args{header};
202     @incpaths = (ref($args{incpath}) ? @{$args{incpath}} : $args{incpath}) 
203         if $args{incpath};
204
205     # each entry is an arrayref containing:
206     # 0: arrayref of library search paths
207     # 1: arrayref of library names
208     my @link_cfgs = map [ \@libpaths, [ $_ ] ], @libs;
209
210     # work-a-like for Makefile.PL's LIBS and INC arguments
211     # if given as command-line argument, append to %args
212     for my $arg (@ARGV) {
213         for my $mm_attr_key qw(LIBS INC) {
214             if (my ($mm_attr_value) = $arg =~ /\A $mm_attr_key = (.*)/x) {
215             # it is tempting to put some \s* into the expression, but the
216             # MM command-line parser only accepts LIBS etc. followed by =,
217             # so we should not be any more lenient with whitespace than that
218                 $args{$mm_attr_key} .= " $mm_attr_value";
219             }
220         }
221     }
222     # using special form of split to trim whitespace
223     if(defined($args{LIBS})) {
224         if (ref $args{LIBS}) {
225             foreach my $arg (@{$args{LIBS}}) {
226                 my @sep = split ' ', $arg;
227                 my @libs = map { /^-l(.+)$/ ? $1 : () } @sep;
228                 my @paths = map { /^-L(.+)$/ ? $1 : () } @sep;
229                 push @link_cfgs, [ \@paths, \@libs ];
230             }
231         }
232         else {
233             my @libs;
234             my @paths;
235             foreach my $arg (split(' ', $args{LIBS})) {
236                 die("LIBS argument badly-formed: $arg\n") unless($arg =~ /^-l/i);
237                 push @{$arg =~ /^-l/ ? \@libs : \@paths}, substr($arg, 2);
238             }
239             push @link_cfgs, map [ \@paths, [ $_ ] ], @libs;
240         }
241     }
242     if(defined($args{INC})) {
243         foreach my $arg (split(' ', $args{INC})) {
244             die("INC argument badly-formed: $arg\n") unless($arg =~ /^-I/);
245             push @incpaths, substr($arg, 2);
246         }
247     }
248
249     my @cc = _findcc();
250     my @missing;
251     my @wrongresult;
252
253     # first figure out which headers we can't find ...
254     my @use_headers;
255     for my $header (@headers) {
256         push @use_headers, $header;
257         my($ch, $cfile) = File::Temp::tempfile(
258             'assertlibXXXXXXXX', SUFFIX => '.c'
259         );
260         print $ch qq{#include <$_>\n} for @use_headers;
261         print $ch qq{int main(void) { return 0; }\n};
262         close($ch);
263         my $exefile = File::Temp::mktemp( 'assertlibXXXXXXXX' ) . $Config{_exe};
264         my @sys_cmd;
265         # FIXME: re-factor - almost identical code later when linking
266         if ( $Config{cc} eq 'cl' ) {                 # Microsoft compiler
267             require Win32;
268             @sys_cmd = (
269                 @cc,
270                 $cfile,
271                 "/Fe$exefile",
272                 (map { '/I'.Win32::GetShortPathName($_) } @incpaths)
273             );
274         } elsif($Config{cc} =~ /bcc32(\.exe)?/) {    # Borland
275             @sys_cmd = (
276                 @cc,
277                 (map { "-I$_" } @incpaths),
278                 "-o$exefile",
279                 $cfile
280             );
281         } else { # Unix-ish: gcc, Sun, AIX (gcc, cc), ...
282             @sys_cmd = (
283                 @cc,
284                 $cfile,
285                 (map { "-I$_" } @incpaths),
286                 "-o", "$exefile"
287             );
288         }
289         warn "# @sys_cmd\n" if $args{debug};
290         my $rv = $args{debug} ? system(@sys_cmd) : _quiet_system(@sys_cmd);
291         push @missing, $header if $rv != 0 || ! -x $exefile; 
292         _cleanup_exe($exefile);
293         unlink $cfile;
294     } 
295
296     # now do each library in turn with headers
297     my($ch, $cfile) = File::Temp::tempfile(
298         'assertlibXXXXXXXX', SUFFIX => '.c'
299     );
300     print $ch qq{#include <$_>\n} foreach (@headers);
301     print $ch "\n$args{prologue}\n" if $args{prologue};
302     print $ch "int main(void) { ".($args{function} || 'return 0;')." }\n";
303     close($ch);
304     for my $link_cfg ( @link_cfgs ) {
305         my ($paths, $libs) = @$link_cfg;
306         my $exefile = File::Temp::mktemp( 'assertlibXXXXXXXX' ) . $Config{_exe};
307         my @sys_cmd;
308         if ( $Config{cc} eq 'cl' ) {                 # Microsoft compiler
309             require Win32;
310             my @libpath = map { 
311                 q{/libpath:} . Win32::GetShortPathName($_)
312             } @libpaths; 
313             # this is horribly sensitive to the order of arguments
314             @sys_cmd = (
315                 @cc,
316                 $cfile,
317                 ( map { "$_.lib" } @$libs ),
318                 "/Fe$exefile", 
319                 (map { '/I'.Win32::GetShortPathName($_) } @incpaths),
320                 "/link",
321                 (map {'/libpath:'.Win32::GetShortPathName($_)} @$paths),
322             );
323         } elsif($Config{cc} eq 'CC/DECC') {          # VMS
324         } elsif($Config{cc} =~ /bcc32(\.exe)?/) {    # Borland
325             @sys_cmd = (
326                 @cc,
327                 "-o$exefile",
328                 (map { "-l$_" } @$libs ),
329                 (map { "-I$_" } @incpaths),
330                 (map { "-L$_" } @$paths),
331                 $cfile);
332         } else {                                     # Unix-ish
333                                                      # gcc, Sun, AIX (gcc, cc)
334             @sys_cmd = (
335                 @cc,
336                 $cfile,
337                 "-o", "$exefile",
338                 (map { "-l$_" } @$libs ),
339                 (map { "-I$_" } @incpaths),
340                 (map { "-L$_" } @$paths)
341             );
342         }
343         warn "# @sys_cmd\n" if $args{debug};
344         my $rv = $args{debug} ? system(@sys_cmd) : _quiet_system(@sys_cmd);
345         push @missing, @$libs if $rv != 0 || ! -x $exefile;
346         push @wrongresult, @$libs if $rv == 0 && -x $exefile && system(File::Spec->rel2abs($exefile)) != 0; 
347         _cleanup_exe($exefile);
348     } 
349     unlink $cfile;
350
351     my $miss_string = join( q{, }, map { qq{'$_'} } @missing );
352     die("Can't link/include $miss_string\n") if @missing;
353     my $wrong_string = join( q{, }, map { qq{'$_'} } @wrongresult);
354     die("wrong result: $wrong_string\n") if @wrongresult;
355 }
356
357 sub _cleanup_exe {
358     my ($exefile) = @_;
359     my $ofile = $exefile;
360     $ofile =~ s/$Config{_exe}$/$Config{_o}/;
361     unlink $exefile if -f $exefile;
362     unlink $ofile if -f $ofile;
363     unlink "$exefile\.manifest" if -f "$exefile\.manifest";
364     return
365 }
366     
367 sub _findcc {
368     my @paths = split(/$Config{path_sep}/, $ENV{PATH});
369     my @cc = split(/\s+/, $Config{cc});
370     return @cc if -x $cc[0];
371     foreach my $path (@paths) {
372         my $compiler = File::Spec->catfile($path, $cc[0]) . $Config{_exe};
373         return ($compiler, @cc[1 .. $#cc]) if -x $compiler;
374     }
375     die("Couldn't find your C compiler\n");
376 }
377
378 # code substantially borrowed from IPC::Run3
379 sub _quiet_system {
380     my (@cmd) = @_;
381
382     # save handles
383     local *STDOUT_SAVE;
384     local *STDERR_SAVE;
385     open STDOUT_SAVE, ">&STDOUT" or die "CheckLib: $! saving STDOUT";
386     open STDERR_SAVE, ">&STDERR" or die "CheckLib: $! saving STDERR";
387     
388     # redirect to nowhere
389     local *DEV_NULL;
390     open DEV_NULL, ">" . File::Spec->devnull 
391         or die "CheckLib: $! opening handle to null device";
392     open STDOUT, ">&" . fileno DEV_NULL
393         or die "CheckLib: $! redirecting STDOUT to null handle";
394     open STDERR, ">&" . fileno DEV_NULL
395         or die "CheckLib: $! redirecting STDERR to null handle";
396
397     # run system command
398     my $rv = system(@cmd);
399
400     # restore handles
401     open STDOUT, ">&" . fileno STDOUT_SAVE
402         or die "CheckLib: $! restoring STDOUT handle";
403     open STDERR, ">&" . fileno STDERR_SAVE
404         or die "CheckLib: $! restoring STDERR handle";
405
406     return $rv;
407 }
408
409 =head1 PLATFORMS SUPPORTED
410
411 You must have a C compiler installed.  We check for C<$Config{cc}>,
412 both literally as it is in Config.pm and also in the $PATH.
413
414 It has been tested with varying degrees on rigourousness on:
415
416 =over
417
418 =item gcc (on Linux, *BSD, Mac OS X, Solaris, Cygwin)
419
420 =item Sun's compiler tools on Solaris
421
422 =item IBM's tools on AIX
423
424 =item SGI's tools on Irix 6.5
425
426 =item Microsoft's tools on Windows
427
428 =item MinGW on Windows (with Strawberry Perl)
429
430 =item Borland's tools on Windows
431
432 =item QNX
433
434 =back
435
436 =head1 WARNINGS, BUGS and FEEDBACK
437
438 This is a very early release intended primarily for feedback from
439 people who have discussed it.  The interface may change and it has
440 not been adequately tested.
441
442 Feedback is most welcome, including constructive criticism.
443 Bug reports should be made using L<http://rt.cpan.org/> or by email.
444
445 When submitting a bug report, please include the output from running:
446
447     perl -V
448     perl -MDevel::CheckLib -e0
449
450 =head1 SEE ALSO
451
452 L<Devel::CheckOS>
453
454 L<Probe::Perl>
455
456 =head1 AUTHORS
457
458 David Cantrell E<lt>david@cantrell.org.ukE<gt>
459
460 David Golden E<lt>dagolden@cpan.orgE<gt>
461
462 Thanks to the cpan-testers-discuss mailing list for prompting us to write it
463 in the first place;
464
465 to Chris Williams for help with Borland support.
466
467 =head1 COPYRIGHT and LICENCE
468
469 Copyright 2007 David Cantrell. Portions copyright 2007 David Golden.
470
471 This module is free-as-in-speech software, and may be used, distributed,
472 and modified under the same conditions as perl itself.
473
474 =head1 CONSPIRACY
475
476 This module is also free-as-in-mason software.
477
478 =cut
479
480 1;