########################################################################
# Copyright (C) 1998-2011 Vix Technology, All rights reserved
#
# Module name   : buildlib.pl
# Module type   : Makefile system
# Compiler(s)   : Perl
# Environment(s): jats
#
# Description   : Process build.pl file and create the build environment
#
# Usage         : Refer to documentation at the end of the file
#
#......................................................................#

use strict;
use warnings;

use JatsError;
use BuildVersion;
use BuildName;
use PackageEntry;
use JatsEnv;
use JatsSystem;
use JatsVersionUtils;
use FileUtils;
use Pod::Usage;
use Getopt::Long;
use File::Path;

our $BuildVersion           = "2.1.0";

#.. Switchs
#
our $ScmBuildlib            = 0;
our $ScmBuildSrc            = "";

our $CmdSwitch              = "";
our $Clobber                = 0;
our $Archive                = 0;
our $Interface              = 0;
our $RootOnly               = 0;
our $Perms                  = 0;
our $Expert                 = 0;
our $All                    = 0;
our $Nolog                  = 0;
our $Cache                  = 0;
our $NoPackageError         = 0;
our $ForceBuildPkg          = 0;
our $Srcdir                 = "";               # default source root
our $ForceBuild             = 1;
our $IgnorePkgs             = 0;

#.. Public symbols, referenced by many build.pl implementations
#
our $BUILDPREVIOUSVERSION   = "0.0.0";          # BuildPreviousVersion()
our @BUILDPLATFORMS         = ();               # BuildPlatforms()
our %BUILDINFO              = ();               # BuildInfo
our @DEFBUILDPLATFORMS      = ();
our %BUILDPLATFORMARGS      = ();
our @BUILD_ACTIVEPLATFORMS  = ();
our @BUILDSUBDIRS           = ();               # BuildSubDir()
our @BUILDSETENV            = ();               # BuildSetenv()
our $BUILDINTERFACE         = "";               # BuildInterface()
our $BUILDLOCAL             = "";               # BuildInterface()
our $BUILDDIRTREE           = "";               # BuildDirTree()
our @BUILD_BADNAME          = ();               # Unknown platforms

our $BUILDNAME              = "";               # BuildName()
our $BUILDVERSION           = "";               # BuildName()
our $BUILDNAME_PACKAGE;                         # Name
our $BUILDNAME_VERSION;                         # Version
our $BUILDNAME_PROJECT;                         # Project(optional)
our $BUILDNAME_SUFFIX;                          # Project (available)
our $DEPLOY_PATCH           = 0;                # Deplyment patch number

our %BUILDALIAS_DELAY       = ();               # Delayed aliases
our %BUILDALIAS_TARGETS     = ();               # BuildAlias from --Targets
our %BUILDALIAS             = ();               # BuildAlias
our %BUILDPRODUCT           = ();               # BuildProduct
our %BUILDPRODUCT_PARTS     = ();               # BuildProduct parts
our %PKGRULES               = ();               # Package include and link rules
our @BUILDTOOLS             = ();               # Extended tool path
our $BUILDPHASE             = 0;                # In Build Phase
our @CLOBBERDIRS            = ();               # Directories to clobber
our @REMOVEDIRS             = ();               # Directories to remove - if empty
our %BUILD_KNOWNFILES       = ();               # Files that will be known

our $Makelib                = "";
our $GBE_CORE;                                  # Root of JATS
our $InterfaceVersion;                          # Interface directory format version
our $ScmRoot;                                   # Package Root
our $ScmInterface;                              # Interface directory
our $ScmBuildFilter;                            # Build Filter when build was created
our $NoBuild                = 0;                # Dummy Build under ABT only


my  $DeleteDPACKAGE         = 0;                # Must clobber DPACKAGE
my  $build_source_pkg       = 0;                # Flag to build source package
my  $opt_help               = 0;
my  $sandbox_exact          = 0;                # Exact or in-exact sandbox

BuildLibInit();

sub BuildLibInit
{

#.. Set environment
#
    EnvImport( 'GBE_VERSION' );
    EnvImport( 'GBE_BIN' );
    EnvImport( 'GBE_CORE' );
    EnvImport( 'GBE_PERL' );
    EnvImport( 'GBE_TOOLS' );
    EnvImport( 'GBE_CONFIG' );
    EnvImport( 'GBE_DPKG' );
    EnvImport( 'GBE_MACHTYPE' );
    EnvImport( 'USER' );
    EnvImport( 'GBE_HOSTNAME');
    EnvImport( 'GBE_DRV' )
        if ( $ScmHost ne 'Unix' );            # DOS or WIN special

    EnvImportOptional ( 'GBE_DPKG_STORE','' );
    EnvImportOptional ( 'GBE_DPKG_CACHE','' );
    EnvImportOptional ( 'GBE_DPKG_LOCAL','' );
    EnvImportOptional ( 'GBE_DPKG_SBOX' ,'' );
    EnvImportOptional ( 'GBE_DPLY'      ,'' );
    EnvImportOptional ( 'GBE_SANDBOX'   ,'' );

    EnvImportOptional ( 'GBE_PLATFORM' );           # optional PLATFORM filter
    EnvImportOptional ( 'GBE_BUILDFILTER' );        # optional BUILD filter       
    EnvImportOptional ( 'GBE_ABT' );                # optional ABT flags          
    
#.. Common stuff
#
    require "$::GBE_TOOLS/common.pl";
    CommonInit( 'buildlib' );
    Debug( "Version:   $BuildVersion" );
    Require( "$::GBE_CONFIG/PLATFORM", "PLATFORM_CFG.PM"  );

#.. Parse command line
#
    $ScmBuildSrc = $0;                          # Name of the build file
    $Cwd = shift @ARGV;
    $Cwd =~ tr~\\/~/~s;;                        # Need / in path, Remove doubles
    $::ScmRoot = StripDrive($Cwd);
    $Makelib = shift @ARGV;                     # Only for legacy build.pl files

    Verbose ("Command Line: @ARGV");
    my $result = GetOptions( "help|h:+"      => \$opt_help,
                             "man:3"         => \$opt_help,
                             "debug:+"       => \$::ScmDebug,
                             "verbose:+"     => \$::ScmVerbose,
                             "expert:1"      => \$Expert,
                             "all"           => \$All,
                             "nolog"         => \$Nolog,
                             "cache:+"       => \$Cache,
                             "package"       => \$NoPackageError,
                             "nopackages"    => \$IgnorePkgs,
                             "forcebuildpkg" => \$ForceBuildPkg,
                             "force!"        => \$ForceBuild,
                             );
    Usage() if ( $opt_help || !$result );

    Debug( "Host:          ", $ScmHost );
    Debug( "Cwd:           ", $Cwd );
    Debug( "Makelib:       ", $Makelib );
    Debug( "BuildFile:     ", $ScmBuildSrc );
    Debug( "Debug:         ", $::ScmDebug );
    Debug( "Verbose:       ", $::ScmVerbose );
    Debug( "Expert:        ", $Expert );
    Debug( "All:           ", $All );
    Debug( "Nolog:         ", $Nolog );
    Debug( "Cache:         ", $Cache );
    Debug( "package:       ", $NoPackageError );
    Debug( "ForcePkg  :    ", $ForceBuildPkg );
    Debug( "ForceBuild :   ", $ForceBuild );
    Debug( "$IgnorePkgs :  ", $IgnorePkgs );

#.. Command
#

    $CmdSwitch = (lc shift @ARGV) if @ARGV;
    Debug( "CmdSwitch:     ", $CmdSwitch );

    if ( $CmdSwitch )
    {
        if ( $CmdSwitch eq "interface" ) {
            $Interface      = 1;

        } elsif ( $CmdSwitch eq "rootonly" ) {
            $RootOnly       = 1;

        } elsif ( $CmdSwitch eq "clobber" ) {
            $Clobber        = 1;

        } elsif ( $CmdSwitch eq "help" || $CmdSwitch eq "usage" ) {
            $opt_help = 1;
            Usage();

        } elsif ( $CmdSwitch eq "changelog" ) {
            if ( -d "CVS" )                         # CVS support subdir
            {
                System( "$::GBE_PERL $::GBE_TOOLS/cvs2cl.pl --tags --branches --revisions --day-of-week" )
            }
            exit(1);

        } else {
            Usage( "(E) build. Unknown command \"$CmdSwitch\"" );
        }
    }

    #
    #   If we are not performing a ForceBuild, then we don't need to continue
    #   We have updated the interface directory with BuildPkgArchive
    #   information.
    #
    unless ( $::GBE_SANDBOX )
    {
        TestForForcedBuild();
    }

    #
    #   Must inform makelib that its running under buildlib
    #
    $ScmBuildlib = 1;

    #
    #   In clobber mode System commands will not force termination
    #   otherwise, within build.pl, a failed system command will die.
    #
    SystemConfig ('UseShell' => 1,
                  'ExitOnError' => ($Clobber == 0) );
}


