Alien-Base-ModuleBuild

 view release on metacpan or  search on metacpan

lib/Alien/Base/ModuleBuild.pm  view on Meta::CPAN

#   |-- (non-api) connection: holder for Net::FTP-like object (needs cwd, ls, and get methods)
__PACKAGE__->add_property( 'alien_repository'         => [] );
__PACKAGE__->add_property( 'alien_repository_default' => {} );
__PACKAGE__->add_property( 'alien_repository_class'   => {} );

# alien_isolate_dynamic
__PACKAGE__->add_property( 'alien_isolate_dynamic' => 0 );
__PACKAGE__->add_property( 'alien_autoconf_with_pic' => 1 );

# alien_inline_auto_include
__PACKAGE__->add_property( 'alien_inline_auto_include' => [] );

# use MSYS even if %c isn't found
__PACKAGE__->add_property( 'alien_msys' => 0 );

# Alien packages that provide build dependencies
__PACKAGE__->add_property( 'alien_bin_requires' => {} );

# Do a staged install to blib instead of trying to install to the final location.
__PACKAGE__->add_property( 'alien_stage_install' => 1 );

# Should modules be installed into arch specific directory?
# Most alien dists will have arch specific files in their share so it makes sense to install
# the module in the arch specific location.  If you are alienizing something that isn't arch
# specific, like javascript source or java byte code, then you'd want to set this to 0.
# For now this will default to off.  See gh#119 for a discussion.
__PACKAGE__->add_property( 'alien_arch' => defined $ENV{ALIEN_ARCH} ? $ENV{ALIEN_ARCH} : 0 );

__PACKAGE__->add_property( 'alien_helper' => {} );

__PACKAGE__->add_property( 'alien_env' => {} );

# Extra enviroment variable to effect to "configure"
__PACKAGE__->add_property( 'alien_extra_site_config' => {} );

################
#  ConfigData  #
################

# working_directory: full path to the extracted source or binary of the library
# pkgconfig: hashref of A::B::PkgConfig objects created from .pc file found in working_directory
# install_type: either system or share
# version: version number installed or available
# Cflags: holder for cflags if manually specified
# Libs:   same but libs
# name: holder for name as needed by pkg-config
# finished_installing: set to true once ACTION_install is finished, this helps testing now and real checks later

