Subversion Repositories DevTools

Rev

Blame | Last modification | View Log | RSS feed

#! /usr/bin/perl
########################################################################
# Copyright (C) 1998-2004 ERG Limited, All rights reserved
#
# Module name   : jats_builder.pl
# Module type   : Makefile system
# Compiler(s)   : n/a
# Environment(s):
#
# Description   : A script to build an entire systems
#                 The script will:
#                   Build a tree of related modules in the correct order
#
#                   Rewrite build.pl files with correct version information
#
#                   Maintain the pkg_archive cache
#
#                   Display interanal and external dependencies
#
#
# Version   Who      Date        Description
# 1.0.0     DDP      8-Jun-04   Created
#
#......................................................................#

use strict;
use JatsError;

use Data::Dumper;                           # Debug only
use Pod::Usage;                             # required for help support
use Getopt::Long;
use Cwd;
use File::Path;

my $VERSION = "2.0.0";                      # Update this

#
#   Options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_help;
my $opt_manual;
my $opt_clean;
my $opt_expert = 1;
my $opt_uselog = 1;
my $opt_resetlog;
my $opt_skip = 2;
my $opt_quick = 0;
my $opt_build = 1;
my $opt_build_all;
my $opt_update_cache;
my @opt_only;
my $opt_only_prereq = 2;
my $opt_dump;
my $opt_dump_all;
my $opt_show;
my $opt_show_rule;
my $opt_show_pkg;
my $opt_label_all;
my $opt_audit;
my $opt_audit_entry;

my $opt_buildfiles;
my $opt_buildfiles_rewrite;
my $opt_buildfiles_checkout;
my $opt_buildfiles_label;
my $opt_buildfiles_labelall;
my $opt_buildfiles_labelrule;
my $opt_config = "build.cfg";
my $opt_script;
my $opt_gen_pathlist;
my $opt_create_dpkg;

#
#   Globals
#
my $GBE_PERL = $ENV{'GBE_PERL'};                # Essential ENV variables
my $GBE_CORE = $ENV{'GBE_CORE'};

my $root_dir;                                   # Root of the project
my $cwd;                                        # The current directory
my @modules;                                    # List of modules to process
my %dir_to_name;                                # path -> component name
my %internal;                                   # Internal components -> path

my $build_label;
my $label;
my $logfile;
my $logfile_dir;
my $logfile_name;
my $logfile_base;
my $audit_logfile;
my $datafile;
my $build_this;
my @audit_args;
my $logfile_inuse = 0;


#
#   Fixed config strings
#
my $BUILDFILE = "build.pl";                     # Basic build file
my $BUILDUSE = "build.use.pl";                  # Massaged build file
my $AUDIT_DO = "build.audit.do";                # Audit derived object
my $AUDIT_LOG = "build.audit.log";              # Post audit log
my $LOGFILE_EXT    = "builder.log";             # Builder logfile extension
my $AUDITFILE_EXT  = "audit.log";               # Post audit logfile

################################################################################
#   Data used in the file rewrite process
#
#
my $project;                    # The project name (short form)

#
#   A Hash of external components and version numbers
#
my %external_component =  ();

#
#   A hash of components that have MULTIPLE version numbers
#
my %kludge_component = ();

#
#   A hash of project extensions that should not be massaged
#
my %project_keep = (
    'mas'   => 'mas',
    'cr'    => 'cr',
    'cots'  => 'cots',
    );

#-------------------------------------------------------------------------------
# Function        : Mainline Entry Point
#
# Description     :
#
# Inputs          :
#
@audit_args = @ARGV;
my $result = GetOptions (
                "help+"         => \$opt_help,              # flag, multiple use allowed
                "manual"        => \$opt_manual,            # flag, multiple use allowed
                "verbose+"      => \$opt_verbose,           # flag, multiple use allowed
                "clean"         => \$opt_clean,             # flag
                "clear"         => \$opt_clean,             # flag
                "log!"          => \$opt_uselog,            # flag with [no] option
                "resetlog"      => \$opt_resetlog,          # flag
                "expert!"       => \$opt_expert,            # flag with [no] option
                "skip!"         => \$opt_skip,              # flag with [no] option
                "quick!"        => \$opt_quick,             # flag with [no] option
                "build!"        => \$opt_build,             # flag with [no] option
                "all!"          => \$opt_build_all,         # flag with [no] option
                "updatecache+"  => \$opt_update_cache,      # flag, multiple use allowed
                "only=s"        => \@opt_only,              # String
                "prereq!"       => \$opt_only_prereq,       # flag with [no] option
                "dump+"          => \$opt_dump,             # flag
                "dumpall"       => \$opt_dump_all,          # flag
                "show"          => \$opt_show,              # flag
                "showrule"      => \$opt_show_rule,         # flag
                "showpkg"       => \$opt_show_pkg,          # flag
                "labelall"      => \$opt_label_all,         # flag
                "config:s"      => \$opt_config,            # String
                "audit"         => \$opt_audit,             # flag
                "AUDITMODE:s"   => \$opt_audit_entry,       # String
                "script:s"      => \$opt_script,            # String

                "buildfiles"            => \$opt_buildfiles,            # flag
                "buildfiles_rewrite!"   => \$opt_buildfiles_rewrite,    # flag
                "buildfiles_checkout!"  => \$opt_buildfiles_checkout,   # flag
                "buildfiles_label!"     => \$opt_buildfiles_label,      # flag
                "buildfiles_labelall!"  => \$opt_buildfiles_labelall,   # flag
                "buildfiles_labelrule!" => \$opt_buildfiles_labelrule,  # flag

                "pathlist"              => \$opt_gen_pathlist,
                "create_dpkg"           => \$opt_create_dpkg,
                );

                #
                #   UPDATE THE DOCUMENTATION AT THE END OF THIS FILE !!!
                #

#
#   Process help and manual options
#
pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
pod2usage(-verbose => 1)  if ($opt_help == 2 );
pod2usage(-verbose => 2)  if ($opt_manual || $opt_help > 2);

ErrorConfig( 'name'    =>'JBUILDER',
             'verbose' => $opt_verbose );

