Subversion Repositories DevTools

Rev

Rev 7447 | Blame | Compare with Previous | Last modification | View Log | RSS feed

########################################################################
# COPYRIGHT - VIX IP PTY LTD ("VIX"). 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 ToolsetFiles;
use Pod::Usage;
use Getopt::Long;
use File::Path;
use XML::Writer;
use ArrayHashUtils;
use Storable qw(dclone);

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 $BuildInfo              = 0;
our $BuildInfoFile          = 'BuildInfo.properties';
our $Expert                 = 0;
our $All                    = 0;
our $Cache                  = $ENV{GBE_DPKG_CACHE_CTL} || 0;
our $NoPackageError         = 0;
our $ForceBuildPkg          = 0;
our $Srcdir                 = "";               # default source root
our $ForceBuild             = 1;
our $IgnorePkgs             = 0;
our $GenericBuild           = undef;            # Build System Generic Build Test

#.. 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 @GENERIC_TARGETS        = ();               # Generic targets - only one allowed

our $BUILDNAME              = "";               # BuildName()
our $BUILDVERSION           = "";               # BuildName()
our $BUILDBASEVERSION       = "";               # 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 @BUILDEXCLUDE           = ();               # Platforms to be excluded
our @BUILDARGUMENTS         = ();               # Build Arguments
our @LINKPKGEXLUDES         = ();               # LinkPkgExclude arguments

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
our $BUILD_UUID             = time() . substr(rand(),2); # Build Unique Identifier

my  $DeleteDPACKAGE         = 0;                # Must clobber DPACKAGE
my  $build_source_pkg       = 0;                # Flag to build source package
my  $opt_help               = 0;
my  $opt_localCache         = 0;                # Create cache within the interface directory
my  $sandbox_exact          = 0;                # Exact or in-exact sandbox
my  $pkgFromSandbox         = 0;                # Flags that we have imported a package from a sandbox

my  $toolsetData;                               # Parsed toolset data
my  $genToolsetPlatform     = 0;                # BuildToolset directive has been seen
my  $genToolsetActive       = 0;                # TOOLSET platform required:1, Error:2
my  $toolsetPlatform        = 'NONE';           # TOOLSET Display Value
my  @genToolsetArgs;                            # Args for a generated TOOLSET
my  $descpkgPath;                               # Path to the dscpkg file

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_ESCROW','' );
    EnvImportOptional ( 'GBE_DPKG_REPLICA','' );
    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
    shift @ARGV;                                # No longer used
    $::ScmRoot = $CwdFull;                      # $CwdFull does not have a driver letter. $CwdFull does
    $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,
                             "cache:+"       => \$Cache,
                             "package"       => \$NoPackageError,
                             "nopackages"    => \$IgnorePkgs,
                             "forcebuildpkg" => \$ForceBuildPkg,
                             "force!"        => \$ForceBuild,
                             "generic!"      => \$GenericBuild,
                             "localcache!"   => \$opt_localCache,
                             "buildinfo:s"   => \$BuildInfoFile,
                             );
    Usage() if ( $opt_help || !$result );

    Debug( "Host:          ", $ScmHost );
    Debug( "Cwd:           ", $CwdFull );
    Debug( "ScmRoot:       ", $::ScmRoot );
    Debug( "Makelib:       ", $Makelib );
    Debug( "BuildFile:     ", $ScmBuildSrc );
    Debug( "Debug:         ", $::ScmDebug );
    Debug( "Verbose:       ", $::ScmVerbose );
    Debug( "Expert:        ", $Expert );
    Debug( "All:           ", $All );
    Debug( "Cache:         ", $Cache );
    Debug( "package:       ", $NoPackageError );
    Debug( "ForcePkg  :    ", $ForceBuildPkg );
    Debug( "ForceBuild :   ", $ForceBuild );
    Debug( "IgnorePkgs :   ", $IgnorePkgs );
    Debug( "GenericTest :  ", $GenericBuild );
    Debug( "LocalCache :   ", $opt_localCache );

#.. 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 "buildinfo" ) {
            $BuildInfo      = 1;
            $Interface      = 1;
            unlink $BuildInfoFile;

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

        } 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) );

    #
    #   Capture messages while processing directives
    # 
    StartCapture(1) 
        unless ($Clobber);
}


#-------------------------------------------------------------------------------
# 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', \@_ ) unless $BuildInfo;
    }
}

#-------------------------------------------------------------------------------
# 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        : isKeyword 
#
# Description     : Test for an attempt to use reserved platform name 
#
# Inputs          : $test   - Name to test
#
# Returns         : Reserved word or none
#
sub isKeyword
{
    my ($test) = @_;
    foreach my $keyword ( @PlatformConfig::BuildKeywords )
    {
        return $keyword if (uc($test) eq $keyword);
    }

    return undef;
}


#-------------------------------------------------------------------------------
# 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", "First: $alias and now $_" )
                if ( $alias ne "" );
            $alias = $_;
        }
    }
    Error( "BuildAlias() missing alias specifications" )
        if ( $alias eq "" );

    if ( isKeyword($alias) ) {
        abtWarning(1, "BuildAlias() attempt to alias a keyword: $alias");
    }

    #
    #   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 ( PlatformConfig::targetHasTag($alias, 'GENERIC') )
    {
        Error( "BuildAlias() cannot create an alias named $alias", "That name is reserved for generic targets" );
    }
    elsif ( $alias ne quotemeta( $alias ) )
    {
        abtWarning (1, "BuildAlias() alias '$alias' contains invalid characters");
    }
    elsif ( $BUILDALIAS{ $alias } )
    {
        abtWarning   (1, "BuildAlias() duplicate alias '$alias'")
    }
    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 );
                    }
                    else
                    {
                        my @badArgs = grep (/^--/, @pargs) ;
                        if (@badArgs) {
                            Warning("Platform arguments in define mode are ignored:($platform)", @badArgs);
                        }
                        
                    }
                }

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

#-------------------------------------------------------------------------------
# Function        : BuildAliasDef  
#
# Description     : Shorthand for BuildAlias (xxx,-Define, ... )
#                   The way is should have been done  :(
#
# Inputs          : $alias          Name of alias to define
#                   arguments       Alias arguments; platforms or targets
#
sub BuildAliasDef()
{
    my( $alias, @arguments ) = @_;
    BuildAlias($alias . ',--Define', @arguments);
}

#-------------------------------------------------------------------------------
# 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 errors
    #
    while ( my($key,$value) = each(%BUILDALIAS_DELAY) )
    {
        if ( exists($BUILDALIAS{$key}) )
        {
            abtWarning(0,"BuildAlias() duplicates internal alias '$key'");
            next;
        }
        $BUILDALIAS{$key} = $value;
    }
    ErrorDoExit();

    #
    #   Empty so that its not seen in Buildfile.
    #
    %BUILDALIAS_DELAY =();

    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        : CleanUp_Aliases 
#
# Description     : Will fully expand aliases to simplify display and processing 
#                       Remove arguments that  start with a '-'
#                       Remove bits that start with a !
#
# Inputs          : 
#
# Returns         : 
#
sub CleanUp_Aliases
{
    #
    #   Clean up Aliases
    #
#DebugDumpData("BEFORE CleanUp_Aliases", \%BUILDALIAS);

    foreach my $alias ( keys %BUILDALIAS )
    {
        #
        #   Build hash of bits to be added and bits to be removed
        #   Remove all options - only need platforms
        #
        my @aliasList = split(/ /, $BUILDALIAS{$alias});
        my @expanded =  ExpandPlatforms(@aliasList);

        my %add;
        my %remove;

        foreach ( @expanded)
        {
            if (m/^-/) {
            } elsif (m/^!(.*)/) {
                $remove{$1} = 1;
            } else {
                $add{$_} = 1;
            }
        }

        #
        #   If there are NO additive expressions in the alias, then
        #   assume all the active targets
        #
        unless (keys %add) {
            %add = map { $_ => 1 } @BUILD_ACTIVEPLATFORMS;
        }

        foreach ( keys %remove) {
            delete $add { $_};
        }

        #
        #   Delete ALIAS if it has no expansion
        #       May cause issues with the Platforms directive
        #
        if (keys %add) {
            $BUILDALIAS{$alias} = join(' ',keys %add);
        } else {
            delete $BUILDALIAS{$alias};
        }
    }
#DebugDumpData("AFTER CleanUp_Aliases", \%BUILDALIAS);
}