############################
#  Initialization Methods  #
############################

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

  # merge default and user-defined repository classes
  $args{alien_repository_class}{$_} ||= $default_repository_class{$_}
    for keys %default_repository_class;

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

  ## Recheck Force System
  if(!defined($ENV{ALIEN_INSTALL_TYPE}) && !defined($ENV{ALIEN_FORCE}) && defined($self->alien_install_type)) {
    if   ($self->alien_install_type eq 'share' ) { $Force = 1; $ForceSystem = 0; }
    elsif($self->alien_install_type eq 'system') { $Force = 0; $ForceSystem = 1; }
  }

  $self->config_data("Force" => $Force);
  $self->config_data("ForceSystem" => $ForceSystem);

  $self->alien_helper->{pkg_config} = 'Alien::Base::PkgConfig->pkg_config_command'
    unless defined $self->alien_helper->{pkg_config};

  my $ab_version = eval {
    require Alien::Base;
    Alien::Base->VERSION;
  };
  $ab_version ||= 0;

  # setup additional temporary directories, and yes we have to add File::ShareDir manually
  if($ab_version < 0.77) {
    $self->_add_prereq( 'requires', 'File::ShareDir', '1.00' );
  }

  # this just gets passed from the Build.PL to the config so that it can
  # be used by the auto_include method
  $self->config_data( 'inline_auto_include' => $self->alien_inline_auto_include );

  if($Force || !$self->alien_check_installed_version) {
    if (any { /(?<!\%)\%c/ } map { ref($_) ? @{$_} : $_ } @{ $self->alien_build_commands }) {
      $self->config_data( 'autoconf' => 1 );
    }

    if ($^O eq 'MSWin32' && ($self->config_data( 'autoconf') || $self->alien_msys)) {
      $self->_add_prereq( 'build_requires', 'Alien::MSYS', '0' );
      $self->config_data( 'msys' => 1 );
    } else {
      $self->config_data( 'msys' => 0 );
    }

    foreach my $tool (keys %{ $self->alien_bin_requires  }) {
      my $version = $self->alien_bin_requires->{$tool};
      if($tool eq 'Alien::CMake' && $version < 0.07) {
        $version = '0.07';
      }
      $self->_add_prereq( 'build_requires', $tool, $version );
    }

    my @repos = ref $args{alien_repository} eq 'ARRAY'
      ? @{ $args{alien_repository} }
      : ( $args{alien_repository} );

    foreach my $repo (@repos)
    {
      next unless defined $repo;
      if(($repo->{protocol}||'') eq 'https')
      {
        $self->_add_prereq( 'build_requires', 'IO::Socket::SSL', '1.56' );
        $self->_add_prereq( 'build_requires', 'Net::SSLeay',     '1.49' );
      }

lib/Alien/Base/ModuleBuild.pm  view on Meta::CPAN

  my $self = shift;
  my $temp_dir = $self->alien_temp_dir;
  my $share_dir = $self->alien_share_dir;

  # make sure we are in base_dir
  local $CWD = $self->base_dir;

  unless ( -d $temp_dir ) {
    mkdir $temp_dir or croak "Could not create temporary directory $temp_dir";
  }
  $self->add_to_cleanup( $temp_dir );

  unless ( -d $share_dir ) {
    mkdir $share_dir or croak "Could not create temporary directory $share_dir";
  }
  $self->add_to_cleanup( $share_dir );

  # add share dir to share dir list
  my $share_dirs = $self->share_dir;
  unshift @{ $share_dirs->{dist} }, $share_dir;
  $self->share_dir( $share_dirs );
  {
    local $CWD = $share_dir;
    open my $fh, '>', 'README' or die "Could not open README for writing (in directory $share_dir)\n";
    print $fh <<'END';
This README file is autogenerated by Alien::Base.

Currently it exists for testing purposes, but it might eventually contain information about the file(s) installed.
END
  }
}

####################
#  ACTION methods  #
####################

sub ACTION_alien_fakebuild {
  my $self = shift;

  # needed for any helpers provided by alien_bin_requires
  $self->_alien_bin_require($_) for keys %{ $self->alien_bin_requires };

  print "# Build\n";
  foreach my $cmd (@{ $self->alien_build_commands })
  {
    my @cmd = map { $self->alien_interpolate($_) } ref($cmd) ? @$cmd : ($cmd);
    print "+ @cmd\n";
  }
  print "# Build install\n";
  foreach my $cmd (@{ $self->alien_install_commands })
  {
    my @cmd = map { $self->alien_interpolate($_) } ref($cmd) ? @$cmd : ($cmd);
    print "+ @cmd\n";
  }
}

sub ACTION_code {
  my $self = shift;
  $self->notes( 'alien_blib_scheme' => $self->alien_detect_blib_scheme );

  # PLEASE NOTE, BOTH BRANCHES CALL SUPER::ACTION_code !!!!!!!
  if ( $self->notes('ACTION_alien_completed') ) {

    $self->SUPER::ACTION_code;

  } else {

    $self->depends_on('alien_code');
    $self->SUPER::ACTION_code;
  }

  my $module = $self->module_name;
  my $file   = File::Spec->catfile($self->blib, 'lib', split /::/, "$module\::Install::Files.pm");
  unless (-e $file) {
    mkpath(File::Spec->catdir($self->blib, 'lib', split /::/, "$module\::Install"), { verbose => 0 });
    open my $fh, '>', $file;
    print $fh <<EOF;
package $module\::Install::Files;
use strict;
use warnings;
require $module;
sub Inline { shift; $module->Inline(\@_) }
1;

=begin Pod::Coverage

  Inline

=end Pod::Coverage

=cut
EOF
    close $fh;
  }

  if($self->alien_arch) {
    my @parts = split /::/, $module;
    my $arch_dir = File::Spec->catdir($self->blib, 'arch', 'auto', @parts);
    File::Path::mkpath($arch_dir, 0, oct(777)) unless -d $arch_dir;
    open my $fh, '>', File::Spec->catfile($arch_dir, $parts[-1].".txt");
    print $fh "Alien based distribution with architecture specific file in share\n";
    close $fh;
  }

  $self->depends_on('alien_install') if $self->alien_stage_install;
}

sub process_share_dir_files {
  my $self = shift;
  $self->SUPER::process_share_dir_files(@_);

  # copy the compiled files into blib if running under blib scheme
  $self->depends_on('alien_install') if $self->notes('alien_blib_scheme') || $self->alien_stage_install;
}

sub alien_extract_archive {
  my ($self, $archive) = @_;
  print "Extracting Archive ... ";
  my $ae = Archive::Extract->new( archive => $archive );
  $ae->extract or croak "Archive extraction failed!";
  print "Done\n";
  return $ae->extract_path;
}

sub ACTION_alien_code {
  my $self = shift;
  local $| = 1; # don't buffer stdout

  $self->alien_init_temp_dir;

  $self->config_data( name => $self->alien_name );
  $self->config_data( ffi_name => $self->alien_ffi_name );

  my $version;
  $version = $self->alien_check_installed_version
    unless $self->config_data('Force');

  if ($version) {
    $self->config_data( install_type => 'system' );
    $self->config_data( version => $version );
    my %system_provides;
    $system_provides{Cflags} = $self->alien_provides_cflags
      if defined $self->alien_provides_cflags;
    $system_provides{Libs}   = $self->alien_provides_libs
      if defined $self->alien_provides_libs;
    $self->config_data( system_provides => \%system_provides );
    return;
  }

  if ($self->config_data('ForceSystem')) {
    die "Requested system install, but system package not detected."
  }

  my @repos = $self->alien_create_repositories;

  my $cabinet = Alien::Base::ModuleBuild::Cabinet->new;

  foreach my $repo (@repos) {
    $cabinet->add_files( $repo->probe );
  }

  $cabinet->sort_files;

  {
    local $CWD = $self->alien_temp_dir;

    my $file = $cabinet->files->[0];

    unless (defined $file) {
      die "no files found in repository";

lib/Alien/Base/ModuleBuild.pm  view on Meta::CPAN

      unless ($self->alien_do_commands('build')) {
        print "Failed\n";
        croak "Build not completed";
      }
    }

    print "Done\n";

  }

  $self->config_data( install_type => 'share' );
  $self->config_data( original_prefix => $self->alien_library_destination );

  my $pc = $self->alien_load_pkgconfig;
  my $pc_version = (
    $pc->{$self->alien_name} || $pc->{_manual}
  )->keyword('Version');

  unless (defined $version) {
    local $CWD = $self->config_data( 'working_directory' );
    $version = $self->alien_check_built_version
  }

  if (! $version && ! $pc_version) {
    print STDERR "If you are the author of this Alien dist, you may need to provide a an\n";
    print STDERR "alien_check_built_version method for your Alien::Base::ModuleBuild\n";
    print STDERR "class.  See:\n";
    print STDERR "https://metacpan.org/pod/Alien::Base::ModuleBuild#alien_check_built_version\n";
    carp "Library looks like it installed, but no version was determined";
    $self->config_data( version => 0 );
    return
  }

  if ( $version and $pc_version and versioncmp($version, $pc_version)) {
    carp "Version information extracted from the file name and pkgconfig data disagree";
  }

  $self->config_data( version => $pc_version || $version );

  # prevent building multiple times (on M::B::dispatch)
  $self->notes( 'ACTION_alien_completed' => 1 );

  return;
}

sub ACTION_alien_test {
  my $self = shift;
  print "Testing library (if applicable) ... ";
  if ($self->config_data( 'install_type' ) eq 'share') {
    if (defined (my $wdir = $self->config_data( 'working_directory' ))) {
      local $CWD = $wdir;
      $self->alien_do_commands('test') or die "Failed\n";
    }
  }
  print "Done\n";
}

sub ACTION_test {
  my $self = shift;
  $self->depends_on('alien_test');
  $self->SUPER::ACTION_test;
}

sub ACTION_install {
  my $self = shift;
  $self->SUPER::ACTION_install;
  if($self->alien_stage_install) {
    $self->alien_relocation_fixup;
  } else {
    $self->depends_on('alien_install');
  }
}

sub ACTION_alien_install {
  my $self = shift;

  local $| = 1; # don't buffer stdout

  return if $self->config_data( 'install_type' ) eq 'system';

  my $destdir = $self->destdir;
  my $target = $self->alien_library_destination;

  if(defined $destdir && !$self->alien_stage_install)
  {
    # Note: no longer necessary when doing a staged install
    # prefix the target directory with $destdir so that package builds
    # can install into a fake root
    $target = File::Spec->catdir($destdir, $target);
  }

  {
    local $CWD = $target;

    # The only form of introspection that exists is to see that the README file
    # which was placed in the share_dir (default _share) exists where we expect
    # after installation.
    unless ( -e 'README' ) {
      die "share_dir mismatch detected ($target)\n"
    }
  }

  if(!$self->config_data( 'finished_installing' ))
  {
    local $CWD = $self->config_data( 'working_directory' );
    local $ENV{DESTDIR} = $ENV{DESTDIR};
    $ENV{DESTDIR} = $destdir if defined $destdir && !$self->alien_stage_install;
    print "Installing library to $CWD ... ";
    $self->alien_do_commands('install') or die "Failed\n";
    print "Done\n";
  }

  if ( $self->alien_isolate_dynamic ) {
    local $CWD = $target;
    print "Isolating dynamic libraries ... ";
    mkdir 'dynamic' unless -d 'dynamic';
    foreach my $dir (qw( bin lib )) {
      next unless -d $dir;
      opendir(my $dh, $dir);
      my @dlls = grep { /\.so/ || /\.(dylib|bundle|la|dll|dll\.a)$/ } grep !/^\./, readdir $dh;
      closedir $dh;
      foreach my $dll (@dlls) {
        my $from = File::Spec->catfile($dir, $dll);
        my $to   = File::Spec->catfile('dynamic', $dll);
        unlink $to if -e $to;
        move($from, $to);
      }
    }
    print "Done\n";
  }

  # refresh metadata after library installation
  $self->alien_refresh_manual_pkgconfig( $self->alien_library_destination );
  $self->config_data( 'finished_installing' => 1 );

  if ( $self->notes( 'alien_blib_scheme') || $self->alien_stage_install) {

    ### TODO: empty if should be claned up before 0.017.
    ### we used to call process_files_by_extension('pm')
    ### here, but with gh#121 it is unecessary
    ## reinstall config_data to blib
    #$self->process_files_by_extension('pm');

  } else {

    # to refresh config_data
    $self->SUPER::ACTION_config_data;

    # reinstall config_data
    $self->SUPER::ACTION_install;

    # refresh the packlist
    $self->alien_refresh_packlist( $self->alien_library_destination );
  }
}

#######################
#  Pre-build Methods  #
#######################

sub alien_check_installed_version {
  my $self = shift;
  my $command = $self->alien_version_check;

  my %result = $self->do_system($command, {verbose => 0});
  my $version = ($result{success} && $result{stdout}) || 0;

  return $version;
}

sub alien_create_repositories {
  my $self = shift;

  ## get repository specs
  my $repo_default = $self->alien_repository_default;
  my $repo_specs = $self->alien_repository;

  # upconvert to arrayref if a single hashref is passed
  if (ref $repo_specs eq 'HASH') {
    $repo_specs = [ $repo_specs ];
  }

  my $module_env_part = uc $self->module_name;
  $module_env_part =~ s/^ALIEN:://;
  $module_env_part =~ s/::/_/g;
  my $env_prefix = 'ALIEN_'.$module_env_part.'_REPO_';

  # Catch e.g. 'ALIEN_MYMODULE_REPO_HTTP_HOST' variables to override repo config
  my @env_overrides =
    grep { index($_, $env_prefix) == 0 }
    keys %ENV;

  unless(@$repo_specs)
  {
    print STDERR "If you are the author of this Alien dist, you need to provide at least\n";
    print STDERR "one repository in your Build.PL.  See:\n";
    print STDERR "https://metacpan.org/pod/Alien::Base::ModuleBuild::API#alien_repository\n";
    croak "No repositories specified.";
  }

  my @repos;
  foreach my $repo ( @$repo_specs ) {
    #merge defaults into spec
    foreach my $key ( keys %$repo_default ) {
      next if defined $repo->{$key};
      $repo->{$key} = $repo_default->{$key};
    }

    $repo->{platform} = 'src' unless defined $repo->{platform};

lib/Alien/Base/ModuleBuild.pm  view on Meta::CPAN

      $config->generate_file( Shell::Guess->power_shell,   'env.ps1' );
    } else {
      local $CWD;
      pop @CWD;
      $config->generate_file( Shell::Guess->bourne_shell,  'env.sh'  );
      $config->generate_file( Shell::Guess->c_shell,       'env.csh' );
    }
  }

  $self->do_system( ref($command) ? @$command : $command );
}

