#!/usr/bin/perl -w
# palinux-autobuilder - automatically build binutils, gcc, glibc, include
#	 tarball, a kernel, a lifimage, and make them available. Keeps a log
#	 file of the build. If the build fails it emails an error report.
#
# MJT 
# started 5/2000

### Setup
use 5;
use strict;
use Net::SMTP;
use POSIX qw(strftime);
use vars qw(
	$address
	$top $src $scratch $tempxc $xcprefix $nativeprefix $logdir $log
	$socks $asmname
	$local $target $target64
	$repository $binutils $gcc $glibc $linux $palo
	$tmpbinutilsconfig $tmpgccconfig
	$xcbinutilsconfig $xcgccconfig $xcglibcconfig
	$xc64binutilsconfig $xc64gccconfig
	$nativebinutilsconfig $nativegccconfig $nativeglibcconfig
	$pseudoroot
	);
# Set to 1 to turn on
# MJT 2000-12-06 NATIVE won't work until you've done the nss hack
use constant CROSS => 1;
use constant CROSS64 => 1;
use constant NATIVE => 0;

### USER VARIABLES

# Where to email problems
$address='taggart@carmen.fc.hp.com';

# use socks?
$socks="socksify";

## build directories
$top="/home/al-76/puffin";	# Top level
$src="$top/palinux";		# Where your checked-out CVS sources live
$scratch="$top/xc-build";	# Where your build sub-dirs go
$tempxc="$top/xc";		# Where the temporary binutils/gcc bits go
$xcprefix="/opt/palinux";	# Where to put the final xc bits, needs to be
				# owned by the current user(to avoid sudo)
$nativeprefix="/usr";		# Where the cross-compiled native tools
				# will be installed on the native system
$pseudoroot="$top/pseudoroot";	# Where the pseudoroot trees will live
$logdir="$top/log";		# Where to put the log files

## which target, host, build
$target="hppa-linux";
$target64="hppa64-linux";
$local="i386-linux";
# The suffix on the asm directory (yes this sucks)
$asmname="parisc";

## CVS module names
$repository=":pserver:anonymous\@192.25.206.4:/home/cvs/parisc";
$binutils="binutils";
$gcc="gcc";
$glibc="glibc";
$linux="linux";
$palo="palo";

## extra configure flags
# For the temporary 32bit tools
$tmpbinutilsconfig="--disable-nls";
$tmpgccconfig="--disable-shared --disable-nls ".
	"--with-headers=$src/include --without-libc ".
	"--enable-languages=c --disable-sanity-checks";

# For the 32bit cross tools
$xcglibcconfig="--without-cvs --disable-shared --disable-profile ".
	"--enable-static-nss --enable-add-ons ".
 	"--with-headers=$src/$linux/include";
$xcbinutilsconfig="--disable-nls";
$xcgccconfig="--disable-shared --disable-nls ".
	"--with-libc --with-libs=$xcprefix/lib";
# workarounds needed for the new gcc devel branch, Dec2000
#	"--with-libc --enable-languages=c --with-libs=$xcprefix/lib ".
#	"--disable-new-gxx-abi --disable-libstdcxx-v3";

# For the 64bit cross tools
$xc64binutilsconfig="--disable-nls";
$xc64gccconfig="--without-libc --enable-languages=c";

# For the cross-built native tools
$nativeglibcconfig="--without-cvs --disable-profile --enable-add-ons";
$nativebinutilsconfig="";
#$nativegccconfig="--with-libc --with-libs=$pseudoroot/$nativeprefix/lib ".
#	"--with-headers=$pseudoroot/$nativeprefix/include";
# shared attempt, not working
#$nativegccconfig="--with-libc";
# Only C cross-builds properly
$nativegccconfig="--disable-shared --disable-nls ".
 	"--without-libc --enable-languages=c --disable-sanity-checks";

### end USER VARIABLES


### MAIN

# make sure some variables are set so we don't cause damage
die "AAAHHHH! Important variables not set, exiting\n" if
	(! $top || ! $xcprefix || ! $tempxc || ! $nativeprefix );