#-------------------------------------------------------------------------------
# Function        : ProcessBuildExclude  
#
# Description     : Process BuildExclude directive info
#                   A list of targets to be excluded
#                   INSTRUMENT is a key word
#  
#
# Inputs          : None
#
# Returns         : Modifies BUILDINFO
#
sub ProcessBuildExclude
{
    for my $word (@BUILDEXCLUDE)
    {
        Debug("ProcessBuildExclude: $word");
        Error('BuildExclude: Unknown option: ' . $word) if ($word =~ m~^-~);
        Error('BuildExclude: Invalid format: ' . $word) if ($word =~ m~^!~);

        #
        #   INSTRUMENT is a key word
        #       Remove all instrumented builds
        #
        if ($word eq 'INSTRUMENT')
        {
            foreach my $platform ( keys %BUILDINFO )
            {
                if ( PlatformConfig::targetHasTag( $platform, 'INSTRUMENT') ) {
                    $BUILDINFO{$platform}{NOT_AVAILABLE} = 2;
                    Debug("ProcessBuildExclude. Remove $platform ");
                }
            }
        }
        elsif (exists $BUILDINFO{$word} )
        {
            $BUILDINFO{$word}{NOT_AVAILABLE} = 2;
            Debug("ProcessBuildExclude. Remove $word ");

        } else {
            abtWarning(0,'BuildExclude: Unknown target:' . $word ) 
        }
    }
    ErrorDoExit();
}

