#!/usr/local/bin/perl5
########################
# configure: creates Makefiles that are configured for user's platform(s)
# Copyright (C) 1998-2001  Carnegie Mellon University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# Copyright (c) 1998 Carnegie Mellon University.
# All rights reserved.
#
# This software is made available for academic and research purposes
# only.  No commercial license is hereby granted.  Copying and other
# reproduction is authorized only for research, education, and other
# non-commercial purposes.  No warranties, either expressed or implied,
# are made regarding the operation, use, or results of the software.
#
########################
#
# File:         configure
#
# Usage:        configure [-h host-platform] [-t target-platform] \
#                         [-c config file]
#
#
# Description:  create Makefiles that are configured for the user's
#                choice of host and target platforms
#
########################
#
# Entry Conditions:
#               the files configHints.txt, MakefileTarget.dist and
#                MakefileHost.dist are present
#
########################

########################
#
# The logic of this file is broken up into three major phases.
#  First, we read in and parse the configuration file.
#  Second, we figure out which platforms we ar configuring for.
#  Finally, we create the Makefiles based on the above information.
#
# The different phases have different concerns, and therefore different
#  variables.  Each phase is seperated from the others by comment blocks
#  followed by declarations of the variables that are important to that
#  section.  Note that some of the variables from earlier sections carry
#  over into latter sections.
#
########################

use strict;

# check on command line arguments
use Getopt::Std;
use vars qw($opt_t $opt_h $opt_c);
unless(getopts('t:h:c:') && !@ARGV) {
    my $name;
    ($name = $0) =~ s|.*/||;
    $!=1;
    die "usage: $name [-h host-platform] [-t target-platform] ".
	"[-c config file]\n";
}


#############################
# Parse the configuration file
#############################
my %config_setting;    # a hash of hashes holding the info from the
                       #  config file.  Given the input:
                       #
                       # <Begin host:solaris>
                       #   host-CC     [CC = g++]
                       #   host-CFLAGS [CFLAGS = -DSUN -ldl -lnsl]
                       # <End host:solaris>
                       #
                       # <Begin target:vxworks>
                       #   host-TARGET_DEF [TARGET_DEF = B_VXWORKS]
                       #   target-CC       [CC = cc68k]
                       #
                       # The form of the hash is:
                       #
                       # %config_setting = (
                       #    'host:solaris' => {
                       #        'host-CC' => 'CC = g++',
                       #        'host-CFLAGS' => 'CFLAGS = -DSUN -ldl -lnsl',
                       #                      },
                       #    'target:vxworks' => {
                       #        'host-TARGET_DEF' => 'TARGET_DEF = B_VXWORKS',
                       #        'target-CC' => 'CC = cc68k',
                       #                        }
                       #                   );

my $current_platform;  # keeps track of the platform declared in a <Begin>
                       #  tag while we are processing the config file.
                       #  Will be empty when we are not inside a
                       #  platform block

my $config;            # a scalar to hold the entire contents of the
                       #  config file

# figure out what config file to use, and read it in
$opt_c = 'configHints.txt' unless $opt_c;
open(CONFIG, $opt_c) || die "Can't open config file $opt_c: $!";
do {
    local $/ = undef;
    $config = <CONFIG>;
};
close CONFIG;

