The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Perl::Dist::Strawberry::Step::InstallPerlCore;

use 5.012;
use warnings;
use base 'Perl::Dist::Strawberry::Step';

use Data::Dump qw(pp);
use Archive::Zip           qw( AZ_OK );
use Archive::Tar           qw();
use File::Spec::Functions  qw(catdir catfile rel2abs catpath splitpath);
use File::Find::Rule;
use File::Path             qw(make_path remove_tree);
use File::Copy             qw(copy);
use File::Glob             qw(bsd_glob);
use File::Slurp;
use Text::Patch;
use Text::Diff;
#use Win32;

sub check {
  my $self = shift;
  my ($rv, $out);
}

sub run {
  my $self = shift;
  my $image_dir = $self->global->{image_dir};

  # Log execute_special's environment
  $self->execute_special('set', catfile($self->global->{debug_dir}, 'perl_set.log.txt'));

  # Get the initial directory contents to compare against later.
  $self->boss->message( 0, 'Preparing '.__PACKAGE__."\n" );
  my $dir = catdir($image_dir, 'perl');
  make_path($dir) unless -d $dir;

  # Download the perl tarball if needed.
  my $tgz = $self->boss->mirror_url( $self->{config}->{url}, $self->global->{download_dir} );

  # Unpack to the build directory
  my $unpack_to = catdir( $self->global->{build_dir}, 'perl_core' );  
  if ( -d $unpack_to ) {
    $self->boss->message( 2, "Removing previous '$unpack_to'\n" );
    remove_tree($unpack_to);
  }
  my @files = $self->_extract( $tgz, $unpack_to );

  # Get the versioned name of the directory
  my $perlsrc;
  for (glob("$unpack_to/*")) {
    $perlsrc = File::Basename::basename($_) if -d $_;
  }
  die "ERROR: cannot detect perl-src dir" unless $perlsrc;
 
  #get verion string - e.g. '5.15.9'
  my ($version) = grep { /INST_VER/ } read_file(catfile($unpack_to, $perlsrc, qw/win32 makefile.mk/));
  $version =~ s/^.*?(5\..*?)[\r\n]*$/$1/;

  # some handy variables
  my $app_id = $self->global->{app_simplename} // 'perl';
  my $app_ver = $self->global->{app_version} // $version;
  $app_ver .= "-beta".$self->global->{beta} if $self->global->{beta};
  my $now = scalar(localtime);
  my $arch = $self->global->{bits} == 64 ? 'x64' : 'i386';
  my $cf_email = $self->{config}->{cf_email} // 'builder@somewhere.com',
  
  # prepare dta used for template (TT) processing
  my $tt_vars = {
        %{$self->global},
        myuname => "Win32 $app_id $app_ver #1 $now $arch",
  };

  # Patch perl source
  my $patch = $self->{config}->{patch};
  if ($patch) {
    while (my ($new, $dst) = each %$patch) {
      $self->_patch_file($self->boss->resolve_name($new), catfile($unpack_to, $perlsrc, $dst), $tt_vars);
    }
  }
  
  # keep *.diff files
  my @items = File::Find::Rule->file->relative->name('*.diff')->in($unpack_to);
  for (@items) {
    my $src = catfile($unpack_to, $_);
    my $dst = $_;
    $dst =~ s/[\\\/]/_/g;
    $dst = catfile($self->global->{debug_dir}, $dst);
    copy($src, $dst) or die "ERROR: copy '$src' > '$dst' failed";
  }

  # Copy in licenses
  if ( ref $self->{config}->{license} eq 'HASH' ) {
    my $licenses = $self->{config}->{license};
    foreach my $key ( keys %{$licenses} ) {
      my $src = catfile($unpack_to, $perlsrc, $key);
      my $dst = $self->boss->resolve_name($licenses->{$key});
      my ($volume,$directories) = splitpath($dst);
      make_path(catdir($volume,$directories));
      copy($src, $dst) or die "ERROR: copy '$src' > '$dst' failed";
    }
  }
  
  # extract global settings passed from commandline
  my $dbg = defined $self->global->{perl_debug}    ? $self->global->{perl_debug}    : ($self->{config}->{perl_debug}    // 0);
  my $u64 = defined $self->global->{perl_64bitint} ? $self->global->{perl_64bitint} : ($self->{config}->{perl_64bitint} // $self->{config}->{use_64_bit_int} // 0);
  # XXX use_64_bit_int is for backwards compatibility

  # Build win32 perl
  SCOPE: {
    my $wd = $self->_push_dir( $unpack_to, $perlsrc, 'win32' );
    my $INST_TOP   = catdir( $image_dir, 'perl' );
    my $CCHOME     = catdir( $image_dir, 'c' );
    my ($INST_DRV) = splitpath( $INST_TOP, 1 );
    my ($new_env, $log);

    # necessary workaround for building 32bit perl on 64bit Windows
    my @make_args = ("INST_DRV=$INST_DRV", "INST_TOP=$INST_TOP", "CCHOME=$CCHOME", "EMAIL=$cf_email");
    push @make_args, 'GCC_4XX=define', 'GCCHELPERDLL=$(CCHOME)\bin\libgcc_s_sjlj-1.dll'; #perl-5.12/14 only

    # enable debug build
    push @make_args, 'CFG=Debug' if $dbg > 0;
    
    # enable 64bit ints on 32bit perl
    push @make_args, 'USE_64_BIT_INT=define' if $u64 && $self->global->{bits} == 32;
    # enable BUILDOPTEXTRA
    push @make_args, "BUILDOPTEXTRA=$self->{config}->{buildoptextra}" if $self->{config}->{buildoptextra};

    $new_env->{USERNAME} = (split /@/, $cf_email)[0]; # trick to set correct cf_by
    my @extralibs;
    if (my $extra1 = catdir((bsd_glob("$CCHOME/*-mingw32/lib"))[0])) {
      push @extralibs, $extra1 if -d $extra1;
    }
    if (my $extra2 = catdir((bsd_glob("$CCHOME/lib/gcc/*-mingw32/*"))[0])) {
      push @extralibs, $extra2 if -d $extra2;
    }
    push @make_args, "EXTRALIBDIRS=" . join(';', @extralibs) if @extralibs;
    if ($self->global->{bits} == 64) {
      $new_env->{PROCESSOR_ARCHITECTURE} = 'AMD64';
    }
    else {
      $new_env->{PROCESSOR_ARCHITECTURE} = 'x86';
      push @make_args, 'WIN64=undef';
    }

    #create debuging build scripts in 'win32' subdir
    my $set_simple_path = "set PATH=$image_dir\\c\\bin;\%SystemRoot\%\\system32;\%SystemRoot\%";
    write_file('_do_dmake.bat', $set_simple_path."\n".join(' ', 'dmake', @make_args).' %*');
    write_file('_do_dmake_install.bat', $set_simple_path."\n".join(' ', 'dmake', @make_args, 'install'));
    write_file('_do_dmake_test.bat', $set_simple_path."\n".join(' ', 'dmake', @make_args, 'test'));

    # Compile perl.
    my $rv;
    $self->boss->message( 1, "Building perl $version (dbg=$dbg, u64=$u64)...\n" );
    $log = catfile($self->global->{debug_dir}, 'perl_dmake_all.log.txt');

    if ($self->global->{bits} == 64) {
      #XXX-FIXME-XXX 'dmake all' fails with redirected output for 64bit build via IPC::Run3
      $rv = $self->execute_special(['dmake', @make_args, 'all'], undef, undef, $new_env);
    }
    else {
      $rv = $self->execute_special(['dmake', @make_args, 'all'], $log, $log, $new_env);
    }

    die "FATAL: dmake all FAILED!" unless(defined $rv && $rv == 0);

    # Get information required for testing and installing perl.
    #my $long_build = Win32::GetLongPathName( rel2abs( $self->global->{build_dir} ) );

    # Testing perl if requested.
    if ($self->global->{test_core}) {
      $new_env->{PERL_SKIP_TTY_TEST} = 1;
      $self->boss->message( 1, "Testing perl $version ...\n" );
      $log = catfile($self->global->{debug_dir}, 'perl_dmake_test.log.txt');
      $self->execute_special(['dmake', @make_args, 'test'], $log, $log, $new_env);
      $self->boss->message( 1, "dmake test FAILED!") unless(defined $rv && $rv == 0);
    }

    # Installing perl.
    $self->boss->message( 1, "Installing perl $version ...\n" );
    $log = catfile($self->global->{debug_dir}, 'perl_dmake_install.log.txt');
    $rv = $self->execute_special(['dmake', @make_args, 'install', 'UNINST=1'], $log, $log, $new_env);
    die "FATAL: dmake install FAILED!" unless(defined $rv && $rv == 0);
  }
  
  # Debug version with separated debug symbols [EXPERIMENTAL]
  if ($dbg > 1) {
    my @bin = File::Find::Rule->file->name('*perl*.dll', 'perl.exe', 'wperl.exe', 'perl5*.exe')->in("$image_dir\\perl\\bin");
    my @lib = File::Find::Rule->file->name('*.dll')->in("$image_dir\\perl\\lib");

    my $ch = "$image_dir\\perl\\lib\\Config_heavy.pl";
    my $ch_content = read_file($ch, {binmode=>':raw'});
    $ch_content =~ s/(ccflags|optimize|ccflags_nolargefiles)(=.*?)-g -O2 -DDEBUGGING/$1$2-s -O2/sg;
    $ch_content =~ s/(lddlflags|ldflags|ldflags_nolargefiles)(=.*?)-g /$1$2-s /sg;

    if ($dbg eq '2') {
      $self->_strip_debug($_, 1) for (@bin); #keep *.debug files
      $self->_strip_debug($_, 1) for (@lib); #keep *.debug files
      my $ro = $self->_unset_ro($ch);
      write_file($ch, {binmode=>':raw'}, $ch_content);
      $self->_restore_ro($ch, $ro);
    }
    if ($dbg eq '3') {
      $self->_strip_debug($_, 1) for (@bin);
      $self->_strip_debug($_, 0) for (@lib); #do not keep *.debug files
      my $ro = $self->_unset_ro($ch);
      write_file($ch, {binmode=>':raw'}, $ch_content);
      $self->_restore_ro($ch, $ro);
    }
  }

  # Delete unwanted dirs
  remove_tree("$image_dir/perl/html") if -d "$image_dir/perl/html";
  remove_tree("$image_dir/perl/man")  if -d "$image_dir/perl/man";
  
  # If using gcc4, copy the helper dll into perl's bin directory.
  my $from;
  $from = catfile($image_dir, qw/c bin libgcc_s_sjlj-1.dll/);
  copy($from, catfile($image_dir, qw/perl bin libgcc_s_sjlj-1.dll/)) if -f $from;
  $from = catfile($image_dir, qw/c bin libstdc++-6.dll/);
  copy($from, catfile($image_dir, qw/perl bin libstdc++-6.dll/)) if -f $from;
  $from = catfile($image_dir, qw/c bin libwinpthread-1.dll/);
  copy($from, catfile($image_dir, qw/perl bin libwinpthread-1.dll/)) if -f $from;

  # Delete a2p.exe (Can't relocate a binary).
  my $a = catfile($image_dir, 'perl', 'bin', 'a2p.exe');
  if (-f $a) {
    $self->boss->message(3, "removing file '$a'");
    unlink $a or die "ERROR: Could not delete '$a'";
  }

  die "FATAL: perl.exe not properly installed" unless -f catfile($image_dir, qw/perl bin perl.exe/);
  
  # Create some missing directories
  my @d = ( catdir($image_dir, qw/perl vendor lib/),
            catdir($image_dir, qw/perl site bin/),
            catdir($image_dir, qw/perl site lib/) );
  for (@d) { make_path($_) unless -d $_; }
 
  # store some output data
  $self->{data}->{output}->{perl_version} = $version;
  
  #XXX-TODO store perl -V
  #$self->{data}->{output}->{perl_version} = `perl -V`;

  return 1;
}

sub test {
  #XXX-FIXME maybe some kind of post_check
}

sub _strip_debug {
  my ($self, $fullpath, $keep_debug) = @_;
  if ($keep_debug) {
    my $fullpath_dbg = "$fullpath.debug";
    my $ro = $self->_unset_ro($fullpath);
    
    ### not correct: warning: section .gnu_debuglink not found in C:\strawberry\perl\bin\perl520.dll.debug
    #$self->execute_standard(['objcopy', '--only-keep-debug', $fullpath, $fullpath_dbg]);
    #$self->execute_standard(['objcopy', '--strip-debug', $fullpath]);
    #$self->execute_standard(['objcopy', "--add-gnu-debuglink=$fullpath_dbg", $fullpath]);
    
    ### workaround from https://sourceware.org/bugzilla/show_bug.cgi?id=14527
    $self->execute_standard(['objcopy', '--only-keep-debug', $fullpath, $fullpath_dbg]);
    $self->execute_standard(['objcopy', "--add-gnu-debuglink=$fullpath_dbg", $fullpath]);
    $self->execute_standard(['objcopy', '--only-keep-debug', $fullpath, $fullpath_dbg]);
    $self->execute_standard(['objcopy', '--remove-section=.gnu_debuglink', $fullpath]);
    $self->execute_standard(['objcopy', '--strip-debug', $fullpath]);
    $self->execute_standard(['objcopy', "--add-gnu-debuglink=$fullpath_dbg", $fullpath]);
    
    $self->_restore_ro($fullpath, $ro);
  }
  else {
    my $ro = $self->_unset_ro($fullpath);
    $self->execute_standard(['objcopy', '--strip-debug', $fullpath]);
    $self->_restore_ro($fullpath, $ro);
  }
}

1;