#-------------------------------------------------------------------------------
# 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=x1,x2:x3    : All use another platform
#                                           --Alias=y1,y2:x3   : All alias to this name
#
#                   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 @commonArgs = ();

    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 (\@commonArgs, $1 );

            } elsif ( /^(--Alias=.*)/ ) {
                UniquePush (\@commonArgs, $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 @arguments 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 = @commonArgs;
    my $target;
    foreach my $arg ( @new_args )
    {
        #
        #   Collect per-platform arguments
        #
        if ( $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 ( PlatformConfig::targetHasTag($target,'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, \@targs );

        @targs = @commonArgs;
        $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
#                   $pArgs                  - Ref to an array of platform arguments
#                                             Known: --Uses=aaa,bbb and --Alias=xxx,yyy
#
# Returns         :
#

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

    #
    #   Create a basic BUILDINFO entry
    #
    $buildentry{FNAME} = $fname;
    $buildentry{NOT_DEFAULT} = $notdefault if $notdefault;
    $buildentry{PRODUCT} = $product;
    $buildentry{TARGET} = $target;
    $buildentry{BASE} = $target;
    foreach ( @$pArgs )
    {
        if ( m~^--Alias=(.+)~ ) {
            foreach my $alias (split('[:,]',$1))
            {
                Error ("$fname() attempt to alias a keyword: $alias")
                    if ( isKeyword($alias) );
                push @{$buildentry{USERALIAS}},$alias;
            }
        } elsif ( m~^--Uses=(.+)~ ) {
            push @{$buildentry{USES}},split('[:,]',$1);

        } else {
            push @{$buildentry{ARGS}}, $_;
        }
    }

    #   Detect reserved words being misused as a platform name
    #   At the moment, the value of NATIVE and TOOLSET are calculate towards the end of the
    #   build process so that it can be limited to platforms that are present.
    foreach my $reserved ( qw (NATIVE TOOLSET INSTRUMENT))
    {
        Error("Invalid use of the platform alias $reserved","The $reserved alias cannot be used to define build platforms")
            if (uc($target) eq uc($reserved));
    }

    #
    #   Expand families of platforms into multiple platforms
    #       ie: DEVLINUX -> COBRA, VIPER, ....
    #
    foreach my $name (@PlatformConfig::BuildFamilies)
    {
        #
        #   Is this target a buildFamily alias
        #
        if (uc($target) eq uc($name))
        {
            #
            #   Get the list of targets that have been tagged with the family name
            #   Must exist - else its an internal error
            #   
            my @targets = PlatformConfig::getTargetsByTag($name);
            Error ("Internal: No platforms are tagged as members of $name") unless @targets;

            #   Instantiate all targets
            #       Give them an ALIAS for the Family name
            #       Register new enrty with the build system
            #
            foreach my $target ( @targets )
            {
                my $pInfo = dclone(\%buildentry);
                $pInfo->{ALIAS} = $pInfo->{TARGET};
                $pInfo->{TARGET} = $target;
                AddBuildPlatformEntry( $pInfo );
            }
            #   Mark the original entry as a TEMPLATE so that it won't be added
            $buildentry{TEMPLATE} = 1;
            last;
        }
    }
    
    #
    #   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};

    #
    #   Ensure that it exsists and is numeric
    #
    $pInfo->{NOT_AVAILABLE} = 0;

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

    #
    #   Detect GENERIC targets
    #       The Build file is only allowed to have one that can be built on any one machine
    #
    my $buildAvailability = PlatformConfig::targetHasTag( $target, 'MACHTYPE' );
    if (PlatformConfig::targetHasTag( $target, 'GENERIC' ) )
    {
        UniquePush (\@GENERIC_TARGETS, $target );
    }

    #
    #   Detect GENERIC_<machType> targets
    #
    if (PlatformConfig::targetHasTag( $target, 'GENERIC_MACHTYPE' ) )
    {
        $pInfo->{IS_GENERIC} = 1;
        $pInfo->{ALIAS} = 'GENERIC';
        $pInfo->{NOT_AVAILABLE} = 1 unless needToolset();
    }

    #
    #   Ensure target is known to JATS
    #   Remove unknown targets from the build. 
    #   Create a list of unknown targets and report them later.
    #
    unless ( $pInfo->{NOT_AVAILABLE} || exists $BUILDINFO{$target} || $pInfo->{IS_GENERIC}  )
    {
        my $base_target = PlatformConfig::targetHasTag( $target, 'BASE_TARGET' ) || $target;
        unless ( Exists( "$::GBE_CONFIG/PLATFORM", $base_target  ) )
        {
            UniquePush (\@BUILD_BADNAME, $target ) unless ($target eq 'NOBUILD'); 
            $pInfo->{NOT_AVAILABLE} = 3;
            $pInfo->{BADNAME} = 1;
        }
    }

    #
    #   Mark as NOT_AVAILABLE platforms that are not available on this machine
    #
    if ($buildAvailability)
    {
         $pInfo->{MACHTYPE} = $buildAvailability;
         $pInfo->{NOT_AVAILABLE} = 1 unless ($buildAvailability eq $::GBE_MACHTYPE)
    }

    #
    #   Extend the build information
    #
    unless ($pInfo->{NOT_AVAILABLE} )
    {
        if ( my $build_cfg = Require( "$::GBE_CONFIG/PLATFORM", "${target}.cfg"  ) )
        {
            Verbose ("Processing(add) Platform Configuration file: $build_cfg");
            my $package_name = "${target}_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} );

        #
        #   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}) && ! $Clobber)
    {
        abtWarning(1,"$fname() duplicate platform '$platform'");
        return;
    }

    #
    #   Add platform (tag) to various lists
    #
    UniquePush( \@BUILDPLATFORMS, $platform )    unless exists ($pInfo->{BADNAME});
    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 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 ) = @_;

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

    #
    #   Just save the arguments for later processing
    #
    push @BUILDARGUMENTS, \@_;
}

#-------------------------------------------------------------------------------
# Function        : ProcessBuildArgument  
#
# Description     : Process BuildArgument operations
#                   This needs to be done AFTER aliases have been created (cleaned up )
#
# Inputs          : Global data: @BUILDARGUMENTS 
#
# Returns         : 
#
sub ProcessBuildArgument 
{
    foreach my $set ( @BUILDARGUMENTS )
    {
        my ($platform, @arguments ) = @{$set};
        my @platforms;
        #
        #   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 @arguments 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 @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 =~/^--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, \@pargs  );

        #
        #   Reset collection variables for next platform
        #
        $platform = "";
        $notdefault  = 0;
        @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        : BuildExclude 
#
# Description     : Allow specific platforms to be excluded from the Build
#                   Intended use:
#                       Allow the use if a platform alias, but not all elements of it
#                       ie: Use DEVLINUX, but not ARM9TDMI as we no longer support it 
#                           in this version.
#                   Multiple BuildExclude directives are allowed
#                   Order or location is not important        
#
# Inputs          : Platforms names and options
#                       zzzz                Build Platform Name
#                       INSTRUMENT          (Keyword - no instrumented builds)
#                       
#
# Returns         : Nothing 
#
sub BuildExclude
{
    my( @arguments ) = @_;
    Debug( "BuildExclude(@arguments)" );

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

    #
    #   Simply save the arguments for later
    #   Allow multiple specs in the one definition
    #
    foreach ( @arguments)
    {
        Error ("Invalid format: $_") if m/[\s]/;
        UniquePush (\@BUILDEXCLUDE, split(/\s*,\s*/,$_));
    }
}

#-------------------------------------------------------------------------------
# Function        : BuildToolset 
#
# Description     : Indicate that this package will build binary parts of a JATS
#                   toolset.
#                   
#                   Should be used to indicate that it building non-binary parts
#                   too.
#                   
#                   Used as a sanity test.
#
# Inputs          : Options to limit the Machine Types affected 
#
# Returns         : 
#
sub BuildToolset
{
    my( @arguments ) = @_;

    $genToolsetPlatform = 1;
    Debug( "BuildToolset(@arguments)" );
    Error( "BuildToolset must appear before BuildName()..." )
        if ( $BUILDNAME ne "" );

    #
    #   Process TOOLSET specific options
    #       --ExcludeMachType=xxxx,yyy
    #       --MachType=xxxx,yyy
    #
    $toolsetData->{enableToolset} = 1;
    foreach ( @arguments )
    {
        if ( m~^--ExcludeMachType=(.+)~i ) {
            foreach my $arch (split(',',$1))
            {
                ReportError("BuildToolset: Unknown MachType: $arch") unless PlatformConfig::validMachType($arch);
                $toolsetData->{exclude}{lc $arch} = 1;
                if (lc($arch) eq lc($::GBE_MACHTYPE))
                {
                    Verbose("Exclude TOOLSET on this machine");
                    $toolsetData->{excludeThis} = lc($arch);
                }
            }
        } elsif ( m~^--MachType=(.+)~i ) {
            foreach my $arch (split(',',$1))
            {
                ReportError("BuildToolset: Unknown MachType: $arch") unless PlatformConfig::validMachType($arch);
                $toolsetData->{include}{lc $arch} = 1;
                if (lc($arch) eq lc($::GBE_MACHTYPE))
                {
                    Verbose("Include TOOLSET on this machine");
                    $toolsetData->{includeThis} = lc($arch);
                }
            }

        } else {
            ReportError ("BuildToolset: Unknown option: $_");
        }
    }

    if ( exists $toolsetData->{exclude} && exists $toolsetData->{include}) {
        ReportError ("BuildToolset: Cannot use both --MachType and --ExcludeMachType");
    }
    ErrorDoExit();

    #
    #   Show we build for this architecture
    #   Have three conditions:
    #       1) Current machType specifically excluded
    #       2) Current machtype specifically included
    #       3) Neither - further processing required
    #
    $genToolsetActive = 0;
    if (exists $toolsetData->{excludeThis}) {
        $toolsetPlatform = 'Excluded';
        return;
    }

    if (exists $toolsetData->{include}) {
        if (exists $toolsetData->{includeThis}) {
            $genToolsetActive = 1;
        } else {
            $toolsetPlatform = 'Not included';
        }
        return;
    }

    $genToolsetActive = 1;
    return;
}


#-------------------------------------------------------------------------------
# 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};
    $BUILDBASEVERSION  = $build_info->{BUILDNAME_BASE_VERSION};

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

    #
    #   Clobber processing done after values have been accumulated
    #   as they may be used later
    #
    return if ( $Clobber );
    ToolsetFiles::AddFile('build.log') unless ($BuildInfo);
    
    #
    #   Determine type of sandbox
    #
    $sandbox_exact = ( -f $::GBE_DPKG_SBOX . '/.exact' )
        if ( $::GBE_DPKG_SBOX );
    
#.. 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( "Legacy Mode. Enabled" ) if $::LegacyMode;

    Log( "Build dir... $CwdFull" ) if defined($::GBE_ABT) || $::GBE_DPKG_SBOX;
    Log( "Build Mach.. $::GBE_HOSTNAME" ) if defined($::GBE_ABT);
    Log( "Build Cmd .. $CmdSwitch") if $CmdSwitch;
    Log( "Build Info . $BuildInfoFile") if $BuildInfo;

    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( "EXCLUDE .... " . PrintList([@BUILDEXCLUDE], $sep) )    if (@BUILDEXCLUDE);
    Log( "BUILDFILTER. " . PrintList([split(' ', $::GBE_BUILDFILTER)], $sep) ) if defined ($::GBE_BUILDFILTER);

    Log( "DPKG_STORE.. $::GBE_DPKG_STORE" );
    Log( "DPKG ....... $::GBE_DPKG" );
    Log( "DPKG_REPLI . $::GBE_DPKG_REPLICA" );
    Log( "DPKG_ESCROW. $::GBE_DPKG_ESCROW" ) if $::GBE_DPKG_ESCROW;
    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_DPKG_SBOX/buildfilter") if ( $::GBE_DPKG_SBOX && -f $::GBE_DPKG_SBOX . '/buildfilter' );

    #
    #   Generate a list of active platforms
    #   Ensure that there are some active platforms
    #
    ProcessBuildExclude();
    GeneratePlatformList();
    Log( "Platforms .. " . PrintPlatforms(\@BUILDPLATFORMS, $sep) );

    #
    #   Detect a mix of Generic and non Generic targets
    #       Cannot mix generic and non-generic targets
    #
    if ($#GENERIC_TARGETS >= 0 && $#BUILDPLATFORMS >= 0)
    {
        if ($#BUILDPLATFORMS != $#GENERIC_TARGETS )
        {
            Verbose("Active:", @BUILD_ACTIVEPLATFORMS);
            Verbose("Generic:", @GENERIC_TARGETS);
            Error("Cannot mix GENERIC and non-GENERIC targets in the one build");
        }
    }

    #
    #   Build System Generic Sanity Test
    #       If Generic   then MUST be a GENERIC build
    #       If NoGeneric then MUST not be a GENERIC build
    #
    if (defined $GenericBuild)
    {
        if ( scalar(@GENERIC_TARGETS) ne $GenericBuild)
        {
            Error("Generic build inconsistency",
                  "Release Manager entry indicates: $GenericBuild",
                  "Build File indicates: " . scalar(@GENERIC_TARGETS)
                  );
        }
    }

    unless( @BUILD_ACTIVEPLATFORMS )
    {
        my $msg = 'GBE_BUILDFILTER prevents any targets being built';
        if (defined($::GBE_ABT) || defined($::GBE_SANDBOX )) {

            # 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;
            $genToolsetActive = 0;
            Log( "Build for .. ". PrintPlatforms(['NOBUILD - ' . $msg], $sep));

        } else {
            Error( $msg );
        }
    }
    else
    {
        Log( "Build for .. ". PrintPlatforms(\@BUILD_ACTIVEPLATFORMS, $sep));
    }

    ProcessToolSetPlatform() if $genToolsetActive;
    Log( "Toolset .... " . $toolsetPlatform ) if $genToolsetPlatform;
    Error ("No suitable TOOLSET platform found") if (($genToolsetActive > 1) && $::GBE_ABT);

    #
    #   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 ( $#GENERIC_TARGETS >= 0 )
        {
            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));
        }

    }

    #
    #   NoBuilds do not generate warnings
    #
    return if $NoBuild && !$BuildInfo;

    #
    #   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) );
        abtWarning (1,"The following platform names are not known to JATS", "@BUILD_BADNAME");
    }

    #
    #   Detect multiple GENERIC targets
    #       Only one such target can be processed on any one machine
    #
    if ($#GENERIC_TARGETS > 0)
    {
        Error ("Multiple GENERIC targets detected", PrintPlatforms(\@GENERIC_TARGETS, $sep));
    }

    generateBuildInfo() if ($BuildInfo);
    return 1;
}

#-------------------------------------------------------------------------------
# Function        : generateBuildInfo 
#
# Description     : Using in BuildInfo mode to save the required information
#
#                   In BuildInfo mode we have all the data that we need
#                   Save it into a file for the build system and then exit the build process
#                   We do not need to do any further work at this point in time
#
# Inputs          : Globals 
#
# Returns         : This function does not return
#                   It will exit as there is no more work to be done
#                   
#                   Will generate a JAVA properties file with BuildInfo required by the
#                   Build System.
#
sub generateBuildInfo
{
    DumpCapture();
    Message ("Generate BuildInfo Data: ". $BuildInfoFile);
    my @propLines = ();


    my $fh = ConfigurationFile::New( $BuildInfoFile, '--Type=Properties' );
    $fh->HeaderSimple( "buildlib (Version $BuildVersion)", "BuildInfo properties" );

    # Build style
    push @propLines, "buildstyle = JATS";

    # Current Build Filter
    push @propLines, "buildfilter = " . join(',', sort split(' ', $::GBE_BUILDFILTER));

    # All platforms that the package can be built for.
    push @propLines, "platforms = " . join(',', sort @BUILDPLATFORMS);

    # All platforms that the package will be built for with the current build filter
    my @AllCanBuild;
    my %filterMap;
    foreach ( split(' ', $::GBE_BUILDFILTER) ) {
        $filterMap{$_} = 1;
        my $genericMachType = PlatformConfig::getGenericMachType($_);
        if ($genericMachType) {
            $filterMap{$genericMachType} = 1;
        }
    }
    push (@AllCanBuild, 'GENERIC') if ($#GENERIC_TARGETS >= 0);

    foreach ( @BUILDPLATFORMS ) {
        if (exists $filterMap{$_}) {
            push @AllCanBuild, $_;
        }
    }
    push @propLines, "build.platforms = " . join(',', sort @AllCanBuild);

    # Platforms that have been specifically excluded from the build
    push @propLines, "excluded.platforms = " . join(',', sort @BUILDEXCLUDE);

    # Platforms not known to JATS
    # Only works in ABT mode. Errors will be detected before this
    push @propLines, "unknown.platforms = " . join(',', sort @BUILD_BADNAME);
    
    # Toolset build information
    CalcToolsetBuildInfo(@AllCanBuild);
    if (exists $toolsetData->{platforms}) {
        push @propLines, "toolset.platforms = " . join(',', sort @{$toolsetData->{platforms}});
    }

    # Indication of production and debug builds
    my (@OnlyProd, @OnlyDebug);
    foreach my $platform ( sort keys %BUILDPLATFORMARGS) {
        foreach my $arg ( split($;, $BUILDPLATFORMARGS{$platform})) {
            push (@OnlyProd,  $platform) if ($arg =~ m~--OnlyProd~);
            push (@OnlyDebug, $platform)  if ($arg =~ m~--OnlyDebug~);
        }
    }
    push @propLines, "prod.platforms = " . join(',', sort @OnlyProd) ;
    push @propLines, "debug.platforms = " . join(',', sort @OnlyDebug) ;

    # JAVA version specified
    if ($BUILDPLATFORMARGS{JAVA}) {
        foreach my $arg ( split($;, $BUILDPLATFORMARGS{JAVA})) {
            if ($arg =~ m~^--Version=(.*)~i) {
                push @propLines, "java.version = " . $1;
                last;
            }
        }
    }

    # The package is GENERIC
    if ($#GENERIC_TARGETS >= 0) {
        push @propLines, "generic.build = 1";
    }

    $fh->WriteLn(@propLines);
    $fh->Close();

    # Display for user gratification (and build system logging)
    Information($BuildInfoFile,@propLines);
    exit 0;
}

#-------------------------------------------------------------------------------
# Function        : needToolset 
#
# Description     : Internal. Determine if this machine needs to build for
#                   a TOOLSET or a GENERIC_<MachType>
#                   
#                   In Build System
#                       BUILDFILTER must identify the machine performing TOOLSET builds
#                       
#                       The build filter MUST contain the psuedo platform TOOLSET in order for 
#                       the alias to be created. This requires that the build daemons be configured to 
#                       specify a TOOLSET. 
#                       
#                       Care MUST be taken to not configure multiple TOOLSET candiates 
#                       on similar machine types.
#                       
#                   Non Build Sytem (User Development)
#                       True    
#
# Inputs          : 
#
# Returns         : 
#
sub needToolset
{
    my $toolsetNeeded;

    $toolsetNeeded = 1 if (!defined($::GBE_ABT));
    if (!$toolsetNeeded && defined($::GBE_BUILDFILTER) ) {
        $toolsetNeeded = grep( /^TOOLSET$/, split( ' ', $::GBE_BUILDFILTER ) );
    }

    return $toolsetNeeded;
}

#-------------------------------------------------------------------------------
# Function        : ProcessToolSetPlatform 
#
# Description     : Locate the TOOLSET platform if required
#                   In Build System
#                       BUILDFILTER must identify the machine performing TOOLSET builds
#                       The first suitable target will be chosen
#                       
#                       The build filter MUST contain the psuedo platform TOOLSET in order for 
#                       the alias to be created. This requires that the build daemons be configured to 
#                       specify a TOOLSET. 
#                       
#                       Care MUST be taken to not configure multiple TOOLSET candiates 
#                       on similar machine types.
#                       
#                   Non Build Sytem (User Development)
#                       The first suitable target will be chosen    
#
# Inputs          : 
#
# Returns         : 
#
sub ProcessToolSetPlatform
{
    my $toolsetNeeded;
    my $toolset;

    #
    #   User is not allowed to create a TOOLSET alias
    #       This should be captured before here, but ...
    #
    if (exists $BUILDALIAS{TOOLSET})
    {
        Error('Internal: User has manually specified a TOOLSET alias' );
    }

    #
    #   Determine if we need to do any work
    #
    return unless needToolset();

    #
    #   Need to ensure that we have a TOOLSET platform in the build set
    #
    my %activePlatformMap = map {$_ => 1} @BUILD_ACTIVEPLATFORMS;
    my @toolsetTargets = PlatformConfig::getTargetsByTag('TOOLSET');
    foreach my $item (@toolsetTargets)
    {
        if (exists($activePlatformMap{$item}))
        {
            #
            #   Add TOOLSET arguments into the existing target
            #   Really only expecting --OnlyProd
            #
            if ( @genToolsetArgs) {
                PlatformArgument($item, @genToolsetArgs);
            }

            # Update the displayed Toolset platform
            $toolset = $item;
            $toolsetPlatform = $toolset;
            last;
        }
    }

    #
    #   No toolset platform found in the current build set
    #   
    unless ($toolset) {
        $toolsetPlatform = "None found in current build set"; 
        $genToolsetActive = 2;
        return;
    }

    #
    #   Insert alias information
    #
    $BUILDALIAS{TOOLSET} = $toolset;
    push @{$BUILDINFO{$toolset}{USERALIAS}}, 'TOOLSET';
}

#-------------------------------------------------------------------------------
# Function        : CalcToolsetBuildInfo 
#
# Description     : Calculate the Toolset component of the BuildInfo
#                   Note: Similar to ProcessToolSetPlatform (above)
#                   
#                   Need to determine the most suitable platform for building toolsets. 
#                   Only consider those that are a part of the build set
#
# Inputs          : AllPlatforms    - an Array of all platrform that this package can
#                                     be built on with the current build filter 
#
# Returns         : 
#
sub  CalcToolsetBuildInfo {
    my (@allPlatforms) = @_;
    return unless $toolsetData->{enableToolset};
       
    my %toolsetSelected;
    my %platformMap = map {$_ => 1} @allPlatforms;
    my @toolsetTargets = PlatformConfig::getTargetsByTag('TOOLSET');
    foreach my $item (@toolsetTargets)
    {
        if (exists($platformMap{$item}))
        {
            my $include = 0;
            my $toolMachType = PlatformConfig::targetHasTag($item, 'MACHTYPE');

            # Specifically included
            if (exists $toolsetData->{include}){
                if (exists $toolsetData->{include}{ lc $toolMachType}) {
                    $include = 1;
                }
            # Specifically excluded
            } elsif (exists $toolsetData->{exclude}) {
                unless (exists $toolsetData->{exclude}{ lc $toolMachType}) {
                    $include = 1;
                }
            # Default
            } else {
                $include = 1;
            }

            if ($include) {
                unless (exists $toolsetSelected{$toolMachType}) {
                    $toolsetSelected{$toolMachType} = $item;
                    push @{$toolsetData->{platforms}}, $item;
                }
            }
        }
    }
#DebugDumpData("toolsetSelected", \%toolsetSelected);
}

#-------------------------------------------------------------------------------
# Function        : BuildPreviousVersion 
#
# Description     : Deprecated. Do not use 
#
# Inputs          : 
#
# Returns         : 
#
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" );
    }

    #
    #   Generate the required directory
    #
    BuildInterfaceInternal($ifdirname);
    Log( "Interface .. $ifdirname" );
    return 1;
}

#-------------------------------------------------------------------------------
# Function        : BuildInterfaceInternal  
#
# Description     : Internal Use Only
#                   Guts of the BuildInterface processing 
#
# Inputs          : $ifdirname  - Name of an interface directory
#
# Returns         : 
#

sub BuildInterfaceInternal
{
    my( $ifdirname ) = @_;
    my @createDirs;

    push @CLOBBERDIRS, $ifdirname;

    if ( $ifdirname eq "local" ) {
        push @createDirs, "$ifdirname/inc";
        $BUILDLOCAL = "local";

    } else {
        push @createDirs, "$ifdirname/include";
        $BUILDINTERFACE = $ifdirname;
        $::ScmInterface = $ifdirname;

        #
        #   Set up the local cache
        #
        my $iCache = "$ifdirname/dpkg_cache";
        if ($BUILDINTERFACE && $opt_localCache) {
            my $iCache = "$ifdirname/dpkg_cache";
            push @createDirs, $iCache;

            $Cache = $opt_localCache;
            $::GBE_DPKG_CACHE = FullPath($iCache);
            $ENV{GBE_DPKG_CACHE} = $::GBE_DPKG_CACHE; 
        } elsif (-d  $iCache ){
            #$Cache = 1 if $Cache == 0;
            $::GBE_DPKG_CACHE = FullPath($iCache);
            $ENV{GBE_DPKG_CACHE} = $::GBE_DPKG_CACHE; 
        }
    }

    unless ($Clobber) {
        push @createDirs, "$ifdirname/bin";
        push @createDirs, "$ifdirname/lib";

        foreach my $dir ( @createDirs)
        {
            mkpath($dir);
        }
        ToolsetFiles::AddDir($ifdirname, 'Internal');
    }
}



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;
    }
}

#-------------------------------------------------------------------------------
# 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 -wait -quiet $name/$version" );
    }

    #
    #   Locate the package ONCE
    #
    Log( "LinkPkgArchive .. $name ($version)" );
    my ($pkg, $local, $pkgSig ) = PackageLocate( $name, $version );
    if ( $pkg )
    {
        #
        #   Create a Package Entry
        #
        my $entry = PackageEntry::New( $pkg, $name, $version, 'link', $local, $pkgSig );

        #
        #   Generate package rules for each active platform
        #
        IncludePkg ( $name, $pkg );
        foreach my $platform ( @BUILD_ACTIVEPLATFORMS ) {
            LinkEntry( $platform, $entry );
        }
    }
}

#-------------------------------------------------------------------------------
# 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
#                   Package Signature
#
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_ESCROW),
                       '--NotEscrow',
                       split( $::ScmPathSep, $::GBE_DPKG_REPLICA),
                       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 ( $dpkg eq '--NotEscrow' )
        {
            last if ($::GBE_DPKG_ESCROW);
            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;
        }

        $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;
            $pkg = TagFileRead($pkg);
            $pkg =~ s~\\~/~g;
            if ($pkg =~ s~^GBE_SANDBOX/~$::GBE_SANDBOX/~)
            {
                    $pkgFromSandbox++;

                    # If the target sandbox is in the 'deploymode' then the package
                    # will not be in the expected location. It will be in a 'build/deploy'
                    # subdir. Remove the pkg/name dir to get to the root of the package
                    my @dirs = File::Spec->splitdir( $pkg );
                    splice(@dirs, -2);
                    my $deployBox = catdir(@dirs, 'build', 'deploy');
                    $pkg = $deployBox if ( -d $deployBox);
            }

            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");
            #print( "                  Cached -> $pkg\n" );
        }

        #
        #   Use the first suitable package found
        #..

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

    #
    #   Package not found
    #       This is an error, although it can be bypassed
    #       In an sandbox it will be ignored if marked as a noBuild
    #       
    my( $link_dir, $base) = genSandboxFile(SplitPackage ($name, $uversion ));
    if ( $link_dir ) {
        my $nob_path  = "$link_dir/$base.nob";
        if (-f $nob_path) {
            Log( "WARNING .... Package flagged as noBuild: '$name/$version'" );
            return;
        }
    }

    Error ("Required package not found: '$name/$version'" ) unless ( $NoPackageError );

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


