File Coverage

blib/lib/FFI/Build.pm
Criterion Covered Total %
statement 119 146 81.5
branch 27 48 56.2
condition 6 11 54.5
subroutine 25 25 100.0
pod 11 11 100.0
total 188 241 78.0


line stmt bran cond sub pod time code
1             package FFI::Build;
2              
3 5     5   69838 use strict;
  5         19  
  5         135  
4 5     5   23 use warnings;
  5         8  
  5         105  
5 5     5   170 use 5.008001;
  5         16  
6 5     5   2056 use FFI::Build::File::Library;
  5         29  
  5         130  
7 5     5   30 use Carp ();
  5         9  
  5         76  
8 5     5   26 use File::Glob ();
  5         9  
  5         62  
9 5     5   26 use File::Basename ();
  5         7  
  5         110  
10 5     5   22 use List::Util 1.45 ();
  5         80  
  5         89  
11 5     5   23 use Capture::Tiny ();
  5         11  
  5         58  
12 5     5   22 use File::Path ();
  5         10  
  5         8011  
13              
14             # ABSTRACT: Build shared libraries for use with FFI
15             our $VERSION = '0.12'; # VERSION
16              
17              
18             sub _native_name
19             {
20 14     14   49 my($self, $name) = @_;
21 14         58 join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
22             }
23              
24             sub new
25             {
26 14     14 1 131221 my($class, $name, %args) = @_;
27              
28 14 50       57 Carp::croak "name is required" unless defined $name;
29              
30 14         126 my $self = bless {
31             source => [],
32             cflags => [],
33             libs => [],
34             alien => [],
35             }, $class;
36            
37 14   66     182 my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
38 14   33     167 my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
39 14   100     108 my $buildname = $self->{buildname} = $args{buildname} || '_build';
40 14         70 my $verbose = $self->{verbose} = $args{verbose};
41              
42 14 100       49 if(defined $args{cflags})
43             {
44 2 50       17 push @{ $self->{cflags} }, ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
  2         18  
  0         0  
45             }
46            
47 14 50       350 if(defined $args{libs})
48             {
49 0 0       0 push @{ $self->{libs} }, ref $args{libs} ? @{ $args{libs} } : $self->platform->shellwords($args{libs});
  0         0  
  0         0  
50             }
51            
52 14 50       67 if(defined $args{alien})
53             {
54 0 0       0 my @aliens = ref $args{alien} ? @{ $args{alien} } : ($args{alien});
  0         0  
55 0         0 foreach my $alien (@aliens)
56             {
57 0 0       0 unless(eval { $alien->can('cflags') && $alien->can('libs') })
  0 0       0  
58             {
59 0         0 my $pm = "$alien.pm";
60 0         0 $pm =~ s/::/\//g;
61 0         0 require $pm;
62             }
63 0         0 push @{ $self->{alien} }, $alien;
  0         0  
64 0         0 push @{ $self->{cflags} }, $self->platform->shellwords($alien->cflags);
  0         0  
65 0         0 push @{ $self->{libs} }, $self->platform->shellwords($alien->libs);
  0         0  
66             }
67             }
68            
69 14 50       55 $self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
  8 100       33  
70              
71 14         108 $self;
72             }
73              
74              
75 25     25 1 394 sub buildname { shift->{buildname} }
76 36     36 1 1378 sub file { shift->{file} }
77 92     92 1 724 sub platform { shift->{platform} }
78 15     15 1 178 sub verbose { shift->{verbose} }
79 14     14 1 95 sub cflags { shift->{cflags} }
80 5     5 1 39 sub libs { shift->{libs} }
81 2     2 1 7 sub alien { shift->{alien} }
82              
83             my @file_classes;
84             sub _file_classes
85             {
86 28 100   28   4459 unless(@file_classes)
87             {
88              
89 2         6 foreach my $inc (@INC)
90             {
91             push @file_classes,
92 16         46 map { $_ =~ s/\.pm$//; "FFI::Build::File::$_" }
  16         48  
93             grep !/^Base\.pm$/,
94 20         864 map { File::Basename::basename($_) }
  20         535  
95             File::Glob::bsd_glob(
96             File::Spec->catfile($inc, 'FFI', 'Build', 'File', '*.pm')
97             );
98             }
99              
100             # also anything already loaded, that might not be in the
101             # @INC path (for testing ususally)
102             push @file_classes,
103 2         26 map { s/::$//; "FFI::Build::File::$_" }
  6         16  
  6         21  
104             grep !/Base::/,
105             grep /::$/,
106             keys %{FFI::Build::File::};
107              
108 2         20 @file_classes = List::Util::uniq(@file_classes);
109 2         7 foreach my $class (@file_classes)
110             {
111 10 100       17 next if(eval { $class->can('new') });
  10         109  
112 4         13 my $pm = $class . ".pm";
113 4         19 $pm =~ s/::/\//g;
114 4         1813 require $pm;
115             }
116             }
117 28         115 @file_classes;
118             }
119              
120              
121             sub source
122             {
123 29     29 1 1076 my($self, @file_spec) = @_;
124            
125 29         98 foreach my $file_spec (@file_spec)
126             {
127 28 100       50 if(eval { $file_spec->isa('FFI::Build::File::Base') })
  28         243  
128             {
129 2         4 push @{ $self->{source} }, $file_spec;
  2         6  
130 2         13 next;
131             }
132 26         1681 my @paths = File::Glob::bsd_glob($file_spec);
133             path:
134 26         109 foreach my $path (@paths)
135             {
136 27         87 foreach my $class (_file_classes)
137             {
138 29         169 foreach my $regex ($class->accept_suffix)
139             {
140 29 100       220 if($path =~ $regex)
141             {
142 27         48 push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
  27         93  
143 27         106 next path;
144             }
145             }
146             }
147 0         0 Carp::croak("Unknown file type: $path");
148             }
149             }
150            
151 29         48 @{ $self->{source} };
  29         125  
152             }
153              
154              
155             sub build
156             {
157 5     5 1 5282 my($self) = @_;
158              
159 5         13 my @objects;
160            
161 5         16 my $ld = $self->platform->ld;
162            
163 5         20 foreach my $source ($self->source)
164             {
165 12 100       101 $ld = $source->ld if $source->ld;
166 12         22 my $output;
167 12         71 while(my $next = $source->build_item)
168             {
169 12 50       274 $ld = $next->ld if $next->ld;
170 12         182 $output = $source = $next;
171             }
172 12         181 push @objects, $output;
173             }
174            
175             my $needs_rebuild = sub {
176 5     5   29 my(@objects) = @_;
177 5 50       57 return 1 unless -f $self->file->path;
178 0         0 my $target_time = [stat $self->file->path]->[9];
179 0         0 foreach my $object (@objects)
180             {
181 0         0 my $object_time = [stat "$object"]->[9];
182 0 0       0 return 1 if $object_time > $target_time;
183             }
184 0         0 return 0;
185 5         156 };
186            
187 5 50       60 return $self->file unless $needs_rebuild->(@objects);
188            
189 5         37 File::Path::mkpath($self->file->dirname, 0, 0755);
190            
191             my @cmd = (
192             $ld,
193             $self->platform->ldflags,
194 12         50 (map { "$_" } @objects),
195 5         66 @{ $self->libs },
  5         43  
196             $self->platform->extra_system_lib,
197             $self->platform->flag_library_output($self->file->path),
198             );
199            
200             my($out, $exit) = Capture::Tiny::capture_merged(sub {
201 5     5   8290 print "+ @cmd\n";
202 5         233974 system @cmd;
203 5         519 });
204            
205 5 50 33     9779 if($exit || !-f $self->file->path)
    50          
206             {
207 0         0 print $out;
208 0         0 die "error building @{[ $self->file->path ]} from @objects";
  0         0  
209             }
210             elsif($self->verbose)
211             {
212 5         147 print $out;
213             }
214            
215 5         66 $self->file;
216             }
217              
218              
219             sub clean
220             {
221 5     5 1 13382 my($self) = @_;
222 5         35 my $dll = $self->file->path;
223 5 50       462 unlink $dll if -f $dll;
224 5         50 foreach my $source ($self->source)
225             {
226 12         120 my $dir = File::Spec->catdir($source->dirname, $self->buildname);
227 12 100       250 if(-d $dir)
228             {
229 4         1038 unlink $_ for File::Glob::bsd_glob("$dir/*");
230 4         346 rmdir $dir;
231             }
232             }
233             }
234              
235             1;
236              
237             __END__