File Coverage

lib/FFI/Build.pm
Criterion Covered Total %
statement 184 202 91.0
branch 44 62 70.9
condition 11 18 61.1
subroutine 32 32 100.0
pod 14 14 100.0
total 285 328 86.8


line stmt bran cond sub pod time code
1             package FFI::Build;
2              
3 10     10   247585 use strict;
  10         16  
  10         351  
4 10     10   53 use warnings;
  10         17  
  10         443  
5 10     10   148 use 5.008004;
  10         39  
6 10     10   4536 use FFI::Build::Plugin;
  10         32  
  10         467  
7 10     10   6687 use FFI::Build::PluginData qw( plugin_data );
  10         30  
  10         756  
8 10     10   4761 use FFI::Build::File::Library;
  10         33  
  10         309  
9 10     10   56 use Carp ();
  10         34  
  10         190  
10 10     10   44 use File::Glob ();
  10         17  
  10         139  
11 10     10   38 use File::Basename ();
  10         17  
  10         223  
12 10     10   5839 use List::Util 1.45 ();
  10         254  
  10         235  
13 10     10   73 use Capture::Tiny ();
  10         27  
  10         167  
14 10     10   53 use File::Path ();
  10         17  
  10         26928  
15              
16             # ABSTRACT: Build shared libraries for use with FFI
17             our $VERSION = '2.11'; # 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 232   33 232   3213 sub _plugins { $plugins ||= FFI::Build::Plugin->new };
35             }
36              
37             sub import
38             {
39 13     13   94 my @caller = caller;
40             # PLUGIN: import
41             # ARGS: @caller, \@args
42 13         40 _plugins->call('build-import', \@caller, \@_);
43             }
44              
45             sub _native_name
46             {
47 38     38   245 my($self, $name) = @_;
48 38         184 join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
49             }
50              
51             sub new
52             {
53 38     38 1 239844 my($class, $name, %args) = @_;
54              
55 38 50       156 Carp::croak "name is required" unless defined $name;
56              
57             # PLUGIN: new-pre
58             # ARGS: $name, \%args
59 38         186 _plugins->call('build-new-pre', $name, \%args);
60              
61 38         536 my $self = bless {
62             source => [],
63             cflags_I => [],
64             cflags => [],
65             libs_L => [],
66             libs => [],
67             alien => [],
68             }, $class;
69              
70 38   66     579 my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
71 38   33     389 my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
72 38   100     360 my $buildname = $self->{buildname} = $args{buildname} || '_build';
73 38   100     158 my $verbose = $self->{verbose} = $args{verbose} || 0;
74 38   100     200 my $export = $self->{export} = $args{export} || [];
75              
76 38 50       138 $self->{verbose} = $verbose = 2 if $ENV{V};
77              
78 38 100       138 if(defined $args{cflags})
79             {
80 3 100       14 my @flags = ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
  1         4  
81 3         7 push @{ $self->{cflags} }, grep !/^-I/, @flags;
  3         19  
82 3         6 push @{ $self->{cflags_I} }, grep /^-I/, @flags;
  3         17  
83             }
84              
85 38 50       125 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       118 if(defined $args{alien})
93             {
94 10 50       34 my @aliens = ref $args{alien} ? @{ $args{alien} } : ($args{alien});
  10         31  
95 10         32 foreach my $alien (@aliens)
96             {
97 2 100       4 unless(eval { $alien->can('cflags') && $alien->can('libs') })
  2 100       37  
98             {
99 1         2 my $pm = "$alien.pm";
100 1         5 $pm =~ s/::/\//g;
101 1         685 require $pm;
102             }
103 2         26925 push @{ $self->{alien} }, $alien;
  2         7  
104 2         5 push @{ $self->{cflags} }, grep !/^-I/, $self->platform->shellwords($alien->cflags);
  2         9  
105 2         5 push @{ $self->{cflags_I} }, grep /^-I/, $self->platform->shellwords($alien->cflags);
  2         14  
106 2         5 push @{ $self->{libs} }, grep !/^-L/, $self->platform->shellwords($alien->libs);
  2         10  
107 2         6 push @{ $self->{libs_L} }, grep /^-L/, $self->platform->shellwords($alien->libs);
  2         11  
108             }
109             }
110              
111 38 100       153 $self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
  23 100       108  
112              
113             # PLUGIN: new-post
114             # ARGS: $self
115 38         109 _plugins->call('build-new-post', $self);
116              
117 38         1452 $self;
118             }
119              
120              
121 78     78 1 4330 sub buildname { shift->{buildname} }
122 20     20 1 175 sub export { shift->{export} }
123 126     126 1 2723 sub file { shift->{file} }
124 333     333 1 2172 sub platform { shift->{platform} }
125 129     129 1 1026 sub verbose { shift->{verbose} }
126 63     63 1 210 sub cflags { shift->{cflags} }
127 62     62 1 265 sub cflags_I { shift->{cflags_I} }
128 20     20 1 191 sub libs { shift->{libs} }
129 20     20 1 221 sub libs_L { shift->{libs_L} }
130 9     9 1 33 sub alien { shift->{alien} }
131              
132             my @file_classes;
133             sub _file_classes
134             {
135 104 100   104   3383 unless(@file_classes)
136             {
137 7 50       33 if(defined $FFI::Build::VERSION)
138             {
139 7         56 foreach my $inc (@INC)
140             {
141             push @file_classes,
142 68         115 map { my $f = $_; $f =~ s/\.pm$//; "FFI::Build::File::$f" }
  68         157  
  68         215  
143             grep !/^Base\.pm$/,
144 58         3892 map { File::Basename::basename($_) }
  85         2947  
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 7         136 map { my $f = $_; $f =~ s/::$//; "FFI::Build::File::$f" }
  20         35  
  20         60  
  20         51  
163             grep !/Base::/,
164             grep /::$/,
165             keys %{FFI::Build::File::};
166              
167 7         107 @file_classes = List::Util::uniq(@file_classes);
168 7         27 foreach my $class (@file_classes)
169             {
170 30 100       57 next if(eval { $class->can('new') });
  30         484  
171 10         27 my $pm = $class . ".pm";
172 10         62 $pm =~ s/::/\//g;
173 10         5620 require $pm;
174             }
175             }
176 104         228 @file_classes;
177             }
178              
179              
180             sub source
181             {
182 68     68 1 1133 my($self, @file_spec) = @_;
183              
184 68         182 foreach my $file_spec (@file_spec)
185             {
186 62 100       105 if(eval { $file_spec->isa('FFI::Build::File::Base') })
  62         589  
187             {
188 7         15 push @{ $self->{source} }, $file_spec;
  7         23  
189 7         25 next;
190             }
191 55 100       156 if(ref $file_spec eq 'ARRAY')
192             {
193 5         22 my($type, $content, @args) = @$file_spec;
194 5         14 my $class = "FFI::Build::File::$type";
195 5 100       77 unless($class->can('new'))
196             {
197 1         3 my $pm = "FFI/Build/File/$type.pm";
198 1         701 require $pm;
199             }
200 5         19 push @{ $self->{source} }, $class->new(
  5         46  
201             $content,
202             build => $self,
203             platform => $self->platform,
204             @args
205             );
206 5         21 next;
207             }
208 50         4477 my @paths = File::Glob::bsd_glob($file_spec);
209             path:
210 50         166 foreach my $path (@paths)
211             {
212 103         236 foreach my $class (_file_classes)
213             {
214 103         403 foreach my $regex ($class->accept_suffix)
215             {
216 103 50       630 if($path =~ $regex)
217             {
218 103         245 push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
  103         256  
219 103         316 next path;
220             }
221             }
222             }
223 0         0 Carp::croak("Unknown file type: $path");
224             }
225             }
226              
227 68         110 @{ $self->{source} };
  68         271  
228             }
229              
230              
231             sub build
232             {
233 20     20 1 15180 my($self) = @_;
234              
235             # PLUGIN: build
236             # ARGS: $self
237 20         71 _plugins->call('build-build', $self);
238              
239 20         42 my @objects;
240              
241 20         69 my $ld = $self->platform->ld;
242              
243 20         86 foreach my $source ($self->source)
244             {
245             # PLUGIN: build-item
246             # ARGS: $self, $source
247 57         398 _plugins->call('build-build-item', $self, $source);
248              
249 57 50       467 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 57 50       295 $ld = $source->ld if $source->ld;
263 57         111 my $output;
264 57         260 while(my $next = $source->build_item)
265             {
266 57 50       768 $ld = $next->ld if $next->ld;
267 57         508 $output = $source = $next;
268             }
269 57         472 push @objects, $output;
270             }
271              
272             my $needs_rebuild = sub {
273 20     20   91 my(@objects) = @_;
274 20 100       191 return 1 unless -f $self->file->path;
275 1         8 my $target_time = [stat $self->file->path]->[9];
276 1         9 foreach my $object (@objects)
277             {
278 1         10 my $object_time = [stat "$object"]->[9];
279 1 50       8 return 1 if $object_time > $target_time;
280             }
281 0         0 return 0;
282 20         454 };
283              
284 20 50       121 return $self->file unless $needs_rebuild->(@objects);
285              
286 20         138 File::Path::mkpath($self->file->dirname, 0, oct(755));
287              
288             my @cmd = (
289             $ld,
290             $self->libs_L,
291             $self->platform->ldflags,
292 57         228 (map { "$_" } @objects),
293             $self->libs,
294 20         248 $self->platform->flag_export(@{ $self->export }),
  20         149  
295             $self->platform->flag_library_output($self->file->path),
296             );
297              
298             # PLUGIN: build-link
299             # ARGS: $self, \@cmd
300 20         272 _plugins->call('build-build-link', $self, \@cmd);
301              
302             my($out, $exit) = Capture::Tiny::capture_merged(sub {
303 20     20   103501 $self->platform->run(@cmd);
304 20         6675 });
305              
306 20 50 33     28797 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 11         478 print $out;
314             }
315             elsif($self->verbose >= 1)
316             {
317 9         56 print "LD @{[ $self->file->path ]}\n";
  9         60  
318             }
319              
320             # PLUGIN: link-postlink
321             # ARGS: $self, \@cmd
322 20         283 _plugins->call('build-build-postlink', $self);
323              
324 20         97 $self->file;
325             }
326              
327              
328             sub clean
329             {
330 8     8 1 8160 my($self) = @_;
331 8         39 my $dll = $self->file->path;
332 8 50       218 if(-f $dll)
333             {
334             # PLUGIN: clean
335             # ARGS: $self, $path
336 8         36 _plugins->call('build-clean', $self, $dll);
337 8         899 unlink $dll;
338             }
339 8         78 foreach my $source ($self->source)
340             {
341 15         103 my $dir = File::Spec->catdir($source->dirname, $self->buildname);
342 15 100       1112 if(-d $dir)
343             {
344 5         679 foreach my $path (File::Glob::bsd_glob("$dir/*"))
345             {
346 13         45 _plugins->call('build-clean', $self, $path);
347 13         4897 unlink $path;
348             }
349 5         67 _plugins->call('build-clean', $self, $dir);
350 5         375 rmdir $dir;
351             }
352             }
353             }
354              
355             1;
356              
357             __END__