#-------------------------------------------------------------------------------
# Function        : LinkEntry
#
# Description     : Scan a package and locate platform specific directories
#                   Create data structures to capture the information
#                   This function is used by LinkPkgArchive
#                   to perfom the bulk of package inclusion work.
#
# Inputs          : $platform   - Platform being processed
#                   $entry      - Reference to a PackageEntry
#
sub LinkEntry
{
    my( $platform, $base_entry ) = @_;

    #
    #   Clone the entry - we will add platform specific data into it
    #
    my $entry = dclone($base_entry);


    #   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 );
        $entry->RuleInc( "/inc." . $part );
        $entry->RuleInc( "/include/" . $part );
        $entry->RuleInc( "/inc/" . $part );
    }

    #
    #   Also search the root include directory - last
    #
    $entry->RuleInc( "/include" );
    $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" );
        $entry->RuleLib("/lib" . "/lib.$part" );
        $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 -wait -quiet $name/$version" );
    }

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

        #
        #   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 = "$CwdFull/$BUILDINTERFACE/BuildTags";
        my $tag_file = "$tag_dir/${name}_${version}.tag";
        my $arglist = GenerateInstallArgumentList();

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

        #
        #   Determine the package format and use the appropriate installer
        #   Supported formats
        #       1) Package has a descpkg file (only style currently supported)
        #
        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
                #
                
                System( "cd $pkg; $::GBE_PERL $::GBE_TOOLS/installpkg.pl $CwdFull/$BUILDINTERFACE $CwdFull @opts $arglist");
                Error( "Package installation error" ) if ( $? != 0 );
            }
            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 );
            FileCreate( $tag_file, $arglist );
        }

        #
        #   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        : LinkPkgExclude 
