#!/usr/bin/perl -w

# This perl script is the core of a generic configuration system as
# described in TIDKs bugzilla Bug #3 The configuration system depends
# on a *.cfg file. This script will generate appropriate configuration
# include files (i.e. *.h files) from the *.cfg file.

# NOTE! If you think you need to edit this script, you are probably
# wrong or I forgot something, so please email me (mmj@ti.com)

# Known bugs/limitations:
# The comment character '#' cannot be embedded within quoted strings

# Notes:

# A target section in the master config file can include another
# section with a line "&SECTION". SECTION can be any section,
# including the special sub-sections which are just like a normal
# section but with the name prefixed by '&'. Only special thing about
# the sub-sections is that they cannot be built and they are not shown
# in the list printed by the -l option.

# TODO:

# Should we delete the configuration file for which there is no
# corresponding lines in the master config file, e.g. if there is no
# DSP_ lines, should we delete the dsp.cfg file?  Or should it be an
# option (-od) Should we give a warning?

# Perhaps we should check for duplicate config lines. This is
# especially useful with the include feature, as this feature increase
# the risk of duplicate lines. As it is now, the lines are just copied
# into the corresponding config/*.cfg file so the compiler will
# complain


###############################################################################
# Customization
###############################################################################

# none!

###############################################################################
# Globals
###############################################################################

# Full list of valid configuration prefixes
@Prefixes = ();

# Target(s) from the global cfg file to build
@Targets = ();

# Default options (overridden by command line arguments)
$verbose = 1;     # verbosity level
$nomake  = 0;     # don't invoke make at end of script
$dryrun  = 0;     # don't make actual config changes.
$listtargets = 0; # List all configs from master config
$printconfig = 0;
$printenv = 0;    # Print out all ENV variables that are set.
$ConfigDir = "config"; # directory to put output config files
$makedb = 0;      # Print out the make database to stdout (gnumake option -p)
$MakeParams = "";
$MakeFile = "";
$MasterConfigFile = "";

$warnings = 0;

$make_name = "gmake";

my %sections;
my $line = 0;
my $current_section = "";
my %expanded;
my %env_var;
my %cfg_file;

####################################################################
# Main 
####################################################################

GetOpts();

# Find the configuration file to use
@ConfigFiles = glob("*.cfg");
$#ConfigFiles < 0 && die "No configuration file found\n";

# Check if MasterConfigFile was already set with the -f<cfg file> option
if ($MasterConfigFile eq "") {
    $#ConfigFiles > 0 && die "Multiple config-files found! (@ConfigFiles)\n";
    $MasterConfigFile = $ConfigFiles[0];
}

# Find name of make file, using the basename of the master config-file
if ($MakeFile eq "") {
    @tmp = $MasterConfigFile =~ /(\w+)/;
    $MakeFile = $tmp[0] . ".mak";
}

ReadMasterConfig($MasterConfigFile);

if ($listtargets) { ListTargets(); }
if ($printconfig) { PrintConfig(); }

foreach $build (@Targets) {

# >>>> Start Adding SB : 17/03/03
# Add an environment variable for master.mak
    $RFname 	= "DELIVERY_BASENAME" ;
    $RFvalue	= $build ;
    if ( defined( $ENV{"$RFname"} ) )
    {
	print STDERR "  Warning : environement vartiable $RFname already set to $ENV{\"$RFname\"} updated to $RFvalue\n" ;
    }
    $ENV{"$RFname"} = $RFvalue ;  # set environment variable
# >>>> Stop Adding SB : 17/03/03

    verb(2,"ExpandMasterConfig for $build\n");
    %expanded = ();
    ExpandMasterConfig($build);

    verb(1, "Building target: $build\n");
    Process($build);
    if ($nomake == 0) {
	@args = ( "$make_name", "$MakeParams", "-f", "$MakeFile" );
	if ($makedb == 1) {
 	    push (@args,"-p");
	}
	verb(1, "Invoking make: @args \n\n");
	system("@args");
    }
}


####################################################################
# Admin procedures
####################################################################