# create log file name
$log = $logdir . "/build-" . strftime("%Y.%m.%d", localtime) . ".log";

## set all output to go to the log file
## make this an option that can be disabled via a flag
#if ( $flag && -e $logdir && -d $dir && -w $dir ) {
#open(STDOUT, ">$log") || die "Can't redirect stdout";
#
#}

# make the output nice
open(STDERR, ">&STDOUT") || die "Can't dup stdout";

# make unbuffered so we can tail -f the log file
select(STDERR); $| = 1;
select(STDOUT); $| = 1;

# print log file header
print "#### palinux-autobuilder start " . localtime() ." ####\n";

# Make sure all directories are setup and writable
&setup($scratch,$tempxc,$xcprefix,$top,$src);

# update our CVS bits and includes
&updatebits;

if (CROSS) {
	## Temporary for glibc build

	# build temporary binutils
	&binutils(
		$target,		# target we're building for
		$local,			# compilers to use
		$tmpbinutilsconfig,	# extra config options
		$tempxc,		# where to install
		$tempxc,		# which tools to use
		""                      # temporary install dir
	);

	# build temporary gcc
	&gcc(
		$target,		# target we're building for
		$local,			# compilers to use
		$tmpgccconfig,		# extra config options
		$tempxc,		# where to install
		$tempxc,		# which tools to use
		""                      # temporary install dir
	);

	# Use the temporary gcc to make the kernel dependencies(like
	#  version.h etc) we need to do this since we just updated the kernel
	# (for now you have to do a 'make oldconfig' before this will work)
	&configkernel();

	## 32bit cross glibc
	# Use temporary binutils/gcc to cross-compile glibc
	&glibc(
		$target,		# target we're building for
		$target,		# compilers to use
		$xcglibcconfig,		# extra config options
		"$xcprefix/$target",	# where to install
		$tempxc,		# which tools to use
		""			# temporary install dir
	);

	## 32bit cross-tools

	# build cross-binutils
	&binutils(
		$target,		# target we're building for
		$local,			# compilers to use
		$xcbinutilsconfig,	# extra config options
		$xcprefix,		# where to install
		$tempxc,		# which tools to use
		""			# temporary install dir
	);

	# copy the linux and asm includes into $xcprefix
	&copyincludes("$xcprefix/$target");

	# build cross-gcc
	&gcc(
		$target,		# target we're building for
		$local,			# compilers to use
		$xcgccconfig,		# extra config options
		$xcprefix,		# where to install
		$xcprefix,		# which tools to use
		""			# temporary install dir
	);
} # end CROSS

## 64bit cross-tools
if (CROSS64) {

	# build 64bit cross-binutils
	&binutils(
		$target64,		# target we're building for
		$local,			# compilers to use
		$xc64binutilsconfig,	# extra config options
		$xcprefix,		# where to install
		$xcprefix,		# which tools to use
		""			# temporary install dir
	);

	# build 64bit cross-gcc
	&gcc(
		$target64,		# target we're building for
		$local,			# compilers to use
		$xc64gccconfig,		# extra config options
		$xcprefix,		# where to install
		$xcprefix,		# which tools to use
		""			# temporary install dir
	);

	## Package cross-tools & cross-glibc
	# 64 milli 
	# diet xc
	# package xc and copy $somewhere
} # end CROSS64


if (NATIVE) {
	## Cross compile native 32bit glibc
	&glibc(
		$target,		# target we're building for
		$target,		# compilers to use
		$nativeglibcconfig,	# extra config options
		$nativeprefix,		# where to install
		$xcprefix,		# which tools to use
		$pseudoroot		# temporary install dir
	);

	## Cross compile native 32bit tools

	# cross-build binutils
	&binutils(
		$target,		# target we're building for
		$target,		# compilers to use
		$nativebinutilsconfig,	# extra config options
		$nativeprefix,		# where to install
		$xcprefix,		# which tools to use
		$pseudoroot		# temporary install dir
	);

	# copy the linux and asm includes into $xcprefix
	&copyincludes("$pseudoroot/glibc/$nativeprefix");

	# cross-build gcc
	&gcc(
		$target,		# target we're building for
		$target,		# compilers to use
		$nativegccconfig,	# extra config options
		$nativeprefix,		# where to install
		$xcprefix,		# which tools to use
		$pseudoroot		# temporary install dir
	);
} # end NATIVE