#-------------------------------------------------------------------------------
# Function        : Log
#
# Description     : Internal function to generate a log file of the build process
#                   The function will print its arguments to the screen and a log file
#
# Inputs          : Arguments will be printed
#
# Returns         : Nothing
#
sub Log
{
    if ( ! $Clobber )
    {
        print "@_\n";
        FileAppend ('build.log', \@_ );
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildSubDir
#
# Description     : Specify one or more directories in which makefile.pl's can be
#                   found to be processed.
#
#                   This function will flag the build 'src' dir.
#                   This will be the first directory specified UNLESS there
#                   is a 'src' directory in the list
#
#                   The function may be called multiple times.
#
# Inputs          : NewDirs             - An array of directories
#
# Returns         : Nothing
#

sub BuildSubDir
{
    my( @NewDirs );

    @NewDirs = map { split /\s+/ } @_;
    @NewDirs = grep { defined $_ } @NewDirs;

    Debug( "BuildSubDir(@NewDirs)" );

    foreach my $ThisDir ( @NewDirs )
    {
        unless ( $Clobber )
        {
            $ThisDir =~ s~/+$~~;
            if ( $ThisDir eq "." )
            {
                Error( "BuildSubDir() cannot specify the current directory (.)",
                       "The makefile.pl in the root directory is included in all makefile.pl's" );
            }

            if ( $ThisDir =~ m~\\~)
            {
                Warning ("BuildSubDir contains a '\\' character: $ThisDir" );
            }
            if ( grep /^$ThisDir$/, @BUILDSUBDIRS )
            {
                Warning( "BuildSubDir() duplicate subdirectory ignored '$ThisDir'." );
                next;
            }
            if ( ! ( -e $ThisDir and -d $ThisDir ) )
            {
                Error( "BuildSubDir() non-existent subdirectory: '$ThisDir'." );
            }
            if ( ! -f $ThisDir . '/makefile.pl' )
            {
                Error( "BuildSubDir() makefile.pl not found in subdirectory: '$ThisDir'." );
            }

            push( @BUILDSUBDIRS, $ThisDir );
        }

        #
        #   Capture the first source directory mentioned
        #   This will be used as the root of the build
        #
        #   If there is a Src directory then use this
        #
        $Srcdir = $ThisDir
            if ( $ThisDir =~ m/^src$/i );
        $Srcdir = $ThisDir
            unless ( $Srcdir );
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildAlias
#
# Description     : Create an alias for multiple targets
#                   The default operations will:
#                       Add the alias as a default platform (* in the makefile.pl)
#                       Perform an implicit BuildPlatform for the alias targets
#
#                   In hindsight this was not good. Options modify the behaviour
#                   Options:
#                       --NotDefault    The alias will not be a part of the default
#                                       platform for the makefile.pls
#                       --Define        Simply define text substitution
#                                       Do not implicitly create platforms
#                       --Target        Accumulate platforms with specified targets
#                                       Complete alias determination is delayed
#                                       The named targets are specified as an alias
#                                       until the calculation is done
#
# Inputs          : alias[,--options]   comma seperated options
#                   arguments           alias arguments; platforms or targets
#
# Returns         : Nothing
#
sub BuildAlias
{
    my( $alias, @arguments ) = @_;
    my $notdefault;
    my $define;
    my $target_mode;

    Debug( "BuildAlias($alias, @arguments)" );
    Error ("BuildAlias: No platforms specified") unless ( @arguments );
    Error( "BuildAlias() must appear before BuildName()..." ) if ( $BUILDNAME );

    #   Parse attributes
    #
    my ( @attrs ) = split( ',', $alias );

    $alias = "";
    foreach ( @attrs ) {
        if ( /^--/ ) {
            if ( /^--NotDefault$/ ) {
                $notdefault = 1;

            } elsif ( /^--Define$/ ) {
                $define = 1;

            } elsif ( /^--Target$/ ) {
                $target_mode = 1;
                
            } else {
                Warning( "BuildAlias() unknown attribute: $_ -- ignored" );
            }

        } else {
            Error( "BuildAlias() multiple alias specifications -- ignored" )
                if ( $alias ne "" );
            $alias = $_;
        }
    }
    Error( "BuildAlias() missing alias specifications" )
        if ( $alias eq "" );


    #
    #   If we need to recalculate the alias based on targets, then tag the alias
    #   to be processed
    #
    $BUILDALIAS_TARGETS{ $alias } = ''
        if ( $target_mode );

    #   Define alias
    #
    if ( $alias eq "GENERIC" )
    {
        Error( "BuildAlias() cannot create an alias named GENERIC" );
    }
    elsif ( $alias ne quotemeta( $alias ) )
    {
        Warning( "BuildAlias() alias '$alias' contains invalid characters -- ignored" );
    }
    elsif ( $BUILDALIAS{ $alias } )
    {
        Warning( "BuildAlias() duplicate alias '$alias' -- alias ignored" );
    }
    else
    {
        #
        #   Expand alias UNLESS using --Target.
        #   The --Target is a real target and not a subject to alias expansion
        #   This solves order dependancy problems.
        #
        @arguments = ExpandPlatforms( @arguments )
            unless $target_mode;

        my $platform = "";                   # current platform
        my @pargs = ();                      # current args

        #
        #   Process the expanded arguments
        #   Collect arguments and process when a new platform is discovered
        #
        foreach my $arg ( @arguments, '++' )
        {
            if ( $arg =~ /^--/ )
            {
                push @pargs, $arg;
                next;
            }
            else
            {
                #
                #   Start of a platform
                #   Process previous data, once a platform has been seen
                #
                if ( $platform )
                {
                    #   Add arguments to BUILDALIAS as we see them
                    #
                    HashJoin( \%BUILDALIAS, ' ', $alias, $platform );
                    HashJoin( \%BUILDALIAS, ' ', $alias, grep(!/^--Uses=/, @pargs) );

                    #
                    #   The BuildAlias can also define a platform.
                    #   (Sounded like a good idea at the time!)
                    #
                    unless ( $define || $target_mode )
                    {
                        push @pargs, '--NotDefault' if ( $notdefault );
                        push @pargs, '--FunctionName=BuildAlias';
                        BuildPlatforms( $platform, @pargs );
                    }
                }

                #
                #   Start collecting args for the next platform
                #
                @pargs = ();
                $platform = $arg;
            }
        }
    }
}


#-------------------------------------------------------------------------------
# Function        : Process_TargetAlias
#
# Description     : Post Process the --Target option for the build alias
#                   Filter all platforms and extract those with a matching targets
#
# Inputs          : None
#
# Returns         : Nothing
#
sub Process_TargetAlias
{

    #
    #   Merge any delayed aliases with the complete set of alias
    #   Delayed alias are not used in expansions during the processing
    #   of platforms and targets, but can be used to pick up
    #
    while ( my($key,$value) = each(%BUILDALIAS_DELAY) )
    {
        if ( exists($BUILDALIAS{$key}) )
        {
            Warning( "BuildAlias() duplicates internal alias '$key' -- internal alias ignored" );
            next;
        }
        $BUILDALIAS{$key} = $value;
    }

    foreach my $alias ( keys %BUILDALIAS_TARGETS )
    {
        Debug( "BuildTargetAlias($alias)" );

        #
        #   Replace the existing alias - it has done its JOB
        #
        my $arguments = delete $BUILDALIAS{ $alias } ;

        foreach my $arg ( split / /, $arguments )
        {
            if ( $arg =~ /^--/ )                # argument
            {
                #   Add arguments to BUILDALIAS as we see them
                #
                HashJoin( \%BUILDALIAS, ' ', $alias, $arg );
                next;
            }

            foreach my $platform ( keys %BUILDINFO )
            {
                foreach my $element ( qw (TARGET BASE ) )
                {
                    my $target = $BUILDINFO{$platform}{$element};
                    if ( $target && $target eq $arg )
                    {
                        HashUniqueJoin( \%BUILDALIAS, ' ', $alias, $platform );
                        Debug( "BuildTargetAlias: $alias, $target -> $platform" );
                    }
                }
            }
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildProduct
#
# Description     : Create a family of Platforms with a common product line
#                   ie: Create two flavors of the TP5, one based on the MOSF and
#                   the othe based on the MOS68 toolset.
#
# Inputs          : $product[,opts]+    The name of the product
#                                       This will be the base name for the family
#                                       Allowed options are:
#                                           --NotDefault    : This is not a default build platform
#                                           --Uses=xxx      : All use another platform
#
#                   platforms           One or more root platforms, with options
#                                       The platform is macro expanded.
#                                       Options may be a part of the platform or
#                                       distinct.
#
# Returns         :
#

sub BuildProduct
{
    my( $product, @arguments ) = @_;
    my $notdefault = 0;
    my @uses = ();

    Debug( "BuildProduct($product, @arguments)" );
    Error( "BuildProduct must appear before BuildName()..." )
        if ( $BUILDNAME ne "" );

    #   Parse attributes
    #
    my( @attrs ) = split( ',', $product );

    $product = "";
    foreach ( @attrs ) {
        if ( /^--/ ) {
            if ( /^--NotDefault$/ ) {
                $notdefault++;

            } elsif ( /^--Uses=(.*)/ ) {
                UniquePush (\@uses, $1);

            } else {
                Warning( "BuildProduct() unknown attribute: $_ -- ignored" );
            }

        } else {
            Error( "BuildProduct() multiple product specifications not allowed" )
                if ( $product ne "" );
            $product = $_;
        }
    }

    #
    #   Sanity test
    #
    Error( "BuildProduct() missing product specifications" )
        if ( $product eq "" );

    Error( "BuildProduct() product '$product' contains invalid characters" )
        if ( $product ne quotemeta( $product ) );

    Error( "BuildProduct() duplicate product '$product'" )
        if ( $BUILDPRODUCT{ $product } );

    Error( "BuildProduct() duplicate alias '$product'" )
        if ( $BUILDALIAS{ $product } );

    #
    #   Expand the user specified targets to allow the use of BuildAlias
    #   The (bad) side effect of this is that target options get reorganised
    #       PLATFORM,--Uses=ANOTHER  ==> PLATFORM --Uses=ANOTHER
    #
    #   Insert markers(++) into @aruments to mark when to process collected data
    #   Insert before each PLATFORM and at the end of the list
    #   platform specifier or the end of the list. Scan the arguments
    #
    @arguments = ExpandPlatforms( @arguments );
    my @new_args;
    foreach  ( @arguments )
    {
        push (@new_args, '++') unless ( m/^--/ );
        push (@new_args, $_ );
    }
    push (@new_args, '++');
    shift @new_args if $new_args[0] eq '++';

    my @targs = ();
    my $target;
    my @tuses = @uses;
    foreach my $arg ( @new_args )
    {
        #
        #   Collect per-platform arguments
        #
        if ( $arg =~ /^--Uses=(.*)/ ) {
            UniquePush (\@tuses, $1);
            next;

        } elsif ( $arg =~ /^--/ ) {
            push @targs, $arg;
            next;
        }

        #
        #   Collect target(platform) name
        #
        unless ( $arg eq '++' )
        {
            $target = $arg;
            Error( "BuildProduct() cannot create a product based on a GENERIC platform" )
                if ( $target eq "GENERIC" );
            next;
        }

        #
        #   Infer a BuildPlatform
        #   Do not provide a platform name. This will be created later when the
        #   full name is known - or can be calculated.
        #
        CreateBuildPlatformEntry('BuildProduct', $notdefault, $product, $target, \@tuses, \@targs );

        @targs = ();
        @tuses = @uses;
        $target = undef;
    }
}

#-------------------------------------------------------------------------------
# Function        : CreateBuildPlatformEntry
#
# Description     : Internal routine to create the Build Entry
#                   Single point to create a platform, whatever one of those is
#
# Inputs          : $fname                  - Name of invoking directive
#                   $notdefault             - True if the platform is not to be added to the
#                                             list of default platforms
#                   $product                - Optional product name
#                   $target                 - Target platform name
#                   $pUses                  - Ref to an array of 'Uses'
#                   $pArgs                  - Ref to an array of platform arguments
#
# Returns         :
#

my $have_generic;
my $have_no_generic;
sub CreateBuildPlatformEntry
{
    my ($fname, $notdefault, $product, $target, $pUses, $pArgs ) = @_;
    my %buildentry;
    my $platform;

    #
    #   Detect GENERIC platform
    #       Generic platforms force a build for all
    #       Don't mix generic with others
    #
    if ( $target eq "GENERIC" )
    {
        $All = 1;
        $have_generic = 1;
    }
    else
    {
        $have_no_generic = 1;
    }
    Error("Cannot use a 'GENERIC' platform with any other platform names")
        if ( $have_generic && $have_no_generic );
        

    #
    #   Create a basic BUILDINFO entry
    #
    $buildentry{FNAME} = $fname;
    $buildentry{NOT_DEFAULT} = $notdefault;
    $buildentry{PRODUCT} = $product;
    $buildentry{TARGET} = $target;
    $buildentry{BASE} = $target;
    $buildentry{USES} = [ @$pUses ] if $pUses;
    foreach ( @$pArgs )
    {
        if ( m~^--Alias=(.+)~ ) {
            push @{$buildentry{USERALIAS}}, split(',',$1);
        }
        else{
            push @{$buildentry{ARGS}}, $_;
        }
    }

    #
    #   Allow per-platform processing to be alter the basic information
    #   Special processing may be perform to extend the information
    #   Allows special processing to be enabled on a per-target basis
    #
    #   There are three forms of processing that have been allowed for:
    #       1) None:        There is not platform specific extension
    #       2) Basic:       The extension will add or extend build information
    #       3) Advanced:    The extension will generate additional build information
    #                       structures.
    #

    #
    #   Locate the optional PLATFORM configuration file
    #   If it does exist then it can alter build-time information
    #
    if ( my $build_cfg = Require( "$::GBE_CONFIG/PLATFORM", "$target.cfg"  ) )
    {
        Verbose ("Processing(new) Platform Configuration file: $build_cfg");

        #
        #   Create package name with an uppercase target
        #   Target should be UC, but under windows its not detected
        #   at this time
        #
        my $package_name = uc($target) . '_Build';

        #
        #   Ensure that the CFG is correclt formed
        #       Perhaps the package that it implements was misnamed
        #
        Error ("INTERNAL: $target.cfg does not satisfy API " )
            unless ( $package_name->can('new_platform') || $package_name->can('add_platform') );

        if ( $package_name->can('new_platform') )
        {
            Verbose ("Processing(new) Platform Configuration: $package_name");
            $package_name->new_platform( \%buildentry );
        }
        else
        {
            Debug ("Processing(new) Platform Configuration: $package_name. 'new_platform' function not found");
        }
    }

    #
    #   Add the basic entry into the build system, unless its been
    #   flagged as a TEMPLATE
    #
    AddBuildPlatformEntry (\%buildentry )
        unless ( $buildentry{TEMPLATE} );
}


#-------------------------------------------------------------------------------
# Function        : AddBuildPlatformEntry
#
# Description     : Internal routine to add a Build Entry into the build system
#                   This function MAY be called from the build extensions
#
#                   NOTES:
#                   No processing of the info structure is performed. This MUST
#                   have been done before now.
#
#                   Additional information may be added to the structure.
#
#
# Inputs          : $pInfo              - Reference to a BuildInfo structure
#
# Returns         : Nothing
#
sub AddBuildPlatformEntry
{
    my ($pInfo) = @_;
    my $fname = $pInfo->{FNAME};

    #
    #   Locate the optional PLATFORM configuration file
    #   If it does exist then it can extend build-time information
    #
    my $target = $pInfo->{TARGET};

    #
    #   Yukky Kludge
    #   JATS has a mechanism whereby packages can create new platforms
    #   Luckily this has only been done for LMOS - don't every do it again
    #   One problem is that we can't validate the target name at this point
    #   in time: as the packages are loaded much later.
    #
    #   Kludge. Assume that a leading LMOS_ can be removed when determing
    #           validity of the target platform.
    #
    my $base_target = $target;
    $base_target =~ s~^LMOS_~~;

    #
    #   Ensure target is known to JATS
    #   Remove unknown targets from the build. Create a list of unknown
    #   targets and report them later.
    #
    #   If there are signs that the target has been processed, then it may be
    #   an alias that has not been expanded.
    #
    #   One result will be that alias platforms, such as DEVLINUX, that don't
    #   expand on WIN32 will be shown as DEVLINUX and not its components.
    #
    unless ( $pInfo->{NOT_AVAILABLE} || exists $BUILDINFO{$target} )
    {
        unless ( Exists( "$::GBE_CONFIG/PLATFORM", $base_target  ) )
        {
            UniquePush (\@BUILD_BADNAME, $target );
            $pInfo->{NOT_AVAILABLE} = 1;
        }
    }

    #
    #   Mark as NOT_AVAILABLE platforms that are not available on the machine
    #
    unless ($pInfo->{NOT_AVAILABLE} )
    {
        $pInfo->{NOT_AVAILABLE} = 1
            unless ( PlatformConfig::checkBuildAvailability( $base_target ) );
    }

    unless ($pInfo->{NOT_AVAILABLE} )
    {
        my $target_cfg = $pInfo->{TARGET_CFG} || $target ;
        if ( my $build_cfg = Require( "$::GBE_CONFIG/PLATFORM", "${target_cfg}.cfg"  ) )
        {
            Verbose ("Processing(add) Platform Configuration file: $build_cfg");
            my $package_name = "${target_cfg}_Build";

            if ( $package_name->can('add_platform') )
            {
                Verbose ("Processing(add) Platform Configuration: $package_name");
                $package_name->add_platform( $pInfo );
            }
            else
            {
                Debug ("Processing(add) Platform Configuration: $package_name. 'add_platform' function not found");
            }
        }
    }

    #
    #   If a product name has been provided then the platform is a product
    #   and will need additional processing
    #
    if ( $pInfo->{PRODUCT} )
    {
        #
        #   Create the platform name. Derived from the product and the target
        #
        $pInfo->{PLATFORM} = $pInfo->{PRODUCT} . '_' . $pInfo->{TARGET};

        #
        #   Remember the product name
        #
        $BUILDPRODUCT{ $pInfo->{PRODUCT} } = 1;

        #
        #   Add platform name to the alias explansion being created
        #   Allows the user to reference the entire FAMILY of platforms
        #
        HashJoin( \%BUILDALIAS, ' ', $pInfo->{PRODUCT}, $pInfo->{PLATFORM} );

        #
        #   Add arguments to the 'alias' based on the product
        #   Ensure they don't make it any further
        #
        HashJoin( \%BUILDALIAS, ' ', $pInfo->{PRODUCT}, @{$pInfo->{ARGS}} ) if ( $pInfo->{ARGS}  );
        $pInfo->{ARGS} = undef;

        #
        #   Create an element to assist in creating %ScmBuildProducts
        #
        $pInfo->{ISPRODUCT} = 1;
        $BUILDPRODUCT_PARTS{$pInfo->{PLATFORM}} = "$pInfo->{PRODUCT},$pInfo->{TARGET}";
    }
    else
    {
        $pInfo->{PRODUCT} = $pInfo->{TARGET};
        $pInfo->{PLATFORM} = $pInfo->{TARGET};
    }

    #---------------------------------------------------------------------------
    #   All the hard work has been done
    #   We now know the platforms full name
    #
    #   Ensure that the target platform has not been been specified
    #   Perhaps this should be an error
    #
    my $platform = $pInfo->{PLATFORM};

    if ( defined ( $BUILDINFO{$platform}) )
    {
        Warning( "$fname() duplicate platform '$platform' -- ignored" )
            unless $Clobber;
        return;
    }

    #
    #   Add platform (tag) to various lists
    #
    UniquePush( \@BUILDPLATFORMS, $platform );
    UniquePush( \@DEFBUILDPLATFORMS, $platform ) unless ( $pInfo->{NOT_DEFAULT} );

    #
    #   Create a simple alias if requested
    #   Used if a platform creates multiple entires
    #
    if ( $pInfo->{ALIAS} )
    {
        HashJoin( \%BUILDALIAS_DELAY, ' ', $_, $platform )
            foreach ( ArrayList($pInfo->{ALIAS}) );
    }

    if ( $pInfo->{USERALIAS} )
    {
        HashJoin( \%BUILDALIAS_DELAY, ' ', $_, $platform )
            foreach ( ArrayList($pInfo->{USERALIAS}) );
    }
    
    #
    #   Create a HARDWARE type alias if requested
    #   ie: SOLARIS_SPARC or SOLARIS_X86
    #
    if ( $pInfo->{HARDWARE} )
    {
        HashJoin( \%BUILDALIAS_DELAY, ' ',  $pInfo->{BASE} . '_' . $pInfo->{HARDWARE}, $platform );
    }

    #
    #   Create the 'parts' of the platform. This is a list of unique
    #   bits to search. It will consist of:
    #       [0]     - platform
    #       [1]     - product
    #       ...     - Uses bits ...
    #       [last]  - target
    #
    my @parts;

    if ( $pInfo->{USES_FIRST} )
    {
        UniquePush (\@parts, @{$pInfo->{USES_FIRST}} );
    }

    UniquePush (\@parts, $platform);

    #
    #   Include all the product extensions
    #
    if ( $pInfo->{ISPRODUCT}  )
    {
        UniquePush (\@parts, map {+ "$pInfo->{PRODUCT}_${_}" } @{$pInfo->{USES}});
        UniquePush (\@parts, map {+ "$pInfo->{PRODUCT}_${_}" } @{$pInfo->{ALSO_USES}});
        UniquePush (\@parts, $pInfo->{PRODUCT} );
    }

    #
    #   Add in non-product expanded parts
    #
    UniquePush (\@parts, @{$pInfo->{EXTRA_USES}});

    #
    #   Create a structure to assist in search path resolution
    #   The order is important. It sets the include search order for include
    #   files and libraries
    #   If A uses B then search in A_B, A, B
    #       ie: GAK uses MOS68K Search stuff in GAK_MOS68K, GAK, MOS68K
    #
    #       Usage:  OBFTP uses TP5 on MOSCF(target)       (BuildProduct)
    #       Expansion: OBFTP, TP5_MOSCF, TP5
    #
    #       Usage: VS2003(target) also uses WIN32(uses)     (BuildPlatform)
    #       Expansion: VS2003, VS2003_WIN32, WIN32
    #
    if ( $pInfo->{ISPRODUCT}  )
    {
        UniquePush (\@parts, map {+ "${_}_$pInfo->{TARGET}", $_, $pInfo->{TARGET}} @{$pInfo->{USES}});
        UniquePush (\@parts, map {+ "${_}_$pInfo->{TARGET}", $_, $pInfo->{TARGET}} @{$pInfo->{ALSO_USES}});
    }
    else
    {
        UniquePush (\@parts, map {+ "$pInfo->{TARGET}_${_}", $pInfo->{TARGET}, $_} @{$pInfo->{USES}});
        UniquePush (\@parts, map {+ "$pInfo->{TARGET}_${_}", $pInfo->{TARGET}, $_} @{$pInfo->{ALSO_USES}});
    }

    #
    #   Finally - the target
    #
    UniquePush (\@parts, $pInfo->{TARGET} );

    #
    #   Save the PARTS
    #   Also saved as BUILDPLATFORM_PARTS for interface with older versions
    #   of the deployments scripts.
    #
    $pInfo->{PARTS} = \@parts;

    #
    #   Add any arguments to the platforms argument list
    #
    PlatformArgument( $platform, @{$pInfo->{ARGS}} ) if ( $pInfo->{ARGS} );

    #
    #   Clean up and save
    #
    delete $pInfo->{TEMPLATE};
    delete $pInfo->{FNAME};
    $BUILDINFO{$platform} = $pInfo;
#    DebugDumpData("BUILDINFO", \%BUILDINFO );
}


sub BuildArgument
{
    my( $platform, @arguments ) = @_;
    my( @platforms );

    Debug( "BuildArgument($platform, @arguments)" );

    Error( "BuildArgument must appear before BuildName()..." )
        if ( $BUILDNAME ne "" );

    #
    #   Allow a wildcard to apply a single argument to all platforms
    #   Should only be used AFTER all the platforms have been specified
    #
    if ( $platform eq '*' )
    {
        @platforms = @BUILDPLATFORMS;          # Simple Wildcard
    }
    else
    {
        @platforms = ExpandPlatforms( $platform );  # aliasing
    }

    foreach my $platform ( @platforms )
    {
        next if ( $platform =~ /^--/ );         # argument, ignore

        PlatformArgument( $platform, @arguments );
    }
}


sub BuildPlatforms
{
    my( @arguments ) = @_;
    my $fname = "BuildPlatforms";

    Debug( "BuildPlatforms(@arguments)" );

    Error( "BuildPlatforms must appear before BuildName()..." )
        if ( $BUILDNAME ne "" );

    #
    #   Expand the user specified platforms to allow the use of BuildAlias
    #   The (bad) side effect of this is that platform options get reorganised
    #       PLATFORM,--Uses=ANOTHER  ==> PLATFORM --Uses=ANOTHER
    #
    #   Insert markers(++) into @aruments to mark when to process collected data
    #   Insert before each PLATFORM and at the end of the list
    #   platform specifier or the end of the list. Scan the arguments
    #
    @arguments = ExpandPlatforms( @arguments );
    my @new_args;
    foreach  ( @arguments )
    {
        push (@new_args, '++') unless ( m/^--/ );
        push (@new_args, $_ );
    }
    push (@new_args, '++');
    shift @new_args if $new_args[0] eq '++';


    my $platform  = "";                         # current platform
    my $notdefault  = 0;
    my @uses = ();
    my @pargs = ();

    foreach my $arg ( @new_args )
    {
        #
        #   Extract known options
        #   Unknown options bind to the current platform
        #
        if ( $arg =~ /^--/ ) {
            if ( $arg =~ /^--NotDefault$/ ) {
                $notdefault = 1;

            } elsif ( $arg =~/^--Uses=(.*)/ ) {
                UniquePush (\@uses, $1);

            } elsif ( $arg =~/^--FunctionName=(.*)/ ) {
                $fname = $1;
                
            } else {
                push @pargs, $arg;
            }
            next;
        }
            
        #
        #   New platform. Save name for later. Collect arguments first
        #
        unless ( $arg eq '++' )
        {
            $platform = $arg;

            Error( "$fname() missing platform specification" )
                unless ( $platform );

            Error( "$fname() product '$platform' contains invalid characters" )
                unless ( $platform eq quotemeta( $platform ) );

            next;
        }

        #
        #   Create new platform
        #   Have collected name and arguments
        #
        CreateBuildPlatformEntry($fname, $notdefault, undef, $platform, \@uses, \@pargs  );

        #
        #   Reset collection variables for next platform
        #
        $platform = "";
        $notdefault  = 0;
        @uses = ();
        @pargs = ();
    }
}


#   PlatformArgument ---
#       Append an argument list to the specified platform argument list.
#       Internal use only
#..

sub PlatformArgument
{
    my( $platform, @arguments ) = @_;

    Debug( "  PlatformArguments($platform, @arguments)" );

    HashJoin( \%BUILDPLATFORMARGS, $;, $platform, @arguments )
        if ( $platform );
}

#-------------------------------------------------------------------------------
# Function        : BuildName
#
# Description     : Defines the package name and version
#
# Inputs          : build arguments
#                   Various formats are allowed for backward compatability
#                   Must support a number of different formats
#                       "name nn.nn.nn prj"
#                       "name nn.nn.nn.prj"
#
#                       "name nn.nn.nn prj", "nn.nn.nn"
#                       "name nn.nn.nn.prj", "nn.nn.nn"
#
#                       "name", "nn.nn.nn.prj"
#
#                       "name", "nn.nn.nn", "prj", --RelaxedVersion
#
# Returns         : Nothing
#
sub BuildName
{
    my( @arguments ) = @_;
    my $relaxed_version_name = 0;
    my @args;

    Debug( "BuildName(@arguments)" );

    Error( "Platform(s) not defined.",
            "BuildAlias, BuildProduct and BuildPlatform directives must be defined prior to BuildName()." )
        unless( scalar @BUILDPLATFORMS );

#.. Parse arguments
#.
    my $build_info = parseBuildName( @arguments );

    $BUILDNAME_PACKAGE = $build_info->{BUILDNAME_PACKAGE};
    $BUILDNAME_VERSION = $build_info->{BUILDNAME_VERSION};
    $BUILDNAME_PROJECT = $build_info->{BUILDNAME_PROJECT};
    $BUILDNAME_SUFFIX  = $BUILDNAME_PROJECT ? '.' . $BUILDNAME_PROJECT : '';

    $BUILDNAME         = $build_info->{BUILDNAME};
    $BUILDVERSION      = $build_info->{BUILDVERSION};

    $DEPLOY_PATCH      = $build_info->{DEPLOY_PATCH} || 0;

    #
    #   Clobber processing done after values have been accumulated
    #   as they may be used later
    #
    return if ( $Clobber );
    ToolsetFile('build.log');
    ToolsetFile('ChangeLog', 'ChangeLog.bak') if ( $ScmHost eq "Unix" );
    
    #
    #   Determine type of sandbox
    #
    $sandbox_exact = ( -f $::GBE_DPKG_SBOX . '/.exact' )
        if ( $::GBE_DPKG_SBOX );
    
#.. Create the ChangeLog
#
    if ( -d "CVS" )                             # CVS support subdir
    {
        System( "$::GBE_PERL $::GBE_TOOLS/cvs2cl.pl --tags --branches --revisions --day-of-week" )
            if ( $Nolog == 0 && $ScmHost eq "Unix" );
    }


#.. Create build.log summary information
#
    my ($sep) = "\n".(" " x 11) . ". ";

    Log( "\nBuild configuration (version $::GBE_VERSION)" );
    Log( "Name ....... $BUILDNAME ($ScmHost)" );
    Log( "Version .... $BUILDNAME_VERSION" );
    Log( "DeployPatch. $DEPLOY_PATCH" ) if ($DEPLOY_PATCH);
    Log( "Project .... $BUILDNAME_PROJECT" )if ($BUILDNAME_PROJECT);
    Log( "Project .... ****** Specifically supressed ******" )unless ($BUILDNAME_PROJECT);
    Log( "DateTime ... $::CurrentTime" );
    Log( "AutoBuild... Enabled:$::GBE_ABT" ) if defined($::GBE_ABT) ;
    Log( "Build dir... $Cwd" ) if defined($::GBE_ABT) || $::GBE_DPKG_SBOX;

    Log( "PERL ....... $::GBE_PERL" );
    Log( "BIN  ....... $::GBE_BIN" );
    Log( "TOOLS ...... $::GBE_TOOLS" );
    Log( "CONFIG ..... $::GBE_CONFIG" );
    Log( "MACHTYPE ... $::GBE_MACHTYPE" );

    Log( "PLATFORM ... " . PrintList([split(' ', $::GBE_PLATFORM)], $sep) )    if defined ($::GBE_PLATFORM);
    Log( "BUILDFILTER. " . PrintList([split(' ', $::GBE_BUILDFILTER)], $sep) ) if defined ($::GBE_BUILDFILTER);

    Log( "DPKG_STORE.. $::GBE_DPKG_STORE" );
    Log( "DPKG ....... $::GBE_DPKG" );
    Log( "DPKG_CACHE . $::GBE_DPKG_CACHE" );
    Log( "DPKG_LOCAL . $::GBE_DPKG_LOCAL" );
    Log( "DPKG_SBOX .. $::GBE_DPKG_SBOX" );
    Log( "Sandbox .... " . ($sandbox_exact ? "Exact" : "Development") );
    Log( "LocalFilter. $::GBE_SANDBOX/buildfilter") if ( $::GBE_SANDBOX && -f $::GBE_SANDBOX . '/buildfilter' );

    Log( "Platforms .. " . PrintPlatforms(\@BUILDPLATFORMS, $sep) );

    #
    #   Generate a list of platforms that are completely unknown to JATS
    #   May be the result of a user typo or a guess
    #
    if ( @BUILD_BADNAME )
    {
        Log( "Unknown Pl . " . PrintPlatforms(\@BUILD_BADNAME, $sep) );
         Warning ("The following platform names are not known to JATS", "@BUILD_BADNAME");
    }
    
    #
    #   Generate a list of active platforms
    #   Ensure that there are some active platforms
    #
    GeneratePlatformList();
    unless( @BUILD_ACTIVEPLATFORMS )
    {
        if (defined($::GBE_ABT)) {

            # Build filter on this machine prevents the package building
            # On a Build System this is not an error
            #   Create a dummy platform called NOBUILD
            #   Do not populate the interface directory with package data
            #   Flag for jmake to do very little
            #
            CreateBuildPlatformEntry('Internal', 0, undef, 'NOBUILD');
            $IgnorePkgs = 1;
            $NoBuild = 1;
            Log( "Build for .. ". PrintPlatforms(['NOBUILD - GBE_BUILDFILTER prevents any targets being built'], $sep));

        } else {
            Error( "GBE_BUILDFILTER prevents any targets being built" );
        }
    }
    else
    {
        Log( "Build for .. ". PrintPlatforms(\@BUILD_ACTIVEPLATFORMS, $sep));
    }

    #
    #   Generate an error if nothing can be done because the GBE_PLATFORM
    #   masks any useful operation.
    #
    if ( $::GBE_PLATFORM )
    {
        my @MAKE_PLATFORMS;
        my %active_platforms;

        #
        #   Create a hash of active platforms based on the array of
        #   active platforms to simplify testing
        #
        $active_platforms{$_} = 1 foreach ( @BUILD_ACTIVEPLATFORMS  );

        unless ( defined($active_platforms{GENERIC}) )
        {
            foreach  ( split( ' ', $::GBE_PLATFORM) )
            {
                push @MAKE_PLATFORMS, $_
                    if ( $active_platforms{$_} );
            }

            Error ("The GBE_PLATFORM filter prevents any targets being made",
                   "GBE_PLATFORM: $::GBE_PLATFORM" ) unless ( @MAKE_PLATFORMS );

            Log( "Make for ... ". PrintPlatforms(\@MAKE_PLATFORMS, $sep));
        }

    }

    return 1;
}


sub BuildPreviousVersion
{
    my( $version ) = shift;

    $BUILDPREVIOUSVERSION = $version;
    Log( "Previous Version ... $BUILDPREVIOUSVERSION" );

    return 1;
}


sub BuildInterface
{
    my( $ifdirname ) = @_;


    #
    #   Clobber the directory - at the end.
    #
    if ( $Clobber )
    {
        #
        #   If this Interface directory contains the Dpackage.cfg file
        #   then JATS has created DPACKAGE and it needs to be clobbered
        #   Flag that it needs to be done later - when we know where it is
        #
        $DeleteDPACKAGE = 1 if ( -f "$ifdirname/Dpackage.cfg" );

        push @CLOBBERDIRS, $ifdirname;
        return;
    }

    #
    #   In AutoBuildTool mode the entire interface directory
    #   will be deleted. This allows the build to be retried
    #
    if (  defined($::GBE_ABT) )   # clobber mode ?
    {
        if ( -d $ifdirname )
        {
                RmDirTree( $ifdirname );
        }
    }

    if ( $ifdirname eq "local" ) {
        mkpath ( "$ifdirname/inc" );
        $BUILDLOCAL = "local";

    } else {
        mkpath ( "$ifdirname/include" );
        $BUILDINTERFACE = $ifdirname;
        $::ScmInterface = $ifdirname;
    }
    mkpath ( "$ifdirname/bin" );
    mkpath ( "$ifdirname/lib" );

    Log( "Interface .. $ifdirname" );
    return 1;
}


sub BuildDirTree
{
    my( $dirfile, $dirhead ) = @_;
    my( $dirname, $c );

    $dirhead = '.'
        unless defined( $dirhead );

    if ( $Clobber )                             # clobber mode ?
    {
        push @CLOBBERDIRS, $dirhead unless $dirhead eq '.';
        return;
    }

    #
    #   Allow for an empty "dirfile". This will allow a directory to be created
    #   without the overhead of the file
    #
    if ( ! $dirfile )
    {
        Log( "DirTree .... $dirhead" );
        mkpath ( $dirhead );
    }
    else
    {
        Log( "DirTree .... $dirfile within $dirhead" );
        mkpath ( $dirhead );

        open( DIRFILE, '<' ,$dirfile ) ||
            Error( "cannot open '$dirfile'" );

        while( $dirname = <DIRFILE> )
        {
            chop $dirname;
            $dirname =~ s/#.*//;
            $c = $dirname =~ s/\s*(\S+).*/$1/g;

            next unless ( $c == 1 );

            if ( ! -d "$dirhead/$dirname" )
            {
                Log( "Dir ........ $dirhead/$dirname" );
                mkpath ( "$dirhead/$dirname" );
            }
        }

        close( DIRFILE );
    }
    $BUILDDIRTREE = $dirhead;
}

#-------------------------------------------------------------------------------
# Function        : IncludePkg
#
# Description     : Examine a fully specified package directory for a file
#                   that will specify packages to be included. This allows
#                   a package to be simply a package of other packages
#
#                   Internal function. Not to be used by users
#
# Inputs          : Name of the package
#                   Full directory path of the package to examine
#
# Returns         : Nothing
#
sub IncludePkg
{
    my ($name, $pkg) = @_;
    my $file = "$pkg/incpkg";

    Debug ("IncludePkg: $name, $pkg" );

    #
    #   Using a require will ensure that the package is only processed once
    #   even though the function user may be called multiple times.
    #   Also prevents recursion within included packages.
    #
    if ( -f $file  )
    {
        Log( "PackageIncludes. $name" ) unless ( $INC{$file} );
        require $file;
    }
}


sub LinkSandbox
{
    my( $name, $path, $platform ) = @_;
    return if ( $Clobber );                     # clobber mode ?

    Warning ("LinkSandbox() This directive is being deprecated.");       #Dec-2011

    Error ("LinkSandbox() expects three arguments:  @_")
        unless ( $#_ == 2 );

    Error ("LinkSandbox not allowed in ABT build","It can only be used in a Development Environment")
        if ( $::GBE_ABT );

    Debug( "LinkSandbox:" );
    Debug( "Package:   $name" );
    Debug( "Version:   $path" );

    DataDirective("LinkSandbox");               # This directive allowed here

    if ( $IgnorePkgs )
    {
        Log( "LinkSandbox. $name ($path) - Ignored" );
        return;
    }

#
#   If GBE_BUILDFILTER exists, Import 'user' platform
#   specification and filter against the BUILD_ACTIVEPLATFORMS.
#
    Log( "LinkSandbox. $name ($path)" );

    if ( ! -d $path )                           # sandbox exists ?
    {
        Log( "WARNING .... Sandbox $path not available" );
    }
    else
    {
        my @platforms;
        if ( !defined($platform) || $platform eq "*" ) {
            @platforms = @BUILD_ACTIVEPLATFORMS;
        } else {
            @platforms = ExpandPlatforms( split( ',', $platform ) );
        }

        $path = Realpath( $path );
        IncludePkg ( $name, $path );
        foreach my $platform ( @platforms )
        {
            LinkEntry( $platform, $path, $name, "!sandbox", 1, 1 );
        }
    }
}


#-------------------------------------------------------------------------------
# Function        : LinkPkgArchive
#
# Description     : Include an external package into the build sandbox
#                   by extending the compiler and linker search paths to
#                   include suitable directories found in the package
#
# Inputs          : package name
#                   package version
#
sub LinkPkgArchive
{
    my( $name, $version ) = @_;

    return BuildPkgArchive( @_ )
        if ( $ForceBuildPkg );                  # Forcing interface directory
    return if ( $Clobber );                     # clobber mode ?

    Debug( "LinkPkgArchive:" );
    Debug( "Name:      $name" );
    Debug( "Version:   $version" );

    DataDirective("LinkPkgArchive");            # This directive allowed here

    if ( $IgnorePkgs )
    {
        Log( "LinkPkgArchive .. $name ($version) - Ignored" );
        return;
    }

    #
    #   Ensure that we have do not have multiple definitions
    #
    if ( PackageEntry::Exists( $name, $version ) )
    {
        Log( "Duplicate Package: $name, $version. Duplicate entry ignored" );
        return;
    }

    if ( $Cache && $::GBE_DPKG_CACHE )
    {
        my $mode = ($Cache > 1) ? "-refresh" : "";
        Log( "LinkPkgArchive .. $name ($version) Update Cache" );
        System('--NoExit', "$::GBE_PERL $::GBE_TOOLS/cache_dpkg.pl $mode -quiet $name/$version" );
    }

    #
    #   Locate the package ONCE
    #
    Log( "LinkPkgArchive .. $name ($version)" );
    my ($pkg, $local ) = PackageLocate( $name, $version );
    if ( $pkg )
    {
        #
        #   Generate package rules for each active platform
        #
        IncludePkg ( $name, $pkg );
        foreach my $platform ( @BUILD_ACTIVEPLATFORMS, '--' )
        {
            LinkEntry( $platform, $pkg, $name, $version, 0, $local );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageLocate
#
# Description     : Locate a package
#                   Once located a package will be processed for each
#                   platform, but it need only be located ONCE
#
# Inputs          : package name
#                   package version
#
# Returns         : path to the package
#                   local       1 - From local package repository
#
sub PackageLocate
{
    my ($name, $uversion ) = @_;
    my $pkg;
    my $local = 1;
    my $sandbox = ! $sandbox_exact;
    my $isa_cache = 0;
    my $version;

    Debug( "PackageLocate: ($name/$uversion)" );

    #
    #   Look in each package archive directory
    #
    foreach my $dpkg ( split( $::ScmPathSep, $::GBE_DPKG_SBOX),
                       '--NotSandbox',
                       split( $::ScmPathSep, $::GBE_DPKG_LOCAL),
                       '--NotLocal',
                       split( $::ScmPathSep, $::GBE_DPKG_CACHE),
                       '--NotCache',
                       split( $::ScmPathSep, $::GBE_DPKG),
                       split( $::ScmPathSep, $::GBE_DPLY),
                       split( $::ScmPathSep, $::GBE_DPKG_STORE) )
    {

        #
        #   Detect various tags that have been placed in the search list
        #   to flag the end of the sandbox search and the end of the local
        #   archive search
        #
        if ( $dpkg eq '--NotSandbox' )
        {
            $sandbox = 0;
            next;
        }
        if ( $dpkg eq '--NotLocal' )
        {
            $local = 0;
            $isa_cache = 1;
            next;
        }
        if ( $dpkg eq '--NotCache' )
        {
            $isa_cache = 0;
            next;
        }
        

        #
        #   If we are playing in a sandbox, then the version number is
        #   not used. The Package suffix is still used so that we can
        #   differentiate sysbasetypes.xxxxx.mas and sysbasetypes.xxxxx.syd
        #
        if ( $sandbox )
        {
            my ($pn, $pv, $ps ) = SplitPackage ($name, $uversion );
            $version = 'sandbox';
            $version .= '.' . $ps if ( $ps );
        }
        else
        {
            $version = $uversion;
        }

        #
        #   Alias support
        #   If the 'version' is '!current' then use a version of:
        #       current
        #       current_<USER_NAME>
        #   This is an old mechanism whose use should not be encouraged
        #
        #..
        if ( $version eq "!current" )
        {
            Error ("Use of !version is not allowed in ABT build")
                if ( $::GBE_ABT );

            $pkg = "$dpkg/$name/current";       # current
            $pkg = "$dpkg/$name/current.lnk"
                if ( -e "$dpkg/$name/current.lnk" );

                                                # USER specific current
            EnvImport( "USER" );
            $pkg = "$dpkg/$name/current_$::USER"
                if ( -e "$dpkg/$name/current_$::USER" );

            $pkg = "$dpkg/$name/current_$::USER.lnk"
                if ( -e "$dpkg/$name/current_$::USER.lnk" );
        }
        else
        {                                       # standard
            $pkg = "$dpkg/$name/$version";
            $pkg = "$dpkg/$name/$version.lnk"
                if ( -e "$dpkg/$name/$version.lnk" );
        }

        #
        #   Using a soft link
        #   Emulate a link in software. The link file contains one line
        #   which is the real pathname of the package
        #
        if ( $pkg =~ m~(.*)\.lnk$~  )
        {
            #
            #   Warn the user if both a link and a real directory
            #   are both present - the link may well be incorrect
            #
            my $non_link = $1;
            Warning ("Suspect package link: $pkg",
                     "Both a link and a package where found - using the link" )
                                                            if ( -d $non_link );

            Debug( "           link found -> $pkg" );
            my $link_src = $pkg;
            open( LNKFILE, '<', "$pkg" ) || Error( "cannot open '$pkg'" );
            $pkg = <LNKFILE>;                   # real path
            close( LNKFILE );
            $pkg = '' unless ( $pkg );
            $pkg =~ s~\s+$~~;;
            $pkg =~ s~^GBE_SANDBOX/~$::GBE_SANDBOX/~;

            unless ( -d $pkg )
            {
                Error ("Broken link: $pkg",
                       "Source link: $link_src",
                       "Try deleting the .lnk file" ) unless ( $NoPackageError );

                Warning ("Package not available. Broken link: $pkg");
            }
        }

        Debug( "           searching $pkg" );

        #   Does the package directory exist?
        #   Terminate the directory name with a "/" to detect hidden spaces
        #..
        $pkg =~ s~//~/~g;
        next unless ( -d "$pkg/" );             # exists ?

        #
        #   If the package exists within the dpkg_archive cache then mark the
        #   version as having been used. Used by cache cleanup algorithms
        #
        if ( $isa_cache  )
        {
            TouchFile ( "$pkg/used.cache", "Marks the cache copy as being used");
        }

        #
        #   Use the first suitable package found
        #..

        Debug( "           importing $pkg" );
        return $pkg, $local;
    }

    #
    #   Package not found
    #   This is an error, although it can be bypassed
    #
    Error ("Required package not found: '$name/$version'" ) unless ( $NoPackageError );

    Log( "WARNING .... Package not available: '$name/$version'" );
    return;
}


#-------------------------------------------------------------------------------
# Function        : LinkEntry
#
# Description     : Scan a package an locate platform specific directories
#                   Create data structures to capture the information
#                   This function is used by LinkPkgArchive and LinkSandbox
#                   to perfom the bulk of package inclusion work.
#
# Inputs          : platform being processed
#                   path to the package
#                   name of the package
#                   version of the package
#                   sandbox support (non-zero)
#                   local package
#
sub LinkEntry
{
    my( $platform, $pkg, $name, $version, $sandbox, $local ) = @_;
    my( $entry );

    #   Create entry record
    #
    #..
    $entry = PackageEntry::New( $pkg, $name, $version, $sandbox, 'link', $local );

    #   Populate includes:
    #
    #   - include/$platform                 (eg include/solaris)
    #   - inc/$platform                     (eg inc/solaris)
    #   - include.$platform                 (eg include.solaris)
    #   - inc.$platform                     (eg inc.solaris)
    #   - include                           (eg include)
    #   - inc                               (eg inc)
    #
    #   plus, product specialisation directores
    #
    #   eg. BuildProduct( 'IDFC', 'WIN32' );
    #
    #   - inc/IDFC_WIN32                    <- derived platform
    #   - inc/IDFC                          <- product
    #   - inc/WIN32                         <- target
    #..
    my $parts = $BUILDINFO{$platform}{PARTS};

    foreach my $part ( @$parts )
    {
        $entry->RuleInc( "/include." . $part ) if ( !$sandbox );
        $entry->RuleInc( "/inc." . $part )     if ( !$sandbox );
        $entry->RuleInc( "/include/" . $part ) if ( !$sandbox );
        $entry->RuleInc( "/inc/" . $part );
    }

    #
    #   Also search the root include directory - last
    #
    $entry->RuleInc( "/include" )               if ( !$sandbox );
    $entry->RuleInc( "/inc" );

    #   Populate libraries:
    #
    #   - lib/lib.$platform[D|P]            (eg lib/lib.sparcD)
    #   - lib/$platform[D|P]                (eg lib/lib.sparc)
    #   - lib.$platform[D|P]                (eg lib.sparcD)
    #
    #   plus, product specialisation directores
    #
    #   eg. BuildProduct( 'IDFC', 'WIN32' );
    #
    #   - lib/IDFC_WIN32                    <- derived platform
    #   - lib/IDFC                          <- product
    #   - lib/WIN32                         <- target
    #..
    $parts = $BUILDINFO{$platform}{PARTS};

    foreach my $part ( @$parts )
    {
        $entry->RuleLib("/lib" . ".$part" )     if ( !$sandbox );
        $entry->RuleLib("/lib" . "/lib.$part" ) if ( !$sandbox );
        $entry->RuleLib("/lib" . "/$part" );
    }

    #
    #   Some extra places to search
    #   None. This is good as it indicates that all locations are described in PARTS
    #
    #   Do NOT search in /lib. There are no libraries that work on all platforms
    #   Libraries are binaries!
    #
    #    $entry->RuleLib( "/lib" );

    #   Tools:
    #
    #   Tools provide an extensible search path for tools and
    #   utilities used to build programs. These are tools that
    #   are executable on the current host machine and are
    #   independent of the toolset.
    #
    #..
    $entry->ExamineToolPath();
    $entry->ExamineThxPath($platform);
    $entry->Cleanup();                          # cleanup tables

    #
    #   Add the package entry to the array of such entries for
    #   the current platform. Maintain the discovery order
    #
    #..
    push ( @{$PKGRULES{$platform}}, $entry );
}


#-------------------------------------------------------------------------------
# Function        : BuildPkgArchive
#
# Description     : Include an external package into the build sandbox
#                   by copying the packages files into the interface directory
#
# Inputs          : package name
#                   package version
#
sub BuildPkgArchive
{
    my( $name, $version ) = @_;

    return if ( $Clobber );                     # clobber mode ?

    Debug( "BuildPkgArchive:" );
    Debug( "Name:      $name" );
    Debug( "Version:   $version" );

    DataDirective("BuildPkgArchive");           # This directive allowed here

    if ( $IgnorePkgs )
    {
        Log( "BuildPkgArchive . $name ($version) - Ignored" );
        return;
    }
    
    #
    #   Ensure that we have do not have multiple definitions
    #
    if ( PackageEntry::Exists( $name, $version ) )
    {
        Log( "Duplicate Package: $name, $version. Duplicate entry ignored" );
        return;
    }

    if ( $Cache && $::GBE_DPKG_CACHE )
    {
        my $mode = ($Cache > 1) ? "-refresh" : "";
        Log( "BuildPkgArchive . $name ($version) Update Cache" );
        System('--NoExit', "$::GBE_PERL $::GBE_TOOLS/cache_dpkg.pl $mode -quiet $name/$version" );
    }

    #
    #   Locate the package
    #   Use the first instance of the package that it found
    #
    Log( "BuildPkgArchive . $name ($version)" );
    my ( $pkg, $local ) = PackageLocate( $name, $version );
    if ( $pkg )
    {
        #
        #   Create a Package Entry
        #
        my $entry = PackageEntry::New( $pkg, $name, $version, 0, 'build', $local );

        #
        #   Determine if the package needs to be installed:
        #       If the package is a 'local' package then force transfer
        #       If the user has specified --cache then force transfer
        #       If package is newer that copy, then force transfer
        #       If copy does not exist, then force a transfer
        #
        my $tag_dir = "$Cwd/$BUILDINTERFACE/BuildTags";
        my $tag_file = "$tag_dir/${name}_${version}.tag";

        my $package_installed;
        $package_installed = 1
            if ( !$local &&
                 !$Cache &&
                 !FileIsNewer( $entry->GetBaseDir('descpkg'), $tag_file ) );

        #
        #   Determine the package format and use the appropriate installer
        #   Supported formats
        #       1) Package has a descpkg file (new style)
        #       2) Package has a InstallPkg.sh file (old style)
        #       3) Package has a Install.sh file (old style is it used ??)
        #
        if ( $package_installed ) {
            Verbose ("Package already installed: $name, $version");
            
        } else {
            Log( "                . installing '$pkg'" );
            Log( "                . -> " . readlink($pkg) ) if ( -l $pkg );

            if ( -e "$pkg/descpkg" )
            {

                #
                #   If forcing a BuildPkg, then don't use symlinks
                #   to files in dpkg_archive
                #
                my @opts;
                push @opts, "--NoSymlinks" if ( $ForceBuildPkg );
                push @opts, "--AllowOverWrite" if ( $local );

                #
                #   Determine all the Platforms, Products and Targets
                #   that need to be installed
                #
                my $arglist = GenerateInstallArgumentList();
                System( "cd $pkg; $::GBE_PERL $::GBE_TOOLS/installpkg.pl $Cwd/$BUILDINTERFACE $Cwd @opts $arglist");
                Error( "Package installation error" ) if ( $? != 0 );
            }
            elsif ( -e "$pkg/InstallPkg.sh" )
            {
                System( "(cd $pkg; ./InstallPkg.sh $Cwd/$BUILDINTERFACE $Cwd)" );
            }
            elsif ( -e "$pkg/Install.sh" )
            {
                System( "(cd $pkg; ./Install.sh $Cwd/$BUILDINTERFACE $Cwd)" );
            }
            else
            {
                Error ("Unknown package format for package $name/$version found in $pkg");
            }

            #
            #   Tag the package as installed - after it has been transferred
            #
            mkdir ( $tag_dir );
            TouchFile( $tag_file );
        }

        #
        #   Process package
        #
        IncludePkg ( $name, $pkg );

        #
        #   Complete the creation of the package entry
        #   Add the information for all platforms
        #
        $entry->Cleanup();
        for my $platform (@BUILD_ACTIVEPLATFORMS)
        {
            $entry->ExamineToolPath();
            $entry->ExamineThxPath($platform);
            push ( @{$PKGRULES{$platform}}, $entry );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : CreateInterfacePackage
#
# Description     : Create a dummy package entry to describe the Interface
#                   This is done AFTER all the BuildPkgArchive directives have
#                   been processed so that the interface directory is fully
#                   processed
#
# Inputs          : None
#
# Returns         : 
#
sub CreateInterfacePackage
{
    foreach my $platform ( @BUILD_ACTIVEPLATFORMS )
    {
        my $entry = PackageEntry::Interface( "$::Cwd/$BUILDINTERFACE" );

        #
        #   Locate include and lib bits within the interface
        #   This is much simpler than for a LinkPkgArchive as the form
        #   has been sanitized
        #
        my $parts = $BUILDINFO{$platform}{PARTS};

        foreach my $part ( @$parts )
        {
            $entry->RuleInc( "/include/" . $part );
        }
        $entry->RuleInc( "/include" );

        foreach my $part ( @$parts )
        {
            $entry->RuleLib("/lib/" . $part );
        }

        $entry->ExamineToolPath();
        $entry->ExamineThxPath($platform);
        $entry->Cleanup();

        #
        #   Add the package entry to the array of such entries for
        #   the current platform. Force it to be the first one as
        #   the interface directory will be scanned first
        #
        unshift ( @{$PKGRULES{$platform}}, $entry );
    }
}

#-------------------------------------------------------------------------------
# Function        : GenerateInstallArgumentList
#
# Description     : Generate an argument list for the installpkg.pl script
#                   The argument list is of the form
#                       --Platform:xx[:xx[:xx]] --Platform:yy[:yy[:yy]] ...
#                   Where xx is:
#                       * a 'part' of the target platform
#                         Order is: platform, product, ...  target( in that order )
#                       * --Option[=yyy]
#                        An option to be passed to the script. These are bound only
#                        to the enclosed platform.
# Inputs          :
#
# Returns         : See above
#
sub GenerateInstallArgumentList
{
    my @arglist;

    #
    #   Generate the argument list as an array
    #
    for (@BUILD_ACTIVEPLATFORMS)
    {
        my @args = '--Platform';
        push @args, @{$BUILDINFO{$_}{PARTS}};
        push @arglist, join (":" , @args );
    }

    return "@arglist";
}

#-------------------------------------------------------------------------------
# Function        : GeneratePlatformList
#
# Description     : Return a list of platforms that should particiate in this
#                   build. This is a function of
#                       1) Platforms defined in the build.pl file
#                       2) User filter defined in GBE_BUILDFILTER
#
#                   The primary use of this function is to limit the creation
#                   of makefiles to those that have supported compilers on
#                   the underlying machine.
#
#                   GBE_BUILDFILTER is a space seperated string of words
#                   Each word may be one of
#                       OPTION=TAG or OPTION=!TAG
#                       TAG or !TAG. This is the same as --TARGET=TAG
#
#                   Bare tags are taken to be TARGETS.
#
#                   Where OPTION may be one of
#                       --PLATFORM
#                       --PRODUCT
#                       --TARGET
#
#                   Special cases
#                   1) If GBE_BUILDFILTER is empty, then all available platforms are used
#                      The global $All is set, then all available platforms are used
#                   2) If the first word of GBE_BUILDFILTER is a negative filter,
#                      ie is used the !xxxx or yyy=!xxxx construct, then it is assumed
#                      that the filter will start with all available platforms
#                   3) The special word --ALL forces selection of ALL platforms
#                      and may reset any existing scanning
#                   4) GBE_BUILDFILTER is parsed left to right. It is possible to add and
#                      subtract items from the list.
#                   5) OPTIONS are case insensitive
#                      TAGS are case sensitive
#
#
# Inputs          : GBE_BUILDFILTER from the environment
#
# Returns         : An array of platforms to include in the build
#                   Maintains @BUILD_ACTIVEPLATFORMS  - the last calculated result
#                   Ensures that @DEFBUILDPLATFORMS is a subset of @BUILD_ACTIVEPLATFORMS
#
sub GeneratePlatformList
{
    #
    #   Return the cached result for speed
    #   The value need only be calculated once
    #
    unless ( @BUILD_ACTIVEPLATFORMS )
    {
        my ($platform_filter);
        my %result;
        my %part_to_platform;

        #
        #   Create a data structure to assist in the production of the platform list
        #   The structure will be a hash of hashes of arrays
        #
        #   The first level hash will be keyed by the word TARGET, PRODUCT or PLATFORM
        #   The second level of the hash will keyed by available targets, products or platforms
        #   The value of the field will be an array of platforms that match the keyword
        #
        for my $platform (keys (%::BUILDINFO))
        {
            my $pParts = $::BUILDINFO{$platform};

            #
            #   Skip platforms that are known to be unavailable on this build
            #   machine. Self configure
            #
            next if ( $pParts->{NOT_AVAILABLE} );

            my $target  = $pParts->{TARGET};
            my $product = $pParts->{PRODUCT};

            push @{$part_to_platform{'PLATFORM'}{$platform}}, $platform;
            push @{$part_to_platform{'TARGET'}  {$target}}  , $platform;
            push @{$part_to_platform{'PRODUCT'} {$product}} , $platform;
        }
        #
        #   Determine the source of the filter
        #   If the user provides one, then use it.
        #   Otherwise its taken from the environment.
        #
        #   Global build all platforms - Kill any user filter
        #
        if ( $All )
        {
            $platform_filter = "";
        }
        else
        {
            $platform_filter = "";
            $platform_filter = $::GBE_BUILDFILTER
                if ( defined($::GBE_BUILDFILTER) );
        }
        Debug( "GeneratePlatformList: Filter:$platform_filter" );

        #
        #   Detect the special cases
        #       1) No user definition - assume all platforms
        #       2) First word contains a subtractive element
        #
        my (@filter) = split( ' ', $platform_filter );

        if ( !scalar @filter || $filter[0] =~ m/^$/ || $filter[0] =~ m/!/ )
        {
            %result = %{$part_to_platform{'PLATFORM'}}
                if exists $part_to_platform{'PLATFORM'} ;
        }
#DebugDumpData( "PartToPlatform", \%part_to_platform );

        #
        #   Process each element in the user filter list
        #   Expand platforms into known aliases
        #
        for my $word (@filter)
        {
            my $platform;

            if ( $word =~ m/^--ALL/i )
            {
                %result = %{$part_to_platform{'PLATFORM'}};
            }
            elsif ( $word =~ m/^--(TARGET)=(!?)(.*)/i ||
                    $word =~ m/^--(PRODUCT)=(!?)(.*)/i ||
                    $word =~ m/^--(PLATFORM)=(!?)(.*)/i ||
                    ( $word !~ m/^-/ && $word =~ m/()(!?)(.*)/ )
                )
            {
                my $table = uc($1);
                $table = "TARGET"
                    unless ( $1 );

                #
                #   Expand PLATFORMs into known aliases
                #   Alias will expand to PLATFORMs so it won't work unless we are
                #   processing PALTFORMs.
                #
                my @taglist = ( $table eq "PLATFORM" ) ? ExpandPlatforms($3) : $3;

                #
                #   Add / Remove items from the result
                #
                for my $item ( @taglist )
                {
                    my $plist = $part_to_platform{$table}{$item};
                    for ( @{$plist})
                    {
                        if ( $2 )
                        {
                            delete $result{$_};
                        }
                        else
                        {
                            $result{$_} = 1;
                        }
                    }
                }
            }
            else
            {
                print "GBE_BUILDFILTER filter term not understood: $word\n";
            }
        }

        #
        #   Return an array of platforms to process
        #
        @BUILD_ACTIVEPLATFORMS = sort keys %result;

        #
        #   Ensure that DEFBUILDPLATFORMS is a subset of build active platforms
        #
        my @NEW_DEFBUILDPLATFORMS;
        foreach ( @DEFBUILDPLATFORMS )
        {
            push @NEW_DEFBUILDPLATFORMS, $_
                if ( exists $result{$_} );
        }
        @DEFBUILDPLATFORMS = @NEW_DEFBUILDPLATFORMS;
    }

    Debug("GeneratePlatformList: Result:@BUILD_ACTIVEPLATFORMS");
    return @BUILD_ACTIVEPLATFORMS;
}

#-------------------------------------------------------------------------------
# Function        : PrintPlatforms
#
# Description     : Petty print the specified platform list, breaking line
#                   on either a primary key change or length width >78.
#
# Returns         : Formated string
#
# Example Output :
#
#           DDU_LMOS_WIN32 DDU_LMOS_linux_armv4 DDU_LMOS_linux_i386
#           IDFC_LMOS_WIN32 IDFC_LMOS_linux_armv4 IDFC_LMOS_linux_i386
#           LMOS_WCEPSPC_ARM LMOS_WCEPSPC_EMU LMOS_WIN32 LMOS_linux_armv4
#           LMOS_linux_i386
#..
sub PrintPlatforms
{
    my ($platforms, $nl) = @_;
    my ($string) = "";                          # result

    if ( @$platforms )
    {
        my ($key_run) = 0;
        my ($pkey);                             # previous key

        #   Perform a simple formatting and determine if there is key 
        #   change greater then 1 or whether the total length exceeds 78.
        #
        #   If the line exceeds 78, the printer shall then reformat 
        #   breaking based on line length and possiblity keys.
        #
        $pkey = "";
        for my $k (sort @$platforms) 
        {
            my ($d);                            # delimitor

            if (($d = index( $k, '_' )) != index( $pkey, '_' ) ||
                    substr( $k, 0, $d ) ne substr( $pkey, 0, $d )) {
                $key_run = 1
                    if ($key_run <= 1);         # change, reset run if <= 1
            } else {
                $key_run++;                     # same primary key
            }

            $string .= " " if ($pkey);
            $string .= $k;
            $pkey = $k;
        }

        #   Reprint if required.
        #
        if (length($nl)+length($string) > 78)
        {
            my ($llen);                         # line length

            $llen = length($nl);

            $pkey = "";
            $string = "";

            for my $k (sort @$platforms)
            {
                my ($klen, $d);                 # key length, delimitor

                $klen = length($k);
                if ($pkey ne "")
                {
                    if ($llen + $klen > 78 ||
                        ($key_run > 1 && (
                            ($d = index( $k, '_' )) != index( $pkey, '_' ) ||
                            substr( $k, 0, $d ) ne substr( $pkey, 0, $d ) )) )
                    {                           # line >70 or key change
                        $string .= $nl;
                        $llen = length($nl);
                    }
                    else
                    {
                        $string .= " ";
                        $llen++;
                    }
                }
                $string .= $k;
                $pkey = $k;
                $llen += $klen;
            }
        }    
    }
    return $string;
}
#-------------------------------------------------------------------------------
# Function        : PrintList
#
# Description     : Pretty format an array to fit within 80 char line
#                   Perform wrapping as required
#
# Inputs          : $list           - Reference to an array
#                   $nl             - New line stuff.
#                                     Use to prefix new lines
#
# Returns         : string
#
sub PrintList
{
    my ($list, $nl) = @_;
    my ($string) = '';                          # result
    my $sep;

    if ( @$list )
    {
        my ($llen) = length($nl);

        for my $k (@$list)
        {
            my $klen = length($k);
            if ($llen + $klen > 78 )
            {
                $string .= $nl;
                $llen = length($nl);
            }
            else
            {
                if ( $sep )
                {
                    $string .= $sep;
                    $llen++;
                }
                else
                {
                    $sep = ' ';
                }
            }
            $string .= $k;
            $llen += $klen;
        }
    }
    return $string;
}

#-------------------------------------------------------------------------------
# Function        : BuildReleaseFile
#
# Description     : Legacy function
#                   Don't know what it was meant to do
#                   Unfortunately it is present in a lot of build.pl files
#
#                   Not well supported on all machine types
#
# Inputs          : None that are used
#
# Returns         : Undefined
#
sub BuildReleaseFile
{
}

#-------------------------------------------------------------------------------
# Function        : BuildSnapshot
#
# Description     : Legacy function
#                   Don't know what it was meant to do
#                   Unfortunately it is present in a lot of build.pl files
#
# Inputs          : None that are used
#
# Returns         : Undefined
#
sub BuildSnapshot
{
}

#-------------------------------------------------------------------------------
# Function        : BuildSrcArchive
#
# Description     : Create a source snapshot of the build source
#                   Designed to provide a source image for packaging
#                   examples
#
#                   Should be platform independent
#
#                   Creates an archive file and places it into the
#                   interface directory. The archive will be packaged
#                   automatically by the build process
#
#                   Use the 'pax' utility
#                       1) Can massage the file path such that the stored
#                          directory image contains the package name and version
#
#                   Directive can be used at any time before the BuildMake
#
#                   Will handle the existence of an auto.pl file by inserting
#                   it as build.pl.
#
# Inputs          : Options
#
#
# Returns         : 
#
sub BuildSrcArchive
{
    #
    #   If we are clobbering, then there is nothing to do
    #   The generated file is placed within the interface
    #   directory and that directory will be deleted during the clobber
    #
    return if ( $Clobber );
    DataDirective("BuildSrcArchive");

    #
    #   Currently this operation is only supported of some platforms
    #   Only supported on Unix platforms
    #   Uses the 'pax' utility
    #
    unless ( LocateProgInPath ( 'pax', '--All' ) && ( $ScmHost eq "Unix" ) )
    {
        Log( "SrcPackage . Not supported" );
        return;
    }

    #
    #   Only allow one instance of the directive
    #
    Error ("Multiple BuildSrcArchive directives not supported")
        if ( $build_source_pkg );

    #
    #   Create the name of the archive
    #       Based on the package name and version
    #       Has no spaces
    #
    my $build_name = $BUILDNAME;
    $build_name =~ s~\s+~_~g;

    #
    #   Create the archive in the interface directory
    #   Don't need to clobber it as the entire interface directory
    #   will be clobbered
    #
    $build_source_pkg = $build_name;
}

#-------------------------------------------------------------------------------
# Function        : BuildSrcArchiveBody
#
# Description     : Function to implement the body of the BuildSrcArchive
#                   operation. Will be invoked during BuildMake
#
# Inputs          : None
#
# Returns         : 
#
sub BuildSrcArchiveBody
{
    return unless ( $build_source_pkg );

    my $archive_dir = "pkg/$BUILDNAME_PACKAGE/src";
    my $archive_file = "$build_source_pkg" . '.tar';

    Log( "SrcPackage . $archive_file.gz" );
    unlink "$archive_dir/$archive_file";
    unlink "$archive_dir/$archive_file.gz";
    mkpath($archive_dir, 0, 0775);
    
    #
    #   Create a list of files and top-level dirs to add to source archive
    #   Many files are ignored
    #   Should only be executed on the first 'build' thus many internal
    #   directories will not be present
    #
    my @flist;
    my $auto_pl;
    opendir (my $tp, '.' ) or Error ("Cannot read current directory");
    while ( $_ = readdir($tp) )
    {
        next if ( m/^\.$/ );
        next if ( m'^\.\.$' );
        next if ( m'^build\.log$' );
        next if ( m'\.gbe$' );
        next if ( m'^local$' );
        next if ( m'^pkg$' );
        next if ( m/^$BUILDINTERFACE$/ );
        $auto_pl = 1, next  if ( m'^auto\.pl$' );
        next if (  m'^build\.pl$' );
        next if ( m/^$build_source_pkg$/ );
        push @flist, $_;
    }
    closedir $tp;

    #
    #   If we don't have an auto.pl, then we add the build.pl file
    #   If we do have a auto.pl - it gets tricky. Its don't after the
    #   initial pax command
    #
    unless ( $auto_pl )
    {
        push @flist, 'build.pl';
    }

    #
    #   Create the command to be executed
    #   Prefix archive paths with build_name
    #
    my @command = ( 'pax', '-w', '-f', "$archive_dir/$archive_file" );
    System( '--NoShell' , @command, '-s', "~^~$build_source_pkg/~", @flist );

    #
    #   If we have an auto.pl file, then we need to add it to the archive
    #   but it needs to be called build.pl
    #
    if ( $auto_pl )
    {
        System( '--NoShell' , @command, '-a', '-s', "~^auto.pl~$build_source_pkg/build.pl~" , 'auto.pl' );
    }

    #
    #   Must now zip the file
    #   Can't zip and append at the same time
    #
    System( '--NoShell' , 'gzip', "$archive_dir/$archive_file" );

    #
    #   Display the results
    #
    System ('--NoShell', 'pax', '-z', "-f$archive_dir/$archive_file.gz")
        if (IsVerbose (1));
}

#-------------------------------------------------------------------------------
# Function        : BuildAccessPerms
#
# Description     : Check if access/permissions setting requested...
#                   Legacy
#
#                   Don't know what it was meant to do
#                   Unfortunately it is present in a lot of build.pl files
#
# Inputs          : None that are used
#
# Returns         : Undefined
#
sub BuildAccessPerms
{
}


sub BuildSetenv
{
    push( @BUILDSETENV, @_ );
}

#-------------------------------------------------------------------------------
# Function        : DataDirective
#
# Description     : Called by data collection directives to ensure that we are
#                   still collecting data and that we have collected other data
#
# Inputs          : $dname              - Directive Name
#
# Returns         : Will error if we are not
#
sub DataDirective
{
    my ($dname) = @_;

    Error( "$dname() must appear after BuildName()...")
        if ( $BUILDNAME eq "" );

    Error( "$dname() must appear after BuildInterface()...")
        unless( $BUILDINTERFACE );

    Error( "$dname() not allowed after BuildDescpkg, BuildIncpkg, BuildVersion or BuildMake")
        if( $BUILDPHASE);
}

#-------------------------------------------------------------------------------
# Function        : StartBuildPhase
#
# Description     : Called by directives that deal with the building phases
#                   to perform common initialisation and to ensure that
#                   directives that collect data are no longer called
#
# Inputs          : last                - True: Last directive expected
#
# Returns         : May generate an error
#
sub StartBuildPhase
{
    my ($last) = @_;

    #
    #   Ensure directive is allowed
    #       $BUILDPHASE >  1     - No more directives allowed
    #       $BUILDPHASE == 1     - Allowed directive
    #
    if ( $BUILDPHASE > 1 )
    {
        my $function = (caller(1))[3];
        $function =~ s~.*::~~;
        Error ("Directive not allowed: $function","'BuildMake' must be the last directive in the build file");
    }

    #
    #   Only do it once
    #
    return if ( $BUILDPHASE  );
    $BUILDPHASE = 1;

    #
    #   If we are not performing a ForceBuild, then we don't need to continue
    #   We have updated the interface directory with BuildPkgArchive
    #   information.
    #
    TestForForcedBuild();

    #
    #   Calcuate the aliases that are being extracted from targets
    #
    Process_TargetAlias();

    #
    #   Create dummy package to describe the Interface directory
    #
    CreateInterfacePackage();

    #
    #   Sanity test the users packages
    #
    PackageEntry::SanityTest() unless $Clobber;

    #
    #   Validate the $Srcdir before its first real use
    #   This is calculated from the user directives
    #

    #.. Determine default "source" root
    #
    if ( $Srcdir eq "" )
    {
        Warning( "Both the directories 'src' and 'SRC' exist ....." )
            if ( $ScmHost eq "Unix" && -e "src" && -e "SRC" );

        if ( -e "src" ) {
            $Srcdir = "src";
        } else {
            ( -e "SRC" ) ||
                Error( "Neither the directory 'src' nor 'SRC' exist ....." );
            $Srcdir = "SRC";
        }
    }

    #
    #   Must have a valid Srcdir
    #
    Error ("Source directory not found: $Srcdir")
        unless ( $Srcdir && -d $Srcdir );

    #
    #   Create source package
    #
    BuildSrcArchiveBody();

    return $Srcdir;
}

#-------------------------------------------------------------------------------
# Function        : TestForForcedBuild
#
# Description     : If a non-forced build has been requested, then see
#                   if a build is required ( ie: build.pl modified )
#
#
# Inputs          : None
#
# Returns         : May not return
#
sub TestForForcedBuild
{
    #
    #   Always return if in clobber mode
    #
    return if ( $Clobber );

    if ( ! $ForceBuild  )
    {
        my @build_warn;
        my $bstamp = -M "$Cwd/$ScmBuildSrc";
        my $tstamp = -M "$Cwd/Makefile.gbe";

        push @build_warn, "Missing: Makefile.gbe" unless ( defined $tstamp );
        push @build_warn, "Modified build file ($ScmBuildSrc)" if ( $tstamp && $bstamp < $tstamp );

        #
        #   Ensure that the build filter has not changed
        #   If the user has changed the buildfilter, then we need to
        #   force a build.
        #
        #   The root Makefile.bge will have a $ScmBuildFilter entry
        #
        unless ( @build_warn )
        {
            use JatsMakeInfo;
            ReadMakeInfo();
                my $line = $::ScmBuildFilter || '';
                $line =~ s~\s+~ ~g;

                my $filter = $::GBE_BUILDFILTER;
                $filter =~ s~\s+~ ~g;
                if ( $line ne $filter )
                {
                    push @build_warn, "Build filter has changed";
                    Verbose2 ("Buildfilter Test: Was:$line, Is:$::GBE_BUILDFILTER");
                }
        }

        if ( @build_warn )
        {
            Verbose ("Forcing Build.", @build_warn );
        }
        else
        {
            Verbose ("No build performed. Build files up to date");
            Log ("Build files up to date") if $::GBE_SANDBOX;
            exit 0;
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : LastBuildDirective
#
# Description     : No more build directives allowed
#
# Inputs          : 
#
# Returns         : 
#
sub LastBuildDirective
{
    $BUILDPHASE = 2;
}

#-------------------------------------------------------------------------------
# Function        : BuildPackageLink
#
# Description     : Create a soft link from sandbox_dpkg_archive to the package
#                   being created by this build
#
#                   For backward compatability.
#                   If GBE_DPKG_SBOX is not defined, then use GBE_DPKG_LOCAL
#
#                   This will allow multiple components to work together
#
#                   Note: When called in Clobber-mode the link will be deleted
#
# Inputs          : $BUILDNAME              - The package name
#                   $BUILDNAME_PROJECT      - Project extension
#                   $::GBE_DPKG_SBOX        - Path of sandbox_dpkg_archive
#                   $::GBE_DPKG_LOCAL       - Path of local_dpkg_archive
#                   $::GBE_DPKG             - Main repository
#
# Returns         : Nothing
#
sub BuildPackageLink
{
    my $target_archive;
    my $target_archive_name;
    my $link_file;
    my $tag;
    my $root_path;

    #
    #   Determine the path (and name) of the target archive
    #   Use sandbox_dpkg_archive if it exists
    #   Use local_dpkg_acrhive for backward compatability (should be removed after JATS 2.64.2+)
    #
    if ( $target_archive = $::GBE_DPKG_SBOX )
    {
        $target_archive_name = "sandbox_dpkg_archive";
        $tag = "Sandbox";
        if ( $sandbox_exact )
        {
            $link_file = "$BUILDVERSION.lnk";
        }
        else
        {
            $link_file  = 'sandbox' . ${BUILDNAME_SUFFIX} . '.lnk';
        }
        $root_path = 'GBE_SANDBOX' . substr($Cwd, length($::GBE_SANDBOX));
        Verbose2("Root Path: $::GBE_SANDBOX, $root_path");
    }
    elsif ( $target_archive = $::GBE_DPKG_LOCAL )
    {
        $target_archive_name = "local_dpkg_archive";
        $link_file = "$BUILDVERSION.lnk";
        $tag = "Local";
        $root_path = $Cwd;
    }
    else
    {
        Verbose("Cannot locate local or sandbox archive")
            unless $Clobber;
        return;
    }

    #
    #   Santity test
    #   Target must be a directory
    #
    unless ( -d $target_archive )
    {
        Warning("$target_archive_name is not a directory: $target_archive")
            unless $Clobber;
        return;
    }

    my $link_dir = "$target_archive/$BUILDNAME_PACKAGE";
    my $link_path = "$link_dir/$link_file";

    if ( $Clobber )
    {
        unlink $link_path;          # Delete the link
        rmdir $link_dir;            # Delete only if empty
    }
    else
    {
        Log( "Local Link . $BUILDNAME_PACKAGE/$link_file ($tag)");
        mkdir $link_dir unless -d $link_dir;
        FileCreate ( $link_path, "$root_path/pkg/$BUILDNAME_PACKAGE");
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildSandboxData
#
# Description     : Create data structures to allow this package to be built
#                   within a multi-package sandbox.
#
#                   This will allow multiple components to work together
#
#                   Note: When called in Clobber-mode the link will be deleted
#
# Inputs          : $BUILDNAME              - The package name
#                   $BUILDNAME_PROJECT      - Project extension
#                   $::GBE_DPKG_SBOX        - Path of sandbox_dpkg_archive
#                   $::GBE_DPKG             - Main repository
#
# Returns         : Nothing
#
sub BuildSandboxData
{
    my $sandbox_dpkg_archive = $::GBE_DPKG_SBOX;
    return unless ( $sandbox_dpkg_archive );

    unless ( -d $sandbox_dpkg_archive )
    {
        Error("sandbox_dpkg_archive is not a directory: $sandbox_dpkg_archive")
            unless $Clobber;
        return;
    }

    #
    #   Create a name for this package in the sandbox
    #   Must use the package name and extension. Don't use the version
    #   information as this will not be correct
    #
    #   PACKAGE/sandbox.PRJ.cfg
    #
    my $link_dir = "$sandbox_dpkg_archive/$BUILDNAME_PACKAGE";
    my $link_file;

    if ( $sandbox_exact )
    {
        $link_file = "$BUILDVERSION.cfg";
    }
    else
    {
        $link_file  = 'sandbox' . ${BUILDNAME_SUFFIX} . '.cfg';
    }
    my $link_path = "$link_dir/$link_file";

    if ( $Clobber )
    {
        unlink $link_path;          # Delete the link
        rmdir $link_dir;            # Delete only if empty
    }
    else
    {
        Log( "Sandbox cfg. $link_file");
        unlink $link_path;
        mkdir $link_dir;

        #
        #   Create the sandbox config data structure
        #
        my %sandbox_info = (
            BUILDDIR     => $Cwd,
            INTERFACEDIR => $BUILDINTERFACE,
            );

        #
        #   Write out the Parsed Config File with new information
        #
        my $fh = ConfigurationFile::New( $link_path );
        $fh->Header( "buildlib (version $::BuildVersion)",
                                  "Sandbox configuration" );

        #
        #   Dump out the configuration information
        #
        $fh->Dump( [\%sandbox_info], [qw(*sandbox_info)] );
        $fh->Close();
    }
}


#-------------------------------------------------------------------------------
# Function        : BuildMake
#
# Description     : Generate the makefiles
#                   This directive MUST be the last directive in the build.pl
#                   file. The directive triggers the processing of all the
#                   information that has been collected
#
#
# Inputs          : None
#
# Returns         : Nothing
#

sub BuildMake
{
    my( $argc, $platform );

    #
    #   Must have a valid $BUILDINTERFACE
    #   Normally this is held in the interface directory, but this is not
    #   always created. If there is no $BUILDINTERFACE, then use the
    #   build directory
    #
    $BUILDINTERFACE = "." unless ( $BUILDINTERFACE );

    #.. Starting the build phase. No more data collection
    #
    StartBuildPhase();
    LastBuildDirective();

    sub DeleteCfg
    {
        #
        #   Delete files that will be re-created
        #   Some of these files are read and written.
        #   Errors in the files are solved by deleting the files now.
        #
        unlink "$BUILDINTERFACE/build.cfg";
        unlink "$BUILDINTERFACE/Makefile.cfg";
        unlink glob ("$BUILDINTERFACE/Makefile*.cfg");
        unlink "$BUILDINTERFACE/Buildfile.cfg";
        unlink "$BUILDINTERFACE/Dpackage.cfg";
    }

    if ( $Clobber )                             # clobber mode ?
    {
        #
        #   Read in toolset files - a list of files collected during
        #   previous builds
        #
        ToolsetFile();

        #
        #   Unmake all the makefiles
        #   No longer needed as we track the file that are created
        #
        #if ( -e "Makefile.gbe" )
        #{
        #    JatsTool ( 'jmake.pl', 'unmakefiles');
        #}

        #
        #   Delete my own configuration files
        #
        DeleteCfg();

        #
        #   JATS creates a 'pkg' directory for the target package
        #
        push @CLOBBERDIRS, "pkg";
        
        #
        #   Deployment creates a 'build/deploy' directory
        #   The 'build' directory may contain user files - only remove if empty
        #
        push @CLOBBERDIRS, "build/deploy";
        push @REMOVEDIRS, "build";

        #
        #   Delete interface directories and other directories that have been
        #   marked to be clobbered
        #
        foreach my $dir ( @CLOBBERDIRS )
        {
            next if ( $dir eq '.' );
            next if ( $dir eq '..' );
            if ( -d $dir )
            {
                RmDirTree ( $dir );
            }
        }

        foreach my $dir ( @REMOVEDIRS )
        {
            next if ( $dir eq '.' );
            next if ( $dir eq '..' );
            if ( -d $dir )
            {
                rmdir ( $dir ); # Only if empty
            }
        }

        if ( exists $::GBE_TOOLSETFiles{Files} )
        {
            foreach my $file (keys %{$::GBE_TOOLSETFiles{Files}})
            {
                if ( -f $file )
                {
                    RmDirTree ( $file );
                }
            }
        }
        
        #
        #   DPACKAGE may be a user file, Only delete it if we created it
        #
        unlink "$Srcdir/DPACKAGE.$::GBE_MACHTYPE" if $DeleteDPACKAGE;

        BuildPackageLink();
        BuildSandboxData();
        return;
    }

    #.. Build support files
    #
    DeleteCfg();
    BuildConfig();
    BuildSharedLibFiles();
    WriteParsedBuildConfig();
    BuildPackageLink();
    BuildSandboxData();
    NoBuildMarker();

    #
    #  ONLY (re)building interface dir
    #
    return
        if ( $Interface );

    #---------------------------------------------------------------------------
    #
    #.. Make bootstrap "makefile",
    #   Simulate a top level makefile
    #       Pass argumenst to makelib
    #       Sumulate SubDir() operations
    #       Sumulate a Platform(*);
    #
    #       Due to the normal way that makelib.pl is executed,
    #       the following substitutions are done.
    #
    @ARGV = ();
    $0 = "makefile.pl ";
    push @ARGV, "$Cwd";                         # current working directory
    push @ARGV, "$::GBE_TOOLS/makelib.pl";     # makelib.pl image
    push @ARGV, "--interface=$BUILDINTERFACE"
        if ($BUILDINTERFACE);

    Debug( "ARGV:      @ARGV" );

    #.. (re)Build root makefile
    #
    $ScmBuildlib = 0;                           # clear Buildlib flag for 'makelib.pl'
    RootMakefile();                             # inform 'makelib.pl'
    MakeLibInit();                              # run initialisation

    #.. Register subdir(s)
    #
    UniquePush (\@BUILDSUBDIRS, $Srcdir );
    SubDir( @BUILDSUBDIRS );
    Platform( @BUILD_ACTIVEPLATFORMS );

    #.. (re)build src makefiles and associated information
    #   JatsTool will not return on error
    #
    my @cmds = ('jmake.pl', 'rebuild');
    push @cmds, 'NORECURSE=1' if ( $RootOnly );
    JatsTool ( @cmds);

    #
    #   Generate some warnings that will be seen at the end of the build
    #
    Warning ("BuildSrcArchive Directive Present","Read JATS Manual for correct usage")
        if ($build_source_pkg);
}


#-------------------------------------------------------------------------------
# Function        : BuildVersion
#
# Description     : Generate version.c and version.h files
#
# Inputs          : Options
#                       --Prefix=prefix         Text prepended to variables created
#                                               as a part of the "C" versions
#                       --Type=type             Type of "C" style data
#                                               Allowed types are: array
#                       --Defs=name             Generate a "C" definitions file.
#                                               This file simply contains definitions
#                       --Defs                  Same as --Defs=defs
#                       --Style=style           Output file style
#                                               Supported styles:
#                                                   "C" - Default
#                                                   "CSharp"
#                                                   "WinRC"
#                                                   "Delphi"
#                                                   "VB"
#                       --File=name             Specifies the output file name
#                                               Default is determined by the style
#
#                   Also allows for an 'old' style format in which
#                   the first three arguments are prefix,type and defs
# Returns         :
#

sub BuildVersion
{
    my ( $Prefix, $Type, $Mode ) = @_;
    my $ModePrefix;
    my $Style = "C";
    my $FileName;
    my $VersionFiles;
    my @opts;
    my $supports_opts;

    StartBuildPhase();                          # Starting the build phase. No more data collection

    if ( defined($Prefix) && $Prefix =~ /^-/ )
    {
        $Prefix = undef;
        $Type = undef;
        $Mode = undef;
        foreach  ( @_ )
        {
            if (      /^--Prefix=(.*)/ ) {
                $Prefix = $1;
                $VersionFiles = 1;

            } elsif ( /^--Type=(.*)/ ) {
                $Type = $1;
                $VersionFiles = 1;

            } elsif ( /^--Defs=(.*)/ ) {
                $Mode = $1;
                $ModePrefix = "_$1";

            } elsif ( /^--Defs$/ ) {
                $Mode = 'defs';
                $ModePrefix = "";

            } elsif ( /^--Style=(.*)/ ) {
                $Style = $1;
                $VersionFiles = 1;
                $supports_opts = 1 if ( $Style =~ /^WinRC/i );

            } elsif ( /^--File=(.*)/ ) {
                $FileName = $1;

            } elsif ($supports_opts ) {
                push @opts, $_;

            } else {
                Error ("BuildVersion: Unknown option: $_");

            }
        }
    }
    else
    {
        #
        #   Old style positional arguments.
        #
        $VersionFiles = 1;
        if ( defined( $Mode ) )
        {
            if ( $Mode =~ m/^defs(=(.*))?$/i )
            {
                $Mode       = $2 ? $2    : 'defs';
                $ModePrefix = $2 ? "_$2" : "";
            }
            else
            {
                Error ("BuildVersion: Bad Mode argument. Need 'defs' or 'defs=name'");
            }
        }
    }

    #
    #   Determine the style of version file to create
    #
    if ( $Style =~ /^CSharp/i ) {
        BuildVersionCSharp( $FileName );

    } elsif ( $Style =~ /^Properties/i ) {
        BuildVersionProperties( $FileName, $Prefix );

    } elsif ( $Style =~ /^WinRC/i ) {
        BuildVersionWinRC( $FileName, @opts );

    } elsif ( $Style =~ /^Delphi/i ) {
        BuildVersionDelphi( $FileName, $Prefix );

    } elsif ( $Style =~ /^VB/i ) {
        BuildVersionVB( $FileName, $Prefix );
        
    } elsif ( $Style eq "C" ) {
        BuildVersionC    ( $FileName, $Prefix, $Type )     if ( $VersionFiles );
        BuildVersionCdefs( $FileName, $Mode, $ModePrefix ) if ( $Mode );

    } else {
        Error("BuildVersion: Unknown style: $Style");
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildDescpkg
#
# Description     : Create a package description file
#                   The format of this file matches that generated by JANTS
#                   Take care when extending the format
#
#                   NOTE: It turns out that JANTS is not a standard and the
#                         implementors (of JANTS) kept on changing it.
#
# Inputs          :
#
# Returns         :
#
sub BuildDescpkg
{
    StartBuildPhase();                      # Starting the build phase. No more data collection
    return if ( $Clobber );                 # clobber mode ?

    #
    #   Store the files location for use at runtime
    #   It will be a file that is 'known' to JATS
    #
    my $pkgfile = BuildAddKnownFile ( $NoBuild ? $Cwd : $Srcdir, 'descpkg' );

    my @desc;
    push @desc, "Package Name:  $BUILDNAME_PACKAGE";
    push @desc, "Version:       $BUILDVERSION";
    push @desc, "Released By:   $::USER";
    push @desc, "Released On:   $::CurrentTime";
    push @desc, "Build Machine: $::GBE_HOSTNAME";
    push @desc, "Path:          $Cwd";
    push @desc, "Jats Version:  $::GBE_VERSION";
    push @desc, "Jats Path:     $::GBE_CORE";
    push @desc, "";
    push @desc, "Build Dependencies:";
    push @desc, "";

    foreach my $tag ( PackageEntry::GetPackageList )
    {
        my ($name, $version, $type) = PackageEntry::GetPackageData($tag);

        my @attributes;

        push @attributes, "name=\"$name\"";
        push @attributes, "version=\"$version\"";
        push @attributes, "build=\"true\"" if $type =~ /Build/i;

        push @desc, "<sandbox @attributes/>";
    }

    FileCreate ($pkgfile, \@desc );
}

#-------------------------------------------------------------------------------
# Function        : NoBuildMarker
#
# Description     : Maintain the nobuild marker
#                   This is file placed in the interface directory simply
#                   to indicate to the 'create_dpkg' utility that this build
#                   does not do anything useful.
#
#                   It will only be used on a build machine by the buid daemon
#
#                   Its not placed in the interface directory as it would be
#                   harder for create_dpkg to find it.
#
# Inputs          : None
# Globals         : $NoBuild, $Clobber
#
# Returns         : Nothing
#
sub NoBuildMarker
{
    return if ( $Clobber );

    # Always delete the file - in case we toggle build forms
    #
    my $markerFile = BuildAddKnownFile( $Cwd, 'noBuild.gbe');
    unlink($markerFile);

    TouchFile($markerFile)
        if ($NoBuild);
}

#-------------------------------------------------------------------------------
# Function        : BuildIncpkg
#
# Description     : Create a package inclusion file
#
# Inputs          :
#
# Returns         :
#
sub BuildIncpkg
{
    StartBuildPhase();                          # Starting the build phase. No more data collection
    if ( $Clobber )                             # clobber mode ?
    {
        RmDirTree( "$Srcdir/incpkg" );
        return;
    }

    my $fh = ConfigurationFile::New( "$Srcdir/incpkg" );
    $fh->Header( "buildlib (Version $BuildVersion)",
                              "Package inclusion list" );

    foreach my $tag ( PackageEntry::GetPackageList )
    {
        my ($name, $version, $type) = PackageEntry::GetPackageData($tag);
        $type = ($type =~ /build/i) ? "Build" : "Link";

        $fh->Write( "${type}PkgArchive( '$name', '$version' );\n" );
    }

    $fh->Close();
}

#-------------------------------------------------------------------------------
# Function        : BuildConfig
#
# Description     : Create the file interface/build.cfg
#                   This file contains information gathered by the build process
#                   that is to be used when makefiles are created and re-created
#
# Inputs          : None
#
# Returns         : Nothing
#
sub BuildConfig
{
    Error( "No BuildInterface directive encountered\n" )
        unless ($BUILDINTERFACE);

    my $fh = ConfigurationFile::New( "$BUILDINTERFACE/build.cfg");
    $fh->Header( "buildlib (Version $BuildVersion)",
                              "Makelib configuration file", "
\$ScmBuildMachType              = \"$::GBE_MACHTYPE\";
\$ScmInterfaceVersion           = \"$::InterfaceVersion\";
\$ScmBuildName                  = \"$BUILDNAME\";
\$ScmBuildPackage               = \"$BUILDNAME_PACKAGE\";
\$ScmBuildVersion               = \"$BUILDNAME_VERSION\";
\$ScmBuildProject               = \"$BUILDNAME_PROJECT\";
\$ScmBuildVersionFull           = \"$BUILDVERSION\";
\$ScmBuildPreviousVersion       = \"$BUILDPREVIOUSVERSION\";
\$ScmLocal                      = \"$BUILDLOCAL\";
\$ScmDeploymentPatch            = \"$DEPLOY_PATCH\";
\$ScmSrcDir                     = \"$Srcdir\";
\$ScmBuildSrc                   = \"$ScmBuildSrc\";
\$ScmExpert                     = \"$Expert\";
\$ScmAll                        = \"$All\";
\$ScmNoBuild                    = \"$NoBuild\";
");

#.. Alias
#
    $fh->DumpData(
        "\n# Aliases.\n#\n",
        "ScmBuildAliases", \%BUILDALIAS );

#.. Products
#
    $fh->DumpData(
        "# Product mapping.\n#\n",
        "ScmBuildProducts", \%BUILDPRODUCT_PARTS );

#.. Create ScmBuildPlatforms
#
    my( @platforms_merged, %platform_args ) = ();

    UniquePush ( \@platforms_merged, @BUILDPLATFORMS );

    foreach my $key ( keys %BUILDPRODUCT ) {
        my( @list ) = split( ' ', $BUILDALIAS{ $key } || '' );
        my( $platform );

        foreach my $elem ( @list ) {
            if ( $elem =~ /^--/ ) {             # argument
                HashJoin( \%platform_args, $;, $platform, $elem )
                    if ( defined($platform) );
                next;
            }
            $platform = $elem;                  # platform
            UniquePush( \@platforms_merged, $elem );
        }
    }

#.. Create ScmBuildPlatforms
#   Contains per platform options extracted from alias and platform args
#
    my %ScmBuildPlatforms;
    foreach my $key ( @platforms_merged ) {

        my( @arguments ) = ();
        UniquePush( \@arguments, split( /$;/, $BUILDPLATFORMARGS{ $key } ))
            if ( exists $BUILDPLATFORMARGS{ $key } );

        UniquePush( \@arguments, split( /$;/, $platform_args{ $key } ))
            if ( exists $platform_args{ $key } );

        $ScmBuildPlatforms{$key} = join "$;", @arguments;
    }

    $fh->DumpData(
        "# Platform and global argument list.\n#\n",
        "ScmBuildPlatforms", \%ScmBuildPlatforms );


# .. Create BuildPkgRules
#
#    This is most of the information contained within %PKGRULES, which
#    requires additional processing within makelib.
#
#   Need the True Path for windows.
#       Some makefile functions (wildcard) only work as expected
#       if the case of the pathname is correct. Really only a problem
#       with badly formed legecy packages where the Windows user
#       guessed at the package format.
#
    my %ScmBuildPkgRules;
    foreach my $platform ( keys %PKGRULES )
    {
        foreach my $package ( @{$PKGRULES{$platform}} )
        {
            my %entry;

            $entry{ROOT}     = TruePath( $package->{'base'} );
            $entry{NAME}     = $package->{'name'};
            $entry{VERSION}  = $package->{'version'};
            $entry{DNAME}    = $package->{'dname'};
            $entry{DVERSION} = $package->{'dversion'};
            $entry{DPROJ}    = $package->{'dproj'};
            $entry{TYPE}     = $package->{'type'};
            $entry{CFGDIR}   = $package->{'cfgdir'} if ( defined( $package->{'cfgdir'} ) );

            foreach my $dir (qw (TOOLDIRS) )
            {
                $entry{$dir} = $package->{$dir} ;
            }

            my $baselen = length($package->{'base'});
            foreach my $dir (qw (PINCDIRS PLIBDIRS THXDIRS) )
            {
                $entry{$dir} = [];
                foreach my $file ( @{$package->{$dir}} )
                {
                    push @{$entry{$dir}}, substr TruePath($package->{'base'} . $file ), $baselen;
                }
            }

            push @{$ScmBuildPkgRules{$platform}}, \%entry;
        }
    }

    $fh->DumpData(
        "# Imported packages.\n#\n",
        "ScmBuildPkgRules", \%ScmBuildPkgRules );

#
#   BUILDPLATFORMS,
#       The value that is saved only contains the active platforms
#
#   DEFBUILDPLATFORMS,
#       The value that is matchs the wildcard specification for Platform 
#       directives.
#
    $fh->DumpData(
        "# A list of platforms active within the view.\n#\n",
        "BUILDPLATFORMS", \@BUILD_ACTIVEPLATFORMS );

    $fh->DumpData(
        "# A list of default platforms within the view.\n#\n",
        "DEFBUILDPLATFORMS", \@DEFBUILDPLATFORMS );

#
#   BUILDTOOLS
#       A list of toolset extension paths
#
    $fh->DumpData(
        "# A list of paths with toolset extension programs.\n#\n",
        "BUILDTOOLSPATH", \@BUILDTOOLS );

#
#   BUILDPLATFORM_PARTS
#       A subset of BUILDINFO exported as BUILDPLATFORM_PARTS
#       This exists only for backward compatability with existing code
#       in external packages ( deployfiles).
#
#   Only save those parts that are part of the current build
#   This will prevent users attempting to build for platforms that have not
#   been correctly constructed.
#
    my %active =  map { ${_} => 1 } @BUILD_ACTIVEPLATFORMS;
    my %active_buildplatform_parts;
    my %active_build_info;
    foreach ( keys %BUILDINFO )
    {
        next unless ( $active{$_} );
        $active_buildplatform_parts{$_} = $BUILDINFO{$_}{PARTS};
        $active_build_info{$_}          = $BUILDINFO{$_};
    }

    $fh->DumpData(
        "# Parts of all platforms.\n#\n",
        "BUILDPLATFORM_PARTS", \%active_buildplatform_parts );
#
#   BUILDINFO
#       Complete TARGET Information
#
    $fh->DumpData(
        "# Extended build information.\n#\n",
        "BUILDINFO", \%active_build_info );

#
#   BUILD_KNOWNFILES
#       All paths are relative to the project root directory
#       ie: The directory that conatins the build.pl file
#
    $fh->DumpData(
        "# Generated Files that may be known when used as Src files.\n#\n",
        "BUILD_KNOWNFILES", \%BUILD_KNOWNFILES );

#
#   Close out the file
#
    $fh->Close();

}

#-------------------------------------------------------------------------------
# Function        : WriteParsedBuildConfig
#
# Description     : Write all the parsed build.pl data to a single file
#                   Its all in there for use
#
# Inputs          : 
#
# Returns         : 
#
sub WriteParsedBuildConfig
{
    my $cfg_file = "$::BUILDINTERFACE/Buildfile.cfg";
    my %cf_build_info = ();

    #
    #   Examine the symbol table and capture most of the entries
    #
    foreach my $symname (keys %main::)
    {
        next if ( $symname =~ m/::/  );                 # No Typeglobs
        next if ( $symname =~ m/^cf_build_info/  );     # Not myself
        next unless ( $symname =~ m/^[A-Za-z]/  );      # No system type names
        next if ( $symname =~ m/^SIG$/  );              # Useless
        next if ( $symname =~ m/^ENV$/  );              # Don't keep the user ENV
        next if ( $symname =~ m/^INC$/  );              # Don't keep the INC paths
        next if ( $symname =~ m/^DEFINES/  );           # Don't keep
        next if ( $symname =~ m/^TOOLSETRULES/  );      # Don't keep

        no strict 'vars';
        local *sym = $main::{$symname};

        $cf_build_info{"\$$symname"} =  $sym if defined $sym;
        $cf_build_info{"\@$symname"} = \@sym if @sym;
        $cf_build_info{"\%$symname"} = \%sym if %sym;
        use strict 'vars';
    }

    #
    #   Dump out the configuration information
    #
    my $fh = ConfigurationFile::New( "$cfg_file" );
    $fh->Header( "buildlib (version $::BuildVersion)",
                              "Buildfile configuration" );
    $fh->Dump( [\%cf_build_info], [qw(*cf_build_info)] );
    $fh->Close();
}


#-------------------------------------------------------------------------------
# Function        : BuildSharedLibFiles
#
# Description     : Create a file in the interface directory that will specify
#                   the locations of shared libraries.
#
#                   Note: Always create a file as makefile targets depend on it.
#
#                   This is a bit ugly.
#
#                   There needs be an association between the build machine and
#                   the target platform. Need to know if the current target is
#                   native to the current build machine. If it is then we can
#                   run tests on the machine and we then need to extend the
#                   search path for the target.
#
#                   The BUILDINFO{EXT_SHARED} is used to control the creation of
#                   the files by specifying the extension of the file.
#
# Inputs          : None
#
# Returns         :
#
sub BuildSharedLibFiles
{
    if ( $ScmHost eq "DOS" || $ScmHost eq "WIN" ) {
        BuildSharedLibFiles_WIN(@_);

    } elsif ( $ScmHost eq "Unix" ) {
        BuildSharedLibFiles_Unix(@_);

    } else {
        Error("Cannot build. Unknown machine type: $ScmHost",
              "Need WIN, DOS or Unix" );
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildSharedLibFiles_WIN
#
# Description     : Implementation of BuildSharedLibFiles for Windows
#
# Inputs          : None
#
sub BuildSharedLibFiles_WIN
{

    foreach my $platform ( @BUILD_ACTIVEPLATFORMS )
    {
        next unless ( exists $BUILDINFO{$platform}{EXT_SHARED} );
        my @dos_paths = BuildSharedLibFiles_list( $platform, $BUILDINFO{$platform}{EXT_SHARED} );

        #
        #   Create a .bat file for WIN32
        #   This may be consumed by user wrapper programs
        #
        #   Features: No Echo
        #             Use of SETLOCAL to prevent pollution of environment
        #
        my $fh = ::ConfigurationFile::New( "$BUILDINTERFACE/set_$platform.bat", '--NoEof', '--Type=bat' );
        $fh->Write ( "\@echo off\n");
        $fh->Header( "Buildlib ($BuildVersion)","Shared Library Paths" );
        $fh->Write ( "\nSETLOCAL\n");
        foreach ( reverse @dos_paths )
        {
            $_ =~ s~/~\\~g;
            $fh->Write ( "PATH=$_;\%PATH\%\n" );
        }
        $fh->Write ( "\n%*\n" );
        $fh->Write ( "\nENDLOCAL\n");
        $fh->Write ( "EXIT /B %ERRORLEVEL%\n");
        $fh->Close();

        #
        #   Create a .sh file for WIN32
        #   This may be consumed by a shell - as used within JATS
        #
        $fh = ::ConfigurationFile::New( "$BUILDINTERFACE/set_$platform.sh", '--NoEof', '--Type=sh' );
        $fh->Header( "Buildlib ($BuildVersion)","Shared Library Paths" );
        foreach ( reverse @dos_paths )
        {
            tr~\\/~/~s;
            $fh->Write ( "PATH=$_\\;\$PATH\n" );
        }
        $fh->Write ( "\n" . '[ -n "$*" ] && "$@"'  ."\n" );
        $fh->Close();
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildSharedLibFiles_Unix
#
# Description     : Implementation of BuildSharedLibFiles for Unix
#                   Extend the Shared Library search path via LD_LIBRARY_PATH
#
# Inputs          : None
#
sub BuildSharedLibFiles_Unix
{
    foreach my $platform ( @BUILD_ACTIVEPLATFORMS )
    {
        next unless ( exists $BUILDINFO{$platform}{EXT_SHARED} );
        my @unix_paths = BuildSharedLibFiles_list( $platform, $BUILDINFO{$platform}{EXT_SHARED} );

        #
        #   Create a .sh file for Unix
        #
        my $file = "$BUILDINTERFACE/set_$platform.sh";
        my $fh = ::ConfigurationFile::New( $file , '--NoEof', '--Type=sh' );
        $fh->Header( "Buildlib ($BuildVersion)","Shared Library Paths" );
        foreach ( reverse @unix_paths )
        {
            $fh->Write ( "export LD_LIBRARY_PATH=$_:\$LD_LIBRARY_PATH\n" );
        }
        $fh->Write ( "\n\"\$\@\"\n" );
        $fh->Close();

        #
        #   Make the file executable under unix
        #
        chmod 0755, $file;
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildSharedLibFiles_list
#
# Description     : Determine a list of Shared Library paths that can be used
#                   by the current target
#
# Inputs          : $platform       - Current platform
#                   $so             - Shared object extensions
#
# Returns         : List of search paths
#
sub BuildSharedLibFiles_list
{
    my ($platform, $so ) = @_;
    my @paths;
    my @parts = @{$BUILDINFO{$platform}{PARTS}};

    #
    #   Paths from the current build
    #       Local directory         - for installed components
    #       Interface directory     - for BuildPkgArchives
    #
    if ( $BUILDLOCAL )
    {
        foreach ( @parts )
        {
            push @paths, AbsPath("$BUILDLOCAL/lib/$_");
        }
    }

    foreach ( @parts )
    {
            push @paths, AbsPath("$BUILDINTERFACE/lib/$_");
    }

    #
    #   For each LinkPkgArchive
    #
    foreach my $package ( @{$PKGRULES{$platform}} )
    {
        next unless ( $package->{'type'} eq 'link' );

        my $base = $package->{'base'};
        for my $path ( @{$package->{'PLIBDIRS'}} )
        {
            my @so_libs;
            push @so_libs, glob ( "$base$path/*$_") foreach ( ArrayList($so) );
            next unless scalar @so_libs;
            push @paths, $base . $path;;
        }
    }

    #
    #   Returns paths found
    #
    return @paths;
}

#-------------------------------------------------------------------------------
# Function        : BuildAddKnownFile
#
# Description     : Save the file as a file that will be known  to the JATS
#                   makefiles. It will be available SRCS, but will not be a
#                   part of any object list.
#
#                   Known Files will be deleted on clobber
#
# Inputs          : $path
#                   $file
#                   $noadd                    - Don't add to known
#
# Returns         : Path and filename
#

sub BuildAddKnownFile
{
    my ($path, $file, $noadd) = @_;
    $path .= '/'. $file;
    $path =~ tr~/~/~s;
    $BUILD_KNOWNFILES {$file} = $path
        unless ( defined($noadd) && $noadd);

    ToolsetFile( $path )
        unless ($Clobber);

    return $path;
}

#-------------------------------------------------------------------------------
# Function        : Usage
#
# Description     : Display program usage information
#
# Inputs          : args            - Text message to display
#
#                   $opt_help       - Level of verbose ness
#
# Returns         : Does not return
#                   This function will exit
#
sub Usage
{
    my( $msg ) = @_;
    my %usage_args;

    #
    #   Create a hash of arguments for the pod2usage function
    #
    $usage_args{'-input'} = __FILE__;
    $usage_args{'-exitval'} = 42;
    $usage_args{'-message'} = "\nbuildlib $msg\n" if $msg;
    $usage_args{'-verbose'} = $opt_help < 3 ? $opt_help - 1 : 3 if ( $opt_help );

    #
    #   Generate nice help
    #
    pod2usage(\%usage_args);
}

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

=pod

=for htmltoc    JATS::build

=head1 NAME

build - Build Environment and Makefiles

=head1 SYNOPSIS

jats build [options] [command]

     [perl buildlib.pl [options] PWD [command]]

 Options:
    -help          - Display terse usage
    -help -help    - Display verbose usage
    -man           - Display internal manual
    -verbose[=n]   - Set level of progress verbosity
    -debug[=n]     - Set the debug level
    -nolog         - Do not generate/update Changelog
    -cache         - Cache packages in the local dpkg_package cache
    -cache -cache  - Forced refresh dependent packages in the local cache
    -package       - Ignore packages that are not available and continue
    -nopackages    - Ignore package processing directives
    -forcebuildpkg - Treat LinkPkgArchive directives as BuildPkgArchive
                     Also suppress the use of symlinks so that the physical
                     file will be copied locally.
    -[no]force     - Force build even if build.pl is not newer
                     Default: -force

 Sticky settings:
    -all           - Build for all platforms ignoring GBE_BUILDFILTER
    -expert[=n]    - Relaxing dependency checks on the user makefiles

 Commands:
    changelog      - Only update ChangeLog.
    clobber        - Remove generated build system (eg Makefiles).
    interface      - Only (re)build the interface tree, including ChangeLog.
    rootonly       - Only (re)build the root directory.

=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<-verbose[=n]>

Increases program output.

If an argument is provided, then it will be used to set the level, otherwise the
existing level will be incremented. This option may be specified multiple times.

=item B<-debug>

Increases program output. Enable internal debugging messages to generate detailed
progress information.

If an argument is provided, then it will be used to set the level, otherwise the
existing level will be incremented. This option may be specified multiple times.

=item B<-nolog>

Do not generate or update the ChangeLog maintained when a CVS directory is detected
in the build directory.

=item B<-cache>

This option will cause dependent packages to be cached in the local
dpkg_archive cache.

If the option is used twice then the packages will be forcibly refreshed.

=item B<-package>

This option will cause the build process to ignore packages that cannot be
located. The package build may fail at a later stage.

This option is used by the Auto Build Tool to handle packages that may not be
needed in all builds.

=item B<-nopackage>

This options will cause all the directives that process external packages to be
ignored.

This directive has limited use. It can be used in conjunction with the
'interface' build command in order to create Version Information files in a
sandbox where the required packages do not yet exist.

=item B<-forcebuildpkg>

This option will force LinkPkgArchive directives to be treated as
BuildPkgArchive directives. The result is that the 'interface' directory will be
populated with the contents of all dependent packages. Moreover, on a Unix
machine, the files will be copied and not referenced with a soft link.

This may be useful for:

=over 8

=item *

Remote Development

=item *

Collecting header files for scanning

=item *

Local modification of files for test/debug/development

=back

=item B<-[no]force>

The '-noforce' option will only perform a build, if the build.pl file
has been modified, or the buildfilter has changed, since the last build.

The default operation will always force a build.

=item B<-all>

This option will cause the build process to generate makefiles for all
possible build targets ignoring the use of GBE_BUILDFILTER.

This option is sticky. Once used in a build it will be retained when makefiles
are rebuilt.

=item B<-expert[=n]>

This option causes the generation of makefiles with relaxed dependancy checks.

If an argument is provided, then it will be used to set the level, otherwise a
level of '1' will be set.

The makefiles will have no dependancy between the makefiles and the JATS build
files or the users makefile. If the user's makefile.pl is changed then JATS
will not detect the change and will not rebuild the makefiles. The user manually
force the rebuild with the command 'jats rebuild'.

This option should be used with care and with full knowledge of its impact.

This option is sticky. Once used in a build it will be retained when makefiles
are rebuilt. It will only be lost when the next 'jats build' is performed.

The effect of the option can be changed when the makefiles are processed. This
option simply sets the default' mode of operation.

=item B<changelog>

This command will only update the CVS change log.

=item B<interface>

This command will only build, or rebuild, the 'interface' directory and the
changelog ( if required ).

This command will not build, or rebuild, the root directory. The build
process will not recurse through the subdirectories creating makefiles.

=item B<rootonly>

This command will only build, or rebuild, the 'interface' directory, the
changelog (if required) and the root-level makefiles.

The build process will not recurse through the subdirectories creating
makefiles. These can be made on-demand by jats if a 'make' command is issued.

=item B<clobber>

This command will remove generated build system files and directories.

=back

=head1 DESCRIPTION

The default build process will parse the user's build.pl file and create the
'interface' directory before creating makefiles for each target platform.

The 'build' process simply generates the build sandbox. It does not invoke the
generated makefiles. This must be done by the user in a different phase.

The 'build' process need only be invoked if the build.pl file has changed. The
generated makefiles will detected changes to the makefile.pl's and cause them to
be generated as required. The 'build' step sets up the sandboxes external
environment.

=head1 INVOCATION

This perl library (buildlib.pl) is not designed to be invoked directly by the
user. It should be invoked through a 'build.pl' file. Moreover, for historical
reasons, the build.pl is not easily invoked. It is best to only invoke the
'build' process via the JATS wrapper scripts : jats.bat or jats.sh.

The build.pl file must be invoked with one fixed arguments, followed by user
options and subcommands

=over 8

=item   1

The current working directory

This could have been derived directly by the program, rather than having it
passed in.

=item   2

Options and commands may follow the first two mandatory arguments.

=back

The build.pl file must 'require' the buildlib.pl and makelib.pl. The preferred
code is:

=over 8

    build.pl: First statements
    $MAKELIB_PL     = "$ENV{ GBE_TOOLS }/makelib.pl";
    $BUILDLIB_PL    = "$ENV{ GBE_TOOLS }/buildlib.pl";

    require         "$BUILDLIB_PL";
    require         "$MAKELIB_PL";

=back

=cut

1;