#
# Description     : Mark a package to be excluded / included for a specific build
#                   Intended use:
#                       Recover from really (reaaly) badly constructed packages
#                       
#                   Essentially this directive will 'Exclude' packages from the
#                   a specific build target (platform) by removing it from data
#                   structures that are exported to the make stage
#                   
#                   Process ONLY works for LinkPkgArchive packages
#
# Inputs          : platform-selector   - A JATS style platform specifier
#                   package-list        - one or or packages to process
#                   
# Example         : LinkPkgExclude('!SK100', 'PulseSdk');
#                   LinkPkgExclude('SK100' , 'crypto');
#
sub LinkPkgExclude
{
    DataDirective('LinkPkgExclude');
    Error("LinkPkgExclude(@_) requires at least two arguments") unless ((scalar @_) >= 2);
    #
    #   Simply save the arguments for later processing
    #
    push @LINKPKGEXLUDES, join($;, @_);
}

#-------------------------------------------------------------------------------
# Function        : ProcessLinkPkgExclude 
#
# Description     : INTERNAL Function
#                   Process the data collected by the LinkPkgExclude directives  
#
# Inputs          : None
#                   Uses: LINKPKGEXLUDES    
#
# Returns         : Will modify interal classes and data structures
#
sub ProcessLinkPkgExclude
{
    my @unknownPlatforms;
    my @unknownPkg;
    my @notLinkPkg;

    #
    #   Create a hash of known platforms - detect and report bad user input
    #
    my %fullPlatformList =  map { $_ => 1 } @BUILDPLATFORMS;

    #
    #   Process each directive
    #
    foreach ( @LINKPKGEXLUDES )
    {
        my (@add, @remove);
        my ($pSel, @pkgList) = split($;, $_);
        Log("LinkPkgExclude $pSel, @pkgList");


        #
        #   Process the selector in the same manor as makefiles
        #       Need the selector to be active
        #       Selector may be an alias or a platform
        #       Selector may be negated
        #       Selectors are comma seperated
        #       Additive elements are processed before subtactive elements
        #           
        my @pItems = split(/\s*,\s*/, $pSel);
        @pItems = ExpandPlatforms( @pItems );

         foreach my $platform ( @pItems) {
             my $pname = $platform;
             my $invert;

             if (substr($platform, 0, 1) eq '!') {
                 $invert = 1;
                 $pname = substr($platform, 1)
                 }

             unless (exists($fullPlatformList{$pname})) {
                 UniquePush(\@unknownPlatforms, $pname);
                 next;
                 }

             if ( $invert )  {
                 push @remove, $pname; 
             } else {
                 push @add, $pname; 
             }
         }

         #
         #   Build complete list of allowed platforms
         #       Process additive rules before removal rules
         #       If there are no additive rules, then assume all protaforms
         #
         my %calcList;
         @add = @BUILDPLATFORMS unless @add;
         $calcList{uc $_} = 1 foreach (@add);
         delete $calcList{uc $_} foreach (@remove);
         Verbose2("LinkPkgExclude ", keys %calcList,",", @pkgList);

         #
         #  Now have a list of platforms to process (exclude)
         #  Now have a list of packages to exclude
         #
         #  Iterate over the package rules and mark those to be excluded
         #
         foreach my $platform ( keys %calcList )
         {
             unless (exists($PKGRULES{$platform})) {
                 next;
             }

             foreach my $ePackage (@pkgList) {
                 my $pkgFound;
                 foreach my $package ( @{$PKGRULES{$platform}} )
                 {
                     my $fname = $package->{'dname'};
                     if ( $package->{'dproj'}) {
                         $fname .= '.' . $package->{'dproj'};
                     }

                     if ( $ePackage eq $package->{'dname'}  || $ePackage eq $fname) {
                         $pkgFound = 1;

                         if ($package->{'type'} ne 'link') {
                             UniquePush(\@notLinkPkg, $ePackage);
                             last;
                         }

                         $package->{'EXCLUDE'} = 1;
                         Debug("Exclude", $platform, $fname );
                         last;
                     }
                 }
                 UniquePush(\@unknownPkg, $ePackage) unless $pkgFound;
             }
         }
   }

    ReportError("LinkPkgExclude. Unknown platforms:", @unknownPlatforms) if @unknownPlatforms;
    ReportError("LinkPkgExclude. Unknown packages:", @unknownPkg) if @unknownPkg;
    ReportError("LinkPkgExclude. BuildPkgArchive not allowed for:", @notLinkPkg) if @notLinkPkg;
    ErrorDoExit();
}