sub GetOpts {
    foreach (@ARGV) {
	if (/^-n/) { $dryrun++; }
	if (/^-c/) { $nomake++; }
	if (/^-p/) { $printconfig++; }
	if (/^-d/) { $makedb++; }
	if (/^-l/) { $listtargets++; }
	if (/^-e/) { $printenv++; }
	if (/^-f(\w+\.?\w*)/) { $MasterConfigFile = $1; }
	if (/^-m(\w+\.?\w*)/) { $MakeFile = $1; }
	if (/^-x([^\s]+)/) { 
	    $MakeParams = $1;
	    $MakeParams =~ tr/,/ /;
	}
	if (/^-o(\w+\.?\w*)/) { $ConfigDir = $1; }
	if (/^-h|--help/) { Usage(); }
	if (/^-v/) { $verbose++; }
	if (/^-q/) { $verbose = -1; }
	if (/^[^\-](\w+)/) {
	    push (@Targets,uc($_));  # push uppercased string
	}
    }
    if ($#Targets < 0) { $Targets[0] = "DEFAULT"; }
}

sub PrintConfig {
    foreach (@Targets) {
	unless (/\&/) { 
            !defined($sections{$_}) && die "Target section $_ not found\n";
            print "Target [$_]:\n";
	    %expanded = ();
	    ExpandSection ("$_",0,1);
	    print "\n\n";
	}
    }
    exit(0);
}

sub ListTargets {
    print "Targets:\n";
    foreach (sort keys %sections) {
	unless (/\&/) { print "  [$_]\n"; }
    }
    exit(0);
}

sub Usage
{
    print <<USAGE_END;
abc.pl v1.10. Automatic Building, Configuration and Dependencies System.
              Copyright TI 2001.

Usage: [perl] abc.pl [OPTION]... [TARGETS]...

OPTIONS:
 -h       Display this help and exit
 -c       Only do configuration step. Do not invoke gnumake.
 -n       Dry run. Print what is going to be done without doing it.
 -l       List all configurations in master config file, then exit
 -f<name> Specify which master configuration file to use.
          Note that there must be no spaces between "-f" and the file name.
	  Default is to use the *.cfg file from the current directory.
 -m<name> Specify which make file use.
          Note that there must be no spaces between "-m" and the file name.
	  Default is to use the makefile <file>.mak file from the current
          directory, where <file> corresponds to the basename of the
          config file.
 -o<name> Specify output directory name (where to put *.cfg files).
          Default is 'config'.
          Note that there must be no spaces between "-o" and the dir name.
 -p       Print out contents of the specified target(s)
 -d       Print make database (corresponds to gnumake -p)
 -e       Print out all ENVironment variables that are set
 -v       Be verbose (repeat several times to be more verbose)
 -q       Be quiet

 TARGETS  Name(s) of the target(s) to build.

Report bugs and suggestions to mmj\@ti.com or skl\@ti.com
USAGE_END
# -t       Touch. Invoke make with additional parameter: -t
    exit(0);
}


####################################################################
## Core procedures
####################################################################

# Expand all section inclusions in each target
sub ExpandMasterConfig
{
    my ($default_section) = @_;
    if (defined($sections{$default_section})){
	#print default section only
	ExpandSection ($default_section);
    } else {
	#error in $default_section
	verb (1,"Section [$default_section] is not defined in $MasterConfigFile \n");
	exit 1;
    }
}

 
sub ExpandSection
{
    my ($section,$level,$show);
    my $j;
    my $var;
    my @values;
    my $indent;
    my $egal_column = 40;

    ($section,$level,$show) = @_;

    verb (2,"Expanding [$section]\n");
    if (!$level){$level = 1};

    $indent = "  "x$level."+- ";
    if ($level > 9){
	die "Including somewhat deep! Did you make a loop?\n";
    }
    @values = split(' ',$sections{"$_[0]"});
    foreach $j (@values){
	if($j =~ /^&/){
	    if ($show){
		print "$indent$j\n";}
	}else{
	    $j =~ /([^=]*)=(.*)/;
	    $var = $1;
	    $value = $2;
	    $value =~ s/\#.*$//;
	    $env_var = '';
	    if ($var =~ /^\$/){
		$var =~ s/^\$//;
		$env_var = '$';
	    }
	    if ( exists $expanded{$var} ) {
		print STDERR "Warning : in [$section], $env_var{$var}$var = $expanded{$var} is overwritten by $env_var$var = $value\n";
	    }
	    $env_var{$var} = $env_var;
	    $expanded{$var} = $value;
	    if ($show){
		$line = $indent.$var;
		$points = "."x(40-length($line)-1);
		print "$line$points = $value\n";
	    }
	}
	if ($j =~ /^&(\w*)/){
	    ExpandSection ($1,++$level,$show);
	    $level--;
	}
    }
}


sub ReadMasterConfig
{
    my ($filename)=(@_);

    verb(1, "Reading master config file: $filename\n");
    [-e $filename] || die "File $filename not found\n";
    
#Gather informations from master.cfg
    open (MASTER_CFG,"$filename") || die "Can't open $filename\n";
    while (<MASTER_CFG>){
	
	while (/[\r\n]$/){s/[\r\n]$//}
	
	#line counter
	$line++;
	
	#ignore line comment
	if (/^\s*\#/) {
	    verb(5, "Skip: comment line \n");
	    next};
	
	#ignore blank lines
	if (/^$/) {
	    verb(5, "Skip: empty line\n");
	    next};
	
	#identify new section
	if (/\[(\w*)\]/){
	    $current_section = uc($1);
	    verb(4, "Reading section: $current_section\n");
	    if (defined($sections{"$current_section"})){
		print STDERR "Warning [$line]: Section $current_section is already defined\n";
		die;
	    } else {
		$sections{"$current_section"}="";
	    }
	    next;
	}
	
	#add section lines
	if ($current_section eq ""){
	    die "Error [$line]: $filename must starts by a [section] definition\n";
	}
	
	s/ //g;
	verb(5, "Read: $_\n");
	$sections{"$current_section"} .= " $_";
	if (/^\$?([^_]+)_\w+\s*=\s*.*/){
	    $Prefixes{$1}=1;
	}
    }
    
    close (MASTER_CFG);
    verb(4, "Successfully read configuration sections\n");
    verb(3, "\n");
}


sub ReadConfigFile
{
    my ($cfg)=(@_);
    my (@list);
    my ($prefix);

    %cfg_file = ();

    $filename = $cfg;
    $filename =~ s@$cfg@$ConfigDir/\L$cfg\E.cfg@;

    # If the file  does not exist, just return empty list
    if (-e $filename) {
	verb(2, "Reading current config file: $filename\n");
    }
    else {
	verb(2, "Config file $filename non-existing\n");
	return;
    }

    $protect = "";
    open (FH, "<$filename") || die "Can't open $filename\n";
    while (<FH>)
    {
	chomp;
	if (m/\#ifndef\s*(\w+)/){
	    $protect = $1;
	}
	# here we add the $cfg prefix to each line
	if (m/\#define\s*([^ ]+)\s*(.*)/) {
	    $var = $1;
	    $val = $2;
	    if ($var =~ /$protect/i){
		verb(6,"Ignore $protect define\n");
		next;
	    }
	    verb(5, "Read: $var=$val\n");
	    $cfg_file{"${cfg}_$var"}=$val;
	}
    }
    close (FH) || die "Can't close $filename\n";
}


sub Process
{
    if (-e $ConfigDir) {
        -d $ConfigDir || die "'$ConfigDir' is a file, should be a directory!\n";
    }
    else {
        verb(4, "Config-dir $ConfigDir not found - creating it...\n");
        @args = ("mkdir", "$ConfigDir");
        system("@args");
    }

    foreach $cfg (keys (%Prefixes)){
	$update = 0;
	ReadConfigFile($cfg);
	verb (2,"File read\n");
	foreach $i (keys(%expanded)){
	    if ($i =~ /^${cfg}_/){
		if (defined($cfg_file{$i})) {
		    if ($cfg_file{$i} ne $expanded{$i}){
			$update = 1;
		    }
		    delete $cfg_file{$i};
		    
		} else {
		    $update = 1;
		}
	    }
	}
	if (keys(%cfg_file) > 0){
	    verb (2,"Some variables are deleted.\n");
	    $update = 1;
	}
	#update ENV vars and cfg files
	if ($update && !$dryrun) {
	    open (FH, ">$filename") || die "Can't open $filename\n";
	    print FH "#ifndef __$cfg\_CFG__\n";
	    print FH "#define __$cfg\_CFG__\n";
	}
	foreach $i (sort (keys(%expanded))){
	    if ($i =~ /^${cfg}_/){
		$var = $i;
		$var =~ s/^${cfg}_//;
		if ($update && !$dryrun){
		    print FH "#define $var $expanded{$i}\n";
		}
		if ($env_var{$i}){
		    if (exists $ENV{$var}){
			verb(1,"Warning environnement varaible is already set to $ENV{$i} in the system and is overwritten by $expanded{$i}\n");}
		    $ENV{$var}=$expanded{$i};
		    verb (2,"Setting $var environnement variable to $ENV{$var}\n");
		}
	    }
	}
	if ($update && !$dryrun) {
	    print FH "#endif /* __$cfg\_CFG__ \*/ \n";
	    close (FH) || die "Can't close $filename\n";
	}   
	if ($update){
	    verb(2, "Updating $filename.\n");
	} else {
	    verb (2,"$cfg file leaved untouched.\n");
	}
    }
}




# Print trace message (without indentation if $level < 0)
sub verb
{
    my ($level, $str) = @_;
    my ($indent) = 0;
    if ($level < 0) {
	$level = -$level;
    }
    else {
	$indent = ($level || 1) - 1;
    }
    if ($verbose >= $level) {
	print "  " x $indent . $str;
    }
}

sub warning
{
    my ($str) = @_;
    print STDERR "WARNING: $str";
    $warnings++;
}

sub error
{
    my ($str) = @_;
    print STDERR "ERROR: $str";
    exit(1);
}
