1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
|
package FFI::Build;
use strict;
use warnings;
use 5.008004;
use FFI::Build::Plugin;
use FFI::Build::PluginData qw( plugin_data );
use FFI::Build::File::Library;
use Carp ();
use File::Glob ();
use File::Basename ();
use List::Util 1.45 ();
use Capture::Tiny ();
use File::Path ();
# ABSTRACT: Build shared libraries for use with FFI
our $VERSION = '2.10'; # VERSION
# Platypus-Man,
# Platypus-Man,
# Friendly Neighborhood Platypus-Man
# Is He Strong?
# Listen Bud
# He's got Proportional Strength of a Platypus
# Hey Man!
# There Goes The Platypus-Man
{
my $plugins = FFI::Build::Plugin->new;
# PLUGIN: require
# ARGS: NONE
$plugins->call('build-require');
sub _plugins { $plugins ||= FFI::Build::Plugin->new };
}
sub import
{
my @caller = caller;
# PLUGIN: import
# ARGS: @caller, \@args
_plugins->call('build-import', \@caller, \@_);
}
sub _native_name
{
my($self, $name) = @_;
join '', $self->platform->library_prefix, $name, scalar $self->platform->library_suffix;
}
sub new
{
my($class, $name, %args) = @_;
Carp::croak "name is required" unless defined $name;
# PLUGIN: new-pre
# ARGS: $name, \%args
_plugins->call('build-new-pre', $name, \%args);
my $self = bless {
source => [],
cflags_I => [],
cflags => [],
libs_L => [],
libs => [],
alien => [],
}, $class;
my $platform = $self->{platform} = $args{platform} || FFI::Build::Platform->default;
my $file = $self->{file} = $args{file} || FFI::Build::File::Library->new([$args{dir} || '.', $self->_native_name($name)], platform => $self->platform);
my $buildname = $self->{buildname} = $args{buildname} || '_build';
my $verbose = $self->{verbose} = $args{verbose} || 0;
my $export = $self->{export} = $args{export} || [];
$self->{verbose} = $verbose = 2 if $ENV{V};
if(defined $args{cflags})
{
my @flags = ref $args{cflags} ? @{ $args{cflags} } : $self->platform->shellwords($args{cflags});
push @{ $self->{cflags} }, grep !/^-I/, @flags;
push @{ $self->{cflags_I} }, grep /^-I/, @flags;
}
if(defined $args{libs})
{
my @flags = ref $args{libs} ? @{ $args{libs} } : $self->platform->shellwords($args{libs});
push @{ $self->{libs} }, grep !/^-L/, @flags;
push @{ $self->{libs_L} }, grep /^-L/, @flags;
}
if(defined $args{alien})
{
my @aliens = ref $args{alien} ? @{ $args{alien} } : ($args{alien});
foreach my $alien (@aliens)
{
unless(eval { $alien->can('cflags') && $alien->can('libs') })
{
my $pm = "$alien.pm";
$pm =~ s/::/\//g;
require $pm;
}
push @{ $self->{alien} }, $alien;
push @{ $self->{cflags} }, grep !/^-I/, $self->platform->shellwords($alien->cflags);
push @{ $self->{cflags_I} }, grep /^-I/, $self->platform->shellwords($alien->cflags);
push @{ $self->{libs} }, grep !/^-L/, $self->platform->shellwords($alien->libs);
push @{ $self->{libs_L} }, grep /^-L/, $self->platform->shellwords($alien->libs);
}
}
$self->source(ref $args{source} ? @{ $args{source} } : ($args{source})) if $args{source};
# PLUGIN: new-post
# ARGS: $self
_plugins->call('build-new-post', $self);
$self;
}
sub buildname { shift->{buildname} }
sub export { shift->{export} }
sub file { shift->{file} }
sub platform { shift->{platform} }
sub verbose { shift->{verbose} }
sub cflags { shift->{cflags} }
sub cflags_I { shift->{cflags_I} }
sub libs { shift->{libs} }
sub libs_L { shift->{libs_L} }
sub alien { shift->{alien} }
my @file_classes;
sub _file_classes
{
unless(@file_classes)
{
if(defined $FFI::Build::VERSION)
{
foreach my $inc (@INC)
{
push @file_classes,
map { my $f = $_; $f =~ s/\.pm$//; "FFI::Build::File::$f" }
grep !/^Base\.pm$/,
map { File::Basename::basename($_) }
File::Glob::bsd_glob(
File::Spec->catfile($inc, 'FFI', 'Build', 'File', '*.pm')
);
}
}
else
{
# When building out of git without dzil, $VERSION will not
# usually be defined and any file plugins that require a
# specific version will break, so we only use core file
# classes for that.
push @file_classes, map { "FFI::Build::File::$_" } qw( C CXX Library Object );
}
# also anything already loaded, that might not be in the
# @INC path (for testing ususally)
push @file_classes,
map { my $f = $_; $f =~ s/::$//; "FFI::Build::File::$f" }
grep !/Base::/,
grep /::$/,
keys %{FFI::Build::File::};
@file_classes = List::Util::uniq(@file_classes);
foreach my $class (@file_classes)
{
next if(eval { $class->can('new') });
my $pm = $class . ".pm";
$pm =~ s/::/\//g;
require $pm;
}
}
@file_classes;
}
sub source
{
my($self, @file_spec) = @_;
foreach my $file_spec (@file_spec)
{
if(eval { $file_spec->isa('FFI::Build::File::Base') })
{
push @{ $self->{source} }, $file_spec;
next;
}
if(ref $file_spec eq 'ARRAY')
{
my($type, $content, @args) = @$file_spec;
my $class = "FFI::Build::File::$type";
unless($class->can('new'))
{
my $pm = "FFI/Build/File/$type.pm";
require $pm;
}
push @{ $self->{source} }, $class->new(
$content,
build => $self,
platform => $self->platform,
@args
);
next;
}
my @paths = File::Glob::bsd_glob($file_spec);
path:
foreach my $path (@paths)
{
foreach my $class (_file_classes)
{
foreach my $regex ($class->accept_suffix)
{
if($path =~ $regex)
{
push @{ $self->{source} }, $class->new($path, platform => $self->platform, build => $self);
next path;
}
}
}
Carp::croak("Unknown file type: $path");
}
}
@{ $self->{source} };
}
sub build
{
my($self) = @_;
# PLUGIN: build
# ARGS: $self
_plugins->call('build-build', $self);
my @objects;
my $ld = $self->platform->ld;
foreach my $source ($self->source)
{
# PLUGIN: build-item
# ARGS: $self, $source
_plugins->call('build-build-item', $self, $source);
if($source->can('build_all'))
{
my $count = scalar $self->source;
if($count == 1)
{
return $source->build_all($self->file);
}
else
{
die "@{[ ref $source ]} has build_all method, but there is not exactly one source";
}
}
$ld = $source->ld if $source->ld;
my $output;
while(my $next = $source->build_item)
{
$ld = $next->ld if $next->ld;
$output = $source = $next;
}
push @objects, $output;
}
my $needs_rebuild = sub {
my(@objects) = @_;
return 1 unless -f $self->file->path;
my $target_time = [stat $self->file->path]->[9];
foreach my $object (@objects)
{
my $object_time = [stat "$object"]->[9];
return 1 if $object_time > $target_time;
}
return 0;
};
return $self->file unless $needs_rebuild->(@objects);
File::Path::mkpath($self->file->dirname, 0, oct(755));
my @cmd = (
$ld,
$self->libs_L,
$self->platform->ldflags,
(map { "$_" } @objects),
$self->libs,
$self->platform->flag_export(@{ $self->export }),
$self->platform->flag_library_output($self->file->path),
);
# PLUGIN: build-link
# ARGS: $self, \@cmd
_plugins->call('build-build-link', $self, \@cmd);
my($out, $exit) = Capture::Tiny::capture_merged(sub {
$self->platform->run(@cmd);
});
if($exit || !-f $self->file->path)
{
print $out;
die "error building @{[ $self->file->path ]} from @objects";
}
elsif($self->verbose >= 2)
{
print $out;
}
elsif($self->verbose >= 1)
{
print "LD @{[ $self->file->path ]}\n";
}
# PLUGIN: link-postlink
# ARGS: $self, \@cmd
_plugins->call('build-build-postlink', $self);
$self->file;
}
sub clean
{
my($self) = @_;
my $dll = $self->file->path;
if(-f $dll)
{
# PLUGIN: clean
# ARGS: $self, $path
_plugins->call('build-clean', $self, $dll);
unlink $dll;
}
foreach my $source ($self->source)
{
my $dir = File::Spec->catdir($source->dirname, $self->buildname);
if(-d $dir)
{
foreach my $path (File::Glob::bsd_glob("$dir/*"))
{
_plugins->call('build-clean', $self, $path);
unlink $path;
}
_plugins->call('build-clean', $self, $dir);
rmdir $dir;
}
}
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
FFI::Build - Build shared libraries for use with FFI
=head1 VERSION
version 2.10
=head1 SYNOPSIS
use FFI::Platypus 2.00;
use FFI::Build;
my $build = FFI::Build->new(
'frooble',
source => 'ffi/*.c',
);
# $lib is an instance of FFI::Build::File::Library
my $lib = $build->build;
my $ffi = FFI::Platypus->new( api => 2 );
# The filename will be platform dependant, but something like libfrooble.so or frooble.dll
$ffi->lib($lib->path);
... # use $ffi to attach functions in ffi/*.c
=head1 DESCRIPTION
Using libffi based L<FFI::Platypus> is a great alternative to XS for writing library bindings for Perl.
Sometimes, however, you need to bundle a little C code with your FFI module, but this has never been
that easy to use. L<Module::Build::FFI> was an early attempt to address this use case, but it uses
the now out of fashion L<Module::Build>.
This module itself doesn't directly integrate with CPAN installers like L<ExtUtils::MakeMaker> or
L<Module::Build>, but there is a light weight layer L<FFI::Build::MM> that will allow you to easily
use this module with L<ExtUtils::MakeMaker>. If you are using L<Dist::Zilla> as your dist builder,
then there is also L<Dist::Zilla::Plugin::FFI::Build>, which will help with the connections.
There is some functional overlap with L<ExtUtils::CBuilder>, which was in fact used by L<Module::Build::FFI>.
For this iteration I have decided not to use that module because although it will generate dynamic libraries
that can sometimes be used by L<FFI::Platypus>, it is really designed for building XS modules, and trying
to coerce it into a more general solution has proved difficult in the past.
Supported languages out of the box are C, C++ and Fortran. Rust is supported via a language plugin,
see L<FFI::Platypus::Lang::Rust>.
=head1 CONSTRUCTOR
=head2 new
my $build = FFI::Build->new($name, %options);
Create an instance of this class. The C<$name> argument is used when computing the file name for
the library. The actual name will be something like C<lib$name.so> or C<$name.dll>. The following
options are supported:
=over 4
=item alien
List of Aliens to compile/link against. L<FFI::Build> will work with any L<Alien::Base> based
alien, or modules that provide a compatible API.
=item buildname
Directory name that will be used for building intermediate files, such as object files. This is
C<_build> by default.
=item cflags
Extra compiler flags to use. Things like C<-I/foo/include> or C<-DFOO=1>.
=item dir
The directory where the library will be written. This is C<.> by default.
=item export
Functions that should be exported (Windows + Visual C++ only)
=item file
An instance of L<FFI::Build::File::Library> to which the library will be written. Normally not needed.
=item libs
Extra library flags to use. Things like C<-L/foo/lib -lfoo>.
=item platform
An instance of L<FFI::Build::Platform>. Usually you want to omit this and use the default instance.
=item source
List of source files. You can use wildcards supported by C<bsd_glob> from L<File::Glob>.
=item verbose
By default this class does not print out the actual compiler and linker commands used in building
the library unless there is a failure. You can alter this behavior with this option. Set to
one of these values:
=over 4
=item zero (0)
Default, quiet unless there is a failure.
=item one (1)
Output the operation (compile, link, etc) and the file, but nothing else
=item two (2)
Output the complete commands run verbatim.
=back
If the environment variable C<V> is set to a true value then the verbosity will be set to C<2> regardless
of what is passed in.
=back
=head1 METHODS
=head2 dir
my $dir = $build->dir;
Returns the directory where the library will be written.
=head2 buildname
my $builddir = $build->builddir;
Returns the build name. This is used in computing a directory to save intermediate files like objects. For example,
if you specify a file like C<ffi/foo.c>, then the object file will be stored in C<ffi/_build/foo.o> by default.
C<_build> in this example (the default) is the build name.
=head2 export
my $exports = $build->export;
Returns a array reference of the exported functions (Windows + Visual C++ only)
=head2 file
my $file = $build->file;
Returns an instance of L<FFI::Build::File::Library> corresponding to the library being built. This is
also returned by the C<build> method below.
=head2 platform
my $platform = $build->platform;
An instance of L<FFI::Build::Platform>, which contains information about the platform on which you are building.
The default is usually reasonable.
=head2 verbose
my $verbose = $build->verbose;
Returns the verbose flag.
=head2 cflags
my @cflags = @{ $build->cflags };
Returns the compiler flags.
=head2 cflags_I
my @cflags_I = @{ $build->cflags_I };
Returns the C<-I> cflags.
=head2 libs
my @libs = @{ $build->libs };
Returns the library flags.
=head2 libs_L
my @libs = @{ $build->libs };
Returns the C<-L> library flags.
=head2 alien
my @aliens = @{ $build->alien };
Returns a the list of aliens being used.
=head2 source
$build->source(@files);
Add the C<@files> to the list of source files that will be used in building the library.
The format is the same as with the C<source> attribute above.
=head2 build
my $lib = $build->build;
This compiles the source files and links the library. Files that have already been compiled or linked
may be reused without recompiling/linking if the timestamps are newer than the source files. An instance
of L<FFI::Build::File::Library> is returned which can be used to get the path to the library, which can
be feed into L<FFI::Platypus> or similar.
=head2 clean
$build->clean;
Removes the library and intermediate files.
=head1 AUTHOR
Author: Graham Ollis E<lt>plicease@cpan.orgE<gt>
Contributors:
Bakkiaraj Murugesan (bakkiaraj)
Dylan Cali (calid)
pipcet
Zaki Mughal (zmughal)
Fitz Elliott (felliott)
Vickenty Fesunov (vyf)
Gregor Herrmann (gregoa)
Shlomi Fish (shlomif)
Damyan Ivanov
Ilya Pavlov (Ilya33)
Petr Písař (ppisar)
Mohammad S Anwar (MANWAR)
Håkon Hægland (hakonhagland, HAKONH)
Meredith (merrilymeredith, MHOWARD)
Diab Jerius (DJERIUS)
Eric Brine (IKEGAMI)
szTheory
José Joaquín Atria (JJATRIA)
Pete Houston (openstrike, HOUSTON)
Lukas Mai (MAUKE)
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2015-2022 by Graham Ollis.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
|