1# $Id: CheckLib.pm,v 1.1 2008/02/24 10:17:48 richdawe Exp $
2
3package Devel::CheckLib;
4
5use strict;
6use vars qw($VERSION @ISA @EXPORT);
7$VERSION = '0.3';
8use Config;
9
10use File::Spec;
11use File::Temp;
12
13require Exporter;
14@ISA = qw(Exporter);
15@EXPORT = qw(assert_lib check_lib_or_exit);
16
17# localising prevents the warningness leaking out of this module
18local $^W = 1;    # use warnings is a 5.6-ism
19
20_findcc(); # bomb out early if there's no compiler
21
22=head1 NAME
23
24Devel::CheckLib - check that a library is available
25
26=head1 DESCRIPTION
27
28Devel::CheckLib is a perl module that checks whether a particular C
29library is available, and dies if it is not.
30
31=head1 SYNOPSIS
32
33    # in a Makefile.PL or Build.PL
34    use lib qw(inc);
35    use Devel::CheckLib;
36
37    check_lib_or_exit( lib => 'jpeg' );
38    check_lib_or_exit( lib => [ 'iconv', 'jpeg' ] );
39
40    # or prompt for path to library and then do this:
41    check_lib_or_exit( lib => 'jpeg', libpath => $additional_path );
42
43=head1 HOW IT WORKS
44
45You pass named parameters to a function
46describing how to build and link to the library.  Currently the only
47parameter supported is 'lib', which can be a string or an arrayref of
48several libraries.  In the future, expect us to add something for
49checking that header files are available as well.
50
51It works by trying to compile this:
52
53    int main(void) { return 0; }
54
55and linking it to the specified libraries.  If something pops out the end
56which looks executable, then we know that it worked.
57
58=head1 FUNCTIONS
59
60All of these take the same named parameters and are exported by default.
61To avoid exporting them, C<use Devel::CheckLib ()>.
62
63=head2 assert_lib
64
65Takes several named parameters.
66
67The value of C<lib> must be either a string with the name of a single
68library or a reference to an array of strings of library names.  Depending
69on the compiler found, library names will be fed to the compiler either as
70C<-l> arguments or as C<.lib> file names.  (E.g. C<-ljpeg> or C<jpeg.lib>)
71
72Likewise, C<libpath> must if provided either be a string or an array of strings
73representing additional paths to search for libraries.
74
75C<LIBS> must be a C<ExtUtils::MakeMaker>-style space-seperated list of
76libraries (each preceded by '-l') and directories (preceded by '-L').
77
78This will die with an error message if any of the libraries listed can
79not be found.  B<Note>: dying in a Makefile.PL or Build.PL may provoke
80a 'FAIL' report from CPAN Testers' automated smoke testers.  Use
81C<check_lib_or_exit> instead.
82
83=head2 check_lib_or_exit
84
85This behaves exactly the same as C<assert_lib()> except that instead of
86dieing, it warns (with exactly the same error message) and exits.
87This is intended for use in Makefile.PL / Build.PL
88when you might want to prompt the user for various paths and
89things before checking that what they've told you is sane.
90
91If a library isn't found, it exits with an exit value of 0 to avoid
92causing a CPAN Testers 'FAIL' report.  CPAN Testers should ignore this
93result -- which is what you want if an external library dependency is not
94available.
95
96=cut
97
98sub check_lib_or_exit {
99    eval 'assert_lib(@_)';
100    if($@) {
101        warn $@;
102        exit;
103    }
104}
105
106sub assert_lib {
107    my %args = @_;
108    my (@libs, @libpaths);
109
110    @libs = (ref($args{lib}) ? @{$args{lib}} : $args{lib})
111        if $args{lib};
112    @libpaths = (ref($args{libpath}) ? @{$args{libpath}} : $args{libpath})
113        if $args{libpath};
114
115    # work-a-like for Makefile.PL's "LIBS" argument
116    if(defined($args{LIBS})) {
117        foreach my $arg (split(/\s+/, $args{LIBS})) {
118            die("LIBS argument badly-formed: $arg\n") unless($arg =~ /^-l/i);
119            push @{$arg =~ /^-l/ ? \@libs : \@libpaths}, substr($arg, 2);
120        }
121    }
122
123    my @cc = _findcc();
124    my($ch, $cfile) = File::Temp::tempfile(
125        'assertlibXXXXXXXX', SUFFIX => '.c', UNLINK => 1
126    );
127    print $ch "int main(void) { return 0; }\n";
128    close($ch);
129
130    my @missing;
131    for my $lib ( @libs ) {
132        my $exefile = File::Temp::mktemp( 'assertlibXXXXXXXX' ) . $Config{_exe};
133        my @sys_cmd;
134        if ( $Config{cc} eq 'cl' ) {                 # Microsoft compiler
135            require Win32;
136            my @libpath = map {
137                q{/libpath:} . Win32::GetShortPathName($_)
138            } @libpaths;
139            @sys_cmd = (@cc, $cfile, "${lib}.lib", "/Fe$exefile",
140                        "/link", @libpath
141            );
142        } elsif($Config{cc} =~ /bcc32(\.exe)?/) {    # Borland
143            my @libpath = map { "-L$_" } @libpaths;
144            @sys_cmd = (@cc, "-o$exefile", "-l$lib", @libpath, $cfile);
145        } else {                                     # Unix-ish
146                                                     # gcc, Sun, AIX (gcc, cc)
147            my @libpath = map { "-L$_" } @libpaths;
148            @sys_cmd = (@cc, $cfile,  "-o", "$exefile", "-l$lib", @libpath);
149        }
150        warn "# @sys_cmd\n" if $args{debug};
151        my $rv = $args{debug} ? system(@sys_cmd) : _quiet_system(@sys_cmd);
152        push @missing, $lib if $rv != 0 || ! -x $exefile;
153        _cleanup_exe($exefile);
154    }
155
156    unlink $cfile;
157    my $miss_string = join( q{, }, map { qq{'$_'} } @missing );
158    die("Can't build and link to $miss_string\n") if @missing;
159}
160
161sub _cleanup_exe {
162    my ($exefile) = @_;
163    my $ofile = $exefile;
164    $ofile =~ s/$Config{_exe}$/$Config{_o}/;
165    unlink $exefile if -f $exefile;
166    unlink $ofile if -f $ofile;
167    unlink "$exefile\.manifest" if -f "$exefile\.manifest";
168    return
169}
170
171sub _findcc {
172    my @paths = split(/$Config{path_sep}/, $ENV{PATH});
173    my @cc = split(/\s+/, $Config{cc});
174    return @cc if -x $cc[0];
175    foreach my $path (@paths) {
176        my $compiler = File::Spec->catfile($path, $cc[0]) . $Config{_exe};
177        return ($compiler, @cc[1 .. $#cc]) if -x $compiler;
178    }
179    die("Couldn't find your C compiler\n");
180}
181
182# code substantially borrowed from IPC::Run3
183sub _quiet_system {
184    my (@cmd) = @_;
185
186    # save handles
187    local *STDOUT_SAVE;
188    local *STDERR_SAVE;
189    open STDOUT_SAVE, ">&STDOUT" or die "CheckLib: $! saving STDOUT";
190    open STDERR_SAVE, ">&STDERR" or die "CheckLib: $! saving STDERR";
191
192    # redirect to nowhere
193    local *DEV_NULL;
194    open DEV_NULL, ">" . File::Spec->devnull
195        or die "CheckLib: $! opening handle to null device";
196    open STDOUT, ">&" . fileno DEV_NULL
197        or die "CheckLib: $! redirecting STDOUT to null handle";
198    open STDERR, ">&" . fileno DEV_NULL
199        or die "CheckLib: $! redirecting STDERR to null handle";
200
201    # run system command
202    my $rv = system(@cmd);
203
204    # restore handles
205    open STDOUT, ">&" . fileno STDOUT_SAVE
206        or die "CheckLib: $! restoring STDOUT handle";
207    open STDERR, ">&" . fileno STDERR_SAVE
208        or die "CheckLib: $! restoring STDERR handle";
209
210    return $rv;
211}
212
213=head1 PLATFORMS SUPPORTED
214
215You must have a C compiler installed.  We check for C<$Config{cc}>,
216both literally as it is in Config.pm and also in the $PATH.
217
218It has been tested with varying degrees on rigourousness on:
219
220=over
221
222=item gcc (on Linux, *BSD, Solaris, Cygwin)
223
224=item Sun's compiler tools on Solaris
225
226=item IBM's tools on AIX
227
228=item Microsoft's tools on Windows
229
230=item MinGW on Windows (with Strawberry Perl)
231
232=item Borland's tools on Windows
233
234=back
235
236=head1 WARNINGS, BUGS and FEEDBACK
237
238This is a very early release intended primarily for feedback from
239people who have discussed it.  The interface may change and it has
240not been adequately tested.
241
242Feedback is most welcome, including constructive criticism.
243Bug reports should be made using L<http://rt.cpan.org/> or by email.
244
245When submitting a bug report, please include the output from running:
246
247    perl -V
248    perl -MDevel::CheckLib
249
250=head1 SEE ALSO
251
252L<Devel::CheckOS>
253
254=head1 AUTHORS
255
256David Cantrell E<lt>david@cantrell.org.ukE<gt>
257
258David Golden E<lt>dagolden@cpan.orgE<gt>
259
260Thanks to the cpan-testers-discuss mailing list for prompting us to write it
261in the first place;
262
263to Chris Williams for help with Borland support.
264
265=head1 COPYRIGHT and LICENCE
266
267Copyright 2007 David Cantrell. Portions copyright 2007 David Golden.
268
269This module is free-as-in-speech software, and may be used, distributed,
270and modified under the same conditions as perl itself.
271
272=head1 CONSPIRACY
273
274This module is also free-as-in-mason software.
275
276=cut
277
2781;
279