## Success
print "#### palinux-autobuilder end   " . localtime() ." ####\n";
### end MAIN


### SUBROUTINES

sub setup {
	my($scratch,$tempxc,$xcprefix,$top,$src)=@_;
	my($dir);
	print "### Setting up ###\n";
	if ( ! -e "$src/include" ) {
		print "$src/include does not exist! exiting\n";
		exit 1;
	}
		
	foreach $dir ( $scratch,$tempxc,$xcprefix,$top,$src ) {
		print "Checking $dir ... ";

		if ( ! -e $dir ) {
			mkdir $dir,0777 or die "Can't create $dir";
		} elsif ( ! -d $dir ) {
			print "$dir is not a directory! exiting\n";
			exit 1;
		} elsif ( ! -w $dir ) {
			print "$dir is not writable! exiting\n";
			exit 1;
		}
		print "OK\n";
	}

# Do this on each host
#	foreach $dir ( $scratch,$tempxc,$xcprefix,$top,$src ) {
#		print "Cleaning $dir ... ";
#		#put rm -rf $dir/* here
#		print "OK\n";
#	}
}


sub updatebits {

	# update cvs bits, the first co gets the initial run case
	&run("cd $src; $socks cvs -d $repository co -l .; ".
		"$socks cvs -z3 -d $repository ".
		"update -dP $binutils $gcc $glibc $linux $palo",
		$address,"Updating CVS bits");

	# TODO
	# setup include tarball
	# $src/include/asm,linux are symlinks to $src/$linux/include/asm,linux
}


sub binutils {
	my($target,$host,$config,$prefix,$xcpath,$install)=@_;
	my $bits ="$src/$binutils";

	my($path);

	print "### Building binutils for $target ###\n";
	# cleanup
	&run("cd $scratch/ && rm -rf binutils; mkdir $scratch/binutils",
		$address,"Cleaning binutils build dir");

	# Prepend $xcpath/bin to the path to get the cross-tools 
	# (may not exist first pass)
	$path="$xcpath/bin:\$PATH";

	# configure
	&run("cd $scratch/binutils; $bits/configure --host=$host ".
		"--target=$target --build=$local --prefix=$prefix $config",
		$address,"Configuring binutils");

	# build
	&run("cd $scratch/binutils; make -j2",$address,"Making binutils");

	# install
	if ($install) {
		# clean pseudoroot first
		# install to pseudoroot
		&run("cd $scratch/binutils; make install ".
			"prefix=$install/binutils",$address,
			"Installing binutils in $install/binutils");
	} else {
		# install to normal prefix
		&run("cd $scratch/binutils; make install",$address,
			"Installing binutils in $prefix");
	}
}


sub gcc {
	my($target,$host,$config,$prefix,$xcpath,$install)=@_;
	my $bits ="$src/$gcc";

	my($path);

	print "### Building gcc for $target ###\n";

	# cleanup
	&run("cd $scratch/ && rm -rf gcc; mkdir $scratch/gcc",$address,
		"Cleaning gcc build dir");

	# Prepend $xcpath/bin to the path to get the cross-tools 
	$path="$xcpath/bin:\$PATH";

	&run("cd $scratch/gcc; export PATH=$path; $bits/configure ".
		"--target=$target --host=$host --build=$local ".
		"--prefix=$prefix $config",$address,"Configuring gcc");

	# build
	&run("cd $scratch/gcc; export PATH=$path; make -j2",$address,
		"Making gcc");

	# install
	if ($install) {
		# clean pseudoroot first
		# install to pseudoroot
		&run("cd $scratch/gcc; export PATH=$path; make install ".
			"prefix=$install/gcc",$address,
			"Installing gcc in $install/gcc");
	} else {
		# install to normal prefix
		&run("cd $scratch/gcc; export PATH=$path; make install",
			$address,"Installing gcc in $prefix");
	}
}


