File Coverage

blib/lib/Module/Build/FFI.pm
Criterion Covered Total %
statement 34 153 22.2
branch 2 58 3.4
condition 0 23 0.0
subroutine 12 23 52.1
pod 3 8 37.5
total 51 265 19.2


line stmt bran cond sub pod time code
1             package Module::Build::FFI;
2              
3 4     4   239246 use strict;
  4         15  
  4         118  
4 4     4   20 use warnings;
  4         15  
  4         98  
5 4     4   2078 use ExtUtils::CBuilder;
  4         296916  
  4         171  
6 4     4   36 use File::Glob qw( bsd_glob );
  4         10  
  4         332  
7 4     4   29 use File::Spec ();
  4         8  
  4         143  
8 4     4   1862 use File::ShareDir::Dist qw( dist_share );
  4         3404  
  4         28  
9 4     4   290 use File::Path ();
  4         10  
  4         64  
10 4     4   21 use Text::ParseWords ();
  4         9  
  4         64  
11 4     4   19 use Config;
  4         9  
  4         145  
12 4     4   22 use base qw( Module::Build );
  4         16  
  4         2373  
13              
14             # ABSTRACT: (Deprecated) Build Perl extensions in C with FFI
15             our $VERSION = '0.53'; # VERSION
16              
17              
18             __PACKAGE__->add_property( ffi_libtest_dir =>
19             default => [ 'libtest' ],
20             );
21              
22             __PACKAGE__->add_property( ffi_include_dir =>
23             default => [ 'include' ],
24             );
25              
26             __PACKAGE__->add_property( ffi_libtest_optional =>
27             default => 1,
28             );
29              
30             __PACKAGE__->add_property( ffi_source_dir =>
31             default => [ 'ffi' ],
32             );
33              
34             sub _inflate ($)
35             {
36 0     0   0 my($self) = @_;
37 0 0       0 $self->ffi_source_dir([$self->ffi_source_dir])
38             unless ref $self->ffi_source_dir;
39              
40 0 0       0 $self->ffi_libtest_dir([$self->ffi_libtest_dir])
41             unless ref $self->ffi_libtest_dir;
42             }
43              
44             sub new
45             {
46 0     0 0 0 my($class, %args) = @_;
47              
48 0         0 my $self = $class->SUPER::new(%args);
49              
50 0         0 _inflate($self);
51 0         0 my $have_compiler = $self->ffi_have_compiler;
52              
53 0 0 0     0 if(-d $self->ffi_source_dir->[0] && !$have_compiler)
54             {
55 0         0 print STDERR "This distribution requires a compiler\n";
56 0         0 exit;
57             }
58              
59 0 0 0     0 if(-d $self->ffi_libtest_dir->[0] && !$self->ffi_libtest_optional && !$have_compiler)
      0        
60             {
61 0         0 print STDERR "This distribution requires a compiler\n";
62 0         0 exit;
63             }
64              
65 0         0 $self;
66             }
67              
68              
69             my @cpp_extensions = qw( c cpp cxx cc c++ );
70              
71             sub ffi_have_compiler
72             {
73 0     0 1 0 my($self) = @_;
74              
75 0         0 my $cpp = 0;
76              
77 0         0 foreach my $dir (@{ $self->ffi_source_dir }, @{ $self->ffi_libtest_dir })
  0         0  
  0         0  
78             {
79 0 0       0 next unless -d $dir;
80 0 0       0 $cpp = 1 if scalar map { bsd_glob("$dir/*.$_") } @cpp_extensions;
  0         0  
81             }
82              
83 0         0 my $cb = $self->cbuilder;
84 0 0 0     0 $cpp ? $cb->have_cplusplus && $cb->have_compiler : $cb->have_compiler;
85             }
86              
87              
88             sub _ffi_headers ($$)
89             {
90 0     0   0 my($self, $dir) = @_;
91              
92 0         0 my @headers;
93              
94 0         0 my @dirs = @$dir;
95 0 0       0 push @dirs, grep { -d $_ } ref $self->ffi_include_dir ? @{ $self->ffi_include_dir } : ($self->ffi_include_dir);
  0         0  
  0         0  
96              
97 0         0 push @headers, map { bsd_glob("$_/*.h") } @dirs;
  0         0  
98              
99 0         0 \@headers;
100             }
101              
102             sub _share_dir
103             {
104 1 50   1   5839 dist_share('Module-Build-FFI') or die "unable to find dist share directory";
105             }
106              
107             sub _ffi_include_dirs ($$)
108             {
109 0     0   0 my($self, $dir) = @_;
110              
111 0         0 my @includes = (@$dir);
112              
113 0 0       0 push @includes, grep { -d $_ } ref $self->ffi_include_dir ? @{ $self->ffi_include_dir } : ($self->ffi_include_dir);
  0         0  
  0         0  
114              
115 0   0     0 push @includes, $ENV{FFI_PLATYPUS_INCLUDE_DIR} || File::Spec->catdir(_share_dir, 'include');
116              
117 0 0       0 push @includes, ref($self->include_dirs) ? @{ $self->include_dirs } : $self->include_dirs
  0 0       0  
118             if defined $self->include_dirs;
119              
120 0         0 \@includes;
121             }
122              
123              
124             sub ffi_build_dynamic_lib ($$$;$)
125             {
126 0     0 1 0 my($self, $dirs, $name, $dest_dir) = @_;
127              
128 0   0     0 $dest_dir ||= $dirs->[0];
129              
130 0   0     0 my $header_time = do {
131             my @list = sort map { (stat $_)[9] } @{ _ffi_headers $self, $dirs };
132             pop @list;
133             } || 0;
134 0         0 my $compile_count = 0;
135 0         0 my $b = $self->cbuilder;
136              
137 0         0 my @obj;
138              
139 0         0 foreach my $dir (@$dirs)
140             {
141             push @obj, map {
142 0         0 my $filename = $_;
143 0         0 my($source_time) = reverse sort ((stat $filename)[9], $header_time);
144 0         0 my $obj_name = $b->object_file($filename);
145 0         0 $self->add_to_cleanup($obj_name);
146 0 0       0 my $obj_time = -e $obj_name ? ((stat $obj_name)[9]) : 0;
147              
148 0         0 my %compile_options = (
149             source => $filename,
150             include_dirs => _ffi_include_dirs($self, $dirs),
151             extra_compiler_flags => $self->extra_compiler_flags,
152             );
153 0 0       0 $compile_options{"C++"} = 1 if $filename =~ /\.(cpp|cxx|cc|\c\+\+)$/;
154              
155 0 0       0 if($obj_time < $source_time)
156             {
157 0         0 $b->compile(%compile_options);
158 0         0 $compile_count++;
159             }
160 0         0 $obj_name;
161 0         0 } sort map { bsd_glob("$dir/*.$_") } qw( c cpp cxx cc c++ s );
  0         0  
162             }
163              
164 0 0       0 return unless $compile_count > 0;
165              
166 0 0       0 if($^O ne 'MSWin32')
167             {
168 0         0 return $b->link(
169             lib_file => $b->lib_file(File::Spec->catfile($dest_dir, $b->object_file("$name.c"))),
170             objects => \@obj,
171             extra_linker_flags => $self->extra_linker_flags,
172             );
173             }
174             else
175             {
176             # On windows we can't depend on MM::CBuilder to make the .dll file because it creates dlls
177             # that export only one symbol (which is used for bootstrapping XS modules).
178 0         0 my $dll = File::Spec->catfile($dest_dir, "$name.dll");
179 0         0 $dll =~ s{\\}{/}g;
180 0         0 my @cmd;
181 0         0 my $cc = $Config{cc};
182 0 0       0 if($cc !~ /cl(.exe)?$/i)
183             {
184 0         0 my $lddlflags = $Config{lddlflags};
185 0         0 $lddlflags =~ s{\\}{/}g;
186 0         0 @cmd = ($cc, Text::ParseWords::shellwords($lddlflags), -o => $dll, "-Wl,--export-all-symbols", @obj);
187             }
188             else
189             {
190 0         0 @cmd = ($cc, @obj, '/link', '/dll', '/out:' . $dll);
191             }
192 0         0 print "@cmd\n";
193 0         0 system @cmd;
194 0 0       0 exit 2 if $?;
195 0         0 return $dll;
196             }
197             }
198              
199              
200             sub ffi_dlext
201             {
202 2 50   2 1 6214 if($^O eq 'darwin')
203 0         0 { ('bundle', 'dylib', 'so' ) }
204             else
205             {
206 2         31 require Config;
207 2         49 $Config::Config{dlext};
208             }
209             }
210              
211             sub _ffi_libtest_name ()
212             {
213 0 0   0     $^O eq 'cygwin' ? 'cygtest-1' : $^O eq 'msys' ? 'msys-test-1' : 'libtest';
    0          
214             }
215              
216             sub ACTION_libtest
217             {
218 0     0 0   my $self = shift;
219 0           _inflate($self);
220 0           my @dirs = @{ $self->ffi_libtest_dir };
  0            
221              
222 0 0         return unless -d $dirs[0];
223              
224 0           foreach my $dir (@dirs)
225             {
226 0           $self->add_to_cleanup(map { "$dir/$_" } qw(
  0            
227             *.o
228             *.obj
229             *.so
230             *.dll
231             *.bundle
232             ));
233             }
234              
235 0           my $have_compiler = $self->ffi_have_compiler;
236              
237 0 0         unless($have_compiler)
238             {
239 0           print STDERR "libtest directory is included, but not compiler is available\n";
240 0           print STDERR "some tests may fail if they depend on libtest\n";
241 0           return;
242             }
243              
244 0           $self->ffi_build_dynamic_lib(\@dirs, _ffi_libtest_name);
245             }
246              
247             sub ACTION_ffi
248             {
249 0     0 0   my $self = shift;
250 0           _inflate($self);
251 0           my @dirs = @{ $self->ffi_source_dir };
  0            
252              
253 0 0         return unless -d $dirs[0];
254              
255 0           foreach my $dir (@dirs)
256             {
257 0           $self->add_to_cleanup(map { "$dir/$_" } qw(
  0            
258             *.o
259             *.obj
260             *.so
261             *.dll
262             *.bundle
263             ));
264             }
265              
266 0 0         unless($self->ffi_have_compiler)
267             {
268 0           print STDERR "a compiler is required.\n";
269 0           exit 2;
270             }
271              
272 0 0         die "Can't determine module name" unless $self->module_name;
273 0           my @parts = split /::/, $self->module_name;
274              
275 0           my $arch_dir = File::Spec->catdir($self->blib, 'arch', 'auto', @parts);
276 0 0         File::Path::mkpath($arch_dir, 0, oct(777)) unless -d $arch_dir;
277              
278 0           my $name = $parts[-1];
279             # yes, of course Strawberry has to be "different"
280 0 0 0       if($^O eq 'MSWin32' && $Config{dlext} eq 'xs.dll')
281             {
282 0           $name = "$name.xs";
283             }
284              
285 0           $self->ffi_build_dynamic_lib(\@dirs, $name, $arch_dir);
286             }
287              
288             sub ACTION_build
289             {
290 0     0 0   my $self = shift;
291 0           $self->depends_on('ffi');
292 0           $self->SUPER::ACTION_build(@_);
293             }
294              
295             sub ACTION_test
296             {
297 0     0 0   my $self = shift;
298 0           $self->depends_on('libtest');
299 0           $self->SUPER::ACTION_test(@_);
300             }
301              
302             1;
303              
304             __END__