File Coverage

blib/lib/FFI/Build.pm
Criterion Covered Total %
statement 164 202 81.1
branch 39 62 62.9
condition 11 18 61.1
subroutine 32 32 100.0
pod 14 14 100.0
total 260 328 79.2


line stmt bran cond sub pod time code
1             package FFI::Build;
2              
3 8     8   230115 use strict;
  8         24  
  8         231  
4 8     8   41 use warnings;
  8         18  
  8         197  
5 8     8   131 use 5.008004;
  8         29  
6 8     8   3499 use FFI::Build::Plugin;
  8         20  
  8         286  
7 8     8   2776 use FFI::Build::PluginData qw( plugin_data );
  8         22  
  8         438  
8 8     8   3513 use FFI::Build::File::Library;
  8         22  
  8         217  
9 8     8   62 use Carp ();
  8         36  
  8         114  
10 8     8   43 use File::Glob ();
  8         23  
  8         102  
11 8     8   35 use File::Basename ();
  8         18  
  8         162  
12 8     8   39 use List::Util 1.45 ();
  8         168  
  8         133  
13 8     8   39 use Capture::Tiny ();
  8         16  
  8         112  
14 8     8   42 use File::Path ();
  8         28  
  8         19448  
15              
16             # ABSTRACT: Build shared libraries for use with FFI
17             our $VERSION = '2.08'; # VERSION
18              
19             # Platypus-Man,
20             # Platypus-Man,
21             # Friendly Neighborhood Platypus-Man
22             # Is He Strong?
23             # Listen Bud
24             # He's got Proportional Strength of a Platypus
25             # Hey Man!
26             # There Goes The Platypus-Man
27              
28              
29             {
30             my $plugins = FFI::Build::Plugin->new;
31             # PLUGIN: require
32             # ARGS: NONE
33             $plugins->call('build-require');
34 199   33 199   2716 sub _plugins { $plugins ||= FFI::Build::Plugin->new };
35             }
36              
37             sub import
38             {
39 9     9   95 my @caller = caller;
40             # PLUGIN: import
41             # ARGS: @caller, \@args
42 9         34 _plugins->call('build-import', \@caller, \@_);
43             }
44              
45             sub _native_name
46             {
47 38     38   358 my($self, $name) = @_;
48 38         146 join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
49             }
50              
51             sub new
52             {
53 38     38 1 22491 my($class, $name, %args) = @_;
54              
55 38 50       228 Carp::croak "name is required" unless defined $name;
56              
57             # PLUGIN: new-pre
58             # ARGS: $name, \%args
59 38         200 _plugins->call('build-new-pre', $name, \%args);
60              
61 38         473 my $self = bless {
62             source => [],
63             cflags_I => [],
64             cflags => [],
65             libs_L => [],
66             libs => [],
67             alien => [],
68             }, $class;
69              
70 38   66     633 my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
71 38   33     636 my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
72 38   100     321 my $buildname = $self->{buildname} = $args{buildname} || '_build';
73 38   100     190 my $verbose = $self->{verbose} = $args{verbose} || 0;
74 38   100     236 my $export = $self->{export} = $args{export} || [];
75              
76 38 50       174 $self->{verbose} = $verbose = 2 if $ENV{V};
77              
78 38 100       132 if(defined $args{cflags})
79             {
80 2 50       18 my @flags = ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
  0         0  
81 2         12 push @{ $self->{cflags} }, grep !/^-I/, @flags;
  2         27  
82 2         8 push @{ $self->{cflags_I} }, grep /^-I/, @flags;
  2         25  
83             }
84              
85 38 50       135 if(defined $args{libs})
86             {
87 0 0       0 my @flags = ref $args{libs} ? @{ $args{libs} } : $self->platform->shellwords($args{libs});
  0         0  
88 0         0 push @{ $self->{libs} }, grep !/^-L/, @flags;
  0         0  
89 0         0 push @{ $self->{libs_L} }, grep /^-L/, @flags;
  0         0  
90             }
91              
92 38 100       140 if(defined $args{alien})
93             {
94 8 50       30 my @aliens = ref $args{alien} ? @{ $args{alien} } : ($args{alien});
  8         44  
95 8         33 foreach my $alien (@aliens)
96             {
97 0 0       0 unless(eval { $alien->can('cflags') && $alien->can('libs') })
  0 0       0  
98             {
99 0         0 my $pm = "$alien.pm";
100 0         0 $pm =~ s/::/\//g;
101 0         0 require $pm;
102             }
103 0         0 push @{ $self->{alien} }, $alien;
  0         0  
104 0         0 push @{ $self->{cflags} }, grep !/^-I/, $self->platform->shellwords($alien->cflags);
  0         0  
105 0         0 push @{ $self->{cflags_I} }, grep /^-I/, $self->platform->shellwords($alien->cflags);
  0         0  
106 0         0 push @{ $self->{libs} }, grep !/^-L/, $self->platform->shellwords($alien->libs);
  0         0  
107 0         0 push @{ $self->{libs_L} }, grep /^-L/, $self->platform->shellwords($alien->libs);
  0         0  
108             }
109             }
110              
111 38 100       182 $self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
  21 100       102  
112              
113             # PLUGIN: new-post
114             # ARGS: $self
115 38         101 _plugins->call('build-new-post', $self);
116              
117 38         251 $self;
118             }
119              
120              
121 51     51 1 5036 sub buildname { shift->{buildname} }
122 19     19 1 250 sub export { shift->{export} }
123 120     120 1 3176 sub file { shift->{file} }
124 290     290 1 2299 sub platform { shift->{platform} }
125 64     64 1 811 sub verbose { shift->{verbose} }
126 32     32 1 160 sub cflags { shift->{cflags} }
127 30     30 1 132 sub cflags_I { shift->{cflags_I} }
128 19     19 1 129 sub libs { shift->{libs} }
129 19     19 1 271 sub libs_L { shift->{libs_L} }
130 9     9 1 35 sub alien { shift->{alien} }
131              
132             my @file_classes;
133             sub _file_classes
134             {
135 74 100   74   2897 unless(@file_classes)
136             {
137 5 50       145 if(defined $FFI::Build::VERSION)
138             {
139 5         35 foreach my $inc (@INC)
140             {
141             push @file_classes,
142 44         88 map { my $f = $_; $f =~ s/\.pm$//; "FFI::Build::File::$f" }
  44         139  
  44         176  
143             grep !/^Base\.pm$/,
144 53         2688 map { File::Basename::basename($_) }
  55         1854  
145             File::Glob::bsd_glob(
146             File::Spec->catfile($inc, 'FFI', 'Build', 'File', '*.pm')
147             );
148             }
149             }
150             else
151             {
152             # When building out of git without dzil, $VERSION will not
153             # usually be defined and any file plugins that require a
154             # specific version will break, so we only use core file
155             # classes for that.
156 0         0 push @file_classes, map { "FFI::Build::File::$_" } qw( C CXX Library Object );
  0         0  
157             }
158              
159             # also anything already loaded, that might not be in the
160             # @INC path (for testing ususally)
161             push @file_classes,
162 5         134 map { my $f = $_; $f =~ s/::$//; "FFI::Build::File::$f" }
  14         40  
  14         54  
  14         56  
163             grep !/Base::/,
164             grep /::$/,
165             keys %{FFI::Build::File::};
166              
167 5         106 @file_classes = List::Util::uniq(@file_classes);
168 5         35 foreach my $class (@file_classes)
169             {
170 22 100       50 next if(eval { $class->can('new') });
  22         361  
171 8         37 my $pm = $class . ".pm";
172 8         49 $pm =~ s/::/\//g;
173 8         4069 require $pm;
174             }
175             }
176 74         256 @file_classes;
177             }
178              
179              
180             sub source
181             {
182 69     69 1 1202 my($self, @file_spec) = @_;
183              
184 69         261 foreach my $file_spec (@file_spec)
185             {
186 62 100       115 if(eval { $file_spec->isa('FFI::Build::File::Base') })
  62         576  
187             {
188 7         19 push @{ $self->{source} }, $file_spec;
  7         34  
189 7         20 next;
190             }
191 55 100       177 if(ref $file_spec eq 'ARRAY')
192             {
193 5         17 my($type, $content, @args) = @$file_spec;
194 5         14 my $class = "FFI::Build::File::$type";
195 5 100       86 unless($class->can('new'))
196             {
197 1         6 my $pm = "FFI/Build/File/$type.pm";
198 1         489 require $pm;
199             }
200 5         23 push @{ $self->{source} }, $class->new(
  5         22  
201             $content,
202             build => $self,
203             platform => $self->platform,
204             @args
205             );
206 5         29 next;
207             }
208 50         3119 my @paths = File::Glob::bsd_glob($file_spec);
209             path:
210 50         218 foreach my $path (@paths)
211             {
212 73         202 foreach my $class (_file_classes)
213             {
214 75         408 foreach my $regex ($class->accept_suffix)
215             {
216 75 100       565 if($path =~ $regex)
217             {
218 73         136 push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
  73         193  
219 73         251 next path;
220             }
221             }
222             }
223 0         0 Carp::croak("Unknown file type: $path");
224             }
225             }
226              
227 69         146 @{ $self->{source} };
  69         321  
228             }
229              
230              
231             sub build
232             {
233 19     19 1 13638 my($self) = @_;
234              
235             # PLUGIN: build
236             # ARGS: $self
237 19         65 _plugins->call('build-build', $self);
238              
239 19         62 my @objects;
240              
241 19         74 my $ld = $self->platform->ld;
242              
243 19         95 foreach my $source ($self->source)
244             {
245             # PLUGIN: build-item
246             # ARGS: $self, $source
247 27         222 _plugins->call('build-build-item', $self, $source);
248              
249 27 50       347 if($source->can('build_all'))
250             {
251 0         0 my $count = scalar $self->source;
252 0 0       0 if($count == 1)
253             {
254 0         0 return $source->build_all($self->file);
255             }
256             else
257             {
258 0         0 die "@{[ ref $source ]} has build_all method, but there is not exactly one source";
  0         0  
259             }
260             }
261              
262 27 100       192 $ld = $source->ld if $source->ld;
263 27         802 my $output;
264 27         227 while(my $next = $source->build_item)
265             {
266 27 50       608 $ld = $next->ld if $next->ld;
267 27         445 $output = $source = $next;
268             }
269 27         323 push @objects, $output;
270             }
271              
272             my $needs_rebuild = sub {
273 19     19   114 my(@objects) = @_;
274 19 50       208 return 1 unless -f $self->file->path;
275 0         0 my $target_time = [stat $self->file->path]->[9];
276 0         0 foreach my $object (@objects)
277             {
278 0         0 my $object_time = [stat "$object"]->[9];
279 0 0       0 return 1 if $object_time > $target_time;
280             }
281 0         0 return 0;
282 19         679 };
283              
284 19 50       221 return $self->file unless $needs_rebuild->(@objects);
285              
286 19         194 File::Path::mkpath($self->file->dirname, 0, oct(755));
287              
288             my @cmd = (
289             $ld,
290             $self->libs_L,
291             $self->platform->ldflags,
292 27         226 (map { "$_" } @objects),
293             $self->libs,
294 19         239 $self->platform->flag_export(@{ $self->export }),
  19         124  
295             $self->platform->flag_library_output($self->file->path),
296             );
297              
298             # PLUGIN: build-link
299             # ARGS: $self, \@cmd
300 19         222 _plugins->call('build-build-link', $self, \@cmd);
301              
302             my($out, $exit) = Capture::Tiny::capture_merged(sub {
303 19     19   34053 $self->platform->run(@cmd);
304 19         2338 });
305              
306 19 50 33     34892 if($exit || !-f $self->file->path)
    100          
    50          
307             {
308 0         0 print $out;
309 0         0 die "error building @{[ $self->file->path ]} from @objects";
  0         0  
310             }
311             elsif($self->verbose >= 2)
312             {
313 12         510 print $out;
314             }
315             elsif($self->verbose >= 1)
316             {
317 7         106 print "LD @{[ $self->file->path ]}\n";
  7         94  
318             }
319              
320             # PLUGIN: link-postlink
321             # ARGS: $self, \@cmd
322 19         391 _plugins->call('build-build-postlink', $self);
323              
324 19         182 $self->file;
325             }
326              
327              
328             sub clean
329             {
330 9     9 1 3486 my($self) = @_;
331 9         52 my $dll = $self->file->path;
332 9 50       213 if(-f $dll)
333             {
334             # PLUGIN: clean
335             # ARGS: $self, $path
336 9         46 _plugins->call('build-clean', $self, $dll);
337 9         445 unlink $dll;
338             }
339 9         94 foreach my $source ($self->source)
340             {
341 17         114 my $dir = File::Spec->catdir($source->dirname, $self->buildname);
342 17 100       358 if(-d $dir)
343             {
344 6         737 foreach my $path (File::Glob::bsd_glob("$dir/*"))
345             {
346 15         89 _plugins->call('build-clean', $self, $path);
347 15         726 unlink $path;
348             }
349 6         311 _plugins->call('build-clean', $self, $dir);
350 6         347 rmdir $dir;
351             }
352             }
353             }
354              
355             1;
356              
357             __END__