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