Subversion Repositories DevTools

Rev

Rev 7226 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

##############################################################################
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
#
# Module name   : makelib.pl2
# Module type   : Makefile system
#
# Description:
#       This modules builds the platform definition makefiles(s)
#
# Notes:                *** DO NOT DETAB ***
#       Beware the use of space v's tab characters within the
#       makefile generation sessions.
#
##############################################################################
# Globals:
#  $ScmVersion          Makelib.pl2 version
#  $ScmRoot             Command line parameter that gives the root directory
#                       location for this directory tree.
#  $ScmMakelib          Command line parameter that points to the location
#                       of THIS script.  ie. location of makelib.pl.
#  $ScmPlatform         Current platform
#  $ScmProduct          Current product (if any)
#  $ScmTarget           Resulting target (derived from Platform)
#  @ScmPlatformArgs     Platform arguments
#  $ScmToolset          Toolset
#  @ScmToolsetArgs      Toolset arguments
#  $ScmDebug            Debug level
#  $ScmVerbose          Verbose setting
#  $ScmSourceTypes      Source types, aliasing for C, C++ and assembler
#                       source.
#  @CFLAGS              List containing all of the defined C flags
#  @CXXFLAGS            List containing all of the defined C++ flags
#  @ASFLAGS             List containing all of the defined assembler flags
#  @CLINTFLAGS          List containing all of the defined C lint flags
#  @CXXLINTFLAGS        List containing all of the defined C++ lint flags
#  @{G|L}_INCDIRS       List containing all of include paths
#  @{G|L}_SRCDIRS       List containing all of source search paths
#  @{G|L}_LIBDIRS       List containing all of library search paths
#  @LDFLAGS             List containing all of the defined linker flags
#  @SRCS                List of ALL source files. ie. C/C++ and other (eg .x)
#                       Key is source file, value is source path
#  @OBJS                List of ALL (non-shared) object files.
#  %SHOBJ_LIB           List of ALL shared library object files and associated library.
#  %OBJSOURCE           List of ALL object files
#                       from that should result from later makes.
#                       Key is objectfile, value is source file
#  %OBJREFS             List of ALL object files, built options.
#  @PROGOBJS            List of ALL application object files.
#  %SRC_ARGS            List of arguments that are to be used when making the
#                       nominated source.  Key is the source name, the
#                       value is a string of arguments to apply.  The
#                       arguments are '$;' separated.
#  %SRC_TYPE            Source file type (user override).
#  @CHDRS               List of C header files.
#  @CSRCS               List of C files
#                       Key is objectfile, value is source file
#  @CXXSRCS             List of C++ files
#  @ASHDRS              List of assembler include files (.inc)
#  @ASSRCS              List of assembler source files
#  @GENERATED           List of files that should result from a 'generate'
#                       make rule.  The programmer is expected to provide
#                       the necessary rule(s).
#  @RULES               List of additional make rules the programmer
#                       has specified to be included in the make.
#  %INSTALL_HDRS        List of headers that are to be installed for later
#                       "public" consumption.
#  %INSTALL_CLSS        List of Java classes or JAR files that are to be installed
#                       for later "public" consumption.
#  @LIBS                List of libraries that are to be built.
#  $LIBS                Ref to a collection of static library descriptors
#                       Indexed by lib name
#  %INSTALL_LIBS        List of libraries that are to be installed for later
#                       public consumption.
#  @MLIBS               List of libraries that are to be built via merging
#  $MLIBS               Ref to a collection of merged lib descriptors
#  @SHLIBS              List of shared libraries that are to be built.
#  $SHLIBS              Ref to collection of shared library information
#  %INSTALL_SHLIBS      List of libraries that are to be installed for later
#                       public consumption.
#  @PROGS               List of programs (binary executables) that are
#                       to be built
#  $PROGS               Ref to collection of program information
#  %SCRIPTS             List of scripts to 'create' (key) and whether they
#                       should be made executable or not (value).  Script
#                       set to executable is denoted by the value being
#                       defined AND true.
#  %INSTALL_PROGS       List of programs for "public" cosumption to install
#                       where (key) is the file and where to install it
#                       to vs. (value) which is composed of the original
#                       location of the file, the destination directory
#                       and a list of service providers this file applies to.
# $ProjectBase          Base of the user's project. This variable is designed to
#                       be used by the user.
#....

require 5.006_001;
use strict;
use warnings;
use Getopt::Long;
use Data::Dumper;
use JatsError;
use JatsEnv;
use MakeEntry;
use JatsLocateFiles;
use JatsDPackage;
use MakeIf;
use ToolsetPrinter;
use MakeObject;
use JatsVersionUtils;
use ToolsetFiles;


our $ScmVersion             = "2.34";
our $ScmGlobal              = 0;
our $ScmExpert              = 0;
our $ScmInterface           = "interface";      # default 'interface'
our $ScmPackage             = 1;                # package active by default.
our $ScmProcessingRootMake  = 0;                # Processing root makefile.pl
our $ScmPlatformSeen        = 0;                # Platform directive has been seen
my  $ScmNotGeneric          = 1;                # Not a generic build

our $ScmToolsetVersion      = "";               # version of toolset
our $ScmToolsetGenerate     = 1;                # generate active by default.
our $ScmToolsetProgDependancies = 1;            # 1: Write program dependancies
                                                # 0: Don't write progdeps. Prog is Phony
our $ScmToolsetSingleType   = 0;                # Toolset does not support Debug and Production
our $ScmToolsetProgSource   = ();               # Toolset Program Source
our $ScmToolsetSoName       = 0;                # 1: Shared library supports SoName
our $ScmToolsetNillLibSrc   = 0;                # 1: Librarys created without source specified
our %ScmToolsetProperties   = ();               # Toolset specific features and limitations
                                                # Known values: UnitTests, AutoUnitTests, LdFlagSpace
our %ScmGlobalOptions       = ();               # Hash of Global(platform) options. Access via functions
our %ScmRecipeTags          = ();               # Hash of exposed recipe names

our $ScmRoot                = "";
our $ScmMakelib             = "";
our $ScmPlatform            = "";
our $ScmMachType            = "";
our $ScmSrcDir              = "";
our @ScmPlatformDirs        = ();
our @ScmPlatformArgs        = ();
our $ScmBuildType           = 0;                # 0, P, D. 0 == P and D
our $ScmProduct             = "";
our $ScmTarget              = "";
our $ScmTargetHost          = "";
our $ScmToolset             = "";
our @ScmToolsetArgs         = ();
our @ScmDepends             = ();
our %ScmSourceTypes         = ();
our $ScmDeploymentPatch     = "";
our $ProjectBase            = "";               # Base of the user's project
our $ScmNoToolsTest         = "";               # Supress compiler tests
our $ScmDependTags          = 0;                # Create dependancy scanning tag
our $ScmMakeUid;                                # Unique makefile id (number 1 .. )

our @CFLAGS                 = ();
our @CFLAGS_DEBUG           = ();
our @CFLAGS_PROD            = ();
our @CLINTFLAGS             = ();
our @CLINTFLAGS_DEBUG       = ();
our @CLINTFLAGS_PROD        = ();
our @CXXFLAGS               = ();
our @CXXFLAGS_DEBUG         = ();
our @CXXFLAGS_PROD          = ();
our @CXXLINTFLAGS           = ();
our @CXXLINTFLAGS_DEBUG     = ();
our @CXXLINTFLAGS_PROD      = ();
our @ASFLAGS                = ();
our @ASFLAGS_DEBUG          = ();
our @ASFLAGS_PROD           = ();
our @LDFLAGS                = ();
our @LDFLAGS_DEBUG          = ();
our @LDFLAGS_PROD           = ();

our @INCDIRS                = ();
our @NODEPDIRS              = ();
our @S_INCDIRS              = ();
our @G_INCDIRS              = ();
our @L_INCDIRS              = ();
our @SRCDIRS                = ();
our @S_SRCDIRS              = ();
our @G_SRCDIRS              = ();
our @L_SRCDIRS              = ();
our @LIBDIRS                = ();
our @S_LIBDIRS              = ();
our @G_LIBDIRS              = ();
our @L_LIBDIRS              = ();

our %SRCS                   = ();
our %SRC_ARGS               = ();
our %SRC_TYPE               = ();
our %SRC_DEPEND             = ();
our %SCRIPTS                = ();
our @COPYIN                 = ();
our @INITS                  = ();
our @DEFINES                = ();
our @OBJS                   = ();
our %SHOBJ_LIB              = ();
our @PROGOBJS               = ();
our @TESTPROGOBJS           = ();
our %OBJSOURCE              = ();
our @CHDRS                  = ();
our @CSRCS                  = ();
our @CXXSRCS                = ();
our @ASHDRS                 = ();
our @ASSRCS                 = ();
our @GENERATED              = ();
our @GENERATED_NOTSRC       = ();
our @RULES                  = ();
our @TOOLSETRULES           = ();
our @TOOLSETDIRS            = ();
our @TOOLSETDIRTREES        = ();
our @TOOLSETGENERATED       = ();
our @USERGENERATED          = ();
our @TOOLSETOBJS            = ();
our @TOOLSETLIBS            = ();
our @TOOLSETPROGS           = ();
our %INSTALL_HDRS           = ();
our %INSTALL_CLSS           = ();
our @CLOBBERFILES           = ();
our @CLOBBERDIRS            = ();

our @TOOLSET_UTF_PRE        = ();       # Toolsets can extend rules run before all unit tests
our @TOOLSET_UTF_POST       = ();       # Toolsets can extend rules run after all unit tests
our @TOOLSET_UTF_COLLATE    = ();       # Toolsets can extend rules run to collate unit tests results

our @LIBS                   = ();
our $LIBS                   = ();
our %LIB_PKG                = ();
our %LIB_INS                = ();
our %INSTALL_LIBS           = ();

our @MLIBS                  = ();
our $MLIBS                  = ();

our @SHLIBS                 = ();
our $SHLIBS                 = ();
our @SHLIB_TARGETS          = ();
our %SHLIB_PKG              = ();
our %SHLIB_INS              = ();
our %INSTALL_SHLIBS         = ();
our @INSTALL_DIRS           = ();

our $TESTPROGS              = ();
our @TESTPROGS              = ();

our $PROGS                  = ();           # Simplify tracking of progs
our @PROGS                  = ();
our @PROGS_EXTRA            = ();           # Look at doing better !!
our %PROG_PKG               = ();
our %PROG_INS               = ();
our %INSTALL_PROGS          = ();

our %PACKAGE_DIST           = ();
our %PACKAGE_SETS           = ();
our %PACKAGE_HDRS           = ();
our %PACKAGE_LIBS           = ();
our %PACKAGE_CLSS           = ();
our %PACKAGE_SHLIBS         = ();
our %PACKAGE_PROGS          = ();
our %PACKAGE_FILES          = ();
our @PACKAGE_DIRS           = ();

our @LINTLIBS               = ();
our @LINTSHLIBS             = ();

our @TESTS_TO_RUN           = ();                           # Info from 'RunTest' directives

our @TESTPROJECT_TO_URUN    = ();                           # List of Unit Tests and Projects names (Auto and Non Auto in order defined in makefile)
our @TESTPROJECT_TO_ARUN    = ();                           # List of Auto Tests and Projects names in order defined in makefile
my  $TESTS_TO_AUTORUN       = undef;                        # Flag - Auto Test found
my  $TESTS_TO_RUN           = undef;                        # Flag - Unit Test found

#our $CurrentTime           = "";
#our $CurrentDate           = "";
#our $Cwd                   = "";

our @GENERATE_FILES         = ();
our %DEPLOYPACKAGE          = ();
our $DEPLOYPACKAGE          = 0;
our %MakeTags;

#
#   Some toolset options that affect the generation of the makefile
#
our $UseAbsObjects          = 0;                # Default is relative paths to objects
our $UseRelativeRoot        = 0;                # Default is absolute paths to build root
our $DPackageDirective      = 0;

#
#   Arrays of hook functions
#
our %MF_RegisterSrcHooks;                       # Hook source file discovery

###############################################################################
#
#   Packaging and Installation Information
#   Held in a structure as its used in a few places
#   Items
#       PBase   - Package Base directory. Used for user overrides
#       IBase   - Local Install Base directory
#       Dir     - Default directory suffix for components. Added to Pbase and IBase
#
#
our %PackageInfo = (
    'File' => { 'PBase' => '$(PKGDIR)'       ,'IBase' => '$(LOCALDIR)'       , 'Dir' => '' },
    'Hdr'  => { 'PBase' => '$(INCDIR_PKG)'   ,'IBase' => '$(INCDIR_LOCAL)'   , 'Dir' => ''},
    'Lib'  => { 'PBase' => '$(LIBDIR_PKG)'   ,'IBase' => '$(LIBDIR_LOCAL)'   , 'Dir' => '/$(GBE_PLATFORM)'},
    'Prog' => { 'PBase' => '$(BINDIR_PKG)'   ,'IBase' => '$(BINDIR_LOCAL)'   , 'Dir' => '/$(GBE_PLATFORM)$(GBE_TYPE)'},
    'Jar'  => { 'PBase' => '$(CLSDIR_PKG)'   ,'IBase' => '$(CLSDIR_LOCAL)'   , 'Dir' => ''},
    'Tool' => { 'PBase' => '$(PKGDIR)'       ,'IBase' => '$(LOCALDIR)'       , 'Dir' => '/tools/bin/$(GBE_HOSTMACH)'},
    );

###############################################################################
#
#   An array of reserved names
#   Used to attempt to prevent developers from naming toolset targets with names reserved
#   within the build system
our @reservedMakeTargets = qw (
    preprocess_tests postprocess_tests collate_test_results
);

MakeLib2Init();                                 # Runtime initialisation

sub MakeLib2Init
{
#.. Test environment
#
    EnvImport( "GBE_CORE" );
    EnvImport( "GBE_BIN" );
    EnvImport( "GBE_PERL" );
    EnvImport( "GBE_TOOLS" );
    EnvImport( "GBE_CONFIG" );
    EnvImport( "GBE_MACHTYPE" );

#.. Common stuff
#
    require "$::GBE_TOOLS/common.pl";           # Common stuff
    push( @ScmDepends, "$::GBE_TOOLS/common.pl" );

    CommonInit( "makelib2" );
    Debug( "version:   $ScmVersion" );

#.. Cache arguments
#
    CommandLine();

#.. Build defaults
#
    $ScmSourceTypes{ ".h" }     = ".h";
    $ScmSourceTypes{ ".hpp" }   = ".h";
    $ScmSourceTypes{ ".c" }     = ".c";
    $ScmSourceTypes{ ".C" }     = ".c";
    $ScmSourceTypes{ ".cpp" }   = ".cc";
    $ScmSourceTypes{ ".cc" }    = ".cc";
    $ScmSourceTypes{ ".asm" }   = ".asm";
    $ScmSourceTypes{ ".x" }     = "--Ignore";
    $ScmSourceTypes{ ".ini" }   = "--Ignore";
    $ScmSourceTypes{ ".sh" }    = "--Ignore";
    $ScmSourceTypes{ ".pl" }    = "--Ignore";
    $ScmSourceTypes{ ".awk" }   = "--Ignore";

#.. Get the stuff from the build configuration file
#
    ConfigLoad();
    $ScmMakeUid = GetMakfilefileUid();
    Debug("ScmMakeUid: $ScmMakeUid");

    if ( (%::ScmBuildPlatforms) )        # Interface/build.cfg
    {
        AddPlatformArg( split( /$;/, $::ScmBuildPlatforms{ $ScmPlatform } ));
    }

    if ( (%::ScmBuildIncludes) )         # Interface/build.cfg
    {
        my( @includes ) = split( ',', $::ScmBuildIncludes{ $ScmPlatform } );
        my( $global ) = $ScmGlobal;

        $ScmGlobal = 1;                         # Follow defs are "global's" ...
        foreach my $elem ( @includes )
        {
            AddIncDir( "*", $elem ) if ($elem);
        }
        $ScmGlobal = $global;                   # Restore global status ...
    }

    if ( (%::ScmBuildLibraries) )        # Interface/build.cfg
    {
        my( @libraries ) = split( ',', $::ScmBuildLibraries{ $ScmPlatform } );
        my( $global ) = $ScmGlobal;

        $ScmGlobal = 1;                         # Follow defs are "global's" ...
        foreach my $elem ( @libraries )
        {
            AddLibDir( "*", $elem ) if ($elem);
        }
        $ScmGlobal = $global;                   # Restore global status ...
    }

#.. Determine the value of $ScmMachType
#   In the makefile GBE_MACHTYPE will be set to $ScmMachType.
#
#   There is an compatibility issue here.
#   A lot of (legacy) package.pl files use GBE_MACHTYPE to specify platform
#   specfic directories and names. This is not to be encouraged.
#
#   Allow for a platformm specific override
#
    if ( exists( $::BUILDINFO{$ScmPlatform}{'SCMMACHTYPE'} ))
    {
        $ScmMachType = $::BUILDINFO{$ScmPlatform}{'SCMMACHTYPE'};
        Verbose("Override ScmMachType: $ScmMachType");
    }
    else
    {
        $ScmMachType = $ScmPlatform;
    }


#.. Get the stuff from the Package definition file
#   A convention is that package.pl provide a package name via $Pbase
#   This may be different to the BUILDNAME. Generate a default $Pbase
#   to allow the package.pl to use the package name part of the buildname
#
    $::Pbase = $::ScmBuildPackage;
    if ( -f "$ScmRoot/package.pl" )
    {
        Warning ("package.pl file used. Use is being deprecated");

        my( $global ) = $ScmGlobal;             # Follow defs are "global's" ...
        $ScmGlobal = 1;
        require "$ScmRoot/package.pl";
        $ScmGlobal = $global;                   # Restore global status ...

        if ( defined ($::ScmBuildPackage) && defined ($::Pbase) )
        {
            #   Special case.
            #   $Pbase is set to ".". Set $Pbase to the Build Name to force
            #   construction of a well formatted package.
            #
            $::Pbase = $::ScmBuildPackage
                if ( $::Pbase eq "." );

            #
            #   Error if Pbase has changed
            #
            Error ("Pbase is not the same as the BuildName (Check package.pl)",
                   "Pbase    : $::Pbase",
                   "BuildName: $::ScmBuildPackage")
                if ( $::Pbase ne $::ScmBuildPackage );
        }
    }

    #
    #   Create objects to keep track of Libraies and Programs
    #
    $LIBS       = MakeObject::NewType( 'Library',       \@LIBS,     '$(LIBDIR)/', \&GenLibName);
    $MLIBS      = MakeObject::NewType( 'MergedLibrary', \@MLIBS,    '$(LIBDIR)/', \&GenLibName);
    $SHLIBS     = MakeObject::NewType( 'SharedLibrary', \@SHLIBS,   '$(LIBDIR)/', \&GenLibName);
    $PROGS      = MakeObject::NewType( 'Program',       \@PROGS,    '$(BINDIR)/', \&GenProgName);
    $TESTPROGS  = MakeObject::NewType( 'TestProgram',   \@TESTPROGS,'$(BINDIR)/', \&GenProgName);
}

#-------------------------------------------------------------------------------
# Function        : GenLibName
#
# Description     : Helper function to generate a (static) library name
#                   Used by MakeObject::NewType
#
#                   If the toolset doesn't support Debug and Prod, then
#                   The library name will not have the suffix
#
# Inputs          : arg0        - Base name of the library
#                   arg1        - Mode: 1 == Plain. No P or D
#
# Returns         : Name of the library as used in the makefiles
#                   Does not include base directory
#
sub GenLibName
{
    if ( $ScmToolsetSingleType || $_[1] ) {
        return "$_[0].$::a"
    } else {
        return "$_[0]\$(GBE_TYPE).$::a"
    }
}

#-------------------------------------------------------------------------------
# Function        : GenProgName
#
# Description     : Helper function to generate a program name
#                   Used by MakeObject::NewType
#
# Inputs          : arg0        - Base name of the library
#
# Returns         : Name of the program as used in the makefiles
#                   Does not include base directory
#
sub GenProgName
{
    return "$_[0]$::exe"
}


#-------------------------------------------------------------------------------
# Function        : CommandLine
#
# Description     : Process the command line.
#                   Arguments describes below
#
# Arguments       : ARG0        - Root of the project
#                   ARG1        - Path to this script
#                   ARG2        - Target Platform
#
#                   Options follow
#                       --interface=name    - Name of interface dir
#                       --arg=xxx           - Platform argument
#
#                   Otherwise display a usage message
#
# Returns         : Nothing
#
sub CommandLine
{
    Verbose ("Command Line: @ARGV");

    #
    #   Extract options first
    #
    my $opt_help = 0;
    my $result = GetOptions (
                "help+"         => \$opt_help,
                "interface=s"   => \$::ScmInterface,
                "arg=s"         => sub{ AddPlatformArg( "--$_[1]") }
                );
    Usage() if ( $opt_help || !$result );

    #
    # Need 3 Arguments
    #
    $ScmRoot     = ${ARGV[0]};
    $ScmRoot     = RelPath( $ScmRoot );
    $ProjectBase = $ScmRoot;

    $ScmMakelib  = ${ARGV[1]};
    $ScmPlatform = ${ARGV[2]};
    $ScmTarget   = $ScmPlatform;

    Message ("[$ScmPlatform] Generate Makefile");
    Debug( "root\t=$ScmRoot" );
    Debug( "makelib\t=$ScmMakelib" );
    Debug( "platform\t=$ScmPlatform" );
}

#   Usage ---
#       Command line usage help.
#..

sub Usage
{
    Error ( "Usage: perl makefile.pl2 <ROOTDIR> <makelib.pl2> <PLATFORM> [options ...]",
            "Valid options:",
            "    --interface=name  Set interface directory",
            "    --arg=text        Specify platform argument",
            );
}


#-------------------------------------------------------------------------------
# Function        : SubDir
#
# Description     : Include a sub-makefile
#                   When called when processing by this script this directive
#                   does nothing. The processing will be done by makelib.pl
#
#                   This directive MUST occur before the Platform directive
#
# Inputs          : None that are used
#
# Returns         : Nothing
#

sub SubDir
{
    Error ("SubDir directive not allowed after the Platform directive")
        if ( $ScmPlatformSeen );
}


###############################################################################
#   Platform support
###############################################################################

sub Platform
{
    my( $global, $file );

    Debug( "Platform( $ScmPlatform, @ScmPlatformArgs )" );

#.. Sanity test
#
    Error ("Platform directive is not allowed in common makefile.pl")
        if ( $ScmProcessingRootMake );

    Error ("Only one Platform directive is allowed")
        if ( $ScmPlatformSeen );
    $ScmPlatformSeen = 1;

#.. Arguments
#
    $ScmTargetHost = $::ScmHost;                # default

#.. Common configuration
#
    $global = $ScmGlobal;                       # Follow defs are "global's" ...
    $ScmGlobal = 1;

#.. Common rules (ScmHost specific)
#
    push( @ScmDepends, "$ScmMakelib" );         # parent

    $file = Require( "$::GBE_CONFIG", "Rules", "Common rules " );
    push( @ScmDepends, "$file" );

#.. Platform (defines ScmToolset)
#
    if ( ( %::ScmBuildProducts ) &&      # interface/build.cfg
           $::ScmBuildProducts{ $ScmPlatform } )
    {
        my( @args ) = split( ',', $::ScmBuildProducts{ $ScmPlatform } );

        $ScmProduct = $args[0];
        $ScmTarget = $args[1];

        Debug( " mapping to product $ScmProduct" );

                                                # Platform/target specific
        MakeIf::PackageDirs( \@ScmPlatformDirs, $ScmPlatform, $ScmTarget );
        push @ScmPlatformDirs, "$::GBE_CONFIG"; # .. plus default

        @ScmPlatformArgs = ( "--product=$ScmProduct", @ScmPlatformArgs );
        $file = Require( "PLATFORM", $ScmTarget,
                    "Platform definition ", @ScmPlatformDirs );
    }
    else                                        # standard
    {
        Debug( " native platform" );

                                                # Platform specific
        MakeIf::PackageDirs( \@ScmPlatformDirs, $ScmPlatform );
        push @ScmPlatformDirs, "$::GBE_CONFIG"; # .. plus default

        #   Map all GENERIC builds onto the one platform definition
        my $platformDefs = $ScmPlatform;
        if ($::BUILDINFO{$ScmPlatform}{IS_GENERIC})
        {
            $ScmNotGeneric = 0;
            $platformDefs = 'GENERIC' ;
        }

        $file = Require( "PLATFORM", $platformDefs,
                    "Platform definition ", @ScmPlatformDirs );
    }
    push( @ScmDepends, "$file" );

    Error( "Toolset undefined for platform $ScmPlatform ...")
        unless( $ScmToolset );

#.. Toolset
#
    $file = Require( "$::GBE_CONFIG/TOOLSET", $ScmToolset, "Toolset definition " );
    push( @ScmDepends, "$file" );

#.. Package definitions
#
#   Global DPACKAGE definitions, which may pull in $ScmTarget specific definitions.
#

    MakeIf::PackageLoad( $ScmPlatform );        # DPACKAGE's (if any)


#.. Package extensions
#   Import, into the current package, files of the form gbe/DIRECTIVES
#   These allow the JATS directives to be extended by the contents of a package
#   without the need to update the core JATS release.
#
#   Intended use: Associate a directive with a tool script, such that the
#   new directive simplifies the use of the tool script.
#
#
#   First: Extend the Perl Search Space to include the toolset extensions
#          Although the directives are in gbe/DIRECTIVES/*.pm, they may need
#          to reference other packages that are not.
#
#           Look in the 'interface' and 'link' packages
#           The 'build' packages are duplicated into the 'interface'
#
    for my $path ( ToolExtensionPaths() )
    {
        UniquePush (\@INC, $path)
            if (glob( "$path/*.pm") || glob( "$path/*/*.pm"));
    }

    for my $entry (@{$::ScmBuildPkgRules{$ScmPlatform} })
    {
        next if ( $entry->{'TYPE'} eq 'build' );
        my $cfgdir = $entry->{'CFGDIR'};
        next unless ( $cfgdir );
        my $base_dir = $entry->{'ROOT'} . $cfgdir . '/DIRECTIVES';
        next unless ( -d $base_dir );
        foreach my $file  ( glob ("$base_dir/*.pm") )
        {
            push( @ScmDepends, "$file" );
            require $file;
        }
    }

    #
    #   Include local toolset extensions
    #   These are rooted in the build directory and are not to be confused with
    #   extensions that may be packaged
    #   Simplify life - add the directory to the BUILDTOOLSPATH
    #
    my $local_base_dir = "$ScmRoot/gbe/DIRECTIVES";
    if ( -d $local_base_dir )
    {
        unshift @::BUILDTOOLSPATH, AbsPath($local_base_dir); 
        foreach my $file  ( glob ("$local_base_dir/*.pm") )
        {
            push( @ScmDepends, "$file" );
            require $file;
        }
    }

    #
    #   All makefile.pl's will include a makefile.pl found in the build
    #   root directory ( The same directory as build.pl ). This makefile.pl
    #   is a little bit different - It should not "require "$ARGV[1]", nor
    #   should it use a Platform directive.
    #
    #   Note: This makefile is processed AFTER the toolset has been initialised
    #         so that toolset extensions are available to the directives
    #
    $file = "$ScmRoot/makefile.pl";
    if ( -e $file ) {
        $ScmProcessingRootMake = 1;
        require "$file";
        $ScmProcessingRootMake = 0;
        push( @ScmDepends, "$file" );
    }

    #
    #   Sanity Test for platforms that do not support both debug and production
    #   builds at the same time. This information is flagged by the toolset
    #   which we have now loaded.
    #
    if ( $ScmToolsetSingleType  )
    {
        unless ( $ScmBuildType )
        {
            Error ("The toolset used by the \"$ScmPlatform\" platform does not support",
                   "both Production and Debug Builds" );
        }
    }

    #
    #   Restore global status ...
    #
    $ScmGlobal = $global;
}


sub PlatformRequire
{
    my( $script, @arguments ) = @_;
    my( $file );

    Debug( "PlatformRequire($script, @arguments)" );

    push( @ScmPlatformArgs, @arguments );       # additional arguments

    $file = Require( "PLATFORM", $script,
                "PlatformRequire ", @ScmPlatformDirs );

    push( @ScmDepends, "$file" );
}


sub PlatformInclude
{
    my( $script, @arguments ) = @_;
    my( $file );

    Debug( "PlatformInclude( @_ )" );

    $file = Require2( \@arguments, "PLATFORM", $script,
                "PlatformInclude ", @ScmPlatformDirs );

    push( @ScmDepends, "$file" );
}


sub PlatformDefine
{
    Debug2( "PlatformDefine(@_)" );

    Define( @_ );
}


sub PlatformDefines
{
    my( $script ) = @_;
    my( $line );

    Debug2( "PlatformDefine(@_)" );

    $script = Exists( "PLATFORM", $script,      # locate image
                "PlatformDefines", @ScmPlatformDirs );

    push( @DEFINES, "# PlatformDefines from: $script" );
    open( my $fh, '<', $script ) || Error( "Opening $script" );
    while (<$fh>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @DEFINES, $_ );
    }
    push( @ScmDepends, "$script" );             # makefile dependencies
    close( $fh );
}


sub PlatformEntry
{
    my( $prelim, $postlim, $prefix, $postfix, @elements ) = @_;

    my $str = "$prelim";
    foreach my $element ( @elements )
    {
        $str .= "${prefix}${element}${postfix}";
    }
    $str .= "$postlim";
    PlatformDefine( $str );
}


#
#   Add arguments to the ScmPlatformArgs, but remove "Global" arguments
#       --OnlyDebug
#       --OnlyProduction
#       --NoToolSet
#
#   Capture OnlyDebug and OnlyProd information
#   Will be sanitized by caller.
#
sub AddPlatformArg
{
    Debug("AddPlatformArg: @_" );
    foreach  ( @_ )
    {
        if ( m~^--OnlyDebug~ ) {
            $ScmBuildType = 'D';
        } elsif ( m~--OnlyProd~ ) {
            $ScmBuildType = 'P';
        } elsif ( m~--NoToolSet~ ) {
            $ScmNoToolsTest = 1;
        } else {
            UniquePush( \@::ScmPlatformArgs, $_ );
        }
    }

    Debug("AddPlatformArg: Result: @::ScmPlatformArgs" );
    1;
}

###############################################################################
# Toolset support
#
#   Toolset( 'platform [, ... ]', name, [arg, ... ] )
#       Specify the toolset for a platform
#
#   ToolDefine( )
#   ToolDefines( )
#       Specifies toolset defines for insertion into the target makefile.
#
#   ToolsetDir
#       Define toolset created directory(s) for removal during
#       'clean' operations.
#
#   ToolsetGenerate
#       Define toolset created file(s) for removal during
#       'clean' operations.
#
#   ToolsetObj
#       Define toolset created object(s) for removal during
#       'clean' operations.
#
#   ToolsetLib
#       Define toolset created library(s) for removal during
#       'clean' operations.
#
#   ToolsetProg
#       Define toolset created prog(s) for removal during
#       'clean' operations.
#
#   ToolsetRule( )
#   ToolsetRules( )
#       Specifies toolset rules for insertion into the target makefile.
#
##############################################################################

sub Toolset
{
    my( $platforms, $toolset, @arguments ) = @_;

    Debug2( "Toolset(@_)" );

    return 1 if ( ! ActivePlatform($platforms) );

    $ScmToolset = $toolset;
    @ScmToolsetArgs = @arguments;
    return 1;
}


sub ToolsetRequire
{
    my( $script, @arguments ) = @_;
    my( $file );

    Debug2( "ToolsetRequire(@_)" );

    @ScmToolsetArgs = @arguments;
    $file = Require( "",
                     $script,
                     "ToolsetRequire",
                     "$::GBE_CONFIG/TOOLSET", @::BUILDTOOLSPATH );
    push( @ScmDepends, "$file" );
}


sub ToolsetDefine
{
    Debug2( "ToolsetDefine(@_)" );

    Define( @_ );
}


sub ToolsetDefines
{
    Debug2( "ToolsetDefines(@_)" );

    Defines( "$::GBE_CONFIG/TOOLSET", @_ );
}


sub ToolsetDir
{
    Debug2( "ToolsetDir(@_)" );

    UniquePush ( \@TOOLSETDIRS, @_ );
}


sub ToolsetDirTree
{
    Debug2( "ToolsetDirTree(@_)" );

    UniquePush ( \@TOOLSETDIRTREES, @_);
}


sub ToolsetGenerate
{
    Debug2( "ToolsetGenerate(@_)" );

    UniquePush( \@TOOLSETGENERATED, @_ );
}

sub ToolsetClobberFile
{
    Debug( "ToolsetClobberFile(@_)" );
    UniquePush( \@CLOBBERFILES, @_ );
}

sub ToolsetClobberDir
{
    Debug( "ToolsetClobberDir(@_)" );
    UniquePush( \@CLOBBERDIRS, @_ );
}

sub ToolsetObj
{
    Debug2( "ToolsetObj(@_)" );

    foreach my $obj ( @_ )
    {
        UniquePush( \@TOOLSETOBJS, "$obj.$::o"  );
    }
}


sub ToolsetLib
{
    Debug2( "ToolsetLib(@_)" );

    foreach my $lib ( @_ )
    {
        UniquePush( \@TOOLSETLIBS, GenLibName( $lib ) );
    }
}


sub ToolsetProg
{
    Debug2( "ToolsetProg(@_)" );

    foreach my $prog ( @_ )
    {
        UniquePush( \@TOOLSETPROGS, GenProgName( $prog ) );
    }
}


sub ToolsetRule
{
    Debug2( "ToolsetRule(@_)" );

    push( @TOOLSETRULES, @_ );
}


sub ToolsetRules
{
    my( $script ) = @_;
    my( $line );

    Debug2( "ToolsetRules(@_)" );

    $script = Exists( "$::GBE_CONFIG/TOOLSET", $script, "ToolsetRules" );
    push( @TOOLSETRULES, "# ToolsetRules from: $script" );
    open( my $fh, '<', $script ) || Error( "Opening $script" );
    while (<$fh>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & newline
        push( @TOOLSETRULES, $_ );
    }
    push( @ScmDepends, "$script" );             # makefile dependencies
    close( $fh );
}

#-------------------------------------------------------------------------------
# Function        : SetGlobalOption  
#
# Description     : Set a global toolset option
#                   The global options are intended to allow platform-specific
#                   operation of various tools and utilities. The scope is wider than 
#                   just the underlying tooolset 
#
# Inputs          : $name           - Name of the option
#                   $value          - Value to save            
#
# Returns         : Nothing
#

sub SetGlobalOption
{
    my ($name, $value) = @_;
    Debug( "SetGlobalOption.", $name, $value );
    $ScmGlobalOptions{$name} = $value;
}

#-------------------------------------------------------------------------------
# Function        : GetGlobalOption   
#
# Description     : Get a global toolset option
#
# Inputs          : $name           - Name of the option to fetch
#                   $default        - Default value to return, if the option
#                                     is not present.
#
# Returns         : The value of the option, or the default value
#

sub GetGlobalOption 
{
    my ($name, $default) = @_;
    if (exists $ScmGlobalOptions{$name})
    {
        $default = $ScmGlobalOptions{$name};
    }
    Debug( "GetGlobalOption .", $name, $default  );
    return $default;
}


#-------------------------------------------------------------------------------
# Function        : ToolsetAddUnitTestPreProcess
#                   ToolsetAddUnitTestPostProcess
#                   ToolsetAddUnitTestCollateProcess
#
# Description     : Functions to allow toolsets to add recipes to be run before
#                   and after Unit Tests are run.    
#
# Inputs          : $target         - Name of the recipe to be run 
#
# Returns         : Nothing
#
sub ToolsetAddUnitTestPreProcess
{
    _ToolsetAddUnitTest(\@TOOLSET_UTF_PRE, @_ );
}

sub ToolsetAddUnitTestPostProcess
{
    _ToolsetAddUnitTest(\@TOOLSET_UTF_POST, @_ );
}

sub ToolsetAddUnitTestCollateProcess
{
    _ToolsetAddUnitTest(\@TOOLSET_UTF_COLLATE, @_ );
}

#-------------------------------------------------------------------------------
# Function        : _ToolsetAddUnitTest  
#
# Description     : Internal helper function used by ToolsetAddUnitTest*
#
# Inputs          : $aref           - Ref to an array of names to extend
#                   $target         - Name of recipe to run 
#
# Returns         : Nothing
#
sub _ToolsetAddUnitTest
{
    my ($aref, $target ) = @_;

    #   Determine name of parent function
    my $fname = (caller(1))[3];
    $fname =~ s~.*::~~;
    Debug2( "$fname ($target)" );

    #
    #   Ensure user is not using a reserved target
    #
    if (grep {$_ eq $target} @reservedMakeTargets) {
        Error("Internal: $fname uses reserved make taget: $target");
    }

    push @$aref, $target;

}

###############################################################################
# User interface:
#
#   AddFlags( 'platform [, ... ]', 'flags' [, 'flag' ... ] )
#       This subroutine takes the C and C++ compiler flags
#       specified adding them to a global list for later
#       inclusion in the built makefile.
#
#   AddCFlags( 'platform [, ... ]', 'flags' [, 'flag' ... ] )
#       This subroutine takes the C compiler flags
#       specified adding them to a global list for later
#       inclusion in the built makefile.
#
#   AddCXXFlags( 'platform [, ... ]', 'flags' [, 'flag' ... ] )
#       This subroutine takes the C++ compiler flags
#       specified adding them to a global list for later
#       inclusion in the built makefile.
#
#   AddLintFlags( 'platform [, ... ]', 'flags' [, ... ] )
#       This subroutine takes the Lint flags specified
#       adding them to a global list for later inclusion
#       in the built makefile.
#
#   AddASFlags( 'platform [, ... ]', 'flags' [, ... ] )
#       This subroutine takes the Assemler flags specified
#       adding them to a global list for later inclusion
#       in the built makefile.
#
#   AddLDFlags( 'platform [, ... ]', 'flags' [, ... ] )
#       This subroutine takes the Linker flags specified
#       adding them to a global list for later inclusion
#       in the built makefile.
#
#   AddDir
#       This subroutine takes the directories specified adding
#       them to a global include and source directory list for
#       later inclusion in the built makefile.
#
#   AddIncDir( 'platform [, ... ]', 'dir' [, ... ] )
#       This subroutine takes the include file directories
#       specified adding them to a global list for later
#       inclusion in the built makefile.
#
#   AddSrcDir( 'platform [, ... ]', 'dir' [, ... ] )
#       This subroutine takes the source file directories
#       specified adding them to a global list used to resolve
#       Src() definitions.
#
#   AddLibDir( 'platform [, ... ]', 'dir' [, ... ] )
#       This subroutine takes the library directories
#       specified adding them to a global list for later
#       inclusion in the built makefile.
#
#   AddSourceType( 'ext', '.c|.cc|.asm' )
#       This subroutine takes the extension(s) specified by the
#       programmer and adds them to a global list for later
#       inclusion in the built makefile.  This list contains
#       the extensions to be recognised as 'C', 'C++' or
#       assembler file types.
#
#   AddSourceFile( 'platform [, ... ]', 'file' [, ... ] )
#       This subroutine takes the non-standard source file(s)
#       and adds them add it to either C, C++ or assembler
#       sources and the object list.
#
#   Init( 'platform [, ... ]', 'rule' )
#       Initialisation rule
#
#   Generate( 'platform [, ... ]', 'file' [, ... ] )
#       This subroutine is used to add the list of given
#       source files to the generate sources list, and if
#       the generated source is of type C, C++ or assember
#       also adds it to either C, C++ or assembler sources and
#       the object lists.
#
#       --c             Treat as a C source file.
#       --cpp           Treat as a C++ source file.
#       --asm           Treat as a assembler source file.
#
#   Rule( 'platform [, ... ]', definition )
#       This subroutine is used to add the non-standard make
#       rules required to build the system.  eg. any rules
#       necessary to produce a .cc & .h file from a .x file.
#
#   Src( 'platform [, ... ]', 'file' [, ... ], [ 'arg' [, ...]] )
#       This subroutine is used to add the list of given source
#       files to the sources list, and if the source is of type
#       C, C++ or assember also adds it to either C, C++ or
#       assembler sources and the object lists.  The optional
#       list of arguments is assigned to all source files.
#
#       --c             Treat as a C source file.
#       --cpp           Treat as a C++ source file.
#       --asm           Treat as a assembler source file.
#       --Shared        Shared, produces position-independent
#                       code (on targets where required).
#
#   Lib( 'platform [, ... ]', 'name', 'obj' [, ... ] [, '-arg' [, ... ]] )
#       This subroutine takes a library definition list and adds
#       the  entries to the 3 libraries definition lists. 'name'
#       of the library to be created.  List of the object files
#       'obj' that make up this library.  List of special
#       arguments 'arg' to pass to the librarian.
#
#   MergeLibrary( 'platform [, ... ]', 'name', 'lib' [, ... ] )
#       This subroutine takes a library merge list and adds
#       the  entries to the 2 merge libraries definition lists. 'name'
#       of the library to be created.  List of the libraries to be merged
#
#   LocalScript( 'platform [, ... ]', name, ['1'] )
#   Script( 'platform [, ... ]', name, ['1'] )
#       This subroutine takes a list that defines the name of
#       the script to be placed in the platform 'bin' directory,
#       and an optional second element that defines whether the
#       script should be made executable or not.
#
#   Prog( 'platform [, ... ]', 'name', ['obj', ... ],
#               ['-llib', ... ], ['options'] )
#       This subroutine takes a list that defines which program
#       (binary) is to be made, what libraries and object it is
#       made from, and any special commands required to perform
#       the program creation.
#
#       @PROGS          Updated list of programs to create
#
#   TestProg( 'platform [, ... ]', 'name', ['obj', ... ],
#               ['-llib', ... ], ['options'] )
#       This subroutine takes a list that defines which test program
#       (binary) is to be made, what libraries and object it is
#       made from, and any special commands required to perform
#       the program creation.
#
#       @TESTPROGS      Updated list of programs to create
#
#   InstallHdr( 'platform [, ... ]', 'file' [, ...], ['-arg'] )
#       This subroutine takes the given list of files and adds them
#       to the install header files list.  Files in this list will be
#       installed into the 'local header directory' area for public
#       consumption.  This is generally API files for other modules
#       to use.
#
#       --Strip         Strip directory from source
#       --Strip=n       Strip part of directory from source
#       --Full          Install using full path
#       --Subdir=subdir Install within the specified sub-directory
#       --Prefix=subdir   "       "     "      "      "     "
#
#   InstallLib( 'platform [, ... ]', 'file', ['subdir'] )
#       This subroutine takes the given list of files and adds them
#       to the install libraries files list.  Files in this list will
#       be installed into the 'local library directory' area for
#       public consumption.
#
#   InstallProg( 'platform [, ... ]', 'file', ['subdir'] ) )
#       This subroutine takes a list that defines the executable file
#       that is to be installed.  The file in this list will be
#       installed into the 'local executable directory' specified for
#       public consumption.
#
###############################################################################


sub Include                                     # User include
{
    my( $path, $name ) = @_;
    my( $file );

    $file = Require( $path, $name, "Include" );
    push( @ScmDepends, "$file" );
}

sub ForceCCompile
{
    CompileOptions( $_[0], 'compile_as_c' );            # Backward compatability
}

#-------------------------------------------------------------------------------
#   Create a data structure to define the global compiler options
#    The hash is keyed by compiler option
#    The value contains another hash.
#       The key is a makefile variable to set ( or remove )
#       The value is the value to assign to the makefile variable
#       If the value is 'undef' then the variable will be deleted
#
#   Keys of the form key=value are also supported
#
#   If the value is a CODE reference, then routine will be called with the key
#   and value as arguments. The return value will be utilised.
#
our %ScmCompilerOptions =
    (
        'strict_ansi'           => { 'USE_STRICT_ANSI'    => '1' },
        'no_strict_ansi'        => { 'USE_STRICT_ANSI'    => '' },      # Default

        'profile'               => { 'USE_PROFILE'        => '1' },
        'no_profile'            => { 'USE_PROFILE'        => '' },       # Default
        

        'prod_no_optimise'      => { 'PROD_USE_OPTIMISE'   => '' },
        'prod_no_debuginfo'     => { 'PROD_USE_DEBUGINFO'  => '' },     # Default
        'prod_optimise'         => { 'PROD_USE_OPTIMISE'   => '1' },    # Default
        'prod_debuginfo'        => { 'PROD_USE_DEBUGINFO'  => '1' },

        'debug_no_optimise'     => { 'DEBUG_USE_OPTIMISE'  => '' },     # Default
        'debug_no_debuginfo'    => { 'DEBUG_USE_DEBUGINFO' => '' },
        'debug_optimise'        => { 'DEBUG_USE_OPTIMISE'  => '1' },
        'debug_debuginfo'       => { 'DEBUG_USE_DEBUGINFO' => '1' },    # Default

        'compile_as_cpp'        => { 'FORCE_CC_COMPILE'    => '1',
                                     'FORCE_C_COMPILE'     => undef },
        'compile_as_c'          => { 'FORCE_C_COMPILE'     => '1',
                                     'FORCE_CC_COMPILE'    => undef },

        'no_define_source_file' => { 'DISABLE__SOURCE__' => '1' },
        'define_source_file'    => { 'DISABLE__SOURCE__' => undef },        # Default

        'warnings_as_errors'    => { 'WARNINGS_AS_ERRORS'        => '1' },
        'no_warnings_as_errors' => { 'WARNINGS_AS_ERRORS'        => undef },       # Default

    );

#
#   The toolset can extend the options by setting the following hash
#
our %ScmToolsetCompilerOptions = ();

#
#   Define default compiler options
#   These are makefile variables that will be assigned
#
our %ScmCompilerOpts =
    (
        'USE_STRICT_ANSI'       => '',
        'USE_PROFILE'           => '',
        'PROD_USE_DEBUGINFO'    => '',
        'PROD_USE_OPTIMISE'     => '1',
        'DEBUG_USE_OPTIMISE'    => '',
        'DEBUG_USE_DEBUGINFO'   => '1',
    );
    

sub CompileOptions
{
    my( $platforms, @elements ) = @_;
    return if ( ! ActivePlatform($platforms) );

    for (@elements)
    {
        my $oref;

        #
        #   The toolset option may be a text string or a definition
        #       Name        - A text string
        #       Name=Value  - A value
        #
        my $value;
        my $key = $_;
        if ( $key =~ m~(.*=)(.*)~ )
        {
            $key = $1;
            $value = $2 || '';
        }
        $key = lc( $key );

        #
        #   Examine the global flags
        #   Then the toolset extensions
        #   Then just drop it
        #
        unless ( $oref = ($ScmCompilerOptions{$key} || $ScmToolsetCompilerOptions{$key}) )
        {
            Warning ("Compile Option ignored: $_");
            next;
        }

        #
        #   Parse the definition and adjust makefile variables as required
        #   Set the value of a make variable or remove the definition
        #
        #   If the user value is a code reference, then call the code
        #   and use the returned value as the value.
        #
        while ( (my($ukey, $uvalue)) = each %{$oref} )
        {
            if ( defined( $uvalue) )
            {
                if ( ref($uvalue) eq "CODE" )
                {
                    $uvalue = &$uvalue( $key, $value, $ukey);
                    unless ( defined $uvalue )
                    {
                        Warning ("Compile Option ignored: $_");
                        next;
                    }
                }
                elsif ( defined $value )
                {
                    $uvalue = $value;
                }

                $ScmCompilerOpts{$ukey} = $uvalue;
            }
            else
            {
                delete $ScmCompilerOpts{$ukey};
            }
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : AddFlags
#                   AddCFlags
#                   AddCXXFlags
#                   AddASFlags
#                   AddLDFlags
#                   AddLintFlags
#
# Description     : Add target specfic flags to the C compiler
#                   This SHOULD only be used to add Defines to the compiler
#                   but it can be absued.
#
# Inputs          : $platform       - Platforms for which the directive is active
#                   ...             - list of flags to add
#
#                   Embedded options include:
#                       --Debug     - Following options are added to the debug build
#                       --Prod      - Following options are added to the production build
#
# Returns         : Nothing
#

sub AddFlags
{
    my( $platforms, @elements ) = @_;

    AddCFlags( $platforms, @elements );
    AddCXXFlags( $platforms, @elements );
}

sub AddCFlags
{
    my( $platforms, @elements ) = @_;

    Debug2( "AddCFlags($platforms, @elements)" );
    return if ( ! ActivePlatform($platforms) );

    WarnIfNastyFlag( @elements );
    __AddFlags( "CFLAGS", \@elements,
                \@CFLAGS, \@CLINTFLAGS,
                \@CFLAGS_DEBUG, \@CLINTFLAGS_DEBUG,
                \@CFLAGS_PROD,  \@CLINTFLAGS_PROD );
}

sub AddCXXFlags
{
    my( $platforms, @elements ) = @_;

    Debug2( "AddCXXFlags($platforms, @elements)" );
    return if ( ! ActivePlatform($platforms) );

    WarnIfNastyFlag( @elements );
    __AddFlags( "CXXFLAGS", \@elements,
               \@CXXFLAGS, \@CXXLINTFLAGS,
               \@CXXFLAGS_DEBUG, \@CXXLINTFLAGS_DEBUG,
               \@CXXFLAGS_PROD,  \@CXXLINTFLAGS_PROD );
}

sub AddASFlags
{
    my( $platforms, @elements ) = @_;

    Debug2( "AddASFlags($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    __AddFlags( "ASFLAGS", \@elements,
                \@ASFLAGS, undef,
                \@ASFLAGS_DEBUG, undef,
                \@ASFLAGS_PROD, undef );
}

sub AddLDFlags
{
    my( $platforms, @elements ) = @_;

    Debug2( "AddLDFlags($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    foreach  ( @elements )
    {
        next if ( m~^--(Debug|Prod)~ );
        Warning("Use of linker flag discouraged (will be used): $_");
        if ( $ScmToolsetProperties{'LdFlagSpace'} ) {
            $_ =~ s/\s/\$(spacealt)/g;
        }
    }
    
    
    
    __AddFlags( "LDFLAGS", \@elements,
                \@LDFLAGS, undef,
                \@LDFLAGS_DEBUG, undef,
                \@LDFLAGS_PROD, undef );
    
}

sub AddLintFlags
{
    my( $platforms, @elements ) = @_;

    return if ( ! ActivePlatform($platforms) );

    Debug2( "AddLintFlags($platforms, @elements)" );

    __AddFlags( "LINTFLAG", \@elements,
                \@CLINTFLAGS, \@CXXLINTFLAGS,
                \@CLINTFLAGS_DEBUG, \@CXXLINTFLAGS_DEBUG,
                \@CLINTFLAGS_PROD, \@CXXLINTFLAGS_PROD  );
}


#-------------------------------------------------------------------------------
# Function        : __AddFlags
#
# Description     : Generic flag adding to lists routine
#                   Internal use only
#
#                   Supports --Debug and --Prod options
#                   if the appropriate list is present.
#
# Inputs          : Lots
#                   References to compiler and lint flags for
#                   common, debug and product builds.
#
#                   Not all the lists are needed.
#
# Returns         : Nothing
#
sub __AddFlags
{
    my ($textname, $eref,
                   $f_all,      $flint_all,
                   $f_debug,    $flint_debug,
                   $f_prod,     $flint_prod ) = @_;

    #
    #   Start added flags to the ALL lists
    #
    my $list = $f_all;
    my $lintlist = $flint_all;
    my $nowarn = 0;

    #
    #   Process flags up front
    #
    $nowarn = 1 if ( grep (/^--NoWarn$/, @$eref) );

    #
    #   Process all the user arguments
    #
    ADD:
    foreach my $element ( @$eref )
    {
        #
        #   Skip flags
        #
        if ( $element eq '--NoWarn' )
        {
            next;
        }

        #
        #   Detect --Debug and --Prod options and swap
        #   lists accordingly.
        #
        if ( $element eq '--Debug' )
        {
            Error ("--Debug not supported for $textname") unless ( $f_debug );
            $list = $f_debug;
            $lintlist = $flint_debug;
            next;
        }

        if ( $element eq '--Prod' )
        {
            Error ("--Prod not supported for $textname") unless ( $f_prod );
            $list = $f_prod;
            $lintlist = $flint_prod;
            next;
        }

        #
        #   Scan all the lists for a possible duplicates
        #
        foreach my $temp ( @$f_all, @$f_debug, @$f_prod ) {
            if ($temp eq $element) {
                Warning( "Duplicate $textname ignored '$element'") unless $nowarn;
                next ADD;
            }
        }

        #
        #   Add the flag to the compiler and lint lists
        #
        push( @$list, $element ) if $list;
        push( @$lintlist, $element ) if $lintlist;
    }
}

sub WarnIfNastyFlag
{
    foreach  ( @_ )
    {
        Warning("Use of compiler flags discouraged (will be used): $_")
            unless ( m/^-[DU]/ || m/^--Debug/ || m/^--Prod/ || /^--NoWarn/ );
    }
}


sub AddDir
{
    AddIncDir( @_);
    AddSrcDir( @_ );
}


sub AddIncDir
{
    _AddDir( 'AddIncDir', 'INCDIR', \@INCDIRS, \@S_INCDIRS, \@G_INCDIRS, \@L_INCDIRS, @_ );
}                                                           
                                                            
sub AddSrcDir                                               
{                                                           
    _AddDir( 'AddSrcDir', 'SRCDIR', \@SRCDIRS, \@S_SRCDIRS, \@G_SRCDIRS, \@L_SRCDIRS, @_ );
}                                                           
                                                            
sub AddLibDir                                               
{                                                           
    _AddDir( 'AddLibDir', 'LIBDIR', \@LIBDIRS, \@S_LIBDIRS, \@G_LIBDIRS, \@L_LIBDIRS, @_ );
}

#-------------------------------------------------------------------------------
# Function        : _AddDir
#
# Description     : Internal routine to add a directory to list of directories
#                   Common code to simplify implementation of other directives
#
# Inputs          : $name           - Name of function
#                   $udir           - User name of dir list
#                   $dirref         - Reference to directory array
#                   $s_dirref       - Reference to system directory array
#                   $g_dirref       - Reference to global directory array
#                   $l_dirref       - Reference to local directory array
#                   @args           - User arguments
#                                       - platforms
#                                       - Directories and --Options
#
sub _AddDir
{
    my( $name, $udir, $dirref, $s_dirref, $g_dirref, $l_dirref, $platforms, @elements ) = @_;

    Debug ( "$name($platforms, @elements)" );
    Error ( "$name: Insufficient arguments") unless ( @elements );
    return if ( ! ActivePlatform($platforms) );

    #
    #   Cleanup user parameters
    #
    foreach ( @elements )
    {
        s/^\s+//;                               # Remove leading space
        s/\s+$//;                               # Remove trailing spaces
        s~/$~~;                                 # Remove trailing /
        s~//~/~g;                               # Remove multiple /
    }

#.. Collect arguments
    my $tlist_ref = $ScmGlobal ? $g_dirref : $l_dirref; # "current" scope ....
    my $nowarn = 0;
    my $nodepend = 0;
    my @dirs;

    foreach ( @elements )
    {
        if ( ! /^--/ ) {                        # Collect directories
            push @dirs, $_;

        } elsif (/^--Local$/) {                 # "local" scope ....
            $tlist_ref = $l_dirref;

        } elsif (/^--Global$/) {                # "global" scope ...
            $tlist_ref = $g_dirref;

        } elsif (/^--System$/) {                # "system" scope ...
            $tlist_ref = $s_dirref;

        } elsif (/^--NoDepend$/) {              # Split from dependency list
            if ( $udir eq 'INCDIR' ) {          # AddIncDir only
                $nodepend = 1;
            }

        } elsif (/^--NoWarn$/) {                # Disable warnings
            $nowarn = 1;

        } elsif (/^--(.*)/) {
            Message( "$name: unknown option $_ -- ignored\n" );

        }
    }

    Error ( "$name: No directory specified: ($platforms, @elements)" )
        unless ( @dirs );
    

#.. Push source path(s)
    foreach ( @dirs )
    {
        #
        #   Add to complete list of directories
        #   Warn on duplicates
        #
        unless ( UniquePush( $dirref, $_) )
        {
            Warning( "Duplicate $udir ignored '$_'" )
                unless ( $nowarn );
            next;
        }

        #
        #   Check that the directory actually exists
        #   If the path contains a $(XXXXX) then it can't be checked
        #
        if ( index( $_, '$' ) == -1 )
        {
            Warning( "$name. Directory not found: $_",
                     "Current directory         : $::Cwd",
                     "Cannot resolved Directory : " . AbsPath($_, $::Cwd, 1),
                       )
                unless ( $nowarn || -d $_ );
        }

        #
        #   Add to suitable list
        #
        push @{$tlist_ref}, $_;
        ToolsetFiles::AddDir($_, 'Include');

        #
        #   Add to the no dependancy list (ie generated depend file)
        #   Only used by AddIncDir, accepted by AddSrcDir
        #
        push( @NODEPDIRS, $_ )
            if ($nodepend);
    }
}

#-------------------------------------------------------------------------------
# Function        : ExtendIncDir 
#
# Description     : Allow the directory search paths to be extended into sub
#                   directories.
#                   
#                   Limit use, but ...
#
# Inputs          : $platform       - Active platforms
#                   ...             - Path extensions
#
# Returns         : 
#
my  %ExtendIncDirSeen;
sub ExtendIncDir
{
    my( $platforms, @paths ) = @_;
    Debug2( "ExtendIncDir($platforms, @paths)" );
    return if ( ! ActivePlatform($platforms) );
    Error ('ExtendIncDir. No extensions listed') unless @paths;
    #
    #   Ensure the user only extends the paths once.
    #   Could silently discard excess - better to force the user to get it right
    #
    foreach my $path ( @paths) {
        ReportError ('ExtendIncDir. Multiple defintions for: ' . $path) if exists $ExtendIncDirSeen{$path}; 
        $ExtendIncDirSeen{$path} = 0;
    }
    ErrorDoExit();

    #
    #   Zip though all the packages and extend the search paths
    #   Also gets those in the interface directory
    #
    for my $package (@{$::ScmBuildPkgRules{$ScmPlatform} })
    {
        #DebugDumpData("package", $package);
        foreach my $path ( @paths)
        {
            if ( $package->{PINCDIRS})
            {
                my @output;
                foreach my $pdir ( @{$package->{PINCDIRS}})
                {
                    my $tdir = catdir ($pdir, $path);
                    my $adir = catdir ($package->{ROOT}, $tdir);
                    if ( -d $adir) {
                        Verbose("Extending $package->{DNAME} $package->{VERSION}: $tdir");
                        push @output, $tdir;
                        $ExtendIncDirSeen{$path}++;
                    }
                    push @output, $pdir;
                }
              @{$package->{PINCDIRS}} = @output;
            }
        }
    }

    #
    #   Report extensions that have not been used
    #
    foreach my $path ( @paths) {
        ReportError ('ExtendIncDir. Search path not extendable: ' . $path) unless $ExtendIncDirSeen{$path}; 
    }

    ErrorDoExit();
}


sub AddProg
{
    my( $platforms, @progs ) = @_;

    Debug2( "AddProg($platforms, @progs)" );

    return if ( ! ActivePlatform($platforms) );

    foreach my $prog (@progs)
    {
        my $pProg = $PROGS->Get($prog);
        Warning( "Duplicate prog ignored '$prog'" )
            if ( $pProg );
        $pProg = $PROGS->NewAdd($prog)
    }
}


sub AddSourceType
{
    my( $ext, $type ) = @_;

    Debug2( "AddSourceType(@_)" );

    #
    #   Default Source Type (C)
    #
    $type = ".c" unless ( $type );

    Error ("Source type '$ext' not allowed")
        if ( $ext !~ /^\.\w+$/ );

    $type = lc($type)
        if ( $::ScmHost ne "Unix" );
    $ScmSourceTypes{ $ext } = $type;
}


sub AddSourceFile
{
    my( $platforms, @elements ) = @_;

    Debug2( "AddSourceFile($platforms, @elements)" );
    return if ( ! ActivePlatform($platforms) );

    foreach my $path ( @elements )
    {
        __AddSourceFile( 1, $path );
    }
}


#-------------------------------------------------------------------------------
# Function        : __AddSourceFile
#
# Description     : Internal function
#                   Add a source file to internal lists
#
#                   Assumes that the current platform is ACTIVE
#
# Inputs          : push    0: Don't push onto OBJS (non-shared objfiles)
#                   path    Filename.extension
#                   obj     object file name (optional)
#                   type    Type of file. "" -> auto detect
#
# Returns         : True        - File is a 'known' source file
#                   False       - File is not a 'known' source file
#
sub __AddSourceFile
{
    my( $push, $path, $obj, $type ) = @_;
    my( $filename, $ext, $srcfile, $is_obj, $ext_type, $result );

    $filename = StripDir($path);                # file name

    $ext  = StripFile($path);                   # extension
    $ext = lc($ext)
        if ( $::ScmHost ne "Unix" );

    if (! ($srcfile = $SRCS{$filename})) {
        $srcfile = $path;                       # generated
    }

    $obj  = StripExt( $filename )               # Base name of object file
        if ( ! defined($obj) || $obj eq "" );

    $type = ""                                  # optional type
        if ( ! defined( $type ) );

    #
    #   Push file onto a suitable source file list
    #
    $result = 0;
    $ext_type = "";                             # map extension
    $ext_type = $ScmSourceTypes{ $ext }
        if ( exists( $ScmSourceTypes{ $ext } ) );
    $result = 1 if ( $ext_type );

    if ( $type eq "" && defined $::ScmToolsetProgSource{$ext} )
    {
        Debug( "SourceFile: $path is ToolsetProgSource   -> $srcfile" );
        push( @CSRCS, $srcfile );
        $result = 1;
    }
    elsif ( ($type eq "" && $ext_type eq ".h") || ($type eq ".h") )
    {
        Debug( "SourceFile: $path is .h   -> $srcfile" );
        push( @CHDRS, $srcfile );
    }
    elsif ( ($type eq "" && $ext_type eq ".inc") || ($type eq ".inc") )
    {
        Debug( "SourceFile: $path is .inc -> $srcfile" );
        push( @ASHDRS, $srcfile );
    }
    elsif ( ($type eq "" && $ext_type eq ".c") || ($type eq ".c") )
    {
        Debug( "SourceFile: $path is .c   -> $srcfile=$obj" );
        push( @CSRCS, $srcfile );
        $is_obj = 1;
    }
    elsif ( ($type eq "" && $ext_type eq ".cc") || ($type eq ".cc") )
    {
        Debug( "SourceFile: $path is .cc  -> $srcfile=$obj" );
        push( @CXXSRCS, $srcfile );
        $is_obj = 1;
    }
    elsif ( ($type eq "" && $ext_type eq ".asm") || ($type eq ".asm") )
    {
        Debug( "SourceFile: $path is .asm -> $srcfile=$obj" );
        push( @ASSRCS, $srcfile );
        $is_obj = 1;
    }
    elsif ( $ext_type eq "--Ignore" )
    {   # ignored ...
        #   .x      "rpcgen" source files
        #   .ini    Configuration
        #   .sh     Shell script
    }
    else
    {
        Debug( "SourceFile: $path is unknown file type" );

        #
        #   Insert source files with unknown extensions onto lists
        #   of there own type
        #
        if ( $ext )
        {
            (my $varname = uc ( $ext . 'SRCS')) =~ s~\.~~g;
            no strict 'refs';
            push @$varname, $srcfile;
            use strict 'refs';
        }
    }

    #
    #   See if there is a hook function for this type of source file
    #   Invoke user function to perform additional processing on the file
    #
    if ( %MF_RegisterSrcHooks )
    {
        my @listeners;
        push @listeners, @{$MF_RegisterSrcHooks{$ext}} if ( exists $MF_RegisterSrcHooks{$ext} );
        push @listeners, @{$MF_RegisterSrcHooks{'*'}}  if ( exists $MF_RegisterSrcHooks{'*'} );
        while ( @listeners )
        {
            Debug( "RegisterSrcHook: Invoke SrcHook function" );
            my ($fname, @args) = @{shift @listeners};
            &$fname ( $srcfile ,$filename, $obj, $ext ,@args );
        }
    }

    #
    #   Object files are saved in
    #       OBJSOURCE   - Generate a recipe to create the object
    #       OBJS        - A list of ALL non-shared object files
    #
    if ( $is_obj && $::o && $ScmNotGeneric )
    {
        $OBJSOURCE{ "$obj" } = $srcfile;
        push( @OBJS, $obj )
            if ($push);
    }

    #
    #   Indicate to the user that the file is a 'known' source file
    #   This implies that the file is required early in the build process
    #   and may need to be generated early.
    #
    return $result;
}

#-------------------------------------------------------------------------------
# Function        : SetValue
#
# Description     : Defines a variable that can be used within the makefile.pl
#                   Use sparingly
#                   An attempt to formalise a mechanism that is used anyway, but
#                   with correct platform detection
#
# Inputs          : $platform       - Platform selector
#                   $name           - Name to set
#                   $value          - Value to set
#                   options         - Options
#                                       --NoWarn
#                                       --Project=xxxx[,xxxx]+
#                                       --
#
sub SetValue
{
    my( $platforms, @elements ) = @_;
    my $name;
    my $value;
    my $nowarn;
    my $nomoreswicthes = 0;

    Debug2( "SetValue($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Process elements extracting values and options
    #
    foreach ( @elements )
    {
        if ( m/^--$/ ) {
            $nomoreswicthes = ! $nomoreswicthes;
            next;
        }

        if ( m/^--/ && ! $nomoreswicthes )
        {
        
            if ( m/^--NoWarn/ ) {
                $nowarn = 1;

            } elsif ( m/^--Project=(.*)/ ) {
                return unless ( ActiveProject( $1) );

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

            }

        } elsif ( ! defined $name ) {
            $name = $_;

        } elsif ( ! defined $value ) {
            $value = $_;

        } else {
            Error ("SetValue: $name. Too many parameters" );

        }
    }

    #
    #   Warn if the named variable already exists
    #   It may be a JATS internal or it may be a user.
    #
    unless ( $nowarn )
    {
        no strict 'refs';
        Warning("SetValue: $name. Redefined") if defined ( $$name );
        use strict 'refs';
    }

    #
    #   Set the value
    #
    no strict 'refs';
    $$name = $value;
    use strict 'refs';
}

#-------------------------------------------------------------------------------
# Function        : SetList
#
# Description     : Defines a list variable that can be used within the makefile.pl
#                   Use sparingly
#                   An attempt to formalise a mechanism that is used anyway, but
#                   with correct platform detection
#
# Inputs          : $platform       - Platform selector
#                   $name           - Name to set
#                   $value,...      - Values to set
#                   options         - Options
#                                       --NoWarn
#                                       --Project=xxxx[,xxxx]+
#                                       --Unique
#                                       --Clear
#                                       --Append
#                                       --
#
my %SetList_names;
sub SetList
{
    my( $platforms, @elements ) = @_;
    my $name;
    my @value;
    my $nowarn;
    my $unique;
    my $clear;
    my $nomoreswicthes = 0;

    Debug2( "SetList($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Process elements extracting values and options
    #
    foreach ( @elements )
    {
        if ( m/^--$/ ) {
            $nomoreswicthes = ! $nomoreswicthes;
            next;
        }

        if ( m/^--/ && ! $nomoreswicthes )
        {
            if ( m/^--NoWarn/ ) {
                $nowarn = 1;

            } elsif ( m/^--Project=(.*)/ ) {
                return unless ( ActiveProject( $1) );

            } elsif ( m/^--Unique/ ) {
                $unique = 1;
            
            } elsif ( m/^--Clear/ ) {
                $clear = 1;

            } elsif ( m/^--Append/ ) {
                $clear = 0;
            
            } else {
                Error ("SetList: Unknown option: $_");
            }
        } elsif ( ! defined $name ) {
            $name = $_;

        } else {
            push @value, $_;

        }
    }

    Error ("SetList: No name specified") unless ( $name );

    #
    #   Warn if the named variable already exists
    #   It may be a JATS internal or it may be a user.
    #
    #   Only do this iff the name is not known to this function
    #   Keep a list a names that have been set.
    #
    if ( ! $SetList_names{$name} && ! $nowarn )
    {
        no strict 'refs';
        Warning("SetList: $name. Defined outside the ScanList/SetList directive","May clash with Jats internals") if ( @$name );
        use strict 'refs';
    }
    $SetList_names{$name} = 1;

    #
    #   Clear list
    #
    if ( $clear )
    {
        no strict 'refs';
        @$name = ();
        use strict 'refs';
    }

    #
    #   Set the value
    #
    no strict 'refs';
    if ( $unique ) {
        UniquePush( \@$name, @value);
    } else {
        push @$name, @value;
    }
    use strict 'refs';
}

#-------------------------------------------------------------------------------
# Function        : ScanList
#
# Description     : Create a list by scanning for files in a directory
#                   The files may be in a local directory or within a package
#                   Care must be taken when using a package as the results
#                   may differ bewteen BuildPkgArchive and LinkPkgArchive
#
#                   Interworks with SetList
#
# Inputs          : $platform       - Platform selector
#                   $name           - Name to set
#                   $value,...      - Values to set
#                   options         - Options
#                                       --NoWarn
#                                       --Project=xxxx[,xxxx]+
#                                       --Unique
#                                       --Clear
#                                       --Append
#
#                                       --Package=xxxx[,ext]
#                                       --Dir=xxx
#
#                                       --Subdir=yyy
#                                       --DirListOnly
#                                       --FileListOnly
#                                       --Recurse (default)
#                                       --NoRecurse
#                                       --FullPath (default)
#                                       --NoFullPath
#
#                                       --FilterIn=xxx
#                                       --FilterInRe=xxx
#                                       --FilterOut=xxx
#                                       --FilterOutRe=xxx
#
# Returns         :
#
sub ScanList
{
    my( $platforms, @elements ) = @_;
    my $name;
    my $package;
    my $dir;
    my $subdir;
    my @set_args;
    my $search = JatsLocateFiles->new('Recurse','FullPath' );

    Debug2( "ScanList($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Process elements extracting values and options
    #
    foreach ( @elements )
    {
        if ( m/^--Unique|--Clear|--Append|--NoWarn/ ) {
            push @set_args, $_;

        } elsif ( m/^--Project=(.*)/ ) {
            return unless ( ActiveProject( $1) );

        } elsif ( m/^--Package=(.*)/ ) {
            $package = $1;

        } elsif ( m/^--Dir=(.*)/ ) {
            $dir = $1;
                
        } elsif ( m/^--Subdir=(.*)/ ) {
            $subdir = $1;

        } elsif ( $search->option( $_ ) ) {
            Verbose ("Search Option: $_" );

        } elsif ( m/^--/ ) {
            Error ("ScanList: Unknown option: $_");

        } elsif ( ! defined $name ) {
            $name = $_;

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

        }
    }

    Error ("ScanList: No variable name specified") unless ( $name );
    Error ("ScanList: Must Specify --Dir or --Package") unless ( $dir || $package );
    Error ("ScanList: --Dir and --Package are mutually exclusive") if ( $dir && $package );

    #
    #   Locate the base of the scan
    #   This may be either a package name or a local directory
    #
    #   Its no use allowing the user to use OBJ/LIB/BIN directories as the
    #   directories MUST exist at build time. Don't really want the user doing
    #   that level of poking out of a package
    #
    if ( $package )
    {
        $dir = GetPackageBase( "ScanList", $package );
        Error ("ScanList: Package not found: $package") unless ( $dir );
    }
    else
    {
        Error ("ScanList: Root directory not found: $dir") unless ( -d $dir );
    }
    if ( $subdir )
    {
        $dir .= "/" . $subdir;
        Error ("ScanList: Sub directory not found: $subdir") unless ( -d $dir );
    }

    #
    #   Use SetList to do the rest of the work
    #
    SetList( $platforms, $name, @set_args, '--', $search->search($dir) );
}


sub Init
{
    push( @INITS, @_ );
}

#-------------------------------------------------------------------------------
# Function        : Generate
#
# Description     : Legacy Function - don't use unless you have too.
#                   Flags files that are to be generated during the
#                   early 'generate' make phase. Will also add named files
#                   to various internal lists
#
#                   Intended to be used in conjunction with the 'Rule' directive
#                   to flag header and source files that need to be created early
#                   in the build process.
#
# Inputs          : See GenerateSrcFile
#
# Returns         : 
#
sub Generate
{
    my( $platforms, @elements ) = @_;

    Debug2( "Generate($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );
    Message("Generate directive used. Consider replacing with GenerateFiles");

    #
    #   Use Non-warning version to do the hard work
    #
    GenerateSrcFile( 1, @elements );
}

#-------------------------------------------------------------------------------
# Function        : Generated
#
# Description     : Legacy Function - don't use unless you have too.
#                   Flags files that are generated by misc Rules
#
#                   Intended to be used in conjunction with the 'Rule' directive
#                   to mark files that have been generated, so that they can be
#                   cleaned up.
#
#                   Note the difference to the 'Generate' directive which will
#                   ensure that the Rule will be run in the 'generate' phase,
#                   this directive doesn't.
#
# Inputs          : Files with internal Makefile Paths and codes
#                   Eg: Generated( '*', "\$(LIBDIR)/libcsf\$(GBE_TYPE).\${a}" );
#                   See why its YUK!
#
# Returns         : 
#
sub Generated
{
    my( $platforms, @elements ) = @_;
    my( @args );

    return if ( ! ActivePlatform($platforms) );
    Debug2( "Generated($platforms, @elements)" );

    #.. Collect arguments
    #
    foreach ( @elements )
    {
        if ( /^-(.*)/ )
        {
            Debug( "Gen: arg $_" );
            push ( @args, $_);
        }
    }

    #.. Push source file(s)
    #
    foreach ( @elements )
    {
        if ( ! /^-(.*)/ )
        {
            Debug( "Generated: $_ (@args)" );
            push (@USERGENERATED, $_);

            #
            #   Add the file to the list of known source files
            #   This will allow them to be packaged
            #
            GenerateSrcFile (0, $_ );
        }
    }
}


#-------------------------------------------------------------------------------
# Function        : GenerateSrcFile
#
# Description     : Internal Function (No $platform)
#                   Determine how to handle a 'Generated' file
#
#
# Inputs          : $generated          - 0: Don't add to GENERATED List
#                                         1: Add to GENERATED List
#                                         2: Add to GENERATED List, if a source file
#                   FileName(s)         - Name of one or more files to process
#                                         All files are processed in the same way
#                                         These file may contain Makefile prefixes
#                                         ie: $(OBJDIR)/file.obj
#                   Options:
#                       --c             - Hint: Its a "C" file
#                       --cpp           - Hint: Its a C++ file
#                       --asm           - Hint: Its an ASM file
#                       -*              - Save as argument attached to the file
#
# Returns         : Number of 'source' file
#
sub GenerateSrcFile                             # Internal Function - no $platform
{
    my( $generated, @elements ) = @_;
    my( $type, @args );
    my $result = 0;

    Debug2( "GenerateSrcFile($generated,@elements)" );

    #.. Collect arguments
    #
    $type = "";
    foreach ( @elements )
    {
        if ( /^--c$/ ) {
            Debug( "Gen: --c" );
            $type = ".c";

        } elsif ( /^--cpp$/ ) {
            Debug( "Gen: --cpp" );
            $type = ".cc";

        } elsif ( /^--asm$/ ) {
            Debug( "Gen: --asm" );
            $type = ".asm";

        } elsif ( /^-(.*)/ ) {
            Debug( "Src: arg $_" );
            push @args, $_;
        }
    }

    #.. Process source file(s)
    #
    #   Determine if file is already a known SRCS file - skip if already known
    #   Update SRCS data
    #   Update SRC_TYPE data
    #   Update SRC_ARGS data
    #   Add the file to a suitable source file list ie: @CHDRS,...
    #   Flag as a GENERATED file - These will be processed during the 'generate' phase
    #
    foreach my $source ( @elements )
    {
        next if ( $source =~ /^-(.*)/ );                # Not a source file

        my $basename = StripDir( $source );
        Debug( "Generate: $source=$basename (@args)" );

        if ($SRCS{ $basename }) {
            Warning( "Duplicate src ignored '$source'" );
            next;
        }
        $SRCS{ $basename } = $source;

        HashJoin( \%SRC_ARGS, $;, $basename, @args )
            if (@args);

        $SRC_TYPE{ $basename } = $type
            if ($type);

        #
        #   Add the file to any source file lists that may like to know
        #   about this file.
        #
        #   If the file was a known source file, then it may need to be generated
        #   very early in the build process.
        #
        my $src_file_type = __AddSourceFile( 1, $basename );
        if ($generated == 1 || ($src_file_type && $generated > 1) )
        {
            push(@GENERATED, $source);
            $result++;
        }
        else
        {
            push(@GENERATED_NOTSRC, $source);
        }
    }

    return $result;
}

#-------------------------------------------------------------------------------
# Function        : GenerateFiles
#
# Description     : Generate files in a controlled manner using a specified
#                   tool to perform the task
#
# Inputs          : $1      - platform specifier '*' (comma delemitered)
#                   $2      - Tool Name
#                   $3...   - Command line argument to generate files with embedded information
#                           - or options. Multiple command line arguments will be joind with
#                             a single space
#
#                   The generated files will be placed in the OBJ directory for
#                   the current target platform. This allows different files to
#                   be generated for each platform, without collision.
#
#                   The full name of the generated files will be added to the list of
#                   source files. Thus the user does not need to know the
#                   full name of the file - it will be tracked by JATS.
#
#                   If a generated file is a header file, then the OBJ directory
#                   will be added as AddIncDir() so that the header files can be
#                   extracted
#
#                   If a generated file is a "C"/"C++" source file, then it will
#                   compiled and the object file made available
#
#                   The tool name may be:
#                       --Tool=name  or "name"
#                               Look in the tool paths in Packages
#                               Look in the JATS tool directory for named script
#                               Look in the JATS bin directory for the named exe
#                               Look in the users path ( and generate a warning )
#                               Give up and hope magic happens later
#                       --Script=name
#                               Resolve the name using known Src paths
#                               The script may be generated and need not exist
#                               at the time the makefile is created.
#                       --Shell
#                               The command line argument is a shell script that
#                               will be passed to a simple shell.
#                       --Prog=name
#                               Resolve to a program generated within this makefile
#
#
#                   The command line argument contains keywords to allow
#                   information to be extracted from the line. Keywords are:
#
#                       --Generated(xxx)        - xxx is a generated file
#                                                 It will be placed in the OBJDIR
#                       --GeneratedCommon(xxx)  - xxx is a generated file
#                                                 File will be placed in the local directory
#                                                 and will be shared by by all platforms
#                       --GeneratedObject(xxx)  - xxx is a generated object file
#                                                 It will be placed in the OBJDIR and will
#                                                 have a suitable object suffix appended
#                       --GeneratedProg(xxx)    - xxx is a generated program file
#                                                 It will be placed in the BINDIR
#                       --Prerequisite(xxx)     - xxx is a prerequisite file
#                                                 The full name of the file will be located
#                                                 and used within the command. The file will
#                                                 be added to the list of recipe prerequisites
#                       --GeneratedDirectory(xxx)
#                       --GeneratedCommonDirectory(xxx)
#                       --GeneratedObjectDirectory(xxx)
#                       --GeneratedProgDirectory(xxx)
#                                               - xxx is a generated file
#                                                 The containing directory will be placed on the command line
#                       --PackageBase(xxx)      - xxx is a package. The keyword will be replaced
#                                                 with the pathname to the package. If the package
#                                                 has been copied in the the interface directory
#                                                 then the interface directory will be used.
#                       --PackageInfo(xxx,--opt)- xxx is a package. The keyword will be replaced
#                                                 with the information requested.
#                                                 Options are:
#                                                   --path
#                                                   --version
#                                                   --fullversion
#                                                   --project
#
#                       Where "xxx" may be of the form:
#                           name,option[,option]
#
#                       Flag options are:
#                           --file             - The file part of the full name
#                           --dir              - The directory part of the full name
#                           --abspath          - Abs path
#                           --absdrive         - Abs path with drive letter
#
#                       --Var(Name,opt)         - Name is the name of a recognised varable
#                                                 Refer to ExpandGenVar function for details
#                                                 of Name and available options
#                                                 The expanded text will be replaced with an
#                                                 suitable makefile variables that will be
#                                                 replaced at run-time.
#                                                 
#                       --Tool(Name,opt)        - Name is the name of a 'tool' found withnin a package
#                                                 The argument is replaced with the full path of the
#                                                 tool. opt may be
#                                                   --dir
#                                                   --file
#                                                   --abspath
#                                                   --absdrive
#
#                   The keyword will be replaced with the resolved name. This may be a file,
#                   a directory or other text.
#
#                   Options do not alter command line text. They do affect the way the command is
#                   processed.
#                   Options include:
#                       --Prereq=name           - The name of a file to add as a prerequisite
#                                                 The file does not form part of the command line
#                       --Created=name          - The name of a file to treat as a generated file
#                       --CreatedCommon=name      The file does not form part of the command line 
#                       --CreatedObject=name
#                       --CreatedProg=name
#
#                       --NoVarTag              - Modifes --Var operation to suppress tags
#                       --NoWarn                - Don't warn if no prerequistes found
#                       --NoGenerate            - Don't warn if no generated files are found
#                                                 Will create a dummy rule name and the recipe will
#                                                 always be executed during the 'GenerateFiles' phase
#                       --UnknownPreq           - Prerequisites are not fully known.
#                                                 Rebuild the target whenever it is required.
#                       --AutoGenerate          - Examine the generated file to determine when the
#                                                 tools is to be run.
#                                                 Must be before any options that declare
#                                                 creation of files.
#                       --Text=<text>           - Display text for command
#
#                       --Clean[=arg]           - Call script with arg[-clean] for cleaning.
#                       --PreDelete             - Delete generated files before running the command
#                       --RecipeTag=Name        - Name the recipe
#                                                 Allows recipe to be called by Name and clean_Name
#                                                 
#                       --UtfFormat=name        - Flags the program as a 'test' to be run in the Test Phase
#                                                 Intended to support things like Gradle that run tests in there own world
#                                                 Enables support for (Must occur first)
#                           --AutoUtf           - Non interactive unit test
#                           --NoAutoUtf         - Interactive unit Test
#                           --UtfArg=nnn        - Argument passed into the UTF formatter
#                           --UtfDir=path       - SUbdir in which the Unit Tests will be run    
#                       
#
#               Eg: GenerateFiles ( '*', "--Tool=mod_if.pl",
#                                        "-src --Prerequisite(udh_module.cfg)",
#                                        "-direct -hdr --Generated(udp.h) -quiet" );
#
my $NoGenIndex = 0;
my %recipeTags;
sub GenerateFiles
{
    #
    #   Remove undfined arguments
    #   Simplifies programatic construction of argument lists
    #
    my ( $platforms, $tool, @args) = grep defined, @_;

    return if ( ! ActivePlatform($platforms) );

    Debug( "GenerateFiles:($platforms, $tool, @args)" );

    my @preq_files;
    my $preq_unknown;
    my @gen_files;
    my $shell_script;
    my $shell_cmds;
    my @tool_args;
    my $no_warn;
    my $clean_tag;
    my $text;
    my $gtype = 1;
    my @has_source;
    my @var_opts;
    my @genreq_seen;
    my $predelete;
    my $recipeTag;
    my $utfAuto;
    my $utfFormat;
    my $utfDir;
    my @utfArgs;
    my $isaUtf;
    my $noGenerate;

    #
    #   Process the first argument - this describes the program that will be used
    #   to generate the files. It may be:
    #       --Tool          - A Jats Tool or Plugin
    #       --Script        - A shell script file
    #       --Shell         - Raw shell commands
    #       --Prog          - A program created within the Makefile
    #
    #
    if ( $tool =~ /^--Tool=(.*)/ || $tool =~ /^([^-].*)/)
    {
        $tool = $1;
        my $tool_no_prereq = 0;

        #
        #   Process the Tool name and determine the location of the tool
        #   Support --Tool=name and "name"
        #   Locate the tool one of the many well known locations
        #       1) Tool paths from Package Archives
        #       2) JATS tool and binaries
        #       3) User PATH (!YUK)
        #

        #
        #   Create a list of known extensions to scan
        #   Basically present so that we can use .exe files without the .exe name
        #
        my @extension_list;
        push @extension_list, '.exe' if ( $::ScmHost ne "Unix" );
        push @extension_list, '.pl', '.sh', '.ksh', '';
        TOOL_SEARCH:
        {
            #
            #   Locate tool with package
            #
            if ( my $fname = ToolExtensionProgram( $tool, @extension_list ))
            {
                $tool = $fname;
                last TOOL_SEARCH;
            }

            #
            #   Search the JATS tools and Bin directory
            #   Retain the symbolic name of the JATS directory
            #
            for my $ext ( @extension_list )
            {
                foreach my $jdir ( qw( / /DEPLOY/ /LOCAL/ ) )
                {
                    if ( -f "$::GBE_TOOLS$jdir$tool$ext" )
                    {
                        $tool = "\$(GBE_TOOLS)$jdir$tool$ext";
                        last TOOL_SEARCH;
                    }
                }

                if ( -f "$::GBE_BIN/$tool$ext" )
                {
                    $tool = "\$(GBE_BIN)/$tool$ext";
                    last TOOL_SEARCH;
                }
            }

            #
            #   Has the user provided an absolute PATH
            #   This is not good, but for testing we can use it
            #
            if ( $tool =~ m~^/~ || $tool =~ m~^.:~ )
            {
                Warning("Absolute path program specified. Uncontrolled tool: $tool");
                for my $ext ( @extension_list )
                {
                    if ( -f "$tool$ext" )
                    {
                        $tool = "$tool$ext";
                        last TOOL_SEARCH;
                    }
                }
            }

            #
            #   May have a relative path to a local tool
            #
            if ( -f $tool )
            {
                UniquePush (\@preq_files, $tool);
                last TOOL_SEARCH;
            }

            #
            #   Search the users PATH
            #   Generate a warning if the program is found. These programs are
            #   not nice as they are not really controlled.
            #
            for my $dir (split( $::ScmPathSep, $ENV{'PATH'} ) )
            {
                for my $ext ( @extension_list )
                {
                    if ( -f "$dir/$tool$ext" )
                    {
                        Warning("External program found in the user's PATH. Uncontrolled tool: $tool");
                        $tool = "$dir/$tool$ext";

                        #
                        #   Do not make the program a pre-requisite if we are running
                        #   under Windows. This avoids two problems:
                        #       1) May have spaces in pathname
                        #       2) May have driver letter in pathname
                        #
                        $tool_no_prereq = 1 if ( $::ScmHost eq "WIN" );
                        last TOOL_SEARCH;
                    }
                }
            }

            #
            #   Specified progrom not found
            #   Generate a warning and use the raw name
            #
            Warning("Tool not found: $tool");
            $tool_no_prereq = 1;
        }
        UniquePush (\@preq_files, $tool) unless ($tool_no_prereq);

    } elsif ( $tool =~ /^--Script=(.*)/ ) {

        #
        #   Locate the script in a known source directory and make
        #   the script a prerequisite of the target files, since the
        #   script may be generated.
        #
        $tool = MakeSrcResolve ( $1 );
        UniquePush (\@preq_files, $tool);

    } elsif ( $tool =~ /^--Shell$/ ) {
        #
        #   The user has provided a shell script within the command body
        #   This will be executed directly by a shell
        #   directores will need to use a "/" separator
        #
        $tool = "InternalShell";
        $shell_script = 1;
        $shell_cmds = 1;


    } elsif ( $tool =~ /^--Prog=(.*)$/ ) {
        #
        #   Using a program that has been created within this script
        #
        my $prog = $1;
        if ( my $pProg = $PROGS->Get($prog) )
        {
            $tool = $pProg->getPath()
                unless ( $tool = $SRCS{$prog} );
        UniquePush (\@preq_files, $tool);
        }
        else
        {
            Error ("Unknown program: $prog");
        }

    } else {

        #
        #   Currently generate a warning and then use the raw tool name
        #
        Error ("Unknown TOOL syntax: $tool");
    }

    #
    #   May need to quote the path
    #   If the toolpath contains spaces then ugliness can occur - so quote the program
    #
    $tool = '"' . $tool . '"'
        if ( (! $shell_script ) && $tool =~ m~\s~ );
    
    #
    #   Determine special startup for various programs
    #       Perl  - use known implemenatation
    #       Shell - use known implemenatation
    #       Otherwise - simply run it
    #
    #   Windows: Shell and Perl don't need '\' in paths
    #
    if ( $tool =~ /\.pl$/ )
    {
        $tool = "\$(GBE_PERL) $tool";
        $shell_script = 1;
    }
    elsif ( $tool =~ /\.k?sh$/ )
    {
        $tool = "\$(GBE_BIN)/sh $tool";
        $shell_script = 1;
    }
    Debug( "GenerateFiles: Tool: $tool" );


    #
    #   Process the remaining arguments
    #   These will be command line arguments or options/flags
    #   Command line arguments are concatenated together
    #
    for my $arg (@args)
    {
        if ( $arg =~ /^--PreDelete$/ )
        {
            #
            #   Delete generated files before running the generation process
            #   Some programs refuse to overwrite existing files
            #
            $predelete = 1;
            next;

        } elsif ( $arg =~ /^--NoVarTag$/ ) {
            #
            #   Modify the operation of --Var to supress the tags
            #   Should be used early as will only affect following --Var usage
            #
            push @var_opts, "--notag";
            next;

        } elsif ( $arg =~ /^--NoWarn$/ ) {
            #
            #   Supress warnings - No prequisites found
            #   This is acceptable, but normally a tool should take an input
            #   and create some output from it.
            #
            $no_warn = 1;
            next;

        } elsif ( $arg =~ /^--NoGenerate$/ ) {
            #
            #   Tool does generate a definable output
            #       Should only be used internally
            #
            $noGenerate = 1;
            next;

        } elsif ( $arg =~ /^--UnknownPreq/ ) {
            #
            #   Indicate that the prequisites are not known, or too complex to
            #   describe. ie: All files in a directory. May be used by packaging
            #   tools.
            #   The recipe will be run EVERY time we want to use the target.
            #
            $preq_unknown = 1;
            $no_warn = 1;
            next;

        } elsif ( $arg =~ /^--AutoGenerate/ ) {
            #
            #   Determine when to run the tool based on the types of files that
            #   are generated. Existance of a source file will force the tool
            #   to be run during the 'generate' phase, othewise the tool will be run
            #   when the generated components are required.
            #
            $gtype = 2;
            Warning ("AutoGenerate MUST occur before options that declare generation of files",
                     "Have seen:", @genreq_seen)
                if (@genreq_seen);
            next;

        } elsif ( $arg =~ /^--Prereq=(.*)/ ) {
            #
            #   Specify a prerequisite file, that is not a part of the command line
            #   Simply add the files to the list of preq files
            #
            my $fn = LocatePreReq ($1);
            UniquePush ( \@preq_files, $fn );
            Debug( "GenerateFiles: ExtraPrereq: $fn" );
            next;

        } elsif ( $arg =~ /^--Created(.*)=(.*)/ ) {
            #
            #   Specify a generated file, that is not a part of the command line
            #   Add the files to the list of generated files
            #
            my $type = $1;
            my $fn = $2;

            #
            #   Append object suffix to CreatedObject
            #
            $fn .= '.' . $::o
                if ( $type =~ m/Object/ );

            #
            #   If the files is 'created' in a subdir, then add the dir
            #   as a prerequisite.
            #
            if ( $type =~ m/Prog/ ) {
                $fn = "\$(BINDIR)/$fn";
                UniquePush (\@preq_files, '$(GBE_BINDIR)');
                
            } elsif ( $type !~ m/Common/ ) {
                $fn = "\$(OBJDIR)/$fn";
                UniquePush (\@preq_files, '$(GBE_OBJDIR)');
            }

            #
            #   Examine the file and see if it needs to be compiled
            #   Add to the list of source files
            #
            push @genreq_seen, $arg;
            if ( UniquePush (\@gen_files, $fn) )
            {
                if ( GenerateSrcFile ( $gtype, $fn  ) && $gtype == 2 )
                {
                    push @has_source, $fn;
                }
            }
            Debug( "GenerateFiles: ExtraCreated: $fn" );
            next;

        } elsif ( $arg =~ /^--Clean($|=(.*))/ ) {
            #
            #   Detect Clean option
            #
            $clean_tag = $2 ? $2 : '-clean';

            #
            #   Shell command with a --Clean will only
            #   be run during a clean phase. They should not have any prereq
            #   and should not generate any files, so simplify the interface.
            #
            push @args, '--NoWarn', '--NoGenerate'
                if ( $shell_cmds );
            next;

        } elsif ( $arg =~ /^--Text=(.*)/ ) {
            #
            #   Display this text when executing commands
            #
            $text = $1;
            next;

        } elsif ( $arg =~ /^--RecipeTag=(.*)/ ) {
            #
            #   Tag the generated Recipe
            #   Only use the last tag - allow users to overwrite system tags
            #
            $recipeTag = $1;
            Error ("Duplicate RecipeTag - $recipeTag") if ($recipeTags{$recipeTag}++ > 1);
            next;

        } elsif ( $arg =~ m/^--AutoUtf$/i) {
            $utfAuto = 1;
            $isaUtf = 1;
            next;

        } elsif ( $arg =~ m/^--NoAutoUtf$/i ) {
            $utfAuto = 0;
            $isaUtf = 1;
            next;

        } elsif ( $arg =~ m/^--UtfFormat=(.*)/i) {
            $utfFormat = $1;
            $isaUtf = 1;
            next;

        } elsif ( $arg =~ m/^--UtfDir=(.*)/i) {
            $utfDir = $1;
            $isaUtf = 1;
            next;

        } elsif ( $arg =~ m/^--UtfArg=(.*)/i) {
            push @utfArgs, $1;
            $isaUtf = 1;
            next;
        }


        #   Not an option. Must be an argument to the tool/program
        #   Process the tool arguments and extract file information
        #   Extract all fields of the form:
        #           --xxxxx(yyyyyy[,zzzzz])
        #           --xxxxx{yyyyyyy}
        #           --xxxxx[yyyyyyy] to allow embedded brackets
        #
        while ( $arg =~ m/--(\w+)               # --CommandWord         $1
                                (               # Just for grouping
                                \((.*?)\)   |   # Stuff like (yyyyy)    $3
                                {(.*?)}     |   # or    like {yyyyy}    $4
                                \[(.*?)\]       # or    like [yyyyy]    $5
                                )/x )           # Allow comments and whitespace
        {
            my $cmd = $1;                       # The command
            my $ufn = $3 || $4 || $5;           # User filename + options
            my $mb = $-[0];                     # Match begin offset
            my $me = $+[0];                     # Match end
            my $flags = '';                     # Optional flags ( --dir or --file )
            my $raw_arg = $ufn;                 # Raw arguments
            my $all = substr( $arg, $mb, $me - $mb ); # All of match. Avoid use of $&
            my $is_path = 1;
            

            Error ("GenerateFiles. Empty element not allowed: $all")
                unless ( defined($ufn) );

            $ufn =~ s/\s+$//;
            $ufn =~ s/^\s+//;
            $ufn =~ s~//~/~g;                   # Remove multiple /
            if ( $ufn =~ m/(.*?),(.*)/ )        # Extract out any flags
            {
                $ufn = $1;
                $flags = $2;
            }

            my $fn = $ufn ;                     # Replacement filename
            my $fnp = '';                       # Prefix to $fn
            Error ("GenerateFiles. Empty element not allowed: $all" )
                if ( length ($ufn) <= 0 );

            #
            #   Process found user command
            #
            if ( $cmd =~ /^Generated/ )
            {
                my $use_dir = "";

                #
                #   Generated filename
                #       Determine the target directory
                #       Determine the full name of the file.
                #       Flag the file as generated
                #
                if ( $cmd =~ /Prog/ )
                {
                    #
                    #   Generated Prog are generated in the BIN directory
                    #   Ensure the directory exists by using its symbolic name
                    #   as a prerequisite.
                    #
                    $use_dir = '$(BINDIR)';
                    UniquePush (\@preq_files, '$(GBE_BINDIR)');
                }
                elsif ( $cmd !~ /Common/ )
                {
                    #
                    #   Files that are not Common are generated in the
                    #   object directory. This directory must exist, so it
                    #   symbolic name GBE_OBJDIR is made a prerequisite too.
                    #
                    #   If the file is a header file, then add the directory
                    #   to the include search path too.
                    #
                    $use_dir = '$(OBJDIR)';
                    UniquePush (\@preq_files, '$(GBE_OBJDIR)');
                    AddIncDir( $platforms , '$(OBJDIR)', '--NoWarn' )
                        if ( $ScmSourceTypes{ StripFile($fn) } && $ScmSourceTypes{ StripFile($fn) } eq ".h" );
                }


                #
                #   Append a toolset specfic object file name suffix
                #   for Object files only
                #
                $fn .= ".$::o"
                    if ( $cmd =~ /Object/ );

                #
                #   Merge directory and filename parts
                #
                $fn = $use_dir . ( $use_dir ? "/" : ""  ) . $fn;

                #
                #   Save for later user
                #   Flag the file as a generated file
                #
                push @genreq_seen, $cmd;
                if ( UniquePush (\@gen_files, $fn) )
                {
                    if ($SRCS{ StripDir( $fn ) })
                    {
                        abtWarning(1,"GenerateFiles. Generated File also a Src file: $fn");
                    }
                    elsif ( GenerateSrcFile ( $gtype, $fn  ) )
                    {
                        push ( @has_source, $fn ) if ($gtype == 2);
                    }
                }

                #
                #   Use the directory or the full name
                #   If using the directory then ensure that we have a name
                #   even if its "."
                #
                $fn = ($use_dir) ? "$use_dir" : "."
                    if ( $cmd =~ /Directory/ );

                Debug( "GenerateFiles: Generate: $fn" );

            }
            elsif ( $cmd =~ /^Prereq/ )
            {
                #
                #   Prerequisite filename
                #       Resolve the full name of the file. It may be known
                #       as a source file (possibly generated) or it may be
                #       located in a known source directory
                #
                $fn = LocatePreReq ($ufn);
                UniquePush (\@preq_files, $fn);

                Debug( "GenerateFiles: Prereq: $fn" );

            }
            elsif ( $cmd =~ /^PackageBase/ )
            {
                $fn = GetPackageBase( "GenerateFiles", $raw_arg );
                UniquePush (\@preq_files, $fn);
            }
            elsif ( $cmd =~ /^PackageInfo/ )
            {
                $fn = GetPackageInfo( "GenerateFiles", $raw_arg );
            }
            elsif ( $cmd =~ /^Var/ )
            {
                # --Var(...)
                ($fnp, $fn, $is_path) = ExpandGenVar( "GenerateFiles", $raw_arg, @var_opts );
                $flags = '';
                if ( $raw_arg eq 'ObjDir' ) {
                    UniquePush (\@preq_files, '$(GBE_OBJDIR)');
                } elsif ( $raw_arg eq 'BinDir' ) {
                    UniquePush (\@preq_files, '$(GBE_BINDIR)');
                } elsif ( $raw_arg eq 'LibDir' ) {
                    UniquePush (\@preq_files, '$(GBE_LIBDIR)');
                }
            }
            elsif ( $cmd =~ /^Tool/ ) {
                # --Tool(toolName)
                ($fn, $is_path) = ExpandTool( "GenerateFiles", $raw_arg );
            }
            else
            {
                Warning ("GenerateFiles: Unknown replacement command: $cmd");
                $fn = $ufn;
            }

            #
            #   Process path modification flags
            #
            $fn = ProcessPathName( $fn, $flags );

            #
            #   Minor kludge under windows. Ensure directores have a "\" sep
            #   Unless the user has specified a straight shell command
            #
            $fn = "\$(subst /,\$(dirsep),$fn)"
                if ( $is_path && $::ScmHost eq "WIN" && ! defined($shell_script) );

            #
            #   Prepend any $fn Prefix
            #   This will be a tag and is not subject to path processing
            #
            $fn = $fnp . $fn;
                
            #
            #   Replace the found string with the real name of the file
            #   Note: 4 argument version of substr is not always available
            #         so we must do it the hard way
            #               substr( $arg, $mb, $me - $mb, $fn);
            #
            $arg = substr( $arg, 0, $mb ) . $fn . substr( $arg, $me );

            Debug2( "GenerateFiles: subs: $all -> $fn" );
        }

        #
        #   Save the tool arguments in an array
        #
        push @tool_args, $arg;
    }

    #
    #   Sanity test. Ensure that some file have been marked as generated
    #                Warn if no prerequisites found
    #
    Warning( "GenerateFiles. --AutoGenerate option has no effect",
             "The following files are 'source' files",  @has_source ) if ( @has_source );
    Warning( "No Prerequisite files found in $tool",@tool_args) unless ( $isaUtf || $no_warn || $#preq_files >= 0 );

    #
    #   These would be nice tests - but break too much
    #   
    #    ReportError("Mixed use of --NoGenerate and generated files") if (@gen_files && $noGenerate);
    #    ReportError  ( "No generated files found in $tool",@tool_args) unless ($isaUtf || $noGenerate || $#gen_files > 0);

    #
    #   Sanity test. If a UTF then we shouldn't generate files
    #   
    if ($isaUtf ) {
        ReportError('In UTF mode generated files are not supported:', @gen_files) if @gen_files;
    }

    #   Invocation does not generate and files
    #       Need to create a dummy name for the rule
    #       Use a base name and a number
    #       
    if ($noGenerate)
    {
        my $dummy_target = 'generate_files_' . $NoGenIndex;
        UniquePush (\@gen_files, $dummy_target );
        UniquePush (\@GENERATED, $dummy_target) unless $isaUtf;
    }

    ErrorDoExit();

    #
    #   Determine the text to display while generating files
    #   Will be either user-text or the first target file (default)
    #   Suffix with RecipeTag, if provided
    #   
    my $txtSuffix = '';
    $txtSuffix = "($recipeTag)" if defined $recipeTag;
    $text = $gen_files[0] unless defined $text;
    $text .= $txtSuffix;

    #
    #   Save information
    #   Will be used to create makefile statements later
    #
    my %gen_data;

    $gen_data{'index'}      = $NoGenIndex++;
    $gen_data{'recipeTag'}  = $recipeTag if defined $recipeTag;
    $gen_data{'shell'}      = $shell_cmds;
    $gen_data{'gen'}        = \@gen_files;
    $gen_data{'preq'}       = \@preq_files;
    $gen_data{'tool'}       = $tool;
    $gen_data{'toolargs'}   = \@tool_args;
    $gen_data{'clean'}      = $clean_tag;
    $gen_data{'text'}       = $text;
    $gen_data{'preq_sus'}   = 1 if ( $preq_unknown );
    $gen_data{'predelete'}  = 1 if ( $predelete );

    if ($isaUtf)
    {
        $gen_data{'isautf'}     = 1;
        $gen_data{'utfauto'}    = $utfAuto if ( $utfAuto );
        $gen_data{'utfformat'}  = $utfFormat if ( $utfFormat );
        $gen_data{'utfdir'}     = $utfDir if ( $utfDir );
        $gen_data{'utfargs'}    = \@utfArgs;

        $TESTS_TO_RUN = 1;
        $TESTS_TO_AUTORUN = 1 if ( $utfAuto );
    }

    push(@GENERATE_FILES, \%gen_data);
#DebugDumpData("GenerateFiles", \%gen_data);
    Debug2( "GenerateFiles: cmd: $tool @tool_args" );
}

#-------------------------------------------------------------------------------
# Function        : MakePerlModule
#
# Description     : Build Perl Module(s) using the Perl Build System
#                   This is a thin wrapper around a specialised script
#
#                   The user can do the same job with correct use of
#                   a GenerateFiles, but this is a lot tidier.
#
# Inputs          : $1      - platform specifier '*' (comma delemitered)
#                   $*      - Paths to Perl Modules[,command options]
#                             Options to the BuildPerl script
#
# Returns         :
#
sub MakePerlModule
{
    my ( $platforms, @args) = @_;

    return if ( ! ActivePlatform($platforms) );

    Debug2( "MakePerlModule:($platforms, @args)" );
    my @opts;

    #
    #   Extract options from paths to Perl Packages
    #   Package names do not start with a '-'
    #
    foreach my $arg ( @args )
    {
        if ( $arg =~ /^-/ ) {
            push @opts, $arg;

        } else {
            #
            #   Perl Package Directory Name
            #   This may also contain embedded command to the Make command
            #   These will be seperated with a comma
            #       ie: module,-options=fred
            #
            my ($name,$options) = split( ',', $arg );
            push @opts, "-PerlPackage=$arg";
            push @opts, "--Prereq=$name/Makefile.PL";
        }
    }

    #
    #   Invoke GenerateFiles with a bunch of additional arguments
    #
    GenerateFiles ($platforms, "--Tool=jats_buildperl.pl",
                          '--Var(MachType)',                        # Build Machine type
                          '--Var(PackageDir)',                      # Package dir
                          '--NoGenerate',                           # Don't know the output
                          '--Text=Make Perl Module',                # Pretty print
                          '--NoWarn',
                          '--Clean=-clean_build',                   # Jats clean support
                          '--NoVarTag',                             # No more Tags
                          @opts,
                          );
}

#-------------------------------------------------------------------------------
# Function        : MakeLinuxDriver
#
# Description     : Build a Linux Device Driver using the Linux Device Driver
#                   Build System
#                   This is a thin wrapper around a specialised script
#
#                   The user can do the same job with correct use of
#                   a GenerateFiles, but this is a lot tidier.
#
# Inputs          : $1      - platform specifier '*' (comma delemitered)
#                   $2      - name of the driver. No extension
#                   $*      - Driver sources
#                             Options to the script
#
# Returns         :
#
sub MakeLinuxDriver
{
    my ( $platforms, $driver_name, @args) = @_;

    return if ( ! ActivePlatform($platforms) );

    Error ("No driver name specified") unless ( $driver_name );
    Debug2( "MakeLinuxDriver:($platforms, $driver_name ,@args)" );
    my @srcs;
    my @opts;

    #
    #   Extract options from source files
    #   Package names do not start with a '-'
    #
    foreach my $arg ( @args )
    {
         if ( $arg =~ /^--Define=(.)/ ) {
            push @opts, $arg;

         } elsif ( $arg =~ /^--ExternalModule=(.)/ ) {
               push @opts, $arg;
            
         } elsif ( $arg =~ /^-/ ) {
            push @opts, $arg;
            Warning ("MakeLinuxDriver: Unknown option: $arg. Passed to script");

        } else {
            push @srcs, $arg;
            push @opts, "--Prereq=$arg";
        }
    }

    #
    #   Cleanup the drive name
    #
    $driver_name =~ s~\.ko$~~;

    #
    #   Remove the specified sources from the list of object files
    #   that will be build. This will ensure that some internal rules are
    #   not generated.
    #
    foreach ( @srcs )
    {
        my $file = StripExt(StripDir( $_ ));
        delete $OBJSOURCE{ $file };
        @OBJS = grep(!/^$file$/, @OBJS);
    }

    #
    #   Invoke GenerateFiles with a bunch of additional arguments
    #   At runtime the include directories will be added as
    #   absolute paths
    #
    GenerateFiles ($platforms, "--Tool=jats_buildlinux.pl",
                    "-Output=--GeneratedProg($driver_name.ko)",
                    "-Driver=$driver_name",
                    "-GccPath=\$(GCC_CC)",
                    "-Arch=\$(HOST_CPU)",
                    "-LeaveTmp=\$(LEAVETMP)",
                    "-Verbose=\$(CC_PRE)",
                    "-Type=\$(GBE_TYPE)",
                    "-Platform=\$(GBE_PLATFORM)",
                    "--Var(LocalBinDir)",
                    '$(patsubst %,-Incdir=%,$(INCDIRS))',
                    '--Clean',
                    @opts,
                    @srcs
                    );
}

#-------------------------------------------------------------------------------
# Function        : GetPackageBase
#
# Description     : Helper routine
#                   Given a package name, determine the base address of the
#                   package
#
# Inputs          : $dname         - Directive name     (Reporting)
#                   $name          - Required package
#                                    Allows two forms:
#                                       package_name
#                                       package_name,ext
#
# Returns         : Path to the directory in which the files are installed
#                   This may be the interface directory
#
sub GetPackageBase
{
    my ($dname, $fname) = @_;
    my $pkg;
    my ($name, $ext) = split(',', $fname);

    $pkg = GetPackageEntry( $name, $ext );
    Error ("$dname: Package not found: $fname") unless ( $pkg );

    #
    #   If a BuildPkgArchive then use the interface directory
    #
    return ( $pkg->{'TYPE'} eq 'link' ) ? $pkg->{'ROOT'} : '$(INTERFACEDIR)';
}

#-------------------------------------------------------------------------------
# Function        : GetPackageInfo
#
# Description     : Helper routine
#                   Given a package name, return some information about the package
#                   Only one information item is allowed with each call
#
# Inputs          : $dname         - Directive name     (Reporting)
#                   $name          - Required package
#                                    Allows two forms:
#                                       package_name
#                                       package_name,ext
#                                    Selector
#                                       --path
#                                       --version
#                                       --fullversion
#                                       --project
#
# Returns         : Package information
my %GetPackageInfo = qw(path ROOT
                        version DVERSION
                        fullversion VERSION
                        project DPROJ);
sub GetPackageInfo
{
    my ($dname, $args) = @_;
    my $pkg;
    my $name;
    my $ext;
    my $info;

    #
    #   Split up the arguments
    #       Options start with '--'
    #   First non-option is the package name
    #   2nd non-option is the packag extension
    #
    #   Only one option allowed
    #       Convert it into a known package info item
    #
    #
    foreach ( split(',', $args) )
    {
        if ( m/^--(.*)/ ) {
            Error( "$dname: Too many info requests: $args") if ( $info );
            $info = $GetPackageInfo{$1};
            Error( "$dname: Unknown info type: $_") unless ($info);

        } elsif ( $ext ) {
            Error("$dname: Too many names: $args");

        } elsif ( $name ) {
            $ext = $_;

        } else {
            $name = $_;
        }
    }

    $pkg = GetPackageEntry( $name, $ext );
    Error ("$dname: Package not found: $args") unless ( $pkg );

    #
    #   If a BuildPkgArchive then use the interface directory
    #   Default data item - path to the package
    #
    $info = 'ROOT' unless ( $info );
    if ( $info eq 'ROOT' &&  $pkg->{'TYPE'} ne 'link' )
    {
        return ( '$(INTERFACEDIR)');
    }

    return ( $pkg->{$info} );
}

#-------------------------------------------------------------------------------
# Function        : GetPackageEntry
#
# Description     : Return the package class pointer given a package name
#
# Inputs          : $name          - Required package
#                   $ext           - Option package extension
#
# Returns         : Class pointer
#
sub GetPackageEntry
{
    my ($name, $ext) = @_;
    $ext = '' unless ( $ext );

    for my $entry (@{$::ScmBuildPkgRules{$ScmPlatform} })
    {
        next unless ( $entry->{'NAME'} eq $name );
        next if ( $ext && $entry->{'DPROJ'} ne $ext );
        return $entry;
    }
    return;
}

#-------------------------------------------------------------------------------
# Function        : ExpandGenVar
#
# Description     : Expand a known variable for the Generate Files option
#
# Inputs          : $dname         - Directive name     (Reporting)
#                   $arg           - Raw argument
#                                    This of the form of
#                                       Tag[,--option]+
#                                    Tags are specified in %ExpandGenVarConvert
#
#                                   Options are:
#                                       --tag
#                                       --notag
#                                       --tag=<SomeTag>
#                                       --absdrive
#                                       --abspath
#                                       --default=text
#                                       --allownone
#                                   Not all options are avalaible on all variables
#                   @opts           - Options
#                                       --notag     - Default is --notag
#
# Returns         : Tag             - Any tag component of the expansion
#                   Path/Value      - Path/Value of the component
#                   is_path         - Above is a path
#                   is_abs          - Path is absolute
#

#
#   Create a Hash to simplify the process of converting Var names
#   into makefile variables. There are two data items, separated by a comma.
#       The first is the runtime expansion value
#       The second describes the first:
#           NotPresent  - Expansion is not a path
#           '-'         - Expansion is a path and is relative to CWD
#           '+'         - Expansion is a path and is absolute
#
my %ExpandGenVarConvert = (
    'BuildName'         => '$(GBE_PBASE)',
    'BuildVersion'      => '$(BUILDVER)',
    'BuildVersionNum'   => '$(BUILDVERNUM)',

    'PackageDir'        => '$(PKGDIR),+',
    'PackagePkgDir'     => '$(PKGDIR)/pkg/pkg.$(GBE_PLATFORM),+',
    'PackageIncDir'     => '$(INCDIR_PKG),+',
    'PackageIncPlatDir' => '$(INCDIR_PKG)/$(GBE_PLATFORM),+',
    'PackageLibDir'     => '$(LIBDIR_PKG)/$(GBE_PLATFORM),+',
    'PackageBinDir'     => '$(BINDIR_PKG)/$(GBE_PLATFORM)$(GBE_TYPE),+',

    'PackageToolDir'    => '$(PKGDIR)/tools,+',
    'PackageToolBin'    => '$(PKGDIR)/tools/bin/$(GBE_HOSTMACH),+',
    'PackageToolScript' => '$(PKGDIR)/tools/scripts,+',

    'LibDir'            => '$(LIBDIR),+',
    'BinDir'            => '$(BINDIR),+',
    'ObjDir'            => '$(OBJDIR),+',

    'InterfaceDir'      => '$(INTERFACEDIR),+',
    'InterfaceIncDir'   => '$(INCDIR_INTERFACE),+',
    'InterfaceLibDir'   => '$(LIBDIR_INTERFACE)/$(GBE_PLATFORM),+',
    'InterfaceBinDir'   => '$(BINDIR_INTERFACE)/$(GBE_PLATFORM)$(GBE_TYPE),+',

    'LocalDir'          => '$(LOCALDIR),+',
    'LocalIncDir'       => '$(INCDIR_LOCAL),+',
    'LocalLibDir'       => '$(LIBDIR_LOCAL)/$(GBE_PLATFORM),+',
    'LocalBinDir'       => '$(BINDIR_LOCAL)/$(GBE_PLATFORM)$(GBE_TYPE),+',

    'Platform'          => '$(GBE_PLATFORM)',
    'Product'           => '$(GBE_PRODUCT)',
    'Target'            => '$(GBE_TARGET)',

    'Type'              => '$(GBE_TYPE)',
    'Arch'              => '$(HOST_CPU)',
    'Architecture'      => '$(HOST_CPU)',
    'MachType'          => '$(GBE_HOSTMACH)',
    'BuildRoot'         => '$(GBE_ROOT),+',


    'Verbose'           => '$(CC_PRE)',
    'LeaveTmp'          => '$(LEAVETMP)',
    'Cwd'               => '$(CURDIR),-',

    # Generated when used
    'CompilerPath'      => '$(SCM_COMPILERPATH)',
    'PkgArch'           => '$(PACKAGE_ARCH)',

    'Native'            => '0',
    'Toolset'           => '0',

    );

sub ExpandGenVar
{
    my ($dname, $args, @uopts) = @_;
    my $expansion;
    my $prefix='';
    my ($tag, @opts) = split('\s*,\s*', $args);
    my $no_prefix;
    my $default_value;
    my $allow_none;
    my $is_abs = 0;

    #
    #   Parse options lists
    #       Options provided by the caller
    #       Options embedded in the argument
    foreach ( @uopts )
    {
        if ( m/^--notag$/ ) {
            $no_prefix = 1;
        } else{
            Error ("$dname: Unknown option: $_")
        }
    }

    foreach ( @opts )
    {
        if ( m/^--default=(.+)/i ) {
            $default_value = $1;
        } elsif ( m/^--allownone$/i ) {
            $allow_none = 1;
        }
    }
    
    #
    #   Perform run-time update on the %ExpandGenVarConvert
    #   Most of it can be initialised at startup - but not all of it.
    #
    $ExpandGenVarConvert{CompilerPath} = undef unless $::ScmToolsetCompilerPath;
    $ExpandGenVarConvert{Product}      = '$(GBE_PLATFORM)' unless $ScmProduct;

    $ExpandGenVarConvert{Native}      = '1'  if isAnAlias ('NATIVE');
    $ExpandGenVarConvert{Toolset}     = '1'  if isAnAlias ('TOOLSET');


    #
    #   Look up a hash of conversions
    #   Could allow for a code ref, but not needed yet
    #
    Error ("$dname: Unknown expansion --Var($tag)")
        unless ( exists $ExpandGenVarConvert{$tag} );

    #
    #   Handle undefined expansions
    #   Only 'CompilerPath', but it can be a pain in user land
    #
    $expansion = $ExpandGenVarConvert{$tag};
    unless ( defined $expansion  )
    {
        return '','',0,0 if ( $allow_none );
        $expansion = $default_value;
        Error ("$dname: Expansion --Var($tag) not be supported by toolset: $ScmToolset")
            unless ( $expansion );
    }


    ($expansion,my $is_path) = split (',', $expansion );
    $is_abs = 1
        if ($is_path && $is_path eq '-' );

    #
    #   Process options
    #   Assume that a tag will be provided
    #
    $prefix =  $no_prefix ? '' : "-$tag=";
    foreach my $opt ( @opts )
    {
        if ( $opt =~ /^--tag=(.*)/i ) {
            $prefix = "$1=";

        } elsif ( $opt =~ m/^--tag$/i ) {
            $prefix = "-$tag=";

        } elsif ( $opt =~ m/^--notag/i ) {
            $prefix = '';

        } elsif ( $is_path && !$is_abs && $opt =~ /--abspath|--absdrive/i ) {
            $expansion = '$(CURDIR)/' . $expansion;
            $is_abs = 1;

        } elsif ( $opt =~ m/^--default=(.+)/i ) {
            # Already processed
        } elsif ( $opt =~ m/^--allownone$/i ) {
            # Already processed
        } else {
            Error ("$dname: Unsupported option($opt) for --Var(@_)");
        }
    }

    Debug ("ExpandGenVar: args $args --> $prefix$expansion");
    return $prefix , $expansion, $is_path ? 1 : 0, $is_abs;

}

#-------------------------------------------------------------------------------
# Function        : ExpandTool
#
# Description     : Locate a 'tool' and provide the complete path
#
# Inputs          : $dname         - Directive name     (Reporting)
#                   $arg           - Name of the tool to locate (no extension) with 
#                                    embedded options. Options are:
#                                       --dir
#                                       --file
#                                       --abspath
#                                       --absdrive
#
#                                    
# Returns         : Path/Value      - Path/Value of the component
#                   is_path         - Above is a path
#                   is_abs          - Path is absolute
#

sub ExpandTool
{
    my ($dname, $args) = @_;
    my ($toolName, @opts) = split('\s*,\s*', $args);
    my $is_abs = 1;
    my $is_path = 1;

    #
    #   Locate the tool in one of the dependent packages
    #
    my @extension_list; 
    push @extension_list, '.exe' if ( $::ScmHost ne "Unix" );
    my $toolFullPath =  ToolExtensionProgram( $toolName, @extension_list );
    if ($toolFullPath) {
        $toolName = $toolFullPath;
    } else {
            Warning("$dname. Tool not found: $toolName", "Searched:", ToolExtensionPaths());
    }

    #
    #   Process options
    #
    foreach my $opt ( @opts )
    {
        if ( $opt =~ m/^--dir/i ) {
            $toolName = StripFileExt($toolName);

        } elsif ( $opt =~ m/^--file/i ) {
            $toolName = StripDir($toolName);
            $is_abs = 0;
            $is_path = 0;

        } elsif ( $opt =~ m/^--abspath/i ) {
            $toolName = AbsPath($toolName);

        } elsif ( $opt =~ m/^--absdrive/i ) {
            $toolName = FullPath($toolName);

        } else {
            Error ("$dname: Unsupported option($opt) for --Tool(@_)");
        }
    }

    Debug ("ExpandTool: $args --> $toolName");
    return $toolName, $is_path ? 1 : 0, $is_abs;
}

#-------------------------------------------------------------------------------
# Function        : isAnAlias 
#
# Description     : Internal Helper
#                   Determine if this platform is an alias for ...
#
# Inputs          :  $target    - Test against this target
#
# Returns         :  True - Is an alais for $target.
#
sub isAnAlias
{
    my ($target) = @_;
    if (exists ($::BUILDINFO{$ScmPlatform}{'USERALIAS'}) )
    {
        if ( grep /^$target$/, @{$::BUILDINFO{$ScmPlatform}{'USERALIAS'}} )
        {
                return 1;    
        }

    }
    if (exists ($::BUILDINFO{$ScmPlatform}{'ALIAS'}) )
    {
        if ( $target eq $::BUILDINFO{$ScmPlatform}{'ALIAS'} )
        {
                return 1;    
        }
    }

    return 0;
}

#-------------------------------------------------------------------------------
# Function        : ProcessPathName
#
# Description     : Massage a pathname according to a set of flags
#
# Inputs          : $fn         - Patchname to massage
#                   $flags      - Flags in a string
#                                   --dir       - only the directory part ( or a "." )
#                                   --file      - only the file part
#                                   --abspath   - Absolute path
#                                   --absdrive  - Absolute path with drive letter(WIN)
#
# Returns         : Massaged pathname
#
sub ProcessPathName
{
    my ( $fn, $flags ) = @_;
    #
    #   Process flags
    #       --dir           - only the directory part ( or a "." )
    #       --file          - only the file part
    #       --abspath       - Absolute path
    #       --absdrive      - Absolute path with drive letter(WIN)
    #
    if ( $flags =~ /--dir/ )
    {
        $fn = '.'
            unless ( $fn =~ s~/[^/]*$~~);
    }

    if ( $flags =~ /--file/ )
    {
        $fn =~ s~.*/~~;
    }

    if ( $flags =~ /--abspath/ )
    {
        $fn = AbsPath( $fn );
    }
    elsif ( $flags =~ /--absdrive/ )
    {
        $fn = AbsPath( $fn );
        if ( $::ScmHost eq "WIN" )
        {
            $fn = $::CwdDrive . '/' . $fn
                unless ( $fn =~ m~^\w:/~  );
            $fn =~ s~//~/~g;
        }
    }

  return $fn;
}

#-------------------------------------------------------------------------------
# Function        : LocatePreReq
#
# Description     : Locate a file known to JATS
#                   There are many places to search
#                       1) Src files - specified with a Src directive
#                       2) Scripts - specified with a script directive
#                       3) Search - Files in the specified search path
#                       4) Programs specified with a 'Prog' directive
#
#                   Should also look in other locations (Libs, SharedLibs)
#                   Not done yet. May be issues of a name clash if a program
#                   and a library have the same name.
#
# Inputs          : Name to locate
#
# Returns         : Full pathname of file
#
sub LocatePreReq
{
    my ( $name ) = @_;
    Debug ("LocatePreReq:Looking for $name");
    #
    #   Try a Src file first
    #
    if ( exists $SRCS{ $name } )
    {
        return $SRCS{ $name };
    }

    #
    #   Try a script
    #
    if ( exists $SCRIPTS{ $name } )
    {
        return $SCRIPTS{ $name };
    }
    
    #
    #   Try a PROG
    #
    if ( my $pProg = $PROGS->Get($name) )
    {
        return $pProg->getPath();
    }

    #
    #   Try searching for the file
    #   Uses Data from AddSrcDir
    #
    #   Done: last because it generates warning messages
    #
    return MakeSrcResolve( $name );
}

#-------------------------------------------------------------------------------
# Function        : ToolExtensionPaths
#
# Description     : Return a list of toolset extension directories
#                   The data will have been discovered by the build process
#                   and will have been saved for the makefile creation phase
#
# Inputs          : None
#
# Returns         : Return an ordered unique list
#
sub ToolExtensionPaths
{
    Debug( "ToolExtensionPaths:", @::BUILDTOOLSPATH );
    return @::BUILDTOOLSPATH;
}

#-------------------------------------------------------------------------------
# Function        : ToolExtensionProgram
#
# Description     : Determine if the named program exists within the PATH
#                   that also includes the toolset extension
#
# Inputs          : program             - Name of program
#                   elist               - An array of possible program extensions
#
# Returns         : Full path the to program or an empty element (not undef)
#
sub ToolExtensionProgram
{
    my ($program, @elist ) = @_;

    #
    #   If elist is empty then insert a defined entry
    #
    push @elist, '' unless ( @elist );

    #
    #   Scan all toolset directories
    #   for the program
    #
    for my $dir ( ToolExtensionPaths() )
    {
        for my $ext ( @elist )
        {
            my $tool = "$dir/$program$ext";
            Debug( "ToolsetExtensionProgram: Look for: $tool" );

            return $tool if ( -f $tool );
        }
    }
}

sub Define
{
    Debug2( "Define(@_)" );

    push( @DEFINES, @_ );
}


sub Defines
{
    my( $path, $script ) = @_;
    my( $line );

    Debug2( "Defines($path, $script)" );

    $script = Exists( $path, $script, "Defines" );
    push( @DEFINES, "# Defines from: $script" );
    open( my $fh, '<', $script ) || Error( "Opening $script" );
    while (<$fh>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @DEFINES, $_ );
    }
    push( @ScmDepends, "$script" );             # makefile dependencies
    close( $fh );
}
#-------------------------------------------------------------------------------
# Function        : Rule
#
# Description     : Add a Rule and Recipe to the generated makefile
#                   This is not encouraged as it has been misused to create
#                   unreadable and unmaintainable makefiles.
#
#                   Rules will be added to the makefile after the rules and
#                   recipes created by JATS directives
#
# Inputs          : $platform               - Platform predicate
#                   @rule                   - Array of rules to add
#
# Returns         : 
#
sub Rule
{
    my( $platforms, @rule ) = @_;

    return if ( ! ActivePlatform($platforms) );

    push( @RULES, @rule );
    Message("Rule directive used. Consider replacing with GenerateFiles");
}

#-------------------------------------------------------------------------------
# Function        : Rules
#
# Description     : Add a file of Rules and Recipes to the generated makefile
#                   Used internally ONLY as there is no platform predicate
#                   Similar to 'Rule()'
#
# Inputs          : $path                   - path to script
#                   $script                 - File fo Rules
#
# Returns         : 
#
sub Rules
{
    my( $path, $script ) = @_;
    my( $line );

    $script = Exists( $path, $script, "Rules" );
    push( @RULES, "# Rules from: $script" );
    open( my $fh, '<', $script ) || Error( "Opening $script" );
    while (<$fh>) {
        $_ =~ s/\s*(\n|$)//;                    # kill trailing whitespace & nl
        push( @RULES, $_ );
    }
    push( @ScmDepends, "$script" );             # makefile dependencies
    close( $fh );
}

#-------------------------------------------------------------------------------
# Function        : AddRule
#
# Description     : Inernal function
#                   Add a line to the Rules area
#
# Inputs          : @elements                   - Array of lines to add
#
# Returns         : Nothing
#
sub AddRule
{
    push( @RULES, @_ );
}

#-------------------------------------------------------------------------------
# Function        : Src
#
# Description     : This directive is used to identify files to JATS
#                   Once a file has been identified as a 'Source' file, then it
#                   can be used by name, without the need to locate the file again.
#                   This implies that filenames must be unique.
#                   The directories cannot be used to make files of the same name
#                   unqiue - this is not the JATS way
#
#                   Source files will be classified as one of:
#                       c, c++, header, assembler or other
#
#
# Inputs          : $platform               - Active Platform Predicate
#                   @elements               - A list of files and options
#
#                   Valid options are:
#                       --c                 - Specifies the type of file
#                       --cpp
#                       --h, --headers
#                       --asm
#                       --FromPackage       - Search packages for the file
#                       --List=xxx          - Append file to a named list
#                       --Depends=xxx       - Manually name a dependency
#                       --IgnoreDuplicates  - Ignore duplicates (mostly internal use)
#
#                   Options are processed before file elements
#                   Thus options apply to all files in the list
#
# Returns         : Nothing
#
sub Src
{
    my( $platforms, @elements ) = @_;
    my( $type, @args, $source, $basename, $from_package, @lists, $ignoreDups );
    my( @depends, @srcs );

    $platforms = '' unless ( $platforms );
    Debug2( "Src($platforms, @elements)" );

    #
    #   Ensure that there is a file within the list
    #
    Warning( "Src directive does not specify any files: Src($platforms, @elements)" )
        unless (grep( /^[^-]/, @elements ) );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Remove spaces from both ends of the arguments.
    #   It is easier to remove spaces now than to detect them later
    #
    foreach ( @elements )
    {
        s/^\s+//;
        s/\s+$//;
        s~//~/~g;                               # Remove multiple /
    }

    #.. Collect arguments
    #
    $type = "";
    foreach ( @elements )
    {
        if ( /^--c$/ )
        {
            Debug( "Src: --c" );
            $type = ".c";
        }
        elsif ( /^--cpp$/ )
        {
            Debug( "Src: --cpp" );
            $type = ".cc";
        }
        elsif ( /^--h$/ || /^--header$/ )
        {
            Debug( "Src: --h" );
            $type = ".h";
        }
        elsif ( /^--asm$/ )
        {
            Debug( "Src: --asm" );
            $type = ".asm";
        }
        elsif ( /^--IgnoreDup/ )
        {
            $ignoreDups = 1;
        }
        elsif ( /^--FromPackage$/ )
        {
            $from_package = 1;
        }
        elsif ( /^--List=(.*)/ )
        {
            my $list_name = $1;
            Error( "Bad list name: $list_name" )
                unless ( $list_name =~ m/^[A-Za-z]\w+/ );
            push @lists, $list_name;
        }
        elsif ( /^--Depends=(.*)/ )
        {
            foreach ( split( ',', $1) )
            {
                my $full = MakeSrcResolveExtended( $from_package, $_ );
                push @depends, $full;
            }
        }
        elsif ( /^-(.*)/ )
        {
            Debug( "Src: arg $_" );
            push @args, $_;
        }
        else
        {
            push @srcs, $_;
            Warning ("Src files contains a '\\' character: $_" ) if (m~\\~);
        }
    }

    #.. Push source file(s)
    foreach ( @srcs )
    {
        if ( ! /^-(.*)/ )
        {
            $source = MakeSrcResolveExtended( $from_package, $_ );
            $basename = StripDir( $source );
            Debug( "Src: $_ -> $source=$basename (@args),(@depends)" );

            if ( $SRCS{ $basename } ) {
                Warning( "Duplicate src ignored '$source'") unless $ignoreDups;
                next;
            }
            $SRCS{ $basename } = $source;

            HashJoin( \%SRC_ARGS, $;, $basename, @args )
                if (@args);

            HashJoin( \%SRC_DEPEND, $;, $basename, @depends )
                if ( @depends );

            $SRC_TYPE{ $basename } = $type
                if ($type);
                

            foreach (@lists) {
                my $lname_short = "LIST_$_";
                my $lname_full = "LIST_FULL_$_";

                no strict 'refs';

                push @$lname_short,$basename;
                push @$lname_full ,$source;

                use strict 'refs';
            }

            __AddSourceFile( 1, $source, "", $type );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : AddToSrc 
#
# Description     : Internal function
#                   Used by plugins and tools
#                   
#                   Will test if specified file is known to JATS, before
#                   adding to the the list of known (Src) files 
#
# Inputs          : $platform
#                   $file           - Only one file
#                   @srcOpts        - Same as Src
#
# Returns         : True if any file can be found
#                   Returns full path to the file    
#
sub AddToSrc
{
    my( $platforms, $file, @args ) = @_;
    Debug2( "AddToSrc($platforms, $file, @args)" );

    #
    #   Process files
    #
    my $basename = StripDir( $file );
    unless (exists $SRCS{$file} || exists  $SRCS{$basename} ) {
        Src ('*', $file, @args);
    }

    return $SRCS{$basename};
}


###############################################################################
#  sub LibNameSplit
#      Just a little help to deal with major/minor stuff for shared libs -
#      given the name of the library as the argument, split out major and minor
#      parts and return the basename, i.e name without major and minor and
#      the pair of major and minor.
###############################################################################

sub LibNameSplit
{
    my ( @bits ) = split('\.', $_[0]);
    my ( $major, $minor );

    if ($#bits >= 1) {
        $major = $bits[0]; $minor = $bits[1];
    } elsif ($#bits >= 0) {
        $major = $bits[0]; $minor = 0;
    } else {
        $major = 1; $minor = 0;
    }
    Debug( "LibName: $@_[0] ($major.$minor)" );
    return ($major, $minor);
}

#-------------------------------------------------------------------------------
# Function        : Lib
#
# Description     : Generate a static library
#
# Inputs          : Platform specifier
#                   Name of the library
#                   Arguemnts ...
#
# Returns         :
#
sub Lib
{
    my( $platforms, $lib, @args ) = @_;
    return if ( ! ActivePlatform($platforms) );

    Error ("Lib: Library name not defined") unless ( $lib );

    #
    #   May be a shared library or a static library - for historic reasons
    #   If the user has specified a --Shared then its a shared library
    #
    return SharedLib( @_ )
        if ( grep (/^--Shared/, @args) );

    #
    #   Does this toolset support libraries
    #
    Error ("Libraries are not supported") unless ( defined $::a );

    #.. Fully qualify library path for addition to library list.
    $lib = "lib$lib"
       if ( $ScmTargetHost eq "Unix" && $lib !~ m/^lib/);
    Debug( "Lib: $lib" );

    #
    #   Create a new object to describe the library
    #   Ensure that only one such lib exists
    #   Add the library to the list of static libraries
    #
    Error( "Library of the same name already defined: $lib" )
        if ( $LIBS->Get($lib) );
    $LIBS->NewAdd($lib);

    #
    #   Process arguments
    #
    push( @LINTLIBS, $lib );
    _LibArgs( $lib, @args );
}


#-------------------------------------------------------------------------------
# Function        : SharedLib
#
# Description     : Generate a shared library
#
# Inputs          : Platform specifier
#                   Name of the library
#                   Arguemnts ...
#
# Returns         :
#
sub SharedLib
{
    my( $platforms, $lib, @args ) = @_;

    return if ( ! ActivePlatform($platforms) );

    Error ("SharedLib: Library name not defined") unless ( $lib );
    Error ("Shared Libraries are not supported") unless ( defined $::so );

#.. Fully qualify library path for addition to library list.
    $lib = "lib$lib"
       if ( $ScmTargetHost eq "Unix" && $lib !~ m/^lib/);
    Debug( "ShLib: $lib" );

    #
    #   Ensure that only one such lib exists
    #
    Error( "Library of the same name already defined: $lib" )
        if ( $SHLIBS->Get($lib) );
    $SHLIBS->NewAdd($lib);

    #
    #   If the user has not specified a --Shared parameter then provide one
    #
    push @args, "--Shared=Current"
        unless ( grep (/^--Shared/, @args) );

    #
    #   Process arguments
    #
    push( @LINTSHLIBS, $lib );
    _SharedLibArgs( $lib, @args );
}


#-------------------------------------------------------------------------------
# Function        : LibArgs
#
# Description     : Add arguments to an existing library directive
#
# Inputs          : Platform specifier
#                   Name of the library
#                   Arguemnts ...
#
# Returns         :
#
sub LibArgs
{
    my( $platforms, $lib, @args ) = @_;
    return if ( ! ActivePlatform($platforms) );

#.. Fully qualify library path for addition to library list.
    $lib = "lib$lib"
       if ( $ScmTargetHost eq "Unix" && $lib !~ m/^lib/);
    Debug( "LibArgs: $lib" );

    #
    #   Process the arguments
    #
    _LibArgs( $lib, @args );
}


#-------------------------------------------------------------------------------
# Function        : _LibArgs
#
# Description     : Process static library arguments
#                   Internal use only
#
# Inputs          : Name of the library
#                   Arguments to process
#
sub _LibArgs
{
    my( $lib, @elements) = @_;
    my $obj;

    #
    #   Ensure that only one such lib exists
    #
    my $libp = $LIBS->Get($lib);
    Error("Library name not defined: $lib")
        unless ( $libp );

    #
    #   Process each element
    #
    foreach (@elements)
    {
        if ( /^\s+/ )
        {
            Error ("Argument cannot start with a space: '$_'");
        }
        if ( /^--Shared/ )
        {
            Error( "--Shared not valid for a static library" );
        }

        if ( /^-l(.*)/ || /^--l(.*)/ || /^-L(.*)/ || /^--L(.*)/ )
        {
        #.. Target library specified - add to library list.
        #
            Warning( "$_ within non shared library specification" );
            next;
        }

        if ( /^--if(.*)/ )
        {
            Warning( "$_ within non shared library specification" );
            next;
        }

        if ( /^--(.*)/ )
        {
            Debug( "LibArgs: arg $_" );

            #.. Argument specified - add to argument list
            #
            $libp->addItem('ARGS', $_);
            
            next;
        }

        if ( %::ScmToolsetProgSource )
        {
            #
            #   Toolset provides support for some file types
            #   to be passed directly to the librarian builder
            #
            my $ext  = StripFile($_);
            if ( exists ($::ScmToolsetProgSource{$ext}) )
            {
                my $full_path = MakeSrcResolve ( $_ );
                my $flag = $::ScmToolsetProgSource{$ext};
                Debug( "LibArgs: src $_" );
                $libp->addItem('ARGS', "$flag$full_path" );
                next;
            }
        }

        if ( $::o )
        {
        #.. Object specified - add to object list.
        #
            $obj = _LibObject( "", $_ );

        #.. Add to object list.
        #   Note:   Object path must be explicit as several
        #           toolsets add additional objects.
        #
            $libp->addItem('OBJS', "\$(OBJDIR)/$obj" );
            next;
        }

        #
        #   Don't know how to handle this type of argument
        #
        Error ("LibArgs: Don't know how to handle: $_" );
    }
}


#-------------------------------------------------------------------------------
# Function        : SharedLibArgs
#
# Description     : Add arguments to an existing shared library directive
#
# Inputs          : Platform specifier
#                   Name of the library
#                   Arguemnts ...
#
# Returns         :
#
sub SharedLibArgs
{
    my( $platforms, $lib, @args ) = @_;
    return if ( ! ActivePlatform($platforms) );

#.. Fully qualify library path for addition to library list.
    $lib = "lib$lib"
       if ( $ScmTargetHost eq "Unix" && $lib !~ m/^lib/);
    Debug( "ShLibArgs: $lib" );

    _SharedLibArgs( $lib, @args );
}


#-------------------------------------------------------------------------------
# Function        : _SharedLibArgs
#
# Description     : Process shared library arguments
#                   Internal use only
#
# Inputs          : Name of the library
#                   Arguments to process
#
sub _SharedLibArgs
{
    my ( $lib, @elements) = @_;

    my $libp = $SHLIBS->Get($lib);
    Error("Library name not defined: $lib")
        unless ( $libp );

    #
    #.. Collect --Shared arguments
    #   Need to process this one first so that we have a version number
    #
    foreach (@elements)
    {
        if ( /^\s+/ )
        {
            Error ("Argument cannot start with a space: '$_'");
        }
        next unless ( /^--Shared/ );

        my $shared;
        if ( /^--Shared$/ )
        {
        #.. Shared library, default library version 1.0
        #
            $shared = "1.0";
        }
        elsif ( /^--Shared=Current$/ )
        {
        #.. Shared library, using 'current' build version
        #
            $shared = $::ScmBuildVersion;
            $shared = "1.0" if ($shared eq "");
        }
        elsif ( /^--Shared=(.*)/ )
        {
        #.. Shared library, specific version
        #
            my($M, $m) = LibNameSplit($1);
            $shared = "$M.$m";
        }

        #
        #   Update the shared Names
        #
        if ( defined $shared )
        {
            Warning( "multiple --Shared arguments" )
                if (exists $libp->{ VERSION });
            Debug( "ShLibArgs: shared $_ ($shared)" );
            $libp->{ VERSION } = $shared;
        }
        else
        {
            Error ("ShLibArgs: --Shared argument not understood");
        }
    }


#.. Parse all of the object and argument entries.
#
    foreach (@elements)
    {
        next if ( /^--Shared(.*)/ );

        if ( /^[-]{1,2}([lL])(.*)/ )
        {
        #.. Target library specified - add to library list.
        #   Support --L and -L and --l and -l
        #
            Debug( "ShLibArgs: lib  -$1$2" );
            $libp->addItem('LIBS', "-$1$2" );
            next;
        }

        if ( /^--if(.*)/ )
        {
        #.. Library conditional - add to library list.
        #
            Debug( "ShLibArgs: cond $_" );
            $libp->addItem('LIBS', $_);
            next;
        }

        if ( /^--SoName=(.*)/i )
        {
        #.. Specify the SoName of the library
        #   Not supported by all toolsets
        #
            my $soMode = $1;
            if ( !$ScmToolsetSoName )
            {
                Warning ("Toolset does not support --SoName. Option ignored");
                next;
            }

            Error ("SharedLib: $lib. Multiple --SoName arguments not allowed")
                if ( $libp->{ SONAME } );

            my ($major, $minor, $patch, $build, $raw_patch) = SplitVersion($::ScmBuildVersionFull);
            my $soname = '.';
            if ( $soMode =~ m/Major/i ) {
                $soname .= $major;
            } elsif ( $soMode =~ m/^Minor/i ) {
                $soname .= "$major.$minor";
            } elsif ( $soMode =~ m/^Patch/i ) {
                $soname .= "$major.$minor.$patch";
            } elsif ( $soMode =~ m/^Build/i ) {
                $soname .= "$major.$minor.$patch.$build";
            } elsif ( $soMode =~ m/^Full/i ) {
                $soname .= $libp->{ VERSION };
            } elsif ( $soMode =~ m/^None/i ) {
                $soname = '';
            } elsif ( $soMode =~ m/^[0-9.]+$/ ) {
                $soname .= $soMode;
            } else {
                Error ("Unknown --SoName mode: $soMode");
            }
            $libp->addItem('ARGS', '--SoNameSuffix=' . $soname);
            $libp->{ SONAME } = 1;
            next;
        }
        
        if ( /^-(.*)/ )
        {                           
        #.. Argument specified - add to argument list
        #
            Debug( "ShLibArgs: arg  $_" );
            $libp->addItem('ARGS', $_);
            next;
        }

        if ( %::ScmToolsetProgSource )
        {
            #
            #   Toolset provides support for some file types
            #   to be passed directly to the program builder
            #
            my $ext  = StripFile($_);
            if ( exists ($::ScmToolsetProgSource{$ext}) )
            {
                my $full_path = MakeSrcResolve ( $_ );
                my $flag = $::ScmToolsetProgSource{$ext};
                Debug( "ShLibArgs: src $_" );
                $libp->addItem('ARGS', "$flag$full_path");
                next;
            }
        }

        if ( $::o )
        {
        #.. Object specified - add to object list.
        #
            my ($obj) = _LibObject( $lib, $_ );

        #.. Add to object list.
        #   Note:   Object path must be explicit as several
        #           toolsets add additional objects.
        #
            $SHOBJ_LIB{ $obj } = $lib;
            $libp->addItem('OBJS', "\$(OBJDIR)/$obj");
            next;
        }

        #
        #   Don't know how to handle this type of argument
        #
        Error ("SharedLib: Don't know how to handle: $_" );
    }
}


#-------------------------------------------------------------------------------
# Function        : _LibObject
#
# Description     : Process library object file
#                   Common processing routine for static and shared library
#                   Internal use only
#
# Inputs          : shared  - Name of the shared library is shared, if defined
#                   fname   - Name of file
#
# Returns         : Name of the object file
#
sub _LibObject
{
    my ($shared, $fname) = @_;
    my ($file, $ext, $obj, $srcfile, $delete_obj);

    #.. Object specified - add to object list.
    #
    #   Want to handle several cases
    #       Normal - User has provided the name of an object file (without the obj suffix)
    #       Other  - User has provided the name of a source file
    #                Need to perform implicit source file processing
    #
    #   The hard part is detecting the difference
    #   Just can't use the existence of a '.' 
    #
    if ($OBJSOURCE{$fname}) {
        $file = $fname;                             # Already know about this file
        $ext = '';                                  # Don't need to split it
    } else {
        $file = StripDirExt($fname);                # file name, without extension or Dir
        $ext  = StripFile($fname);                  # extension
    }

    if ($shared) {
        $obj = "$shared/$file";                 # library specific subdir
    } else {
        $obj = "$file";
    }

    Debug( "LibObjs: obj [$shared]$fname ($file$ext)" );

    #.. Unqualified object name
    #
    if ( $ext eq '' ) {
        #
        #   Object file not covered by a "Src" statement
        #   Assume that it will be created
        #
        unless ( $srcfile = $OBJSOURCE{$file} )
        {
            #
            #   If the object is "generated" then it will be in the
            #   SRCS list
            #
            unless ( $srcfile = $SRCS{"$file.$::o"} )
            {
                Warning( "No source for object '$fname' ($file)" );
            }
        }
        $delete_obj = 1;
    }

    #.. Qualified object name (ie has extension)
    #       Strip extension and resolve ...
    #       Assume that the named file can be built into an object file
    #
    else
    {
        #.. Resolve
        #
        if ( !($srcfile = $OBJSOURCE{ "$file" }) )
        {
            $srcfile = MakeSrcResolve( $fname );
            $SRCS{ $fname } = $srcfile;
            __AddSourceFile( 0, $fname, $obj );
            $delete_obj = 1;
        }
    }

    #.. Delete generated object file
    #   Ensure that the object file is added to the delete list
    #   Add it to the ToolsetObj deletion list as the main OBJ deleltion
    #   list will aready have been processed
    #
    ToolsetObj( "\$(OBJDIR)/$obj" )
        if ( $delete_obj );


    #.. Shared library objects,
    #       Must explicitly relate source and object, as shared libraries
    #       objects are built within a library specific subdirs.
    #
    $OBJSOURCE{ $obj } = $srcfile
        if ( $shared && defined $srcfile );

    return $obj;
}


# MergeLibrary
#   Merge a list of libraries into one library
#
sub MergeLibrary
{
    my( $platforms, $lib, @elements ) = @_;

    return if ( ! ActivePlatform($platforms) );


#.. Fully qualify library path for addition to library list.
    $lib = "lib$lib"
       if ( $ScmTargetHost eq "Unix" && $lib !~ m/^lib/);
    Debug( "MergeLibrary: $lib" );

    #
    #   Create a new object to describe the library
    #   Ensure that only one such lib exists
    #   Add the library to the list of static libraries
    #
    Error( "Merged Library of the same name already defined: $lib" )
        if ( $MLIBS->Get($lib) );
    my $libp = $MLIBS->NewAdd($lib);

#.. Parse all of the object and argument entries.
#
    foreach (@elements)
    {
        if ( /^--(.*)/ )
        {
            $libp->addItem('ARGS', $_);
        }
        else
        {
            my ($llib);

            #
            #   Collect the source libraries
            #   These must have been installed and will be in a known area
            #   Create full names for the libaries
            #
            if ( $ScmTargetHost eq "Unix" ) {
                $llib = "lib$_";                # Prefix "lib" ....
                $lib =~ s/^liblib/lib/;         # @LIBS already has lib added
            } else {
                $llib = $_;
            }

            Debug( "MergeLibrary: merge $llib" );
            $libp->addItem('LIBS', $llib);
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : Script
#
# Description     : Locate a script for test purposes
#
# Inputs          : $platforms      - Platform selector
#                   $script         - A single script name
#                   $execute        - Flag to indicate that the script is to
#                                     marked as executable when used in a TestProg
#                                     This flag is NOT used as the script will
#                                     be forced executable
#
# Returns         : Nothing
#
sub Script
{
    my( $platforms, $script, $execute ) = @_;

    Debug2( "Script(@_)" );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Locate the script as a source file
    #
    my $file = MakeSrcResolve ( $script );
    $script = StripDir( $file );
    $SCRIPTS{ $script } = $file;
}

#-------------------------------------------------------------------------------
# Function        : RunTest
#
# Description     : Define a test to be run with the 'run_tests' and 'run_unit_tests'
#
# Inputs          : $platform       - Enabled for these platforms
#                   $prog           - Program to run
#                                     This SHOULD return a non-zero exit status
#                                     on error. The program may be a 'TestProg'
#                                     or a 'Script'.
#                   @elements       - Options and test arguments
#                                     Options are:
#                                       --Auto              - Non interactive unit test
#                                       --Unit              - Same and --Auto
#                                       --UtfFormat=nnn     - Specifies Automated Unit Test, 
#                                                             results post processed with formatter
#                                       --UtfArg=nnn        - Argument passed into the UTF formatter    
#                                       --Name=nnn          - Test Name.
#                                       --CopyIn=file       - A file to be copied into the test directory.
#                                       --MaxTime=fff.ff[smhd] - Max Test Time. Default 30m
#
#                                     Non Options are passed to the test program.
#                                     --PackageBase(xxx)    - Base of package
#                                     --PackageInfo(xxx)    - Package information
#                                     --File(xxx)           - Resolved name of file
#                                     --Var(xxx)            - Expanded variable
#                                     --Local(xxx)          - File within the local directory
#
#                                     Toolset Framework support (ie NUNIT in csharp.pl)
#                                       --FrameWork=name    - Name of framework
#                                       --xxxx              - Args passed to framework constructor
#
# Returns         : Nothing
#
my %RunTestNames;                       # Unique Name Tests
sub RunTest
{
    my( $platforms, $prog, @elements ) = @_;
    my $command = './';                 # program prefix / command
    my $winprog = 1;                    # 1: Convert / -> \ (WIN32 only)
    my $framework;
    my @framework_opts;
    my @copy = ();
    my $auto;
    my $utfFormat;
    my @utfArgs;
    my $utfName;
    my $maxTime;

    return if ( ! ActivePlatform($platforms) );

    #
    #   Scan @elements and extract useful information
    #   Need to process twice as some args will modify the
    #   processing done later
    #
    my @args;
    foreach ( @elements )
    {
        if ( m/^--FrameWork=(.+)/ ) {
            $framework = $1;

        } elsif ( m/^--Auto/ || m/^--Unit/) {
            $auto = 1;

        } elsif ( m/^--Name=(.*)/) {
            $utfName = $1;

            Error("Duplicate Test Name: $utfName")
                if (exists $RunTestNames{$utfName} );
            $RunTestNames{$utfName} = 1;

        } elsif ( m/^--UtfFormat=(.*)/) {
            $utfFormat = $1;

        } elsif ( m/^--UtfArg=(.*)/) {
            push @utfArgs, $1;

        } elsif ( m/^--MaxTime=(.*)/) {
            $maxTime = $1;
            unless ($maxTime =~ m~^[0-9]*\.?[0-9]+[smhd]?$~) {
                Error("MaxTime invalid: $maxTime");
            }

        } elsif ( m/^--CopyIn=(.*)/ ) {
            push @copy, MakeSrcResolve ( $1 );

        } elsif ( $framework && m/^--\w+=(.+)/ ) {
            push @framework_opts, $_;

        } else {
            push @args, $_;
        }
    }
    @elements = @args;
    @args = ();

    #
    #   Determine the source of the test prog
    #   If using a plug-in framework, then we don't know
    #   If not, then may be a script or a TESTPROGS
    #

    unless ( $framework )
    {
        if ( $TESTPROGS->Get($prog) || $PROGS->Get($prog)  ) {
            #
            #   Append a suitable EXE suffix
            #
            $prog = GenProgName( $prog );

        } elsif ( exists $SCRIPTS{$prog} ) {
            #
            #   Script names are raw
            #   Perl script are invoked directly
            #
            $command = "\$(GBE_PERL) -w "
                if ( $prog =~ /\.pl$/ );

            #
            #   Pass / to shells
            #
            $winprog = 0
                unless ( $prog =~ m~\.bat$~ )

        } else {
            Warning("RunTest program not known: $prog",
                  "It is not a TestProg, Prog or a Script",
                  "The test may fail" );
        }
    }

    #
    #   Extract and process options
    #
    my @uargs = ();
    my @preq_files;

    foreach my $arg (@elements) {
        #
        #   Process the tool arguments and extract file information
        #   Extract all fields of the form:
        #           --xxxxx(yyyyyy[,zzzzz])
        #           --xxxxx{yyyyyyy}
        #           --xxxxx[yyyyyyy] to allow embedded brackets
        #
        while ( $arg =~ m/--(\w+)               # --CommandWord         $1
                                (               # Just for grouping
                                \((.*?)\)   |   # Stuff like (yyyyy)    $3
                                {(.*?)}     |   # or    like {yyyyy}    $4
                                \[(.*?)\]       # or    like [yyyyy]    $5
                                )/x )           # Allow comments and whitespace
        {
            my $cmd = $1;                       # The command
            my $ufn = $3 || $4 || $5;           # User filename + options
            my $mb = $-[0];                     # Match begin offset
            my $me = $+[0];                     # Match end
            my $flags = '';                     # Optional flags ( --dir or --file )
            my $raw_arg = $ufn;                 # Raw arguments
            my $all = substr( $arg, $mb, $me - $mb ); # All of match. Avoid use of $&
            my $is_abs;
            my $is_path = 1;

            Error ("RunTest. Empty element not allowed: $all")
                unless ( defined($ufn) );

            $ufn =~ s/\s+$//;
            $ufn =~ s~//~/~g;                   # Remove multiple /
            if ( $ufn =~ m/(.*?),(.*)/ )        # Extract out any flags
            {
                $ufn = $1;
                $flags = $2;
            }

            my $fn = $ufn ;                     # Replacement filename
            my $fnp = '';                       # Prefix to $fn
            Error ("RunTest. Empty element not allowed: $all" )
                if ( length ($ufn) <= 0 );

            #
            #   Process found user command
            #
            if ( $cmd =~ /^File/ )
            {
                #
                #   Prerequisite filename
                #       Resolve the full name of the file. It may be known
                #       as a source file (possibly generated) or it may be
                #       located in a known source directory
                #
                $fn = MakeSrcResolve ( $ufn );
                UniquePush (\@preq_files, $fn);

                Debug( "RunTest: Prereq: $fn" );

            }
            elsif ( $cmd =~ /^PackageBase/ )
            {
                $fn = GetPackageBase( "RunTest", $raw_arg );
                UniquePush (\@preq_files, $fn);
            }
            elsif ( $cmd =~ /^PackageInfo/ )
            {
                $fn = GetPackageInfo( "RunTest", $raw_arg );
            }
            elsif ( $cmd =~ /^Var/ )
            {
                ($fnp, $fn, $is_path, $is_abs) = ExpandGenVar( "RunTest", $raw_arg );
                $flags = '';
            }
            elsif ( $cmd =~ /^Tool/ )
            {
                ($fn, $is_path, $is_abs) = ExpandTool( "RunTest", $raw_arg );
                $flags = '';
            }
            elsif ( $cmd =~ /^Local/ )
            {
                $fn = '$(LOCALDIR)/' . $ufn ;
                UniquePush (\@preq_files, $fn);
            }
            elsif ( $cmd =~ /^Dir/ )
            {
                # Item is a directory.
                # Must be massaged so that it will be correct within the context
                # Modified path is simply added to the command line
                # 
                $fn = $ufn;
                unless (-d $fn) {
                    if (-f $fn) {
                        Warning ("Not a directory. Its a file: $arg") ;
                    } else {
                        Warning ("Directory not found: $arg");
                    }
                }
            }
            else
            {
                Warning ("RunTest: Unknown replacement command: $cmd");
                $fn = $ufn;
            }

            #
            #   Process path modification flags
            #       --dir           - only the directory part ( or a "." )
            #       --file          - only the file part
            #       --abspath       - Absolute path
            #       --absdrive      - Absolute path with drive letter(WIN)
            #
            $fn = ProcessPathName( $fn, $flags );

            #
            #   The program is going to be executed within a subdirectory
            #   so add one more level of indirection to the path, but only if
            #   the path is relative
            #
            if ( $is_path && ! $is_abs )
            {
                unless ( $fn =~ m~^/|^\w:/~  )
                {
                    $fn = '../' . $fn
                        unless( $fn =~ s~=~=../~ );
                    $fn =~ s~/.$~~;
                }
            }

            #
            #   Minor kludge under windows. Ensure directores have a "\" sep
            #   Unless the user has specified a straight shell command
            #
            $fn = "\$(subst /,\$(dirsep),$fn)"
                if ( $::ScmHost eq "WIN" && $winprog );

            #
            #   Prepend any $fn Prefix
            #   This will be a tag and is not subject to path processing
            #
            $fn = $fnp . $fn;

            #
            #   Replace the found string with the real name of the file
            #   Note: 4 argument version of substr is not always available
            #         so we must do it the hard way
            #               substr( $arg, $mb, $me - $mb, $fn);
            #
            $arg = substr( $arg, 0, $mb ) . $fn . substr( $arg, $me );

            Debug2( "RunTest: subs: $all -> $fn" );
        }
        push(@uargs, "'$arg'");
    }

    #
    #   Create the test entry
    #   This is a structure that will be placed in an array
    #   The array preserves order and uniqness
    #
    my %test_entry;
    $test_entry{'framework'}= $framework if ( $framework );
    $test_entry{'framework_opts'}= \@framework_opts if ( $framework );
    $test_entry{'command'}  = $command . $prog unless ( $framework);

    $test_entry{'prog'}     = $prog;
    $test_entry{'copyprog'} = 1;
    $test_entry{'args'}     = \@uargs;
    $test_entry{'auto'}     = $auto if ( $auto );
    $test_entry{'utfformat'}= $utfFormat if ( $utfFormat );
    $test_entry{'utfargs'}  = \@utfArgs;
    $test_entry{'utfname'}  = $utfName;
    $test_entry{'maxtime'}  = $maxTime if ($maxTime);
    $test_entry{'copyin'}   = \@copy;
    $test_entry{'copyonce'} = ();
    $test_entry{'preq'}     = \@preq_files;
    $test_entry{'testdir'}  = 'BINDIR';

    push ( @TESTS_TO_RUN, \%test_entry );

    #
    #   Flag Auto Run processing required
    #
    $TESTS_TO_RUN = 1;
    $TESTS_TO_AUTORUN = 1 if ( $auto );
}


sub TestProg
{
    my( $platforms, $prog, @elements ) = @_;

    Debug2( "TestProg($platforms, $prog, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    Error ("TestProg: Program name not defined") unless ( $prog );
    Error ("Programs are not supported") unless ( defined $::exe );
    
    #
    #   Create a new Prog object, or retrieve any existing one
    #
    my $pProg = $TESTPROGS->Get($prog);
    $pProg = $TESTPROGS->NewAdd($prog)
        unless ( $pProg );

#.. Parse all of the object, library and argument entries
    Debug( "TestProg: $prog" );
    foreach (@elements)
    {
        if ( /^[-]{1,2}([lL])(.*)/ )
        {
        #.. Target Library specified - add to library list.
        #  
            Debug( "TestProg: lib  -$1$2" );
            $pProg->addItem('LIBS', "-$1$2");
            next;
        }

        if ( /^--if(.*)/ )
        {
        #.. Library conditional - add to library list.
        #
            Debug( "TestProg: cond $_" );
            $pProg->addItem('LIBS', $_);
            next;
        }

        if ( /^-(.*)/ )
        {
        #.. Argument specified - add to argument list
        #
            Debug( "TestProg: arg $_" );
            $pProg->addItem('ARGS', $_);
            next;
        }

        if ( %::ScmToolsetProgSource )
        {
            #
            #   Toolset provides support for some file types
            #   to be passed directly to the program builder
            #
            my $ext  = StripFile($_);
            if ( exists ($::ScmToolsetProgSource{$ext}) )
            {
                my $full_path = MakeSrcResolve ( $_ );
                my $flag = $::ScmToolsetProgSource{$ext};
                Debug( "TestProg: src $_" );
                $pProg->addItem('ARGS', "$flag$full_path");
                next;
            }
        }

        if ( $::o )
        {
        #.. Object specified - add to object list.
        #
            my $obj = _LibObject( "", $_ );

        #.. Add to program object list.
            $pProg->addItem('OBJS', "\$(OBJDIR)/$obj");
            next;
        }

        #
        #   Don't know how to handle this type of argument
        #
        Error ("TestProg: Don't know how to handle: $_" );
    }
}


sub Prog
{
    my( $platforms, $prog, @elements ) = @_;

    Debug2( "Prog($platforms, $prog, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    Error ("Prog: Program name not defined") unless ( $prog );
    Error ("Programs are not supported") unless ( defined $::exe );
    
    #
    #   Create a new Prog object, or retrieve any existing one
    #
    my $pProg = $PROGS->Get($prog);
    $pProg = $PROGS->NewAdd($prog)
        unless ( $pProg );

#.. Parse all of the object, library and argument entries
    Debug( "Prog: $prog" );
    foreach (@elements)
    {
        if ( /^[-]{1,2}([lL])(.*)/ )
        {
        #.. Target Library specified - add to library list.
        #  
            Debug( "Prog: lib  -$1$2" );
            $pProg->addItem('LIBS', "-$1$2");
            next;
        }

        if ( /^--if(.*)/ )
        {
        #.. Library conditional - add to library list.
        #
            Debug( "Prog: cond $_" );
            $pProg->addItem('LIBS', $_);
            next;
        }

        if ( /^-(.*)/ )
        {
        #.. Argument specified - add to argument list
        #
            Debug( "Prog: arg $_" );
            $pProg->addItem('ARGS', $_);
            next;
        }

        if ( %::ScmToolsetProgSource )
        {
            #
            #   Toolset provides support for some file types
            #   to be passed directly to the program builder
            #
            my $ext  = StripFile($_);
            if ( exists ($::ScmToolsetProgSource{$ext}) )
            {
                my $full_path = MakeSrcResolve ( $_ );
                my $flag = $::ScmToolsetProgSource{$ext};
                Debug( "Prog: src $_" );
                $pProg->addItem('ARGS', "$flag$full_path");
                next;
            }
        }

        if ( $::o )
        {
        #.. Object specified - add to object list.
        #
            my $obj = _LibObject( "", $_ );

        #.. Add to program object list.
            $pProg->addItem('OBJS', "\$(OBJDIR)/$obj");
            next;
        }

        #
        #   Don't know how to handle this type of argument
        #
        Error ("Prog: Don't know how to handle: $_" );
    }
}

#-------------------------------------------------------------------------------
# Function        : ProgAddExtra
#
# Description     : This (internal) function allows a toolset to list additional
#                   binaries as a part of a program. This will ensure that the
#                   binaries are generated in the 'make_prog' phase with the main
#                   program.
#
#                   The files are not listed for packaging, by this function
#
#                   The function does not ensure that the files are not already
#                   listed as a @PROG ( as @PROGS is not fully resolved at this point )
#
# Inputs          :     $name               - Tag name of program being built
#                                             Not used (yet)
#                       $prog               - Fully resolved path to a file
#
# Returns         : Nothing
#
sub ProgAddExtra
{
    my ($name, $prog) = @_;
    Debug2( "ProgAddExtra($name: $prog)" );

    UniquePush(\@PROGS_EXTRA, $prog);
}

our %PROJECTS;                          # Project information
my  @PROJECTS_ORDER;
#-------------------------------------------------------------------------------
# Function        : MakeProjectName 
#
# Description     : Create a uniq project name
#
# Inputs          : srcPath 
#
# Returns         : A unique project name 
#
sub MakeProjectName
{
    my ($srcPath) = @_;
    my $suffix = "";
    my $index = 1;

    my $proj = StripDir( $srcPath );
    while (exists $PROJECTS{$proj . $suffix})
    {
        $suffix = '.' . $index++;
    }
    return $proj . $suffix; 
}

#-------------------------------------------------------------------------------
# Function        : MakeProject
#
# Description     : A nasty directive that is intended to build a Microsoft
#                   project for WINCE, WIN32 and .NET builds.
#
#                   There are many constraints:
#                       Cannot be mixed with multi-platform builds
#                       Some parameters are tool specific
#
#                   Allow programs to be Installed as well as Packaged
#                   The 'Progect' is treated' as a program and it doesn't work
#                   to well if we Install libraries.
#
#                   Only Reason to Install Programs is to allow the Cab Maker
#                   to locate them.
#
# Inputs          : Platform        - Active platform
#                   Project         - Project Name with extension
#                   Options         - Many options
#
# Returns         :
#
sub MakeProject
{
    my( $platforms, $proj, @elements ) = @_;

    Debug2( "MakeProject($platforms, $proj, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Sanity test
    #
    Error ("MakeProject: Project name not defined") unless ( $proj );

    #
    #   Take the project name and convert it into a full path
    #   Need to create a uniq project name - allowing for multiple uses
    #
    my $project = MakeSrcResolve ( $proj );
    $proj = MakeProjectName($project);

    Error ("Project File Not found: $project") unless ( -f $project );

    my $basedir = StripFileExt( $project );

    #
    #   Collect user arguments
    #   They are all processed within the toolset
    #
    my @tool_options;
    my $unit_tests;
    my $auto_tests;
    foreach ( @elements )
    {
        if ( m/^--Debug/ ) {
            $PROJECTS{$proj}{'Debug'} = 1;

        } elsif ( m/^--Prod/ ) {
            $PROJECTS{$proj}{'Prod'} = 1;

        } elsif ( m/^--(Package|Install)ProgDebug=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir,'Prog', 'D', $2 );

        } elsif ( m/^--(Package|Install)Prog(Prod)*=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Prog', 'P', $3 );

        } elsif ( m/^--(Package)LibDebug=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Lib', 'D', $2 );

        } elsif ( m/^--(Package)Lib(Prod)*=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Lib', 'P', $3 );

        } elsif ( m/^--(Package)SharedLibDebug=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Lib', 'D', $2 );

        } elsif ( m/^--(Package)SharedLib(Prod)*=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Lib', 'P', $3 );

        } elsif ( m/^--(Package)Hdr=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Hdr', undef, $2 );

        } elsif ( m/^--(Package)File=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'File', undef, $2 );

        } elsif ( m/^--(Package)Tool(Prod)*=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Tool', 'P', $3 );

        } elsif ( m/^--(Package)ToolDebug=(.*)/ ) {
            _PackageFromProject( $1, $proj, $basedir, 'Tool', 'D', $2 );

        } elsif ( m/^--(Package|Install)/ ) {
            Error("MakeProject. Unknown $1 option: $_");

        } elsif ( m/^--UnitTest/ ) {
            $unit_tests = 1;

        } elsif ( m/^--AutoTest/ ) {
            $auto_tests = 1;

        } else {
            push @tool_options, $_;
        }
    }

    #
    #   Validate some of the arguments
    #   Ensure has not specified both --Prod and --Debug
    #
    Error ("Makeproject. Conflicting options --Debug and --Prod" )
        if ( $PROJECTS{$proj}{'Debug'}  && $PROJECTS{$proj}{'Prod'} );

    #   Ensure that global --OnlyProd/Debug don't prevent builds
    $PROJECTS{$proj}{'Debug'} = 1 if ($ScmBuildType eq 'D');
    $PROJECTS{$proj}{'Prod'} = 1 if ($ScmBuildType eq 'P');
    Error ("Makeproject. Global and Local options --Debug and --Prod prevent project being built" )
        if ( $PROJECTS{$proj}{'Debug'}  && $PROJECTS{$proj}{'Prod'} );

    #
    #   Save the information
    #
    $PROJECTS{$proj}{'options'} = \@tool_options;
    $PROJECTS{$proj}{'name'} = $proj;
    $PROJECTS{$proj}{'project'} = $project;
    $PROJECTS{$proj}{'basedir'} = $basedir;
    $PROJECTS{$proj}{'unittest'} = $unit_tests if ( $unit_tests );
    $PROJECTS{$proj}{'autotest'} = $auto_tests if ( $auto_tests );
    UniquePush (\@PROJECTS_ORDER, $proj);

}

#-------------------------------------------------------------------------------
# Function        : _PackageFromProject
#
# Description     : Save Packaged data from the project
#
# Inputs          : $tgt        - Install or Package
#                   $proj       - Name of the project
#                   $base       - Base directory of files
#                   $etype      - Type of Package (Progs, Libs, ... )
#                   $type       - Debug or Production or both
#                   $items      - Item to add. It may be comma seperated
#
my %PackageToData = ( 'Package' =>
                        { 'Hdr'   => \%PACKAGE_HDRS,
                          'Lib'   => \%PACKAGE_LIBS,
                          'Prog'  => \%PACKAGE_PROGS,
                          'File'  => \%PACKAGE_FILES,
                          'Tool'  => \%PACKAGE_FILES,
                          '_BASE' => 'PBase',
                        },
                      'Install' =>
                        { 'Hdr'   => \%INSTALL_HDRS,
                          'Lib'   => \%INSTALL_LIBS,
                          'Prog'  => \%INSTALL_PROGS,
                          'File'  => undef,
                          'Tool'  => undef,
                          '_BASE' => 'IBase',
                        },
                    );

sub _PackageFromProject
{
    my( $tgt, $proj, $base, $etype, $type, $items ) = @_;
    my $subdir = '';

    #
    #   Sanity test
    #
    $type = '' unless ( $type );
    Error ("INTERNAL. Bad packaging option: $tgt")   unless ( exists $PackageToData{$tgt} );
    Error ("INTERNAL. Bad packaging option: $etype") unless ( exists $PackageToData{$tgt}{$etype} );
    Error ("Unsupported packaging combination: $tgt$etype$type=$items") unless ( defined $PackageToData{$tgt}{$etype} );

    #
    #   Determine the index into the 'PackageInfo' structure
    #   This provides the symbolic name for the target package path
    #   for Package or Install
    #
    #   The key '_BASE' is internal. Used only to provide this information
    #
    my $tbase = $PackageToData{$tgt}{'_BASE'};

    #
    #   Process options
    #
    foreach my $item ( split (/,/, $items ) )
    {
        next unless ( $item =~ m/^--/ );
        if ( $item =~ m/^--Subdir=(.*)/ )
        {
            $subdir = '/' . $1;
            $subdir =~ s~//~/~g;
            $subdir =~ s~/$~~g;
        }
        else
        {
            Warning( "MakeProject: Unknown packaging option ignored: $_" );
        }
    }

    #
    #   Process files
    #
    foreach my $item ( split (/,/, $items ) )
    {
        next if ( $item =~ m/^--/ );

        my $tdir = $PackageInfo{$etype}{$tbase} . $PackageInfo{$etype}{'Dir'} . $subdir ;
        my $fname = StripDir( $item );
        my $target = $tdir . '/' . $fname;

        $item = "$base/$item" if ( $base );

        #
        #   Do not use $(GBE_TYPE) in the target name
        #   The existing package mechanism does not handle different
        #   production and debug file naming mechanism, whereas the project
        #   must. Convert $(GBE_TYPE) into P or D to ensure uniquness
        #
        $target =~ s~\$\(GBE_TYPE\)~$type~ if ($type);

        #
        #   Create a PACKAGE entry suitable for processing by the normal packaging
        #   routines. This is complicated because the Projects do not adhere to
        #   the JATS file name conventions
        #
        my %package_entry;
        $package_entry{'src'}   = $item;
        $package_entry{'dir'}   = $tdir;
        $package_entry{'set'}   = 'ALL' if ($tgt eq 'Package');
        $package_entry{'type'}  = $type if ($type);

        $PackageToData{$tgt}{$etype}->{$target} = {%package_entry};
    }
}

#-------------------------------------------------------------------------------
# Function        : MakeAnt
#
# Description     : A nasty directive to create JAR files via ANT
#                   There are several limitations
#                   This is closely related to the MakeProject directive
#
#
# Inputs          : Platform            - Active platform
#                   buildfile           - Name of the build.xml file
#                   Options             - A few options
#                                         --Jar=file
#                                               Generated JAR file(s)
#                                         --GeneratedFile=file
#                                               Other generated files
#                                               Used to flag JNI that must
#                                               Occur early
#                                          --AutoTest=<name>
#                                               Supports unitAutomated unit test
#                                               by calling build target <name>
#                                          --UnitTest=<name>
#                                               Supports unit test
#                                               by calling build target <name>
#                                          --PackageBase
#                                               Provides path to base of all packages
#                                          --AllPackages
#                                               Provide paths to both LinkPkgArchive and BuildPkgArchive
#
# Returns         :
#
our %JAR_FILES;
sub MakeAnt
{
    my( $platforms, $proj, @elements ) = @_;

    Debug2( "MakeAnt($platforms, $proj, @elements)" );

    return if ( ! ActivePlatform($platforms) );

    #
    #   Sanity test
    #
    Error ("MakeAnt: build.xml name not defined") unless ( $proj );

    #
    #   Take the project name and convert it into a full path
    #
    my $project;
    $project = MakeSrcResolve ( $proj );
    $proj = MakeProjectName($project);
    Error ("Build File Not found: $project") unless ( -f $project );

    my $basedir = StripFileExt( $project );

    #
    #   Collect user arguments
    #   They are all processed within the toolset
    #
    my @tool_options;
    my @generated;
    my $unit_tests;
    my $auto_tests;
    my $package_base;
    my $allPackages;

    foreach ( @elements )
    {
        if ( m/^--Debug/ ) {
            $PROJECTS{$proj}{'Debug'} = 1;

        } elsif ( m/^--Prod/ ) {
            $PROJECTS{$proj}{'Prod'} = 1;

        } elsif ( m/^--Jar=(.*)/ ) {
            my $tgt = $1;
               $tgt = "$basedir/$tgt" if ( $basedir );
            my $fn = StripDir( $1 );
            $JAR_FILES{$fn} = $tgt;
            GenerateSrcFile( 0, $tgt );

        } elsif ( m/^--GeneratedFile=(.*)/ ) {
            my $tgt = $1;
            $tgt = "$basedir/$tgt" if ( $basedir );
            push @generated, $tgt;
            GenerateSrcFile( 2, $tgt );

        } elsif ( m/^--UnitTest=(.*)/ ) {
            $unit_tests = $1

        } elsif ( m/^--AutoTest=(.*)/ ) {
            $auto_tests = $1

        } elsif ( m/^--PackageBase/ ) {
            $package_base = 1;

        } elsif ( m/^--AllPackages/i ) {
            $allPackages = 1;
            
        } elsif ( m/^--/ ) {
            Error("MakeAnt. Unknown option ignored: $_");

        } else {
            push @tool_options, $_;
        }
    }

    #
    #   Extend option arguments to include the base dir of packages
    #   Create definitions of the form PACKAGE_<name>
    #
    for my $entry (getPackageList())
    {
        my $pkgType = $entry->getType();
        next if $pkgType eq 'interface'  ;
        next unless ( ( $pkgType eq 'link') || $allPackages);
        my $dir = $entry->getBase(2);
        my $name = $entry->getName();
        unless ( $package_base )
        {
            $dir .= '/jar';
            next unless ( -d $dir );
        }
        push @tool_options, "-DPACKAGE_$name=\$(call myabspath,$dir)";
    }
    #
    #   Extend options to include the base dir of the created package
    #   Allows careful use for direct packaging of artifacts
    #
    push @tool_options, '-DPACKAGEDIR=$(call myabspath,$(PKGDIR))';

    #
    #   Save the information
    #
    $PROJECTS{$proj}{'options'} = \@tool_options;
    $PROJECTS{$proj}{'generated'} = \@generated if ( @generated );
    $PROJECTS{$proj}{'name'}    = $proj;
    $PROJECTS{$proj}{'project'} = $project;
    $PROJECTS{$proj}{'basedir'} = $basedir;
    $PROJECTS{$proj}{'type'}    = 'ant';
    $PROJECTS{$proj}{'unittest'} = $unit_tests if ( $unit_tests );
    $PROJECTS{$proj}{'autotest'} = $auto_tests if ( $auto_tests );
    UniquePush (\@PROJECTS_ORDER, $proj);

    $TESTS_TO_AUTORUN = 1 if ( $auto_tests );
    $TESTS_TO_RUN     = 1 if ( $unit_tests || $auto_tests );

    #
    #   Validate some of the arguments
    #
    Error ("MakeAnt. Conflicting options --Debug and --Prod" )
        if ( $PROJECTS{$proj}{'Debug'}  && $PROJECTS{$proj}{'Prod'} );
}

###############################################################################
#
#   Installation/Packaging util functions
#
#-------------------------------------------------------------------------------
# Function        : __TargetDir
#
# Description     : Internal function to process common arguments for
#                   the PackageXxx directives
#
# Inputs          : flags           - Indicate how to handle this argument
#                   base            - Base directory for this type of package
#                   argument        - Argument to process
#                   pdir            - Reference to resultant directory
#                   ptype           - Reference to resultant type (P or D)(optional)
#
# Returns         : 0               - Agument not consumed
#                   1               - Argument consumed
#                   2               - Skip this directive
#
my $T_TYPE  = 0x0001;                           # Postfix GBE_TYPE
my $T_PKG   = 0x0002;                           # Special --Dir handling
my $T_MACH  = 0x0004;                           # Allow --Machine too
my $T_GBE   = 0x0008;                           # Allow --Gbe too
my $T_FILE  = 0x0010;                           # Suffix or prefix subdir

sub __TargetDir
{
    my( $flags, $base, $argument, $pdir, $ptype ) = @_;
    my $dir  = "";
    my $consumed = 0;

    #
    #   Generate basic parts
    #   Note Product will default to Platform
    #
    my $str_platform = '$(GBE_PLATFORM)';
    my $str_product = $ScmProduct ? '$(GBE_PRODUCT)' : '$(GBE_PLATFORM)';
    my $str_target = '$(GBE_TARGET)';
    my $str_common = '$(GBE_OS_COMMON)';

    my $str_common_avail = 0;
       $str_common_avail = 1 if ( exists( $::BUILDINFO{$ScmPlatform}{OS_COMMON} ));


    #
    #   Add requested suffix
    #
    if ($flags & $T_TYPE)
    {
        $str_platform .= '$(GBE_TYPE)';
        $str_product  .= '$(GBE_TYPE)';
        $str_target   .= '$(GBE_TYPE)';
        $str_common   .= '$(GBE_TYPE)';
    }

    #
    #   Process the argument
    #
    $_ = $argument;
    if ( /^--Debug/ ) {                         # In the Debug build only
        if ( $ptype ) {
            $$ptype = "D";
            $consumed = 1;
        }

    } elsif ( /^--Prod$/ || /^--Production$/ ) { # In the Production build only
        if ( $ptype ) {
            $$ptype = "P";
            $consumed = 1;
        }

    } elsif (/^--Prefix=(.*)/) {                # Prefix with subdir
        $dir = "$base/$1";

    } elsif (/^--Subdir=(.*)/) {                # same as 'prefix'
        $dir = "$base/$1";

    } elsif (/^--Platform$/) {                  # Platform installation
        $dir = "$base/$str_platform";

    } elsif (/^--Platform=(.*?),(.*)/) {        # prefix and suffix with platform specific subdir
        $dir = "$base/$1/$str_platform/$2";

    } elsif (/^--Platform=(.*)/) {              # prefix with platform specific subdir
        if ($flags & $T_FILE) {
            $dir = "$base/$1/$str_platform";
        } else {
            $dir = "$base/$str_platform/$1";
        }

    } elsif (/^--Product$/) {                   # Product installation
        $dir = "$base/$str_product";

    } elsif (/^--Product=(.*?),(.*)/) {         # prefix and suffix with product specific subdir
        $dir = "$base/$1/$str_product/$2";

    } elsif (/^--Product=(.*)/) {               # prefix with product specific subdir
        if ($flags & $T_FILE) {
            $dir = "$base/$1/$str_product";
        } else {
            $dir = "$base/$str_product/$1";
        }

    } elsif (/^--Target$/) {                    # Target installation
        $dir = "$base/$str_target";

    } elsif (/^--Target=(.*?),(.*)/) {          # prefix and suffix with target specific subdir
        $dir = "$base/$1/$str_target/$2";

    } elsif (/^--Target=(.*)/) {                # prefix with target specific subdir
        if ($flags & $T_FILE) {
            $dir = "$base/$1/$str_target";
        } else {
            $dir = "$base/$str_target/$1";
        }

    } elsif (/^--OsCommon/) {

        unless ( $str_common_avail ) {
            Warning("Packaging option --OsCommon not supported on this platform($ScmPlatform). Directive skipped");
            $consumed = 2;

        } elsif (/^--OsCommon$/) {                  # OS installation
            $dir = "$base/$str_common";

        } elsif (/^--OsCommon=(.*?),(.*)/) {        # prefix and suffix with target specific subdir
            $dir = "$base/$1/$str_common/$2";

        } elsif (/^--OsCommon=(.*)/) {              # prefix with target specific subdir
            if ($flags & $T_FILE) {
                $dir = "$base/$1/$str_common";
            } else {
                $dir = "$base/$str_common/$1";
            }
        }

    } elsif (/^--Derived=(.*?),(.*?),(.*)/) {   # Derived target + prefix + subdir
        $dir = "$base/$2/$1_$str_platform/$3";

    } elsif (/^--Derived=(.*?),(.*)/) {         # Derived target + subdir
        if ($flags & $T_FILE) {
            $dir = "$base/$2/$1_$str_platform";
        } else {
            $dir = "$base/$1_$str_platform/$2";
        }

    } elsif (/^--Derived=(.*)/) {               # Derived target
        $dir = "$base/$1_$str_platform";

    } elsif ($flags & $T_MACH && /^--Machine(([=])(.*))?$/) {   # Allow Machine and Machine=xxx specfic target
        #
        #   Special: Append machine type to user dir
        #            Intended to create tools/bin/win32 and tools/bin/sparc directories
        my $path = ( defined( $3) ) ? "/$3" : "";
        $dir = "$base$path/\$(GBE_HOSTMACH)";

    } elsif ($flags & $T_GBE && /^--Gbe(([=])(.*))?$/) {   # Allow Gbe and Gbe=xxx specfic target
        my $path = ( defined( $3) ) ? "/$3" : "";
        $dir = "$base/gbe$path";

    } elsif (/^--Dir=(.*)/) {                   # prefix with target specific subdir
        Error ('Packaging directive with --Dir option does not specify a directory.',
               'Possible bad use of option of the form:--Dir=$xxx',
               'Note: Use of package.pl and this construct is deprecated') unless ( $1 );
        my $udir = $1;

        #
        #   Remove leading ./
        #   Check for leading ../
        #   
        #   Remove any stupid path manipulation elements
        #   
        if ($udir =~ s~^([./]*/)~~)
        {
            Warning("Packaging directive with --Dir option contains path manipulation elements (removed)", "Option: $_");
        }

        if ($flags & $T_PKG) {
            $dir = __PkgDir( $udir );
        } else {
            $dir = $base . "/" . $udir;
        }
    }

    return ($consumed) if ($dir eq "");
    $dir =~ s~//~/~g;
    $dir =~ s~/$~~;
    $$pdir = $dir;
    return (1);
}


#   __PkgDir ---
#       Convert --Dir Package directives, removing leading subdir if
#       matching the global $Pbase value.
#
#       Required as PKGDIR has the value 'GBE_ROOT/pkg/$Pbase'.
#       Required to maintain compatability with older (package.pl) constructs
#..

sub __PkgDir
{
    my( $dir ) = @_;
    my $org = $dir;

    $dir =~ s~^\Q$::Pbase\E[/]?~~;
    Debug2( "  PkgDir: converted \"$org\" to \"$dir\"" );

    $dir = "\$(PKGDIR)/$dir";
    return $dir;
}


#   getMajorMinor ---
#       Just a little help to deal with major/minor stuff for shared libs -
#       given the name of the library as the argument, split out major and
#       minor parts and return the basename, i.e name without major and minor
#       and the pair of major and minor.
#..

sub getMajorMinor
{
    my @bits = split ('\.', $_[0]);
    my $stop;
    my $major;
    my $minor;

    if ( $#bits > 2 )
    {
        $stop = $#bits - 2;
        $major = $bits[$#bits-1];
        $minor = $bits[$#bits];
    }
    elsif ($#bits > 1)
    {
        $stop = $#bits-1;
        $major = $bits[$#bits];
        $minor=0;
    }
    else
    {
        $stop = $#bits; $major = 1; $minor = 0;
    }

    my $base = $bits[0];
    for ( my $i=1; $i <= $stop; $i++ ) {
        $base = join ('.', $base, $bits[$i]);
    }

    return ($base, $major, $minor);
}

###############################################################################
#
#   Installation
#

sub InstallHdr
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $srcfile, $full, $strip, $package );
    my( $len, $name, $basename );

    Debug2( "InstallHdr($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );
    Warning ("InstallHdr: Needs local directory specified in build.pl") unless ( $::ScmLocal );

#.. Arguments
#
    $base = $PackageInfo{'Hdr'}{'IBase'};       # Base of target
    $dir = $base . $PackageInfo{'Hdr'}{'Dir'};  # Installation path (default)
    $full = $strip = 0;

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir(0, $base, $_, \$dir);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Full/) {                        # using full (resolved) path
            $full = 1;

        } elsif (/^--Strip$/) {                 # Strip path from source files
            $strip = -1;

        } elsif (/^--Strip=(\d+)$/) {           # Strip some f the path from source files
            $strip = $1;
                                                # Package
        } elsif (/^--Package$/ || /^--Package=(.*)/) {
            $package = 1;

        } elsif (/^--(.*)/) {
            Message( "InstallHdr: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            $name = $_;
            $basename = StripDir( $name );
            if ( !($srcfile = $SRCS{ $basename }) ) {
                $srcfile = $name;
            }

            if ( $full )
            {
                my $subdir = StripFileExt($srcfile);
                $subdir = $1
                    if ( $subdir =~ m~^$ProjectBase/(.*)~ );
                $dir .= '/' . $subdir;
                $dir =~ s~//~/~g;
                $dir =~ s~/./~/~g;
                $dir =~ s~/$~~g;
                $name = $basename;
            }

            $name = StripPath($name, $strip) if ($strip);

            Debug( "InstallHdr( $dir/$name, src: $srcfile, dest: $dir)" );

            $package_entry{'src'} = $srcfile;
            $package_entry{'dir'} = StripFileExt( "$dir/$name" );
            $INSTALL_HDRS{ "$dir/$name" } = {%package_entry};
        }
    }

#.. Package
#
    PackageHdr( @_ )                            # auto package
        if ( $package );
}


sub InstallLib
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $package );
    my( $lib, $strip );
    my $org_lib;

    Debug2( "InstallLib($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );
    Warning ("InstallLib: Needs local directory specified in build.pl") unless ( $::ScmLocal );

#.. Arguments
#
    $base = $PackageInfo{'Lib'}{'IBase'};       # Base of target
    $dir = $base . $PackageInfo{'Lib'}{'Dir'};  # Installation path (default)

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir(0, $base, $_, \$dir);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Package$/ || /^--Package=(.*)/) {
            $package = 1;

        } elsif (/^--Strip$/) {                 # Strip path from source files
            $strip = -1;

        } elsif (/^--Strip=(\d+)$/) {           # Strip some f the path from source files
            $strip = $1;

        } elsif (/^--(.*)/) {
            Message( "InstallLib: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            $_ = StripPath($_, $strip) if ($strip);
            $org_lib = $_;                      # Original name

            if ( $ScmTargetHost eq "Unix" ) {
                $lib = "lib$_";                 # Prefix "lib" ....
                $lib =~ s/^liblib/lib/;         # @LIBS already has lib added
            } else {
                $lib = $_;
            }

            if (  my $libp = $SHLIBS->Get($lib) )
            {
                Debug( "InstallLib( $dir/$lib\$(GBE_TYPE).$::so, " .
                    "src: \$(LIBDIR)/$lib\$(GBE_TYPE).$::so, dest: $dir)" );

                #
                #   Create a "placekeeper" entry within $INSTALL_SHLIBS
                #   The exact format of the name of the shared library is
                #   toolset specific. Create an entry to allow the toolset
                #   to extend the packaging information when the shared library
                #   recipe is constructed.
                #
                my $ver = $libp->{ VERSION };
                my $name = "$dir/$lib.$ver.PlaceKeeper";

                $package_entry{'placekeeper'} = 1;
                $package_entry{'version'} = $ver;
                $package_entry{'lib'} = $lib;
                $package_entry{'dir'} = $dir;

                push @{$SHLIB_INS{$lib}}, $name;
                $INSTALL_SHLIBS{$name} = {%package_entry};
            }

            #
            #   Clean up the package_entry
            #   Insert common items
            #
            %package_entry = ();
            $package_entry{'lib'} = $lib;
            $package_entry{'dir'} = $dir;

            if ( my $libfile = $SRCS{$org_lib} )
            {
                #
                #   Allow the user to package a sourced file as a library
                #   But must be the un-massaged name of the file.
                #
                $package_entry{'dst'} = "$dir/$org_lib";
                $package_entry{'src'} = $libfile;
            }
            elsif ( $LIBS->Get($lib) )
            {
                #
                #   Install a library known to the makefile
                #
                my $libp = $LIBS->Get($lib);

                $package_entry{'dst'}    = $dir . '/' . $libp->getFullName();
                $package_entry{'src'}    = $libp->getPath();
            }
            elsif ( ! $SHLIBS->Get($lib) )
            {
                #
                #   Not a known shared lib
                #   Not a known static lib
                #   Not a 'sourced' file
                #   Assume the a static library has magically appeared
                #   in the standard LIB directory. May have been placed there
                #   by a 'rule'
                #
                my $libp = $LIBS->New($lib);

                $package_entry{'dst'}    = $dir . '/' . $libp->getFullName();
                $package_entry{'src'}    = $libp->getPath();
            }

            #
            #   Add entry to various lists if required
            #
            PackageLib_AddEntry ('InstallLib', \%LIB_INS, \%INSTALL_LIBS, \%package_entry )
                if ( exists $package_entry{'dst'} );
        }
    }

#.. Package
#
    PackageLib( @_ )                            # auto package
        if ( $package );
}


sub InstallJar
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $package );
    my( $jar );

    Debug2( "InstallJar($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );
    Warning ("InstallJar: Needs local directory specified in build.pl") unless ( $::ScmLocal );

#.. Arguments
#
    $base = $PackageInfo{'Jar'}{'IBase'};       # Base of target
    $dir = $base . $PackageInfo{'Jar'}{'Dir'};  # Installation path (default)

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir(0, $base, $_, \$dir);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Package$/ || /^--Package=(.*)/) {
            $package = 1;

        } elsif (/^--(.*)/) {
            Message( "InstallJar: unknown option $_ -- ignored\n" );
        }
    }


#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            $jar = $_;
            my $src;
            my $dest;

            if ( $JAR_FILES{$jar} )
            {
                $src = $JAR_FILES{$jar};
                $dest = $jar;
            }
            else
            {
                $src = "\$(CLSDIR)/$jar\$(GBE_TYPE).jar";
                $dest = "$jar\$(GBE_TYPE).jar";
            }
            

            Debug( "InstallJar( $dir/$dest, " .
                "src: $src, dest: $dir)" );

            $package_entry{'src'} = $src;
            $package_entry{'dir'} = $dir;
            $INSTALL_CLSS{ "$dir/$dest" } = {%package_entry};
                
        }
    }

#.. Package
#
    PackageJar( @_ )                            # auto package
        if ( $package );
}


sub InstallProg
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $package );
    my( $prog );

    Debug2( "InstallProg($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );
    Warning ("InstallProg: Needs local directory specified in build.pl") unless ( $::ScmLocal );

#.. Arguments
#
    $base = $PackageInfo{'Prog'}{'IBase'};       # Base of target
    $dir = $base . $PackageInfo{'Prog'}{'Dir'};  # Installation path (default)

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir($T_TYPE, $base, $_, \$dir);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Package$/ || /^--Package=(.*)/) {
            $package = 1;

        } elsif (/^--(.*)/) {
            Message( "InstallProg: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            my $ext = "";
            $prog = $_;

            #
            #   If the named target is a program then append the correct
            #   extension. Otherwise assume that the target is either a script
            #   or a some other file - and don't append an extension
            #
            $ext = $::exe
                if ( $PROGS->Get($prog) );

            #
            #   A "file" that is specified with a "Src" directive may be
            #   installed as though it were a program
            #
            my $progfile;
            $progfile = "\$(BINDIR)/$prog$ext"
                unless ( $progfile = $SRCS{$prog} );

            Debug( "InstallProg( $dir/$prog$ext, " .
                 "src: $progfile, dest: $dir)" );

            push @{$PROG_INS{$prog}}, "$dir/$prog$ext";

            $package_entry{'src'} = $progfile;
            $package_entry{'dir'} = $dir;
            $INSTALL_PROGS{ "$dir/$prog$ext" } = {%package_entry};
        }
    }

#.. Package
#
    PackageProg( @_ )                           # auto package
        if ( $package );
}

#-------------------------------------------------------------------------------
# Function        : StripPath 
#
# Description     : Internal function to strip bits from a pathname
#                   Will never strip the filename, even if asked to strip too much
#
# Inputs          : $name       - Name to process
#                   $stripCount - Strip part
#                                 <0 - strip all paths
#                                 =0  - Do nothing
#                                 >0 - Strip count
#
# Returns         : Processed name
#
sub StripPath
{
    my( $name, $stripCount) = @_;

    if ($stripCount)
    {
        $name =~ s~\\~/~g;
        $name =~ s~//~/~g;

        my @items = split('/', $name);
        if ($stripCount > 0)
        {
            my $len = scalar @items;
            my $remove = $stripCount; 
            if ($stripCount >= $len ) {
                $remove = $len - 1;
            }
            splice @items, 0, $remove;
            $name = join('/', @items);
        }
        else
        {
            $name = pop @items;
        }
    }
    return $name;
}


###############################################################################
#
#   Packaging
#
sub PackageDist
{
    my( $name, @elements ) = @_;

    Debug2( "PackageDist($name, @elements)" );

    foreach ( @elements )
    {
    #.. Distribution sets
    #
        HashJoin( \%PACKAGE_DIST, $;, $name, "$_" );

    #.. Summary of distribution sets
    #
        $PACKAGE_SETS{ $_ }{'TAG'} = 1
            if ( ! exists $PACKAGE_SETS{ $_ }{'TAG'} );
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageDir 
#                   InstallDir
#
# Description     : Directive to package an entire directory tree
#                   Will package the contents of the directory without regard as to there content
#                   
#                   Differs from PackageFile (... --DirTree ) in that the process is dynamic
#                   It will support the packaging of files that are generated
#                   
#                   NOT intended to support the JATS BIN and LIB structure
#                   It knows nothing of these types of files
#
# Inputs          : platforms   - Active platform list
#                   Options:    - Many from PackageFile
#                       --DirTree=xxx   Source Tree [Mandatory]
#                       --Subdir=yyy    Target [ Mandatory]
#
sub PackageDir { 
    return if ( !$ScmPackage );                 # Packaging enabled ?
    _PackageInstallDir('PackageDir', 'PBase', \@PACKAGE_DIRS, @_);
    }

sub InstallDir { 
    Warning ("InstallDir: Needs local directory specified in build.pl") unless ( $::ScmLocal );
    _PackageInstallDir('InstallDir', 'IBase', \@INSTALL_DIRS, @_);
    }

sub _PackageInstallDir
{
    my( $cmdName, $tbase, $dirRef, $platforms, @elements ) = @_;
    my( $base, $dir, $path, $type );
    my %data;

    Debug2( "$cmdName($platforms, @elements)" );

    return if ( ! ActivePlatform($platforms) );

#.. Arguments
#
    $base = $PackageInfo{'File'}{$tbase};           # Base of target
    $dir = $base . $PackageInfo{'File'}{'Dir'};     # Installation path (default)

    foreach ( @elements )
    {
        my $rv = __TargetDir($T_MACH|$T_GBE|$T_FILE, $base, $_, \$dir, \$type);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Executable$/) {                  # Mark the file as executable
            $data{exefile} = "X";

        } elsif (/^--PreserveSymlink/i) {        # Preserve symlink to local file
            delete $data{noPreserveSymlink};

        } elsif (/^--NoPreserveSymlink/i) {      # Preserve symlink to local file
            $data{noPreserveSymlink} = 1;

        } elsif ( /^--DirTree=(.*)/ ) {
            Error("DirTree. Multiple directories not allowed.") if ( $data{dirTree} );
            $data{dirTree} =  $1;

        } elsif ( /^--FilterOut=(.*)/ ) {
            push @{$data{exclude}}, $1;

        } elsif ( /^--FilterIn=(.*)/ ) {
            push @{$data{include}}, $1;

        } elsif ( /^--FilterOutRe=(.*)/ ) {
            push @{$data{excludeRe}}, $1;

        } elsif ( /^--FilterInRe=(.*)/ ) {
            push @{$data{includeRe}}, $1;

        } elsif ( /^--StripDir/ ) {
            $data{strip_base} = 1;

        } elsif ( m/^--Recurse/ ) {
            delete $data{noRecurse};

        } elsif ( m/^--NoRecurse/ ) {
            $data{noRecurse} = 1;

        } elsif (/^--(.*)/) {
            Message( "$cmdName: unknown option $_ -- ignored\n" );
        }
    }
    Error("DirTree. No path specified") unless ( defined($data{dirTree}) && $data{dirTree} ne "" );
    Debug2( "$cmdName. Raw DirTree: $data{dirTree}" );

    # Prevent the user from escaping from the current directory
    Error("$cmdName. Absolute paths are not allowed",
          "Directory: $data{dirTree}") if ( $data{dirTree} =~ m~^/~ || $data{dirTree} =~ m~^.\:~ );

    #
    #   Convert the relative path to one that is truely relative to the current
    #   directory. This may occur when the user uses $ProjectBase
    #
    my $abs_dir_tree = AbsPath($data{dirTree});
    $data{dirTree} = RelPath($abs_dir_tree);

    #
    #   Ensure that the user is not trying to escape the package
    #   Don't allow the user to attempt to package the entire package either
    #
    #   Calculate the relative path from $ProjectBase to the target directory
    #   It must not be above the $ProjectBase 
    #
    if ( $data{dirTree} =~ m~^\.\.~)
    {
        my $dirFromBase = RelPath($abs_dir_tree, AbsPath($ProjectBase));
        Error("$cmdName. DirTree cannot extend outside current package.",
              "Directory: $dirFromBase") if ( $dirFromBase =~ m~\.\.~ );
        Error("$cmdName. DirTree cannot package entire package.",
            "Directory: $dirFromBase") if ( $dirFromBase eq '.' );
    }

    Debug( "$cmdName( $data{dirTree}");
    $data{dir} = $dir;
    $data{type} = $type if defined $type;
    #DebugDumpData("$cmdName", \%data);
    push @{$dirRef}, \%data;
}

#-------------------------------------------------------------------------------
# Function        : PackageFile
#
# Description     : Directive to package files
#                   Not to be used to package libraries, executables, headers
#                   as this should be done by specialised directives
#
#                   Use to package other files
#                   Can package an entire tree (ugly)
#
# Inputs          : 
#
#
sub PackageFile
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $full, $path, $dist, $strip, $exefile, $type );
    my( $name, $basename, $len, $srcfile );
    my( $dir_tree, @dir_tree_exclude, @dir_tree_include, $strip_base, $strip_dots );
    my $recurse = 1;
    my $preserveSymlink = 0;

    Debug2( "PackageFile($platforms, @elements)" );

    return if ( !$ScmPackage );                 # Packaging enabled ?
    return if ( ! ActivePlatform($platforms) );

#.. Arguments
#
    $dist = "ALL";                                  # Default set (ALL)
    $base = $PackageInfo{'File'}{'PBase'};          # Base of target
    $dir = $base . $PackageInfo{'File'}{'Dir'};     # Installation path (default)
    $full = 0;
    $strip = 0;
    $strip_base = 0;
    $strip_dots = 0;
    $exefile = 0;

    foreach ( @elements )
    {
        my $rv = __TargetDir($T_PKG|$T_MACH|$T_GBE|$T_FILE, $base, $_, \$dir, \$type);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Full/) {                        # Using full (resolved) path
            $full = 1;

        } elsif (/^--Set=(.*)/) {               # Distribution set
            $dist = "$1";

        } elsif (/^--Package$/) {               # Package .. call by InstallFile
        } elsif (/^--Package=(.*)/) {
            $dist = "$1";

        } elsif (/^--Strip$/) {                 # Strip path from source files
            $strip = -1;

        } elsif (/^--Strip=(\d+)$/) {           # Strip path from source files
            $strip = $1;

        } elsif (/^--Executable$/) {            # Mark the file as executable
            $exefile = "X";

        } elsif (/^--PreserveSymlink/i) {       # Preserve symlink to local file
            $preserveSymlink = 1;

        } elsif ( /^--DirTree=(.*)/ ) {
            Error("DirTree. Multiple directories not allowed.") if ( $dir_tree );
            $dir_tree =  $1;
            Error("DirTree. No path specified") unless ( defined($dir_tree) && $dir_tree ne "" );

            # Prevent the user from escaping from the current directory
            Error("DirTree. Absolute paths are not allowed",
                  "Directory: $dir_tree") if ( $dir_tree =~ m~^/~ || $dir_tree =~ m~^.\:~ );

            #
            #   Convert the relative path to one that is truely relative to the current
            #   directory. This may occur when the user uses $ProjectBase
            #
            my $abs_dir_tree = AbsPath($dir_tree);
            $dir_tree = RelPath($abs_dir_tree);

            #
            #   Ensure that the user is not trying to escape the package
            #   Don't allow the user to attempt to package the entire package either
            #
            #   Calculate the relative path from $ProjectBase to the target directory
            #   It must not be above the $ProjectBase 
            #
            if ( $dir_tree =~ m~^\.\.~)
            {
                my $dirFromBase = RelPath($abs_dir_tree, AbsPath($ProjectBase));
                Error("DirTree cannot extend outside current package.",
                      "Directory: $dirFromBase") if ( $dirFromBase =~ m~\.\.~ );
                Error("DirTree cannot package entire package.",
                    "Directory: $dirFromBase") if ( $dirFromBase eq '.' );
            }
            
            Debug2( "PackageFile. DirTree: $dir_tree" );

            Error("DirTree. Directory not found",
                  "Directory: $dir_tree") unless  ( -d $dir_tree );

            # If packaging a parent directory then force dot_stripping of the base directory
            # strip_base will have precedence if both are active
            if ( $dir_tree =~ m~\.\.~ )
            {
                $dir_tree =~ m~(\.\./)+~;
                $strip_dots = length($1);
            }

        } elsif ( /^--FilterOut=(.*)/ ) {
            push @dir_tree_exclude, $1;

        } elsif ( /^--FilterIn=(.*)/ ) {
            push @dir_tree_include, $1;

        } elsif ( /^--StripDir/ ) {
            $strip_base = 1;

        } elsif ( m/^--Recurse/ ) {
            $recurse = 1;

        } elsif ( m/^--NoRecurse/ ) {
            $recurse = 0;

        } elsif (/^--(.*)/) {
            Message( "PackageFile: unknown option $_ -- ignored\n" );
        }
    }


    #.. DirTree expansion
    #   Note: Uses REs, not simple globs
    #         Use JatsLocateFiles to do the hard work
    if ( $dir_tree )
    {
        my $search = JatsLocateFiles->new('FullPath' );
        $search->recurse($recurse);
        $search->filter_in_re ( $_ ) foreach ( @dir_tree_include );
        $search->filter_out_re( $_ ) foreach ( @dir_tree_exclude );
        $search->filter_out_re( '/\.svn/' );
        $search->filter_out_re( '/\.git/' );
        @elements = $search->search ( $dir_tree );
        if ($strip_base){
            $strip_base = length( $dir_tree ) if ( $strip_base );
        } elsif ($strip_dots) {
            $strip_base = $strip_dots;
        }
    } else {
        $strip_base = 0;
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        $name = $_;
        my $symlink;

        #
        #   Trap special files
        #       DPACKAGE - but only if we have a DPackageLibrary directive
        #                  in the same makefile.
        #
        if ( m~^DPACKAGE$~ && $DPackageDirective ) {
            $name = 'DPACKAGE.' . $::GBE_MACHTYPE;
        }

        if ( ! /^--(.*)/ )
        {
            $basename = StripDir( $name );
            if ( !($srcfile = $SRCS{ $basename }) ) {
                $srcfile = $name;
            }

            if ( $full )
            {
                my $subdir = StripFileExt($srcfile);
                $subdir = $1
                    if ( $subdir =~ m~^$ProjectBase/(.*)~ );
                $dir .= '/' . $subdir;
                $dir =~ s~//~/~g;
                $dir =~ s~/./~/~g;
                $dir =~ s~/$~~g;
                $name = $basename;
            }
            $name = StripPath($name, $strip) if ($strip);

            if ( $strip_base )
            {
                $name = substr $name, $strip_base;
                $name =~ s~^/~~;
            }

            $dir =~ s~//~/~g;
            $dir =~ s~/$~~;

            #
            #   Preserve Symlink
            #
            if ($preserveSymlink && -l $srcfile)
            {
                $symlink = 1;
            }

            #
            #   Sanity test the source filename
            #   User may have misused an option
            #
            if ( !$dir_tree && ( ( $srcfile =~ m/=/ ) || ( $srcfile =~ m/^-/ ) || ( $srcfile =~ m~/-~ ))  )
            {
               Warning ("PackageFile: Suspect source filename: $srcfile");
            }

            Debug( "PackageFile( $dir/$name, " .
                "src: $srcfile, dest: $dir, dist: $dist, exe: $exefile )" );

            $package_entry{'src'} = $srcfile;
            $package_entry{'dir'} = StripFileExt( "$dir/$name" );
            $package_entry{'set'} = $dist;
            $package_entry{'exe'} = $exefile if $exefile;
            $package_entry{'type'} = $type if ( $type );
            $package_entry{'symlink'} = 1 if ( $symlink );

            $PACKAGE_FILES{ "$dir/$name" } = {%package_entry};
        }
    }
}

sub PackageHdr
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $full, $path, $dist, $strip );
    my( $name, $basename, $len, $srcfile );

    Debug2( "PackageHdr($platforms, @elements)" );

    return if ( !$ScmPackage );                 # Packaging enabled ?
    return if ( ! ActivePlatform($platforms) );

#.. Arguments
#
    $dist = "ALL";                                  # Default set (ALL)
    $base = $PackageInfo{'Hdr'}{'PBase'};           # Base of target
    $dir = $base . $PackageInfo{'Hdr'}{'Dir'};      # Installation path (default)
    $full = 0;
    $strip = 0;

    foreach ( @elements )
    {
        my $rv = __TargetDir($T_PKG, $base, $_, \$dir);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Full/) {                        # Using full (resolved) path
            $full = 1;

        } elsif (/^--Set=(.*)/) {               # Distribution set
            $dist = "$1";

        } elsif (/^--Package$/) {               # Package .. call by InstallHdr
        } elsif (/^--Package=(.*)/) {
            $dist = "$1";

        } elsif (/^--Strip$/) {                 # Strip path from source files
            $strip = -1;

        } elsif (/^--Strip=(\d+)$/) {           # Strip some f the path from source files
            $strip = $1;

        } elsif (/^--(.*)/) {
            Message( "PackageHdr: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            $name = $_;
            $basename = StripDir( $name );
            if ( !($srcfile = $SRCS{ $basename }) ) {
                $srcfile = $name;
            }

            if ( $full )
            {
                my $subdir = StripFileExt($srcfile);
                $subdir = $1
                    if ( $subdir =~ m~^$ProjectBase/(.*)~ );
                $dir .= '/' . $subdir;
                $dir =~ s~//~/~g;
                $dir =~ s~/./~/~g;
                $dir =~ s~/$~~g;
                $name = $basename;
            }

            $name = StripPath($name, $strip) if ($strip);

            Debug( "PackageHdr( $dir/$name, " .
                "src: $srcfile, dest: $dir, dist: $dist )" );

            $package_entry{'src'} = $srcfile;
            $package_entry{'dir'} = StripFileExt( "$dir/$name" );
            $package_entry{'set'} = $dist;

            $PACKAGE_HDRS{ "$dir/$name" } = {%package_entry};
        }
    }
}


sub PackageLib
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $dist, $type );
    my( $lib, $org_lib, %extras, $strip );

    Debug2( "PackageLib($platforms, @elements)" );

    return if ( !$ScmPackage );                 # Packaging enabled ?
    return if ( ! ActivePlatform($platforms) );

#.. Arguments
#
    $dist = "ALL";                              # Default set (ALL)
    $base = $PackageInfo{'Lib'}{'PBase'};       # Base of target
    $dir = $base . $PackageInfo{'Lib'}{'Dir'};  # Installation path (default)
    $type = "";

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir($T_PKG, $base, $_, \$dir, \$type);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Set=(.*)/) {                    # Distribution set(s)
            $dist = "$1";

        } elsif (/^--Package$/) {               # Package .. call by PackageLib
        } elsif (/^--Package=(.*)/) {
            $dist = "$1";

        } elsif (/^--Extras=(.*)/) {            # Extras=[none, .. ,all]
            foreach my $elem ( split( ',', $1 ) )
            {
                Error ("PackageLib: Unknown Extras mode: $elem")
                    unless ( grep m/$elem/, qw(none stub map lint debug all) );
                $extras{$elem} = 1;
            }
            %extras = () if ( $extras{'all'} );

        } elsif (/^--Strip$/) {                 # Strip path from source files
            $strip = -1;

        } elsif (/^--Strip=(\d+)$/) {           # Strip some f the path from source files
            $strip = $1;

        } elsif (/^--(.*)/) {
            Message( "PackageLib: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            $_ = StripPath($_, $strip) if ($strip);

            $org_lib = $_;                      # Original name
            if ( $ScmTargetHost eq "Unix" ) {
                $lib = "lib$_";                 # Prefix "lib" ....
                $lib =~ s/^liblib/lib/;         # @LIBS already has lib added
            } else {
                $lib = $_;
            }

            if (  my $libp = $SHLIBS->Get($lib) )
            {
                Debug( "PackageLib( $dir/$lib\$(GBE_TYPE).$::so, " .
                    "src: \$(LIBDIR)/$lib\$(GBE_TYPE).$::so, dest: $dir, dist: $dist, type: $type )" );

                #
                #   Create a "placekeeper" entry within $PACKAGE_SHLIBS
                #   The exact format of the name of the shared library is
                #   toolset specific. Create an entry to allow the toolset
                #   to extend the packaging information when the shared library
                #   recipe is constructed.
                #
                #
                my $ver = $libp->{ VERSION };
                my $name = "$dir/$lib.$ver.PlaceKeeper";

                $package_entry{'placekeeper'} = 1;
                $package_entry{'version'} = $ver;
                $package_entry{'lib'} = $lib;
                $package_entry{'dir'} = $dir;
                $package_entry{'set'} = $dist;
                $package_entry{'type'} = $type if ( $type );
                $package_entry{'extras'} = {%extras} if ( scalar %extras );

                push @{$SHLIB_PKG{$lib}}, $name;
                $PACKAGE_SHLIBS{$name} = {%package_entry};
            }

            #
            #   Clean up the package_entry
            #   Insert common items
            #
            %package_entry = ();
            $package_entry{'lib'} = $lib;
            $package_entry{'dir'} = $dir;
            $package_entry{'set'} = $dist;
            $package_entry{'extras'} = {%extras} if ( scalar %extras );
            $package_entry{'type'} = $type if ( $type );

            if ( my $libfile = $SRCS{$org_lib} )
            {
                #
                #   Allow the user to package a sourced file as a library
                #   But must be the un-massaged name of the file.
                #
                $package_entry{'dst'} = "$dir/$org_lib";
                $package_entry{'src'} = $libfile;
            }
            elsif ( $LIBS->Get($lib) )
            {
                #
                #   Package up a library known to the makefile
                #
                my $libp = $LIBS->Get($lib);

                $package_entry{'dst'}    = $dir . '/' . $libp->getFullName();
                $package_entry{'src'}    = $libp->getPath();
            }
            elsif ( ! $SHLIBS->Get($lib) )
            {
                #
                #   Not a known shared lib
                #   Not a known static lib
                #   Not a 'sourced' file
                #   Assume the a static library has magically appeared
                #   in the standard LIB directory. May have been placed there
                #   by a 'rule'
                #
                my $libp = $LIBS->New($lib);

                $package_entry{'dst'}    = $dir . '/' . $libp->getFullName();
                $package_entry{'src'}    = $libp->getPath();
            }

            #
            #   Add entry to various lists if required
            #
            PackageLib_AddEntry ('PackageLib', \%LIB_PKG, \%PACKAGE_LIBS, \%package_entry )
                if ( exists $package_entry{'dst'} );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageLib_AddEntry
#
# Description     : Helper function to add a package entry
#                   to the lists
#
# Inputs          : $directive          - Directive name
#                   $pList              - Ref to array list to maintain
#                   $pHash              - Ref to hash to maintain
#                   $pData              - Packaging Data
#                                         Must Take a copy.
#
# Returns         : 
#

sub PackageLib_AddEntry
{
    my ($directive, $pList, $pHash, $pData) = @_;

    my $lib = delete $pData->{'lib'};
    my $dst = delete $pData->{'dst'};

    Error ("INTERNAL PackageLib_AddEntry: lib or dst not defined")
        unless ( $lib && $dst );

    Debug( "$directive( ",$dst,
            ", src: " ,$pData->{'src'},
            ", dest: ",$pData->{'dir'},
            ", dist: ",$pData->{'set'},
            ", type: ",$pData->{'type'} || '',
            " )" );

    push @{$pList->{$lib }}, $dst;
    $pHash->{$dst } = {%$pData};
}


sub PackageProg
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $dist, $type );
    my( $prog, %extras, $strip );

    Debug2( "PackageProg($platforms, @elements)" );

    return if ( !$ScmPackage );                 # Packaging enabled ?
    return if ( ! ActivePlatform($platforms) );

#.. Arguments
#
    $dist = "ALL";                              # Default set (ALL)
    $base = $PackageInfo{'Prog'}{'PBase'};       # Base of target
    $dir = $base . $PackageInfo{'Prog'}{'Dir'};  # Installation path (default)
    $type = "";

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir($T_PKG|$T_TYPE, $base, $_, \$dir, \$type);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Set=(.*)/) {                    # Distribution set(s)
            $dist = "$1";

        } elsif (/^--Package$/) {               # Package .. call by PackageLib
        } elsif (/^--Package=(.*)/) {
            $dist = "$1";

        } elsif (/^--Tool(([=])(.*))?$/) {      # Allow Tool and Tool=xxx specfic target
            my $path = ( defined( $3) ) ? "/$3" : "";
            $dir = "\$(PKGDIR)$path/\$(GBE_HOSTMACH)";

        } elsif (/^--Extras=(.*)/) {            # Extras=[none, .. ,all]
            foreach my $elem ( split( ',', $1 ) )
            {
                Error ("PackageLib: Unknown Extras mode: $elem")
                    unless ( grep m/$elem/, qw(none stub map lint debug all) );
                $extras{$elem} = 1;
            }
            %extras = () if ( $extras{'all'} );

        } elsif (/^--Strip$/) {                 # Strip path from source files
            $strip = -1;

        } elsif (/^--Strip=(\d+)$/) {           # Strip some f the path from source files
            $strip = $1;

        } elsif (/^--(.*)/) {
            Message( "PackageProg: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( m~descpkg~ ) {
            PackageFile($platforms, @elements);

        } elsif ( ! /^--(.*)/ ) {
            $_ = StripPath($_, $strip) if ($strip);

            my $ext = "";
            $prog = $_;

            #
            #   If the named target is a program then append the correct
            #   extension. Otherwise assume that the target is either a script
            #   or a some other file - and don't append an extension
            #
            #   A program may not have any object files, only libraries
            #
            $ext = $::exe
                if ( $PROGS->Get($prog) );

            #
            #   A "file" that is specified with a "Src" directive may be
            #   installed as though it were a program
            #
            my $progfile;
            if ( $progfile = $SRCS{$prog} )
            {
                $progfile = $progfile;
                $prog = $prog;
            }
            else
            {
                $progfile = "\$(BINDIR)/$prog$ext";
            }

            Debug( "PackageProg( $dir/$prog$ext, " .
                 "src: $progfile, dest: $dir, dist: $dist, type: $type )" );

            my $target = "$dir/$prog$ext";
            push @{$PROG_PKG{$prog}}, $target;

            $package_entry{'src'}   = $progfile;
            $package_entry{'dir'}   = $dir;
            $package_entry{'set'}   = $dist;
            $package_entry{'extras'}= {%extras} if ( scalar %extras );
            $package_entry{'type'}  = $type if ( $type );

            $PACKAGE_PROGS{$target} = {%package_entry};
        }
    }
}


sub PackageJar
{
    my( $platforms, @elements ) = @_;
    my( $base, $dir, $dist, $type );
    my( $jar );

    Debug2( "PackageJar($platforms, @elements)" );

    return if ( !$ScmPackage );                 # Packaging enabled ?
    return if ( ! ActivePlatform($platforms) );

#.. Arguments
#
    $dist = "ALL";                              # Default set (ALL)
    $base = $PackageInfo{'Jar'}{'PBase'};       # Base of target
    $dir = $base . $PackageInfo{'Jar'}{'Dir'};  # Installation path (default)
    $type = "";

    foreach ( @elements )
    {
                                                # Standard targets
        my $rv = __TargetDir($T_PKG, $base, $_, \$dir, \$type);
        next if ( $rv == 1 );
        return if ( $rv == 2 );

        if (/^--Set=(.*)/) {                    # Distribution set(s)
            $dist = "$1";

        } elsif (/^--Package$/) {               # Package .. call by InstallJar
        } elsif (/^--Package=(.*)/) {
            $dist = "$1";

        } elsif (/^--(.*)/) {
            Message( "PackageJar: unknown option $_ -- ignored\n" );
        }
    }

#.. Files
#
    foreach ( @elements )
    {
        my %package_entry;
        if ( ! /^--(.*)/ )
        {
            $jar = $_;
            my $src;
            my $dest;

            if ( $JAR_FILES{$jar} )
            {
                $src = $JAR_FILES{$jar};
                $dest = $jar;
            }
            else
            {
                $src = "\$(CLSDIR)/$jar\$(GBE_TYPE).jar";
                $dest = "$jar\$(GBE_TYPE).jar";
            }


            Debug( "PackageJar( $dir/$dest, " .
                "src: $src, dest: $dir, dist: $dist, type: $type )" );

            $package_entry{'src'} = $src;;
            $package_entry{'dir'} = $dir;
            $package_entry{'set'} = $dist;
            $package_entry{'type'} = $type if ( $type );

            $PACKAGE_CLSS{ "$dir/$dest" } = {%package_entry};
                
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageProgAddFiles         - Add files to a PackageProg
#                   PackageLibAddFiles          - Add files to a PackageLib
#                   PackageShlibAddFiles        - Add files to a PackageLib (shared lib)
#                   PackageShlibAddLibFiles     - Add files to a PackageLib (shared lib)
#                                                 Add static library files
#
# Description     : Add files to a Program package or installation
#                   For use by Tool sets to allow additional files to be
#                   packaged with a program.
#
#                   The files are only added if the named program is being
#                   packaged and/or installed.
#
#
# Inputs          : prog        - program identifier
#                   file        - A file to be add
#                   args        - Additional packageing arguments
#
# Returns         : Nothing
#

sub PackageProgAddFiles
{
    Debug("PackageProgAddFiles");

    PackageAddFiles ( \%PACKAGE_PROGS, \%PACKAGE_PROGS, \%PROG_PKG, @_);
    PackageAddFiles ( \%INSTALL_PROGS, \%INSTALL_PROGS, \%PROG_INS, @_);
}

sub PackageLibAddFiles
{
    Debug("PackageLibAddFiles");

    PackageAddFiles ( \%PACKAGE_LIBS, \%PACKAGE_LIBS, \%LIB_PKG, @_ );
    PackageAddFiles ( \%INSTALL_LIBS, \%INSTALL_LIBS, \%LIB_INS, @_ );
}

sub PackageShlibAddFiles
{
    my ($prog, $file, @args) = @_;
    Debug("PackageShlibAddFiles");

    PackageAddFiles ( \%INSTALL_SHLIBS, \%INSTALL_SHLIBS, \%SHLIB_INS, @_ );
    PackageAddFiles ( \%PACKAGE_SHLIBS, \%PACKAGE_SHLIBS, \%SHLIB_PKG, @_ );

    #
    #   These files become the target of the "make_install_shlib" operation unless:
    #       Conditionally packaged files are not always created
    #       RemoveOnly files are not always generated
    #
    my $no_add;
    foreach ( @args )
    {
        if ( m/^defined=/ or m/^RemoveOnly=/ or /NoTarget=/ )
        {
            $no_add = 1;
            last;
        }
    }

    push (@SHLIB_TARGETS, $file ) unless $no_add;
}

sub PackageShlibAddLibFiles
{
    Debug("PackageShlibAddLibFiles");

    PackageAddFiles ( \%PACKAGE_SHLIBS, \%PACKAGE_LIBS, \%SHLIB_PKG, @_ , 'Class=lib');
    PackageAddFiles ( \%INSTALL_SHLIBS, \%INSTALL_LIBS, \%SHLIB_INS, @_ , 'Class=lib');
}

#-------------------------------------------------------------------------------
# Function        : PackageAddFiles
#
# Description     : Internal function to add files to the data structures that
#                   describe a package or installation
#
#                   Use this function to package or install additional files with
#                   the Progs and Libs
#
#                   ie: Add a LIB file to be packaged with a Shared Library
#                   ie: Add a MAP file to be packaged with a program
#
# Inputs          : ref_spkg  - Reference to the hash that contains the package data
#                   ref_dpkg  - Reference to the target package/install hash
#                               Normally the same as ref_dpkg, but does allow
#                               a static library to be added to a dynamic library
#                               package.
#                   ref_list  - Reference to a hash that may contain package keys to process
#                   prog      - Key for index to above
#                   file      - A file to be added
#                   args      - Additional packaging arguments
#
# Returns         :
#
sub PackageAddFiles
{
    my ($ref_spkg, $ref_dpkg, $ref_list, $prog, $file, @args ) = @_;

    #
    #   Process entry
    #   The files may need to be added to multiple packages
    #
    Debug("PackageAddFiles: $file");

    return unless ( $ref_list->{$prog} );

    #
    #   Parse arguments and extract the "Class=xxx" argument. This may be used
    #   to limit the extra files piggybacked with the base file
    #   All files without a class will be treated as base files
    #
    my $class;
    foreach ( @args )
    {
        next unless ( m~^Class=(.*)$~ );
        $class = $1 unless ( $1 eq 'none' );
    }
    Debug("PackageAddFiles: Class: ", $class || 'Default=None');

    foreach my $entry_key ( @{$ref_list->{$prog}} )
    {
        Debug("PackageAddFiles: Entry found: $entry_key");

        #
        #   Copy of the template entry
        #
        my %package_entry = %{$ref_spkg->{$entry_key}};
        Error ("INTERNAL: Expected entry in PACKAGE_ hash not found: $entry_key" )
            unless ( %package_entry );

        #
        #   Do not add the file if the user has limited the extra files added
        #   to the packaging list and the current file is not in the class list
        #
        if ( $class && $package_entry{'extras'} )
        {
            next unless ( $package_entry{'extras'}{$class} );
        }

        #
        #   Create new entries for the file
        #
        $package_entry{'src'} = $file;
        foreach ( @args )
        {
            m~^(.*)=(.*)$~;
            $package_entry{$1} = $2;
        }

        #
        #   Clean out useless fields
        #   Must remove the placekeeper marker to allow the entry to be visible
        #
        delete $package_entry{'placekeeper'};
        delete $package_entry{'version'};
        delete $package_entry{'lib'};
#       delete $package_entry{'extras'};                   # Keep these
        delete $package_entry{'Class'};

        #
        #   Add the entry
        #
        #   Under some conditions is it possible to attempt to add the same named
        #   file. This will result in a circular dependancy in the makefile
        #
        #   The condition is when merged libaries with PDBs (WINCE+WIN32) are merged
        #   and the source for the merge is the "local directory.
        #
        #
        my $dst = $package_entry{'dir'} ;
        ( my $dfile = $file) =~ s~.*/~~;
        Debug( "    added $dst/$dfile = $file" );

        $ref_dpkg->{"$dst/$dfile"} = {%package_entry}
            unless ( "$dst/$dfile" eq "$file" );
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageProgRemoveFiles
#
# Description     : Flag a Packaged program to be not packaged
#                   This mechanism is used to remove a program from packageing
#                   under conditions where the toolset has generated a different
#                   program.
#
#                   The entry is flagged as a placeholder
#
# Inputs          : prog        - Program to process
#
# Returns         : Nothing
#
sub PackageProgRemoveFiles
{
    my ($prog) = @_;
    Verbose ("PackageProgRemoveFiles: $prog" );
    return unless (exists($PROG_PKG{$prog}));

    #
    #   Must lookup the TAG to locate the  required entry
    #
    my $tag = $PROG_PKG{$prog};
    foreach my $entry ( @$tag )
    {
        Verbose("Do not package: $entry");
        if ( exists $PACKAGE_PROGS{$entry} )
        {
            $PACKAGE_PROGS{$entry}{placekeeper} = 'ProgRemoved';
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : DPackageLibrary
#
# Description     : Collect information to allow the generation of a DPACKAGE
#                   file. This directive allows the generation of "Library"
#                   directives within the final DPACKAGE
#
#                   This directive does generate the DPACKAGE file.
#
# Inputs          : platform    - This does not need to be an active platform
#                                 it is simply passed to the DPACKAGE builder
#
#                   using       - The "using" target
#
#                   ...         - Arguments for the Library directive
#
# Returns         :
#
sub DPackageLibrary
{
    JatsDPackage::DPackageAdd ( @_ );
    $DPackageDirective = 1;
}

#-------------------------------------------------------------------------------
# Function        : SetProjectBase
#
# Description     : Allows the user to modify the build's concept of the Base
#                   of the build. By default the base is the same directory as
#                   the build.pl file, but in some contorted environments it
#                   is a great deal simpler to specify a differnt base.
#
#                   The use may use the variable $ProjectBase as a path
#                   specifier to locate files and directories
#
#                   Both absolute and relative paths are supported
#                   If the initial value of $ProjectBase is relative then
#                   it will be maintained as a relative path.
#
# Inputs          : elements        path to base
#                                   These may be:
#                                       --Up=xx
#                                       name
#
# Returns         : Nothing
#

#
#   Allow the user to modify the project base variable
#
sub SetProjectBase
{
    my $rip = 0;
    my $path = "";
    my $is_relative;

    Debug("ProjectBase Initial: $ProjectBase, @_");

    #
    #   Ensure that the ProjectBase is in a "nice" form
    #   1) No /./ bits
    #   2) No trailing /
    #   3) Not equal to .
    #   4) No training /.
    #   5) No //
    #
    $ProjectBase =~ s~/\./~/~g;
    $ProjectBase =~ s~/$~~g;
    $ProjectBase =~ s~^\.$~~g;
    $ProjectBase =~ s~/\.$~~g;
    $ProjectBase =~ s~//$~/~g;

    #
    #   ProjectBase may be absolute or relative
    #   Determine this before we mess with it
    #
    $is_relative = ($ProjectBase !~ m~^/~);

    #
    #   Process each argument
    #
    foreach ( @_ )
    {
        if ( /^--Up=([0-9]*)/ ) {
            $rip = $1;
        } elsif ( /^--/ ) {
            Warning( "SetProjectBase - unknown option \"$_\" - ignored" );
        } else {
            $path = $_;
        }
    }

    #
    #   Process the UP requests
    #   If the tail directory is a ".." then up is done by adding another ".."
    #   If the tail directory is not a "..", then up is done by removing it
    #
    #   If we go past the start of the path then simply add ".."
    #
    while ( $rip-- > 0 )
    {
        Debug2("ProjectBase: $ProjectBase, Up: $rip, IsRel: $is_relative");

        #
        #   If ending in a /.. or is exactly equal to ..
        #   Then its a dot-dot and the way to go UP is to append another ..
        #
        if ( $ProjectBase =~ m~(/\.\.$)|(^\.\.$)~ )
        {
            $ProjectBase .= '/..';
        }
        else
        {
            #
            #   Not a dot-dot ending
            #   Attempt to remove the last directory of the form
            #       /xxxxx
            #   Where the leading / is optional
            #   Note: Must have at least one character in the dirname
            #         This prevents leading / from matching - which is needed
            #
            unless ($ProjectBase =~ s~/?[^/]+$~~)
            {
                #
                #   Removal failed
                #   If a relative path then we can keep on going up,
                #   otherwise we are dead.
                #
                Error ("ProjectBase outside project") unless ($is_relative);
                $ProjectBase = '..';
            }

            #
            #   Ensure that the leading / in an absolute path is not deleted
            #
            $ProjectBase = '/'
                unless ( $is_relative || $ProjectBase );
        }
    }

    #
    #   Append the user path
    #
    $ProjectBase .= '/' . $path if ( $path );
    $ProjectBase = '.' unless ( $ProjectBase );
    Debug("ProjectBase set to : $ProjectBase");

    #
    #   Once upon a time I tried to convert paths that contained spaces into
    #   short (mangled) names. This was not sucessful because:
    #       1) Clearcase dynamic views do not support name mangling
    #       2) Samba file system does not appear to support name mangling
    #
    #   Spaces in paths are not good for MAKE
    #   Now I simple generate a message
    #
    Warning( "ProjectBase contains a space: $ProjectBase")
        if ( $ProjectBase =~ m/ / );

    #
    #   Sanity check
    #   Absolute paths can be checked easily
    #   Checking of relative paths does not appear to work
    #   When I tested it chdir, opendir and stat would limit themselves
    #   and drop into the root directory ( under windows )
    #
    #   Solution: Check the path does not extend beyond the file tree
    #
    my $distance = 1;
    my $tpath = $ProjectBase;
    
    if ( $is_relative && $tpath ne '.' )
    {
        #
        #   Build up the complete pathname by merging it with the
        #   current directory. Then clean it up.
        #
        $tpath = $::Cwd . '/' . $ProjectBase;

        #
        #   Scan the list of diretories and count the distance from the root
        #   This should not be greater than zero for sanity
        #   Note: Get an empty elemement from the split due to
        #         the leading / of the ABS path
        #
        $distance = 0;
        foreach (  split ('/', $tpath) )
        {
            if ( m~\.\.~ )
            {
                $distance--;
            }
            else
            {
                $distance++;
            }
        }
    }

    #
    #   Warn if not a valid directory
    #
    Warning( "ProjectBase is not a directory: $ProjectBase")
        if ( $distance <= 0 || !  -d $tpath  );
            
    #
    #   $ProjectBase will always be a valid directory, but if its the top
    #   directory (/) and it is added to a path we will get //path
    #   This is not good, so // will be removed later in the AddIncDir and
    #   AddSrcDir commands where $ProjectBase is really used.
    #
    #   Alternatively we could set $ProjectBase to an empty string, but then
    #   this may be confused with an empty relative directory
    #
    Debug("ProjectBase Final  : $ProjectBase");
}

#-------------------------------------------------------------------------------
# Function        : DeployPackage
#
# Description     : Generate a deployed package
#                   This is a gateway to a different packaging system
#
#                  DeployPackage and PackageXxxxx directives are mutually
#                  exclusive. Only one person can play in the package area.
#
# Inputs          : Platform Specifier
#                   Package Name    (Optional)
#                   Options
#                       --Name : Base name of the package. The default is taken
#                                from the build.pl file
#                       --Dir  : Package directory
#                                The default is based on the package name
#
# Returns         :
#
sub DeployPackage
{
    my( $platforms, @elements ) = @_;
    my $dir;
    my $name;

    #
    #   Flag that this build creates a deployable package, even if its not
    #   active on this platform.
    #
    $DEPLOYPACKAGE = 1;


    Debug2( "DeployPackage($platforms, @elements)" );
    return if ( ! ActivePlatform($platforms) );

    #
    #   Only allow one use of this directive
    #
    Error("DeployPackage can only be used once" ) if ( %DEPLOYPACKAGE );
    $DEPLOYPACKAGE = 2;

    #
    #   Ensure that the deployment file is available
    #
    my $command_file = $ScmDeploymentPatch ? "deploypatch.pl" : "deployfile.pl";
    Error("DeployPackage: $command_file not found") unless (-f "./$command_file");
    #
    #   Collect arguments
    #
    foreach (@elements )
    {
        if ( m/^--Dir=(.*)/ ) {
            Error ("DeployPackage: Package directory defined multiple times") if $dir;
            $dir = $1;

        } elsif ( m/^--Name=(.*)/ ) {
            Error ("DeployPackage: Package name defined multiple times") if $name;
            $name = $1;

        } elsif ( m/^--/ ) {
            Warning( "DeployPackage: Unknown option ignored: $_");

        } else {
            Error ("DeployPackage: Package name defined multiple times") if $name;
            $name = $_;

        }
    }

    $name = $::ScmBuildPackage unless ( $name );

    #
    #   Save the deployment data
    #
    $dir = lc($name) unless ( $dir );
    $DEPLOYPACKAGE{'name'} = $name;
    $DEPLOYPACKAGE{'dir'} = $dir;
    $DEPLOYPACKAGE{'cmdfile'} = $command_file;

    #
    #   Flag that toolset tests should be supressed
    #   The Deploy world does not really use the full makefiles and if the
    #   compilers are not installed will not be able to create deployment
    #   packages
    #
    $ScmNoToolsTest = 1;
}


###############################################################################
###############################################################################
# Private function section.
#       The following functions are used INTERNALLY by makelib.pl2.
###############################################################################

###############################################################################
#   A collection of functions to write to the MAKEFILE handle
#
#   MakeHeader          - Write a nice section header
#   MakeNewLine         - Print a new line
#   MakePrint           - Print a line ( without trailing \n)
#   MakeQuote           - Escape \ and " character, then print a line
#   MakePrintList       - Print an array
#   MakeEntry           - Complex line printer
#   MakePadded          - Padded line printer (internal)
#   PadToPosn           - Calc space+tabs to tabstop (internal)
#   MakeEntry3          - Complex Line Printer
#   MakeDefEntry        - Print a definition line (Production + Debug support)
#   MakeIfDefEntry      - Print ifdef entry
#   MakeIfnDefEntry     - Print ifndef entry
#   MakeIfZeroEntry     - Print ifeq entry
#
###############################################################################

sub MakeHeader
{
    my ($text, @rest) = @_;
    my $length = length ($text);

    print MAKEFILE "\n";
    print MAKEFILE "#--------- $text ", '-' x (80 - 12 - $length)  ,"\n";
    print MAKEFILE "#    $_\n" foreach  ( @rest ) ;
    print MAKEFILE "#\n";
}

sub MakeNewLine         # Print a newline to the current 'Makefile'
{
    print MAKEFILE "\n";
}

sub MakePrint           # Print to the current 'Makefile'
{
    print MAKEFILE @_
        if ( defined $_[0] );
}

sub MakeQuote           # Quote a makefile text line
{
    my( $line ) = @_;
    $line =~ s/\\/\\\\/g;                       # quote all '\' characters
    $line =~ s/"/\\"/g;                         # Then quote '"' characters
    $line =~ s/=#/=\\#/g;                       # Then quote '=#' sequence
    print MAKEFILE $line;
}

sub MakePrintList
{
    print MAKEFILE $_ . "\n" foreach (@{$_[0]});
}

sub QuoteArray
{
    my $quote = "'";
    if ( @_ ) {
        return ($quote . join("$quote $quote", @_) . $quote);
    }
    return '';
}

#-------------------------------------------------------------------------------
# Function        : MakeEntry
#
# Description     : Build a entry based on the element list
#                   Creates text of the form
#                       $(BINDIR)/prog.exe: object1.obj \
#                                           object2.obj
#
#
# Inputs          : $prelim         - Preamble (one-off)
#                   $postlim        - Postamble (one-off)
#                   $prefix         - Pefix (to each element of array)
#                   $postfix        - Postfix (to each element of array )
#                   @elements       - Array of element to wrap
#
# Returns         :   1 Always
#
# Notes:
#       The above description means that the following entry format is
#       produced:
#
#           <preliminary><prefix><variant1><prefix><variant2>...<final>
#
#       With judicious use of newline and tab characters, a target
#       and dependency list along with the command(s) to build the
#       target can be constructed.
#
sub MakeEntry
{
    my( $prelim, $postlim, $prefix, $postfix, @elements ) = @_;

    MakePrint $prelim;
    MakePrint "${prefix}${_}${postfix}" foreach ( @elements );
    MakePrint $postlim if ($postlim);
    return 1;
}

#-------------------------------------------------------------------------------
# Function        : MakePadded
#
# Description     : Generate aligned output of the form
#                       Prefix_text           Aligned_text
#                   where the aligned text is at a specified TAB boundary
#
# Inputs          : $align      - Tab stop (One tab = 8 chars)
#                   $prefix     - Text to print before alignment occurs
#                   @line       - Remainder of the line
#
sub MakePadded          # Print to the current 'Makefile', tab aligning
{
    my( $align, $prefix, @line ) = @_;

    my $strlen = length( $prefix );
    my $pad = PadToPosn( $strlen, $align * 8 );

    print MAKEFILE $prefix . $pad;
    print MAKEFILE @line;
}

#-------------------------------------------------------------------------------
# Function        : PadToPosn
#
# Description     : Given that we are at $startposn return a tab and space
#                   string to place us at $endposn
#
sub PadToPosn
{
    my ($startposn, $endposn ) = @_;


    #
    #   Case where we are already too far into the line
    #
    return ( ' ' )if ( $endposn <= $startposn );

    my $tcount = 0;
    my $scount = 0;

    while ( $startposn < $endposn )
    {
        $tcount ++;
        $startposn = ($startposn >> 3) * 8 + 8;

        my $delta = $endposn - $startposn;
        if ( $delta < 8 )
        {
            $scount = $delta;
            last;
        }

    }
    return ( "\t" x $tcount .  ' ' x $scount );
}

#-------------------------------------------------------------------------------
# Function        : MakeEntry3
#
# Description     : Build a makefile entry based on the element list, tab aligned
#                   Can creat text of the form:
#                       TAG = NAME0 \       TAG : NAME0 \ 
#                             NAME1               NAME1
#
#
# Inputs          : $prelim             - Preliminary text
#                   $presep             - Preliminary seperator
#                   $elem_ref           - Either a single name or a reference to
#                                         and array of names, or a hash.
#
# Returns         : Writes directly to the Makefile
#
sub MakeEntry3
{
    my( $prelim, $presep, $elem_ref ) = @_;

    #
    #   The prelim may have some "\n" characters at the start
    #   These simplify formatting, but mess up the nice formatting
    #
    if ($prelim =~ m~(^\n+)(.*)~ )
    {
        MakePrint $1;
        $prelim = $2;
    }

    #
    #   Print the definition and the sep with nice padding
    #
    MakePadded ( 3, $prelim, $presep );
    my $leadin = ' ';

    #
    #   If a HASH reference then use a sorted list of keys from the hash.
    #
    if ( ref ($elem_ref) eq "HASH" )
    {
        my @hash_list;
        @hash_list = sort keys ( %{$elem_ref} );
        $elem_ref = \@hash_list;
    }

    #
    #   If the list is only one element long, then create a simple form
    #   If the list is not an array ref, then treat it as a single element
    #
    if ( ref ($elem_ref) eq "ARRAY" )
    {
        my $line = 0;
        foreach my $element ( @$elem_ref )
        {
            print MAKEFILE $leadin . $element;
            $leadin = " \\\n" . PadToPosn(0,24 + length( $presep ) + 1 ) unless ($line++);
        }
    }
    elsif ( defined $elem_ref )
    {
            print MAKEFILE $leadin . $elem_ref;
    }
    MakeNewLine();
    return 1;
}

#-------------------------------------------------------------------------------
# Function        : MakeDefEntry
#
# Description     : Make a definition entry of the form
#
#                       TAG = NAME0 \
#                             NAME1
#
#                   Support a list of definitions that will always be created
#                   as well as a production and a debug list.
#
#                   Will always generate the "TAG = " string, even if the list
#                   is empty.
#
#                   Will supress the TAG if there is no data if the FIRST opr starts with a '+'
#
# Inputs          : TAG         - Text tag to create
#                   FIRST       - First assignement opr. = or +=
#                   ALL_LIST    - A reference to a list of names to assign
#                                 or a single name.
#                   PROD_LIST   - Optional list to extend the definition with for a production build
#                   DEBUG_LIST  - Optional list to extend the definition with for a debug build
#
# Returns         : Nothing
#

sub MakeDefEntry
{
    my( $tag, $assign, $all, $prod, $debug ) = @_;

    #
    #   Do not generate anything if the $opr is "+=" and there is no data
    #   to output. ie: Supress empty TAG += statements
    #
    return if ( $assign =~ m/\+/ && ( ref($all) && ! defined $all->[0] ) );

    #
    #   TAG for all entries
    #
    MakeEntry3( $tag, $assign, $all );


    #
    #   TAGs for PROD build
    #   TAGs for DEBUG build
    #
    if ( defined $prod && defined $prod->[0] )
    {
        print MAKEFILE 'ifeq "$(DEBUG)" "0"' . "\n";
        MakeEntry3( $tag, "+=", $prod );
        print MAKEFILE 'endif' . "\n";
    }

    if ( defined $debug && defined $debug->[0] )
    {
        print MAKEFILE 'ifeq "$(DEBUG)" "1"' . "\n";
        MakeEntry3( $tag, "+=", $debug );
        print MAKEFILE 'endif' . "\n";
    }

}

sub MakeIfDefEntry
{
    my( $iftag, @rest ) = @_;

    print MAKEFILE "ifdef $iftag\n";
    MakeDefEntry (@rest);
    print MAKEFILE "endif\n\n";
}

sub MakeIfnDefEntry
{
    my( $iftag, @rest ) = @_;

    print MAKEFILE "ifndef $iftag\n";
    MakeDefEntry (@rest);
    print MAKEFILE "endif\n\n";
}

sub MakeIfZeroEntry
{
    my( $iftag, @rest ) = @_;

    print MAKEFILE "ifeq (\$($iftag),0)\n";
    MakeDefEntry (@rest);
    print MAKEFILE "endif\n\n";
}

#-------------------------------------------------------------------------------
# Function        : CreateNameList
#
# Description     : Create a list of names by adding a prefix and suffix to a
#                   list of items. This is used to add a directory prefix and a
#                   file suffix to a list of files.
#
# Inputs          : $prefix             ie: '$(OBJDIR)/'
#                   $suffix             ie: '.obj'
#                   $elem_ref           ie: A list of files ( passed be ref )
#                                           If a Hash then its sorted keys is used
#
# Returns         : A ref to the resulting list
#
sub CreateNameList
{
    my( $prefix, $suffix, $elem_ref ) = @_;
    my @result;

    if ( ref ($elem_ref) eq "HASH" )
    {
        my @hash_list;
        @hash_list = sort keys ( %{$elem_ref} );
        $elem_ref = \@hash_list;
    }

    foreach  ( @$elem_ref )
    {
        push @result, $prefix . $_ . $suffix;
    }
    return \@result;
}

#-------------------------------------------------------------------------------
# Function        : ListGeneratedProjects
#
# Description     : Return a list of generated/nongenerated projects
#                   Used in conjunction with CreateNameList
#
# Inputs          : $type       - TRUE : Generated
#                                 FALSE: Not Generated
#
# Returns         : A reference to a list of projects
#                   undef will be retuend if there are no projects
#
sub ListGeneratedProjects
{
    my ($type) = @_;
    my @list;
    foreach my $project ( @PROJECTS_ORDER )
    {
        if ( exists($PROJECTS{$project}->{'generated'}) xor $type )
        {
            push @list, $project;
        }
    }
    return @list ? \@list : undef;
}

#-------------------------------------------------------------------------------
# Function        : ListCleanGenerated
#
# Description     : return a list of generated targets that have 'clean'
#                   operations. This is used in conjunction with CreateNameList
#
# Inputs          : None
#
# Returns         : A list of project indexes, that can be cleaned
#
sub ListCleanGenerated
{
    my @list;
    foreach my $i ( @GENERATE_FILES )
    {
        push @list, $i->{'index'}
            if ( $i->{'clean'} );
    }
    return \@list;
}

#-------------------------------------------------------------------------------
# Function        : MakeResolve
#
# Description     : Internal Function
#                   Locate a source file by examining a list of directories
#
#                   Don't use directly
#                   Use MakeSrcResolve or MakeSrcResolveExtended
#
# Inputs          : $dirs           - Ref to an array of directories to scan
#                   $source         - File to locate
#
# Returns         : Resolved path to the file
#                   Will warn if multiple instances of the file are found
#
sub MakeResolve
{
    my( $dirs, $source ) = @_;
    my( $first, $count );

    #
    #   If the path contains a '$' then its assumed to be
    #   a variable name in the path. Just assume that it exists
    #
    return $source if ( $source =~ m#\$# );

    #
    #   If the path is absolute or contains a leading ., then don't search
    #   Warn if it can't be found
    #
    if ( $source =~ m#^(/|\.)# )
    {
        Warning( "Unable to resolve '$source' path" ) unless -f $source;
        return $source;
    }

    my @found;
    # Search the local path first
    push (@found, $source ) if -f ($source);

    foreach my $dir (@$dirs)
    {
        next if ( $dir eq '.' );
        my $temp = $dir . '/' . $source;
        Debug2( "MakeResolve: Looking in: $temp" );
        push (@found, $temp) if (-f $temp);
    }

    Warning( "Unable to resolve path to '$source'" ) unless $found[0];
    if (scalar @found > 1)
    {
        Warning("Duplicates for '$source'. Using the first", @found);
    }

    return $found[0] || $source;
}

#-------------------------------------------------------------------------------
# Function        : MakeSrcResolve
#
# Description     : Locate a source file by examining the list of source
#                   directories. There are a few frills
#
#                   Look for a source file in
#                       1) %::BUILD_KNOWNFILES
#                       2) %SRCS
#                       3) Dirs specified by the array @SRCSDIRS
#
# Inputs          : Name of a file to resolve
#
# Returns         : Resolved path.
#                   Input file - if not found at all
#
sub MakeSrcResolve
{
    my ($name) = @_;
    my $file;

    if ( exists ( $::BUILD_KNOWNFILES{$name} ) ) {
        #
        #   The Known Files list is relative to ScmRoot
        #   This must be included in the full path
        #
        $file = $ScmRoot . '/' . $::BUILD_KNOWNFILES{$name};

    } elsif ( exists $SRCS{$name} ) {
        $file = $SRCS{$name};

    } else {
        $file = MakeResolve( \@SRCDIRS, @_ );
    }
    return $file;
}


# MakeSrcResolveExtended
#   from_global = 0 : Search user specified directories
#               = 1 : Search LinkPkgArchive list
#
our @PkgSrcDirList;
sub MakeSrcResolveExtended
{
    my ( $from_global, $file ) = @_;

    #
    #   Simple Case. Resolve source from known source directories
    #
    #
    return MakeSrcResolve( $file )
        unless ( $from_global );

    #
    #   Not so simple Case
    #   Resolve the source from the imported packages
    #
    #   Create a list of directores to search, but only the first time
    #       - Interface directories - from BuildPkgArchive
    #       - LnkPkgArchive directories
    #         Using target,product,platform include directories
    #
    unless ( @PkgSrcDirList )
    {
        for my $entry (@{$::ScmBuildPkgRules{$ScmPlatform} })
        {
            next if ( $entry->{'TYPE'} eq 'build' ); # Ignore BuildPkgArchives
            
            for (@{$entry->{'PINCDIRS'}}, @{$entry->{'THXDIRS'}}, '' )
            {
                my $dir = $entry->{'ROOT'} . "/" . $_ ;
                $dir =~ s~//~/~g;
                $dir =~ s~/$~~;
                push ( @PkgSrcDirList, $dir );
            }
        }
    }

    return MakeResolve( \@PkgSrcDirList, $file );
}

#-------------------------------------------------------------------------------
# Function        : GetPackageRoot
#
# Description     : Determine the root directory for a given package
#                   This routine is intended for non-standard JATS scripts that
#                   access package contents directly
#
#                   Note: This routine does not attempt to handle multiple
#                         instances of a package ( sysbasetypes ).
#
# Inputs          : $pname          - Name of the package
#
# Returns         :
#
sub GetPackageRoot
{
    my( $pname ) = @_;
    Debug( "GetPackageRoot(@_)" );

    my $result = undef;
    my $pkg = GetPackageEntry( $pname );
    if ( $pkg )
    {
        $result = $pkg->{'ROOT'};
        Debug( "GetPackageRoot: $result" );
    }

    return $result;
}

#-------------------------------------------------------------------------------
# Function        : ActiveProject
#
# Description     : Determine if the specified project is currenly 'active'
#
# Inputs          : $project            - one or more project names separated
#                                         by either a comma or a colon
#
# Returns         : TRUE    if the project is active
#
sub ActiveProject
{
    my ($project) = @_;
    foreach (  split( '\s*[:,]\s*', $project ) )
    {
        return 1
            if ( $_ eq $::ScmBuildProject );
    }
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : ActiveDefine
#
# Description     : Determine if the specified definition is currenly 'active'
#
# Inputs          : $defs               - one or more variable names separated
#                                         by either a comma or a colon
#
# Returns         : TRUE    if any of the definitions are known
#
sub ActiveDefine
{
    my ($defs) = @_;
    no strict 'refs';
    foreach (  split( '\s*[:,]\s*', $defs ) )
    {
        return 1
            if ( defined( $$_ ) || ( @$_ ) );
    }
    use strict 'refs';
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : ActiveMachType
#
# Description     : Determine if the specified MachType is currenly 'active'
#
# Inputs          : $mtype              - one or more machine names separated
#                                         by either a comma or a colon
#
# Returns         : TRUE    if any of the current MachType is in the list
#
sub ActiveMachType
{
    my ($mtype) = @_;
    foreach (  split( '\s*[:,]\s*', $mtype ) )
    {
        return 1
            if ( uc($_) eq uc($::GBE_MACHTYPE) );
    }
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : ActiveAlias
#
# Description     : Determine if the specified Alias is currenly 'active'
#
# Inputs          : $mtype              - one or more alias names separated
#                                         by either a comma or a colon
#
# Returns         : TRUE    if any of the current Aliases is in the list
#
sub ActiveAlias
{
    my ($mtype) = @_;
    foreach (  split( '\s*[:,]\s*', $mtype ) )
    {
        return 1
            if ( isAnAlias( uc($_) ) );
    }
    return 0;
}


#-------------------------------------------------------------------------------
# Function        : ActivePlatform
#
# Description     : Determine if the specified platform is currently 'active'
#                   This is used by all user directives in order to determine
#                   if the directive should be ignored for the current platform
#
# Inputs          : $platform_spec      - A platform specifier
#                                         This is a bit complex.
#
#                   Format of platform_spec. One or more of
#                       PlatformName
#                       AliasName
#                       TargetName[,--Target]
#                   Special Options (Must all be True)
#                       --Project=ProjectName[:ProjectName]+
#                       --Defined=SomeValue[:SomeValue]+
#                       --MachType=SomeMachType[:SomeMachType]+
#                       --Alias=SomeAlias[:SomeAlias]+
#                   Some shortcuts
#                       '*'     ==> Always true
#
#                   Each can be prefixed with a '!' to negate the test
#                   
#                   PlatformNames are either additive or negative(prefixed with !)
#                       Order is not important                   
#                       All additive names are accumulated before the negative items are considered.
#                           ie: aa,!bb => !bb,aa
#                       If only negative names are provided then JATS assumes a leading '*'
#                           ie: !aaa => *,!aaa
#                   
#                   Valid options are:
#                       --Target        - indicates that the platform is a 'target'
#
# Returns         : TRUE if the platform spec is satisfied
#
sub ActivePlatform
{
    my( $platform_spec ) = @_;
    my( @platforms, $scmplatform, $platform );
    my( %arguments, @args );
    my @plist;

    #
    #   Short circuit check
    #       '*' is used so often that it pays to check it first
    #
    if ( $platform_spec eq '*' )
    {
        Debug3( " ActivePlatform(@_) = TRUE" );
        return 1;
    }

    #
    #   Platform specifier may be a comma seperated list
    #   ie:  WIN32,MOS,XXX
    #   Extract non-platform arguments
    #   Process to yield a dummy platform of '0' or '1' - these will be seen later
    #
    foreach ( split( '\s*,\s*', $platform_spec ) )
    {
        my ($result, $not);
        if ( m~^(!?)--Project=(.+)~ ) {
            $not = $1;
            $result = ActiveProject($2);

        } elsif ( m~^(!?)--Defined=(.+)~ ) {
            $not = $1;
            $result = ActiveDefine($2);

        } elsif ( m~^(!?)--MachType=(.+)~ ) {
            $not = $1;
            $result = ActiveMachType($2);

        } elsif ( m~^(!?)--Alias=(.+)~ ) {
            $not = $1;
            $result = ActiveAlias($2);

        } else {
            #
            #   Must be a platform argument
            #   Add to a list
            #
            push @platforms, $_;
            next;
        }

        #
        #   Continue processing non-platform arguments
        #   Each one must be TRUE, allowing for negation.
        #
        $result = $result ? 1 : 0;
        $result = ! $result if ( $not );
        return 0 unless ( $result );
    }

    #
    #   If we have no platforms then the test was purely non-platform arguments.
    #
    if ($platform_spec ne '' && ! @platforms) {
        Debug3( " ActivePlatform(@_ == $ScmPlatform) = TRUE" );
        return 1;
    }

    unless (@platforms) {
        Debug3( " ActivePlatform(@_ == $ScmPlatform) = FALSE" );
        return 0;
    }


    #   Platform specified may be an Alias
    #   Perform alias expansion
    #
    @platforms = ExpandPlatforms( @platforms );         # aliasing
    Debug3( " ActivePlatform(@_)" );
#    Debug0( " Platforms(@platforms)" );

#.. Arguments
#   At this point we have a list of platforms and arguments
#   Build up a hash of arguments for each platform being parsed
#   Multiple arguments can follow a platform name
#   Arguments apply to the preceeding platform name
#
    $platform = undef;
    foreach ( @platforms )
    {
        if ( /^--Target/ ) {                     # Arguments
            if ( $platform ) {
                $arguments{$platform}{'Target'} = 1;
            } else {
                Warning ("No Platform preceding platform option: $_");
            }

        } elsif ( /^--Only(Prod)|(Debug)/ || /--board=/ ) {
            # Known arguments
            # Bit of a kludge. Must be a better way

        } elsif ( /^--/ ) {
            Warning ("Unknown platform option: $_");

        } else {                                # Target
            $platform = $_;
            push @plist, $platform;
        }
    }

#.. Scan the expression
#
    $scmplatform = uc( $ScmPlatform );              # current platform
    my @add;
    my @remove;
    foreach ( @plist ) {
        my $platform = uc( Trim( $_ ) );            # trim white and convert case
        my $pname = $platform;
        my $invert = 0;
        if (substr($platform, 0, 1) eq '!') {
            $invert = 1;
            $pname = substr($platform, 1)
        }

        #
        #   Determine filter comparison
        #       Either a Platform or a Target
        #
        if ( $arguments{$platform}{'Target'} ) {
            $pname = $scmplatform if ($pname eq  uc( $ScmTarget ));
        }

        #
        #   Catch some historically bad practices
        #
        $pname = $scmplatform if ( $pname eq '*'  || $pname eq '1' ); 

        Debug3( "   Platform=$platform, PName=$pname" );

        #
        #   Examine platform names
        #   Allow negation (removal) of the name
        #
        if ( substr($platform, 0, 1) eq '!' )  {
            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);
#DebugDumpData("Add", \@add);
#DebugDumpData("Remove", \@remove);
#DebugDumpData("calcList", \%calcList);

    #
    #   If the current build target is left in the platform list, then we are building for it
    #   
    if (exists $calcList{$scmplatform}) {
        Debug3( " ActivePlatform(@_ == $ScmPlatform) = TRUE" );
        return 1;
    }

    Debug3( " ActivePlatform(@_ == $ScmPlatform) = FALSE" );
    return 0;
}

#-------------------------------------------------------------------------------
# Function        : If
#
# Description     : Function for embedding arguments in directives
#                   To be used within another directive
#                   ie:
#                       AnyDirective ('*',  arg1, arg2, ...
#                                           If (SomePlatform, arg1, .. ,argn))
#
# Inputs          : $platform               - Active Platform specifier
#                   @args                   - Args
#
# Returns         : @args or nothing
#
sub  If
{
    my $platform = shift;
    return @_
        if ( ActivePlatform( $platform ));
    return;
}

#-------------------------------------------------------------------------------
# Function        : RegisterMakefileGenerate
#
# Description     : Register a function to be called at the start of the
#                   makefile generation process
#
# Inputs          : $fname      - Name of the function
#                   $args       - Function Arguments
#
# Returns         : Nothing
#
our @MF_Generators;
sub RegisterMakefileGenerate
{
   my ($fref) = @_;
   my $rtype = ref($fref) || 'not a reference';

   Error ("RegisterMakefileGenerate called incorrectly",
          "First argument MUST be a code reference",
          "It is a $rtype"
          ) unless ( $rtype eq 'CODE' );

   #
   #    Save the arguments by reference in an array
   #    The array will be processed later
   #
   push @MF_Generators, \@_;
}

#-------------------------------------------------------------------------------
# Function        : RegisterSrcHook
#
# Description     : Register a function to be called when a source file is
#                   declared
#
# Inputs          : $ext        - Extension of interest
#                                 '*' will be used by all
#                   $fname      - Name of the function
#                   $args       - Function Arguments
#
# Returns         : Nothing
#
sub RegisterSrcHook
{
    my $ext = shift;
    my ($fref) = @_;
    my $rtype = ref($fref) || 'not a reference';

    Error ("RegisterSrcHook called incorrectly",
           "Second argument MUST be a code reference",
           "It is a $rtype"
           ) unless ( $rtype eq 'CODE' );

    #
    #    Save the arguments by reference in an array
    #    The array will be processed later
    #
    push @{$MF_RegisterSrcHooks{$ext}}, \@_;
}


#-------------------------------------------------------------------------------
# Function        : MakefileHeader
#
# Description:    : Generate a "standard" makefile header.
#
#..

sub MakefileHeader
{
    my ($file, $desc, $by, @trailing) = @_;
    my ($diff);

    $diff = 0 if (($diff = ((80-5) - length($desc))) < 0);
    $desc .= ' ' . ('-' x $diff);

    print $file <<EOF;
#-- $desc
#
#                   -- Please do not edit this file. --
#
#       To do so may result in a system failure, in additional to any
#       changes made shall be overwritten.
#
# Created by $by
#         on $::CurrentTime
#
EOF
    #
    #   Print out the trailer
    #   This is an array. Place each entry on a new line
    #
    print $file $_ . "\n" for ( @trailing );
}

###############################################################################
# MakeFileGenerate:
#       This subroutine does all of the actual make file generation based
#       on information provided in the calls to the various public
#       interface routines.
#
# Inputs:
#
# Returns:
###############################################################################

my $MakefileGenerate_once = 0;
sub MakefileGenerate
{
    my $Makefile = "$ScmPlatform.mk";
    Debug( "MakefileGenerate: $Makefile" );

    #
    #   Nasty things happen when we generate a makefile twice
    #   Just warn the user and do nothing
    #   If its in the common makefile.pl then just ignore it
    #
    if ( $ScmProcessingRootMake )
    {
        Warning ("MakefileGenerate directive is not allowed in common makefile.pl");
        return;
    }

    if ( $MakefileGenerate_once )
    {
        Warning ("MakefileGenerate should only be called once.",
                 "Dir: $::Cwd");
        return;
    }
    $MakefileGenerate_once = 1;

    #
    #   Invoke all registered Makefile Generator functions
    #   These allow clever directives to collect information to be
    #   processed before the makefiles are created
    #
    while ( @MF_Generators )
    {
        Debug( "MakefileGenerate: Invoke RegisterMakefileGenerate function" );
        my ($fname, @args) = @{shift @MF_Generators};
        &$fname ( @args );
    }

    #
    #   Allow the toolset the opportunity to process all the collected data
    #   before the makefile is created. This is optional
    #
    my( $if ) = MakeIf::Factory();              # build interface
    $if->Preprocess();

    #
    #   If we have supressed the Toolset use, then we need to sanity test
    #   the use of the toolset
    #
    if ( $ScmNoToolsTest )
    {
        ReportError ("Building programs not supported with --NoToolset") if ( @PROGS || @TESTPROGS );
        ReportError ("Building libraries not supported with --NoToolset") if ( @LIBS || @MLIBS || @SHLIBS );
        ReportError ("Building projects not supported with --NoToolset") if ( %PROJECTS );
        ErrorDoExit();
    }

    #
    #   Auto package the 'descpkg' file
    #   If this makefile packages any files, then it can also package the descpkg file
    #   The descpkg will be piggybacked into all makefiles that do a package
    #
    if ( %PACKAGE_FILES || %PACKAGE_HDRS || %PACKAGE_CLSS || %PACKAGE_LIBS
                        || %PACKAGE_SHLIBS || %PACKAGE_PROGS || @PACKAGE_DIRS )
    {
        Src ('*', 'descpkg') unless ($SRCS{ descpkg });
        PackageFile ('*', 'descpkg');
    }

    #
    #   Some toolsets NEED a relative root
    #   Note: At the moment ScmRoot is relative anyway, thus this code
    #         does nothing
    #
    my $gbe_root = $::ScmRoot;
    if ( $::UseRelativeRoot )
    {
        $gbe_root =  RelPath( $::ScmRoot );
    }
    
    #
    #   Now start to create the makefile
    #
    ToolsetFiles::AddFile($Makefile);
    open( MAKEFILE, '>', $Makefile ) || Error( "Cannot create $Makefile" );
    ::MakefileHeader( *MAKEFILE,
                      'Auto-generated Platform Dependent Makefile',
                      "$ScmMakelib (version $ScmVersion)",
                      "# COPYRIGHT - VIX IP PTY LTD (\"VIX\"). ALL RIGHTS RESERVED.",
                      '#',
                      "# Located in $::Cwd",
                      "# Platform $::ScmPlatform",
                      '#' . ('-' x 79),
                      );

    #
    #   Ensure that some essential variables are set
    #
    print MAKEFILE <<EOF;
#
#   Validate essential environment variables
#
ifndef GBE_BIN
    \$(error ERROR: GBE_BIN is not available)
endif
ifndef GBE_PERL
    \$(error ERROR: GBE_PERL is not available)
endif
ifndef DEBUG
    \$(error ERROR: DEBUG is not defined)
endif
EOF


    print MAKEFILE <<EOF;

#
#   Basic definitions
#
GBE_ROOT      := $gbe_root
GBE_ROOT_ABS  := \$(abspath \$(GBE_ROOT))
GBE_HOST      := $::ScmHost
GBE_HOSTMACH  := $::GBE_MACHTYPE
GBE_TARGET    := $::ScmTarget
GBE_MACHTYPE  := $::ScmMachType
GBE_PLATFORM  := $::ScmPlatform
GBE_PBASE     := $::Pbase
GBE_TYPE      := \$(if \$(findstring 1,\$(DEBUG)),D,P)
EOF

MakePrint( "GBE_ARGS      := @ScmPlatformArgs\n" )
    if ( scalar @ScmPlatformArgs );

MakePrint( "GBE_PRODUCT   := $ScmProduct\n" )
    if ( $ScmProduct ne "" );

MakePrint( "GBE_OS_COMMON := $::BUILDINFO{$ScmPlatform}{OS_COMMON}\n" )
    if ( exists($::BUILDINFO{$ScmPlatform}{OS_COMMON}) );

    print MAKEFILE <<EOF;

SHELL           := \$(GBE_BIN)/sh
SHELLARGS       :=
EXTENDED_LINE   := \$(GBE_BIN)/extend.lst
export EXTENDED_LINE MAKE

MFLAGS           := --no-print --warn -r
BUILDNAME        := $::ScmBuildName
BUILDVER         := $::ScmBuildVersionFull
BUILDVERNUM      := $::ScmBuildVersion
BUILDPREVIOUSVER := $::ScmBuildPreviousVersion
DEPLOYPATCH      := $ScmDeploymentPatch
GBE_NOTOOLSTEST  := $ScmNoToolsTest
MAKEFILEUID      := $ScmMakeUid
export MAKEFILEUID

#
#   Ensure PWD is correctly set
#
PWD             := \$(CURDIR)
export PWD

#
#   NODEPEND    - Used to suppress generated dependency file checking
#                 Mostly done in jmake.pl
#   EXPERT      - Used to suppress dependency on this makefile
#
EOF

MakePrint( "EXPERT\t\t?= " . ($ScmExpert ? '1' : '0' ) . "\n" );
MakePrint( "NODEPEND\t?= 0\n" );

print MAKEFILE <<EOF;

#
#   SCM_MAKEFILE - The name of the file to depend upon
#                  Supressed in EXPERT mode
#
ifneq (\$(EXPERT),0)
SCM_MAKEFILE    :=
else
SCM_MAKEFILE    := $Makefile
endif
EOF

#
#   Setup the base directory for the packaging process
#   When building a deployable package the base directory is changed to match
#   that used by the deployment world. This is done so that the descpkg file
#   can be created in the correct location
#
my  $PKGDIR = "pkg/$::Pbase";
    $PKGDIR = "build/deploy" if ( $DEPLOYPACKAGE );
Verbose("Setting PKGDIR: $PKGDIR");

print MAKEFILE <<EOF;

#--------- Targets -------------------------------------------------------------

.PHONY:         default all build install package unpackage uninstall \\
                clean unbuild clobber deploy

default:
all:            install package deploy
build:          make_init generate install_hdr depend make_lib \\
                install_lib make_install_shlib make_prog install_class install_dirs
install:        build install_prog
package:        package_dirs package_files package_hdr package_lib package_shlib \\
                package_prog package_class
unpackage:      unpackage_class unpackage_prog unpackage_shlib \\
                unpackage_lib unpackage_hdr unpackage_files unpackage_dirs 
uninstall:      uninstall_dirs uninstall_class uninstall_prog uninstall_shlib \\
                uninstall_lib uninstall_hdr
clean:          make_clean unmake_prog unmake_test unmake_lib unobj \\
                undepend ungenerate rmlitter unmake_dir
unbuild:        clean uninstall
clobber:        unpackage unbuild clobberfiles clobberdirs
deploy:         install run_deploy

#--------- Macros --------------------------------------------------------------

OBJDIR          = \$(GBE_PLATFORM)\$(GBE_TYPE).OBJ
LIBDIR          = \$(GBE_PLATFORM).LIB
BINDIR          = \$(GBE_PLATFORM)\$(GBE_TYPE).BIN
CLSDIR          = classes\$(GBE_TYPE)

PKGDIR          = \$(GBE_ROOT)/$PKGDIR
INCDIR_PKG      = \$(PKGDIR)/include
LIBDIR_PKG      = \$(PKGDIR)/lib
BINDIR_PKG      = \$(PKGDIR)/bin
CLSDIR_PKG      = \$(PKGDIR)/classes
UTFDIR_PKG      = \$(GBE_ROOT_ABS)/$PKGDIR/utfResults

LOCALDIR        = \$(GBE_ROOT)/local
INCDIR_LOCAL    = \$(LOCALDIR)/inc
LIBDIR_LOCAL    = \$(LOCALDIR)/lib
BINDIR_LOCAL    = \$(LOCALDIR)/bin
CLSDIR_LOCAL    = \$(LOCALDIR)/classes
BINDIR_LOCAL_PATH = \$(GBE_ROOT_ABS)/local/bin/\$(GBE_PLATFORM)\$(GBE_TYPE)

INTERFACEDIR    = \$(GBE_ROOT)/$ScmInterface
INCDIR_INTERFACE= \$(INTERFACEDIR)/include
LIBDIR_INTERFACE= \$(INTERFACEDIR)/lib
BINDIR_INTERFACE= \$(INTERFACEDIR)/bin
CLSDIR_INTERFACE= \$(INTERFACEDIR)/classes

.SUFFIXES:              # Delete the default suffixes

EOF

    MakePrintList( \@DEFINES );
    MakeNewLine();

#-------------------------------------------------------------------------------
#
#
    MakeHeader ("Defines, flags and file sets");

    # Flags
    foreach my $opt ( sort keys %ScmCompilerOpts )
    {
        MakeDefEntry ( $opt, "=", $ScmCompilerOpts{$opt} );
    }

    MakeDefEntry( "CFLAGS",         "=", \@CFLAGS, \@CFLAGS_PROD, \@CFLAGS_DEBUG );
    MakeDefEntry( "CLINTFLAGS",     "=", \@CLINTFLAGS, \@CLINTFLAGS_PROD, \@CLINTFLAGS_DEBUG );
    MakeDefEntry( "CDEPENDFLAGS",   "=", \@CFLAGS, \@CFLAGS_PROD, \@CFLAGS_DEBUG );
    MakeDefEntry( "CXXFLAGS",       "=", \@CXXFLAGS, \@CXXFLAGS_PROD, \@CXXFLAGS_DEBUG );
    MakeDefEntry( "CXXLINTFLAGS",   "=", \@CXXLINTFLAGS, \@CXXLINTFLAGS_PROD, \@CXXLINTFLAGS_DEBUG );
    MakeDefEntry( "CXXDEPENDFLAG",  "=", \@CXXFLAGS, \@CXXFLAGS_PROD, \@CXXFLAGS_DEBUG );
    MakeDefEntry( "ASFLAGS",        "=", \@ASFLAGS, \@ASFLAGS_PROD, \@ASFLAGS_DEBUG );
    MakeDefEntry( "LDFLAGS",        "=", \@LDFLAGS, \@LDFLAGS_PROD, \@LDFLAGS_DEBUG );


#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Tool Search Path",
                "Extend the PATH seen by all the tools to include",
                "The tools/bin directories discovered in Packages" );
    my $put_PATH;
    my $put_LD_LIBRARY_PATH;

    MakePrint( "PATH := \$(BINDIR_LOCAL_PATH)$::ScmPathSep\$(PATH)\n" );
    $put_PATH = 1;

    for my $path ( ToolExtensionPaths() )
    {
        MakePrint( "PATH := $path$::ScmPathSep\$(PATH)\n" );
        $put_PATH = 1;

        if ( $::ScmHost eq "Unix" )
        {
        MakePrint( "LD_LIBRARY_PATH ?= \n" );
        MakePrint( "LD_LIBRARY_PATH := $path$::ScmPathSep\$(LD_LIBRARY_PATH)\n" );
            $put_LD_LIBRARY_PATH =1;
        }
    }

    #   Export the appropriate environment variables
    #   Note: Windows has an issue with PATH and Path
    #         Haven't got to the bottom of it yet, but it would appear that DLL
    #         searching uses Path and other stuff uses PATH. Not too sure how we
    #         end up with two (or more in the environment)
    #
    #
    if ( $put_LD_LIBRARY_PATH )
    {
        MakePrint( "export LD_LIBRARY_PATH\n" );
    }

    if ( $put_PATH )
    {
        MakePrint( "Path := \$(PATH)\n" );
        MakePrint( "export PATH Path\n" );
    }

#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Perl Module Search Path",
                "Extend the PERL5LIB seen by invocations of perl");

    my $perl_module_found;
    for my $path ( ToolExtensionPaths() )
    {
        if (my @results =  glob( "$path/*.pm"))
        {
            MakePrint( "PERL5LIB := $path$::ScmPathSep\$(PERL5LIB)\n" );
            $perl_module_found = 1;
        }
    }
    if ( $perl_module_found  )
    {
    MakePrint( "export PERL5LIB\n" );
    }

#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Include Search Paths",
                "Package Include Paths for header files and libraries" );

    MakeDefEntry( 'PINCDIRS'  , '=', '# includes');
    MakeDefEntry( 'PINCDIRS_INTERFACE', '=', '# Interface includes');
    MakeDefEntry( 'PINCDIRS_LOCAL', '=', '# Local includes');
    MakeDefEntry( 'PLIBDIRS'  , '=', '# libraries');
    MakeDefEntry( 'PLIBDIRS_INTERFACE', '=', '# Interface libraries');
    MakeDefEntry( 'PLIBDIRS_LOCAL', '=', '# Local libraries');

    for my $package (@{$::ScmBuildPkgRules{$ScmPlatform} })
    {
        #
        #   Skip the pseudo package that encapsulates the interface
        #   directory. Currently the makefiles do this in a different
        #   manner - to be resolved
        #
        #   Just comment out the lines so that the data is visible
        #   Its a hint to make use of the data
        #
        my $prefix = '';
        my $suffix = '';
        $prefix = '# ' if ( $package->{'TYPE'} eq 'build' );

        #
        #   The interface directory is a little bit different
        #
        my ($name,$base);
        if ( $package->{'TYPE'} eq 'interface' ) {
            $base = '$(INTERFACEDIR)';
            $name = 'Interface Directory';
            $suffix = '_INTERFACE';
        } else {
            $name = $package->{'NAME'} . '/' . $package->{'VERSION'};
            $base = $package->{'ROOT'};
        }

        my @doc;
        push (@doc, "From: $base");
        push (@doc, 'BuildPkgArchive via Interface' )if $package->{'TYPE'} eq 'build' ;

        MakeHeader ("Source: $name", @doc);

        #
        #   List include and library directories
        #   Note: 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 legacy packages where the Windows user
        #       guessed at the package format.
        #
        #       The conversion to a TruePath is done when ScmBuildPkgRules
        #       is created. Create once, use many time.
        #
        for my $type (qw (PINCDIRS PLIBDIRS) ) {
            for my $path ( @{$package->{$type}} ) {
                MakeDefEntry ( "$prefix$type$suffix", "+=", $base . $path);
            }
        }
    }

    #
    #   Local Paths
    #   These are a little bit special
    #
    MakeHeader ('Source: Local',  'From: Package Local');

    sub MakeLocalPaths 
    {
        my ($name, $root, $addRoot) = @_;
        my @pathlist;

        foreach my $tag ( $ScmPlatform, $ScmProduct, $ScmTarget ) {
            UniquePush( \@pathlist, "$root/$tag" ) if ( $tag );
        }

        #   Add the root directory too
        UniquePush( \@pathlist, $root) if $addRoot;

        MakeDefEntry ( $name , "+=", \@pathlist);
    }

    MakeLocalPaths ( 'PINCDIRS_LOCAL', '$(INCDIR_LOCAL)', 1 );
    MakeLocalPaths ( 'PLIBDIRS_LOCAL', '$(LIBDIR_LOCAL)', 0 );


#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Include Search Paths",
                "Local Include Paths",
                " LINKDIRS - Local include search path (short)",
                " INCDIRS  - Include search path (complete)",
                " NODEPDIRS - ",
                " SRCDIRS - ",
                " LIBDIRS - Library search path",

                );

    # Include search path
    #
    #   user-local
    #   local
    #   interface
    #       BuildPkgArchive
    #       LinkPkgArchive
    #   user-global
    #

    MakeDefEntry ( "\nLINCDIRS",    "= ", \@L_INCDIRS );                    # .. Local
    MakeDefEntry ( "LINCDIRS",      "+=", '$(PINCDIRS_LOCAL)');             # .. Sandbox local
    MakeDefEntry ( "LINCDIRS",      "+=", '$(PINCDIRS_INTERFACE)');         # .. Sandbox interface
    MakeDefEntry ( "LINCDIRS",      "+=", \@G_INCDIRS );                    # .. Global

    MakeDefEntry ( "INCDIRS",  "= ", '$(LINCDIRS)' );                       # Local
    MakeDefEntry ( "INCDIRS",  "+=", '$(PINCDIRS)' );                       # Package
    MakeDefEntry ( "LINCDIRS", "+=", \@S_INCDIRS );                         # System

    # Source search path

    MakeDefEntry( "\nNODEPDIRS",        "=", \@NODEPDIRS );

    MakeDefEntry( "\nSRCDIRS","= "  , [ @L_SRCDIRS, @G_SRCDIRS ] );         # Local
    MakeDefEntry ( "SRCDIRS", "+=" , '$(PINCDIRS)' );                       # Package
    MakeDefEntry ( "SRCDIRS", "+=" , \@S_INCDIRS );                         # System

    # Library search path
    #
    #   user-local
    #   local
    #   interface
    #       BuildPkgArchive
    #       LinkPkgArchive
    #   user-global
    #   
    #   Kludge Note:
    #       The LIBDIRS path needs an element with a directory seperator in it
    #       Needed by (broken) cmdfile o determine the file seperator to use
    #       

    MakeDefEntry( "\nLIBDIRS",  "= ", '$(LIBDIR)' );                    # User Local
    MakeDefEntry( "LIBDIRS",    "+=", \@L_LIBDIRS );                    # Local
    MakeDefEntry( "LIBDIRS",    "+=", '$(PLIBDIRS_LOCAL)' );            # Sandbox/local
    MakeDefEntry( "LIBDIRS",    "+=", '$(PLIBDIRS_INTERFACE)' );        # Sandbox/interface
    MakeDefEntry( "LIBDIRS",    "+=", '$(LIBDIR_INTERFACE)' );          # Kludge. See note above
    MakeDefEntry( "LIBDIRS",    "+=", \@G_LIBDIRS );                    # Global
    MakeDefEntry( "LIBDIRS",    "+=", '$(PLIBDIRS)' );                  # Package
    MakeDefEntry( "LIBDIRS",    "+=", \@S_LIBDIRS );                    # System

#-------------------------------------------------------------------------------
#
#   Subdir creation and deletion
#   Creation is done on the fly
#   Deletion is done AFTER the toolset functions have been invoked to create the
#   build artifacts so that the toolsets can create directories too

    MakeHeader ("Subdir creation");
    CreateMkdirRules();
    MkdirRule( '$(OBJDIR)', 'OBJDIR', '--Extra=depend,depend.err' );    # Object build directory
    MkdirRule( '$(OBJDIR)/'.$_ ) foreach (@SHLIBS);                     # Shared library build directory
    MkdirRule( '$(LIBDIR)', 'LIBDIR' );                                 # Library directory
    MkdirRule( '$(BINDIR)', 'BINDIR' );                                 # Binary directory

    #
    #   Create a directory for library merge tool to work within
    #
    MkdirRule( "\$(MLIBDIR)", 'MLIBDIR', '--Path=$(GBE_PLATFORM).MRG', '--RemoveAll' ) if (@MLIBS);
    
#-------------------------------------------------------------------------------
#   Generate rules and recipes to create all the toolset specific parts
#   This is done fairly early to allow the toolsets to extend various
#   definitions that may be used later in the makefile construction
#
    MakeHeader ("Construct Programs");

    foreach my $i ( @PROGS )
    {
        my $pProg = $PROGS->Get($i);
        my $pArgs = $pProg->getItems('ARGS');
        my $pObjs = $pProg->getItems('OBJS');
        my $pLibs = $pProg->getItems('LIBS');

        #
        #   Create a list of program object files
        #
        push @PROGOBJS, @$pObjs;

        MakePrint( "#---- (${i})\n\n" );
        if ( $ScmToolsetProgDependancies )
        {
            #
            #   Original style Prog Interface
            #   Write some dependency information here and some in the toolset
            #   Problems:
            #       1) Errors in library dependency generation will be
            #          reported after all the object files have been created
            #          Thus the error message and the make-stop are seperated
            #          by many,many lines of output. This makes it difficult
            #          to see the error.
            #
            #       2) Lack of Flexability
            #
            MakeEntry( "\$(BINDIR)/$i$::exe: ", "", "\\\n\t\t", ".$::o ", @$pObjs );
        }
        else
        {
            #
            #   New Style Prog Interface
            #   The toolset does it all
            #
            #   Flag the progam packaging as a placeholder.
            #   The toolset will replace/update it.
            #
            PackageProgRemoveFiles( $i );
        }

        $if->LD    ( $i, $pArgs, $pObjs, $pLibs );
        $if->LDLINT( $i, $pArgs, $pObjs, $pLibs );
    }

#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Construct Test Programs");
    foreach my $i ( @TESTPROGS )
    {
        my $pProg = $TESTPROGS->Get($i);
        my $pArgs = $pProg->getItems('ARGS');
        my $pObjs = $pProg->getItems('OBJS');
        my $pLibs = $pProg->getItems('LIBS');

        #
        #   Create a list of program object files
        #
        push @TESTPROGOBJS, @$pObjs;
        
        MakePrint( "#---- (${i})\n\n" );
        if ( $ScmToolsetProgDependancies )
        {
            MakeEntry( "\$(BINDIR)/$i$::exe: ", "", "\\\n\t\t", ".$::o ", @$pObjs );
        }
        else
        {
            PackageProgRemoveFiles( $i );
        }

        $if->LD    ( $i, $pArgs, $pObjs, $pLibs );
        $if->LDLINT( $i, $pArgs, $pObjs, $pLibs );
    }

#-------------------------------------------------------------------------------
#
#
    MakeHeader ("Transfer Scripts to BINDIR");
    foreach my $i ( sort ( values %SCRIPTS ))
    {
        my $tname = "\$(BINDIR)/" . StripDir( $i );


        MakePrint( "$i:\t\tmakefile.pl\n" .
            "\t\$(XX_PRE)if [ ! -f \"$i\" ]; then echo 'Script [$i] not found'; exit 2; fi\n\n" );

        #
        #   Create a rule to copy the script into the BIN directory
        #   Mark the script as executable - It can't hurt and its there
        #   to be run as part of a test.
        #
        MakePrint "$tname:\t\$(GBE_BINDIR) $i\n" .
                  "\t\$(XX_PRE)\$(cp) -f $i $tname\n" .
                  "\t\$(XX_PRE)\$(chmod) -f +wx $tname\n\n"
    }

#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Construct Libraries");
    foreach my $i ( @LIBS )
    {
        my $pLib  = $LIBS->Get($i);
        my $pArgs = $pLib->getItems('ARGS');
        my $pObjs = $pLib->getItems('OBJS');

        unless ( $ScmToolsetNillLibSrc )
        {
            Error ("Library has no component objects: $i")
                if ( scalar @$pObjs <= 0 );
        }

        MakePrint "#---- (${i})\n\n";
        $if->AR(     $i, $pArgs, $pObjs, $pLib);
        $if->ARLINT( $i, $pArgs, $pObjs, $pLib );
    }

#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Construct Merged Libraries");
    sub MlibEntry
    {
        my( $mlib, $plib, $pLibs ) = @_;
        my @flib;

        MakePrint '$(LIBDIR)/' . GenLibName($mlib) . ":";
        foreach my $lib ( @$pLibs )
        {
            #
            #   Each library name may contains one embedded option which
            #   specifies the source directory
            #       libname[,--Option | BaseSubdir]
            #
            my ($slib, $sdir) = split( ',', $lib );
            my $mode;

            #
            #   By default the librares are pulled from LOCAL unless the
            #   library is built in this directory, in which case it will
            #   be used.
            #
            $sdir = ( $LIBS->Get($slib) ) ? '--Here' : '--Local'
                unless ( $sdir );

            #
            #   --Interface     - Pull library from the interface directory
            #   --Local         - Pull library from the local directory
            #   --SubDir=xxxx   - Pull library from specified subdirectory
            #   --Here          - Pull from local directory if built locally
            #   otherwise       - Pull library from specified subdirectory
            #
            if ($sdir eq '--Interface') {
                $sdir = '$(LIBDIR_INTERFACE)/$(GBE_PLATFORM)';


            } elsif ($sdir eq '--InterfacePlain') {
                $sdir = '$(LIBDIR_INTERFACE)/$(GBE_PLATFORM)';
                $mode = 1;
                
            } elsif ( $sdir eq '--Local') {
                $sdir = $PackageInfo{'Lib'}{'IBase'} .  # Base of Installed libs
                        $PackageInfo{'Lib'}{'Dir'};     # Default subdir

            } elsif ( $sdir =~ m~^--SubDir=(.*)~ ) {
                $sdir = $1 . '/$(LIBDIR)';

            } elsif ( $sdir eq '--Here') {
                $sdir = '$(LIBDIR)';

            } else {
                $sdir .= '/$(LIBDIR)';
            }

            MakePrint " \\\n\t\t${sdir}/" . GenLibName($slib, $mode);
            push @flib, "${sdir}/${slib}";
        }
        return \@flib;
    }

    foreach my $i ( @MLIBS )
    {
        my $pLib  = $MLIBS->Get($i);
        my $pArgs = $pLib->getItems('ARGS');
        my $pLibs = $pLib->getItems('LIBS');

        MakePrint "#---- (${i})\n\n";
        
        unless ( defined &ToolsetARMerge )
        {
            Warning( "Merging of libraries not supported in this toolset yet" );
            Warning( "MergeLibrary: \"$i\" will not be created" );
        }
        else
        {
            #
            #   Create the dependency rule
            #       Target library : source library list
            #           Recipe - generated by the toolset
            #
            foreach ( @$pArgs )
            {
                Warning( "Ignoring unknown argument to MergeLibrary. $_" );
            }
            $pLibs = MlibEntry( $i, $pLib, $pLibs );
            $if->ARMerge( $i, $pArgs, $pLibs, $pLib );
        }
    }

#-------------------------------------------------------------------------------
#   
#
    MakeHeader ("Construct Shared Libraries");

    foreach my $i ( @SHLIBS )
    {
        my $pShlib  = $SHLIBS->Get($i);
        my $pArgs = $pShlib->getItems('ARGS');
        my $pObjs = $pShlib->getItems('OBJS');
        my $pLibs = $pShlib->getItems('LIBS');
        my $version = $pShlib->{VERSION};

        $if->SHLD    ( $i, $pArgs, $pObjs, $pLibs, $version );
        $if->SHLDLINT( $i, $pArgs, $pObjs, $pLibs, $version );
    }

#-------------------------------------------------------------------------------
#   Construct Objects
#   For each object within OBJSOURCE construct the following:
#
#   $(OBJDIR)/object-name:     source-name [makefile]
#       Toolset ...
#
#   
#
    MakeHeader ("Construct Objects");

    foreach my $i ( sort keys %OBJSOURCE )
    {
        my( $src, $sname, $ext, $type, @args );

        $src  = $OBJSOURCE{ $i };
        $sname = StripDir( $src );
        $ext  = StripFile( $src );
        $ext = lc($ext)
            if ( $::ScmHost ne "Unix" );
        $type = ($ScmSourceTypes{ $ext } || '')
            unless (( $type = $SRC_TYPE{ $sname }) );

        #
        #   Object source is an object file
        #   No need the generate the object, just create makefile rule
        #   [ddp] Not too sure how we get here
        #
        if ( $ext eq ".$::o" )
        {
            MakePrint "$src:";
            MakePrint " \$(SCM_MAKEFILE)";
            MakeNewLine();
            next;
        }

        #
        #   Need to create object file
        #
        @args = split( /$;/, $SRC_ARGS{ StripDir( $sname ) } )
            if $SRC_ARGS{ $sname };

        push( @args, "--Shared" )
            if ( exists $SHOBJ_LIB{$i} );

        #
        #   Convert relative paths to absolute paths if required by the
        #   toolset. Some compilers need ABS paths to generate nice debug
        #   information.
        #
        $src = AbsPath($src)
            if ( $UseAbsObjects );

        #
        #   Extract any user specified dependancies
        #   These will be added to the dependency list
        #
        my @dlist;
        @dlist = split( /$;/, $SRC_DEPEND{$sname} )
            if ( exists $SRC_DEPEND{$sname} );

        #
        #   Create the dependency part of the object rule
        #   The source file MUST be the first dependent recipes
        #   may assume that $< is the name source file
        #
        MakeEntry( "\$(OBJDIR)/$i.$::o: $src \$(SCM_MAKEFILE)", "", " \\\n\t", "", @dlist );

        if ( $type eq ".c" ) {
            $if->CC(  $src, $i, \@args );

        } elsif ( $type eq ".cc" ) {
            $if->CXX( $src, $i, \@args );

        } elsif ( $type eq ".asm" ) {
            $if->AS( $src, $i, \@args );

        } else {
            $if->EXT( $src, $i, \@args ) ||
                Warning( "Don't know how to build '$ext' images' for $src, $i" );
            MakeNewLine();
        }
    }

#-------------------------------------------------------------------------------
#   Construct Projects
#   Construct toolset specific projects
#
    MakeHeader ("Construct Projects");
    while ( my($project, $entry) = each %PROJECTS)
    {
        $if->PROJECT( $entry );
    }

#-------------------------------------------------------------------------------
#   Automated tests
#
    MakeHeader ("Automated tests");

    my $idx = 0;
    my @copy_set = ();
    
    foreach my $pEntry ( @TESTS_TO_RUN )
    {                                           # Foreach test
        $idx++;
        $pEntry->{'index'} = $idx;
        $pEntry->{'test_name'} = "run_test_$idx";
        $pEntry->{'echoname'} = $pEntry->{'utfname'} || '';  
        $pEntry->{'utfname'} = $pEntry->{'test_name'} unless defined $pEntry->{'utfname'};  

        #
        #   If the test is being run within a 'FrameWork' then the underlying
        #   toolset must instantiate the frame work.
        #
        #   This may change. Perhaps frameworks shouldn't be a part of the
        #   toolset. Perhaps they should be standalone. May change
        #
        if ( $pEntry->{framework} )
        {
            $if->TESTFRAMEWORK( $pEntry );
        }

        #
        #   Create a rule to run the test
        #

        my $tdir_alias = $pEntry->{'testdir'};
        my $tdir = '$(' . $tdir_alias . ')';

        my $test_name = $pEntry->{'test_name'};
        push @TESTPROJECT_TO_URUN, $test_name;
        push @TESTPROJECT_TO_ARUN, $test_name if     ($pEntry->{'auto'} );

        my $tprog = $tdir . '/' . StripDir( $pEntry->{'prog'} );

        #
        #   Determine the maximum time that the automated test should run
        #       Default is 30 minutes
        #   Non-auto tests are not time limited
        #       
        my $timeout = '';
        if ($pEntry->{'auto'})
        {
            $timeout = 'timeout -Time:' . ($pEntry->{'maxtime'} || '30m') . ' ';
        }
        
        my $me = MakeEntry::New( *MAKEFILE, $test_name, '--Phony' );

        #
        #   Export GBE_UTF... for the duration of the test
        #
        $me->AddDefn('export GBE_UTFNAME', $pEntry->{'utfname'});
        $me->AddDefn('export GBE_UTFUID', '$(MAKEFILEUID)' . '_' . $pEntry->{'index'});
        $me->AddDefn('export GBE_UTFFILE','$(UTFDIR_PKG)/$(GBE_PLATFORM)-$(GBE_TYPE)-$(GBE_UTFUID)' . '.xml');
        $me->AddDefn('export GBE_UTFTEST','TEST-$(GBE_UTFNAME)-$(GBE_TYPE)-$(GBE_UTFUID)' );

        #
        #   A bit of a kludge for 'googletest'
        #       If we have another kludge like then then consider placing this work into a module based on the filter name
        #       with some sort of interface to allow the ENVvars and format and command line args to be massaged
        #       
        #   For googletest
        #       Set EnvVar and then post process with junit
        #
        if ($pEntry->{'utfformat'})
        {
            if ($pEntry->{'utfformat'} eq 'gtest') {
                $pEntry->{'utfformat'} = 'junit';
                $me->AddDefn('export GTEST_OUTPUT ', 'xml:${GBE_UTFTEST}.xml');
            }
        }

        # Workaround for dirsep under windows when being wrapped in a timeout
        $me->AddDefn('dirsep', '$(dirsep)$(dirsep)') if ($timeout && ($::ScmHost ne "Unix"));

        $me->AddDependancy( "\$(GBE_$tdir_alias)" );
        $me->AddDependancy( "\$(INTERFACEDIR)/set_$::ScmPlatform.sh" );
        $me->AddDependancy( $tprog ) if $pEntry->{'copyprog'};
        $me->AddDependancy( @{ $pEntry->{'copyin' } } );
        $me->AddDependancy( map { $tdir . '/' . StripDir($_) } @{ $pEntry->{'copyonce' } } );
        $me->AddDependancy( @{ $pEntry->{'preq'} } );
        $me->RecipePrefix ('$(XX_PRE)');
        $me->RecipeWrapper( $timeout . 'sh -c \'', '\'') if $timeout;
        $me->RecipeComment( "------ Running test [$idx] $pEntry->{'echoname'} ..." );

        #
        #   Create package utfResults directory
        #       Simplify use of the file
        #
        $me->AddShellRecipe ( 'mkdir -p $(UTFDIR_PKG)' );

        #
        #   Extend the PATH seen by the script to include the local/bin directory
        #   Allows programs and tests that have been created elsewhere in the component
        #   to be accessed within the script.
        #
        $me->AddShellRecipe ( ". \$(INTERFACEDIR)/set_$::ScmPlatform.sh" );

        #
        #   Copy in the files that we need
        #
        foreach my $file ( @{$pEntry->{'copyin'}} )
        {
            my $dst = $tdir . '/' . StripDir( $file );
            UniquePush( \@COPYIN, $dst );
            UniquePush( \@copy_set, $file );
            $me->AddShellRecipe ( "\$(cp) -f $file $dst" );
            $me->AddShellRecipe ( "\$(chmod) -f +wx $dst" );
        }

        #
        #   Insert any FrameWork Recipe bits
        #
        $me->AddShellRecipe ( @{$pEntry->{'ShellRecipe'}} );

        #
        #   Insert command
        #       Save result code to a known file
        #
        $me->AddShellRecipe ( "cd $tdir" );
        $me->AddShellRecipe ( ["GBE_TYPE=\$(GBE_TYPE)",
                               "GBE_HOST=\$(GBE_HOST)",
                               "GBE_ROOT=\$(GBE_ROOT_ABS)",
                               "PATH=.\\$::ScmPathSep\$(BINDIR_LOCAL_PATH)\\$::ScmPathSep\$\$PATH",
                               $pEntry->{'command'},
                               @{$pEntry->{'args'}},
                               ] , 
                               'echo $$? > utf.$${GBE_UTFUID}.rc' );

        #
        #   Create the basic command line for 'jats_runutf'
        #       Use the simplistic 'internal' filter unless the user has provided one
        #
        my @cmdline;
        push @cmdline, '--';
        push @cmdline, '$(VERBOSE_OPT)';
        push @cmdline, '-filter=' . ($pEntry->{'utfformat'} || 'internal');
        push @cmdline, '-root=$(GBE_ROOT_ABS)' ;
        push @cmdline, "-dir=$tdir";
        push @cmdline, '-target=$(GBE_PLATFORM)';
        push @cmdline, '-pkgdir=$(PKGDIR)';
        push @cmdline, '-local=$(LOCALDIR)';
        push @cmdline, '-interface=$(INTERFACEDIR)';
        push @cmdline, "-rcfile=$tdir/utf.\$\${GBE_UTFUID}.rc";
        push @cmdline, map { '-arg='. $_ } @{$pEntry->{'utfargs' }};
        
        #
        #   Insert commands to post process the test results according to the specified formatter
        #
        $me->NewSection     ();
        $me->SectionIfDef   ('UTF_POSTPROCESS');
        $me->RecipePrefix   ('$(XX_PRE)');
        $me->AddOneRecipe   ( "\$(GBE_PERL) -Mjats_runutf -e processUtf", @cmdline );

        $me->Print();


        #
        #   Create entries to handle the copy-once files
        #
        foreach my $file ( @{ $pEntry->{'copyonce' } } )
        {
            my $tname = $tdir . '/' . StripDir($file);
            my $me = MakeEntry::New( *MAKEFILE, $tname  );
            $me->AddDependancy( $file );
            $me->AddRecipe ( "\$(call CopyFile,CopyIn,$tname,$file,$tdir,)"  );
            $me->Print();

            UniquePush( \@COPYIN, $tname );
            UniquePush( \@copy_set, $file );
            
        }
    }

    #
    #   Generate sanity test for each copyin script
    #   Simply to provide a nice error message for generated scripts
    #   that do not exist at run-time
    #
    test_copy_in:
    foreach my $i ( @copy_set )
    {
        next if ( $SCRIPTS{$i} );
        foreach (  @SHLIB_TARGETS )
        {
            next test_copy_in if ( $i eq $_ );
        }
        MakePrint( "\n$i:\t\tmakefile.pl\n" .
            "\t\@if [ ! -f \"$i\" ]; then echo 'ERROR: CopyIn Script [$i] not found'; exit 2; fi\n" );
    }

#-------------------------------------------------------------------------------
#   Deploy rules
#
MakeHeader ("Deploy Rules");

print MAKEFILE <<EOF;
.PHONY:         run_deploy
EOF

#
#   Build up the deployfile.pl command line from the available pieces
#
my $command_file = "";
my @command_line;

if ( %DEPLOYPACKAGE )
{
    $command_file = $DEPLOYPACKAGE{'cmdfile'};

    push @command_line, "\$(XX_PRE)\$(GBE_PERL) -w $command_file";
    push @command_line, "-r \"\$(GBE_ROOT)\"";
    push @command_line, "-n \"$DEPLOYPACKAGE{'name'}\"";
    push @command_line, "-d \"$DEPLOYPACKAGE{'dir'}\"";
    push @command_line, "-v \"\$(BUILDVER)\"";
    push @command_line, "-t \"\$(GBE_TYPE)\"";
    push @command_line, "-o \"\$(BUILDPREVIOUSVER)\"";
    push @command_line, "-m \"\$(GBE_PLATFORM)\"";
    push @command_line, "-g \"\$(GBE_TARGET)\"";
    push @command_line, "-k \"\$(GBE_PRODUCT)\""        if ( $ScmProduct );
    push @command_line, "-p \"\$(DEPLOYPATCH)\""        if ( $ScmDeploymentPatch );

}

MakeEntry( "run_deploy:\t$command_file\n", "\n", "\t\t", " \\\n", @command_line );

#-------------------------------------------------------------------------------
#   Custom Rules
#
    MakeHeader ("Custom Rules");
    MakePrintList ( \@RULES );

#-------------------------------------------------------------------------------
#   Generated Files
#
    MakeHeader ("Generated Files");
    MakePrint ("\n.PHONY: phony_generate\n\n" );
    my $generateMustAddTestPostProcess;
    foreach my $i ( @GENERATE_FILES )
    {
        my $gen_tag = $i->{'index'};
        my $genName = 'generate_' . $gen_tag;

        my $me = MakeEntry::New( *MAKEFILE );
        $me->AddComment    ('Generate Files');
        $me->AddName(@{$i->{'gen'}});

        #
        #   Generate user-provided recipe names to allow the rule to be called by name.
        #
        my $recipeTag = $i->{'recipeTag'} || '';
        my $recipeName = '';
        my $recipeCleanName = '';

        if ($recipeTag) {
            $recipeName = $recipeTag;
            $recipeCleanName = 'clean_' . $recipeTag;

            # for 'jats make help'
            $ScmRecipeTags{$recipeTag} = defined $i->{'clean'} ? 1 : 0;

            $me->Phony() ;
            $me->AddName($recipeName);
        }

        #
        #   If predelete is enabled, then create a list of files to delete
        #
        if ( $i->{'predelete'}  ) {
            $me->AddDefn("generate_gen_$gen_tag", join(' ', @{$i->{'gen'}} )  );
        }

        my $target = join (' ', @{$i->{'gen'}}, $recipeName);

        #
        #   If a UnitTest then insert runtime defs
        #
        if ($i->{'isautf'})
        {
            my $test_name = $i->{'gen'}[0];
            $generateMustAddTestPostProcess = 1;

            push @TESTPROJECT_TO_URUN, $test_name;
            push @TESTPROJECT_TO_ARUN, $test_name if ($i->{'utfauto'} );

            $me->AddComment    ('  This is a Unit Test');
            $me->AddDefn('export GBE_UTFNAME', $test_name );
            $me->AddDefn('export GBE_UTFUID', 'G$(MAKEFILEUID)' . '_' . $i->{'index'});
            $me->AddDefn('export GBE_UTFFILE','$(UTFDIR_PKG)/$(GBE_PLATFORM)-$(GBE_TYPE)-$(GBE_UTFUID)' . '.xml');
            $me->AddDefn('export GBE_UTFTEST','TEST-$(GBE_UTFNAME)-$(GBE_TYPE)-$(GBE_UTFUID)' );
        }

        #
        #   Generate the basic generate rule and recipe
        #   together with the prerequisites
        #
        unless ( $i->{'clean'} && $i->{'shell'} )
        {
            $me->AddDependancy(@{$i->{'preq'}});
            $me->AddDependancy("phony_generate") if $i->{'preq_sus'};
            $me->AddDependancy("\$(SCM_MAKEFILE)");
            $me->RecipePrefix   ('$(AA_PRE)');
            $me->AddRecipe("\$(echo) '[$i->{'text'}] generating..'");
            $me->RecipePrefix   ('$(XX_PRE)');
            $me->AddRecipe("\$(call RmFiles,generate_gen_$gen_tag)") if ( $i->{'predelete'}  );

            if ($i->{'isautf'}) {
                my $filter = $i->{'utfformat'} || 'internal';
                my $uargs = join(' ', map { '-arg='. $_ } @{$i->{'utfargs' }});
                my $tdir = $i->{'utfdir'} || '.';
                $me->AddShellRecipe (  [
                                       "PATH=.\\$::ScmPathSep\$(BINDIR_LOCAL_PATH)\\$::ScmPathSep\$\$PATH",
                                       "\$(call $genName,)"
                                       ] , 
                                       'echo $$? > ${INTERFACEDIR}/utf.$${GBE_UTFUID}.rc',
                                       "\$(call UtfPostProcess,$filter,$tdir,$uargs)" );

            } else {
                $me->AddRecipe("\$(call $genName,)");
            }
        }
        $me->Print();

        #
        #   Generate 'clean' rules and recipes
        #
        if ( $i->{'clean'} )
        {
            my $me = MakeEntry::New( *MAKEFILE, "clean_$genName", '--Phony' );
            $me->AddName($recipeCleanName) if $recipeCleanName;
            $me->RecipePrefix('$(XX_PRE)');
            $me->AddRecipe("-\$(call $genName,$i->{'clean'})");
            $me->Print();
        }

        #
        #   Define a function to contain the body of the generation call
        #   The first argument will be a 'clean' argument
        #
        my $md = MakeEntry::New( *MAKEFILE, $genName, '--Define' );
        if ( $i->{'shell'} ) {
            $md->AddShellRecipe( @{$i->{'toolargs'}} );

        } else {
            $md->AddOneRecipe( $i->{'tool'} . ' $1' ,@{$i->{'toolargs'}} );
        }
        $md->Print();
    }

    if ($generateMustAddTestPostProcess)
    {
        #
        #   Define the UTF post processing
        #   Define amacro thattakes two arguments
        #       $1 - name of the filter
        #       $2 - Directory to process
        #       $3 - additional arguments
        #

        #
        #   Create the basic command line for 'jats_runutf'
        #       Use the simplistic 'internal' filter unless the user has provided one
        #
        my @cmdline;
        push @cmdline, '--';
        push @cmdline, '$(VERBOSE_OPT)';
        push @cmdline, '-filter=$1';
        push @cmdline, '-root=$(GBE_ROOT_ABS)' ;
        push @cmdline, '-dir=$2';
        push @cmdline, '-target=$(GBE_PLATFORM)';
        push @cmdline, '-pkgdir=$(PKGDIR)';
        push @cmdline, '-local=$(LOCALDIR)';
        push @cmdline, '-interface=$(INTERFACEDIR)';
        push @cmdline, '-rcfile=${INTERFACEDIR}/utf.$${GBE_UTFUID}.rc';
        push @cmdline, '$3';

        #
        #   Insert commands to post process the test results according to the specified formatter
        #
        my $me = MakeEntry::New( *MAKEFILE, 'UtfPostProcess', '--Define' );
        $me->AddComment    ('Post Process a UNIT TEST');
        $me->AddComment    ('  arg1 - utffile name');
        $me->AddComment    ('  arg2 - Directory to start scan for Unit Test Results');
        $me->AddComment    ('  arg3 - Additional arguments to the filter');
        $me->SectionIfDef  ('UTF_POSTPROCESS');
        $me->AddOneRecipe  ("\$(GBE_PERL) -Mjats_runutf -e processUtf", @cmdline );
        $me->Print();
    }


#-------------------------------------------------------------------------------
#   Toolset Post Processing
#   Allow the toolset to perform any post processing, before we finally write
#   out any definitions.
#
#   We will not interprete any more user directives, but new stuff may get added
#
#
MakeHeader ("Toolset Post Processing");
$if->Postprocess();

################################################################################
#   All interactions with the toolset are now complete
#   All lists are now complete
#
#   Can now create internal definitions
#   
################################################################################

    #
    #   Would be nice if this would work
    #   Unfortunatelty we still need $if for the CCDEPENDS and CTAGS work
    #   These must be defined AFTER the definitions
    #
    #   Ideally we should construct our makefile in sections
    #   and then we can order the sections when we write them out
    #
#$if = 0;                            # Ensure the MakeIf class is not called
                                     # If this file is modified

#-------------------------------------------------------------------------------
#   Sources
#
MakeHeader  ( "Sources");
MakeDefEntry( "CSRCS",      "=",  \@CSRCS );
MakeDefEntry( "CXXSRCS",    "=",  \@CXXSRCS );
MakeDefEntry( "ASSRCS",     "=",  \@ASSRCS );

#-------------------------------------------------------------------------------
#   Generated, Installed and Packaged components
#
MakeHeader  ("Generated, Installed and Packaged components");
MakeDefEntry( "INITS",           "=",  \@INITS )   if ( @INITS );
MakeDefEntry( "GENERATED",       "=",  \@GENERATED ) if ( @GENERATED );
MakeDefEntry( "GENERATED_NOTSRC","=",  \@GENERATED_NOTSRC ) if ( @GENERATED_NOTSRC );
MakeDefEntry( "GENERATEDCLEAN",  "=",  CreateNameList( 'clean_generate_', '', ListCleanGenerated() ));
MakeDefEntry( "INSTALL_HDRS",    "=",  \%INSTALL_HDRS ) if ( %INSTALL_HDRS );
MakeDefEntry( "INSTALL_CLSS",    "=",  \%INSTALL_CLSS ) if ( %INSTALL_CLSS );
MakeDefEntry( "OBJS",            "=", CreateNameList( '$(OBJDIR)/', ".$::o", \@OBJS) );
MakeDefEntry( "SHOBJS",          "=", CreateNameList( '$(OBJDIR)/', ".$::o", \%SHOBJ_LIB ));
MakeDefEntry( "PROGOBJS",        "=", CreateNameList( '', ".$::o", \@PROGOBJS ));
MakeDefEntry( "TESTPROGOBJS",    "=", CreateNameList( '', ".$::o", \@TESTPROGOBJS ));
MakeDefEntry( "LIBS",            "=", $LIBS->AllTargets() ) if ($::a);
MakeDefEntry( "MLIBS",           "=", $MLIBS->AllTargets() ) if ($::a);
MakeDefEntry( "SHNAMES",         "=", \@SHLIBS );
MakeDefEntry( "SHDIRS",          "=", CreateNameList( '$(OBJDIR)/', "", \@SHLIBS ));
MakeDefEntry( "SHLIBS",          "=", \@SHLIB_TARGETS );
MakeDefEntry( "SCRIPTS",         "=", CreateNameList( '$(BINDIR)/', "", \%SCRIPTS ));
MakeDefEntry( "COPYIN",          "=", \@COPYIN );
MakeDefEntry( "PROGS",           "=", $PROGS->AllTargets() );
MakeDefEntry( "PROGS_EXTRA",     "=", \@PROGS_EXTRA );
MakeDefEntry( "TESTPROGS",       "=", $TESTPROGS->AllTargets());
MakeDefEntry( "LINTLIBS",        "=", CreateNameList( 'lib_', '_lint', \@LINTLIBS ));
MakeDefEntry( "LINTSHLIBS",      "=", CreateNameList( 'shlib_', '_lint', \@LINTSHLIBS ));
MakeDefEntry( "LINTPROGS",       "=", CreateNameList( 'prog_', '_lint', \@PROGS ));
MakeDefEntry( "LINTPROGS",      "+=", CreateNameList( 'prog_', '_lint', \@TESTPROGS ));
MakeDefEntry( "PROJECTS",        "=", CreateNameList( 'Project_', '', ListGeneratedProjects(1) ));
MakeDefEntry( "PROJECTSGEN",     "=", CreateNameList( 'Project_', '', ListGeneratedProjects(0) ));
MakeDefEntry( "PROJECTSCLEAN",   "=", CreateNameList( 'ProjectClean_', '', \%PROJECTS ));

MakeDefEntry( "UNITTESTS",       "=", \@TESTPROJECT_TO_URUN );
MakeDefEntry( "AUTOUNITTESTS",   "=", \@TESTPROJECT_TO_ARUN );

MakeDefEntry( "AUTOUNITTESTS_PRE",    "=", \@TOOLSET_UTF_PRE );
MakeDefEntry( "AUTOUNITTESTS_POST",   "=", \@TOOLSET_UTF_POST );
MakeDefEntry( "AUTOUNITTESTS_COLLATE","=", \@TOOLSET_UTF_COLLATE );


MakeHeader ("Toolset components");
MakeDefEntry( "USERGENERATED",        "=", \@USERGENERATED )    if ( @USERGENERATED );
MakeDefEntry( "TOOLSETGENERATED",     "=", \@TOOLSETGENERATED ) if ( @TOOLSETGENERATED );
MakeDefEntry( "TOOLSETOBJS",          "=", \@TOOLSETOBJS )      if ( @TOOLSETOBJS );
MakeDefEntry( "TOOLSETLIBS",          "=", \@TOOLSETLIBS )      if ( @TOOLSETLIBS );
MakeDefEntry( "TOOLSETPROGS",         "=", \@TOOLSETPROGS )     if ( @TOOLSETPROGS );
MakeDefEntry( "TOOLSETDIRS",          "=", \@TOOLSETDIRS )      if ( @TOOLSETDIRS );
MakeDefEntry( "TOOLSETDIRTREES",      "=", \@TOOLSETDIRTREES )  if ( @TOOLSETDIRTREES );
MakeDefEntry( "TOOLSETCLOBFILES",      "=", \@CLOBBERFILES )    if ( @CLOBBERFILES );
MakeDefEntry( "TOOLSETCLOBDIRS",       "=", \@CLOBBERDIRS )      if ( @CLOBBERDIRS );


#--------- Determine compiler flag groups to use ----------------------------
#
#   Allows the compiler options to be controlled for both the debug and
#   the production builds. Allows control over
#       1) Optimisations
#       2) Debug Information
#
MakeHeader ("Determine compiler flag groups to use");

print MAKEFILE <<EOF;

ifneq "\$(DEBUG)" "1"
USE_OPTIMISE    := \$(PROD_USE_OPTIMISE)
USE_DEBUGINFO   := \$(PROD_USE_DEBUGINFO)
else
USE_OPTIMISE    := \$(DEBUG_USE_OPTIMISE)
USE_DEBUGINFO   := \$(DEBUG_USE_DEBUGINFO)
endif

EOF

#-------------------------------------------------------------------------------
#   Source browsing tools
#
MakeHeader ("Source browsing tools");
    print MAKEFILE <<EOF;
.PHONY:                 ctags
ctags:
EOF
    $if->CTAGS()
        if (@CSRCS || @CXXSRCS);

#-------------------------------------------------------------------------------
#   Depend
#   If we are build C or C++ source files then create rules and recipes
#   to invoke a dependency generator.
#
#   NODEPEND is used to disable, at make-time, the dependency generation
#   and inclusion process.
#
#
MakeHeader ("Depend");
if ($::o && (@CSRCS || @CXXSRCS) && $ScmNotGeneric)
{
    $ScmDependTags = 1;
    print MAKEFILE <<EOF;
depend:                 \$(OBJDIR)/depend

\$(OBJDIR)/depend:      \$(SCM_MAKEFILE) \$(GBE_OBJDIR)
\$(OBJDIR)/depend:      \$(CSRCS) \$(CXXSRCS)
ifeq (\$(NODEPEND),0)
        \@echo '[\$@] Doing a make depend..'
        -\$(XX_PRE)\$(rm) -f \$(OBJDIR)/depend
EOF
    $if->CCDepend( "\$(OBJDIR)/depend", "\$(CSRCS)" )
        if ( @CSRCS );
    $if->CXXDepend( "\$(OBJDIR)/depend", "\$(CXXSRCS)" )
        if ( @CXXSRCS );
    MakePrint
        "\t-\@\$(touch) -f \$(OBJDIR)/depend\n";
    print MAKEFILE <<EOF;
else
        \@echo '[\$@] Skipping make depend..'
        -\$(XX_PRE)\$(rm) -f \$(OBJDIR)/depend
endif
EOF
}
else
{
    print MAKEFILE <<EOF;
depend:
EOF
}

#
#   Rule to unmake the depend file
#       No longer neeed.
#       The file is deleted as a part of the OBJDIR cleanup
#
    print MAKEFILE <<EOF;

undepend:
EOF

#--------- IFLAG Documentation -------------------------------------------------
#
#   IFLAG - iteration flag. This is setting by the calling process
#                           and is a function of the phase being processed
#       0   No external dependencies.
#       1   Source dependency list.
#       2   Shared library dependency list
#       3   Application dependency list
#
#
#--------- Dependencies --------------------------------------------------------
#   Include the 'depend' file if required
#
    MakeHeader ("Dependency Inclusion");
    print MAKEFILE <<EOF;
ifeq (\$(NODEPEND),0)
 ifdef IFLAG
  ifneq "\$(IFLAG)" "0"
-include        \$(OBJDIR)/depend
  endif
 endif
endif

EOF

#-------------------------------------------------------------------------------
#   Standard rules
#
    MakeHeader ("Standard rules");
    print MAKEFILE <<EOF;
.PHONY:         make_clean
make_clean:
        -\@echo "Removing generated files (objects, libraries, binaries etc)";

.PHONY:         rmlitter
rmlitter:
        -\$(AA_PRE)JatsFileUtil 'D0' 'Removing litter' '.' 'core' '*.bak' '*.tmp' '*.err'

.PHONY:         lint_init
lint_init:

EOF

#
#   Dependencies for 'make_init'
#
#
my @initdep;
push @initdep, '$(INITS)' if ( @INITS );

#
#   Dependencies for 'make_dir'
#
my @mkdirdep;
push @mkdirdep, '$(GBE_OBJDIR)' if ( @CSRCS || @CXXSRCS || @OBJS || @PROGOBJS || @TESTPROGOBJS );
push @mkdirdep, '$(SHDIRS)'     if ( %SHOBJ_LIB || @SHLIBS);
push @mkdirdep, '$(GBE_LIBDIR)' if ( @LIBS || @MLIBS || @SHLIBS || %INSTALL_LIBS || %PACKAGE_LIBS );
push @mkdirdep, '$(GBE_BINDIR)' if ( @SHLIBS || %SCRIPTS || @PROGS || @TESTPROGS || %INSTALL_PROGS || %PACKAGE_PROGS );

#
#   Actions for for 'unobj'
#
my @unobjact;
push @unobjact, RmFilesCmd( 'OBJS' )            if ( @OBJS );
push @unobjact, RmFilesCmd( 'SHOBJS' )          if ( %SHOBJ_LIB );
push @unobjact, RmFilesCmd( 'PROGOBJS' )        if ( @PROGOBJS );
push @unobjact, RmFilesCmd( 'TESTPROGOBJS' )    if ( @TESTPROGOBJS );
push @unobjact, RmFilesCmd( 'TOOLSETOBJS' )     if ( @TOOLSETOBJS );

#
#   Dependencies for 'make_lib'
#
my @libdep;
push @libdep, '$(GBE_OBJDIR)', '$(GBE_LIBDIR)', '$(LIBS)' if ( @LIBS );

#
#   Dependencies for 'lint_lib'
#
my @liblintdep;
push @liblintdep, 'lint_init', '$(GBE_OBJDIR)', '$(GBE_LIBDIR)', '$(LINTLIBS)' if ( @LIBS );

#
#   Dependencies for 'make_mlib'
#
my @mlibdep;
push @mlibdep, '$(GBE_OBJDIR)', '$(GBE_LIBDIR)', '$(GBE_MLIBDIR)', '$(MLIBS)' if ( @MLIBS );

#
#   Dependencies for 'make_install_shlib' (tag)
#
my @shlibdep;
push @shlibdep, '$(SHDIRS)', '$(SHLIBS)' if ( @SHLIBS );

#
#   Dependencies for 'lint_shlib'
#
my @shliblintdep;
push @shliblintdep, 'lint_init', '$(GBE_LIBDIR)', '$(LINTSHLIBS)' if ( @SHLIBS );

#
#   Actions for 'unmake_lib'
#
my @unlibact;
push @unlibact, RmFilesCmd( 'SHLIBS' )      if ( @SHLIBS );
push @unlibact, RmFilesCmd( 'MLIBS' )       if ( @MLIBS );
push @unlibact, RmFilesCmd( 'LIBS' )        if ( @LIBS );
push @unlibact, RmFilesCmd( 'TOOLSETLIBS' ) if ( @TOOLSETLIBS );

#
#   Actions for 'unmake_mlib'
#
my @unmlibact;
push @unmlibact, RmFilesCmd( 'MLIBS' ) if ( @MLIBS );

#
#   Dependencies for 'make_script'
#
my @scriptdep;
push @scriptdep, '$(GBE_BINDIR)', '$(SCRIPTS)' if ( %SCRIPTS );

#
#   Actions for 'unmake_script'
#
my @unscriptact;
push @unscriptact , RmFilesCmd( 'SCRIPTS' ) if ( %SCRIPTS );
push @unscriptact , RmFilesCmd( 'COPYIN' )  if ( @COPYIN );

#
#   Dependencies for 'make_prog'
#
my @progdep;
push @progdep, '$(GBE_OBJDIR)', '$(GBE_BINDIR)', '$(PROGS)' if ( @PROGS );
push @progdep, '$(PROGS_EXTRA)' if (@PROGS_EXTRA);

#
#   Dependencies for 'make_prog' created for 'projects'
#
my @projectdep;
push @projectdep, '$(PROJECTS)' if (ListGeneratedProjects(1) );

#
#   Dependencies for 'generate' created for 'projects'
#
my @projectgendep;
push @projectgendep, '$(PROJECTSGEN)' if (ListGeneratedProjects(0) );

#
#   Dependencies for 'unmake_prog' created for 'projects'
#
my @projectcleandep;
push @projectcleandep, '$(PROJECTSCLEAN)' if (%PROJECTS);

#
#   Dependencies for 'lint_prog'
#
my @proglintdep;
push @proglintdep, 'lint_init', '$(GBE_OBJDIR)', '$(GBE_BINDIR)', '$(LINTPROGS)' if ( @PROGS || @TESTPROGS );

#
#   Actions for 'unmake_prog'
#
my @unprogact;
push @unprogact, RmFilesCmd( 'PROGS' )        if ( @PROGS );
push @unprogact, RmFilesCmd( 'TOOLSETPROGS' ) if ( @TOOLSETPROGS );

#
#   Dependencies for 'exec_tests'
#
my @testprogdep;
push @testprogdep, '$(GBE_OBJDIR)', '$(GBE_BINDIR)', '$(TESTPROGS)' if ( @TESTPROGS );

my @autoruntestdep;
push @autoruntestdep, 'makefile.pl', '$(AUTOUNITTESTS)' if ( @TESTPROJECT_TO_ARUN );

my @runtestdep;
push @runtestdep    , 'makefile.pl', '$(UNITTESTS)' if ( @TESTPROJECT_TO_URUN );

#
#   Dependencies for 'exec_tests' and friends
#
my @untestprogact;
push @untestprogact ,RmFilesCmd( 'TESTPROGS' ) if ( @TESTPROGS );

#
#   Dependencies for 'generated'
#
my @generatedep;
push @generatedep, '$(GENERATED)' if ( @GENERATED );

#
#   Actions for 'ungenerate'
#
my @ungenact;
push @ungenact, RmFilesCmd( 'GENERATED' ) if ( @GENERATED );
push @ungenact, RmFilesCmd( 'GENERATED_NOTSRC' ) if ( @GENERATED_NOTSRC );
push @ungenact, RmFilesCmd( 'TOOLSETGENERATED' ) if ( @TOOLSETGENERATED );
push @ungenact, RmFilesCmd( 'USERGENERATED' ) if ( @USERGENERATED );

#
#   Dependencies for 'ungenerate'
#
my @ungeneratedep;
push @ungeneratedep, '$(GENERATEDCLEAN)';

#
#   Actions for clobberfiles
#   
my @clobberfiles;
push @clobberfiles, RmFilesCmd('TOOLSETCLOBFILES') if (@CLOBBERFILES); 

#-------------------------------------------------------------------------------
# Function        : PrintPhonyRule
#
# Description     : Helper function to print some internal phony makefile targets
#                   These are used to hold the basic makefile together
#
# Inputs          : $target         - Name of the phony target
#                   $prereq         - Prerequisites
#                                     Leading spaces removed
#                   $recipe         - Optional Reference to an array of recipes
#                                     Will be printed one per line
#
#
sub PrintPhonyRule
{
    my ($target, $prereq, $recipe) = @_;
    $prereq =~ s/^\s+//;

    MakePadded( 2, '.PHONY:', $target, "\n");
    MakePadded( 2,"$target:", $prereq, "\n");
    MakePrint ("\t\t" . $_ . "\n") foreach ( @{$recipe} );
    MakePrint ("\n");
}

#   make_init - Test toolset presence and sanity
#   Will only be called ONCE for each platform in a recursive build
#   Should be used to ensure that the required toolset is present
#
PrintPhonyRule ('make_init',            "@initdep" );

#   make_dir    - Create required subdirectories
#   Will be invoked as a part of most targets that create files
#   Will be invoked by the calling wrappers
#   Should not be invoked when cleaning
#
PrintPhonyRule ('make_dir',             "@mkdirdep" );

PrintPhonyRule ('generate',             "@generatedep @projectgendep" );
PrintPhonyRule ('ungenerate',           "@ungeneratedep",  \@ungenact);
PrintPhonyRule ('unobj',                "",  \@unobjact);
PrintPhonyRule ('make_lib',             "@libdep" );
PrintPhonyRule ('lint_lib',             "@liblintdep" );
PrintPhonyRule ('make_mlib',            "@mlibdep" );
PrintPhonyRule ('lint_shlib',           "@shliblintdep" );
PrintPhonyRule ('unmake_lib',           "", \@unlibact );
PrintPhonyRule ('unmake_mlib',          "", \@unmlibact );
PrintPhonyRule ('make_script',          "@scriptdep" );
PrintPhonyRule ('unmake_script',        "", \@unscriptact );
PrintPhonyRule ('make_prog',            "make_script @progdep @projectdep" );
PrintPhonyRule ('unmake_prog',          "unmake_script @projectcleandep", \@unprogact );
PrintPhonyRule ('lint_prog',            "@proglintdep" );
PrintPhonyRule ('exec_tests',           "make_script @testprogdep @runtestdep" );
PrintPhonyRule ('exec_unit_tests',      "make_script @testprogdep @autoruntestdep" );
PrintPhonyRule ('make_test',            "make_script @testprogdep" );
PrintPhonyRule ('unmake_test',          "unmake_script", \@untestprogact );
PrintPhonyRule ('preprocess_tests',     '$(AUTOUNITTESTS_PRE)' );
PrintPhonyRule ('postprocess_tests',    '$(AUTOUNITTESTS_POST)' );
PrintPhonyRule ('collate_test_results', '$(AUTOUNITTESTS_COLLATE)' );
PrintPhonyRule ('clobberfiles',         "",\@clobberfiles );

#-------------------------------------------------------------------------------
#   Package and Installation Summary
#
    MakeHeader ("Package and Installation Summary");
    sub InstallTarget
    {
        my( $target, $hashp, $prereq, $fprereq ) = @_;
        my( $element );

        my $me = MakeEntry::New( *MAKEFILE, $target, '--Phony' );
        $me->AddDependancy( $fprereq ) if ($fprereq);
        foreach my $element ( sort keys %{$hashp} )
        {
            #
            #   Skip placekeepers
            #
            next if ( $hashp->{$element}{'placekeeper'} );

            #
            #   Prepend any prerequisites (once)
            #
            $me->AddDependancy( $prereq ) if ( $prereq );
            $prereq = 0;

            $me->AddDependancyEscaped( $element );
        }
        $me->Print();

    }

InstallTarget( "install_hdr",       \%INSTALL_HDRS );
InstallTarget( "install_lib",       \%INSTALL_LIBS,  'make_mlib' );
InstallTarget( "make_install_shlib",\%INSTALL_SHLIBS, '', "@shlibdep" );
InstallTarget( "install_prog",      \%INSTALL_PROGS, 'make_script' );
InstallTarget( "install_class",     \%INSTALL_CLSS );

InstallTarget( "package_files",     \%PACKAGE_FILES );
InstallTarget( "package_hdr",       \%PACKAGE_HDRS );
InstallTarget( "package_lib",       \%PACKAGE_LIBS );
InstallTarget( "package_shlib",     \%PACKAGE_SHLIBS );
InstallTarget( "package_prog",      \%PACKAGE_PROGS, 'make_script' );
InstallTarget( "package_class",     \%PACKAGE_CLSS );

#-------------------------------------------------------------------------------
#   Installations

MakeHeader ("Installations");
PackageRule    ( \&InstallCmd, \%INSTALL_HDRS  );
PackageRule    ( \&InstallCmd, \%INSTALL_CLSS  );
PackageRule    ( \&InstallCmd, \%INSTALL_LIBS  );
PackageRule    ( \&InstallCmd, \%INSTALL_SHLIBS  );
PackageRule    ( \&InstallCmd, \%INSTALL_PROGS  );
PackageDirRule ('install_dirs',    \@INSTALL_DIRS);

#-------------------------------------------------------------------------------
#   Packaging
#
MakeHeader ("Packaging");
PackageRule    ( \&PackageCmd, \%PACKAGE_FILES );
PackageRule    ( \&PackageCmd, \%PACKAGE_HDRS );
PackageRule    ( \&PackageCmd, \%PACKAGE_CLSS );
PackageRule    ( \&PackageCmd, \%PACKAGE_LIBS );
PackageRule    ( \&PackageCmd, \%PACKAGE_SHLIBS );
PackageRule    ( \&PackageCmd, \%PACKAGE_PROGS );
PackageDirRule ('package_dirs', \@PACKAGE_DIRS);

#-------------------------------------------------------------------------------
#   Uninstall/unpackaging
#
MakeHeader ("Uninstall/unpackaging");

UnpackageRule  ( 'uninstall_hdr',         \&UninstallCmd, \%INSTALL_HDRS );
UnpackageRule  ( 'uninstall_lib',         \&UninstallCmd, \%INSTALL_LIBS );
UnpackageRule  ( 'uninstall_shlib',       \&UninstallCmd, \%INSTALL_SHLIBS );
UnpackageRule  ( 'uninstall_prog',        \&UninstallCmd, \%INSTALL_PROGS );
UnpackageRule  ( 'uninstall_class',       \&UninstallCmd, \%INSTALL_CLSS );
PackageDirRule ( 'uninstall_dirs',        \@INSTALL_DIRS);

UnpackageRule  ( 'unpackage_files',       \&UnpackageCmd, \%PACKAGE_FILES );
UnpackageRule  ( 'unpackage_hdr',         \&UnpackageCmd, \%PACKAGE_HDRS );
UnpackageRule  ( 'unpackage_lib',         \&UnpackageCmd, \%PACKAGE_LIBS );
UnpackageRule  ( 'unpackage_shlib',       \&UnpackageCmd, \%PACKAGE_SHLIBS );
UnpackageRule  ( 'unpackage_prog',        \&UnpackageCmd, \%PACKAGE_PROGS );
UnpackageRule  ( 'unpackage_class',       \&UnpackageCmd, \%PACKAGE_CLSS );
PackageDirRule ( 'unpackage_dirs',        \@PACKAGE_DIRS);

#-------------------------------------------------------------------------------
#   Distribution Sets
#
MakeHeader ("Distribution Sets");
PackageSetRules();

#-------------------------------------------------------------------------------
#
#   Subdir deletion
#   This is done AFTER the toolset functions have been invoked to create the
#   build artifacts so that the toolsets can create directories too
#
#   Note: Toolset directories are deleted first
#   Note: User Directories are deleted in the reverse order of creation
#
#   Add them into the directory data structure
#
    foreach my $path ( @TOOLSETDIRS )
    {
        MkdirRule( $path, '', '--NoCreate' );
    }

    foreach my $path ( @TOOLSETDIRTREES )
    {
        MkdirRule( $path, '', '--NoCreate' , '--RemoveAll');
    }
    
    MakeHeader ("Subdir deletion");
    RmdirRules();
    ClobberDirsRule();
    MakeNewLine();

#--------- Toolset Rules -------------------------------------------------------
    MakeHeader ("Toolset Rules");
    MakePrintList ( \@TOOLSETRULES );

#--------- Maketags ------------------------------------------------------------

    Maketag( "make_init",           @INITS );
    Maketag( "make_dir",            @mkdirdep );
    Maketag( "generate",            @generatedep || @projectgendep || @USERGENERATED || ($ScmToolsetGenerate != 0) );
    Maketag( "depend",              $ScmDependTags != 0 );
    Maketag( "make_lib",            @libdep );
    Maketag( "make_mlib",           @mlibdep );
    Maketag( "make_install_shlib",  %INSTALL_SHLIBS || @shlibdep);
    Maketag( "make_script",         @scriptdep );
    Maketag( "make_prog",           @progdep || @projectdep );
    Maketag( "make_test",           @testprogdep );
    Maketag( "exec_tests",          $TESTS_TO_RUN     || @TESTPROJECT_TO_URUN );
    Maketag( "exec_unit_tests",     $TESTS_TO_AUTORUN || @TESTPROJECT_TO_ARUN );
    Maketag( "process_tests",       @TOOLSET_UTF_PRE || @TOOLSET_UTF_POST || @TOOLSET_UTF_COLLATE);
    Maketag( "install_hdr",         %INSTALL_HDRS );
    Maketag( "install_class",       %INSTALL_CLSS );
    Maketag( "install_lib",         %INSTALL_LIBS );
    Maketag( "install_prog",        %INSTALL_PROGS );
    Maketag( "install_dirs",        @INSTALL_DIRS );
    Maketag( "deploy",              %DEPLOYPACKAGE );
    Maketag( "package",             %PACKAGE_FILES || %PACKAGE_HDRS || %PACKAGE_CLSS ||
                                    %PACKAGE_LIBS || %PACKAGE_SHLIBS || %PACKAGE_PROGS );

    #
    #   Display tags in the MAKEFILE
    #       Not used here - just for show
    #
    MakeHeader ("Maketags");
    foreach my $tag ( sort keys %MakeTags )
    {
        MakePadded( 3, "#   $tag:", '1', "\n");
    }

#-------------------------------------------------------------------------------
#   End of Makefile
#
    MakeHeader ("End of Makefile");
    close( MAKEFILE );

#
#   Save all platform information
#   Done after the makefile is written as toolsets can extend the data
#
    WriteParsedConfig();

#
#   Write out any accumulated DPACKAGE data
#
    JatsDPackage::DPackageSave();

    return 0;
}

#-------------------------------------------------------------------------------
# Function        : QuoteForMake
#
# Description     : Escape/Quote a pathname for make
#                       Allow files with a $ in the name
#                       Allow files with a space in the name
#                       Allow files with a comma in the name
#                       Allow for paths that have make-varible prefixes
#                           $(GBE_...) or ${GBE_...} or $(OBJDIR) or $(BUILDVERNUM)
#                           as these may be generated internally
#                       Allow for files with a colon in the name
#                           Mode dependent
#                               0 - No effect
#                               T - \\\:
#                               S = \:    
#
#                       Must also allow $(GBE_TYPE) in the remainder
#
# Inputs          : uarg            - Arg to quote
#                   mode            - Mode of operation
#                                     T - Makefile target
#                                     S - Makefile source
#                                     0 - Neither
#
# Returns         : Quoted arg
#

sub QuoteForMake($;$)
{
    my ($uarg, $mode) = @_;
    $mode = '0' unless defined $mode;

    #
    #   Split into two
    #       $(xxx)/             - Makefile variables
    #       Remainder           - Stuff to quote
    #
    $uarg =~ m~^((\$\(.*?\)/)*)(.*)~;
    my $prefix = defined $1 ? $1 : '';
    my $arg    = defined $3 ? $3 : '';

    $arg =~ s~\$(?!\(GBE_[A-Z]+\)|{GBE_[A-Z]+}|\(OBJDIR\)|\(BUILDVERNUM\))~\$\$~g;       # $, not followed by (GBE_ or ${GBE_ or (OBJDIR)- is not $(GBE_ AND not $(OBJDIR)
    $arg =~ s~ ~\\ ~g;
    $arg =~ s~,~\$(comma)~g;
    $arg =~ s~%~\\%~g;
    $arg =~ s~:~\\\\\\:~g if ($mode eq 'T' &&  $::ScmHost eq "Unix");
    $arg =~ s~:~\\:~g     if ($mode eq 'S' &&  $::ScmHost eq "Unix");
    return $prefix . $arg;
}

#-------------------------------------------------------------------------------
# Function        : Maketag
#
# Description     : Create Makefile tags to speed up recursive makes
#
# Inputs          : tag_name
#                   dep
#
# Returns         : 
#
sub Maketag
{
    my( $tag, $dep ) = @_;
    $MakeTags{$tag} = 1 if ( defined($dep) && $dep );
}

#-------------------------------------------------------------------------------
#   Function to create and delete directories within the build system
#
#    To stop make regenerating directory dependent targets each time the
#    directory content is modified, rule should only be dependent on a internally
#    created alias file 'gbedir', which represents the time a dir was created not
#    last modified.
#
#    Must use tags like GBE_BINDIR, GBE_LIBDIR and GBE_OBJDIR to ensure that the
#    directories are created correctly.
#
my %MkdirRuleData;
my @MkdirRuleOrder;
my $MkdirRulePrinting = 0;
my $MkdirRuleGbeFile = ( $::ScmHost eq "Unix" ) ? ".gbedir" : "_gbedir";

#-------------------------------------------------------------------------------
# Function        : MkdirRule
#
# Description     : Create Rules and Recipes to create a directory at make-time
#                   Mark the information for such that the directories will
#                   be deleted in a 'clean'
#
#                   Can be called before we start writing the makefile
#                   Such entries will be retained and dumped at a known time
#
# Inputs          : $subdir     - Symbolic name of the subdir $(OBJDIR)
#                   $alias      - Optional script alias for the dir 'OBJDIR' --> GBE_OBJDIR
#                   Options:
#                       --Path=path             Optional value of $subdir '$(GBE_PLATFORM)$(GBE_TYPE).OBJ'
#                       --RemoveAll             Remove all files on clean
#                       --Extra=file[,file]     Additional files to remove
#                       --NoCreate              Do not Create the Directory, just delete it
#
# Returns         : Nothing
#

sub MkdirRule
{
    my( $subdir, $alias, @opts ) = @_;

    #
    #   Create data entry once
    #
    $alias =~ s~^GBE_~~ if $alias;
    unless ( $MkdirRuleData{$subdir}  )
    {
        my %data;

        #
        #   Parse options
        #
        foreach ( @opts )
        {
            if ( /^--Path=(.+)/ ) {
                $data{path} = $1;
            } elsif ( /^--RemoveAll/ ) {
                $data{remove_all} = 1;
            } elsif ( /^--NoCreate/ ) {
                $data{noCreate} = 1;
            } elsif ( /^--Extra=(.+)/ ) {
                @{$data{extraFiles}} = split(/,/, $1);
            } else {
                Error ("MkdirRule: Unknown option: $_");
            }
        }
        $data{alias} = $alias if ( $alias );

        $MkdirRuleData{$subdir} = \%data;
        push @MkdirRuleOrder, $subdir;
    }

    #
    #   Save or print
    #
    return unless ( $MkdirRulePrinting );
    return if ( $MkdirRuleData{$subdir}{noCreate} );

    #
    #   Create a definition of the physical directory
    #
    my $path = $MkdirRuleData{$subdir}{path};
    MakePadded (2, $alias, ":= $path\n") if ( $path && $alias );

    #   Create an alias to be used within rules
    #   The defined aliase will be prefixed with 'GBE_'
    #
    MakePadded (2, "GBE_$alias", ":= $subdir/$MkdirRuleGbeFile\n") if ( $alias );

    #
    #   Create a recipe to create the directory
    #   This is not as simple as it sounds
    #   The touch is required.
    #       Had 'timestamp' issues on solaris'. The 'echo' did not appear
    #       to be enough. Perhaps the output was not flushed
    #
    MakePadded (2, "$subdir", ": $subdir/$MkdirRuleGbeFile\n");
    MakePrint
        "$subdir/$MkdirRuleGbeFile:\n".
        "\t\$(XX_PRE)if [ ! -d $subdir ]; then \$(mkdir) -p $subdir; fi; \\\n".
        "\t\$(echo) '# DO NOT REMOVE.' > \$@; \\\n".
        "\t\$(touch) \$@\n\n";
}

#-------------------------------------------------------------------------------
# Function        : RmdirRules
#
# Description     : Create the body of a recipe to delete the directories that
#                   have been created.
#
#                   Use JatsFileUtil rather than shell script
#                       Faster under windows (and others)
#                       Solved long pathname issues
#                       Simpler to use and control
#
# Inputs          : Uses $MkdirRuleData
#
# Returns         : Nothing.
#                   Prints to the makefile
#
sub RmdirRules
{
    MakePrint( ".PHONY:\tunmake_dir\n" );
    MakePrint( "unmake_dir:\n" );

    #
    #   Determine the list of directories to delete
    #   Sort such that subdirs are deleted first
    #
    my $txt = 'Removing directories';
    foreach my $subdir ( reverse sort keys %MkdirRuleData )
    {
        my @args = $subdir;

        push (@args, $MkdirRuleGbeFile, 'core', '*.bak', '*.tmp', '*.err', 'utf.*.rc')
            unless $MkdirRuleData{$subdir}{remove_all};

        push (@args, @{$MkdirRuleData{$subdir}{extraFiles}})
            if ( $MkdirRuleData{$subdir}{extraFiles} );

        my $mode = $MkdirRuleData{$subdir}{remove_all} ? 'T0' : 'D0';

        MakePrint ("\t-\$(AA_PRE)JatsFileUtil ", QuoteArray( $mode, $txt, @args ), "\n");
        $txt = '';
    }
}

#-------------------------------------------------------------------------------
# Function        : ClobberDirsRule 
#
# Description     : Create the body of a recipe to delete the directories that
#                   will be removed in a clobber
#
#                   Use JatsFileUtil rather than shell script
#                       Faster under windows (and others)
#                       Solved long pathname issues
#                       Simpler to use and control
#
# Inputs          : @CLOBBERDIRS
#
# Returns         : Nothing.
#                   Prints to the makefile
#
sub ClobberDirsRule
{
    MakeNewLine();
    MakePrint( ".PHONY:\tclobberdirs\n" );
    MakePrint( "clobberdirs:\n" );

    #
    #   Determine the list of directories to delete
    #   Sort such that subdirs are deleted first
    #
    my $txt = 'Removing toolset directories';
    foreach my $subdir ( reverse sort @CLOBBERDIRS )
    {
        my @args = $subdir;
        MakePrint ("\t-\$(AA_PRE)JatsFileUtil ", QuoteArray( 'T0', $txt, @args ), "\n");
        $txt = '';
    }
}

#-------------------------------------------------------------------------------
# Function        : CreateMkdirRules
#
# Description     : Create Rules to make dirs at runtime
#                   This function is called to instantiate those entries
#                   That have been requested before the makefile has has
#                   started to be created.
#
#                   Once this function has been called all new MkdirRule calls
#                   will result in the recipes being created in-line.
#
# Inputs          : Nothing
#
# Returns         : Even Less
#
#
sub CreateMkdirRules
{
    $MkdirRulePrinting = 1;
    foreach my $subdir ( @MkdirRuleOrder )
    {
        my $data = $MkdirRuleData{$subdir};
        MkdirRule($subdir, $data->{alias}, $data->{path} );
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageRule
#
# Description     : Generate rules and recipes to "install" and "package" files
#
# Inputs          : codecmd     - A code reference to the actual installer routine
#                   hashp       - A reference to a INSTALL or PACKAGE hash
#
#                   hashp is a reference to a hash
#                       The key is the full path of the install target
#                       The value is (another) hash that describes the install options
#
#                   Valid keys are:
#                       src                 - Path of the source file [Mandatory]
#                       dir                 - Target directory [Mandatory]
#
#                       defined             - Copy the file only if value is defined
#                       Exists              - Copy the file only if it exists
#                       exe                 - Mark the file as executable
#                       Mode                - File modes. Default is -w
#                       placekeeper         - Marks SHARED library placekeepers
#                       set                 - Distribution sets
#                       type                - Copy the file in DEBUG or PROD mode
#                                             Valid values are "D" or "P"         
#                       version             - Shared library version information
#                       symlink             - File is a symlink
#                       RemoveOnly          - Do not install the file. Entries are
#                                             created to allow the removal of the file
#                       NoTarget            - Reserved - Implemented elsewhere
#
# Returns         :
#
sub PackageRule
{
    my ($codecmd, $hashp) = @_;

    foreach my $dest ( keys %{$hashp} )
    {

        my $entry = $hashp->{$dest};
        my $destText = QuoteForMake($dest,'T');
        #
        #   Skip placekeepers
        #
        next if ( $entry->{'placekeeper'} );

        #
        #   Some entries are not installed via this mechanism, but can be removed
        #   if they exist. Mark these as PHONY to keep targets happy
        #
        if ( $entry->{'RemoveOnly'} )
        {
            MakePrint ".PHONY:\t$destText\n";
            MakePrint "$destText:\n\n";
            next;
        }

        my $fname = $entry->{'src'};
        my $fnameText = QuoteForMake($fname,'S');
        my $fmode = $entry->{'Mode'};
        $fmode .= "+x" if ( $entry->{'exe'}  );
        $fmode .= "+l" if ( $entry->{'symlink'}  );

        #
        #   User conditionional
        #   Mark both the source and the target as PHONY if the condition is not met
        #   This will ensure that the target need not be built.
        #
        my $udef = $entry->{'defined'};
        if ( $udef )
        {
            MakePrint "ifndef $udef \n";
            MakePrint ".PHONY:\t\t$destText\n";
            MakePrint ".PHONY:\t\t$fnameText\n";
            MakePrint "$destText:\n";
            MakePrint "else\n"
        }

        #
        #   File exists
        #   Only package the file if it has been generated. ie: .exe.manifest
        #
        my $fexist = $entry->{'Exists'};
        if ($fexist)
        {
            MakePrint "ifeq (\"\$(wildcard $fnameText)\",\"\")\n";
            MakePrint ".PHONY:\t\t$destText\n";
            MakePrint "$destText:\n";
            MakePrint "else\n"
        }

        #
        #   Conditional installation for DEBUG/PRODUCTION
        #
        my $type = $entry->{'type'};
        if ( $type )
        {
            if ( $type eq "D" ) {
                MakePrint 'ifeq "$(DEBUG)" "0"'."\n";
            } elsif ( $type eq "P" ) {
                MakePrint 'ifneq "$(DEBUG)" "0"'."\n";
            } else {
                Error("INTERNAL: Unexpected packaging type: $type");
            }
            MakePrint ".PHONY:\t\t$destText\n";
            MakePrint "$destText:\n";
            MakePrint "else\n"
        }

        #
        #   The body of the copy
        #
        MakePadded( 4, $destText . ':' );
        MakePrint "\t" . $fnameText . "\n";
        MakePrint $codecmd->( $dest, $fname, $fmode );
        MakeNewLine();

        #
        #   Unwind conditionals
        #
        MakePrint "endif\n" if ( $type );
        MakePrint "endif\n" if ( $fexist );
        MakePrint "endif\n" if ( $udef );

        #
        #   Distribution sets
        #
        my $dist = $entry->{'set'};
        if ( $dist )
        {
            foreach my $set ( split( ',', $dist ) )
            {
                push @{$PACKAGE_SETS{$set}{LIST}}, $dest;
            }
            MakeNewLine();
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageDirRule 
#
# Description     : Generate special rules for dynamic packaging/installation of directories 
#
# Inputs          : $mode        - package_dirs/unpackage_dirs/install_dirs/uninstall_dirs
#                   $dataRef     - Ref to package/install list
#                       
#
# Returns         : 
#
sub PackageDirRule
{
    my ($mode, $dataRef) = @_;
    my $me = MakeEntry::New( *MAKEFILE, $mode , '--Phony' );

    my $modeText = 'packaging';
    my $cmdText = 'PackageDir';
    if ($mode =~ m~install~) {
        $modeText = 'installing';
        $cmdText = 'InstallDir';
    }
    my $cmd = 'copyDir'; 
    if ($mode =~ m~^un~) {
        $cmd = 'unCopyDir';
        $modeText = 'un' . $modeText;
        $cmdText = 'Un' . $cmdText;
    }


    foreach my $entry ( @{$dataRef}) {
        $me->NewSection();

        #
        #   Conditional installation for DEBUG/PRODUCTION
        #
        my $type = $entry->{'type'};
        if ( $type )
        {
           if ( $type eq "D" ) {
               $me->SectionIfNeq('$(DEBUG)','0');
           } elsif ( $type eq "P" ) {
               $me->SectionIfEq('$(DEBUG)','0');
           } else {
               Error("INTERNAL: Unexpected packaging type: $type");
           }
        }

        #
        #   Quote the REs so that they can be passed to a command line
        #       Replace $ with $$
        #
        my $QuoteRe = sub {
            my ($arg) = @_;
            $arg=~ s~\$~\$\$~g;
            return $arg;
            };

        #
        #   The body of the copy
        #   Create a command line for run-time command
        #   
        my @cmd;
        push @cmd, '$(JatsRunTime)', $cmd, '--', '-$(VERBOSE_OPT)', '--Name='. $cmdText, '--';
        push @cmd, '-mode=' . $modeText;
        push @cmd, "'" . '-src=' . $entry->{dirTree} . "'";
        push @cmd, "'" . '-dst=' . $entry->{dir} . "'";
        push (@cmd, '-execute' ) if $entry->{exefile};
        push (@cmd, '-noSymlink' ) if $entry->{noPreserveSymlink};
        push (@cmd, '-noRecurse' ) if $entry->{noRecurse};
        push (@cmd, '-stripBase' ) if $entry->{strip_base};
        push (@cmd, "'" . '-exclude+=' . $QuoteRe->($_) . "'" ) foreach @{$entry->{exclude}};
        push (@cmd, "'" . '-include+=' . $QuoteRe->($_) . "'" ) foreach @{$entry->{include}};
        push (@cmd, "'" . '-excludeRe+=' . $QuoteRe->($_) . "'" ) foreach @{$entry->{excludeRe}};
        push (@cmd, "'" . '-includeRe+=' . $QuoteRe->($_) . "'" ) foreach @{$entry->{includeRe}};

        $me->AddRecipe(join(' ', @cmd ) );
    }
    $me->Print();
}

#-------------------------------------------------------------------------------
# Function        : PackageSetRules
#
# Description     : Generate the packageset rules
#                   These appear to be a now-defuct feature
#
#                   By default all packaged files are a part of package_setALL
#
# Inputs          : None
#                   Takes data from %PACKAGE_SET
#
# Returns         : Nothing
#
sub PackageSetRules
{
    foreach my $set ( sort keys %PACKAGE_SETS )
    {
        my $me = MakeEntry::New( *MAKEFILE, "package_set$set", '--Phony' );
        $me->AddDependancyEscaped( @{$PACKAGE_SETS{$set}{LIST}} );
        $me->Print();
    }
}

#-------------------------------------------------------------------------------
# Function        : UnPackageRule
#
# Description     : Generate rules and recipes to "uninstall" and "unpackage" files
#
# Inputs          : target      - Name of the target
#                   codecmd     - A code reference to the actual installer routine
#                   hashp       - A reference to a INSTALL or PACKAGE hash
#
# Returns         :
#
sub UnpackageRule
{
    my ($target, $codecmd, $hashp) = @_;

    MakePrint ".PHONY:\t\t"."$target\n";
    MakePrint "$target:\t";
    
    foreach my $dest ( sort keys %{$hashp} )
    {

        my $entry = $hashp->{$dest};

        #
        #   Skip placekeepers
        #
        next if ( $entry->{'placekeeper'} );

        MakePrint "\n" . $codecmd->($dest);
    }
    MakePrint "\n\n";
}


#
#   Internal macro interface, see RULE.STD for definitions:
#
sub RmFilesCmd
{
    my ( $list ) = @_;
    return "\$(call RmFiles,$list)";
}

sub InstallCmd
{
    my( $dest, $file, $fmode ) = @_;

    $fmode = "-w"                           # default, read-only
        if ( !defined( $fmode ) || $fmode eq "" );

    $dest = QuoteForMake($dest);
    $file = QuoteForMake($file);
    return "\t\$(call InstallFile,$dest,$file,$fmode)";
}

sub UninstallCmd
{
    my( $file ) = @_;
    $file = QuoteForMake($file);
    return "\t\$(call UninstallFile,$file)";
}

sub PackageCmd
{
    my( $dest, $file, $fmode ) = @_;

    $fmode = "-w"                           # default, read-only
        if ( !defined( $fmode ) || $fmode eq "" );

    $dest = QuoteForMake($dest);
    $file = QuoteForMake($file);
    return "\t\$(call PackageFile,$dest,$file,$fmode)";
}

sub UnpackageCmd
{
    my( $file ) = @_;
    $file = QuoteForMake($file);
    return "\t\$(call UnpackageFile,$file)";
}

1;