The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Alien::make::Module::Build;

use strict;
use warnings;

use base qw( Module::Build );

use File::Basename qw( dirname );
use File::Spec;
use File::Path 2.07 qw( make_path );

use constant SRCDIR => "src";

BEGIN {
   # GNU make is called 'gmake' on most non-Linux platforms, gnumake on Dariwn.
   # Rather than hardcode it we'll try to identify a suitable command by inspection
   foreach my $make (qw( make gmake gnumake )) {
      no warnings 'exec';
      my $output = `$make --version`;
      next if $?;
      next unless $output =~ m/^GNU Make /;

      constant->import( MAKE => $make );
      last;
   }
   die "OS unsupported - unable to find GNU make\n" unless defined &MAKE;

   # GNU libtool is called 'glibtool' on Darwin
   foreach my $libtool (qw( libtool glibtool )) {
      no warnings 'exec';
      my $output = `$libtool --version`;
      next if $?;
      next unless $output =~ m/^.*\(GNU libtool\)/;

      constant->import( LIBTOOL => $libtool );
      last;
   }
   die "OS unsupported - unable to find GNU libtool\n" unless defined &LIBTOOL;

}

use constant MAKEARGS => "LIBTOOL=".LIBTOOL;

__PACKAGE__->add_property( 'tarball' );
__PACKAGE__->add_property( 'pkgconfig_module' );
__PACKAGE__->add_property( 'pkgconfig_version' );

sub new
{
   my $class = shift;
   my %args = @_;

   my $use_bundled = 0;

   $args{get_options}{bundled} = {
      store => \$use_bundled,
      type  => "+",
   };

   my $self = $class->SUPER::new( %args );

   my $module = $self->pkgconfig_module;
   my $version = $self->pkgconfig_version;

   $use_bundled = 1 if
      !$use_bundled and system( "pkg-config", $module, "--atleast-version", $version ) != 0;

   if( $use_bundled ) {
      print "Building bundled source\n";
   }
   else {
      print "Detected $module version >= $version from pkg-config\n";
   }

   $self->notes( use_bundled => $use_bundled );

   return $self;
}

sub _srcdir
{
   my $self = shift;
   return File::Spec->catdir( $self->base_dir, SRCDIR );
}

sub _stampfile
{
   my $self = shift;
   my ( $name ) = @_;

   return File::Spec->catfile( $self->base_dir, ".$name-stamp" );
}

sub in_srcdir
{
   my $self = shift;

   chdir( $self->_srcdir ) or
      die "Unable to chdir to srcdir - $!";

   shift->();
}

sub make_in_srcdir
{
   my $self = shift;
   my @args = @_;

   $self->in_srcdir( sub {
      system( MAKE, MAKEARGS, @args ) == 0 or
         die "Unable to make - $!";
   } );
}

sub ACTION_src
{
   my $self = shift;

   return unless $self->notes( 'use_bundled' );

   -d $self->_srcdir and return;

   my $tarball = $self->tarball;

   system( "tar", "xzf", $tarball ) == 0 or
      die "Unable to untar $tarball - $!";

   ( my $untardir = $tarball ) =~ s{\.tar\.[a-z]+$}{};

   -d $untardir or
      die "Expected to find a directory called $untardir\n";

   rename( $untardir, $self->_srcdir ) or
      die "Unable to rename src dir - $!";
}

sub ACTION_code
{
   my $self = shift;

   my $blib = File::Spec->catdir( $self->base_dir, "blib" );

   my $libdir = File::Spec->catdir( $blib, "arch" );
   my $incdir = File::Spec->catdir( $libdir, "include" );
   my $mandir = File::Spec->catdir( $blib, "libdoc" );

   # All these at least must exist
   -d $_ or mkdir $_ for $blib, $libdir;

   my $pkgconfig_module = $self->pkgconfig_module;

   my $buildstamp = $self->_stampfile( "build" );

   if( $self->notes( 'use_bundled' ) and !-f $buildstamp ) {
      $self->depends_on( 'src' );

      $self->make_in_srcdir( () );

      $self->make_in_srcdir( "install",
         "LIBDIR=$libdir",
         "INCDIR=$incdir",
         "MAN3DIR=$mandir",
         "MAN7DIR=$mandir",
      );

      open( my $stamp, ">", $buildstamp ) or die "Unable to touch .build-stamp file - $!";

      # Now hack the .pc file to put ${libdir}-relative paths in it
      my $pcfile = "$libdir/pkgconfig/$pkgconfig_module.pc";
      if( -f $pcfile ) {
         open my $in, "<", $pcfile or die "Cannot open $pcfile for reading - $!";
         open my $out, ">", "$pcfile.new" or die "Cannot open $pcfile.new for writing - $!";

         while( <$in> ) {
            s{\Q$libdir\E}{\${libdir}}g;
            print { $out } $_;
         }

         # Cygwin/Windows doesn't like it when you rename open files
         close $in;
         close $out;

         rename "$pcfile.new", $pcfile or die "Cannot rename $pcfile.new to $pcfile - $!";
      }
   }

   my @module_file = split m/::/, $self->module_name . ".pm";
   my $srcfile = File::Spec->catfile( $self->base_dir, "lib", @module_file );
   my $dstfile = File::Spec->catfile( $blib, "lib", @module_file );

   unless( $self->up_to_date( $srcfile, $dstfile ) ) {
      my %replace = (
         USE_BUNDLED      => $self->notes( 'use_bundled' ),
         PKGCONFIG_MODULE => $pkgconfig_module,
      );

      # Turn ' into \' in replacements
      s/'/\\'/g for values %replace;

      $self->cp_file_with_replacement(
         srcfile => $srcfile,
         dstfile => $dstfile,
         replace => \%replace,
      );
   }
}

sub cp_file_with_replacement
{
   my $self = shift;
   my %args = @_;

   my $srcfile = $args{srcfile};
   my $dstfile = $args{dstfile};
   my $replace = $args{replace};

   make_path( dirname( $dstfile ), { mode => 0777 } );

   open( my $inh,  "<", $srcfile ) or die "Cannot read $srcfile - $!";
   open( my $outh, ">", $dstfile ) or die "Cannot write $dstfile - $!";

   while( my $line = <$inh> ) {
      $line =~ s/\@$_\@/$replace->{$_}/g for keys %$replace;
      print $outh $line;
   }
}

sub ACTION_test
{
   my $self = shift;

   return unless $self->notes( 'use_bundled' );

   $self->depends_on( "code" );

   $self->make_in_srcdir( "test" );
}

sub ACTION_clean
{
   my $self = shift;

   if( $self->notes( 'use_bundled' ) ) {
      if( -d $self->_srcdir ) {
         $self->make_in_srcdir( "clean" );
      }

      unlink( $self->_stampfile( "build" ) );
   }

   $self->SUPER::ACTION_clean;
}

sub ACTION_realclean
{
   my $self = shift;

   if( -d $self->_srcdir ) {
      system( "rm", "-rf", $self->_srcdir ); # best effort; ignore failure
   }

   $self->SUPER::ACTION_realclean;
}

0x55AA;