The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -w
$|++;

use ExtUtils::MakeMaker;
use Config qw(%Config); #for $Config{cc} 
use Getopt::Long;
use Cwd;

use strict;

my ($force_cflags, $force_pp, $force_cc) = ('', undef, undef);
unless( GetOptions( 'cc=s'        => \$force_cc,
                    'cflags=s'    => \$force_cflags,
                    'xs'          => sub { $force_pp = 'n' },
                    'pp|pureperl' => sub { $force_pp = 'y' } ) ) {
	die "usage: $0 [ -pp | -xs [ -cc=/path/to/cc ] [ -cflags='-O -fPIC' ] ]\n".
	    "       -pp indicates pureperl, -xs indicates the xs implementation\n".
	    "       -cc is used to specify the path to the cc you would like to\n".
	    "       use if autodetection fails. similarly for -cflags, which is\n".
	    "       particularly useful when building with a different cc than\n".
	    "       the one which was used to build perl. -cflags also accepts\n".
	    "       the shortcut argument \"gcc\" which uses options for that\n".
	    "       compiler. -cc and -cflags have no effect when used with -pp\n";
}
#-cc or -cflags imply -xs
$force_pp = 'n' if($force_cflags or $force_cc);

#TODO: is this general enough?
if($force_cflags eq 'gcc') {
	$force_cflags = '-O -g -fPIC -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64';
}

### non-compile-related platform specific hacks go here, for example,
### on windows NT pids are in multiples of 4
my $defines = '';
# win32 crud
if ($^O eq 'MSWin32') {
	if(defined($ENV{OS})) {
		if($ENV{OS} eq "Windows_NT") {
			# use multiples of 4 for pid
			$defines .= " -Dwin32_pids_mult4";
		} else {
			# win95/98/me, i presume...
			warn "wow, will i really run on $ENV{OS}?"; 
		}
	} else {
		warn "unsupported win32 OS - no \$ENV{OS} set...";
	}
}

# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
my %Makefile = (
	NAME              => 'Proc::Exists',
	VERSION_FROM      => 'Exists.pm',
	PREREQ_PM         => {
	                       'Config'              => 0,
	                       'Exporter'            => 0,
	                       'ExtUtils::MakeMaker' => 0,
	                       'Getopt::Long'        => 0,
	                       'Test::More'          => 0, 
                        },
	($] >= 5.005 ?     ## Add these new keywords supported since 5.005
		(ABSTRACT_FROM => 'Exists.pm',
		 AUTHOR        => 'Brian Szymanski <ski-cpan@allafrica.com>') : ()),
	LIBS              => [''],
	DEFINE            => $defines, 
	INC               => '-I.',

	#MakeMaker LICENSE support: 6.30 - no, 6.30_0[1234] - yes, >=6.31 - yes
	#but some versions that identify as 6.30_01 are really rev 4535 which
	#does NOT understand the LICENSE key. and yes, this is quite a lot
	#of effort to avoid a single warning message on old systems :-)
	( ( (compare_version( $ExtUtils::MakeMaker::VERSION, '6.30') > 0 ) &&
	    ($ExtUtils::MakeMaker::Revision !~ /4535/) ) ?
         (LICENSE => 'perl') : () ),

	#MakeMaker NO_META support introduced in 6.10_03
	( compare_version($ExtUtils::MakeMaker::VERSION, '6.10.03') >= 0 ?
		(NO_META => 1) : () ),
	                           
	##Un-comment this if you add C files to link with later:
	# OBJECT          => '$(O_FILES)', # link all the C files too
	XS                => {},
	C                 => [],

	dist              => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
	clean             => { FILES => 'Proc-Exists-*' },
	realclean         => { FILES => '*~' },
);

# An existing makefile can confuse the CC test.
unlink('Makefile');

### ask if you want to use XS or pureperl (default=XS)
if(!defined($force_pp)) {
	print <<END;
It is recommended to use the XS code, which is faster, and compatible 
with non-cygwin win32. You should only use the pureperl option if you 
are on a POSIX system *without* a functional c compiler, or you have
trouble compiling the XS.

END
	$force_pp = prompt("Do you want to use the pureperl implementation (y/n)?", "no");
}

### determine what C compiler to use, if any
my $CC; 
my $use_pp; 

