package ExtUtils::MM_Win32;

use strict;
use warnings;

=head1 NAME

ExtUtils::MM_Win32 - methods to override UN*X behaviour in ExtUtils::MakeMaker

=head1 SYNOPSIS

 use ExtUtils::MM_Win32; # Done internally by ExtUtils::MakeMaker if needed

=head1 DESCRIPTION

See L<ExtUtils::MM_Unix> for a documentation of the methods provided
there. This package overrides the implementation of these methods, not
the semantics.

=cut

use ExtUtils::MakeMaker::Config;
use File::Basename;
use File::Spec;
use ExtUtils::MakeMaker qw(neatvalue _sprintf562);

require ExtUtils::MM_Any;
require ExtUtils::MM_Unix;
our @ISA = qw( ExtUtils::MM_Any ExtUtils::MM_Unix );
our $VERSION = '7.78';
$VERSION =~ tr/_//d;

$ENV{EMXSHELL} = 'sh'; # to run `commands`

my ( $BORLAND, $GCC, $MSVC ) = _identify_compiler_environment( \%Config );

sub _identify_compiler_environment {
	my ( $config ) = @_;

	my $BORLAND = $config->{cc} =~ /\bbcc/i ? 1 : 0;
	my $GCC     = $config->{cc} =~ /\bgcc\b/i ? 1 : 0;
	my $MSVC    = $config->{cc} =~ /\b(?:cl|icl)/i ? 1 : 0; # MSVC can come as clarm.exe, icl=Intel C

	return ( $BORLAND, $GCC, $MSVC );
}


=head2 Overridden methods

=over 4

=item B<dlsyms>

=cut

sub dlsyms {
    my($self,%attribs) = @_;
    return '' if $self->{SKIPHASH}{'dynamic'};
    $self->xs_dlsyms_iterator(\%attribs);
}

=item xs_dlsyms_ext

On Win32, is C<.def>.

=cut

sub xs_dlsyms_ext {
    '.def';
}

=item replace_manpage_separator

Changes the path separator with .

=cut

sub replace_manpage_separator {
    my($self,$man) = @_;
    $man =~ s,[/\\]+,.,g;
    $man;
}


=item B<maybe_command>

Since Windows has nothing as simple as an executable bit, we check the
file extension.

The PATHEXT env variable will be used to get a list of extensions that
might indicate a command, otherwise .com, .exe, .bat and .cmd will be
used by default.

=cut

sub maybe_command {
    my($self,$file) = @_;
    my @e = exists($ENV{'PATHEXT'})
          ? split(/;/, $ENV{PATHEXT})
	  : qw(.com .exe .bat .cmd);
    my $e = '';
    for (@e) { $e .= "\Q$_\E|" }
    chop $e;
    # see if file ends in one of the known extensions
    if ($file =~ /($e)$/i) {
	return $file if -e $file;
    }
    else {
	for (@e) {
	    return "$file$_" if -e "$file$_";
	}
    }
    return;
}


=item B<init_DIRFILESEP>

Using \ for Windows, except for "gmake" where it is /.

=cut

sub init_DIRFILESEP {
    my($self) = shift;

    # The ^ makes sure it's not interpreted as an escape in nmake
    $self->{DIRFILESEP} = $self->is_make_type('nmake') ? '^\\' :
                          $self->is_make_type('dmake') ? '\\\\' :
                          $self->is_make_type('gmake') ? '/'
                                                       : '\\';
}

=item init_tools

Override some of the slower, portable commands with Windows specific ones.

=cut

sub init_tools {
    my ($self) = @_;

    $self->{NOOP}     ||= 'rem';
    $self->{DEV_NULL} ||= '> NUL';

    $self->{FIXIN}    ||= $self->{PERL_CORE} ?
      "\$(PERLRUN) -I$self->{PERL_SRC}\\cpan\\ExtUtils-PL2Bat\\lib $self->{PERL_SRC}\\win32\\bin\\pl2bat.pl" :
      'pl2bat.bat';

    $self->SUPER::init_tools;

    # Setting SHELL from $Config{sh} can break dmake.  Its ok without it.
    delete $self->{SHELL};

    return;
}


=item init_others

Override the default link and compile tools.

LDLOADLIBS's default is changed to $Config{libs}.

Adjustments are made for Borland's quirks needing -L to come first.

=cut