# alias for backwards compatibility
*_env_do_system = \&alien_do_system;

sub alien_check_built_version {
  return;
}

sub alien_do_commands {
  my $self = shift;
  my $phase = shift;

  my $attr = "alien_${phase}_commands";
  my $commands = $self->$attr();

  print "\n+ cd $CWD\n";

  foreach my $command (@$commands) {

    my %result = $self->alien_do_system( $command );
    unless ($result{success}) {
      carp "External command ($result{command}) failed! Error: $?\n";
      return 0;
    }
  }

  return 1;
}

# wrapper for M::B::do_system which interpolates alien_ vars first
# futher it either captures or tees depending on the value of $Verbose
sub do_system {
  my $self = shift;
  my $opts = ref $_[-1] ? pop : { verbose => 1 };

  my $verbose = $Verbose || $opts->{verbose};

  # prevent build process from cwd-ing from underneath us
  local $CWD;
  my $initial_cwd = $CWD;

  my @args = map { $self->alien_interpolate($_) } @_;

  print "+ @args\n";

  my $old_super_verbose = $self->verbose;
  $self->verbose(0);
  my ($out, $err, $success) =
    $verbose
    ? tee     { $self->SUPER::do_system(@args) }
    : capture { $self->SUPER::do_system(@args) }
  ;
  $self->verbose($old_super_verbose);

  my %return = (
    stdout => $out,
    stderr => $err,
    success => $success,
    command => join(' ', @args),
  );

  # restore wd
  $CWD = $initial_cwd;

  return wantarray ? %return : $return{success};  ## no critic (Policy::Community::Wantarray)
}

