#!/usr/bin/perl
# Bootstrap GNU base system to HP-UX 11v1
# Created by <georg@bege.email>
# Version 0.70 - Licensed under the BSD license
use 5.8.0;
use strict;
use warnings;
our $Build	= "hppa2.0w-hp-hpux11.11";
#our $Build = "hppa64-hp-hpux11.11";
#our $CFlags = "+DD64";

our $Prefix = "/home/build32/gnu";
our $CC		= "/opt/ansic/bin/cc";
our $AS		= "/usr/ccs/bin/as";
our $LD		= "/usr/ccs/bin/ld";
our $Make	= "/usr/bin/make";
our $Gunzip = "/usr/contrib/bin/gunzip";
our $CLEANUP = defined;	# Note: Comment if you dont want to remove source directories (beware space usage!)
our $MIRROR	= "http://hpux.unix.io/bootstrap";
our $KITNAME = "hppa20-tools.tar";

### sanity checks first ###
if(`uname -s` !~ /HP-UX/ or `uname -r` !~ /B.11.11/) {
	die "This bootstrap script is only for HP-UX 11i v1!\n";
}
#elsif(`uname -n` !~ /hppa/) {
#	die "This bootstrap script is only for HPPA!\n";
#}

if(`whoami` =~ /^root/) {
	die "Do not run this script as root, create a dedicated user for example 'build32'.\n";
}
if(-e "/usr/local" && !-o "/usr/local") {
	die "If /usr/local does exist, it must be owned by us (spurious gcc3 Makefile requirements...)!\n";
}

# could be that some configure routines will automatically detect stuff here?
#die "/usr/local should not be accessible to this user.\n" if -x "/usr/local";
# iconv.h accessible only a issue for >= gcc4.x?
die "/usr/include/iconv.h should not be readable by this user.\n" if -r "/usr/include/iconv.h";

# Check compiler requirements
die "Missing ANSIC compiler: $CC\n"			if !-x $CC;
die "Missing HPUX assembler: $AS\n"			if !-x $AS;
die "Missing HPUX linker: $LD\n"			if !-x $LD;
die "Missing HPUX Make: $Make\n"			if !-x $Make;
die "Please create local prefix: $Prefix\n" if !-x $Prefix;