#
#   Validate user options
#   Not expecting any user arguments, other than options, so spit if any are found
#
Error ("Unexpected command line arguments present" )
    if ( $#ARGV >= 0 );

#
#   Determine the current working directory
#
$cwd = getcwd();
Verbose ("Current Working Directory: " . $cwd );

#
#   Locate the base config file
#   This will be located with in the root directory and this will be where
#   the build log file will be created
#
my $test_dir = $cwd;
LOOP: {
    do
    {
        Verbose2 ("Testing: $test_dir");
        my $win_test_file = "$test_dir/$opt_config" ;
        if ( -f $win_test_file )
        {
            Verbose ("Found build config file: $win_test_file");
            $root_dir = $test_dir;
            last;
        }
        #
        #   Remove one directory
        #   Terminate the loop when no more can be removed
        #
    } while ( $test_dir =~ s~[/][^/]*$~~ );
}

$datafile = "$root_dir/$opt_config";
Error ("Cannot locate config file: $opt_config")
    unless ( -f $datafile );

#
#   Intercept AUDITMODE operation
#       ClearCase audit requires a program shell to be audited. It can't simply
#       be turned on and off. This is achived by using a special invocation of
#       this program to provide the auditable suite of functions
#
audit_operation()
    if ( $opt_audit_entry );

if ( $opt_audit )
{
    Error ("Cannot audit a non-dynamic clearcase view")
        unless ( is_dynamic_view() );
}


#
#   Read in the build config file
#       There are a few directives that are parsed as we go
#       The root directory may be redefined
#
read_config_file( $datafile );
Error ("No LABEL found in config file")
    unless ( $label );
Error ("No components to build in the config file")
    unless ( $#modules >= 0  );

#
#   Generate the name of the logfile
#
( $logfile_base = $opt_config ) =~ s~\.\w*$~~;
$logfile_dir = $root_dir;

#
#   Determine default operation (no local build.pl file)
#   If we are in the root directory, then, by default, build ALL modules
#                               otherwise, by default, build the current one
#
unless ( $opt_build_all || ( $#opt_only >= 0 ) )
{
    #
    #   If a local build file is found, then just build it
    #   Set default operation to skip and nopreq
    #
    if ( -f $BUILDFILE )
    {
        $build_this = 1;
    }
    else
    {
        #
        #   No user specified modules to build
        #   If we are not in the root directory, then die
        #
        Error ("Cannot determine what to do from this directory\n")
            unless ( -f $opt_config );
    }
}

#
#   Determine the "here" component
#   Ensure that the component is in the module list, even if its not in the config file
#
if ( $build_this )
{
    $build_this = substr ( $cwd, 1 + length ( $root_dir ) );
    Verbose2 ("Here: $build_this" );
    my $file = "$root_dir/$build_this/$BUILDFILE" ;
    Error ("Cannot determine component. Not Found: $file" )
        unless ( -f $file );
    push @modules, $build_this;

    #
    #   Building the module in the current directory
    #   Set a few options - if they are still defaulted
    #       Force noskip to ensure the component is built
    #       Force nopreq to prevent prerequisites being built
    #       Generate a logfile in the current directory
    #
#    $opt_skip = 0
#        if ( $opt_skip == 2 );
#    $opt_only_prereq = 0
#        if ( $opt_only_prereq == 2 );

    $logfile_dir = ".";
    $opt_resetlog = 1;
}

################################################################################
#   Restart the log file
#
$audit_logfile = "$logfile_dir/$logfile_base.$AUDITFILE_EXT";
$logfile = "$logfile_dir/$logfile_base.$LOGFILE_EXT";
if ( $opt_resetlog || $opt_clean )
{
    print "Delete logfile: $logfile\n";
    System ("rm -f $logfile" );

    print "Delete audit log: $logfile\n";
    System ("rm -f $audit_logfile" );
}
    
################################################################################
#
#   Label all files in the view
#
if ( $opt_label_all )
{
    label_all_files();
    exit 0;
}

################################################################################
#
#   Update the local dpkg_archive cache - if required
#   This does not require processing of the external packages
#
if ( $opt_update_cache )
{
    my @cache_list;
    my $mode;

    $mode = "-refresh" if ( $opt_update_cache > 1 );

    for my $file (sort keys %external_component)
    {
        if ( $kludge_component{$file} )
        {
            for my $fl (sort keys %{$kludge_component{$file}} )
            {
                push @cache_list, "$file/$kludge_component{$file}{$fl}.$fl";
            }
        }
        else
        {
            push @cache_list, "$file/$external_component{$file}";
        }
    }

    JatsCmd( "dpkg_cache $mode @cache_list" );
}

################################################################################
#
#   Examine all named modules and determine the build order
#   Limit the list of built modules if required
#
@modules = determine_build_order( @modules );

################################################################################
#   Generate a directory list
#
if ( $opt_gen_pathlist )
{
    generate_pathlist();
    exit 0;
}

################################################################################
#
#   External Script Interface
#
if ( $opt_script )
{
    Verbose ("Script:perl $ENV{'GBE_TOOLS'}/$opt_script @modules");
    System ("perl $ENV{'GBE_TOOLS'}/$opt_script @modules");
    exit $result;
}

################################################################################
#   Display the external packages as LinkPkgArchive statements
#
if ( $opt_show_pkg)
{
    my  $pkg;
    my  $ver;

    for my $file (sort keys %external_component)
    {
        if ( $kludge_component{$file} )
        {
            for my $fl (sort keys %{$kludge_component{$file}} )
            {
                $pkg = $file;
                $ver = "$kludge_component{$file}{$fl}.$fl";
                print "LinkPkgArchive( '$pkg', '$ver' );\n";
            }
        }
        else
        {
            $pkg = $file;
            $ver = $external_component{$file};
            print "LinkPkgArchive( '$pkg', '$ver' );\n";
        }
    }
    exit 0;
}

################################################################################
#   Display the external packages and the build order
#
if ( $opt_show || $opt_show_rule || $opt_show_pkg)
{
    print "\nExternal packages (Alphabetic Order)\n";
    for my $file (sort keys %external_component)
    {
        if ( $kludge_component{$file} )
        {
            for my $fl (sort keys %{$kludge_component{$file}} )
            {
                printf "    %-20s %s.%s\n", $file,$kludge_component{$file}{$fl},$fl;
            }
        }
        else
        {
            printf "    %-20s %s\n", $file,$external_component{$file};
        }
    }

    print "\nInternal Components (Build Order)\n";
    for my $module ( @modules )
    {
        if ( $opt_show_rule )
        {
            printf "    %-50s %-25s %s\n", $module, $dir_to_name{$module}, cleacase_rule($module);
        }
        else
        {
            printf "    %-50s %s\n", $module, $dir_to_name{$module};
        }
    }
    print "\n";

#    print "\nBuildFiles\n";
#    for my $module ( @modules )
#    {
#        printf "    %s/build.pl\n", $module ;
#    }
#    print "\n";

}

################################################################################
#
#   Setup STDOUT and STDERR to a logfile
#   This will force all output to be directed to the logfile, by default
#
logfile_control(1);

################################################################################
#
#   Start a clean build
#   Delete the local package archive and then clean out all packages
#
if ( $opt_clean )
{
    for my $module ( @modules )
    {
        my  $dir = "$root_dir/$module";
        print OLDOUT "Cleaning package: $module";
        if ( -d $dir )
        {
            chdir ( $dir );

            System ("rm -f $BUILDUSE $AUDIT_DO $AUDIT_LOG *$LOGFILE_EXT *$AUDITFILE_EXT" );
            JatsCmd ( "clean" );
            JatsCmd ( "clobber" );
            rm_dir ( "pkg" ) if ( -d pkg );
        }
        print OLDOUT "\n";
    }

    #
    #   Remove local archive LAST to give the user a chance to control-C out
    #   of this massive delete - iff done in Error :(
    #
    print OLDOUT "Removing local_dpkg_archive";
    rm_dir ( "$root_dir/local_dpkg_archive" );
    print OLDOUT "\n";
}

################################################################################
#
#   Ensure that the local dpkg_archive directory exists
#
System ("mkdir -p $root_dir/local_dpkg_archive" );

################################################################################
#   Process build files only
#
#   Ensure that the $BUILDFILE files are up to date
#   If required the files will be checkedout and updated
#   A label will be applied to all the build files ( + makefile.pl )
#

sub ed4w
{
    my ($name) = @_;
    system ( "c:/Progra~1/SAIG/ED4W/ED32.exe $name" );
}

if ( $opt_buildfiles )
{
    my $same = 0;
    my $not_same = 0;
    my $ecount = 0;

    for my $module ( @modules )
    {
        my  $dir = "$root_dir/$module";
        my  $efound = 0;
        printf OLDOUT "Processing %-50s", $module;
        if ( -d $dir )
        {
            chdir ( $dir );

#            if ( ! is_checked_out ("$BUILDFILE") )
#            {
#                print OLDOUT " Checkout";
#                System ("cleartool co -nc $BUILDFILE" );
#                print OLDOUT "\n";
#            }
#            ed4w( "$dir/build.pl" );
#            next;

            $result = rewrite_build_file ( $module );
            Verbose ("rewrite build.pl result): $result");
            if ( $result )
            {
                print OLDOUT "DIFFERENT";
                $not_same++;

                if ( $opt_buildfiles_checkout )
                {
                    if ( ! is_checked_out ("$BUILDFILE") )
                    {
                        print OLDOUT " Checkout";
                        System ("cleartool co -nc $BUILDFILE" );
                    }
                }

                if ( $opt_buildfiles_rewrite || $opt_buildfiles_checkout )
                {
                    System ("rm -f $BUILDFILE" );
                    if ( System ("mv -f $BUILDUSE $BUILDFILE" ) )
                    {
                        print OLDOUT " ERROR:ReWrite";
                    }
                    else
                    {
                        print OLDOUT " ReWrite";
                    }
                }
                

            }
            elsif ( $result == 0 )
            {
                print OLDOUT "Same";
                $same++;
            }
            else
            {
                print OLDOUT "Error";
                $ecount++;
                $efound = 1;
            }
        }
        else
        {
            print OLDOUT "Not Found";
            $ecount++;
            $efound = 1;
        }

        if ( $opt_buildfiles_label || $opt_buildfiles_labelall || $opt_buildfiles_labelrule )
        {
            if ( ! $efound )
            {
                my @list;
                my $label = $build_label;

                if ( $opt_buildfiles_labelrule )
                {
                    $label = cleacase_rule( $module );
                }

                #
                #   Determine files to label
                #
                unless ( $opt_buildfiles_labelall )
                {
                   @list = qw ( build.pl );
                }
                else
                {
                    @list = qw ( build.pl makefile.pl src/makefile.pl src . ..);
                    push @list, glob ("warnings.*" );
                }

                for my $file ( @list )
                {
                    if ( ! has_label( $file, $label) )
                    {
                        print OLDOUT " Label:$file";
                        my $mode = "";
                        my $mode = "-replace" unless($file eq ".." );
                        Verbose ("cleartool mklabel -nc $mode $label $file" );
                        $result = System ("cleartool mklabel -nc $mode $label $file" );
                        print OLDOUT "-ERR"
                            if ( $result );

                    }
                }
            }
        }
        print OLDOUT "\n";
    }
    printf OLDOUT "Diffs: %d, Same: %d, Errors: %d\n", $not_same, $same, $ecount;
    exit 0;
}

################################################################################
#
#   Compile each module
#
$result = 0;
if ( $opt_build )
{
    for my $module ( @modules )
    {
        if ( build_module ( $module ) > 0 )
        {
            $result = 1;
            last;
        }
    }

    #
    #   Install packages into dpkg_archive, but only if the packages built OK
    #   This is an interactive process, so disable the logfile
    #
    if ( ! $result and $opt_create_dpkg )
    {
        logfile_control ( 0 );
        for my $module ( @modules )
        {
            if ( install_package ( $module ) > 0 )
            {
                $result = 1;
                last;
            }
        }
    }
}

logfile_control ( 0 );
exit $result;


#-------------------------------------------------------------------------------
# Function        : logfile_control
#
# Description     : Enable and disable the use of a logfile
#
# Inputs          : $1  - True: Enable logfile
#                         False: Disable logfile
#
# Returns         :
#
sub logfile_control
{
    ( my $enable ) = @_;
    if ( $enable )
    {
        if ( ! $logfile_inuse )
        {
            open OLDOUT, ">&STDOUT"     or Error ("Can't dup STDOUT: $!");
            open OLDERR, ">&STDERR"     or Error ("Can't dup STDERR: $!");

            if ( $opt_uselog )
            {
                open STDOUT, '>>', $logfile or die "Can't redirect STDOUT: $!";
                open STDERR, ">&STDOUT"     or die "Can't dup STDOUT: $!";


                select OLDERR; $| = 1;      # make unbuffered
                select OLDOUT; $| = 1;      # make unbuffered

                select STDERR; $| = 1;      # make unbuffered
                select STDOUT; $| = 1;      # make unbuffered
            }
            $logfile_inuse = 1;
        }
    }
    else
    {
        if ( $logfile_inuse )
        {
            #
            # Restore redirection
            #
            close STDOUT;
            close STDERR;

            open STDOUT, ">&OLDOUT"     or die "Can't dup OLDOUT: $!";
            open STDERR, ">&OLDERR"     or die "Can't dup OLDERR: $!";

            select STDERR; $| = 1;      # make unbuffered
            select STDOUT; $| = 1;      # make unbuffered
            
            $logfile_inuse = 0;
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : audit_operation
#
# Description     : Provide a basic set of functions to be used by the
#                   clearaudit operation.
#
#                   This mode should only be invoked by the program itself
#
#
# Inputs          :
#
# Returns         :
#
sub audit_operation
{
    my $result;
    Verbose ( "AUDIT Operation: $datafile, $opt_audit_entry" );

    #
    #   Read in the config file(s)
    #   This will ensure that all the relevent config files are noted by audit
    #
    #   Do not perform any sanity test. This will have been done by the wrapper
    #   invocation.
    #
    read_config_file( $datafile );

    #
    #   Rewrite the build.pl file
    #
    rewrite_build_file( $opt_audit_entry );

    my $mode = "";
    $mode = "--expert" if ( $opt_expert );

    $result = JatsCmd ( "-b $BUILDUSE build $mode" );

    $mode = "all";
    $mode = "debug package_debug" if ( $opt_quick );
    $result = JatsCmd ( "-b $BUILDUSE make $mode" )
        unless ( $result );

    #
    #   Generate a "derived object" so that the audit has something to track
    #   This file is created last to ensure that ALL build activity is captured
    #
    open  AUDO, ">$AUDIT_DO" or die "Cannot create audit derived object\n";
    print AUDO "This file is used to create an audit trail - it can be deleted";
    close AUDO;

    Verbose ( "AUDIT Operation Result: $result" );
    exit $result;
}

#-------------------------------------------------------------------------------
# Function        : SystemLog
#
# Description     : Similar to the System command, except that standard
#                   output and standard Error are appended to a log file
#
#                   Used since I was having problems with calling other programs
#                   and control-C. It could hang the terminal session.
#
# Inputs          :
#
# Returns         :
#
sub System
{
    my( $cmd ) = @_;

    Verbose2 "... $cmd";

    if ( $logfile_inuse )
    {
        open(CMD, "$cmd |")    || Error "can't run command: $!";
        while (<CMD>)
        {
            print $_;
        }
        close(CMD);
    }
    else
    {
        system( "$cmd" );
    }
    
    return $? / 256;
}

#-------------------------------------------------------------------------------
# Function        : JatsCmd
#
# Description     : Issue a command to JATS.PL
#
# Inputs          : Command line
#
# Returns         : Error code
#
sub JatsCmd
{
    Error ("Environment variable GBE_PERL not set") unless ( defined $GBE_PERL );
    Error ("Environment variable GBE_CORE not set") unless ( defined $GBE_CORE );

    System ( "$GBE_PERL $GBE_CORE/TOOLS/jats.pl @_" );
}

#-------------------------------------------------------------------------------
# Function        : read_config_file
#
# Description     : Read in the config file and build up data structures
#                   Format of the configuration file
#
#                     Comments begin with a # and go the end of the line
#                     There are three types of config line
#                       LABEL nn.nn.nn.prj
#                            Specifies the build label
#                            Specifies the 3 letter project name (Mandatory)
#                            This value is used to substitute xxx project names
#
#                       ROOTDIR name
#
#                       INLCUDE filename
#
#                       EXTERNAL package version [ALLOW_MULTI]
#                            Specifies the version of a package to use
#                            The version may be of the form:
#                                nn.nn.nn.aaa
#                            or
#                                nn.nn.nn
#                            A project specifier of .mas and .cr are not modified
#                            A project specifier of .xxx will be changed to the current project
#
#                            The "ALLOW_MULTI" tag handles the special case where
#                            a package may have a project specific version AND a
#                            generic MAS version - that are not the same.
#
#                       LinkPkgArchive ('package', 'version' );  [ALLOW_MULTI]
#                       BuildPkgArchive ('package', 'version' );  [ALLOW_MULTI]
#                            See EXTERNAL
#
#                       MESSAGE text
#                           Display the text
#
#                       EXCLUDED text
#                           Display the text
#
#                       Otherwise the line is taken to be the path to a
#                       $BUILDFILE file that will be part of the build.
#
# Inputs          : Name of the config file
#                   Name of the current file handle - used for recusive calls
#
# Returns         : Will terminate on error
#
sub read_config_file
{
    my ($datafile, $fh) = @_;
    my $root_found;
    Verbose2 ("Read Config File: $datafile");

    #
    #   Detect too many levels of inclusion
    #
    Error ("Too many levels of include file. Possible circular inclusion")
        if ( $fh > 10 );

no strict 'refs';
    $fh++;
    open ( $fh, $datafile ) or Error ("Config file ($datafile) not found" );
use strict 'refs';

    while ( <$fh> )
    {
        #
        #   Clean up lines
        #   Skip comments and blank lines
        #   Remove leading and training white space
        #
        chomp;
        s~^\s*~~;
        s~#.*$~~;
        s~\s*$~~;
        next if ( length( $_) <= 0 );

        Verbose3 ("CFG: " . $_);

        #
        #   LABEL nn.nn.nn.prj
        #       Required project label
        #       Extract the project suffix from the label
        #
        if ( m/LABEL/ )
        {
            my ($key, $ext) = split( ' ', $_, 2);
            Error ("Multiple LABEL statements not allowed") if ( $build_label );
            Error ("Missing label") unless ( $ext );
            Error ("Bad label format") unless ( $ext =~ m~^\d+\.\d+\.\d+.(\w+)$~ );

            $project = $1;
            $project_keep{$project} = $project;

            $label = $ext;
            $build_label="daf_build_$label";

            Verbose ("Label: $label, $build_label, Project: $project");
            next;
        }

        #
        #   ROOTDIR name
        #       Specify the name of the project root directory
        #       This MUST be a parent directory
        #
        if ( m/^ROOTDIR/ )
        {
            my ($key, $dir_name) = split( ' ', $_, 2);
            Error ("Multiple ROOTDIR statements not allowed") if ( $root_found );
            Error ("Missing root directory name") unless ( $dir_name );

            #
            #   Locate the root directory within the current file tree
            #
            Error ("ROOTDIR not in CWD. $dir_name not in $cwd") unless ( $cwd =~ m~/$dir_name~ );

            $root_dir = substr( $cwd, 0, $+[0] );
            $root_found = 1;
            Verbose ("ROOTDIR: $root_dir");
            next;
        }

        #
        #   INCLUDE config_file
        #   Include another configuration file
        #
        #   The included confg file must reside in the same directory as the
        #   current config file
        #
        if ( m/^INCLUDE/ )
        {
            my ($key, $ifile) = split( ' ', $_, 2);
            Error ("No include file specified") unless ( $ifile );

            #
            #   Extract the path from the current config file
            #
            ( my $path = $datafile ) =~ s~/[^/]+$~~;
            my $cfile = $path . '/' . $ifile;

            Error ("Include file not found: $cfile") unless ( -f $cfile );
            Verbose ("INCLUDE: $fh, $cfile");
            read_config_file ( $cfile, $fh );
            next;
        }

        #
        #   MESSAGE Text
        #   Display message
        #
        if ( m/^MESSAGE/ )
        {
            my ($key, $ext) = split( ' ', $_, 2);
            print $ext . "\n";
            next;
        }

        #
        #   EXCLUDE dirname
        #   Allows a config line to be ignored and displayed
        #
        if ( m/^EXCLUDE/ )
        {
            my ($key, $ext) = split( ' ', $_, 2);
            print "EXCLUDED: " . $ext . "\n";
            next;
        }

        #
        #   EXTERNAL
        #   Defines external packages AND the version of such packages
        #
        if ( m/^EXTERNAL/ )
        {
            my ( $key, $comp, $ver, $opt ) = split( ' ', $_, 4);
            Error ("Version not specified for: $comp") unless ( $ver );
            Error ("Bad version format for: $comp ($ver)") unless ( $ver =~ m~^\d+\.\d+\.\d+.\w+$~ || $ver =~ m~^\d+\.\d+\.\d+$~ );
            Error ("Invalid options: $comp($opt)") if ( $opt && ($opt ne "ALLOW_MULTI" ));
            if ( $opt eq "ALLOW_MULTI" )
            {
                #
                #   Some special components are allowed to have mutliple
                #   versions. These are a gross kludge and are handled with great care
                #
                #   Split out the vesrion into two parts
                #
                (my $suff = $ver) =~ s~^\d+\.\d+\.\d+.~~;
                (my $vnum = $ver) =~ s~.\w+$~~;
                Error ("Bad format for ALLOW_MULTI. Suffix($suff) must be present") unless ( length($suff) > 2 );

                $kludge_component{$comp}{$suff} = $vnum;
                $external_component{$comp} = $ver;
                Verbose ("MULTI package: $comp, $vnum, $suff");
                next;
            }

            Error ("Multiple definition of $comp") if defined $external_component{$comp};

            #
            #   Save the data
            #
            $external_component{$comp} = $ver;
            Verbose  ("Package: $comp, $ver");
            next;
        }

        #
        #   Process LinkPkgArchive and BuildPkgArchive statements
        #   These allow simple updating of the config file from Release manager
        #
        if ( m/LinkPkgArchive/ or m/BuildPkgArchive/ )
        {
            my $comp;
            my $ver;

            m/'(.*)'[^']*'(.*)'/;

            my $comp = $1;
            my $ver = $2;

            m/;(.*)/;
            my $opt = $1;
            $opt =~ s~^\s*~~;
            $opt =~ s~\s*$~~;
#print "Got Archive stuff: $_ : $comp, $ver : \"$opt\"\n";

            Error ("Version not specified for: $comp") unless ( $ver );
            Error ("Bad version format for: $comp ($ver)") unless ( $ver =~ m~^\d+\.\d+\.\d+.\w+$~ || $ver =~ m~^\d+\.\d+\.\d+$~ );
            Error ("Invalid options: $comp($opt)") if ( $opt && ($opt ne "ALLOW_MULTI" ));
            if ( $opt eq "ALLOW_MULTI" )
            {
                #
                #   Some special components are allowed to have mutliple
                #   versions. These are a gross kludge and are handled
                #   with great care
                #
                #   Split out the vesrion into two parts
                #
#print "Process MULTI\n";
                (my $suff = $ver) =~ s~^\d+\.\d+\.\d+.~~;
                (my $vnum = $ver) =~ s~.\w+$~~;
                Error "Bad format for ALLOW_MULTI. Suffix($suff) must be present" unless ( length($suff) > 2 );

                $kludge_component{$comp}{$suff} = $vnum;
                $external_component{$comp} = $ver;
                Verbose "MULTI package: $comp, $vnum, $suff";
                next;
            }

            
            Error "Multiple definition of $comp" if defined $external_component{$comp};

            #
            #   Save the data
            #
            $external_component{$comp} = $ver;
            Verbose  "Package: $comp, $ver";
            next;
        }
        

        #
        #   SET var string
        #   Set and export an environment variable
        #   Primarilry used to setup GBE_PLATFORM and GBE_BUILDFILTER
        #
        if ( m/^SET/ )
        {
            my ($key, $var, $val) = split( ' ', $_, 3);
            Error ("SET has missing parameters") unless ( $var );

            $ENV{$var} = $val;
            Verbose ("Set: $var, $val");
            next;
        }

        #
        #   Default
        #   Save the line as the path to a $BUILDFILE file to be included
        #

        push @modules, $_;
    }
    close ($fh);
}


#-------------------------------------------------------------------------------
# Function        : build_module
#
# Description     : Build one module
#
# Inputs          : The path to the module to build
#
sub build_module
{
    my ($module) = @_;
    my  $dir = "$root_dir/$module";
    my  $exit_string;
    my  $result = 0;


    #
    #   A nice header in the log file
    #
    print   "=" x 40 . " " . localtime() . " " . "=" x 40;
    print   "\nModule: $module\n";

    printf OLDOUT "%-50s %-30s", $module, $dir_to_name{$module};
    chdir ($dir);

    #
    #   If the module conatins a pkg directory and that directory is also
    #   in the local dpkg_archive, then skip this module
    #
    #       The following check assumes that the package name is the same
    #       as the modules BuildName.
    #
    #       It does not check the contents simply, that the target exists
    #
    if (    $opt_skip
         && -d $root_dir ."/local_dpkg_archive/" . $dir_to_name{$module}
         && $build_this ne "$module" )
#    if ( $opt_skip && -d "$dir/pkg" )
    {
        my $pkg_name = qx( ls $dir/pkg );
        chomp( $pkg_name );
        Verbose "pkg name: $pkg_name";
        $exit_string = "Skipped - Pkg Found"
            if ( -d "$root_dir/local_dpkg_archive/$pkg_name" );
    }

    unless ( $exit_string )
    {
        rm_dir ("$dir/pkg" );
        rewrite_build_file( $module );

        if ( $opt_audit )
        {
            #
            #   Audit the build
            #   Refer to comments in "audit_operation"
            #
            is_in_vob();
            $result = System ("clearaudit /c perl $0 -AUDITMODE=$module @audit_args" );
            System ("cleartool catcr -element -type fdl -long $AUDIT_DO > $AUDIT_LOG" );
            System ("cat $AUDIT_LOG" );
            System ("cat $AUDIT_LOG >> $audit_logfile" );
            System ("rm -f $AUDIT_DO");
        }
        else
        {
            #
            #   Build the component
            #
            my $mode = "";
            $mode = "--expert" if ( $opt_expert );

            $result = JatsCmd ( "-b $BUILDUSE build $mode" );

            $mode = "all";
            $mode = "debug package_debug" if ( $opt_quick );
            $result = JatsCmd ( "-b $BUILDUSE make $mode" )
                unless ( $result );

        }

        $result = JatsCmd ( "-b $BUILDUSE install" )
            unless ( $result );

        $exit_string = "Package did not build and compile"
            if ( $result );
    }

    #
    #   Double check that the package installed
    #
    unless ( $exit_string )
    {
        if ( -d "$dir/pkg" )
        {
            my $pkg_name = qx( ls $dir/pkg );
            chomp( $pkg_name );

            unless ( -d "$root_dir/local_dpkg_archive/$pkg_name" )
            {
                $exit_string = "Package not transferred";
                $result = 1;
            }

            unless ( $dir_to_name{$module} eq $pkg_name )
            {
                $exit_string = "OK, but package name should be:$dir_to_name{$module} ";
            }
        }
        else
        {
            $exit_string = "Package not found";
            $result = 1;
        }
    }

    $exit_string = "OK" unless ( $exit_string );
    print OLDOUT "$exit_string\n";
    Verbose "build_module: Exit code: $result";

    #
    #   A nice footer in the log file
    #
    printf   ("\nEnd Module: %s Name: %s\n", scalar( gmtime()), $module );

    return $result;
}

#-------------------------------------------------------------------------------
# Function        : install_package
#
# Description     : Install packages into dpkg_archive
#                   This should only be done IFF all modules have been built
#
# Inputs          :
#
# Returns         :
#
sub install_package
{
    my ($module) = @_;
    my  $dir = "$root_dir/$module";
    my  $result = 0;


    printf "Install Package: %-50s %-30s\n", $module, $dir_to_name{$module};
    chdir ($dir);

    $result = JatsCmd ( "-b $BUILDUSE create_dpkg -o" );

    return $result;
}

#-------------------------------------------------------------------------------
# Function        : has_label
#
# Description     : Determine if a file has a clearcase label
#
# Inputs          : Name of the file
#                   Label
#
# Returns         : True if it has the label
#

sub has_label
{
    my( $file, $label ) = @_;

    my $data = qx( cleartool describe -fmt %Nl $file );
    for (split ' ', $data)
    {
        return 1
            if ( /$label/ );
    }
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : is_checked_out
#
# Description     : Determine if a file has been checked out
#
# Inputs          : $file
#
# Returns         : True if checked out
#
sub is_checked_out
{
    my( $file ) = @_;
    my $data = qx( cleartool describe -fmt %f $file );
    Verbose ("is_checked_out:$data\n");
    return 1
        if ( length ($data) > 0 );
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : is_dynamic_view
#
# Description     : Determine if the current clearcase view is dynamic
#
# Inputs          :
#
# Returns         : True if it is a Dynamic View
#
sub is_dynamic_view
{
    my $data = qx( cleartool lsview -cview );
    Verbose ("is_dynamic_view:$data\n");
    return 1
        if ( $data =~ m~^\*~ );
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : is_in_vob
#
# Description     : Determine if the current directory is with a VOB
#                   clearaudit will generate an error if the CWD is not within
#                   a knwon VOB.
#
#                   This can be caused by the case of the name of the current
#                   directory. Windows is not case sensitive, but clearaudit is.
#
# Inputs          :
#
# Returns         :
#
sub is_in_vob
{
    my $data = qx( cleartool ls . );

    if ( $? / 256 )
    {
        my $cwd = getcwd();
        Error ("Directory $cwd is not within a ClearCase VOB.\n",
                "Possible problems:\n",
                "   The directory is under version control\n",
                "   The case of the directory does not match that stored within the VOB\n",
                );
    }
}

#-------------------------------------------------------------------------------
# Function        : clearcase_rule
#
# Description     : Return a string that is the clearcase rule used to conatin
#                   the component.
#
#                   Use the ../.. directory.
#
# Inputs          : Directory
#
# Returns         :
#
#
sub cleacase_rule
{
    (my $base) = @_;

    $base =~ s~/[^/]+$~~;
    $base =~ s~/[^/]+$~~;
    $base = "$root_dir/$base";

    my $data = qx(cleartool ls -d $base);

    $data =~ m~([^\s]+$)~;
    my $rule = $1;

    return $rule;
}


#-------------------------------------------------------------------------------
# Function        : determine_build_order
#
# Description     : This function will determine the build order of all of
#                   the component modules
#
#                   This routine also sets up some important data structures
#                   and supports the creation of a subset of the modules.
#
# Inputs          : None
#
# Returns         :
#
sub determine_build_order
{
    my %src;
    my @build_order;
    my %opt_list;
    my %component;

    for my $module (@modules)
    {
        my $file = "$root_dir/$module/$BUILDFILE";
        Verbose2 "Now Processing File: $file";
        open (FILE, "<$file" ) or Error "Can't open the file: $file\n";
        while (<FILE>)
        {
            if ( m/^BuildName\s/)
            {

                m/'([^\s]*)\s+(.*)'/;
                my $build_name = $1;
                $src{$module}{'name'} = $build_name;

                $component{$build_name} = $module;
                $dir_to_name{$module} = $build_name;

                $internal{$build_name} = $module
                    unless ( defined $external_component{$build_name} );
                
                Verbose2 "   BuildName: $build_name";
            }

            if ( m/^LinkPkgArchive/ || m/^BuildPkgArchive/ )
            {
                chomp;
                m/'(.*)'[^']*'(.*)'/;
    #            print "$_ : $1 :: $2\n";

                #
                #   Only interested in the non-external components
                #   These are components not encountered in the config file
                #   We can't build external components, but should be able to build
                #   all others.
                #
                my $comp = $1;
                my $ver = $2;
                if ( defined $external_component{$comp} )
                {
                    $src{$module}{'external'}{$comp} = $ver;
                    Verbose2 "   External: $comp : $ver";
                    next;
                }

                $src{$module}{'depend'}{$comp} = $ver;
# Stuffs %internal !!               $internal{$comp} = $module;
#                Verbose2 "   Internal: $comp : $module, $ver";
            }
        }
        close FILE;
    }

    #
    #   Build only the component in the current directory
    #   Add the component to the list to build
    #
    if ( $build_this )
    {
        push @opt_only, $dir_to_name{$build_this};
        Verbose2 "Build this: $build_this\n";
    }

    #
    #   If the user has requested that a single package be built then
    #   create a hash of the prerequiste packages to limit the
    #   algorithm to these packages
    #
    if ( $#opt_only >= 0 )
    {
        my @examine;

        for (@opt_only)
        {
            Error ("Specified package not found: $_")
                if ( ! defined $component{$_} );

            #
            #   Add user specified components to the list
            #
            Verbose2 "Specified user package: $_\n";
            $opt_list{$_} = 1;
            push @examine, keys (%{$src{$component{$_}}{'depend'}} )
                if ( $opt_only_prereq );
        }

        while ( $#examine >= 0)
        {
            my $file = pop @examine;
            Verbose2 "NEED: $file   ; $component{$file}\n";
            unless ( $opt_list{$file}  )
            {
                $opt_list{$file} = 1;
                push @examine, keys %{$src{$component{$file}}{'depend'}};
            }
        }
    }

    #
    #   Dump all the modules dependency information
    #
    if ( $opt_dump_all || $opt_dump )
    {
        $opt_build = 0;

        sub pscan
        {
            my ( $ptr, $ptrbase, $comp ) = @_;
            for ( sort keys %{$src{$internal{$comp}}{'depend'}} )
            {
                $ptr->{$_}++
                    unless( $ptrbase->{$_} );
                pscan ($ptr, $ptrbase, $_ );
            }
        }
        
        for my $file (sort keys %src)
        {
            my %base_dep;
            for ( sort keys %{$src{$file}{'depend'}} )
            {
                $base_dep{$_}++;
            }

            #
            #   Determine complete list of all dependants other than those in the
            #   top level.
            #
            my %full_dep = ();
            for ( keys %base_dep )
            {
                pscan (\%full_dep, \%base_dep , $_ );
            }

            #
            #   Print it all out
            #
            print "Component: $src{$file}{'name'} in ${file} requires:\n";
            for ( sort keys %base_dep )
            {
                print "    $_\n";
            }

            if ( $opt_dump > 1 )
            {
                for ( sort keys %full_dep )
                {
                    print "            + $_\n";
                }
            }
            

            if ( $opt_dump_all )
            {
                for ( sort keys %{$src{$file}{'external'}} )
                {
                    print "    $_/$src{$file}{'external'}{$_}\n";
                }
            }
        }
#        print Dumper( \%src, \%internal );
    }

    #
    #   Determine the order in which modules must be built
    #   This is done by:
    #       Determine ALL modules that don't have a dependency
    #       Remove all discovered dependencies from ALL modules
    #       Repeat, until no more dependencies are discovered
    #

    my $go = 1;
    while ( $go)
    {
        #
        #   Append those entries that don't have any dependencies
        #
        my @dep_list;
        for my $file (sort keys %src)
        {
            next if ( $src{$file}{'done'} );

            my $num_dep = keys %{$src{$file}{'depend'}};
            if ( ! $num_dep  )
            {
                my $module = $src{$file}{'name'};
                Verbose3 "BuildOrder: $file : $module";
                push @dep_list, $module;
                $src{$file}{'done'} = 1;

                push @build_order, $file
                    unless ( ($#opt_only >= 0 ) && ! $opt_list{$module} );
            }
        }

        #
        #   Remove all of the discovered dependencies
        #
        my $mod;
        $go = 0;
        for my $file (sort keys %src)
        {
            for $mod (@dep_list)
            {
                $go =1;
                if (  exists ($src{$file}{'depend'}{$mod})  )
                {
                    $src{$file}{'discovered'}{$mod} = $src{$file}{'depend'}{$mod};
                    delete $src{$file}{'depend'}{$mod};
                }
            }
        }
    }

    #
    #   Ensure that there is nothing left to build
    #
    for my $file (sort keys %src)
    {
        my $num_dep = keys %{$src{$file}{'depend'}};
        if ( $num_dep )
        {
            printf "CANNOT BUILD: %s(%s). Missing: %s\n", $file, $src{$file}{'name'}, join ' ' ,sort (keys %{$src{$file}{'depend'}});
        }
    }

    #
    #   Return the ordered list of components to build
    #
    return @modules
        if ( $opt_buildfiles );

    return @build_order;
}

#-------------------------------------------------------------------------------
# Function        : label_all_files
#
# Description     : Label all files in the view
#
# Inputs          :
#
# Returns         :
#
sub label_all_files
{
    my $result;
    print "Label all files in the view with: $build_label\n";
    $result = System ("cleartool mklabel -rec $build_label $root_dir");
    if ( $result )
    {
        print "WARNING: Error reported by the labeling program";
    }
}


#-------------------------------------------------------------------------------
# Function        : rewrite_build_file
#
# Description     : Use a set of re-write rules to update directives within
#                   the $BUILDFILE file. Creates a $BUILDUSE file
#
# Inputs          : Directory of the target $BUILDFILE file
#
# Returns         : 0 - all is well, file not modified
#                   1 - all is well, file was modified
#
#
sub rewrite_build_file
{
    my ( $dir ) = @_;
    my $modified = 0;

    my $infile = "$root_dir/$dir/$BUILDFILE";
    my $ofile = "$root_dir/$dir/$BUILDUSE";

    #
    #   Adutit mode is special
    #       Ensure that ALL files read by this function are audited
    #       Do not delete or re-write the output file as we don't have enough
    #       information to do this
    #
    if ( $opt_audit_entry )
    {
        open ( INFILE, "<$infile" )   || Error "Rewrite: Cannot open $infile\n";
        my $dummy = readline( INFILE );
        close (INFILE );
        return;
    }


    #
    #   Unlink any OLD output file
    #
    unlink $ofile;

    #
    #   Open the input and output files
    #
    open ( INFILE, "<$infile" )   || Error "Rewrite: Cannot open $infile\n";
    open ( OUTFILE, ">$ofile" ) || Error "Rewrite: Cannot open $ofile\n";

    my $build_name = "UNKNOWN";
    my $build_version;

    my $release_name;
    my $release_version;

    print "\nRewrite: $infile\n";
    while ( <INFILE> )
    {
        next if ( m~^\s*#~ );            # Skip comments

        #
        #   Process BuildName
        #
        if ( m~\s*BuildName[\s\(]~ )
        {
            m/'([^\s]+)\s+(.*)'/;
            $build_name = $1;
            $build_version = $2;

            #
            #   Ensure that we have a valid name
            #
            my $new_ver;
            my $message;
            
            unless (is_known($build_name))
            {
                $message = " Error: BuildName not known";
                $new_ver = $build_version;
            }
            else
            {
                #
                #   Process the current line
                #
                $new_ver = version_to_project ( $build_name, "current" );

                #
                #   The version bit needs a space in it
                #
                $new_ver =~ s~\.(\w+)$~ $1~;

                s/'(.*)\s+(.*)'/'$build_name $new_ver'/;
            }
            print_update( $build_name ,$build_version, $new_ver, $message );
            $modified = 1
                if ( $build_version ne $new_ver );
            
            
        }

        #
        #   Process BuildReleaseFile
        #
        if ( m~\s*BuildReleaseFile[\s\(]~ )
        {
            m/'(.*)\s(.*)'/;
            $release_name = $1;
            $release_version = $2;

            my $new_ver;
            my $message;
            
            unless (is_known($build_name))
            {
                $message = " Error: BuildName not known";
                $new_ver = $release_version;
            }
            else
            {

                #
                #   Process the current line
                #
                $new_ver = version_to_project ( $build_name, "current" );
                s/'(.*)\s(.*)'/'$build_name $new_ver'/;
            }

            print_update ($release_name ,$release_version, $new_ver, $message );
            $modified = 1
                if ( $release_version ne $new_ver );
        }

        #
        #   Process BuildPkgArchive and LinkPkgArchive
        #   Must take care to handle project and non-project extensions
        #       ie: version 12.12.12.mas needs to be replaced with 13.13.13.mas
        #      and          12.12.12.syd needs to be replaced with 13.13.13.syd
        #      for the SAME component.
        #
        #
        if ( m/^LinkPkgArchive/ or m/^BuildPkgArchive/ )
        {
            my $comp;
            my $ver;
            my $new_ver;
            my $message;

            m/'(.*)'[^']*'(.*)'/;

            my $comp = $1;
            my $ver = $2;

            #
            #   Ensure that we have a valid name
            #
            unless (is_known($comp))
            {
                $message = " ERROR: Name not known: $comp";
                $new_ver = $ver;
                
            }
            else
            {
                #
                #   Examine the version part and determine if there is any project
                #   specfic part
                #
                $new_ver = version_to_project ( $comp, $ver );

                #
                #   Process the current line
                #
                s/'(.*)'([^']*)'(.*)'/'$comp'$2'$new_ver'/;
            }

            print_update ($comp ,$ver, $new_ver, $message );
            $modified = 1
                if ( $ver ne $new_ver );
        }
        

    }
    continue
    {
        #
        #   Always output the resultant line
        #
        print OUTFILE $_;
    }

    #
    #   Cleanup
    #
    close INFILE;
    close OUTFILE;

    return $modified;
}

#-------------------------------------------------------------------------------
# Function        : is_known
#
# Description     : Determine if this component is known to the program
#                   Internal and external packages are KNOWN
#                   Other packages need to be specified
#
# Inputs          : Component
#
# Returns         :
#
# is_known
sub is_known
{
    my ( $comp ) = @_;

    return 1
        if ( (defined $external_component{$comp}) || ( defined $internal{$comp} ) );

    return 0;
}


#-------------------------------------------------------------------------------
# Function        : version_to_project
#
# Description     : Take a version string and return a project extension
#
# Inputs          :
#
# Returns         :
#
sub version_to_project
{
    my ( $comp, $ver ) = @_;
    my $proj;

    #
    #   Replace Internal components with a single project wide build version
    #
    return $label
        unless ( $external_component{$comp}  );


    #
    #   The version string may have a trailing project specifier
    #       ie: 12.12.12.mas
    #   Determine the suffix
    #
    $proj = $1
        if ($ver =~ m/\.([^\d]+)$/);


#print "         Project: comp=$comp, ver=$ver, proj=$proj,\n";
        
    #
    #   Some extension need special treatment
    #   "current"   - Assume that the TAG is in the component HASH
    #
    if ( $ver =~ m/current/ )
    {
        $proj = "";
    }
    elsif ( $proj )
    {
        $proj = $project unless ( $project_keep{$proj} );
    }
    $proj = "." . $proj if ( $proj );


    my $new_comp;
    if ( $kludge_component{$comp} )
    {
        my $type = "xxx";
        $type = "mas"
            if ( $proj =~ m/mas/ );
        if ( $kludge_component{$comp}{$type} )
        {
            $new_comp = $kludge_component{$comp}{$type};
        }
        else
        {
            $new_comp = $kludge_component{$comp}{$project};
        }

    }
    else
    {
        $new_comp = $external_component{$comp};
    }
    $new_comp =~ s~xxx~$project~;
    $proj = "" if ( $new_comp =~ m/\.([^\d]+)$/ );
        

    return $new_comp . $proj;

}

#-------------------------------------------------------------------------------
# Function        : print_update
#
# Description     : Generate a display line tracking the changes made
#
# Inputs          :
#
# Returns         :
#
sub print_update
{
    my ($name, $version, $new_version, $message ) = @_;
    my $diff = " ";
    $diff = "*"
        if ( $version ne $new_version );

    printf "Name     : %-25s, Version: %-12s %s-> %-12s %s\n", $name ,$version, $diff, $new_version,$message;
}

#-------------------------------------------------------------------------------
# Function        : rm_directory
#
# Description     : Cannot rely on the underlying 'rm' command. Many of them
#                   are brain-dead. ie: The one in the JATS BIN.win32 directory
#
# Inputs          :
#
# Returns         :
#
sub rm_dir
{
    print ("Remove Directory: @_\n");
    rmtree( @_ , $opt_verbose, 0);
}

#-------------------------------------------------------------------------------
# Function        : generate_pathlist
#
# Description     :
#
# Inputs          :
#
# Returns         :
#
sub generate_pathlist
{
    our %cf_info;                           # Contains Makefile.cfg data
    our %cf_build_info;                     # Contains Buildfile.cfg
    our %cf_filelist;                       # Contains a hash of other makefiles

    my %mdata;                              # Hash of collected data

    #-------------------------------------------------------------------------------
    #   Process each module specified and create a data structure that represents
    #   all the relevent information
    #
    for my $module ( @modules )
    {
        Verbose( "Processing: $module" );

        #
        #   Locate the JATS generated data file
        #   This will be in the "interface" directory, or if this directory does not
        #   exist the file will be found in the current directory
        #
        #   The data file can be "require"ed directly by Perl. The data will be
        #   conatined with a few cf_xxxx hashes
        #
        for (qw(Makefile.cfg Buildfile.cfg))
        {
            Verbose( "generate_pathlist: $_" );
            my $fname = "$root_dir/$module/$_";
            $fname = "$root_dir/$module/interface/$_" unless ( -f $fname );
            Error ( "Cannot locate $fname") unless ( -f $fname );
            require $fname;
        }

        Error ("Data in Makefile.cfg is not valid" )
            unless ( keys(%cf_filelist) > 0 );

        #
        #   Process all the Makefile_xx.cfg files
        #
        for my $cfg_file ( sort values(%cf_filelist))
        {
            Verbose( "generate_pathlist: Processing: $cfg_file" );
            my $fname = "$root_dir/$module/$cfg_file";
            $fname = "$root_dir/$module/interface/$cfg_file" unless ( -f $fname );

            #
            #   Simply ignore missing config files
            #
            next unless ( -f $fname );
            require $fname;
            Verbose( "generate_pathlist: Have data from: $cfg_file" );

            #
            #   All the data is held within %cf_info, which is hashed
            #   by platform
            #
            foreach my $key ( sort keys(%cf_info) )
            {
                my %dirs;
                my $pData = \%{$mdata{$key}};

                Verbose( "Processing $cfg_file, PLATFORM: $key" );

                my $pInfo = \%{$cf_info{$key}};

                $pData->{'Platform'} = $pInfo->{'$ScmPlatform'};
                $pData->{'Product'}  = $pInfo->{'$ScmProduct'};
                $pData->{'Target'}   = $pInfo->{'$ScmTarget'};

                for ( @{$pInfo->{'@INCDIRS'}}, @{$pInfo->{'@SRCDIRS'}} )
                {
                    $dirs{$_}++;
                }

                push @{$pData->{'dirs'}}, keys (%dirs);
            }
        }

        #
        #   Release the memory used by the read data
        #   This will also clear it for next iteration of the loop
        #
        %cf_info = ();
        %cf_build_info = ();
    }

    #-------------------------------------------------------------------------------
    #   Data has been collected, but it is a bit mixed up.
    #   There are basically two types of modules encountered
    #       1) Platforms that have a PRODUCT and a TARGET
    #       2) Simple modules that have a TARGET
    #          These are used by all many modules
    #
    #   To add to the confusion, some PLATFORM are really simple TARGETS too.
    #   The only difference is that they are not used by anything else
    #
    #   Make a single pass over the data and MARK entries that are used as targets
    #   Ignore entries where the target and the platform are the same as these are
    #   simple
    #
    for my $key ( keys %mdata)
    {
        my $target = $mdata{$key}{'Target'};
        $mdata{$target}{'MARK'}++
            unless ( $key eq $target );
    }

    #
    #   Generate complete source directory lists for entries with NO mark
    #   These are the platforms at the top of the food chain, not simply targets
    #   for use by products and platforms.
    #
    for my $key ( keys %mdata)
    {
        my $pData = \%{$mdata{$key}};

        next if ( $pData->{'MARK'}  );
        Verbose ( "PLATFORM: $key" );

        my $Platform = $pData->{'Platform'};
        my $Product  = $pData->{'Product'};
        my $Target   = $pData->{'Target'};

        #
        #   Generate a list of unique directories
        #
        my %dirs;
        for ( @{$pData->{'dirs'}} )
        {
            $dirs{$_}++;
        }
        #
        #   Add in the Target directories too
        #
        unless ( $Platform eq $Target )
        {
            for ( @{$mdata{$Target}{'dirs'}} )
            {
                $dirs{$_}++;
            }
        }

        #
        #   Generate the output file
        #
        my $ofile = "$key.pathlist";
        my $localtime = localtime();
        print "Generate pathlist for: $key\n";
        open ( OFILE, ">$ofile" ) or Error ("Cannot create $ofile");
        select OFILE;

        print <<EOF;
#   List of directories specified in the build
#
#   Generated: $localtime
#
#   Platform : $Platform
#   Product  : $Product
#   Target   : $Target
#
EOF
        for (sort keys %dirs )
        {
            #
            #   Convert / -> \ for Windows
            #   Remove paths with internal names, like: $(OBJDIR)
            #
            (my $dirs = $_) =~ s~/~\\~g;
            print $dirs . "\n"
                unless ( $dirs =~ m~\$\([A-Z]+\)~ );
        }

        close OFILE;
        select STDOUT;
    }
}


#-------------------------------------------------------------------------------
#   Documentation
#

=pod

=head1 NAME

jats_builder - Build a system with multiple build.pl files

=head1 SYNOPSIS

 perl jats_builder.pl [options]

 Options:
    -help              - brief help message
    -help -help        - Detailed help message
    -man               - Full documentation
    -config=xxx        - Specify config file. Default is build.cfg
    -clean             - Clean out packages. Clean build
    -only pkg          - Build a single package - and possibly all prerequisites.
                         This option can be used multiple times.
    -resetlog          - Restart the logfile
    -show              - Display external modules and the build order
    -showrule          - Display external modules, the build order, and clearcase rule
    -showpkg           - Display external modules as LinkPkgArchive statements
    -verbose           - Verbose operation
    -updatecache       - Ensure all external packages are cached

    -audit             - Generate a clearaudit trail of the build
    -[no]build         - Build (default is build)
    -[no]all           - Build all components (default is noall)
    -[no]expert        - Expert mode. Allows build to restart. (default is expert)
    -[no]log           - Use a logfile (default is log)
    -[no]prereq        - Build prerequisites to "only" components. Default
    -[no]quick         - Build only debug packages (default is noquick)
    -[no]skip          - Skip installed packages (default)
    -create_dpkg       - Install packages into dpkg_archive, after all has built

    The following options imply "nobuild"

    -buildfiles                     - Enables operations only build.pl and build.use.pl
        -[no]buildfiles_rewrite     - Rewrite build.pl (default is no)
        -[no]buildfiles_checkout    - Checkout build.pl if required (default is no)
        -[no]buildfiles_label       - Label build.pl (default is no)
        -[no]buildfiles_labelall    - Label all builder files (default is no)
        -[no]buildfiles_labelrule   - Label all builder files with a rule based label (default is no)

    -dump              - Dump component dependency info, except external packages
    -dump -dump        - Dump component dependency info (with sub components), except external packages
    -dumpall           - Dump all component dependency info
    -labelall          - Label all files in the view
    -pathlist          - Generate pathlists for all platforms

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exits.

=item B<-help -help>

Print a detailed help message with an explanation for each option.

=item B<-man>

Prints the manual page and exits.


=item B<-config=file>

Specify the name of a config file to use. If no configuration file is specified
then the program will be use B<build.cfg>. The program will search for the specified
config file upwards from the current directory.

The name of the config file will be used as the basis for the logfile name;
any extension is removed and an extension of ".log" will be added.

The directory in which the config file is found will be used as the basis for
the log file.

=item B<-clean>

Deletes the local_dpkg_archive directory contents and then cleans and clobbers
all the configured components. The logfile is also deleted.

=item B<-only pkg>

Will build a single component. This option may be used multiple times to force
multiple components to be built.

If the "-prereq" option is selected, then the prerequisites components will be
determined and may be built.

=item B<-resetlog>

Deletes the build.log logfile.

In a full system build the logfile is not deleted be default. This allows the
build to be stopped and restarted.

If a single ( or a list ) of components is being built, then the logfile will
always be deleted.

=item B<-show>

Display the external and internal components.

The external components together with their version numbers, in alphabetic order.

The internal components are displayed in the required build order. When building
a selected set of components only the selected components are displayed.

=item B<-showrule>

Display the external and internal components. The clearcase selection rule are
also shown for the internal components.

The external components together with their version numbers, in alphabetic order.

The internal components are displayed in the required build order. When building
a selected set of components only the selected components are displayed.

The clearcase selection rule is taken from the directory two above the build file.
This ensures that the rule for the component is used.

=item B<-showpkg>

Display the external components as LinkPkgArchive statements.

This is intended to generate list of external components that can be copied
directly into release manager.

This command will terminate the program. It implies B<-nobuild>.

=item B<-verbose>

Increase the level of debugging information displayed. This option may be used
multiple times.

=item B<-updatecache>

Allows external components are determined and are copied into a local
dpkg_archive cache, if  present. This will significantly speed up operation if
dpkg_archive is on a network drive.

The local dpkg_archive cache is located by examining the DPKG_ARCHIVE variable
for an archive name that contains the word "cache".

If used once then, packages will be copied into the cache only if not already
present. If used more than once, then packages will be refreshed - they
will be deleted and copied again.

=item B<-[no]build>

The nobuild option will prevent the program from building components.
The configuration file will be read and any errors will be reported.

The default is "build", which will determine the components to be built, the
order in which the components should be built and then proceed to build the
components.

=item B<-[no]all>

Normally the program will build B<all> components only if it is started in the same
directory as the configuration file, otherwise it will build the named components
or the component found in the current directory.

The B<all> option will force the program to build all components within
the configuration file. The default is "noall".

=item B<-[no]expert>

The use of the -expert mode allows the build to be restarted at the point were
item had been terminated. The components are not cleaned before being built.

The default mode is expert.

=item B<-[no]log>

Capture and log all the build information to a logfile.

With nolog the build information is displayed on the screen. This may be useful
for debugging.

=item B<-[no]prereq>

When building one or more named components the complete list of prerequisites
modules will be built.

The "noprereq" option allows one modules to be built without examination of the
prerequisites. This may lead to a build problem unless all the prerequisites can
be located.

=item B<-[no]quick>

The "quick" option only builds the "debug" version of all the components.

The, default, "noquick" option builds the "debug" and "production" version of components.

=item B<-[no]skip>

By default, a component will not be build if item looks as though item has been
built. The "noskip" option suppresses this operation.

A component is deemed to have been built iff:

=over 8

=item 1

The components "pkg" directory exists

=item 2

The component is found in the local dpkg_archive

=back

=item B<-audit>

If B<audit> is enabled then the clearcase audit system will be invoked and an
audit log of the files used in the build will be recorded.

The audit log will be recorded with each built component and a global audit logfile
will be created in the root directory.

=item B<-create_dpkg>

Install built packages into the main dpkg_archive.

This operation is performed in conjunction with a build, but only after all the
packages have been built sucessfully.

The installation process is interactive and the output is not logged to a logfile.

=item B<-buildfiles>

If this option is present, then the otherwise buildfile options are activated and
will the build.pl and p=build.use.pl files may be modified.

In all cases a file called build.use.pl file will be created.

The use of this option prevents components being built.

=item B<-[no]buildfiles_rewrite>

If -buildfiles is present then this option will allows the build.pl file to be re-written.

The default operation is to NOT modify the build.pl file, only the build.use.pl.

=item B<-[no]buildfiles_checkout>

If -buildfiles is present then this option will instruct the program to checkout the
build.pl file, before it is modified. The file is not checked in.

The default operation is to NOT checkout the build.pl file.

=item B<-[no]buildfiles_label>

If -buildfiles is present then this option instructs the program to label the
build.pl file.

The default operation is to NOT label the files.

=item B<-[no]buildfiles_labelall>

If -buildfiles is present then this option instructs the program to label the
build.pl file as well as : makefile.pl src/makefile.pl and the directories . and ..

The default operation is to use a label derived from the B<LABEL> directive
in the builder configuration file. This is modified if the  B<-[no]buildfiles_labelrule>
option is specified.

The default operation is to NOT label the files.

=item B<-[no]buildfiles_labelrule>

If -buildfiles is present then this option instructs the program to label the
build.pl file as well as : makefile.pl src/makefile.pl and the directories . and ..

The label that is used is derived from the clearcase rule that loads the parents
parents directory. This is the label shown with the B<-showrule> option.

The default operation is to use a label derived from the B<LABEL> directive
in the builder configuration file.


=item B<-dump>

Dump the the dependency information for all internal components

When used twice the dump also includes all dependants of the dependants.

The use of this option prevents components being built.

=item B<-dumpall>

Dump the the dependency information for all external and internal components

The use of this option prevents components being built.

=item B<-labelall>

Label all files in the view

The use of this option prevents components being built.

=item B<-pathlist>

This option will generate files for each major platform that contain pathlists
for all files used in the compilation. This file is intended to be used by
source level debuggers.

The path information is extracted from information generated by the makefile build
operation. The modules not need to be compiled. Conversely only
information that is present to the build system will be in the pathlist file.

Pathlist files are named as F<xxxx.pathlist>.

=back

=head1 DESCRIPTION

B<This program> will build a system containing one or more inter-related build
files using the JATS build tools.

The program also provides a numbers of ancillary function to assist in the
maintenance of build systems.

In normal operation the program will:

=over 8

=item *

Locate a configuration file called F<build.cfg>

=item *

Determine the components in the build

=item *

Determine the build order of the components

=item *

Build and install the components

=back

=head1  THE CONFIGURATION FILE

The configuration provides information essential to the building process. It is
located in the root of the build tree and as such determines the root directory
for the build. The root directory is used to contain the local dpkg_archive
directory and the logfile.

The configuration file is called F<build.cfg>. It is a simple text file.
Comments begin with a "#" and continue to the end of the line. Blank line are
ignored.

The file provides the following information:

=over 8

=item *

The project version number and name.

=item *

Location of the project root directory

=item *

Other configuration files

=item *

A list of external components and required versions

=item *

A list of internal components.

=back

=head2 Project Version number and name

A LABEL statement is used to specify the version number of the build as well as
the project name. The project name is used to identify project specific options
within the build, such as external packages. The label used as a basis for
labeling the build files.

=head3 LABEL directive format

The format of the label directive is

=over 8

=item   B<LABEL> C<nn.nn.nn.prj>

=back

=head2 Root Directory

A ROOTDIR statement is used to specify the root of all the internal components.
If no ROOTDIR statement is encountered then the root directory is that in which
the config file was found.

The ROOTDIR directory must be within the current path. The config file must be
within the file tree at the same level, or below the root directory.

=head3 ROOTDIR directive format

The format of the ROOTDIR directive is

=over 8

=item   B<ROOTDIR> C<dir_name>

Where C<dir_name> is a simple name. It cannot contain ".", ".." or "/" characters.

=back

=head2 Included configuration files

An INCLUDE statement is used to include another configuration file.

Configuration files may include a common component, perhaps tools define a core
set of external components, or a common label or root directory.

Included Configuration files may be nested to a depth of 10.

=head3 INCLUDE directive format

The format of the INCLUDE directive is

=over 8

=item   B<INCLUDE> C<file_name>

Where C<file_name> is the name of a configuration file to be included.

=back


=head2 External components

An external component is a component that is not to be built by this program.
The component is provided by a package to be found in dpkg_archive.

Several directives are available to specify external components. These include:

=over 8

=item *

EXTERNAL directive

=item *

LinkPkgArchive statements

=item *

BuildPkgArchive statements

=back

The LinkPkgArchive and BuildPkgArchive statements allows a simple interface to
the B<Release Manager> and perform the same job as the EXTERNAL directive.

=head3 EXTERNAL directive

The format of the EXTERNAL directive is:

=over 8

=item   B<EXTERNAL> I<name> I<version> [OPTIONS]

=back

Where B<name> is the name of the external package.
B<version> is the version of the external package. This may contain a suffix.
B<OPTIONS> is an optional option.

The version may contain a project specific suffix. If this is "xxx" then it will
be replaced with the project suffix.

Available options include:

=over 8

=item   B<ALLOW_MULTI>

This option allows multiple packages with the same name, but different version
information. This is required to provide correct processing of packages that
have a project independent part and a project dependent part, such a
C<sysbasetypes>.

=back

=head4 EXTERNAL directive example

    BuildPkgArchive ( 'solidbasetypes', '1.0.0.mas' );
    BuildPkgArchive ( 'sysbasetypes'  , '20.0.0.mas' );       ALLOW_MULTI
    BuildPkgArchive ( 'sysbasetypes'  , '20.0.0.syd' );       ALLOW_MULTI
    BuildPkgArchive ( 'sysswis'       , '20.1.0.syd' );
    EXTERNAL          crypta_dsi        20.0.0.cr
    EXTERNAL          crc               1.0.2.cr

=head2 Internal components

Internal components are those that can be built by this program. Allows lines in
the configuration file that are not another directive will be treated as the
specification of an internal component.

Internal components are specified by the path, from the project root directory
to the JATS build.pl file. The order in which the components is specified is
B<not> important.

=head4 Internal component example

     ar/build/jats
     br/enquiry/build/jats
     br/farepayment/build/jats
     br/framework/build/jats

=head2 Miscellaneous directives

The configuration file may contain a number of Miscellaneous directives.

=head3 MESSAGE

The MESSAGE directive simply displays the remainder of the line.

=head3 EXCLUDE

The EXCLUDE directive displays the remainder of the line with the prefix
of B<EXCLUDED>. It may be used to indicate that a directive is being suppressed.

=head1  BUILD OPERATION

The steps in building a components are detailed in the following sections.

=head2  Determine if the component needs to be built

The program will not build a component if it has already been built and
installed into the local dpkg_archive, unless the -noskip option is present.

=head2  Re-write the build.pl file

The components build.pl file is re-written as build.use.pl. The re-write process
will massage the package versions found within the B<BuildName>,
B<BuildRelease>, B<LnkPkgArchive> and B<BuildPkgArchive> directives. This file
is then used in later operations and is available for manual use by JATS.

=head2  Generate the JATS makefiles

Use the re-written build.use.pl file to create the JATS build environment. This will
copy into the user sandbox any external components specified with a B<BuildPkgArchive>
as well as generate makefiles for (all the required targets.

=head2  Compile the component

JATS is used to "make" the component. The B<quick> option allows only the
"debug" portions to be created, otherwise both the B<debug> and B<prod>unction portions
are made.

=head2  Install the component

If the component is successfully built and made, then the resultant package
will be installed into a local dpkg_archive for use by otherwise dependent
components.

=cut