The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package MakeMaker::Test::Utils;

use File::Spec;

use Config;

our ($VERSION, @ISA, @EXPORT);

require Exporter;
@ISA = qw(Exporter);

$VERSION = 0.03;

@EXPORT = qw(which_perl perl_lib makefile_name makefile_backup
             make make_run run make_macro calibrate_mtime
             setup_mm_test_root
	     have_compiler
            );

my $Is_VMS   = $^OS_NAME eq 'VMS';
my $Is_MacOS = $^OS_NAME eq 'MacOS';


=head1 NAME

MakeMaker::Test::Utils - Utility routines for testing MakeMaker

=head1 SYNOPSIS

  use MakeMaker::Test::Utils;

  my $perl     = which_perl;
  perl_lib;

  my $makefile      = makefile_name;
  my $makefile_back = makefile_backup;

  my $make          = make;
  my $make_run      = make_run;
  make_macro($make, $targ, %macros);

  my $mtime         = calibrate_mtime;

  my $out           = run($cmd);

  my $have_compiler = have_compiler();


=head1 DESCRIPTION

A consolidation of little utility functions used through out the
MakeMaker test suite.

=head2 Functions

The following are exported by default.

=over 4

=item B<which_perl>

  my $perl = which_perl;

Returns a path to perl which is safe to use in a command line, no
matter where you chdir to.

=cut

sub which_perl {
    my $perl = $^EXECUTABLE_NAME;
    $perl ||= 'perl';

    # VMS should have 'perl' aliased properly
    return $perl if $Is_VMS;

    $perl .= config_value("exe_ext") unless $perl =~ m/$(config_value("exe_ext"))$/i;

    my $perlpath = 'File::Spec'->rel2abs( $perl );
    unless( $Is_MacOS || -x $perlpath ) {
        # $^X was probably 'perl'

        # When building in the core, *don't* go off and find
        # another perl
        die "Can't find a perl to use (\$^X=$^EXECUTABLE_NAME), (\$perlpath=$perlpath)" 
          if env::var('PERL_CORE');

        foreach my $path ( 'File::Spec'->path) {
            $perlpath = 'File::Spec'->catfile($path, $perl);
            last if -x $perlpath;
        }
    }

    return $perlpath;
}

=item B<perl_lib>

  perl_lib;

Sets up environment variables so perl can find its libraries.

=cut

my $old5lib = env::var('PERL5LIB');
my $had5lib = defined $old5lib;
sub perl_lib {
                               # perl-src/t/
    my $lib =  env::var('PERL_CORE') ?? qq{../lib}
                               # ExtUtils-MakeMaker/t/
                               !! qq{../blib/lib};
    $lib = 'File::Spec'->rel2abs($lib);
    my @libs = @($lib);
    push @libs, env::var('PERL5LIB') if defined env::var('PERL5LIB');
    env::var('PERL5LIB' ) = join(config_value("path_sep"), @libs);
    unshift $^INCLUDE_PATH, $lib;
}

END {
    env::var('PERL5LIB' ) = $old5lib;
}


=item B<makefile_name>

  my $makefile = makefile_name;

MakeMaker doesn't always generate 'Makefile'.  It returns what it
should generate.

=cut

sub makefile_name {
    return $Is_VMS ?? 'Descrip.MMS' !! 'Makefile';
}   

=item B<makefile_backup>

  my $makefile_old = makefile_backup;

Returns the name MakeMaker will use for a backup of the current
Makefile.

=cut

sub makefile_backup {
    my $makefile = makefile_name;
    return $Is_VMS ?? "$makefile".'_old' !! "$makefile.old";
}

=item B<make>

  my $make = make;

Returns a good guess at the make to run.

=cut

sub make {
    my $make = env::var('MAKE') // config_value("make");

    return $make;
}

=item B<make_run>

  my $make_run = make_run;

Returns the make to run as with make() plus any necessary switches.

=cut

sub make_run {
    my $make = make;
    $make .= ' -nologo' if $make eq 'nmake';

    return $make;
}