#-------------------------------------------------------------------------------
# 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( "$CwdFull/$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) Platforms excluded in the build.pl file
#                       3) 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
#                   For a BuildProduct( AA,BB,CC)
#                       Product     - AA
#                       Targets     - BB, CC
#                       Platforms   - AA_BB, AA_CC
#
#                   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
#                   Rebuilds BUILDPLATFORMS to ensure that excluded platforms are not present
#
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;
        my @New_BUILDPLATFORMS = ();

        #
        #   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};

            #
            #   Include into New_BUILDPLATFORMS 
            #
            if ($pParts->{NOT_AVAILABLE} <= 1) {
                push @New_BUILDPLATFORMS, $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 = uc $::GBE_BUILDFILTER
                if ( defined($::GBE_BUILDFILTER) );
        }
        Debug( "GeneratePlatformList: Filter:$platform_filter" );

        #
        #   Detect the special cases
        #       1) No user definition
        #       2) First word contains a subtractive element
        #   And assume all platforms    
        #
        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'} ;
        }

        #
        # Add GENERIC platforms to the platform filter, if available
        #
        if (exists $part_to_platform{'PLATFORM'})
        {
            foreach my $platform( 'GENERIC', 'GENERIC_' . uc($::GBE_MACHTYPE) )
            {
                $result{$platform} = 1 if (exists $part_to_platform{'PLATFORM'}{$platform}) 
            }
        }

#DebugDumpData( "PartToPlatform", \%part_to_platform );
#DebugDumpData("Result", \%result);

        #
        #   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 PLATFORMs.
                #
                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;

        #
        #   Update BUILDPLATFORMS
        #
        @BUILDPLATFORMS = @New_BUILDPLATFORMS;
    }

    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 >100.
#
# Inputs          : $list           - Reference to an array
#                   $nl             - New line stuff.
#                                     Use to prefix new lines
#
# Returns         : Formatted 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) = "";
    my $maxLen = 100 - length($nl);

    if ( @$platforms )
    {
        my $line = "";
        my $pel = "";
        my $first = 1;
        my $prefix = '';
        foreach my $k ( sort @$platforms)
        {
            my $k2 = substr($k, 0 , 2);
            if (( $k2 ne $pel  || (length($line) + length ($k) > $maxLen) ) && (!$first)) {
                $string .= $line;
                $line = $nl;
                $prefix = '';
            }
            $line .= $prefix . $k;
            $pel = $k2;
            $first = 0;
            $prefix = ' ';
        }
        $string .= $line;
    }
    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 ($platforms, $nl) = @_;
    my ($string) = "";
    my $maxLen = 100 - length($nl);

    if ( @$platforms )
    {
        my $line = "";
        my $first = 1;
        my $prefix = '';
        foreach my $k ( sort @$platforms)
        {
            if ((length($line) + length ($k) > $maxLen) && (!$first)) {
                $string .= $line;
                $line = $nl;
                $prefix = '';
            }
            $line .= $prefix . $k;
            $first = 0;
            $prefix = ' ';
        }
        $string .= $line;
    }
    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
{
    Warning("BuildReleaseFile directive does nothing and should be removed") unless $Clobber;
}

#-------------------------------------------------------------------------------
# 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
{
    Warning("BuildSnapshot directive does nothing and should be removed") unless $Clobber;
}

