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   228461 use strict;
  8         21  
  8         233  
4 8     8   37 use warnings;
  8         19  
  8         175  
5 8     8   138 use 5.008004;
  8         30  
6 8     8   3395 use FFI::Build::Plugin;
  8         25  
  8         342  
7 8     8   2813 use FFI::Build::PluginData qw( plugin_data );
  8         20  
  8         491  
8 8     8   3412 use FFI::Build::File::Library;
  8         26  
  8         218  
9 8     8   47 use Carp ();
  8         15  
  8         134  
10 8     8   40 use File::Glob ();
  8         30  
  8         108  
11 8     8   37 use File::Basename ();
  8         16  
  8         151  
12 8     8   36 use List::Util 1.45 ();
  8         175  
  8         144  
13 8     8   39 use Capture::Tiny ();
  8         18  
  8         95  
14 8     8   33 use File::Path ();
  8         19  
  8         18933  
15              
16             # ABSTRACT: Build shared libraries for use with FFI
17             our $VERSION = '2.06_01'; # TRIAL 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   2668 sub _plugins { $plugins ||= FFI::Build::Plugin->new };
35             }
36              
37             sub import
38             {
39 9     9   77 my @caller = caller;
40             # PLUGIN: import
41             # ARGS: @caller, \@args
42 9         36 _plugins->call('build-import', \@caller, \@_);
43             }
44              
45             sub _native_name
46             {
47 38     38   296 my($self, $name) = @_;
48 38         161 join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
49             }
50              
51             sub new
52             {
53 38     38 1 25225 my($class, $name, %args) = @_;
54              
55 38 50       175 Carp::croak "name is required" unless defined $name;
56              
57             # PLUGIN: new-pre
58             # ARGS: $name, \%args
59 38         189 _plugins->call('build-new-pre', $name, \%args);
60              
61 38         415 my $self = bless {
62             source => [],
63             cflags_I => [],
64             cflags => [],
65             libs_L => [],
66             libs => [],
67             alien => [],
68             }, $class;
69              
70 38   66     552 my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
71 38   33     495 my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
72 38   100     311 my $buildname = $self->{buildname} = $args{buildname} || '_build';
73 38   100     162 my $verbose = $self->{verbose} = $args{verbose} || 0;
74 38   100     218 my $export = $self->{export} = $args{export} || [];
75              
76 38 50       166 $self->{verbose} = $verbose = 2 if $ENV{V};
77              
78 38 100       142 if(defined $args{cflags})
79             {
80 2 50       23 my @flags = ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
  0         0  
81 2         14 push @{ $self->{cflags} }, grep !/^-I/, @flags;
  2         32  
82 2         6 push @{ $self->{cflags_I} }, grep /^-I/, @flags;
  2         23  
83             }
84              
85 38 50       122 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       145 if(defined $args{alien})
93             {
94 8 50       31 my @aliens = ref $args{alien} ? @{ $args{alien} } : ($args{alien});
  8         29  
95 8         30 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       179 $self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
  21 100       93  
112              
113             # PLUGIN: new-post
114             # ARGS: $self
115 38         112 _plugins->call('build-new-post', $self);
116              
117 38         242 $self;
118             }
119              
120              
121 51     51 1 4390 sub buildname { shift->{buildname} }
122 19     19 1 224 sub export { shift->{export} }
123 120     120 1 2899 sub file { shift->{file} }
124 290     290 1 1864 sub platform { shift->{platform} }
125 64     64 1 729 sub verbose { shift->{verbose} }
126 32     32 1 122 sub cflags { shift->{cflags} }
127 30     30 1 116 sub cflags_I { shift->{cflags_I} }
128 19     19 1 118 sub libs { shift->{libs} }
129 19     19 1 191 sub libs_L { shift->{libs_L} }
130 9     9 1 31 sub alien { shift->{alien} }
131              
132             my @file_classes;
133             sub _file_classes
134             {
135 74 100   74   4359 unless(@file_classes)
136             {
137 5 50       97 if(defined $FFI::Build::VERSION)
138             {
139 5         27 foreach my $inc (@INC)
140             {
141             push @file_classes,
142 44         78 map { my $f = $_; $f =~ s/\.pm$//; "FFI::Build::File::$f" }
  44         119  
  44         152  
143             grep !/^Base\.pm$/,
144 53         2327 map { File::Basename::basename($_) }
  55         1713  
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         107 map { my $f = $_; $f =~ s/::$//; "FFI::Build::File::$f" }
  14         31  
  14         49  
  14         49  
163             grep !/Base::/,
164             grep /::$/,
165             keys %{FFI::Build::File::};
166              
167 5         68 @file_classes = List::Util::uniq(@file_classes);
168 5         21 foreach my $class (@file_classes)
169             {
170 22 100       45 next if(eval { $class->can('new') });
  22         316  
171 8         32 my $pm = $class . ".pm";
172 8         45 $pm =~ s/::/\//g;
173 8         3838 require $pm;
174             }
175             }
176 74         253 @file_classes;
177             }
178              
179              
180             sub source
181             {
182 69     69 1 1263 my($self, @file_spec) = @_;
183              
184 69         244 foreach my $file_spec (@file_spec)
185             {
186 62 100       120 if(eval { $file_spec->isa('FFI::Build::File::Base') })
  62         501  
187             {
188 7         15 push @{ $self->{source} }, $file_spec;
  7         22  
189 7         23 next;
190             }
191 55 100       160 if(ref $file_spec eq 'ARRAY')
192             {
193 5         20 my($type, $content, @args) = @$file_spec;
194 5         20 my $class = "FFI::Build::File::$type";
195 5 100       80 unless($class->can('new'))
196             {
197 1         3 my $pm = "FFI/Build/File/$type.pm";
198 1         531 require $pm;
199             }
200 5         16 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         2917 my @paths = File::Glob::bsd_glob($file_spec);
209             path:
210 50         181 foreach my $path (@paths)
211             {
212 73         171 foreach my $class (_file_classes)
213             {
214 75         342 foreach my $regex ($class->accept_suffix)
215             {
216 75 100       520 if($path =~ $regex)
217             {
218 73         114 push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
  73         226  
219 73         257 next path;
220             }
221             }
222             }
223 0         0 Carp::croak("Unknown file type: $path");
224             }
225             }
226              
227 69         120 @{ $self->{source} };
  69         302  
228             }
229              
230              
231             sub build
232             {
233 19     19 1 13243 my($self) = @_;
234              
235             # PLUGIN: build
236             # ARGS: $self
237 19         63 _plugins->call('build-build', $self);
238              
239 19         53 my @objects;
240              
241 19         65 my $ld = $self->platform->ld;
242              
243 19         101 foreach my $source ($self->source)
244             {
245             # PLUGIN: build-item
246             # ARGS: $self, $source
247 27         163 _plugins->call('build-build-item', $self, $source);
248              
249 27 50       278 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       161 $ld = $source->ld if $source->ld;
263 27         706 my $output;
264 27         218 while(my $next = $source->build_item)
265             {
266 27 50       565 $ld = $next->ld if $next->ld;
267 27         317 $output = $source = $next;
268             }
269 27         328 push @objects, $output;
270             }
271              
272             my $needs_rebuild = sub {
273 19     19   104 my(@objects) = @_;
274 19 50       183 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         568 };
283              
284 19 50       144 return $self->file unless $needs_rebuild->(@objects);
285              
286 19         149 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         173 (map { "$_" } @objects),
293             $self->libs,
294 19         277 $self->platform->flag_export(@{ $self->export }),
  19         92  
295             $self->platform->flag_library_output($self->file->path),
296             );
297              
298             # PLUGIN: build-link
299             # ARGS: $self, \@cmd
300 19         213 _plugins->call('build-build-link', $self, \@cmd);
301              
302             my($out, $exit) = Capture::Tiny::capture_merged(sub {
303 19     19   31647 $self->platform->run(@cmd);
304 19         1908 });
305              
306 19 50 33     32084 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         417 print $out;
314             }
315             elsif($self->verbose >= 1)
316             {
317 7         67 print "LD @{[ $self->file->path ]}\n";
  7         76  
318             }
319              
320             # PLUGIN: link-postlink
321             # ARGS: $self, \@cmd
322 19         377 _plugins->call('build-build-postlink', $self);
323              
324 19         105 $self->file;
325             }
326              
327              
328             sub clean
329             {
330 9     9 1 3423 my($self) = @_;
331 9         37 my $dll = $self->file->path;
332 9 50       219 if(-f $dll)
333             {
334             # PLUGIN: clean
335             # ARGS: $self, $path
336 9         50 _plugins->call('build-clean', $self, $dll);
337 9         433 unlink $dll;
338             }
339 9         89 foreach my $source ($self->source)
340             {
341 17         115 my $dir = File::Spec->catdir($source->dirname, $self->buildname);
342 17 100       335 if(-d $dir)
343             {
344 6         687 foreach my $path (File::Glob::bsd_glob("$dir/*"))
345             {
346 15         67 _plugins->call('build-clean', $self, $path);
347 15         699 unlink $path;
348             }
349 6         254 _plugins->call('build-clean', $self, $dir);
350 6         352 rmdir $dir;
351             }
352             }
353             }
354              
355             1;
356              
357             __END__