=item B<make_macro>

    my $make_cmd = make_macro($make, $target, %macros);

Returns the command necessary to run $make on the given $target using
the given %macros.

  my $make_test_verbose = make_macro(make_run(), 'test', 
                                     TEST_VERBOSE => 1);

This is important because VMS's make utilities have a completely
different calling convention than Unix or Windows.

%macros is actually a list of tuples, so the order will be preserved.

=cut

sub make_macro {
    my@($make, $target) = @(shift, shift);

    my $is_mms = $make =~ m/^MM(K|S)/i;

    my $cmd = $make;
    my $macros = '';
    while( my@(?$key,?$val) = @: splice(@_, 0, 2) ) {
        if( $is_mms ) {
            $macros .= qq{/macro="$key=$val"};
        }
        else {
            $macros .= qq{ $key=$val};
        }
    }

    return $is_mms ?? "$make$macros $target" !! "$make $target $macros";
}

=item B<calibrate_mtime>

  my $mtime = calibrate_mtime;

When building on NFS, file modification times can often lose touch
with reality.  This returns the mtime of a file which has just been
touched.

=cut

sub calibrate_mtime {
    open(my $file, ">", "calibrate_mtime.tmp") || die $^OS_ERROR;
    print $file, "foo";
    close $file;
    my $mtime = @(stat('calibrate_mtime.tmp'))[9];
    unlink 'calibrate_mtime.tmp';
    return $mtime;
}

=item B<run>

  my $out = run($command);
  my @out = run($command);

Runs the given $command as an external program returning at least STDOUT
as $out.  If possible it will return STDOUT and STDERR combined as you
would expect to see on a screen.

=cut

sub run {
    my $cmd = shift;

    use ExtUtils::MM;

    # Unix can handle 2>&1 and OS/2 from 5.005_54 up.
    # This makes our failure diagnostics nicer to read.
    if( MM->os_flavor_is('Unix') or
        (MM->os_flavor_is('OS/2'))
      ) {
        return `$cmd 2>&1`;
    }
    else {
        return `$cmd`;
    }
}

=item B<setup_mm_test_root>

Creates a rooted logical to avoid the 8-level limit on older VMS systems.  
No action taken on non-VMS systems.

=cut

sub setup_mm_test_root {
    if( $Is_VMS ) {
        # On older systems we might exceed the 8-level directory depth limit
        # imposed by RMS.  We get around this with a rooted logical, but we
        # can't create logical names with attributes in Perl, so we do it
        # in a DCL subprocess and put it in the job table so the parent sees it.
        open( my $mmtmp, ">", 'mmtesttmp.com' ) || 
          die "Error creating command file; $^OS_ERROR";
        print $mmtmp, <<'COMMAND';
$ MM_TEST_ROOT = F$PARSE("SYS$DISK:[-]",,,,"NO_CONCEAL")-".][000000"-"]["-"].;"+".]"
$ DEFINE/JOB/NOLOG/TRANSLATION=CONCEALED MM_TEST_ROOT 'MM_TEST_ROOT'
COMMAND
        close $mmtmp;

        system '@mmtesttmp.com';
        1 while unlink 'mmtesttmp.com';
    }
}

=item have_compiler

  $have_compiler = have_compiler;

Returns true if there is a compiler available for XS builds.

=cut

sub have_compiler {
    my $have_compiler = 0;

    # ExtUtils::CBuilder prints its compilation lines to the screen.
    # Shut it up.
    local $^STDOUT = $^STDOUT;
    local $^STDERR = $^STDERR;

    my $buffer;
    open my $fh, '>', \$buffer;
    $^STDOUT = *{$fh}{IO};
    $^STDERR = *{$fh}{IO};

    try {
	require ExtUtils::CBuilder;
	my $cb = 'ExtUtils::CBuilder'->new;

	$have_compiler = $cb->have_compiler;
    };

    return $have_compiler;
}


=back

=head1 AUTHOR

Michael G Schwern <schwern@pobox.com>

=cut

1;