if($force_pp =~ /^y/i) {
	$use_pp = "user requested pureperl";
} else {
	$use_pp = use_pure_perl();
	$Makefile{CC} = $CC; 
	
	if($CC && !$use_pp) {
		my $is_gcc = `$CC -v 2>&1`; $is_gcc = ($is_gcc =~ /^gcc\s+version/im);
		#my $is_sc = `$CC -V 2>&1`; $is_sc = ($is_sc =~ /^cc:\s+Sun\s+C/im);

		# if we have a perl built by something other than gcc, but a gcc
		# compiler is available, we'll need to munge CFLAGS and such later.
		if($is_gcc && !$Config::Config{gccversion}) {
			$force_cflags = '-O2 -g -fPIC';
			unless($^O =~ /^(?:solaris|hpux)/) {
				warn "gcc on $^O is uncharted territory, please report results!\n";
			}
		}

		# don't try to link via a non-existent cc (solaris 2006-06 w/ only gcc)
		if(($^O eq 'solaris') && ($CC ne $Config{cc}) && ($Config{ld} eq $Config{cc})) {
			$Makefile{LD} = $CC; 
		}
	}

	if($use_pp) {
		print "NO: $use_pp\n"; 
		print <<END;

I cannot detect a working C compiler. I will install the
perl-only implementation. Expect degraded performance.
Alternately, you can re-run Makefile.PL like so:
   perl Makefile.PL -cc=/path/to/your/c-compiler
note there is also a -cflags option.

END
	} else {
		print "YES\n";
	}
}

#tinker with %Makefile based om whether we are using pure perl or not
if($use_pp) {
	delete $Makefile{CC}; 
	delete $Makefile{LD}; 
} else {
	delete $Makefile{XS}; 
	delete $Makefile{C}; 
}

### change %Makefile as necessary to comply with the compiler we have
### if we have a force_cflags argument.
if( $force_cflags ) {
	#NOTE: must be a true value to override, so use a space instead
	#      of an empty string in to override the autodetected values
	$Makefile{OPTIMIZE} = ' ';
	$Makefile{CCFLAGS} = $force_cflags;
	$Makefile{CCCDLFLAGS} = ' '; #just on hpux?
} 
### (finished tinkering with cc related variables in Makefile)

### write out Configuration.pm and Makefile
write_config( defined $use_pp ? (want_pureperl => $use_pp ? 1 : 0) : () );
WriteMakefile(%Makefile); 

### return -1, 0, or 1 based on whether the first version string is 
### greater than the second. assume 1.2 < 1.2.0 when necessary
sub compare_version {
	my ($v1, $v2) = @_; 
	# 6.30_01 => ( 6, 30, 01 ), etc.
	my @v1 = split /[._]/, $v1; 
	my @v2 = split /[._]/, $v2; 
	#pad versions to the same length. 1.2 < 1.2.0 if they both exist
	push @v2, -1 while(@v2 < @v1); 
	push @v1, -1 while(@v1 < @v2); 
	for my $i (0..$#v1) {
		my $r = $v1[$i] <=> $v2[$i]; 
		return $r if $r; 
	}
	return 0;
}

### update the relevant entries in Configuration.pm
sub write_config {
	my %args = @_; 

	my $wd = getcwd();
	chdir('Exists') || die "can't change to Exists subdirectory: $!"; 
	open(CONF, '<Configuration.pm') || die "can't read Configuration.pm: $!"; 
	my @lines = map { chomp; $_ } <CONF>; 
	close(CONF);
	my $do_update = 0;
	my %old_config; 
	foreach my $key (keys %args) {
		my @t = grep {defined($_)} map { /^\s*\$Proc::Exists::Configuration::$key\s*=\s*(\d+)\s*;\s*$/; $1 } @lines; 
		die "Configuration.pm in a weird state, not the right number of ${key}'s\n... if you are confused, restore Exists/Configuration.pm from the distribution\n" if(@t != 1);
		$old_config{$key} = $t[0]; 
		$do_update = 1 unless $args{$key} eq $t[0]; 
	}
	if($do_update) {
		my $warn_str = 'updating Configuration.pm -- ';
		foreach my $k (sort keys %args) {
			unless($old_config{$k} eq $args{$k}) {
				$warn_str .= "$k: $old_config{$k} => $args{$k}, ";
			}
		}
		$warn_str =~ s/, $//; 
		warn "$warn_str on os: $^O\n"; 
		foreach my $ln (0..$#lines) {
			foreach my $key (keys %args) {
				$lines[$ln] =~ s/$key\s*=\s*\d+\s*;\s*$/$key = $args{$key};/; 
			}
		}
		open(CONF, '>Configuration.pm') || die "can't write Configuration.pm: $!"; 
		print CONF "$_\n" foreach @lines; 
		close(CONF);
	}
	chdir($wd) || die "can't restore pwd to '$wd': $!"; 
}