sub _alien_execute_helper {
  my($self, $helper) = @_;
  my $code = $self->alien_helper->{$helper};
  die "no such helper: $helper" unless defined $code;

  if(ref($code) ne 'CODE') {
    my $perl = $code;
    $code = sub {
      my $value = eval $perl; ## no critic (Policy::BuiltinFunctions::ProhibitStringyEval)
      die $@ if $@;
      $value;
    };
  }

  $code->();
}

sub alien_interpolate {
  my $self = shift;
  my ($string) = @_;

  my $prefix = $self->alien_exec_prefix;
  my $configure = $self->alien_configure;
  my $share  = $self->alien_library_destination;
  my $name   = $self->alien_name || '';

  # substitute:
  #   install location share_dir (placeholder: %s)
  $string =~ s/(?<!\%)\%s/$share/g;
  #   local exec prefix (ph: %p)
  $string =~ s/(?<!\%)\%p/$prefix/g;
  #   correct incantation for configure on platform
  $string =~ s/(?<!\%)\%c/$configure/g;
  #   library name (ph: %n)
  $string =~ s/(?<!\%)\%n/$name/g;
  #   current interpreter ($^X) (ph: %x)
  my $perl = $self->perl;
  $string =~ s/(?<!\%)\%x/$perl/g;
  $perl =~ s{\\}{/}g if $^O eq 'MSWin32';
  $string =~ s/(?<!\%)\%X/$perl/g;

  # Version, but only if needed.  Complain if needed and not yet
  # stored.
  if ($string =~ /(?<!\%)\%v/) {



( run in 0.409 second using v1.01-cache-2.11-cpan-119454b85a5 )