1package FFI::Build::MM; 2 3use strict; 4use warnings; 5use 5.008004; 6use Carp (); 7use FFI::Build; 8use JSON::PP (); 9use File::Glob (); 10use File::Basename (); 11use File::Path (); 12use File::Copy (); 13use ExtUtils::MakeMaker 7.12; 14 15# ABSTRACT: FFI::Build installer code for ExtUtils::MakeMaker 16our $VERSION = '1.56'; # VERSION 17 18 19sub new 20{ 21 my($class, %opt) = @_; 22 23 my $save = defined $opt{save} ? $opt{save} : 1; 24 25 my $self = bless { save => $save }, $class; 26 $self->load_prop; 27 28 $self; 29} 30 31 32sub mm_args 33{ 34 my($self, %args) = @_; 35 36 if($args{DISTNAME}) 37 { 38 $self->{prop}->{distname} ||= $args{DISTNAME}; 39 $self->{prop}->{share} ||= "blib/lib/auto/share/dist/@{[ $self->distname ]}"; 40 $self->{prop}->{arch} ||= "blib/arch/auto/@{[ join '/', split /-/, $self->distname ]}"; 41 $self->save_prop; 42 } 43 else 44 { 45 Carp::croak "DISTNAME is required"; 46 } 47 48 49 if(my $build = $self->build) 50 { 51 foreach my $alien (@{ $build->alien }) 52 { 53 next if ref $alien; 54 $args{BUILD_REQUIRES}->{$alien} ||= 0; 55 } 56 } 57 58 if(my $test = $self->test) 59 { 60 foreach my $alien (@{ $test->alien }) 61 { 62 next if ref $alien; 63 $args{TEST_REQUIRES}->{$alien} ||= 0; 64 } 65 } 66 67 %args; 68} 69 70sub distname { shift->{prop}->{distname} } 71 72sub sharedir 73{ 74 my($self, $new) = @_; 75 76 if(defined $new) 77 { 78 $self->{prop}->{share} = $new; 79 $self->save_prop; 80 } 81 82 $self->{prop}->{share}; 83} 84 85sub archdir 86{ 87 my($self, $new) = @_; 88 89 if(defined $new) 90 { 91 $self->{prop}->{arch} = $new; 92 $self->save_prop; 93 } 94 95 $self->{prop}->{arch}; 96} 97 98sub load_build 99{ 100 my($self, $dir, $name, $install) = @_; 101 return unless -d $dir; 102 my($fbx) = File::Glob::bsd_glob("./$dir/*.fbx"); 103 104 my $options; 105 my $platform = FFI::Build::Platform->default; 106 107 if($fbx) 108 { 109 $name = File::Basename::basename($fbx); 110 $name =~ s/\.fbx$//; 111 $options = do { 112 package FFI::Build::MM::FBX; 113 our $DIR = $dir; 114 our $PLATFORM = $platform; 115 116 # make sure we catch all of the errors 117 # code copied from `perldoc -f do` 118 my $return = do $fbx; 119 unless ( $return ) { 120 Carp::croak( "couldn't parse $fbx: $@" ) if $@; 121 Carp::croak( "couldn't do $fbx: $!" ) unless defined $return; 122 Carp::croak( "couldn't run $fbx" ) unless $return; 123 } 124 125 $return; 126 }; 127 } 128 else 129 { 130 $name ||= $self->distname; 131 $options = { 132 source => ["$dir/*.c", "$dir/*.cxx", "$dir/*.cpp"], 133 }; 134 # if we see a Go, Rust control file then we assume the 135 # ffi mod is written in that language. 136 foreach my $control_file ("$dir/Cargo.toml", "$dir/go.mod") 137 { 138 if(-f $control_file) 139 { 140 $options->{source} = [$control_file]; 141 last; 142 } 143 } 144 } 145 146 $options->{platform} ||= $platform; 147 $options->{dir} ||= ref $install ? $install->($options) : $install; 148 $options->{verbose} = 1 unless defined $options->{verbose}; 149 FFI::Build->new($name, %$options); 150} 151 152sub build 153{ 154 my($self) = @_; 155 $self->{build} ||= $self->load_build('ffi', undef, $self->sharedir . "/lib"); 156} 157 158sub test 159{ 160 my($self) = @_; 161 $self->{test} ||= $self->load_build('t/ffi', 'test', sub { 162 my($opt) = @_; 163 my $buildname = $opt->{buildname} || '_build'; 164 "t/ffi/$buildname"; 165 }); 166} 167 168sub save_prop 169{ 170 my($self) = @_; 171 return unless $self->{save}; 172 open my $fh, '>', 'fbx.json'; 173 print $fh JSON::PP::encode_json($self->{prop}); 174 close $fh; 175} 176 177sub load_prop 178{ 179 my($self) = @_; 180 return unless $self->{save}; 181 unless(-f 'fbx.json') 182 { 183 $self->{prop} = {}; 184 return; 185 } 186 open my $fh, '<', 'fbx.json'; 187 $self->{prop} = JSON::PP::decode_json(do { local $/; <$fh> }); 188 close $fh; 189} 190 191sub clean 192{ 193 my($self) = @_; 194 foreach my $stage (qw( build test )) 195 { 196 my $build = $self->$stage; 197 $build->clean if $build; 198 } 199 unlink 'fbx.json' if -f 'fbx.json'; 200} 201 202 203sub mm_postamble 204{ 205 my($self) = @_; 206 207 my $postamble = ".PHONY: fbx_build ffi fbx_test ffi-test fbx_clean ffi-clean\n\n"; 208 209 # make fbx_realclean ; make clean 210 $postamble .= "realclean :: fbx_clean\n" . 211 "\n" . 212 "fbx_clean ffi-clean:\n" . 213 "\t\$(FULLPERL) -MFFI::Build::MM=cmd -e fbx_clean\n\n"; 214 215 # make fbx_build; make 216 $postamble .= "pure_all :: fbx_build\n" . 217 "\n" . 218 "fbx_build ffi:\n" . 219 "\t\$(FULLPERL) -MFFI::Build::MM=cmd -e fbx_build\n\n"; 220 221 # make fbx_test; make test 222 $postamble .= "subdirs-test_dynamic subdirs-test_static subdirs-test :: fbx_test\n" . 223 "\n" . 224 "fbx_test ffi-test:\n" . 225 "\t\$(FULLPERL) -MFFI::Build::MM=cmd -e fbx_test\n\n"; 226 227 $postamble; 228} 229 230sub action_build 231{ 232 my($self) = @_; 233 my $build = $self->build; 234 if($build) 235 { 236 my $lib = $build->build; 237 if($self->archdir) 238 { 239 File::Path::mkpath($self->archdir, 0, oct(755)); 240 my $archfile = File::Spec->catfile($self->archdir, File::Basename::basename($self->archdir) . ".txt"); 241 open my $fh, '>', $archfile; 242 my $lib_path = $lib->path; 243 $lib_path =~ s/^blib\/lib\///; 244 print $fh "FFI::Build\@$lib_path\n"; 245 close $fh; 246 } 247 } 248 return; 249} 250 251sub action_test 252{ 253 my($self) = @_; 254 my $build = $self->test; 255 $build->build if $build; 256} 257 258sub action_clean 259{ 260 my($self) = @_; 261 my $build = $self->clean; 262 (); 263} 264 265sub import 266{ 267 my(undef, @args) = @_; 268 foreach my $arg (@args) 269 { 270 if($arg eq 'cmd') 271 { 272 package main; 273 274 my $mm = sub { 275 my($action) = @_; 276 my $build = FFI::Build::MM->new; 277 $build->$action; 278 }; 279 280 no warnings 'once'; 281 282 *fbx_build = sub { 283 $mm->('action_build'); 284 }; 285 286 *fbx_test = sub { 287 $mm->('action_test'); 288 }; 289 290 *fbx_clean = sub { 291 $mm->('action_clean'); 292 }; 293 } 294 } 295} 296 2971; 298 299__END__ 300 301=pod 302 303=encoding UTF-8 304 305=head1 NAME 306 307FFI::Build::MM - FFI::Build installer code for ExtUtils::MakeMaker 308 309=head1 VERSION 310 311version 1.56 312 313=head1 SYNOPSIS 314 315In your Makefile.PL: 316 317 use ExtUtils::MakeMaker; 318 use FFI::Build::MM; 319 320 my $fbmm = FFI::Build::MM->new; 321 322 WriteMakefile($fbmm->mm_args( 323 ABSTRACT => 'My FFI extension', 324 DISTNAME => 'Foo-Bar-Baz-FFI', 325 NAME => 'Foo::Bar::Baz::FFI', 326 VERSION_FROM => 'lib/Foo/Bar/Baz/FFI.pm', 327 ... 328 )); 329 330 sub MY::postamble { 331 $fbmm->mm_postamble; 332 } 333 334Then put the C, C++ or Fortran files in C<./ffi> for your runtime library 335and C<./t/ffi> for your test time library. 336 337=head1 DESCRIPTION 338 339This module provides a thin layer between L<FFI::Build> and L<ExtUtils::MakeMaker>. 340Its interface is influenced by the design of L<Alien::Build::MM>. The idea is that 341for your distribution you throw some C, C++ or Fortran source files into a directory 342called C<ffi> and these files will be compiled and linked into a library that can 343be used by your module. There is a control file C<ffi/*.fbx> which can be used to 344control the compiler and linker options. (options passed directly into L<FFI::Build>). 345The interface for this file is still under development. 346 347=head1 CONSTRUCTOR 348 349=head2 new 350 351 my $fbmm = FFI::Build::MM->new; 352 353Create a new instance of L<FFI::Build::MM>. 354 355=head1 METHODS 356 357=head2 mm_args 358 359 my %new_args = $fbmm->mm_args(%old_args); 360 361This method does two things: 362 363=over 4 364 365=item reads the arguments to determine sensible defaults (library name, install location, etc). 366 367=item adjusts the arguments as necessary and returns an updated set of arguments. 368 369=back 370 371=head2 mm_postamble 372 373 my $postamble = $fbmm->mm_postamble; 374 375This returns the Makefile postamble used by L<ExtUtils::MakeMaker>. The synopsis above for 376how to invoke it properly. It adds the following Make targets: 377 378=over 4 379 380=item fbx_build / ffi 381 382build the main runtime library in C<./ffi>. 383 384=item fbx_test / ffi-test 385 386Build the test library in C<./t/ffi>. 387 388=item fbx_clean / ffi-clean 389 390Clean any runtime or test libraries already built. 391 392=back 393 394Normally you do not need to build these targets manually, they will be built automatically 395at the appropriate stage. 396 397=head1 AUTHOR 398 399Author: Graham Ollis E<lt>plicease@cpan.orgE<gt> 400 401Contributors: 402 403Bakkiaraj Murugesan (bakkiaraj) 404 405Dylan Cali (calid) 406 407pipcet 408 409Zaki Mughal (zmughal) 410 411Fitz Elliott (felliott) 412 413Vickenty Fesunov (vyf) 414 415Gregor Herrmann (gregoa) 416 417Shlomi Fish (shlomif) 418 419Damyan Ivanov 420 421Ilya Pavlov (Ilya33) 422 423Petr Písař (ppisar) 424 425Mohammad S Anwar (MANWAR) 426 427Håkon Hægland (hakonhagland, HAKONH) 428 429Meredith (merrilymeredith, MHOWARD) 430 431Diab Jerius (DJERIUS) 432 433Eric Brine (IKEGAMI) 434 435szTheory 436 437José Joaquín Atria (JJATRIA) 438 439Pete Houston (openstrike, HOUSTON) 440 441=head1 COPYRIGHT AND LICENSE 442 443This software is copyright (c) 2015,2016,2017,2018,2019,2020 by Graham Ollis. 444 445This is free software; you can redistribute it and/or modify it under 446the same terms as the Perl 5 programming language system itself. 447 448=cut 449