### use_pure_perl determines if we should use the pure perl 
### implementation or the faster XS
# The perl/C checking voodoo is stolen from Olaf Kolkman's 
# Net-DNS via Graham Barr's Scalar-List-Utils distribution.
sub use_pure_perl {
	print "Testing if you have a working C compiler and the needed header files...\n";

	return "cannot write compile.c" unless open(FH, ">compile.c");

	print FH <<'EOF';
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
int main() { return 0; }
EOF

	return "cannot close compile.c" unless close(FH); 

	#sometimes $Config{CC} is not the answer (e.g. solaris10 w/ gcc but
	#no sun c package installed)...
	my @cc_alternatives = ( $Config{cc}, qw( cc gcc clang egcs icc pcc lcc ));
	#if all fails but CC env var is set, try it, maybe it will link
	push @cc_alternatives, $ENV{CC} if($ENV{CC}); 
	if(!$force_cc && ($^O eq 'hpux')) {
		#- acc must come before cc for hp-ux, where we need C/ANSI C
		#compiler and not the default cc ( /usr/ccs/bin/cc )
		#TODO: instead of skippping default cc, try to massage its arguments
		if (grep /option\s+is\s+available\s+only\s+with\s+the\s+C\/ANSI\s+C\s+product/, `cc -Ae 2>&1` ) {
			warn "hpux: skipping built in cc, which can't support the same ".
			     "options that c/ansi c (which was used to compile perl) ".
			     "says we need\n";
			@cc_alternatives = grep !/^cc$/, @cc_alternatives; 
			#acc (HPUX C/ANSI C) will however work if it exists...
			unshift @cc_alternatives, 'acc';
		}
		unlink 'a.out'; #clean up after `cc -Ae` test
	}
	unshift @cc_alternatives, $force_cc if(defined($force_cc)); 
	my $ret; 
	print "trying compilers: "; 
	foreach my $cc ( @cc_alternatives )  {
		print "$cc... "; 
		my $cmd = "$cc -c compile.c -o compile$Config{obj_ext}"; 
		$ret = system($cmd); 
		#if on hpux (or others?) check the bitness of perl and cc's output
		#in general, check file's output on `which cc` and compile*
		if(($ret == 0) && ($^O eq 'hpux')) {
			my $out_type  = (split /:\s*/, `file compile$Config{obj_ext}`)[1]; 
			chomp $out_type; 
			my $perl_type = $Config::Config{archname}; 
			$out_type =~ /RISC\s*(1\.[01]|2\.0)/;
			my $out_risc_spec = ($out_type =~ /RISC\s*(1\.[01]|2\.0)/, $1); 
			my $perl_risc_spec = ($perl_type =~ /RISC\s*(1\.[01]|2\.0)/, $1); 
			#e.g. PA-RISC2.0-thread-multi-LP64 vs. PA-RISC1.1-thread-multi
			if($out_risc_spec ne $perl_risc_spec) {
				warn "compiler $cc seems to output the wrong type of files, ".
				     "skipping: '$out_type' vs. '$perl_type'\n";
				$ret = -1; #that's an error
				foreach my $file (glob('compile*')) {
					next if($file eq 'compile.c');
					unlink($file) || warn "Could not delete $file: $!\n"; 
				}
			}
		}
		if($ret==0) { $CC=$cc; last };
	}
   
	foreach my $file (glob('compile*')) {
		unlink($file) || warn "Could not delete $file: $!\n"; 
	}

	if ($ret == 0) {
		return 0;
	} else {
		return "failed";
	}
}