sub configkernel {
	my $kernel="$src/$linux";


	print "### Making kernel dependencies ###\n";

	&run("cd $kernel; export PATH=$tempxc/bin:\$PATH; make clean; ".
		"make dep",$address,"Building kernel dependencies");
}


sub glibc {
	my($target,$host,$config,$prefix,$xcpath,$install)=@_;
	my $bits ="$src/$glibc";
	my($path);

	print "### Building glibc ###\n";

	# cleanup
	&run("cd $scratch/ && rm -rf glibc; mkdir $scratch/glibc",$address,
		"Cleaning glibc build dir");

	# Prepend $xcpath/bin to the path to use the new xcompiler(it knows
	#    to use the $target-foo tools)
	$path="$xcpath/bin:\$PATH";

	# configure
	&run("cd $scratch/glibc; export PATH=$path; $bits/configure ".
		"--target=$target --host=$host --build=$local ".
		" --prefix=$prefix $config",
		$address,"Configuring glibc");

	# build (make -j doesn't work)
	&run("cd $scratch/glibc; export PATH=$path; make",$address,
		"Making glibc");

	# install
	if ($install) {
		# clean pseudoroot first
		# install to pseudoroot
		&run("cd $scratch/glibc; export PATH=$path; make install ".
			"install_root=$install/glibc",$address,
			"Installing glibc in $install/glibc");
	} else {
		# install to normal prefix
		&run("cd $scratch/glibc; export PATH=$path; make install",
			$address,"Installing glibc in $prefix");
	}
}


sub copyincludes {
	my($incpath)=@_;
	my $linuxheaders="$src/$linux";

	&run("mkdir -p $incpath/include/",$address,
		 "Making $incpath/include/ directory");

	&run("cp -R $linuxheaders/include/asm-$asmname $incpath/include/asm",
		$address,
		 "Copying asm-parisc headers to $incpath/include/asm");
	&run("cp -R $linuxheaders/include/linux $incpath/include/",
		$address,
		"Copying linux headers to $incpath/include/");
}


sub run {
	my($command,$address,$subject)=@_;
	my($output,$smtp);
	
	# Print log header
	print "## $subject ##\n";

	# Run command
	print "Running \"$command\"\n";
	open(COMMAND,"$command |");
	while (<COMMAND>) {
		print;
		# save the output for mail
		$output .= $_;
	}
	
	close COMMAND;

	# If we fail send error mail
	# This is not working properly right now as it doesn't send STDERR
	# I'll fix later, MJT
	if ($?) {
	
		$smtp = Net::SMTP->new('localhost') or die "Mail is b0rken";

		# from the current user
		# sendmail on pehc wants it this way
		$smtp->mail("$ENV{USER}\@puffin.external.hp.com");
		# to $address
		$smtp->to("$address");

		$smtp->data();
		$smtp->datasend("To: $address\n");
		$smtp->datasend("Subject: PALINUX-AUTOBUILD- Failed while ".
			"$subject\n");
		$smtp->datasend("\n");
		$smtp->datasend("The palinux-autobuild script failed while ".
			"$subject\n");
		$smtp->datasend("Here is the output\n");
		$smtp->datasend("$output\n");
		$smtp->dataend();

		$smtp->quit;

		exit 1;
	}
}


sub package {
	# tar up the toolchain

	# use alien to make it a deb

}

### end SUBROUTINES

#
# TODO list
#
#- cvs login
#- Clean out $prefix before build
#- Call diet/milli and tar up $PREFIX, publish
#- make include tarball, publish
#- Specify email from command line
#- Specify host from command line
#- logging command line disable
#- usage
#- command line flags for different hosts
#- Fix mail output to include STDERR
#- test if cvs modules exist before update
#- build a serial kernel
#- build a sti kernel
#- build a ramdisk lifimage