#-------------------------------------------------------------------------------
# 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          : $platform   - In ABT mode. Process under this platform name
#
#
# Returns         : 
#
sub BuildSrcArchive
{
    my ($platform) = @_;
    Error ("BuildSrcArchive requires one platform specifier") unless (defined $platform);


    #
    #   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");

    #
    #   If not in ABT mode, then build archive on developers machine
    #   In ABT mode only build the archive on a machine whose platform name is in the build filter
    #
    my $doBuild;
    if (defined($::GBE_ABT))
    {
        if (defined ($::GBE_BUILDFILTER))
        {
            $doBuild  = grep( /^$platform/, split( ' ', $::GBE_BUILDFILTER ) );
        }
        unless ( $doBuild )
        {
            Log( "SrcPackage . Not on this machine" );
            return; 
        }
    }


    #
    #   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'\.svn$' );
        next if ( m'\.git$' );
        next if ( m'\.cvs$' );
        next if ( m'local_dpkg_archive$' );
        next if ( m'\.jats.packageroot$' );
        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
{
    Warning("BuildAccessPerms directive does nothing and should be removed") unless $Clobber;
}


sub BuildSetenv
{
    Warning("BuildSetenv directive does nothing and should be removed") unless $Clobber;
    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();

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

    #
    #   Calculate defined aliases
    #       Limit the Aliases to active platforms
    #       ie: NATIVE INSTRUMENT PKG_WIN PKG_RPM PKG_DEB SK
    #
    my %activePlatformMap;
    foreach my $item ( keys %BUILDINFO) {
           my $pInfo = $BUILDINFO{$item};
           next if $pInfo->{NOT_AVAILABLE} > 1; 
           push @{$activePlatformMap{$pInfo->{TARGET}}}, $item;
    }

    foreach my $alias ( @PlatformConfig::BuildAliases )
    {
        if (exists $BUILDALIAS{$alias})  
        {
            # Will occur if GBE_ABT has been set, for backward compatibility
            Warning("User has manually specified a $alias alias",'Default alias will not be set.');
            #DebugDumpData("BUILDALIAS", \%BUILDALIAS);
        }
        else
        {
            my @activeAliases;
            foreach my $item (PlatformConfig::getTargetsByTag($alias)) {
                if (exists($activePlatformMap{$item})) {
                    push (@activeAliases, @{$activePlatformMap{$item}});
                }
            }

            $BUILDALIAS{$alias} = join(' ', @activeAliases) if (@activeAliases);

            #
            #   Add to the build entry too
            #
            foreach my $aliasTarget (@activeAliases) {
                push @{$BUILDINFO{$aliasTarget}{USERALIAS}}, $alias;
            }
        }
    }
    CleanUp_Aliases();
    ProcessBuildArgument();
    ProcessLinkPkgExclude();

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

    #
    #   Sanity test the users packages
    #       In a sandbox all bet are off
    #
    PackageEntry::SanityTest() unless ($Clobber || $::GBE_SANDBOX);

    #
    #   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 )
#                   
#                   Check:
#                       Time stamp of build.pl
#                       Time stamp of Makefile.gbe
#                       BuildFilter has not changed
#                       Signature of dependencies has not changed
#                       No packages consumed from within the sandbox
#
# Inputs          : None
#
# Returns         : May not return
#                   Will return if we need to perform a build
#
sub TestForForcedBuild
{
    #
    #   Always return if in clobber mode
    #
    return if ( $Clobber );

    if ( ! $ForceBuild  )
    {
        my @build_warn;
        my $bstamp = -M "$CwdFull/$ScmBuildSrc";
        my $tstamp = -M "$CwdFull/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.gbe 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 any of the imported packages are from a sandbox, then we must force a build
        #   and a make.
        #
        if ($pkgFromSandbox)
        {
            push @build_warn, "Consuming packages from within the sandbox";
        }
        
        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;

    #
    #   In the build system we do NOT want to consume packages from within the sandbox
    #   because they may be partially formed. They need to be consumed from a published
    #   archive.
    #   
    #   If we are in an ABT sandbox, then don't create a link file
    #   
    return if (defined($::GBE_ABT));

    #
    #   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($CwdFull, 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 = $CwdFull;
    }
    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 || $NoBuild )
    {
        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        : genSandboxFile 
#
# Description     : Generate the base part of a sandbox file name 
#
# Inputs          : $pkgName
#                   $pkgVersion
#                   $pkgSuffix
#
# Returns         : Base part of a sandbox file name
#                       Undef if not in a sandbox
#
sub genSandboxFile
{
    my ($pkgName, $pkgVersion, $pkgSuffix) = @_;

    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;
    }

    #
    #   Need to supporttwo forms of pkgsuffix
    #   With and without a leading '.'
    #   
    if (defined $pkgSuffix ) {
        unless ($pkgSuffix =~ m~^\.~) {
            $pkgSuffix = '.' . $pkgSuffix;
        }
    }

    #
    #   Create a name for this package in the sandbox
    #       Must use the package name and extension - unless exact sandbox
    #       Must use packahe name, version and extension if its an exact sandbox
    #
    my $link_dir = "$sandbox_dpkg_archive/$pkgName";
    my $base = 'sandbox' . $pkgSuffix; 
    if ($sandbox_exact) {
        $base = 'sandbox.' . $pkgVersion . $pkgSuffix; 
    }

    return $link_dir, $base;
}

#-------------------------------------------------------------------------------
# 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
#                   Creates:
#                       sandbox.int     - Rel path to the packages interface
#                       sandbox.ffp     - Fast FingerPrint over the package body
#                       sandbox.nob     - No Build marker
#
#                   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( $link_dir, $base) = genSandboxFile($BUILDNAME_PACKAGE, $BUILDNAME_VERSION, $BUILDNAME_SUFFIX );
    return unless ( $link_dir );

    my $nob_path  = $base . ".nob";
    my $ffp_path  = $base . ".ffp";
    my $int_path  = $base . ".int";

    $nob_path = "$link_dir/$nob_path";
    $ffp_path = "$link_dir/$ffp_path";
    $int_path = "$link_dir/$int_path";

    if ( $Clobber )
    {
        rmdir $link_dir;            # Delete only if empty
    }
    else
    {
        ToolsetFiles::AddFile($nob_path);
        ToolsetFiles::AddFile($ffp_path);
        ToolsetFiles::AddFile($int_path);

        #Log( "Sandbox Data. $base");
        unlink $int_path;
        mkdir $link_dir;

        #
        #   File with path to the interface directory
        #   Relative to the base of the sandbox
        #
        FileCreate($int_path, CatPaths('GBE_SANDBOX',RelPath($CwdFull,$::GBE_SANDBOX),$BUILDINTERFACE ));

        #
        #   Indicate packages not build on this machine
        #
        unlink $nob_path;           # Delete the NoBuild marker
        if ($NoBuild) {
            TouchFile($nob_path);
        }
    }
}


#-------------------------------------------------------------------------------
# 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
    #
    BuildInterfaceInternal($::ScmInterface) unless ( $BUILDINTERFACE );


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

    #
    #   Now that the bulk of the information has been displayed
    #   we can display captured messages. These warnings will be 
    #   at the end of the log so that users can see them.
    DumpCapture();

    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 ?
    {
        #
        #   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";

        #
        #   List of files maintained by the build system
        #
        my @toolsetFiles = ToolsetFiles::GetFiles();

        #
        #   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
            }
        }

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

        BuildPackageLink();
        BuildSandboxData();
        return;
    }
    #
    #   Generate the path to the descpkg file
    #   The file is created later in the build proccess, but the makefile generation 
    #   needs to have a known path to the file.
    #    
    #   It will be a file that is 'known' to JATS
    #
    $descpkgPath = BuildAddKnownFile ( $NoBuild ? $CwdFull : $Srcdir, 'descpkg' );

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

    #
    #  ONLY (re)building interface dir
    #
    unless ( $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, "$CwdFull";                         # 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 package signature
    #   Write the descpkg file again - with a signature this time
    #
    ErrorConfig( 'name' => 'buildlib')   ;
    WriteDescpkg();
    NoBuildMarker();

    #
    #   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.
#                         
#                   NOTE: This directive is now ignored
#                         The descpkg file is generated internally
#
# Inputs          : $mode - 'Internal' - Skip sanity test
#
# Returns         :
#
sub BuildDescpkg
{
    StartBuildPhase();                  # Starting the build phase. No more data collection
}

#-------------------------------------------------------------------------------
# Function        : WriteDescpkg 
#
# 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 WriteDescpkg
{
    return if ( $Clobber );                 # clobber mode ?

    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:          $CwdFull";
    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 ($descpkgPath, \@desc );
}

#-------------------------------------------------------------------------------
# Function        : NoBuildMarker
#
# Description     : Maintain the nobuild marker
#                   This file is created to indicate that this build does nothing useful
#                   Used by:
#                       'create_dpkg' utility that this build does not do anything useful.
#                                     but on a build machine we still need to generate a package fragment
#
#                   Used on a build machine by the build daemon
#                   Used in a sandbox
#
# 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( $::ScmRoot, '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\";
\$ScmBuildBaseVersion           = \"$BUILDBASEVERSION\";
\$ScmBuildPreviousVersion       = \"$BUILDPREVIOUSVERSION\";
\$ScmLocal                      = \"$BUILDLOCAL\";
\$ScmDeploymentPatch            = \"$DEPLOY_PATCH\";
\$ScmSrcDir                     = \"$Srcdir\";
\$ScmBuildSrc                   = \"$ScmBuildSrc\";
\$ScmExpert                     = \"$Expert\";
\$ScmAll                        = \"$All\";
\$ScmNoBuild                    = \"$NoBuild\";
\$ScmBuildUuid                  = \"$BUILD_UUID\";
");

#.. 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 $pInfo = $BUILDINFO{$key};
        next if ($pInfo->{NOT_AVAILABLE});

        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 ScmBuildMatrix
#   Contains information on all the platforms that the package builds for and target machines
#   
    my %ScmBuildMatrix;
    foreach my $key ( keys %BUILDINFO ) {
        my $pInfo = $BUILDINFO{$key};
        next if exists ($pInfo->{BADNAME});
        next if ($pInfo->{NOT_AVAILABLE} > 1);
        Error ("Internal: No MACHTYPE provided for $key") unless exists $pInfo->{MACHTYPE};
        $ScmBuildMatrix{$key} = $pInfo->{MACHTYPE};
    }

    $fh->DumpData(
        "# Build Matrix.\n#\n",
        "ScmBuildMatrix", \%ScmBuildMatrix );

# .. 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}} )
        {
            next if exists $package->{'EXCLUDE'};
            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 )
    {
        my @pathList;
        if (exists $BUILDINFO{$platform}{EXT_SHARED} ) {
            push @pathList, reverse BuildSharedLibFiles_list( $platform, $BUILDINFO{$platform}{EXT_SHARED} );
        }
        push @pathList, @BUILDTOOLS; 

        #
        #   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 ( @pathList ) {
            $_ =~ 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 ( @pathList ) {
            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
#                   
#                   Create sonames for all external shared libraries
#
# Inputs          : None
#
sub BuildSharedLibFiles_Unix
{
    foreach my $platform ( @BUILD_ACTIVEPLATFORMS )
    {
        my @unix_paths;
        if ( exists $BUILDINFO{$platform}{EXT_SHARED} ) {
            @unix_paths = BuildSharedLibFiles_list( $platform, $BUILDINFO{$platform}{EXT_SHARED} );

            #
            #   Create sonames for all shared libraries
            #   Append to the begging of the search list - so that it will rendered last
            #   
            my $sodir = BuildSoNameLinks_Unix($platform, @unix_paths);
            unshift( @unix_paths, $sodir ) if defined $sodir;
        }

        #
        #   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, @BUILDTOOLS ) {
            $fh->Write ( "export LD_LIBRARY_PATH=$_:\$LD_LIBRARY_PATH\n" );
        }

        #
        #   Extend the search path to allow tools to be located
        #   Have already extended LD_LIBRARY_PATH to allow for tools shared libaraies 
        #   
        $fh->Write ( "\n# Extend Tool Search Path\n" );
        foreach ( @BUILDTOOLS ) {
            tr~\\/~/~s;
            $fh->Write ( "PATH=$_:\$PATH\n" );
        }

        $fh->Write ( "\n\"\$\@\"\n" );
        $fh->Close();

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

#-------------------------------------------------------------------------------
# Function        : BuildSoNameLinks_Unix 
#
# Description     : Generate soname links for all shared libraries from external
#                   packages.
#                   
#                   There is a bit of a cheat. We don't examine the library to determine
#                   the soname. We simple create all possible sonames to the library
#
# Inputs          : $platform       - Target platform
#                   @paths          - Array of paths to scan for libraries 
#
# Returns         : soLinkDir       - Absolute path to the directory of gernerated
#                                     symlinks
#
sub BuildSoNameLinks_Unix
{
    my ($platform, @paths) = @_;
    my $soLinkDir = catdir($BUILDINTERFACE, 'soLinks', $platform );

    Verbose("Create Unix SoName links - $soLinkDir");
    RmDirTree( $soLinkDir );

    #
    #   Search provided library paths for shared libaries
    #       These are names of the form *.so.* ie : libz.so.1.2.5
    #
    foreach my $path (@paths)
    {
        foreach my $file (glob(catdir($path, '*.so.*')))
        {
            #
            #   Skip the debug symbol files
            #
            next if $file =~ m~\.debug$~;
            next if $file =~ m~\.dbg$~;

            #
            #   Generate all possible sonames by removing .nnnn from the 
            #   end of the file name
            #   
            my $sofile = $file;
            while ($sofile =~ m~(.*)\.\d+$~)
            {
                $sofile = $1;
                unless (-f $sofile) {
                    Verbose2("Need Soname: $sofile");

                    #
                    #   Create link from soname to full name
                    #   
                    mkpath ( $soLinkDir ) unless -d $soLinkDir;
                    my $sofilename = $sofile;
                    $sofilename =~ s~.*/~~;
                    $sofilename = catdir($soLinkDir, $sofilename);
                    unless (-f $sofilename) {
                        symlink ($file, $sofilename) || Error ("Cannot create symlink to $sofilename. $!"); 
                    }
                }
            }
        }
    }

    #
    #   Return the path the generated soLink dir
    #
    return AbsPath($soLinkDir) if (-d $soLinkDir);
    return undef;
}

#-------------------------------------------------------------------------------
# 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 )
    {
        my @localParts;
        UniquePush \@localParts, $BUILDINFO{$platform}{PLATFORM} , $BUILDINFO{$platform}{PRODUCT}, $BUILDINFO{$platform}{TARGET};
        foreach ( @localParts )
        {
            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);

    ToolsetFiles::AddFile( $path )
        unless ($Clobber);

    return $path;
}

#-------------------------------------------------------------------------------
# Function        : WinPath 
#
# Description     : Covert path to a windows formatted path
#
# Inputs          : One path element
#
# Returns         : One ugly path element
#

sub WinFullPath
{
    my ($path) = @_;
    $path = FullPath($path);
    $path =~ tr~\\/~\\~s;
    return $path;
}

#-------------------------------------------------------------------------------
# Function        : BuildPropertyPages 
#
# Description     : Create a props file suitable for use by VS2010, VS2012 (possibly others)
#                   Only supported for C/C++ projects
#                   Provide info for:
#                       Include Search paths
#                       Library search paths
#                       Nice Macros 
#
# Inputs          : 
#
# Returns         : 
#
sub BuildPropertyPages
{
    StartBuildPhase();                      # Starting the build phase. No more data collection
    return if $Clobber;
    foreach my $platform ( keys %BUILDINFO )
    {
        next unless $BUILDINFO{$platform}{MSBUILDPROPS};
        my $propsFile = BuildAddKnownFile ($Srcdir, 'jats_'. $BUILDINFO{$platform}{TARGET} . '.props');

        Message("BuildPropertyPages: $propsFile");

        my %macros;
        my @libpaths;
        my @incpaths;
        my @parts = @{$BUILDINFO{$platform}{PARTS}};

        #
        #   Basic definitions
        #   
        $macros{'GBE_ROOT'}     = WinFullPath(".");
        $macros{'GBE_PLATFORM'} = $BUILDINFO{$platform}{PLATFORM};
        $macros{'GBE_PRODUCT'}  = $BUILDINFO{$platform}{PRODUCT};
        $macros{'GBE_TARGET'}   = $BUILDINFO{$platform}{TARGET};
        $macros{'GBE_MACHTYPE'} = $::GBE_MACHTYPE;
        $macros{'GBE_PKGDIR'}   = WinFullPath('./pkg/' . $BUILDNAME_PACKAGE);
        $macros{'GBE_BUILDNAME'}= $BUILDNAME_PACKAGE;

        #
        #   Paths from the current build
        #       Local directory         - for installed components
        #       Interface directory     - for BuildPkgArchives
        #
        if ( $BUILDLOCAL )
        {
            my $macroName = 'GBE_LOCALDIR';
            $macros{$macroName} = WinFullPath("$BUILDLOCAL") ;
            $macroName = '$(' . $macroName . ')';
            my @localParts;
            UniquePush \@localParts, $BUILDINFO{$platform}{PLATFORM} , $BUILDINFO{$platform}{PRODUCT}, $BUILDINFO{$platform}{TARGET};
            foreach ( @localParts )
            {
                push @libpaths, catdir($macroName, 'lib', $_);
                push @incpaths, catdir($macroName ,'include' ,$_);
            }
            push @incpaths, catdir($macroName ,'include');
        }

        my $macroName = 'GBE_INTERFACEDIR';
        $macros{$macroName} = WinFullPath("$BUILDINTERFACE") ;
        $macroName = '$(' . $macroName . ')';

        foreach ( @parts )
        {
                push @libpaths, catdir($macroName, 'lib' , $_);
                push @incpaths, catdir($macroName ,'include' ,$_);
        }
        push @incpaths, catdir($macroName ,'include');

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

            my $macroName = 'GBE_PACKAGE_'.$package->{'name'};
            $macros{$macroName} = WinFullPath($package->{'base'}) ;
            $macroName = '$(' . $macroName . ')';

            for my $path ( @{$package->{'PLIBDIRS'}} )
            {
                push @libpaths, catdir($macroName, $path);
            }
            for my $path ( @{$package->{'PINCDIRS'}} )
            {
                push @incpaths, catdir($macroName, $path);
            }
        }

        my $AdditionalIncludeDirectories = join(';', @incpaths );
        my $AdditionalLibraryDirectories = join(';', @libpaths);
        my $PreprocessorDefinitions = 'JATS=1';

        #
        #   Create a props file formatted for VS2012
        #
        open (my $XML, '>', $propsFile) || Error ("Cannot create output file: $propsFile", $!);

        my $writer = XML::Writer->new(OUTPUT => $XML, UNSAFE => 0, DATA_INDENT => 4, DATA_MODE => 1);
        $writer->xmlDecl("UTF-8");
        $writer->comment('This file is generated by JATS build');
        $writer->comment('Do not edit this file');
        $writer->comment('Do not version control this file');
        $writer->startTag('Project', "ToolsVersion", "4.0", "xmlns", "http://schemas.microsoft.com/developer/msbuild/2003");
        $writer->emptyTag('ImportGroup', 'Label' , "PropertySheets");

        #
        #   Special Macro for handling production/debug libraries
        #
        $writer->startTag('PropertyGroup', 'Label' , "UserMacros", 'Condition', "'\$(Configuration)' == 'Debug'");
        $writer->dataElement('GBE_TYPE', 'D');
        $writer->endTag('PropertyGroup');
        
        $writer->startTag('PropertyGroup', 'Label' , "UserMacros", 'Condition', "'\$(Configuration)' != 'Debug'");
        $writer->dataElement('GBE_TYPE', 'P');
        $writer->endTag('PropertyGroup');

        #
        #   Define macros
        #   
        $writer->startTag('PropertyGroup', 'Label' , "UserMacros");
        foreach my $key ( sort keys %macros)
        {
            $writer->dataElement($key, $macros{$key});
        }
        $writer->endTag('PropertyGroup');
        $macros{'GBE_TYPE'}     = 1;

        #
        #   Extend the search paths for includes and libaraies
        #   
        #$writer->emptyTag('ItemDefinitionGroup');
        $writer->startTag('ItemDefinitionGroup');

        $writer->startTag('ClCompile');
        $writer->dataElement('AdditionalIncludeDirectories', $AdditionalIncludeDirectories . ';%(AdditionalIncludeDirectories)');
        $writer->dataElement('PreprocessorDefinitions', $PreprocessorDefinitions . ';%(PreprocessorDefinitions)');
        $writer->endTag('ClCompile');

        $writer->startTag('Link');
        $writer->dataElement('AdditionalLibraryDirectories', $AdditionalLibraryDirectories . ';%(AdditionalLibraryDirectories)');
        $writer->endTag('Link');
        $writer->endTag('ItemDefinitionGroup');

        #
        #   Specify all macro names
        #
        $writer->startTag('ItemGroup');
        foreach my $key ( sort keys %macros)
        {
            $writer->startTag('BuildMacro', 'Include' , $key);
            $writer->dataElement('Value', '$(' . $key . ')');
            $writer->endTag('BuildMacro');
        }

        #
        #   Close tags and write the XML file
        #
        $writer->endTag('ItemGroup');
        $writer->endTag('Project');
        $writer->end();
    }
}


#-------------------------------------------------------------------------------
# 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
    -cache          - Cache packages in the local dpkg_package cache
    -localcache     - Cache packages into the interface directory
    -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
    -[no]generic    - Build system sanity test
                      Default: Do not test
    -buildinfo=path - Specify name of the buildinfo file

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

 Commands:
    clobber        - Remove generated build system (eg Makefiles).
    interface      - Only (re)build the interface tree.
    rootonly       - Only (re)build the root directory.
    buildinfo      - Only generate build system info.

=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<-cache>

This option will cause dependent packages to be cached in the users local
dpkg_archive cache as defined via GBE_DPKG_CACHE

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

=item B<-localcache>

This option will cause dependent packages to be cached into the interface 
directory. This option simplifies the use of a cache when building single
packages.

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<-[no]generic>

If used, this option will perform a sanity test on the build type. If set to 
Generic then the build must be a GENERIC build. If set to noGeneric then the build
must not be a GENERIC build.

The default is to not perform the test.

This option is intended to be used by the automated build system.

=item B<-buildinfo>

This option is only used in conjunction with the 'buildinfo' command. It will specify 
the path of a file to write the build information. The dafault value is 'BuildInfo.properties'.

This option, by itslef, will not invoke the buildinfo mode.

=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<interface>

This command will only build, or rebuild, the 'interface' directory.

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 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<buildinfo>

This command is used by the build system to generate information about the
build. It will create a file with the following information.

=over 4

=item * All platforms that the package can be built for.

=item * All platforms that the package will be built for with the current build filter

=item * Platforms that have been specifically excluded from the build

=item * Platforms not known to JATS

=item * Toolset build information

=item * Generic build information

=item * Indication of production and debug builds

=item * JAVA version specified

=back



=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;