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   184885 use strict;
  8         20  
  8         190  
4 8     8   32 use warnings;
  8         14  
  8         139  
5 8     8   102 use 5.008004;
  8         24  
6 8     8   2621 use FFI::Build::Plugin;
  8         17  
  8         248  
7 8     8   2296 use FFI::Build::PluginData qw( plugin_data );
  8         17  
  8         357  
8 8     8   2773 use FFI::Build::File::Library;
  8         19  
  8         177  
9 8     8   43 use Carp ();
  8         13  
  8         103  
10 8     8   33 use File::Glob ();
  8         20  
  8         85  
11 8     8   33 use File::Basename ();
  8         17  
  8         117  
12 8     8   28 use List::Util 1.45 ();
  8         155  
  8         121  
13 8     8   34 use Capture::Tiny ();
  8         15  
  8         106  
14 8     8   40 use File::Path ();
  8         22  
  8         15357  
15              
16             # ABSTRACT: Build shared libraries for use with FFI
17             our $VERSION = '2.07'; # 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   2417 sub _plugins { $plugins ||= FFI::Build::Plugin->new };
35             }
36              
37             sub import
38             {
39 9     9   67 my @caller = caller;
40             # PLUGIN: import
41             # ARGS: @caller, \@args
42 9         22 _plugins->call('build-import', \@caller, \@_);
43             }
44              
45             sub _native_name
46             {
47 38     38   245 my($self, $name) = @_;
48 38         138 join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
49             }
50              
51             sub new
52             {
53 38     38 1 18208 my($class, $name, %args) = @_;
54              
55 38 50       139 Carp::croak "name is required" unless defined $name;
56              
57             # PLUGIN: new-pre
58             # ARGS: $name, \%args
59 38         152 _plugins->call('build-new-pre', $name, \%args);
60              
61 38         353 my $self = bless {
62             source => [],
63             cflags_I => [],
64             cflags => [],
65             libs_L => [],
66             libs => [],
67             alien => [],
68             }, $class;
69              
70 38   66     461 my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
71 38   33     370 my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
72 38   100     248 my $buildname = $self->{buildname} = $args{buildname} || '_build';
73 38   100     135 my $verbose = $self->{verbose} = $args{verbose} || 0;
74 38   100     176 my $export = $self->{export} = $args{export} || [];
75              
76 38 50       118 $self->{verbose} = $verbose = 2 if $ENV{V};
77              
78 38 100       107 if(defined $args{cflags})
79             {
80 2 50       15 my @flags = ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
  0         0  
81 2         9 push @{ $self->{cflags} }, grep !/^-I/, @flags;
  2         27  
82 2         5 push @{ $self->{cflags_I} }, grep /^-I/, @flags;
  2         24  
83             }
84              
85 38 50       105 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       97 if(defined $args{alien})
93             {
94 8 50       24 my @aliens = ref $args{alien} ? @{ $args{alien} } : ($args{alien});
  8         36  
95 8         22 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       146 $self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
  21 100       92  
112              
113             # PLUGIN: new-post
114             # ARGS: $self
115 38         90 _plugins->call('build-new-post', $self);
116              
117 38         197 $self;
118             }
119              
120              
121 51     51 1 3542 sub buildname { shift->{buildname} }
122 19     19 1 109 sub export { shift->{export} }
123 120     120 1 2168 sub file { shift->{file} }
124 290     290 1 1884 sub platform { shift->{platform} }
125 64     64 1 592 sub verbose { shift->{verbose} }
126 32     32 1 108 sub cflags { shift->{cflags} }
127 30     30 1 92 sub cflags_I { shift->{cflags_I} }
128 19     19 1 68 sub libs { shift->{libs} }
129 19     19 1 219 sub libs_L { shift->{libs_L} }
130 9     9 1 24 sub alien { shift->{alien} }
131              
132             my @file_classes;
133             sub _file_classes
134             {
135 74 100   74   3483 unless(@file_classes)
136             {
137 5 50       85 if(defined $FFI::Build::VERSION)
138             {
139 5         20 foreach my $inc (@INC)
140             {
141             push @file_classes,
142 44         64 map { my $f = $_; $f =~ s/\.pm$//; "FFI::Build::File::$f" }
  44         97  
  44         128  
143             grep !/^Base\.pm$/,
144 53         1894 map { File::Basename::basename($_) }
  55         1380  
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         88 map { my $f = $_; $f =~ s/::$//; "FFI::Build::File::$f" }
  14         25  
  14         38  
  14         40  
163             grep !/Base::/,
164             grep /::$/,
165             keys %{FFI::Build::File::};
166              
167 5         78 @file_classes = List::Util::uniq(@file_classes);
168 5         20 foreach my $class (@file_classes)
169             {
170 22 100       44 next if(eval { $class->can('new') });
  22         266  
171 8         27 my $pm = $class . ".pm";
172 8         29 $pm =~ s/::/\//g;
173 8         3205 require $pm;
174             }
175             }
176 74         188 @file_classes;
177             }
178              
179              
180             sub source
181             {
182 69     69 1 998 my($self, @file_spec) = @_;
183              
184 69         187 foreach my $file_spec (@file_spec)
185             {
186 62 100       103 if(eval { $file_spec->isa('FFI::Build::File::Base') })
  62         434  
187             {
188 7         15 push @{ $self->{source} }, $file_spec;
  7         24  
189 7         19 next;
190             }
191 55 100       143 if(ref $file_spec eq 'ARRAY')
192             {
193 5         18 my($type, $content, @args) = @$file_spec;
194 5         12 my $class = "FFI::Build::File::$type";
195 5 100       65 unless($class->can('new'))
196             {
197 1         4 my $pm = "FFI/Build/File/$type.pm";
198 1         403 require $pm;
199             }
200 5         13 push @{ $self->{source} }, $class->new(
  5         24  
201             $content,
202             build => $self,
203             platform => $self->platform,
204             @args
205             );
206 5         16 next;
207             }
208 50         2443 my @paths = File::Glob::bsd_glob($file_spec);
209             path:
210 50         155 foreach my $path (@paths)
211             {
212 73         144 foreach my $class (_file_classes)
213             {
214 75         276 foreach my $regex ($class->accept_suffix)
215             {
216 75 100       421 if($path =~ $regex)
217             {
218 73         94 push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
  73         163  
219 73         204 next path;
220             }
221             }
222             }
223 0         0 Carp::croak("Unknown file type: $path");
224             }
225             }
226              
227 69         96 @{ $self->{source} };
  69         247  
228             }
229              
230              
231             sub build
232             {
233 19     19 1 15470 my($self) = @_;
234              
235             # PLUGIN: build
236             # ARGS: $self
237 19         48 _plugins->call('build-build', $self);
238              
239 19         34 my @objects;
240              
241 19         52 my $ld = $self->platform->ld;
242              
243 19         72 foreach my $source ($self->source)
244             {
245             # PLUGIN: build-item
246             # ARGS: $self, $source
247 27         131 _plugins->call('build-build-item', $self, $source);
248              
249 27 50       205 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       126 $ld = $source->ld if $source->ld;
263 27         566 my $output;
264 27         173 while(my $next = $source->build_item)
265             {
266 27 50       361 $ld = $next->ld if $next->ld;
267 27         309 $output = $source = $next;
268             }
269 27         227 push @objects, $output;
270             }
271              
272             my $needs_rebuild = sub {
273 19     19   75 my(@objects) = @_;
274 19 50       142 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         355 };
283              
284 19 50       123 return $self->file unless $needs_rebuild->(@objects);
285              
286 19         108 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         141 (map { "$_" } @objects),
293             $self->libs,
294 19         246 $self->platform->flag_export(@{ $self->export }),
  19         79  
295             $self->platform->flag_library_output($self->file->path),
296             );
297              
298             # PLUGIN: build-link
299             # ARGS: $self, \@cmd
300 19         119 _plugins->call('build-build-link', $self, \@cmd);
301              
302             my($out, $exit) = Capture::Tiny::capture_merged(sub {
303 19     19   24699 $self->platform->run(@cmd);
304 19         1423 });
305              
306 19 50 33     23279 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         435 print $out;
314             }
315             elsif($self->verbose >= 1)
316             {
317 7         45 print "LD @{[ $self->file->path ]}\n";
  7         66  
318             }
319              
320             # PLUGIN: link-postlink
321             # ARGS: $self, \@cmd
322 19         243 _plugins->call('build-build-postlink', $self);
323              
324 19         137 $self->file;
325             }
326              
327              
328             sub clean
329             {
330 9     9 1 2777 my($self) = @_;
331 9         37 my $dll = $self->file->path;
332 9 50       215 if(-f $dll)
333             {
334             # PLUGIN: clean
335             # ARGS: $self, $path
336 9         34 _plugins->call('build-clean', $self, $dll);
337 9         463 unlink $dll;
338             }
339 9         87 foreach my $source ($self->source)
340             {
341 17         86 my $dir = File::Spec->catdir($source->dirname, $self->buildname);
342 17 100       361 if(-d $dir)
343             {
344 6         565 foreach my $path (File::Glob::bsd_glob("$dir/*"))
345             {
346 15         95 _plugins->call('build-clean', $self, $path);
347 15         573 unlink $path;
348             }
349 6         225 _plugins->call('build-clean', $self, $dir);
350 6         326 rmdir $dir;
351             }
352             }
353             }
354              
355             1;
356              
357             __END__