# Check software requirements
if(-x "/usr/sbin/swlist") {
	print "> Checking software requirements...\n";
	my @req = ("B3901BA", "BUNDLE11i", "GOLDAPPS11i", "GOLDBASE11i", "PHSS_34412");
	my $swcmd = '/usr/sbin/swlist|awk {\'print $1\'}';
	my @swlist = split /\n/, qx/$swcmd/;
	@swlist = grep {!/^#|^\s+$/} @swlist;
	my %swlist = map { $_ => 1 } @swlist;
	foreach(@req) {
		die "Please install $_ first!\n" if !exists $swlist{$_};
	}
}
else {
	warn "> Cannot access swlist, unable to check software requirements!\n";
}

# Check PATH environment
our @Path = split /:/, $ENV{'PATH'};
our %Path = map { $_ => 1 } @Path;
die "Expecting /opt/ansic/bin in PATH!\n"	if !exists $Path{"/opt/ansic/bin"};
die "Expecting /usr/ccs/bin in PATH!\n"		if !exists $Path{"/usr/ccs/bin"};
die "Expecting $Prefix/bin in PATH!\n"		if !exists $Path{"$Prefix/bin"};
$ENV{'CC'} = $CC;
# Note: since $Prefix is in path, with each install, more and more GNU tools will take over
# hence enabling more compatibility for all further packages.

# check available tool tarballs
# Note: please keep traditional tarball names (not .tbz...)
our @Bootstrap = (
	"grep-2.5.1.tar.bz2",
	"sed-4.1a.tar.gz",
	"m4-1.4.11.tar.bz2",
	"make-3.80.tar.bz2",		# Note: make will be switched to GNU make once installed
	"gzip-1.3.9.tar.gz",
	"libiconv-1.9.2.tar.gz",
	"gettext-0.14.1.tar.gz",	# circular dep
	"gawk-3.1.4.tar.bz2",
	"libunistring-0.9.1.1.tar.gz",
	"autoconf-2.63.tar.bz2",
	"automake-1.9b.tar.bz2",
	"texinfo-4.5.tar.gz",
	"flex-2.5.36.tar.gz",
	"bison-1.31.tar.bz2",
	"zlib-1.2.1.1.tar.gz",
	"bzip2-1.0.1.tar.gz",
	"tar-1.25.tar.gz",
	"binutils-2.14.tar.bz2",
	"gcc-3.3.6.tar.bz2"			# gcc3 is the END of bootstrapping
);

our @Additionals = (
	"make-4.0.tar.bz2",
	"gdb-5.3.tar.bz2",
	"bash-3.0.tar.gz",
	"nano-1.2.3.tar.gz",
#	"wget-1.11.1.tar.bz2",
);

if(-r $KITNAME.".gz" and !-x $Gunzip) {
	die "> Found $KITNAME, but no access to gunzip tool - please gunzip the archive yourself, then re-run the script.\n";
}
elsif(-r $KITNAME.".gz") {
	system($Gunzip." ".$KITNAME);
}

if(-r $KITNAME) {
	print "> Extract tools to bin...\n";
	system("tar -vxf $KITNAME");
}
else {
	die "Unable to find $KITNAME... bailing out!\n";
}

# check for tools available, fetch as required
foreach(@Bootstrap) {
	next if -r $_;
	system("bin/wget $MIRROR/$_");
	die "Missing $_!\n" if !-r $_;
}

our %Configure = (
	"grep"	=> "./configure --prefix=$Prefix --build=$Build --disable-nls",
	"sed"	=> "./configure --prefix=$Prefix --build=$Build --disable-nls --enable-threads=posix",
	"m4"	=> "./configure --prefix=$Prefix --build=$Build",
	"make-4.0" => "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"make"	=> "./configure --prefix=$Prefix --build=$Build",
	"gzip"	=> "./configure	--prefix=$Prefix --build=$Build",
	"libiconv" => "./configure --prefix=$Prefix --build=$Build --disable-nls --without-libiconv-prefix --without-libintl-prefix",
	"gettext" => "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"gawk"	=> "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"libunistring" => "./configure --prefix=$Prefix --build=$Build --with-libiconv-prefix=$Prefix",
	"autoconf"	=> "./configure --prefix=$Prefix --build=$Build",
	"automake"	=> "./configure --prefix=$Prefix --build=$Build",
	"texinfo"	=> "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"flex"		=> "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"bison"		=> "./configure --prefix=$Prefix --build=$Build	--disable-nls --with-libiconv-prefix=$Prefix",
	"zlib"		=> "./configure --prefix=$Prefix",
	"binutils"	=> "./configure --prefix=$Prefix --build=$Build",
	"gmp"		=> "./configure --prefix=$Prefix --build=$Build",
	"mpfr"		=> "./configure --prefix=$Prefix --build=$Build --with-gmp=$Prefix",
	"gcc"		=> "./configure --prefix=$Prefix --build=$Build --enable-languages=c,c++ --with-gnu-as --without-gnu-ld --with-as=$Prefix/bin/as --with-ld=$LD --enable-threads=posix",
	"gcc-4.2"	=> "./configure --prefix=$Prefix --build=$Build --enable-languages=c,c++ --with-gnu-as --without-gnu-ld --with-as=$Prefix/bin/as --with-ld=$LD --enable-threads=posix --with-gmp=$Prefix --with-mpfr=$Prefix",
	"gdb"		=> "./configure --prefix=$Prefix --build=$Build",
	"tar"		=> "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"wget"		=> "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix",
	"bash"		=> "./configure --prefix=$Prefix --build=$Build --disable-nls --with-libiconv-prefix=$Prefix --without-libintl-prefix"
);

sub Prompt($) {
	my $ret = int shift;
	
	print "Do you want to continue? [Y/N] ";
	if(<STDIN> !~ /^Y/) {
		print "Aborted.\n";
		exit($ret);
	}
}

# Extract tarball
# - provide filename information
sub Extract($) {
	my $abs		= shift;
	my $dir		= (split /.tar/, $abs)[0];
	my $name	= (split /-/, $dir)[0];
	my $ver		= (split /-/, $dir)[1];
	
	print "> Extracting $abs...\n";
	if($abs =~ /.gz$/) {
		system("bin/zcat $abs|bin/tar xf -");
	}
	elsif($abs =~ /.bz2$/) {
		system("bin/bzcat $abs|bin/tar xf -");
	}
	else {
		die "Unknown format: $abs!\n";
	}
	
	return $dir, $name, $ver;
}

sub Bzip2_fix($) {
	my $dir = shift;
	open my $inp, '<', "$dir/Makefile" or die "Can't read Makefile: $!\n";
	open my $out, '>', "$dir/Makefile.new" or die "Can't open Makefile.new for writing: $!\n";
	foreach(<$inp>) {
		s/^CC=gcc/CC=$CC/;
		s/^CFLAGS=.+$/CFLAGS=-O2/;
		print $out $_;
	}
	close $out;
	close $inp;
	system("mv $dir/Makefile.new $dir/Makefile");
	my $cmd = "cd $dir; $Make && $Make PREFIX=$Prefix install";
	if(system($cmd) != 0) {
		warn "ERROR: While make process in $dir!\n";
		Prompt(1);
	}
}

sub GMP511_postinst_fix {
	my @patch = (
		"\n",
		"#if __GMP_HAVE_CONST\n",
		"#define __gmp_const   const\n",
		"#define __gmp_signed  signed\n",
		"#else\n",
		"#define __gmp_const\n",
		"#define __gmp_signed\n",
		"#endif\n"
	);
	
	open my $inp, '<', "$Prefix/include/gmp.h" or die "Can't read gmp.h: $!\n";
	my @lines = <$inp>;
	close $inp;
	# insert fix after line 61
	if($lines[60] !~ /^#endif/) {
		warn "Couldn't fix gmp.h, line 61 was unexpected.\n";
		Prompt(1);
		return;
	}
	splice @lines, 61, 0, @patch;
	
	open my $out, '>', "$Prefix/include/gmp.new", or die "Can't open gmp.new for writing: $!\n";
	print $out @lines;
	close $out;
	system("mv $Prefix/include/gmp.new $Prefix/include/gmp.h");
}

sub GCC42_libstdc_fix($) {
	my $dir = shift;
	print "> Applying patches for GCC4.2 in $dir...\n";
	
	# cstdlib fix, commenting "using ::abs" out
	my $cstdlib_file = $dir."/$Build/libstdc++-v3/include/cstdlib";
	if(!-r $cstdlib_file) {
		warn "Unable to find $cstdlib_file!\n";
		return 1;
	} else {
		open my $inp, '<', $cstdlib_file or die "Unable to open file $cstdlib_file: $!\n";
		my @lines = <$inp>;
		close $inp;
		
		foreach(@lines) {
			last if s/using ::abs;$/\/\/using ::abs;/;
		}
		open my $out, '>', $cstdlib_file.".new" or die "Unable to open file $cstdlib_file: $!\n";
		print $out @lines;
		close $out;
		system("mv $cstdlib_file.new $cstdlib_file");
	}
	
	# cmath fix, commenting "using ::pow" out
	my $cmath_file = $dir."/$Build/libstdc++-v3/include/cmath";
	if(!-r $cmath_file) {
		warn "Unable to find $cmath_file!\n";
		return 1;
	} else {
		open my $inp, '<', $cmath_file or die "Unable to open file $cmath_file.new: $!\n";
		my @lines = <$inp>;
		close $inp;
		
		foreach(@lines) {
			last if s/using ::pow;$/\/\/using ::pow;/;
		}
		open my $out, '>', $cmath_file.".new" or die "Unable to open file $cmath_file.new: $!\n";
		print $out @lines;
		close $out;
		system("mv $cmath_file.new $cmath_file");
	}
	
	# missing size_t include fix, inserting in cstddef
	my @cstddef_patch = (
		"#  ifndef _SIZE_T\n",
		"#    include <sys/_size_t.h>\n",
		"#  endif\n"
	);
	my $cstddef_file = $dir."/$Build/libstdc++-v3/include/cstddef";
	if(!-r $cstddef_file) {
		warn "Unable to find $cstddef_file!\n";
		return 1;
	} else {
		open my $inp, '<', $cstddef_file or die "Unable to open $cstddef_file: $!\n";
		my @lines = <$inp>;
		close $inp;
		
		if($lines[50] !~ /^#include/) {
			warn "Unable to find junction line in $cstddef_file!\n";
			return 1;
		}
		
		splice @lines, 50, 0, @cstddef_patch;
		open my $out, '>', $cstddef_file.".new" or die "Unable to open file $cstddef_file.new: $!\n";
		print $out @lines;
		close $out;
		system("mv $cstddef_file.new $cstddef_file");
	}
	
	# va_args fix, replacing lines in stdarg.h
	my @vaargs_patch = (
		"#ifndef	__GNUC_VA_LIST\n",
		" #define __GNUC_VA_LIST\n",
		" #ifndef __STRICT_ANSI__\n", 
		"  #ifdef _VA_LIST /* HPUX VA_LIST	is not compatible with gcc 4.x builtin */\n",
		"   #define	 va_list  __hp_va_list\n",
		"  #endif\n",
		" #endif\n\n",
		" typedef	__builtin_va_list __gnuc_va_list;\n",
		" #ifndef __STRICT_ANSI__\n",
		"  typedef	__gnuc_va_list va_list;\n",
		" #endif\n",
		"#endif\n"
	);
	my $stdarg_file = $dir."/host-$Build/gcc/include/stdarg.h";
	if(!-r $stdarg_file) {
		warn "Unable to find $stdarg_file!\n";
		return 1;
	} else {
		open my $inp, '<', $stdarg_file or die "Unable to open file $stdarg_file: $!\n";
		my @lines = <$inp>;
		close $inp;
		
		if($lines[40] !~ /__GNUC_VA_LIST$/) {
			warn "Unable to find junction line in $stdarg_file!\n";
			return 1;
		}
		
		splice @lines, 40, 4, @vaargs_patch;
		open my $out, '>', $stdarg_file.".new" or die "Unable to open file $stdarg_file.new: $!\n";
		print $out @lines;
		close $out;
		system("mv $stdarg_file.new $stdarg_file");
	}
	
	# va_args call fix, replacing lines in c++locale.h
	my @vaargs_patch2 = (
		"double *__args;\n",
		"void *_gnuc_args = __args;\n"
	);
	my $cpplocale_hdr_file = $dir."/$Build/libstdc++-v3/include/$Build/bits/c++locale.h";
	if(!-r $cpplocale_hdr_file) {
		warn "Unable to find $cpplocale_hdr_file!\n";
		return 1;
	} else {
		open my $inp, '<', $cpplocale_hdr_file or die "Unable to open file $cpplocale_hdr_file: $!\n";
		my @lines = <$inp>;
		close $inp;
		
		if($lines[75] !~ /va_list __args;$/) {
			warn "Unable to find junction line in $cpplocale_hdr_file!\n";
			return 1;
		}
		
		splice @lines, 75, 1, @vaargs_patch2;
		$lines[77] =~ s/__args/_gnuc_args/;
		$lines[85] =~ s/__args/_gnuc_args/;
		open my $out, '>', $cpplocale_hdr_file.".new" or die "Unable to open file $cpplocale_hdr_file.new: $!\n";
		print $out @lines;
		close $out;
		system("mv $cpplocale_hdr_file.new $cpplocale_hdr_file");
	}
	
	# double-cast fix, replacing lines in c++locale.cc
	# Note: we need to continue compiling, until this file is prepared
	my @dblcast_patch = (
		"long_double __hpld = strtold(__s, &__sanity);\n",
		"long double __ld = *((long double*)&__hpld);\n"
	);
	
	my $cpplocale_impl_file = $dir."/$Build/libstdc++-v3/src/c++locale.cc";
	system("cd $dir; $Make") if !-r $cpplocale_impl_file;
	if(!-r $cpplocale_impl_file) {
		warn "Unable to find $cpplocale_impl_file!\n";
		return 1;
	} else {
		open my $inp, '<', $cpplocale_impl_file or die "Unable to open file $cpplocale_impl_file: $!\n";
		my @lines = <$inp>;
		close $inp;
		
		if($lines[115] !~ /__sanity\);$/) {
			warn "Unable to find junction line in $cpplocale_impl_file!\n";
			return 1;
		}
		splice @lines, 115, 1, @dblcast_patch;
		open my $out, '>', $cpplocale_impl_file.".new" or die "Unable to open file $cpplocale_impl_file.new: $!\n";
		print $out @lines;
		close $out;
		system("mv $cpplocale_impl_file.new $cpplocale_impl_file");
	}
	
	return 0;
}