# parse the config file in a loop
#  each pass through this loop should pull a chunk off of the beginning
#  of $config, and possibly put a corresponding entry into %config_setting.
#  If we fall all the way through, the last case will complain that we didn't
#  recognize something
 CONFIG:
    while ($config) {
	# skip whitespace
	$config =~ s/^\s+//s && next CONFIG;
	# skip comments
	$config =~ s/^#.*\n// && next CONFIG;
	
	# recognize beginning of a platform block
	$config =~ s/^<\s*begin\s+((host|target):(\w+))\s*>//si &&
	    do {
		$current_platform = $1;
		next CONFIG;
	    };
	    
	# recognize ending of a platform block
	$config =~ s/^<\s*end\s+(\S+)\s*>//si &&
	    do {
		unless ($1 == $current_platform) {
		    warn "warning: platform block was opened as " .
			"$current_platform, but closed as $1\n";
		}
		
		$current_platform = '';
		next CONFIG;
	    };

	# recognize a variable setting
	#  NOTE - we are using the /x modifier to try to make this regex a
	#   little more readable
	$config =~ s/^(
	            (\S+)      # non-whitespace
                    \s+        # whitespace
                    \[         # opening bracket
                      ([^\]]*) # some number of non-closing bracket characters
                    \]         # closing bracket
			)//sx  
	    && do {
		unless ($current_platform) {
		    warn "warning: Found text that looks like a variable ".
			"setting outside of\n\ta platform block\n".
			    "the offending string was:\n$1\n";
		    next CONFIG;
		}
		
		$config_setting{$current_platform}->{$2} = $3;
		next CONFIG;
	    };

	# fall through
	#  if we get here, none of the above regexen matched, so
	#  we have no idea what they may have meant
	$config =~ s/(.+)//s;
	warn "warning: Found unrecognized text; " .
	    "The offending string was:\n$1\n";
	next CONFIG;
	
    }

#############################
# Determine host and target platforms
#############################

# hashes to map the strings returned from uname(1) and arch(1) to our
#  mnemonic platform names
my %uname2platform = (
		      'SunOS' => 'solaris',
		      'Linux' => 'linux',
		      'OSF1'  => 'osf1',
		      );
my %arch2platform = (
		     'sun4' => 'solaris',
		     'i686' => 'linux',
		     );

# determine the host platform
#  NOTE - we are setting up this block so that we can use next to jump
#  into the continue block that checks to make sure we have a valid platform
HOST : 
{
    # command line setting has priority
    $opt_h && next HOST;

    # NOTE - substr is used to lop off the newline from the command
    #  we can't use chomp() because that would modify the read-only string

    $opt_h = $uname2platform{substr `uname`, 0, -1};
    $opt_h && next HOST;

    $opt_h = $arch2platform{substr `arch`, 0, -1};
    $opt_h && next HOST;

#    $!=1;
#    die "Can't determine host platform.  Specify with -h\n";
     exec './configure -h default';
} continue {
    unless ($config_setting{"host:$opt_h"}) {
	$!=1;
	die "host platform determined to be $opt_h,\n".
	    "but no configuration options are available for that platform\n";
    }
}

# determine the target platform
#  NOTE - this is structured the same as the HOST block above
TARGET :
{
    # command line setting has priority
    $opt_t && next TARGET;

    # if target platform not specified, default it to the host platform
    $opt_t = $opt_h;
} continue {
    unless ($config_setting{"target:$opt_t"}) {
	$!=1;
	die "target platform determined to be $opt_t,\n".
	    "but no configuration options are available for that platform\n";
    }
}

# show what platforms we are configuring for, mainly as a sanity check for
#  the benefit of the user
print "Configuring for host platform: $opt_h\n";
print "              target platform: $opt_t\n";

#############################
# Create the Makefiles
#############################

# create a hash containing only the settings from the platforms we are
#  interested in.  NOTE - the syntax %{ hash-ref } will take the hash-ref
#  and flatten into it a list which can then be assigned to the new hash
my %active_config_setting = (
			     %{$config_setting{"host:$opt_h"}},
			     %{$config_setting{"target:$opt_t"}}
			     );
# build a regex that will match any of the keys of keys from the above hash
#  so that we can replace the keywords when we find them in the files
my $regex =
    '@(' .
    join('|', keys %active_config_setting) .
    ')@';

# the files that we actually want to create
my @makefiles = qw(MakefileHost MakefileTarget);
foreach my $file (@makefiles) {
    open(INPUT , "<$file.dist") || die "Can't open file $file.dist";
    open(OUTPUT, ">$file"     ) || die "Can't open file $file";

    while (<INPUT>) {
	s/$regex/$active_config_setting{$1}/g;
	print OUTPUT;
    }
}