my @LIBS_VARNAMES = qw(EXTRALIBS BSLOADLIBS LDLOADLIBS LD_RUN_PATH);
sub init_others {
    my $self = shift;

    $self->{LD}     ||= 'link';
    $self->{AR}     ||= 'lib';

    $self->SUPER::init_others;
    # If Config.pm defines a set of default libs,
    # add them to EXTRALIBS, BSLOADLIBS and LDLOADLIBS, unless the user
    # specified :nodefault or gave no LIBS
    if (grep /\S/ && !/:nodefault/i, @{$self->{LIBS}}) {
        my @libs = $self->extliblist($Config{perllibs});
        for my $ind (0..$#LIBS_VARNAMES) {
          next unless my $to_add = $libs[$ind];
          my $varname = $LIBS_VARNAMES[$ind];
          $self->{$varname} .= ' ' if $self->{$varname};
          $self->{$varname} .= $to_add;
        }
    }

    $self->{LDLOADLIBS} ||= $Config{libs};
    # -Lfoo must come first for Borland, so we put it in LDDLFLAGS
    if ($BORLAND) {
        my $libs = $self->{LDLOADLIBS};
        my $libpath = '';
        while ($libs =~ s/(?:^|\s)(("?)-L.+?\2)(?:\s|$)/ /) {
            $libpath .= ' ' if length $libpath;
            $libpath .= $1;
        }
        $self->{LDLOADLIBS} = $libs;
        $self->{LDDLFLAGS} ||= $Config{lddlflags};
        $self->{LDDLFLAGS} .= " $libpath";
    }

    return;
}


=item init_platform

Add MM_Win32_VERSION.

=item platform_constants

=cut

sub init_platform {
    my($self) = shift;

    $self->{MM_Win32_VERSION} = $VERSION;

    return;
}

sub platform_constants {
    my($self) = shift;
    my $make_frag = '';

    foreach my $macro (qw(MM_Win32_VERSION))
    {
        next unless defined $self->{$macro};
        $make_frag .= "$macro = $self->{$macro}\n";
    }

    return $make_frag;
}

=item specify_shell

Set SHELL to $ENV{COMSPEC} only if make is type 'gmake'.

=cut

sub specify_shell {
    my $self = shift;
    return '' unless $self->is_make_type('gmake');
    "\nSHELL = $ENV{COMSPEC}\n";
}

=item constants

Add MAXLINELENGTH for dmake before all the constants are output.

=cut

sub constants {
    my $self = shift;

    my $make_text = $self->SUPER::constants;
    return $make_text unless $self->is_make_type('dmake');

    # dmake won't read any single "line" (even those with escaped newlines)
    # larger than a certain size which can be as small as 8k.  PM_TO_BLIB
    # on large modules like DateTime::TimeZone can create lines over 32k.
    # So we'll crank it up to a <ironic>WHOPPING</ironic> 64k.
    #
    # This has to come here before all the constants and not in
    # platform_constants which is after constants.
    my $size = $self->{MAXLINELENGTH} || 800000;
    my $prefix = qq{
# Get dmake to read long commands like PM_TO_BLIB
MAXLINELENGTH = $size

};

    return $prefix . $make_text;
}

=item dep_constants

Makes dependencies that work for nmake.

=cut

sub dep_constants {
    my ($self) = @_;
    return $self->SUPER::dep_constants if !$self->is_make_type('nmake');
    my @m = ();
    for my $macro (qw(PERL_ARCHLIBDEP PERL_INCDEP)) {
        next unless defined $self->{$macro};
        # pathnames can have sharp signs in them; escape them so
        # make doesn't think it is a comment-start character.
        $self->{$macro} =~ s/#/\\#/g;
        $self->{$macro} = $self->quote_dep($self->{$macro})
          if $ExtUtils::MakeMaker::macro_dep{$macro};
        push @m, "$macro = $self->{$macro}\n";
    }
    push @m, qq{
\n# Dependencies info
PERL_ARCHIVEDEP    = "$self->{PERL_ARCHIVE}"
};
    push @m, qq{
# Where is the Config information that we are using/depend on
CONFIGDEP = "\$(PERL_ARCHLIB)\$(DFSEP)Config.pm" "\$(PERL_INC)\$(DFSEP)config.h"
} if -e $self->catfile($self->{PERL_INC}, 'config.h');
    join '', @m;
}

sub _perl_header_files_fragment {
  my ($self, $separator) = @_;
  return $self->SUPER::_perl_header_files_fragment($separator)
    if !$self->is_make_type('nmake');
  return join("\\\n",
    "PERL_HDRS = ",
    map sprintf("        \"\$(PERL_INC)\\%s\"            ", $_),
      $self->_perl_header_files
  ) . "\n\n"
  . "\$(OBJECT) : \$(PERL_HDRS)\n";
}

sub tool_xsubpp_emit {
  my ($self, $xsdir, $tmdeps, $tmargs) = @_;
  return $self->SUPER::tool_xsubpp_emit($xsdir, $tmdeps, $tmargs)
    if !$self->is_make_type('nmake');
  return qq{
XSUBPPDIR = $xsdir
XSUBPP = "\$(XSUBPPDIR)\$(DFSEP)xsubpp"
XSUBPPRUN = \$(PERLRUN) \$(XSUBPP)
XSPROTOARG = $self->{XSPROTOARG}
XSUBPPDEPS = @$tmdeps \$(XSUBPP)
XSUBPPARGS = @$tmargs
XSUBPP_EXTRA_ARGS =
};
}

=item special_targets

Add .USESHELL target for dmake.

=cut

sub special_targets {
    my($self) = @_;

    my $make_frag = $self->SUPER::special_targets;

    $make_frag .= <<'MAKE_FRAG' if $self->is_make_type('dmake');
.USESHELL :
MAKE_FRAG

    return $make_frag;
}

=item static_lib_pure_cmd

Defines how to run the archive utility

=cut

sub static_lib_pure_cmd {
    my ($self, $from) = @_;
    $from =~ s/(\$\(\w+)(\))/$1:^"+"$2/g if $BORLAND;
    sprintf qq{\t\$(AR) %s\n}, ($BORLAND ? '$@ ' . $from
                          : ($GCC ? '-ru $@ ' . $from
                                  : '-out:$@ ' . $from));
}

=item dynamic_lib

Methods are overridden here: not dynamic_lib itself, but the utility
ones that do the OS-specific work.

=cut

sub xs_make_dynamic_lib {
    my ($self, $attribs, $from, $to, $todir, $ldfrom, $exportlist) = @_;
    my @m = sprintf '%s : %s $(MYEXTLIB) %s$(DFSEP).exists %s $(PERL_ARCHIVEDEP) $(INST_DYNAMIC_DEP)'."\n", $to, $from, $todir, $exportlist;
    if ($GCC) {
      # per https://rt.cpan.org/Ticket/Display.html?id=78395 no longer
      # uses dlltool - relies on post 2002 MinGW
      #                             1            2
      push @m, _sprintf562 <<'EOF', $exportlist, $ldfrom;
	$(LD) %1$s -o $@ $(LDDLFLAGS) %2$s $(OTHERLDFLAGS) $(MYEXTLIB) "$(PERL_ARCHIVE)" $(LDLOADLIBS) -Wl,--enable-auto-image-base
EOF
    } elsif ($BORLAND) {
      my $ldargs = $self->is_make_type('dmake')
          ? q{"$(PERL_ARCHIVE:s,/,\,)" $(LDLOADLIBS:s,/,\,) $(MYEXTLIB:s,/,\,),}
          : q{"$(subst /,\,$(PERL_ARCHIVE))" $(subst /,\,$(LDLOADLIBS)) $(subst /,\,$(MYEXTLIB)),};
      my $subbed;
      if ($exportlist eq '$(EXPORT_LIST)') {
          $subbed = $self->is_make_type('dmake')
              ? q{$(EXPORT_LIST:s,/,\,)}
              : q{$(subst /,\,$(EXPORT_LIST))};
      } else {
            # in XSMULTI, exportlist is per-XS, so have to sub in perl not make
          ($subbed = $exportlist) =~ s#/#\\#g;
      }
      push @m, sprintf <<'EOF', $ldfrom, $ldargs . $subbed;
        $(LD) $(LDDLFLAGS) $(OTHERLDFLAGS) %s,$@,,%s,$(RESFILES)
EOF
    } else {	# VC
      push @m, sprintf <<'EOF', $ldfrom, $exportlist;
	$(LD) -out:$@ $(LDDLFLAGS) %s $(OTHERLDFLAGS) $(MYEXTLIB) "$(PERL_ARCHIVE)" $(LDLOADLIBS) -def:%s
EOF
      # Embed the manifest file if it exists
      push(@m, q{	if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2
	if exist $@.manifest del $@.manifest});
    }
    push @m, "\n\t\$(CHMOD) \$(PERM_RWX) \$\@\n";

    join '', @m;
}

sub xs_dynamic_lib_macros {
    my ($self, $attribs) = @_;
    my $otherldflags = $attribs->{OTHERLDFLAGS} || ($BORLAND ? 'c0d32.obj': '');
    my $inst_dynamic_dep = $attribs->{INST_DYNAMIC_DEP} || "";
    sprintf <<'EOF', $otherldflags, $inst_dynamic_dep;
# This section creates the dynamically loadable objects from relevant
# objects and possibly $(MYEXTLIB).
OTHERLDFLAGS = %s
INST_DYNAMIC_DEP = %s
EOF
}

=item extra_clean_files

Clean out some extra dll.{base,exp} files which might be generated by
gcc.  Otherwise, take out all *.pdb files.

=cut

sub extra_clean_files {
    my $self = shift;

    return $GCC ? (qw(dll.base dll.exp)) : ('*.pdb');
}

=item init_linker

=cut

sub init_linker {
    my $self = shift;

    $self->{PERL_ARCHIVE}       = "\$(PERL_INC)\\$Config{libperl}";
    $self->{PERL_ARCHIVEDEP}    = "\$(PERL_INCDEP)\\$Config{libperl}";
    $self->{PERL_ARCHIVE_AFTER} = '';
    $self->{EXPORT_LIST}        = '$(BASEEXT).def';
}


=item perl_script

Checks for the perl program under several common perl extensions.

=cut

sub perl_script {
    my($self,$file) = @_;
    return $file if -r $file && -f _;
    return "$file.pl"  if -r "$file.pl" && -f _;
    return "$file.plx" if -r "$file.plx" && -f _;
    return "$file.bat" if -r "$file.bat" && -f _;
    return;
}

sub can_dep_space {
    my ($self) = @_;
    return 1 if !$self->is_make_type('dmake'); # GNU & n-make are fine
    return 0 unless $self->can_load_xs;
    require Win32;
    require File::Spec;
    my ($vol, $dir) = File::Spec->splitpath($INC{'ExtUtils/MakeMaker.pm'});
    # can_dep_space via GetShortPathName, if short paths are supported
    my $canary = Win32::GetShortPathName(File::Spec->catpath($vol, $dir, 'MakeMaker.pm'));
    (undef, undef, my $file) = File::Spec->splitpath($canary);
    return (length $file > 11) ? 0 : 1;
}

=item quote_dep

=cut

sub quote_dep {
    my ($self, $arg) = @_;
    return $arg if $arg !~ / /;
    return $self->SUPER::quote_dep($arg) if $self->is_make_type('gmake');
    return qq{"$arg"} if $self->is_make_type('nmake');
    require Win32; # dmake, get 8.3 name
    $arg = Win32::GetShortPathName($arg);
    die <<EOF if not defined $arg or $arg =~ / /;
Tried to use make dependency with space for dmake:
  '$arg'
Fallback to short pathname failed.
EOF
    $arg;
}


=item xs_obj_opt

Override to fixup -o flags for MSVC.

=cut

sub xs_obj_opt {
    my ($self, $output_file) = @_;
    ($MSVC ? "/Fo" : "-o ") . $output_file;
}


=item pasthru

All we send is -nologo to nmake to prevent it from printing its damned
banner.

=cut

sub pasthru {
    my($self) = shift;
    my $old = $self->SUPER::pasthru;
    return $old unless $self->is_make_type('nmake');
    $old =~ s/(PASTHRU\s*=\s*)/$1 -nologo /;
    $old;
}


=item arch_check (override)

Normalize all arguments for consistency of comparison.

=cut

sub arch_check {
    my $self = shift;

    # Win32 is an XS module, minperl won't have it.
    # arch_check() is not critical, so just fake it.
    return 1 unless $self->can_load_xs;
    return $self->SUPER::arch_check( map { $self->_normalize_path_name($_) } @_);
}

sub _normalize_path_name {
    my $self = shift;
    my $file = shift;

    require Win32;
    my $short = Win32::GetShortPathName($file);
    return defined $short ? lc $short : lc $file;
}


=item oneliner

These are based on what command.com does on Win98.  They may be wrong
for other Windows shells, I don't know.

=cut

sub oneliner {
    my($self, $cmd, $switches) = @_;
    $switches = [] unless defined $switches;

    # Strip leading and trailing newlines
    $cmd =~ s{^\n+}{};
    $cmd =~ s{\n+$}{};

    $cmd = $self->quote_literal($cmd);
    $cmd = $self->escape_newlines($cmd);

    $switches = join ' ', @$switches;

    return qq{\$(ABSPERLRUN) $switches -e $cmd --};
}


sub quote_literal {
    my($self, $text, $opts) = @_;
    $opts->{allow_variables} = 1 unless defined $opts->{allow_variables};

    # See: http://www.autohotkey.net/~deleyd/parameters/parameters.htm#CPP

    # Apply the Microsoft C/C++ parsing rules
    $text =~ s{\\\\"}{\\\\\\\\\\"}g;  # \\" -> \\\\\"
    $text =~ s{(?<!\\)\\"}{\\\\\\"}g; # \"  -> \\\"
    $text =~ s{(?<!\\)"}{\\"}g;       # "   -> \"
    $text = qq{"$text"} if $text =~ /[ \t#]/; # hash because gmake 4.2.1

    # Apply the Command Prompt parsing rules (cmd.exe)
    my @text = split /("[^"]*")/, $text;
    # We should also escape parentheses, but it breaks one-liners containing
    # $(MACRO)s in makefiles.
    s{([<>|&^@!])}{^$1}g foreach grep { !/^"[^"]*"$/ } @text;
    $text = join('', @text);

    # dmake expands {{ to { and }} to }.
    if( $self->is_make_type('dmake') ) {
        $text =~ s/{/{{/g;
        $text =~ s/}/}}/g;
    }

    $text = $opts->{allow_variables}
      ? $self->escape_dollarsigns($text) : $self->escape_all_dollarsigns($text);

    return $text;
}


sub escape_newlines {
    my($self, $text) = @_;

    # Escape newlines
    $text =~ s{\n}{\\\n}g;

    return $text;
}


=item cd

dmake can handle Unix style cd'ing but nmake (at least 1.5) cannot.  It
wants:

    cd dir1\dir2
    command
    another_command
    cd ..\..

=cut

sub cd {
    my($self, $dir, @cmds) = @_;

    return $self->SUPER::cd($dir, @cmds) unless $self->is_make_type('nmake');

    my $cmd = join "\n\t", map "$_", @cmds;

    my $updirs = $self->catdir(map { $self->updir } $self->splitdir($dir));

    # No leading tab and no trailing newline makes for easier embedding.
    my $make_frag = sprintf <<'MAKE_FRAG', $dir, $cmd, $updirs;
cd %s
	%s
	cd %s
MAKE_FRAG

    chomp $make_frag;

    return $make_frag;
}


=item max_exec_len

nmake 1.50 limits command length to 2048 characters.

=cut

sub max_exec_len {
    my $self = shift;

    return $self->{_MAX_EXEC_LEN} ||= 2 * 1024;
}


=item os_flavor

Windows is Win32.

=cut

sub os_flavor {
    return('Win32');
}

=item dbgoutflag

Returns a CC flag that tells the CC to emit a separate debugging symbol file
when compiling an object file.

=cut

sub dbgoutflag {
    $MSVC ? '-Fd$(*).pdb' : '';
}

=item cflags

Defines the PERLDLL symbol if we are configured for static building since all
code destined for the perl5xx.dll must be compiled with the PERLDLL symbol
defined.

=cut

sub cflags {
    my($self,$libperl)=@_;
    return $self->{CFLAGS} if $self->{CFLAGS};
    return '' unless $self->needs_linking();

    my $base = $self->SUPER::cflags($libperl);
    foreach (split /\n/, $base) {
        /^(\S*)\s*=\s*(\S*)$/ and $self->{$1} = $2;
    };
    $self->{CCFLAGS} .= " -DPERLDLL" if ($self->{LINKTYPE} eq 'static');

    return $self->{CFLAGS} = qq{
CCFLAGS = $self->{CCFLAGS}
OPTIMIZE = $self->{OPTIMIZE}
PERLTYPE = $self->{PERLTYPE}
};

}

=item make_type

Returns a suitable string describing the type of makefile being written.

=cut

sub make_type {
    my ($self) = @_;
    my $make = $self->make;
    $make = +( File::Spec->splitpath( $make ) )[-1];
    $make =~ s!\.exe$!!i;
    if ( $make =~ m![^A-Z0-9]!i ) {
      ($make) = grep { m!make!i } split m![^A-Z0-9]!i, $make;
    }
    return "$make-style";
}

1;
__END__

=back