sub GCC42_Build($) {
	my $abs = shift;
	my ($dir, $name, $ver) = Extract($abs);
	
	my $config = $Configure{"gcc-4.2"};
	my $cmd = "cd $dir; $config && $Make";
	# Note: make is expected to fail while doing libstdc++
	
	if(system($cmd) != 0 && GCC42_libstdc_fix($dir) != 0) {
		warn "ERROR: While make process in $dir!\n";
		Prompt(1);
		return;
	}
	
	# continue with GCC42 make
	$cmd = "cd $dir; $Make && $Make install";
	if(system($cmd) != 0) {
		warn "ERROR: While make process in $dir!\n";
		Prompt(1);
	}
}

sub Build($$) {
	my $abs = shift @_;	# arg 0: path to tarball
	my $run = shift @_;	# arg 1: make commands
	
	# Extract tarball-
	my ($dir, $name, $ver) = Extract($abs);
	
	# Note: bzip2 requires altering of Makefile!
	print "> Bootstrapping $name...\n";
	if($name =~ /^bzip2/) {
		Bzip2_fix($dir);
		goto cleanup;
	}
	
	# select configuration string
	my $config = $Configure{$name.'-'.$ver};
	$config = $Configure{$name} if !defined($config);
	
	if( !defined($config) ) {
		die "ERROR: Unable to find configure for $name\n";
	}
	
	# default build process
	else {
		my $cmd = "cd $dir; ".$config;
		if(system($cmd) != 0) {
			warn "ERROR: While configure in $dir!\n";
			Prompt(1);
		
			print "> Deleting $dir...\n";
			system("rm -rf $dir") if defined($CLEANUP);		
			next;
		}
	
		$cmd = "cd $dir; ".$run;
		if(system($cmd) != 0) {
			warn "ERROR: While make process in $dir!\n";
			Prompt(1);
		}
	};
	
cleanup:
	print "> Deleting $dir...\n";
	system("rm -rf $dir") if defined($CLEANUP);
}

