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   98562 use strict;
  5         18  
  5         138  
4 5     5   25 use warnings;
  5         7  
  5         106  
5 5     5   79 use 5.008001;
  5         16  
6 5     5   2089 use FFI::Build::File::Library;
  5         14  
  5         126  
7 5     5   30 use Carp ();
  5         9  
  5         66  
8 5     5   25 use File::Glob ();
  5         9  
  5         120  
9 5     5   24 use File::Basename ();
  5         11  
  5         95  
10 5     5   22 use List::Util 1.45 ();
  5         83  
  5         79  
11 5     5   21 use Capture::Tiny ();
  5         7  
  5         70  
12 5     5   21 use File::Path ();
  5         9  
  5         8090  
13              
14             # ABSTRACT: Build shared libraries for use with FFI::Platypus
15             our $VERSION = '0.10'; # VERSION
16              
17              
18             sub _native_name
19             {
20 14     14   45 my($self, $name) = @_;
21 14         90 join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
22             }
23              
24             sub new
25             {
26 14     14 1 137291 my($class, $name, %args) = @_;
27              
28 14 50       68 Carp::croak "name is required" unless defined $name;
29              
30 14         135 my $self = bless {
31             source => [],
32             cflags => [],
33             libs => [],
34             alien => [],
35             }, $class;
36            
37 14   66     161 my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
38 14   33     170 my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
39 14   100     98 my $buildname = $self->{buildname} = $args{buildname} || '_build';
40 14         56 my $verbose = $self->{verbose} = $args{verbose};
41              
42 14 100       47 if(defined $args{cflags})
43             {
44 2 50       15 push @{ $self->{cflags} }, ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
  2         16  
  0         0  
45             }
46            
47 14 50       383 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       56 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       67 $self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
  8 100       51  
70              
71 14         98 $self;
72             }
73              
74              
75 25     25 1 212 sub buildname { shift->{buildname} }
76 36     36 1 1267 sub file { shift->{file} }
77 92     92 1 694 sub platform { shift->{platform} }
78 15     15 1 133 sub verbose { shift->{verbose} }
79 14     14 1 75 sub cflags { shift->{cflags} }
80 5     5 1 26 sub libs { shift->{libs} }
81 2     2 1 9 sub alien { shift->{alien} }
82              
83             my @file_classes;
84             sub _file_classes
85             {
86 28 100   28   3523 unless(@file_classes)
87             {
88              
89 2         6 foreach my $inc (@INC)
90             {
91             push @file_classes,
92 20         53 map { $_ =~ s/\.pm$//; "FFI::Build::File::$_" }
  20         70  
93             grep !/^Base\.pm$/,
94 21         923 map { File::Basename::basename($_) }
  25         643  
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         25 map { s/::$//; "FFI::Build::File::$_" }
  6         16  
  6         19  
104             grep !/Base::/,
105             grep /::$/,
106             keys %{FFI::Build::File::};
107              
108 2         20 @file_classes = List::Util::uniq(@file_classes);
109 2         8 foreach my $class (@file_classes)
110             {
111 10 100       19 next if(eval { $class->can('new') });
  10         119  
112 4         14 my $pm = $class . ".pm";
113 4         20 $pm =~ s/::/\//g;
114 4         1728 require $pm;
115             }
116             }
117 28         95 @file_classes;
118             }
119              
120              
121             sub source
122             {
123 29     29 1 1271 my($self, @file_spec) = @_;
124            
125 29         89 foreach my $file_spec (@file_spec)
126             {
127 28 100       43 if(eval { $file_spec->isa('FFI::Build::File::Base') })
  28         218  
128             {
129 2         3 push @{ $self->{source} }, $file_spec;
  2         4  
130 2         5 next;
131             }
132 26         1491 my @paths = File::Glob::bsd_glob($file_spec);
133             path:
134 26         98 foreach my $path (@paths)
135             {
136 27         74 foreach my $class (_file_classes)
137             {
138 29         151 foreach my $regex ($class->accept_suffix)
139             {
140 29 100       197 if($path =~ $regex)
141             {
142 27         42 push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
  27         73  
143 27         93 next path;
144             }
145             }
146             }
147 0         0 Carp::croak("Unknown file type: $path");
148             }
149             }
150            
151 29         49 @{ $self->{source} };
  29         117  
152             }
153              
154              
155             sub build
156             {
157 5     5 1 5672 my($self) = @_;
158              
159 5         12 my @objects;
160            
161 5         13 my $ld = $self->platform->ld;
162            
163 5         29 foreach my $source ($self->source)
164             {
165 12 100       94 $ld = $source->ld if $source->ld;
166 12         24 my $output;
167 12         97 while(my $next = $source->build_item)
168             {
169 12 50       194 $ld = $next->ld if $next->ld;
170 12         117 $output = $source = $next;
171             }
172 12         120 push @objects, $output;
173             }
174            
175             my $needs_rebuild = sub {
176 5     5   32 my(@objects) = @_;
177 5 50       54 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         147 };
186            
187 5 50       62 return $self->file unless $needs_rebuild->(@objects);
188            
189 5         30 File::Path::mkpath($self->file->dirname, 0, 0755);
190            
191             my @cmd = (
192             $ld,
193             $self->platform->ldflags,
194 12         53 (map { "$_" } @objects),
195 5         80 @{ $self->libs },
  5         30  
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   7890 print "+ @cmd\n";
202 5         232299 system @cmd;
203 5         511 });
204            
205 5 50 33     7699 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         155 print $out;
213             }
214            
215 5         32 $self->file;
216             }
217              
218              
219             sub clean
220             {
221 5     5 1 2421 my($self) = @_;
222 5         28 my $dll = $self->file->path;
223 5 50       420 unlink $dll if -f $dll;
224 5         47 foreach my $source ($self->source)
225             {
226 12         70 my $dir = File::Spec->catdir($source->dirname, $self->buildname);
227 12 100       218 if(-d $dir)
228             {
229 4         765 unlink $_ for File::Glob::bsd_glob("$dir/*");
230 4         251 rmdir $dir;
231             }
232             }
233             }
234              
235             1;
236              
237             __END__