print "> Starting GNU bootstrap to $Prefix...\n";
sleep(2);
$Make = "$Prefix/bin/make" if -x "$Prefix/bin/make";

foreach(@Bootstrap) {
	last if $_ =~ /^gcc/;
	Build($_, "$Make && $Make install");
	
	# - update make tool with GNU Make
	$Make = "$Prefix/bin/make" if $_ =~ /^make/;
}
print "> Phase 1 is complete.\n";

# It's time to bootstrap GCC3
print "> Starting bootstrap of GCC3...\n";
Build("gcc-3.3.6.tar.bz2", "$Make bootstrap && $Make && $Make install");
print "> Phase 2 is complete -- bootstrap was successful.\n";

print "> Do you want to continue bootstrapping GCC4.2?\n";
Prompt(0);

## bootstrapping GCC4.2 ##
# - dependencies first
print "> Bootstrapping required dependencies for GCC4.2...\n";
sleep(2);
$ENV{'CC'} = "$Prefix/bin/gcc";
delete $ENV{'CFLAGS'};

# - set sane default GCC CFLAGS for this platform (if possible)
our $CFLags = "";
if		($Build =~ /^hppa2.0/) {	$CFLags = "-mpa-risc-2-0";	}
elsif	($Build =~ /^hppa1.1/) {	$CFLags = "-mpa-risc-1-1";	}
elsif	($Build =~ /^hppa1.0/) {	$CFLags = "-mpa-risc-1-0";	}

Build("gmp-5.1.1.tar.bz2", "$Make && $Make install");
print "> Applying post-install fixes for GMP 5.1.1\n";
GMP511_postinst_fix();
# Note: if we've to override CFLAGS in env, then it's sane to also specify our ABI
$ENV{'CFLAGS'} = "$CFLags -D__GMP_HAVE_CONST";
Build("mpfr-2.3.2.tar.bz2", "$Make && $Make install");
delete $ENV{'CFLAGS'};

# - starting bootstrap of GCC4.2
print "> Starting bootstrap of GCC4.2...\n";
print "> Note: GCC4.2 will fail a couple of times, this is to be expected.\n";
sleep(1);
GCC42_Build("gcc-4.2.3.tar.bz2");
print "> Bootstrap complete.\n";

print "> The script can continue to bootstrap additional utilities.\n";
Prompt(0);

# Continue with additional packages
foreach(@Additionals) {
	system("bin/wget $MIRROR/$_");
	next if !-r $_;
	Build($_, "$Make && $Make install");
}
print "> Phase 3 is complete -- all packages completed.\n";

exit(0);
