Subversion Repositories DevTools

Rev

Rev 7441 | 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   : jats_sandbox.pl
# Module type   : JATS Utility
# Compiler(s)   : Perl
# Environment(s): JATS
#
# Description   : A script to build a collection of packages in the
#                 same sandbox. This script will:
#
#                   Determine the packages in the sandbox
#                   Determine the build order of the packages
#                   Build the packages in the correct order
#                   Make the packages in the correct order
#
#                 The script will allow for:
#                   The creation of a sandbox
#                   The addition of packages to the sandbox
#                   Removal of packages from the sandbox
#
#
#                 Command syntax (basic)
#                   jats sandbox <command> (options | actions)@
#
#                 Commands include:
#                   create              - Create a sandbox
#                   delete              - Delete a sandbox
#                   info                - Show sandbox info
#
#                   build               - Build all packages in the sandbox
#                   make                - make all packages in the sandbox
#
#......................................................................#

require 5.008_002;
use strict;
use warnings;
use JatsError;
use JatsSystem;
use FileUtils;
use JatsBuildFiles;
use JatsVersionUtils;
use ArrayHashUtils;
use ToolsetFiles;

use Pod::Usage;                             # required for help support
use Getopt::Long qw(:config require_order); # Stop on non-option
use Digest;

my $VERSION = "1.0.0";                      # Update this
my $SCANDEPTH = 3;                          # Almost a constant

#
#   Options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_help = 0;                           # Help level
my $opt_exact = 0;                          # Exact (escrow) build
my $opt_toPackage;                          # Control recursion
my $opt_fromPackage;                        # Control recursion
my @opt_justPackage;                        # Control recursion
my @opt_ignorePackage;                      # Control recursion
my $opt_allSandbox;                         # Extend scope to entire sandbox
my $opt_processUsedBy;                      # Process dependants(consumers) not dependents(children)
my $opt_keepgoing;                          # Ignore errors
my $opt_reScan;                             # Rescan for buildfiles
my $opt_multiBuilders = 0;                  # How handle packages with multiple bulders: 0:Error, 1:Report, 2:Ignore
my $opt_onlyLevel = 0;                      # Single level processing

#
#   Globals - Provided by the JATS environment
#
my $USER            = $ENV{'USER'};
my $UNIX            = $ENV{'GBE_UNIX'};
my $HOME            = $ENV{'HOME'};
my $GBE_SANDBOX     = $ENV{'GBE_SANDBOX'};
my $GBE_DPKG_SBOX   = $ENV{'GBE_DPKG_SBOX'};
my $GBE_MACHTYPE    = $ENV{'GBE_MACHTYPE'};
my $GBE_BUILDFILTER = $ENV{'GBE_BUILDFILTER'};
my $GBE_OPTS        = $ENV{'GBE_OPTS'};

my $GBE_DPKG_LOCAL      = $ENV{'GBE_DPKG_LOCAL'};
my $GBE_DPKG_CACHE      = $ENV{'GBE_DPKG_CACHE'};
my $GBE_DPKG            = $ENV{'GBE_DPKG'};
my $GBE_DPLY            = $ENV{'GBE_DPLY'};
my $GBE_DPKG_STORE      = $ENV{'GBE_DPKG_STORE'};
my $GBE_DPKG_REPLICA    = $ENV{'GBE_DPKG_REPLICA'};
my $GBE_DPKG_ESCROW     = $ENV{'GBE_DPKG_ESCROW'};

#
#   Globals
#
my @stopped = ();                               # Stopped entries
my @build_order = ();                           # Build Ordered list of entries
my %extern_deps;                                # Hash of external dependencies
my %packages;                                   # Hash of packages
my $currentPkgTag;                              # Tag of the current package - if any                                          
my $scanDepth;                                  # Depth for build file scan
my $maxDname = 0;                               # Pretty display

#
#   Known files
#
$GBE_DPKG_SBOX = '' unless defined $GBE_DPKG_SBOX;
my $cacheFile  = $GBE_DPKG_SBOX . '/location_cache';
my $depthFile  = $GBE_DPKG_SBOX . '/scanDepth';
my $filterFile = $GBE_DPKG_SBOX . '/buildfilter';

#-------------------------------------------------------------------------------
# Function        : Mainline Entry Point
#
# Description     :
#
# Inputs          :
#

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

#
#   Configure the error reporting process now that we have the user options
#
ErrorConfig( 'name'    => 'SANDBOX',
             'verbose' => $opt_verbose );

#
#   Reconfigure the options parser to allow subcommands to parse options
#
Getopt::Long::Configure('permute');

#
#   Determine Sandbox type. Exact or local
#
$opt_exact = (-f  $GBE_SANDBOX . '/sandbox_dpkg_archive/.exact' )
    if ( $GBE_SANDBOX );

#   Flag to subcommands that the build is occuring within a SANDBOX
#   Example usage: Prevent gradle subproject inclusion
$ENV{'GBE_SANDBOX_BUILD'} = 1;

#
#   Parse the user command and decide what to do
#
#   Remove user command from the command line. This will leave command options
#   in @ARGV so that they can be parsed by the subcommand.
#
my $cmd = shift @ARGV || "";
help(1)                                 if ( $cmd =~ m/^help$/ || $cmd eq "" );
buildcmd($cmd, @ARGV )                  if ( $cmd =~ m/(^all$)|(^build$)/  );
cache(@ARGV)                            if ( $cmd =~ m/^cache$/  );
clean($cmd, @ARGV)                      if ( $cmd =~ m/(^clobber$)|(^clean$)/  );
cmd('make', $cmd, @ARGV )               if ( $cmd =~ m/(^make$)/  );
cmd('cmd', @ARGV)                       if ( $cmd =~ m/^cmd$/ );
create_sandbox()                        if ( $cmd =~ m/^create$/ );
delete_sandbox()                        if ( $cmd =~ m/^delete$/ );
info(@ARGV)                             if ( $cmd =~ m/^info$/ );
populate(@ARGV)                         if ( $cmd =~ m/^populate$/  );
buildfilter(@ARGV)                      if ( $cmd =~ m/^buildfilter$/  );
skipBuild(0, @ARGV)                     if ( $cmd =~ m/^skip$/  );
skipBuild(1, @ARGV)                     if ( $cmd =~ m/^unskip$/  );
fingerPrintPkg(@ARGV)                   if ( $cmd =~ m/^finger/i );
testLinks(@ARGV)                        if ( $cmd =~ m/^testlinks/i );
setScanDepth(@ARGV)                     if ( $cmd =~ m/^scandepth/i );

Error ("Unknown sandbox command: $cmd");
exit 1;


#-------------------------------------------------------------------------------
#
#   Give the user a clue
#
sub help
{
    my ($level) = @_;
    $level = $opt_help unless ( $level );

    pod2usage(-verbose => 0, -message => "Version: ". $VERSION)  if ($level == 1 );
    pod2usage(-verbose => $level -1 );
}

#-------------------------------------------------------------------------------
# Function        : create_sandbox
#
# Description     : create a sandbox in the current current directory
#
# Inputs          : None
#
#
sub create_sandbox
{
    my  $opt_force;
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "exact"         => \$opt_exact,
                "force!"        => \$opt_force,
                ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Create Sandbox") if ($opt_help || $#ARGV >= 0 );

    Error ("Cannot create a sandbox within a sandbox",
           "Sandbox base is: $GBE_SANDBOX" ) if ( $GBE_SANDBOX && !$opt_force );

    mkdir ('sandbox_dpkg_archive') || Error ("Cannot create the directory: sandbox_dpkg_archive") ;

    TouchFile( 'sandbox_dpkg_archive/.exact', 'Sandbox marker file')
        if ($opt_exact);

    Message ('Sandbox created' . ($opt_exact ? ' - With exact version number processing' : ''));
    exit  0;
}

#-------------------------------------------------------------------------------
# Function        : delete_sandbox
#
# Description     : Delete a sandbox
#                   Its up to the user the delete the components in the sandbox
#
# Inputs          : None
#
# Returns         :
#
sub delete_sandbox
{
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Delete Sandbox") if ($opt_help || $#ARGV >= 0 );

    unless ( $GBE_SANDBOX )
    {
        Warning("No sandbox found to delete");
    }
    else
    {
        Error ("Sandbox directory not completely removed")
            if RmDirTree( "$GBE_SANDBOX/sandbox_dpkg_archive" );

        Message("Sandbox information deleted",
                "Sandbox components must be manually deleted");
    }
    exit 0;
}

#-------------------------------------------------------------------------------
# Function        : skipBuild 
#
# Description     : Mark the building of a package for skipping
#
# Inputs          : Mode -: Skip, 1:Unskip
#                   User commaand line 
#
# Returns         : Nothing
#
sub skipBuild
{
    my ($mode, @cmd_opts ) = @_;
    my $machine;

    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                        'machine!' => \$machine,
                        ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Skip Build") if ($opt_help );
    Error ("Command must be executed from within a Sandbox") unless ( $GBE_SANDBOX );

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info(1);

    #
    #   Display only
    #   
    unless( @cmd_opts)
    {
        foreach my $pkg (keys %packages)
        {
            my $fe = $packages{$pkg};
            next unless $fe->{buildSkip};

            my $skipMsg = ($fe->{buildSkip}) ? ' (Build Skipped)' : ' (Build Suppressed)';
            if ($fe->{buildCurrent}) {
                $skipMsg .= ' (Current Package)';
            }

            Message( ' Name: ' . $fe->{dname} . $skipMsg );

        }
        exit 0;
    }

    foreach ( @cmd_opts )
    {
        #
        #   Locate the package
        #
        my $pe;
        if ($currentPkgTag && ($_ eq '.'))
        {
            if (exists $packages{$currentPkgTag})
            {
                $pe = $packages{$currentPkgTag}; 
            }
        }
        unless ($pe) {
            foreach my $pkg (keys %packages)
            {
                my $entry = $packages{$pkg};
                if ($entry->{dname} eq $_ || $entry->{fname} eq $_ )
                {
                    $pe = $entry;
                    last;
                }
            }
        }
        
        unless ($pe) {
            Warning("No package found matching: $_");
            next;
        }
        
        my $skipFile;
        if ($machine) {
            $skipFile = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', '_skip.' . $GBE_MACHTYPE . '.' . $pe->{dname});
        } else {
            $skipFile = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', '_skip.'. $pe->{dname});
        }
        Verbose("SkipFile: $skipFile");
        if ($mode)
        {
            unlink $skipFile;
        }
        else
        {
            TouchFile($skipFile);
        }
    }
    exit 0;
}

#-------------------------------------------------------------------------------
# Function        : info
#
# Description     : Display Sandbox information
#
# Inputs          : Command line args
#                   -v  - Be more verbose
#
# Returns         : Will exit
#
sub info
{
    my (@cmd_opts ) = @_;
    my $show = 0;
    my $showUsage = 0;
    my $showFingerPrint = 0;
    my $showDependencies = 1;
    my $showOrder = 1;
    my $showPath = 0;

    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                          'verbose:+'       => \$show,
                          'usedby'          => \$showUsage,
                          'fingerprint!'    => \$showFingerPrint,
                          'dependencies!'   => \$showDependencies,
                          'buildorder!'     => \$showOrder,
                          'path!'           => \$showPath,
                        ) || Error ("Invalid command line" );
    SubCommandHelp( $opt_help, "Sandbox Information") if ($opt_help || $#cmd_opts >=0 );
    $showUsage = 1 if ($show >= 2);

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info();

    #
    #   Display information
    #
    Message ("Type       : " . ($opt_exact ? 'Exact' : 'Development') );
    Message ("Scope      : " . ($opt_allSandbox ? 'Entire Sandbox' : $packages{$currentPkgTag}{dname} ));
    Message ("Options    : " . $GBE_OPTS) if $GBE_OPTS;
    Message ("Base       : $GBE_SANDBOX");
    Message ("Archive    : $GBE_DPKG_SBOX");
    Message ("BuildFilter: $GBE_BUILDFILTER" . ( (-f $filterFile)  ? ' - Local to sandbox' : ''));

    if ($showOrder)
    {
        Message ("Build Order");
        foreach my $pname ( @stopped ) {
            Message( "    Level:" . "--"  . " [---] Name: " . $pname . ' (Stopped)');
        }

        foreach my $fe ( @build_order )
        {
            displayHeader($fe, { indent => '    ', testFingerPrint =>  $showFingerPrint, showSimplePath => $showPath });

            if ( $show )
            {
                Message( DisplayPath ("        Path: $fe->{dir}" ));
                foreach my $idep ( sort values %{$fe->{'ideps'}} )
                {
                    Message ("        I:$idep");
                }

                foreach my $edep ( sort keys %{$fe->{'edeps'}} )
                {
                    my ($ppn,$ppv) = split( $; , $edep);
                    Message ("        E:$ppn $ppv");
                }
            }

            if ($showUsage && exists($fe->{'usedBy'}))
            {
                foreach my $edep ( sort {uc($a) cmp uc($b)} @{$fe->{'usedBy'}} )
                {
                    Message ("        U:$packages{$edep}{dname}");
                }
            }
        }
    }

    #
    #   External dependencies flags
    #       * - Package does not exist in dpkg_archive
    #       + - Multiple versions of this package are used

    if ($showDependencies)
    {
        Message("External Dependencies");
        foreach my $de ( sort {uc($a) cmp uc($b)} keys %extern_deps )
        {
            my @vlist = keys %{$extern_deps{$de}};
            my $flag = $#vlist ? '+' : '';
            foreach my $pve ( sort {uc($a) cmp uc($b)} @vlist )
            {
                my ($pn,$pv) = split( $; , $pve );
                my $exists = check_package_existance($pn,$pv  ) ? '' : '*';
                my $flags = sprintf ("%4.4s", $flag . $exists);
                Message ("${flags}${pn} ${pv}");
                if ( $show || $showUsage )
                {
                    foreach my $pkg ( sort {uc($a) cmp uc($b)} @{$extern_deps{$de}{$pve}} )
                    {
                        my $ppn = join ('.', split( $; , $pkg));
                        Message ("        U:$ppn");
                    }
                }

            }
        }
    }

    if ( $show > 2 || $opt_verbose > 2 )
    {
        DebugDumpData( "extern_deps", \%extern_deps);
        DebugDumpData( "build_order", \@build_order);
        DebugDumpData( "packages", \%packages);
    }
    exit (0);
}

#-------------------------------------------------------------------------------
# Function        : fingerPrintPkg 
#
# Description     : Various finger print operations on the current package
#
# Inputs          : @ARGV   - Arguments
#
# Returns         : WIll not return
#
sub fingerPrintPkg
{
    my ( @cmd_opts ) = @_;
    my $opt_generate;
    my $opt_delete;

    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                          'generate!' => \$opt_generate,
                          'delete!'   => \$opt_delete,
                        ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Sandbox Finger Print") if ($opt_help );
    Error ("Command must be executed from within a Sandbox") unless ( $GBE_SANDBOX );

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info(1);

    #
    #   Determine the named packages, or use the current package
    #
    my @pkgList;
    if (@cmd_opts)
    {
        @pkgList = @cmd_opts;
    }
    else
    {
        if ( $currentPkgTag )
        {
            if (exists $packages{$currentPkgTag})
            {
                push @pkgList, $packages{$currentPkgTag}{fname};
            }
        }
    }
    Error ("Command must be used within a package, or with named packages.") 
        unless @pkgList;


    #
    #   Process all required packages
    #
    foreach my $pkgName ( @pkgList)
    {
        #
        #   Locate the package
        #
        my $pe;
        foreach my $pkg (keys %packages)
        {
            my $entry = $packages{$pkg};
            if ($entry->{dname} eq $pkgName || $entry->{fname} eq $pkgName )
            {
                $pe = $entry;
                last;
            }
        }

        unless ( $pe ) {
            Warning ("Cannot locate package: $pkgName");
            next;
        }

        #
        #   Recalculate finger print
        #
        my $tagFile = getPkgFingerPrintFile($pe);
        if ($opt_generate)
        {
            my $ifaceDir = getpkgInterface($pe);
            if ($ifaceDir)
            {
                Message ("Generate Fingerprint");
                Verbose ("Fingerprint file: $tagFile");
                FileCreate( $tagFile, genPkgFingerPrint($pe,'Generation') );
            }
            else
            {
                Warning("Package has not been built. Cannot generate fingerprint: $pkgName");
            }
        }
        elsif ($opt_delete)
        {
            unlink $tagFile;
            Message ("Fingerprint file removed");
        }
        else
        {
            #
            #   Test the finger print on the current package
            #
            if ( -e $tagFile )
            {
                if ( TagFileMatch($tagFile, genPkgFingerPrint($pe, 'Test')) )
                {
                    Message ("Fingerprint match");
                }
                else
                {
                    Message("Fingerprint missmatch");
                }
            }
            else
            {
                Message ("Package does not have a fingerprint file");
            }
        }
    }

    exit (0);
}

#-------------------------------------------------------------------------------
# Function        : testLinks 
#
# Description     : Test the lnk files in the sandbox directory
#                   These may be stake after packages are deleted
#
# Inputs          : 
#
# Returns         : This function will not return
#
sub testLinks
{
    my ( @cmd_opts ) = @_;
    my $opt_delete;

    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                'delete!'       => \$opt_delete,
                ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Sandbox Test Links") if ($opt_help || ($#ARGV >= 0) );
    Error ("Command must be executed from within a Sandbox") unless ( $GBE_SANDBOX );

    #
    #   Process all packages in the sandbox
    #
    foreach my $linkFile ( glob "$GBE_DPKG_SBOX/*/*.lnk")
    {
        my $pkg = getPackageLink( TagFileRead($linkFile) );
        unless ( -d $pkg )
        {
            if ($opt_delete)
            {
                Message ("Delete: $linkFile");
                unlink $linkFile;
            } 
            else
            {
                Message ("Broken link: $pkg", "Source link: $linkFile" );
            }
        }

    }
    
    exit (0);
}

#-------------------------------------------------------------------------------
# Function        : getPackageLink 
#
# Description     : Read a package link file and process the data to generate 
#                   the link to the package
#
# Inputs          : $linkFile   - File that contains the link
#
# Returns         : The resolved link
#
sub getPackageLink
{
    my ($linkFile) = @_;
    my $pkg = TagFileRead($linkFile);
    $pkg =~ s~\\~/~g;
    if ($pkg =~ s~^GBE_SANDBOX/~$GBE_SANDBOX/~)
    {
            # If the target sandbox is in the 'deploymode' then the package
            # will not be in the expected location. It will be in a 'build/deploy'
            # subdir. Remove the pkg/name dir to get to the root of the package
            my @dirs = File::Spec->splitdir( $pkg );
            splice(@dirs, -2);
            my $deployBox = catdir(@dirs, 'build', 'deploy');
            $pkg = $deployBox if ( -d $deployBox);
    }

    return $pkg;
}


#-------------------------------------------------------------------------------
# Function        : setScanDepth 
#
# Description     : Set the depth of the build file scan usd in this sandbox 
#
# Inputs          : Command line arguments
#
# Returns         : This function will not return
#
sub setScanDepth
{
    my ( @cmd_opts ) = @_;
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Sandbox Scan Depth") if ($opt_help || ($#ARGV > 0 ));
    Error ("Command must be executed from within a Sandbox") unless ( $GBE_SANDBOX );

    if (defined $ARGV[0])
    {
        FileCreate($depthFile, $ARGV[0]);

        #
        #   Force a rescan of the cached information
        #
        unlink $cacheFile;
        exit 0;
    }

    #
    #   Report the current scan depth
    #
    getScanDepth();
    Information ("Build File scan depth: ". $scanDepth);
    exit (0);

}

#-------------------------------------------------------------------------------
# Function        : getScanDepth  
#
# Description     : Determine the build file scan depth for the current sandbox
#                   Non-default data is maintained in a data file    
#
# Inputs          : None
#
# Returns         : Nothing
#

sub getScanDepth
{
    my $depth = $SCANDEPTH;
    if ( -f $depthFile)
    {
        $depth = TagFileRead($depthFile);
        my $invalid = 0;
        if ($depth !~ m~^\d+$~) {
            $invalid = 1;
        } elsif ( $depth lt 0) {
            $invalid = 1;
        }

        if ($invalid)
        {
            unlink $depthFile;
            Warning ("Invalid scandepth file. File deleted.");
            $depth = $SCANDEPTH;
        }
    }
    $scanDepth = $depth;
}


#-------------------------------------------------------------------------------
# Function        : check_package_existance
#
# Description     : Determine if a given package-version exists
#
# Inputs          : $name           - Package Name
#                   $ver            - Package Version
#
# Returns         : true            - Package exists
#
sub check_package_existance
{
    my ($name, $ver) = @_;
    #
    #   Look in each package archive directory
    #
    foreach my $dpkg ( $GBE_DPKG_SBOX,
                       $GBE_DPKG_LOCAL,
                       $GBE_DPKG_CACHE,
                       $GBE_DPKG_ESCROW,
                       '--NotEscrow',
                       $GBE_DPKG_REPLICA,
                       $GBE_DPKG,
                       $GBE_DPLY,
                       $GBE_DPKG_STORE )
    {
        next unless ( $dpkg );
        if ( $dpkg eq '--NotEscrow' )
        {
            last if ($GBE_DPKG_ESCROW);
            next;
        }

        if ( -d "$dpkg/$name/$ver" )
        {
            Verbose("Exists: $dpkg/$name/$ver");
            return 1;
        }
    }
   return 0;
}

#-------------------------------------------------------------------------------
# Function        : getpkgInterface 
#
# Description     : Locate the packages interface directory 
#
# Inputs          : $fe     - Package Entry 
#
# Returns         : Undef if not found
#                   Path to the packages interface directory
#
sub getpkgInterface
{
    my ($fe) = @_;

    #
    #   Determine the packages 'interface' directory
    #
    my $ifaceDir = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', $fe->{name}, 'sandbox' . ($opt_exact ? $fe->{version} : $fe->{prj}) . '.int');
    return unless -f $ifaceDir; 
    $ifaceDir = TagFileRead($ifaceDir);
    $ifaceDir =~ s~\\~/~g;
    $ifaceDir =~ s~GBE_SANDBOX/~$GBE_SANDBOX/~;
    return $ifaceDir; 
}


#-------------------------------------------------------------------------------
# Function        : calc_sandbox_info
#
# Description     : Examine the sandbox and determine all the important
#                   information
#
#                   Operation will be modified by
#                       $opt_toPackage
#                       $opt_fromPackage
#                       @opt_justPackage
#                       @opt_ignorePackage
#                       $opt_onlyLevel
#
# Inputs          : quiet       undef - noisy
#                               1 - quiet           
#
# Returns         : Will exit if not in a sandbox
#                   Populates global variables
#                       @build_order - build ordered array of build entries
#
sub calc_sandbox_info
{
    my ($quiet) = @_;
    my $buildFileDepth = 0;
    getScanDepth();

    #
    #   Start from the root of the sandbox
    #
    Error ("Command must be executed from within a Sandbox") unless ( $GBE_SANDBOX );
    my $startDir = Getcwd();
    chdir ($GBE_SANDBOX) || Error ("Cannot chdir to $GBE_SANDBOX");

    #
    #   Retain subdir so we can figure out a starting package
    # 
    $startDir = '' unless ($startDir =~ s~^$GBE_SANDBOX/~~);

    #
    #   Locate all packages within the sandbox
    #   These will be top-level directories - one per package
    #
    my @build_list;
    my $currentRootFingerPrint = calc_rootFingerPrint();

    if (-f $cacheFile && !$opt_reScan) 
    {
        #
        #   Read in the location cache
        #   Greatly speeds up the location process
        #
        open( my $lf, '<', $cacheFile) || Error ("Read error: $cacheFile. $!");

        #
        #   Check root directory signature
        #       Has the user added or removed packages from the sandbox
        #
        my $rootFingerPrint = <$lf>;
        $rootFingerPrint =~ s~\s*$~~;

        if ($rootFingerPrint eq $currentRootFingerPrint )
        {
            while (<$lf>)
            {
                s~\s+$~~;
                my @locationEntry = split($;, $_);
                my $pname = $locationEntry[0];
                if ($locationEntry[1] eq ':STOP:') {
                    push @stopped, $pname;
                    Verbose("Package contains stop file: $pname");
                    next;
                }

                #
                #   Locate the build files in each package
                #   Scan the build files and extract dependancy information
                #
                my $bscanner = BuildFileScanner( $pname, 'build.pl',
                                                         '--LocateAll',
                                                         '--LimitDepth=' . $scanDepth,
                                                         $opt_exact ? '--ScanExactDependencies' : '--ScanDependencies',
                                                         '--Stop' );
                unless ($bscanner->setLocation($_)) {
                    Verbose("Build file missing: Force full scan");
                    @build_list = ();
                    last;
                }
                $bscanner->scan();
                my @blist = $bscanner->getInfo();
                unless ($quiet) {
                    Warning ("Package does not have build files: $pname") unless ( @blist );
                    Warning ("Package has multiple build files: $pname") if ( $#blist > 0 );
                }
                push @build_list, @blist;
            }
        }
        close $lf;
    }

    unless (@build_list)
    {
        Message ("Scanning sandbox for build files");
        FileCreate($cacheFile, $currentRootFingerPrint );

        my @locationData;
        foreach my $pname ( glob("*") )
        {
            next if ( $pname =~ m~^\.~ );
            next if ( $pname =~ m~dpkg_archive$~ );
            next if ( $pname eq 'CSV' );
            next if ( $pname eq 'lost+found' );
            next unless ( -d $pname );
            if ( -d $pname . '/sandbox_dpkg_archive' ) {
                Warning("Nested sandbox ignored: $pname");
                next ;
            }
            Verbose ("Package discovered: $pname");

            if ( -f "$pname/stop" || -f "$pname/stop.$GBE_MACHTYPE" )
            {
                push @stopped, $pname;
                Warning("Package contains stop file: $pname");
                push @locationData, join($;, $pname, ':STOP:');
                next;
            }

            #
            #   Locate the build files in each package
            #   Scan the build files and extract dependancy information
            #
            my $bscanner = BuildFileScanner( $pname, 'build.pl',
                                                     '--LocateAll',
                                                     '--LimitDepth=' . $scanDepth,
                                                     $opt_exact ? '--ScanExactDependencies' : '--ScanDependencies',
                                                     '--Stop' );
            $bscanner->scan();
            my @blist = $bscanner->getInfo();
            unless ($quiet) {
                Warning ("Package does not have build files: $pname") unless ( @blist );
                Warning ("Package has multiple build files: $pname") if ( $#blist > 0 );
            }
            push @build_list, @blist;
            push @locationData, $bscanner->getLocation();

            #
            #   Determine max build file depth - just to show user
            #
            foreach ( @blist)
            {
                my $count = () = $_->{dir} =~ m~/~g;
                $buildFileDepth = $count if ($count > $buildFileDepth)
            }
            

        }
        Message ("Max Build File Depth : " . ($buildFileDepth + 1));
        Message ("Build File Scan Depth: ". $scanDepth) if ($SCANDEPTH ne $scanDepth);


        #
        #   Save (cache) location information
        #
        FileAppend($cacheFile, @locationData);
    }

    #
    #   Process each build file and extract
    #       Name of the Package
    #       Dependency list
    #   Build up a hash of dependence information
    #

    my %depends;
    my %multi;
    foreach my $be ( @build_list )
    {
        Verbose( DisplayPath ("Build file: " . $be->{dir} . " Name: " . $be->{file} ));
        #
        #   Sandbox vs Exact processing
        #       Set a suitable display name
        #       Set a suitable tag
        #
        $be->{dname} = $opt_exact ? $be->{full}    : $be->{mname};
        $be->{tag}   = $opt_exact ? $be->{fullTag} : $be->{package};
        $be->{fname} = join ('_', $be->{name}, $be->{version});
        if (length ($be->{dname}) > $maxDname ) {
             $maxDname = length ($be->{dname});
        }

#        DebugDumpData ("be", $be );

        #
        #   Catch multiple builds for the same package
        #   Report later - when we have all
        #
        next unless ( $be->{dname} );
        push @{$multi{$be->{dname}}},$be;
    }

    #
    #   Detect, process and report packages that have multiple builders
    #       An error that can be ignored
    #
    foreach my $dname ( sort keys %multi )
    {
        my $errFn = $opt_multiBuilders ? \&Warning : \&ReportError;
        if ( scalar @{$multi{$dname}} > 1 )
        {
            my @dirList;
            foreach my $be (@{$multi{$dname}}) {
                push @dirList, $be->{dir};
            }           
            &$errFn("Multiple builders for : $dname", @dirList ) unless $opt_multiBuilders > 1;
        }
        else
        {
            #
            #   Add into dependency struct
            #
            foreach my $be (@{$multi{$dname}}) {
                $depends{$be->{tag}} = $be;
            }
        }
    }
    %multi = ();
    ErrorDoExit();

#DebugDumpData ("depends", \%depends );

    #
    #   Remove any dependencies to 'external' packages
    #   These will not be met internally and can be regarded as constant
    #
    #   Split 'depends' into internal (ideps) and external (edeps)
    #       edeps : External Dependencies
    #               Key:        Name;Version
    #               Value:      'tag' - index into packages
    #       ideps : Internal Dependencies
    #               Key:        'tag'   - Index into packages
    #               Value:      'dname' - Display Name
    #
    foreach my $key ( keys %depends )
    {
        foreach my $build ( keys( %{$depends{$key}{depends}} ))
        {
            unless (exists $depends{$build})
            {
                $depends{$key}{'edeps'}{$depends{$key}{depends}{$build}} = $build;
                delete ($depends{$key}{depends}{$build}) ;
                Verbose2( "Not in set: $build");
            }
            else
            {
                $depends{$key}{'ideps'}{$build} = $depends{$build}{dname};
            }
        }
    }

#DebugDumpData ("depends", \%depends );

    #
    #   Determine package build order
    #       Scan the list of packages in the build set and determine
    #       those with no dependencies. These can be built.
    #       Remove those packages as dependents from all packages
    #       Repeat.
    #
    #
    #   Determine the build order
    #
    @build_order = ();
    my $more = 1;
    my $level = 0;
    my %found    = map { $_ => 1 } @opt_ignorePackage;
    my %notFound = map { $_ => 1 } @opt_justPackage;
    my $scan_start = 0;
    my $scan_stop = 0;
    my $scan_active = ($opt_fromPackage) ? 0 : 1;
    $scan_active = 0 if ( !$opt_fromPackage && !$opt_fromPackage && !@opt_ignorePackage && @opt_justPackage );

    while ( $more )
    {
        $more = 0;
        $level++;
        my @build;

        #
        #   Locate packages with no dependencies
        #
        foreach my $key ( sort {lc($a) cmp lc($b)} keys %depends )
        {
            next if ( keys( %{$depends{$key}{depends}} ) );
            push @build, $key;
        }

        foreach my $build ( @build )
        {
            $more = 1;
            my $fe = $depends{$build};
            my $scan_add = $scan_active ? 1 : 0;

            if ( $opt_fromPackage && (($fe->{mname} eq $opt_fromPackage) || ($fe->{name} eq $opt_fromPackage) || ($fe->{fname} eq $opt_fromPackage)))
            {
                $scan_add = 1;
                $scan_active = 1;
                $scan_start = 1;
            }

            if ( $opt_toPackage && (($fe->{mname} eq $opt_toPackage) || ($fe->{name} eq $opt_toPackage) || ($fe->{fname} eq $opt_toPackage)))
            {
                $scan_add = 0;
                $scan_active = 0;
                $scan_stop = 1;
            }

            if ( @opt_justPackage )
            {
                foreach my $pname ( @opt_justPackage )
                {
                    if ( (($fe->{mname} eq $pname) || ($fe->{name} eq $pname) || ($fe->{fname} eq $pname)))
                    {
                        $scan_add = 1;
                        delete $notFound{$pname};
                    }
                }
            }
            
            if ( @opt_ignorePackage )
            {
                foreach my $pname ( @opt_ignorePackage )
                {
                    if ( (($fe->{mname} eq $pname) || ($fe->{name} eq $pname) || ($fe->{fname} eq $pname)))
                    {
                        $scan_add = 0;
                        delete $found{$pname};
                    }
                }
            }

            #
            #   Test for a skip marker
            #
            my $skipFile = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', '_skip.'. $fe->{dname});
            my $skipMachFile = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', '_skip.' . $GBE_MACHTYPE . '.' . $fe->{dname});
            if ( -f $skipFile || -f $skipMachFile )
            {
                Warning("Package marked for skip: $fe->{dname}, $fe->{dir}") unless $quiet;
                $scan_add = 0;
                $fe->{buildSkip} = 1;
            }

            #
            #   Select one level
            #
            if ($opt_onlyLevel && $opt_onlyLevel != $level) {
                $scan_add = 0;
            }

            $fe->{level} = $level;
            $fe->{buildActive} = $scan_add;
            $packages{$build} = $fe;
            push (@build_order, $fe);
            delete $depends{$build};
            delete $fe->{depends};                          # remove now its not needed
        }

        foreach my $key ( keys %depends )
        {
            foreach my $build ( @build )
            {
                delete $depends{$key}{depends}{$build};
            }
        }
    }

    #
    #   Detect bad user specifications
    #
    ReportError ("Specified FromPackage not found: $opt_fromPackage") if ( $opt_fromPackage && !$scan_start );
    ReportError ("Specified ToPackage not found: $opt_toPackage") if ( $opt_toPackage && !$scan_stop );
    ReportError ("Specified ExactPackages not found: ", keys( %notFound) ) if ( %notFound );
    ReportError ("Specified IgnorePackages not found: ", keys( %found) ) if ( %found );
    ErrorDoExit();

    #
    #   Just to be sure to be sure
    #
    if ( keys %depends )
    {
        #DebugDumpData ("depends", \%depends );
        Error( "Internal algorithm error: Bad dependancy walk",
               "Possible circular dependency");
    }

    #
    #   Determine FULL internal dependency list for each package
    #       Walk packages in build order so that we can leverage the calculations
    #       already done.
    # 
    foreach my $fe ( @build_order )
    {
        my @pkgBuildOrder;
        my %pkgSeen;
        my $key = $fe->{tag};
        if (exists $packages{$key}{'ideps'})
        {
            foreach ( keys %{$packages{$key}{'ideps'}} )
            {
                foreach my $ikey (@{$packages{$_}{'AllIdepsOrder'}})
                {
                    push @pkgBuildOrder, $ikey unless $pkgSeen{$ikey};
                    $pkgSeen{$ikey} = 1;
                }
            }
        }
        push @pkgBuildOrder, $key;
        $fe->{'AllIdepsOrder'} = \@pkgBuildOrder;

        #
        #   Is this package in the current directory
        #
        if ($startDir)
        {
            my $matchBase = $startDir . '/'; 
            if ($matchBase =~ m~^$fe->{dir}/~)
            {
                $fe->{buildCurrent} = 1;
                $currentPkgTag = $key;
            }
        }
    }

    #
    #   Now that we have a full dependency list for each package we can calculate a full
    #   usage list. This is a complete list of all package that depend on a package both directly 
    #   and inderectly. Useful for regression testing
    #   
    foreach my $fe ( @build_order )
    {
        foreach my $itag (@{$fe->{'AllIdepsOrder'}})
        {
            next if ($itag eq $fe->{tag});
            push @{$packages{$itag}{usedBy}}, $fe->{tag};
        }
    }

    #
    #   If the CWD is within a package then limit the build to that package and
    #   its dependents, unless user specifies entire sandbox.
    #   
    if ($currentPkgTag && ! $opt_allSandbox )
    {
        if (!$opt_processUsedBy )
        {
            #
            #   Reform the build_order to reflect the current sandbox
            #   The @build_order is an array of package entries
            #   The per-package build order is an array of keys
            #
            my @pkgOrder;
            foreach ( @{$packages{$currentPkgTag}{AllIdepsOrder}})
            {
                push @pkgOrder,  $packages{$_} ;
            }

            # Reform the build order based on original level
            #   Simply done to look pretty - and be consistient when compared with the entire sandbox
            @build_order = sort {$a->{level} <=> $b->{level}} @pkgOrder;

        }
        else
        {
            #
            #   Reform the build order to reflect the consumers of the current package
            #   This does not include the current package. The assumption is that the package
            #   has been built. 
            #
            #   The @build_order is an array of package entries
            #   The per-package build order is an array of keys
            #
            my @pkgOrder;
            foreach ( @{$packages{$currentPkgTag}{'usedBy'}})
            {
                push @pkgOrder,  $packages{$_} ;
            }

            # Reform the build order based on original level
            #   Simply done to look pretty - and be consistient when compared with the entire sandbox
            @build_order = sort {$a->{level} <=> $b->{level}} @pkgOrder;
        }
    }
    else
    {
        $opt_allSandbox = 1;
    }

    #
    #   Calculate the external dependencies
    #       Only process packages that are a part of the build
    #
    #   extern_deps structure
    #       Hash key: 'tag'   - Index into packages
    #          Value: Hash of:
    #                 Key  : Name;Version
    #                 Value: Array of: 'tags' (Index into packages)
    #                                   of packages that use the external
    #                                   component.
    {
        Verbose ("Calculate external dependencies on packages in build");
        %extern_deps = ();

        foreach my $pe (@build_order)
        {
                next unless ( $pe->{buildActive} );
                next unless ( $pe->{'edeps'} );
                foreach ( keys %{$pe->{'edeps'}} )
                {
                    push @{$extern_deps{$pe->{'edeps'}{$_}} {$_} }, $pe->{tag};
                }
        }
    }


#   DebugDumpData("Packages", \%packages);
#   DebugDumpData ("Order", \@build_order);
#   DebugDumpData("External Depends", \%extern_deps );
}

#-------------------------------------------------------------------------------
# Function        : calc_rootFingerPrint 
#
# Description     : Calculate a finger print of all the directories in the
#                   sandbox root.
#                   
#                   Used to determine if the user has added or remove a package
#                   from the sandbox
#
# Inputs          : None
#
# Returns         : SHA1 hash
#
sub calc_rootFingerPrint
{
    my $fpSha1 = Digest->new("SHA-1");

    foreach my $pname ( glob("*") )
    {
        next if ( $pname =~ m~^\.~ );
        next if ( $pname =~ m~dpkg_archive$~ );
        next if ( $pname =~ m~^CVS$~ );
        next unless ( -d $pname );

        $fpSha1->add($pname);

        #
        #   Include stop files in fingerprint too
        #
        $fpSha1->add("$pname/stop" ) if ( -f "$pname/stop" ); 
        $fpSha1->add("$pname/stop.$GBE_MACHTYPE" ) if ( -f "$pname/stop.$GBE_MACHTYPE" ); 

    }
    return $fpSha1->hexdigest;
}

#-------------------------------------------------------------------------------
# Function        : cmd
#
# Description     : Execute a command in all the sandboxes
#                       Locate the base of the sandbox
#                       Locate all packages in the sandbox
#                       Locate all build files in each sandbox
#                       Determine build order
#                       Issue commands for each sandbox in order
#
# Inputs          : Arguments passed to jats build
#
# Returns         : Will exit
#
sub cmd
{
    my ($hcmd, @cmd_opts ) = @_;
    my $opt_reverse;

    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                          'reverse!' => \$opt_reverse,
                          ) || Error ("Invalid command line" );
    SubCommandHelp( $opt_help, $hcmd) if ($opt_help  );

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info();
    if ($opt_reverse) {
        @build_order = reverse @build_order;
    }
    foreach my $fe ( @build_order )
    {
        my $active = displayHeader($fe, { showPath => 1 });

        if ($active)
        {
            my $dir = $fe->{dir};
            my $result = JatsCmd( "-cd=$dir", @cmd_opts);
            if ( $result ) {
                if ( $opt_keepgoing ) {
                    Warning ("Cmd failure");
                } else {
                    Error   ("Cmd failure");
                }
            }
        }
    }

    exit 0;
}

#-------------------------------------------------------------------------------
# Function        : buildcmd
#
# Description     : Build the entire sandbox
#                   The all and the build are similar.
#                   Its not really useful to do a build without a make
#                   so we don't try
#
# Inputs          : Arguments passed to jats make
#
#
# Returns         : Will exit
#

sub buildcmd
{
    my ($cmd, @cmd_opts) = @_;
    my @build_opts;
    my @make_opts;
    my $opt_skip = 1;
    my $opt_clean = 0;

    #
    #   Extract and options
    #
    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                          'skip!' => \$opt_skip,
                          'clean!' => \$opt_clean,
    ) || Error ("Invalid command line" );
    SubCommandHelp( $opt_help, "Command $cmd") if ($opt_help );

    #
    #   Insert default options
    #
    push @build_opts, '-noforce' if ( $cmd eq 'all' );
    push @build_opts, '-force'   if ( $cmd ne 'all' );

    #
    #   Attempt to split the options into build and make options
    #   Only handle the often used options to build.
    #
    foreach  ( @cmd_opts )
    {
        if ( m/^-cache/ || m/^-package/ || m/^-forcebuildpkg/ || m/-expert/) {
            push @build_opts, $_;
        } else {
            push @make_opts, $_;
        }
    }

    push @make_opts, 'all'  unless ( @make_opts );

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info();
    foreach my $fe ( @build_order )
    {
        my $active = displayHeader($fe, { showPath => 1 });
        if ($active) {

            #
            #   Determine build success tag file
            #   If the tag file exists, then see if any files in the package source are more
            #   recent than the tag file
            #
            my $mustBuild = 1;
            my $sigMatch = 0;
            my $tagFile = getPkgFingerPrintFile($fe);

            if ( -e $tagFile ) {
                if ( TagFileMatch($tagFile, genPkgFingerPrint($fe, 'Test')) ) {
                    $sigMatch = 1;
                    if ($opt_skip) {
                        $mustBuild = 0;
                    } else {
                        $mustBuild = 2;
                    }
                } else {
                }
            } else {
                    $mustBuild = 2;
                    $sigMatch = -1;
            }

#Debug0("Skip: $opt_skip, sigMatch: $sigMatch, Build: $mustBuild, Opts: @make_opts",);
            if ($mustBuild)
            {
                if (1)
                {
                    unlink $tagFile;
                    my $dir = $fe->{dir};
                    my $result;

                    $result = JatsCmd( "-cd=$dir", 'build', @build_opts);
                    if ($result)
                    {
                        if ($opt_keepgoing)
                        {
                            Warning( "Build Cmd failure - Keep going");
                            next;
                        }
                        Error ("Build Cmd failure: $dir");
                    }

                    #
                    #   Skip make if we have a prebuilt package
                    #
                    my $mustMake = 1;

                    if ($mustMake)
                    {
                        JatsCmd( "-cd=$dir", 'make', 'clean') if $opt_clean;
                        $result = JatsCmd( "-cd=$dir", 'make',  @make_opts);
                        if ($result)
                        {
                            if ($opt_keepgoing)
                            {
                                Warning( "Make Cmd failure - Keep going");
                                next;
                            }
                            Error ("Make Cmd failure: $dir");
                        }
                    }
                }

                Verbose ("Save fingerprint: $tagFile");
                FileCreate( $tagFile, genPkgFingerPrint($fe,'Generation') );
            }
            else
            {
                Message ("No file changes since last build. Skipping")
            }
        }
    }

    exit 0;
}

#-------------------------------------------------------------------------------
# Function        : clean
#
# Description     : Execute a command in all the sandboxes
#                       Locate the base of the sandbox
#                       Locate all packages in the sandbox
#                       Locate all build files in each sandbox
#                       Determine build order
#                       Issue commands for each sandbox in order
#
# Inputs          : Arguments passed to jats build
#
# Returns         : Will exit
#
sub clean
{
    my ($mode, @cmd_opts ) = @_;

    #
    #   Extract and options
    #
    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts ) || Error ("Invalid command line" );
    SubCommandHelp( $opt_help, "Clean") if ($opt_help );

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info();

    my @cmd = $mode eq 'clobber' ? ('clobber') : ('make', 'clean' );

    #
    #   Clobber and clean need to be done in the reverse order
    #
    foreach my $fe ( reverse @build_order )
    {
        my $active = displayHeader($fe, { showPath => 1 });
        if ($active)
        {
            my $dir = $fe->{dir};
            my $result = JatsCmd( "-cd=$dir", @cmd, @cmd_opts);
            if ($result)
            {
                if ($opt_keepgoing)
                {
                    Warning("Command Failure");
                }
                else
                {
                    Error ("Cmd failure") if ( $result );
                }
            }
        }
    }

    exit 0;
}


#-------------------------------------------------------------------------------
# Function        : cache
#
# Description     : Cache external packages into the sandbox
#
# Inputs          : @opts                   - User options
#
# Returns         : Nothing
#
sub cache
{
    my (@cmd_opts) = @_;

    getOptionsFromArray ( \@cmd_opts ) || Error ("Invalid command line" );
    SubCommandHelp( $opt_help, "Cache") if ($opt_help || $#cmd_opts >= 0 );

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    Message("Cache External Dependencies");
    Error ("GBE_DPKG_CACHE not defined") unless $GBE_DPKG_CACHE;
    calc_sandbox_info();

    JatsTool ('cache_dpkg', "core_devl/jats2_current" );

    #
    #   Walk the list of external dependencies and cache each one
    #
    foreach my $de ( sort keys %extern_deps )
    {
        my @vlist = keys %{$extern_deps{$de}};
        foreach my $pve ( @vlist )
        {
            my ($pn,$pv) = split( $; , $pve );
            Message ("Cache ${pn} ${pv}");
            JatsTool ('cache_dpkg', "${pn}/${pv}" );
        }
    }

    exit 0;

}

#-------------------------------------------------------------------------------
# Function        : populate
#
# Description     : Populate the sandbox with package versions
#
#
# Inputs          : commands            - Array of command line arguments
#                   Mode-0:
#
#                       pkg_name pkg_version        - Import files for named package
#                       options:
#                           -recurse                - Import dependent packages too
#                           -missing                - Import dependencies not in dpkg_archive
#                           -test                   - Show what would be done
#                           -extractfiles           - Extract file, no view
#
#
#
#
# Returns         : Does not return
#
use JatsRmApi;
use DBI;

#
#   Data Base Interface
#
my $RM_DB;
my $PopLevel = 0;
my %PopPackage;
my @StrayPackages;
my @PopBase;

sub populate
{
    my (@cmd_opts ) = @_;
    my $opt_missing = 0;
    my $opt_recurse = 0;
    my $opt_test = 0;
    my $opt_show = 0;
    my $opt_extractfiles;
    my @opt_extract = qw(-extract);
    my @opt_fnames;
    my @opt_exclude;
    my $opt_all;


    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                "all"               => \$opt_all,
                "missing"           => \$opt_missing,
                "test"              => \$opt_test,
                "show"              => \$opt_show,
                "recurse:100"       => \$opt_recurse,
                'excludePackage:s'  => sub{ opts_add2List( \@opt_exclude, @_ )},
                ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Populate Sandbox") if ($opt_help );

    #
    #   Sanity tests
    #
    Error ("Populate: -missing and -all options are mutually exclusive")
        if ( $opt_missing && $opt_all );

    #
    #   Extract options for the jats extract utility
    #
    foreach ( @cmd_opts )
    {
        if ( m~^-~ ) {
            push ( @opt_extract, $_);
        } else {
            push ( @opt_fnames, $_);
        }
    }

    #
    #   Allow exactly zero or two 'bare' arguments
    #   Create an array of package-versions to be processed.
    #
    if ( $#opt_fnames >= 0 )
    {
        Error ("Populate: Must specify both a package name and version")
            if ( $#opt_fnames != 1 );
        push @PopBase, join( $;, @opt_fnames );
    }
    elsif ( $opt_missing || $opt_all )
    {
        #
        #   User has not provided a package name to extract
        #   Assume that the user will want all or missing dependencies
        #
        #   Determine packages that are not present
        #
        calc_sandbox_info();

        #
        # Scan for missing dependencies
        #
        foreach my $de ( sort keys %extern_deps )
        {
            my @vlist = keys %{$extern_deps{$de}};
            foreach my $pve ( @vlist )
            {
                my ($pn,$pv) = split( $; , $pve );
                unless ($opt_missing && check_package_existance( $pn, $pv ))
                {
                    push @PopBase, join( $;, $pn , $pv );
                }
            }
        }
    }
    else
    {
        Error ("No command or option specified. See help for command usage");
    }

    #
    #   Process the list of package-versions
    #   These are top level packages. Get details from Release Manager
    #
    #DebugDumpData("Data", \@PopBase );
    $PopLevel = 0;
    foreach my $entry ( @PopBase )
    {
        my ($pname, $pver ) = split( $; , $entry);

        my $pv_id = getPkgDetailsByName($pname, $pver);
        Error ("populate: $pname, $pver not found in Release Manager" )
            unless ( $pv_id );
        getPkgDetailsByPV_ID($pv_id);
    }
    #
    #   If recursing then process packages that have yet to
    #   be processed. At the start there will be the initial user specified
    #   packages on the list. Place a marker at the end so that we can
    #   determine how far we are recursing down the dependency tree.
    #
    $opt_recurse = ($opt_all ? 100 : $opt_recurse);
    if ( $opt_recurse )
    {
        my $marker = join($; , '_NEXT_LEVEL_', 0, 0 );
        push @StrayPackages, $marker;
        $PopLevel++;

        while ( $#StrayPackages >= 0 )
        {
            my ($name, $ver, $pv_id) = split($;, shift @StrayPackages);

            #
            #   Marker.
            #   Increment the level of recursion
            #   Detect end conditions
            #
            if ( $name eq '_NEXT_LEVEL_' )
            {
                last unless ($#StrayPackages >= 0 );
                $PopLevel++;
                last if ( $PopLevel > $opt_recurse );
                push @StrayPackages, $marker;
                next;
            }

            next if ( exists $PopPackage{$name}{$ver}{done} );
            getPkgDetailsByPV_ID ( $pv_id );
            #print "Stray: $pv_id, $name, $ver\n";
        }
    }
    #DebugDumpData("Data", \%PopPackage );

    #
    #   Determine packages that need to be extracted
    #   Sort alphabetically - case insensitive
    #
    foreach my $pname ( sort {lc($a) cmp lc($b)} keys %PopPackage )
    {
        pkgscan:
        foreach my $pver ( sort keys %{$PopPackage{$pname}} )
        {
            #
            #   Create a nice view name for the extraction
            #   Will also be used to test for package existence
            #
            my $vname = "$pname $pver";
            $vname =~ s~ ~_~g;
            $vname =~ s~__~~g;

            if ( -d "$GBE_SANDBOX/$vname" )
            {
                Warning("Package already in sandbox: $pname, $pver");
                next;
            }

            #
            #   If scanning for missing packages, then examine archives
            #   for the packages existence. Don't do this on level-0 packages
            #   These have been user specified.
            #
            if ( $opt_missing && $PopPackage{$pname}{$pver}{level}  )
            {
                my $found = check_package_existance( $pname, $pver );
                if ( $found )
                {
                    Verbose ("Package found in archive - skipped: $pname, $pver");
                    next;
                }
            }

            #
            #   Has the user specifically excluded this package
            #   Allow three forms
            #       packageName
            #       packageName_Version
            #       packageName.projectName
            #
            my $excluded;
            foreach my $ename ( @opt_exclude )
            {
                if ( $ename eq $pname ) {
                    $excluded = 1;
                } elsif ($ename eq $pname .'_' . $pver ) {
                    $excluded = 1;
                } else {
                    if ( $pver =~ m~(\.[a-z]{2,4})$~ )
                    {
                        $excluded = ($ename eq $pname . $1 );
                    }
                }

                if ( $excluded )
                {
                    Message ("Package excluded by user - skipped: $pname, $pver");
                    next pkgscan;
                }
            }

            #
            #   Generate commands to extract the package
            #
            my $vcstag = $PopPackage{$pname}{$pver}{vcstag};
            my @cmd = qw(jats_vcsrelease);
            push @cmd, "-view=$vname", "-label=$vcstag", @opt_extract;
            if ( $opt_show )
            {
                Message ("$pname $pver");
            }
            elsif ( $opt_test )
            {
                Message "jats " . QuoteCommand (@cmd );
            }
            else
            {
                Message "Extracting: $pname $pver";
                my $rv = JatsCmd (@cmd);
                Error ("Package version not extracted")
                    if ( $rv );
            }
        }
    }

    #
    # This command does not return
    #
    exit (0);
}

#-------------------------------------------------------------------------------
# Function        : buildfilter 
#
# Description     : Manipulate the sandbox build filter
#
# Inputs          : Optional filter names
#                       +NAME - will add filter
#                       -NAME will remove it
#                       NAME will set it
#                   No args will just display the build filter
#
# Returns         : Does not return
#
sub buildfilter
{
    my (@cmd_opts ) = @_;
    my @filter_list;
    my $first_arg = 1;
    my $modified;

    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts ) || Error ("Invalid command line" );

    SubCommandHelp( $opt_help, "Buildfilter") if ($opt_help );

    #
    #   Set the initial filter list
    #   This will have been parsed by JATS before we get here
    #
    UniquePush (\@filter_list, split( /[,\s]+/, join(',', $GBE_BUILDFILTER)));

    #
    #   Extract options for the jats extract utility
    #
    foreach ( @cmd_opts )
    {
        if (m~^\+(.*)~)
        {
            UniquePush( \@filter_list, $1);
        }
        elsif (m~^\-(.*)~)
        {
            ArrayDelete( \@filter_list, $1);
        }
        else
        {
            @filter_list = () if ($first_arg);
            UniquePush( \@filter_list, $_);
        }
        $first_arg = 0;
        $modified = 1;
    }

    #
    #   Display the results to the user
    #
    @filter_list = sort @filter_list;
    Message('BuildFilter:', @filter_list);

    #
    #   Write out a new file
    #
    if ($modified)
    {
        Error ("Command must be executed from within a Sandbox") unless ( $GBE_SANDBOX );
        FileCreate($filterFile, @filter_list);
    }

    #
    # This command does not return
    #
    exit (0);
}


#-------------------------------------------------------------------------------
# Function        : getPkgDetailsByName
#
# Description     : Determine the PVID for a given package name and version
#
# Inputs          : $pname          - Package name
#                   $pver           - Package Version
#
# Returns         :
#

sub getPkgDetailsByName
{
    my ($pname, $pver) = @_;
    my $pv_id;
    my (@row);

    connectRM(\$RM_DB) unless ($RM_DB);

    # First get details for a given package version

    my $m_sqlstr = "SELECT pv.PV_ID, pkg.PKG_NAME, pv.PKG_VERSION" .
                    " FROM RELEASE_MANAGER.PACKAGE_VERSIONS pv, RELEASE_MANAGER.PACKAGES pkg" .
                    " WHERE pkg.PKG_NAME = \'$pname\' AND pv.PKG_VERSION = \'$pver\' AND pv.PKG_ID = pkg.PKG_ID";
    my $sth = $RM_DB->prepare($m_sqlstr);
    if ( defined($sth) )
    {
        if ( $sth->execute( ) )
        {
            if ( $sth->rows )
            {
                while ( @row = $sth->fetchrow_array )
                {
                    $pv_id = $row[0];
                    my $name = $row[1];
                    my $ver = $row[2];
                    Verbose( "getPkgDetailsByName :PV_ID= $pv_id");
                }
            }
            $sth->finish();
        }
    }
    else
    {
        Error("Prepare failure" );
    }
    return $pv_id;
}

#-------------------------------------------------------------------------------
# Function        : getPkgDetailsByPV_ID
#
# Description     : Populate the Packages structure given a PV_ID
#                   Called for each package in the SBOM
#
# Inputs          : PV_ID           - Package Unique Identifier
#
# Returns         : Populates Package
#
sub getPkgDetailsByPV_ID
{
    my ($PV_ID) = @_;
    my $foundDetails = 0;
    my (@row);

    connectRM(\$RM_DB) unless ($RM_DB);

    # First get details from pv_id

    my $m_sqlstr = "SELECT pv.PV_ID, pkg.PKG_NAME, pv.PKG_VERSION, release_manager.PK_RMAPI.return_vcs_tag($PV_ID)" .
                    " FROM RELEASE_MANAGER.PACKAGE_VERSIONS pv, RELEASE_MANAGER.PACKAGES pkg " .
                    " WHERE pv.PV_ID = \'$PV_ID\' AND pv.PKG_ID = pkg.PKG_ID";

    my $sth = $RM_DB->prepare($m_sqlstr);
    if ( defined($sth) )
    {
        if ( $sth->execute( ) )
        {
            if ( $sth->rows )
            {
                while ( @row = $sth->fetchrow_array )
                {
                    my $pv_id       = $row[0];
                    my $name        = $row[1];
                    my $ver         = $row[2];
                    my $vcstag      = $row[3] || '';

                    $vcstag =~ tr~\\/~/~;
                    Verbose ("getPkgDetailsByPV_ID: $PV_ID, $name, $ver, $vcstag");

                    $PopPackage{$name}{$ver}{pvid} = $PV_ID;
                    $PopPackage{$name}{$ver}{done} = 1;
                    $PopPackage{$name}{$ver}{vcstag} = $vcstag;
                    $PopPackage{$name}{$ver}{level} = $PopLevel;
                    getDependsByPV_ID( $pv_id, $name, $ver );
                }
            }
            else
            {
                Warning ("No Package details for: PVID: $PV_ID");
            }
            $sth->finish();
        }
        else
        {
            Error("getPkgDetailsByPV_ID: Execute failure", $m_sqlstr );
        }
    }
    else
    {
        Error("Prepare failure" );
    }
}

#-------------------------------------------------------------------------------
# Function        : getDependsByPV_ID
#
# Description     : Extract the dependancies for a given package version
#
# Inputs          : $pvid
#
# Returns         :
#
sub getDependsByPV_ID
{
    my ($pv_id, $pname, $pver) = @_;

    connectRM(\$RM_DB) unless ($RM_DB);

    #
    #   Now extract the package dependencies
    #
    my $m_sqlstr = "SELECT pkg.PKG_NAME, pv.PKG_VERSION, pd.DPV_ID" .
                   " FROM RELEASE_MANAGER.PACKAGE_DEPENDENCIES pd, RELEASE_MANAGER.PACKAGE_VERSIONS pv, RELEASE_MANAGER.PACKAGES pkg" .
                   " WHERE pd.PV_ID = \'$pv_id\' AND pd.DPV_ID = pv.PV_ID AND pv.PKG_ID = pkg.PKG_ID";
    my $sth = $RM_DB->prepare($m_sqlstr);
    if ( defined($sth) )
    {
        if ( $sth->execute( ) )
        {
            if ( $sth->rows )
            {
                while ( my @row = $sth->fetchrow_array )
                {
                    my $name = $row[0];
                    my $ver = $row[1];

                    Verbose2( "       Depends: $name, $ver");
                    unless ( exists $PopPackage{$name} && exists $PopPackage{$name}{$ver} && exists $PopPackage{$name}{$ver}{done} )
                    {
                        push @StrayPackages, join($;, $name, $ver, $row[2] );
                    }
                }
            }
            $sth->finish();
        }
    }
    else
    {
        Error("GetDepends:Prepare failure" );
    }
}

#-------------------------------------------------------------------------------
# Function        : getOptionsFromArray
#
# Description     : Like getOptions, but handles an array
#                   Provided as the version of Perl used does not have one
#
# Inputs          : pArray                  - Ref to array
#                   ....                    - GetOptions arguments
#
# Returns         : 
#
sub getOptionsFromArray
{
    my ($pArray, %args) = @_;

    #
    #   Helper to parse --multiBuilders
    #
    my $parseMulti = sub {
        my ($ref, $value) = @_;
        $value = 'error' unless length($value);
        my %valid = (error => 0, report => 1, ignore => 2);
        unless (exists $valid{lc $value}) {
            die ("Invalid option for  --$ref->{name}\n");
        };
        $opt_multiBuilders = $valid{lc $value};
    };


    #
    #   Common arguments
    #
    my %commonOptions = (
        'help|h:+'          => \$opt_help,
        'manual:3'          => \$opt_help,
        'verbose:+'         => \$opt_verbose,
        'topackage:s'       => \$opt_toPackage,
        'frompackage:s'     => \$opt_fromPackage,
        'justpackage:s'     => sub{ opts_add2List( \@opt_justPackage, @_ )},
        'ignorepackage:s'   => sub{ opts_add2List( \@opt_ignorePackage, @_ )},
        'entireSandBox!'    => \$opt_allSandbox,
        'users!'            => \$opt_processUsedBy,
        'keepgoing!'        => \$opt_keepgoing,
        'rescan!'           => \$opt_reScan,
        'multiBuilders:s'   => $parseMulti, 
        'onlylevel:i'       => \$opt_onlyLevel,
        );

    #
    #   Merge in the user options
    #
    @commonOptions{keys %args} = values %args;

    local ( @ARGV );
    @ARGV = @$pArray;
    my $rv = GetOptions ( %commonOptions );
    @$pArray = @ARGV;

    ErrorConfig('verbose' => $opt_verbose );
    return $rv;
}

#-------------------------------------------------------------------------------
# Function        : displayHeader 
#
# Description     : Display a build header, if the entry is active
#                   ie: Between a --From and --To
#
# Inputs          : $fe             - Build entry
#                   Hash of options
#                       indent          => Text       - Indent text
#                       showPath        => Bool
#                       showSimplePath  => Bool
#                       testFingerPrint => Bool
#
# Returns         : True if this entry is to be fully process
#                   False if its being skipped 
#
sub displayHeader
{
    my $fe = shift @_;
    if ($fe->{buildActive} || $fe->{buildSkip})
    {
        my $args = pop @_ if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
        #    my ($fe, %args) = @_;

        my $indent = $args->{indent} || '';
        my $showPath = $args->{showPath};
        my $showSimplePath = $args->{showSimplePath};

        my ($status, $estatus) = addBuildInfo($fe, $args);
        $estatus = '' if $showSimplePath;
         
        my $msg1 = $indent . sprintf('Level:%02d [%s] Name: %s', $fe->{level}, $status, $fe->{dname} . $estatus );
        if ($showSimplePath) {
            my $msg1Len = length($msg1);
            $msg1 = sprintf("%-*s Path: %s", 26 + $maxDname ,$msg1, $fe->{dir}); 
        }
        if ( $showPath) {
            my $msg1Len = length($msg1);
            if ($msg1Len < 80) {
                $msg1 .= ' ' . '=' x (79 - $msg1Len);
            }
        }
        Message( $msg1 ,  $showPath ? DisplayPath ("        Path: $fe->{dir}" ) : undef);
    }

   return $fe->{buildActive};
}

#-------------------------------------------------------------------------------
# Function        : addBuildInfo 
#
# Description     : Add some build info status
#                   It will have a .sig file if its been sucessfully built
#                   
#                   StatusFlags
#                   Position 1: S - Build Skipped, s - Build Suppressed
#                   Position 2: Blank - Not processed  , - = NoBuild, B - Built * - Current package
#                   Position 3: E - Exists in dPkg, L - Local, M - Both local and dPkg
#                   Position 4: G - Good Fingerprint, b - Bad Fingerprint
#
# Inputs          : $fe     - Package info 
#
# Returns         : StatusFlags
#                   StatusText
#
sub addBuildInfo
{
    my ($fe, $args) = @_;
    my $txt = '';
    my $statusS = ' ';
    my $statusB = ' ';
    my $statusP = ' ';
    my $statusF = ' ';

    unless ($fe->{buildActive}) {
        $txt .= ($fe->{buildSkip}) ? ' (Build Skipped)' : ' (Build Suppressed)';
        $statusS = ($fe->{buildSkip}) ? 'S' : 's';
    }

    if ($fe->{buildCurrent})
    {
        $statusB = '*';
        $txt .= ' (Current Package)';
    }

    my $fpFile = getPkgFingerPrintFile($fe);
    if (-f $fpFile)
    {
        my $nobFile = $fpFile;
        $nobFile =~ s~ffp$~nob~;
        if (-f $nobFile) {
            $txt .= ' [NoBuild]';
            $statusB = '-';
        } else {
            $txt .= ' [Built]';
            $statusB = 'B';
        }

        if ($args->{testFingerPrint})
        {
            if ( TagFileMatch($fpFile, genPkgFingerPrint($fe, 'Test')) )
            {
                $txt .= ' [GoodFinger]';
                $statusF = 'G';
            } else {
                $txt .= ' [BadFinger]';
                $statusF = 'b';
            }
        }
    }

    my $preBuilt = check_package_existance($fe->{name}, $fe->{version});
    if ($preBuilt) {
        $txt .= ' [dPkg]';
        $statusP = 'E';
    }
    
    my $plink = catdir( $GBE_DPKG_SBOX, $fe->{name}, 'sandbox.' . $fe->{prj} . '.lnk' );
    my $linkTarget = getPackageLink ($plink);

    Verbose ("Sandbox link: $plink -> $linkTarget");
    if (-d $linkTarget) {
        $txt .= ' [Local]';
        if ($preBuilt) {
            $statusP = 'M';
        } else {
            $statusP = 'L';
        }
    }

    return "$statusS$statusB$statusP$statusF", $txt ;

}

#-------------------------------------------------------------------------------
# Function        : opts_add2List
#
# Description     : Option processing helper
#                   Add comma separated options to an array
#                   User can then add items one at a time, or several at once
#
# Inputs          : aref        - Ref to an array to extent
#                   arg2        - Option name
#                   arg3        - Option value
#
# Returns         : 
#
sub opts_add2List
{
    my( $ref, $name, $value) = @_;
    if ( $value )
    {
        foreach ( split(/\s*,\s*/,$value) )
        {
            push @{$ref}, $_;
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : genPkgFingerPrint 
#
# Description     : Generate a fingerprint over all files in the packages tree as
#                   well as the package dependencies
# 
#                   Only used to detect changes to files in the subdir
# 
#                   This version does not actually generate a fingerprint over
#                   the data file, rather the metadata of the file. This is much (much)
#                   faster as there is no file i/o.
#                   
#                   It does assume that the file metadata will change if the file is changed
#                   Will also detect the addition / deletion of files
#                   
#                   Note: This signature is not machine - type safe
#                         It will vary between machines (windows/Linux/...)
#                         At the moment the Sandbox is really a single machine tool
#
# Inputs          : $fe            - Package entry of the package to process
#                   $mode          - Diagnostic: mode
#
# Returns         : A SHA1 hash over all the files
#                   
#
sub genPkgFingerPrint
{
    my ($fe, $mode) = @_;
    my $genPkgFingerPrintSha1;
    my $genPkgFingerPrintCount;
    my @fpdata;

    #
    #   Get the package GbeFiles.cfg file
    #       This is held in the interface directory and is created during the build
    #       Since the fingerprint is only genertated AFTER a successful build the file will always
    #       be available
    #       
    #       All we need from this file is a list of Src directories that were discovered during
    #       the build. Unfortuanatley they are not always below the root of the package.
    #
    my $ifaceDir = getpkgInterface($fe);
    return 0 unless defined $ifaceDir;
    return 0 unless ToolsetFiles::GetDataFile($ifaceDir); 

    #
    #   Generate a list of directories in the package
    #   This is the root directory and all other Src directories discovered
    #
    my @dirList = ToolsetFiles::GetSubTrees($ifaceDir);
    Error ("Internal:ToolsetFiles::GetDirList for $fe->{dname} not populated" ) unless @dirList;

    #
    #   Generate a hash of toolset files and toolset-internal directories
    #       These won't be included in the finger print as they are subject
    #       to change by a 'build'.
    #
    my %toolsetFiles = map { $_ => 1 } ToolsetFiles::GetFiles($ifaceDir, 1);
    my %toolsetDirs  = map { $_ => 1 } ToolsetFiles::GetBuildDirs($ifaceDir);

    #
    #   Create the hash
    #
    $genPkgFingerPrintSha1 = Digest->new("SHA-1");
    push @fpdata, $mode;

    #
    #   Include all dependent packages in the fingerprint
    #       We are using the sandbox fingerprint of dependent packages
    #       This will ensure that a change in a package will ripple through
    #
    foreach my $idep ( sort keys %{$fe->{ideps}})
    {
        my $ipkg = $packages{$idep};
        my $tagFile = getPkgFingerPrintFile($ipkg); 
        my $tag = TagFileRead($tagFile);
        my $text = "$tagFile: $tag";
        $genPkgFingerPrintSha1->add($text);
#Debug0("genPkgFingerPrint: $text, ", $genPkgFingerPrintSha1->clone->hexdigest() );
        push @fpdata, $text . ':' . $genPkgFingerPrintSha1->clone->hexdigest();
    }
    
    #
    #   Anonymous sub: findFile wanted function
    #   Unlike the generation of the package signature, we don't need to
    #   exclude any files.
    #
    my $wanted = sub {
        my $item = $File::Find::name;

        #
        #   Get file info
        #       Kill of the last access time - not useful
        #       
        #       Need to exclude files that change during a null build
        #           /interface/GbeFiles.cfg
        #           /build.log
        #           These files are tracked in GbeBuild.cfg
        #       Need to directories that change during a null build
        #           These files are tracked in GbeBuild.cfg
        #           
        #       Symlinks present problems.
        #       Some packages generate symlinks as they create file system images. The
        #       links are not designed to be interpreted in the context of the current
        #       computer. As a result.
        #           Some may be broken - on the current machine
        #           Some may address files that change - ie: /proc, /var, /dev
        #           Some may address files that such as /afc, /root
        #       Don't want to discard all symlinks. Many of them will address package
        #       dependencies and we do want to detect changes, but those changes will
        #       be picked up by the packages fingerprint.
        #       
        #       Directories also appear to be a problem
        #       The created and modified date-times appear to be modified for no good reason
        #       
        #       Current solution: Do not include 'stat' data for ANY symlink or directory
        #
        my @data = stat($item);
        my $text;

        if ( exists $toolsetFiles{$item}) {
            $text = "$item : SKIPPED FILE : ";

        }  elsif (-d $item ) {
            $text = "$item : DIRECTORY";
            if ( exists $toolsetDirs{$item}) {
                $File::Find::prune = 1;
                $text = "$item : SKIP INTERNAL DIRECTORY";
            }

        }  elsif (-l $item ) {
            if ( ! @data) {
                $text = "$item : BROKEN SYMLINK : ";
            } else {
                my $linkTarget = readlink($item) || 'Read Error';
                $text = "$item :SYMLINK: $linkTarget";
            }

        } else {
            $data[8] = 0;               # atime - will change
            $data[12] = '-';            # blocks - seen to change for unknown reasons
            for my $ii ( 0 .. $#data) {
                unless (defined $data[$ii]) {
                    $data[$ii] = 'xx';
                }
            }
            $text = "$item : @data";
        }
        $genPkgFingerPrintCount++;
        $genPkgFingerPrintSha1->add($text);
#Debug0("genPkgFingerPrint: $text, ", $genPkgFingerPrintSha1->clone->hexdigest() );
        push @fpdata, $text . ':' . $genPkgFingerPrintSha1->clone->hexdigest();
    };

    #
    #   Process all files in the package
    #
    $genPkgFingerPrintCount = 0;
    my $dir = $fe->{dir};
    File::Find::find( { wanted => $wanted , no_chdir => 1}, @dirList );
#Debug0("genPkgFingerPrint: $dir, $genPkgFingerPrintCount, ", $genPkgFingerPrintSha1->clone->hexdigest() );
    push @fpdata, $dir . ':'. $genPkgFingerPrintCount . ':' . $genPkgFingerPrintSha1->clone->hexdigest();

    #
    #   Debugging - delete later
    #   Save FP data to a file
    #
   #my $fpDebugFile = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', $fe->{name} . '.' . $fe->{prj} . '_' . time() . '.fpd');
   #Debug0("fpDebugFile: $fpDebugFile");
   #FileCreate($fpDebugFile, @fpdata);

    return $genPkgFingerPrintSha1->hexdigest;
}


#-------------------------------------------------------------------------------
# Function        : getPkgFingerPrintFile 
#
# Description     : Return the package file that contains the packages Fast Finger Print
#
# Inputs          : $fe     - Package entry 
#
# Returns         : Full path to the packages fingerprint tag file
#
sub getPkgFingerPrintFile
{
    my ($fe) = @_;
    my $tagFile = catdir($GBE_SANDBOX, 'sandbox_dpkg_archive', $fe->{name}, 'sandbox.' . ($opt_exact ? $fe->{version} : $fe->{prj} )  . '.ffp');
    return $tagFile;
}


#-------------------------------------------------------------------------------
# Function        : SubCommandHelp
#
# Description     : Provide help on a subcommand
#
# Inputs          : $help_level             - Help Level 1,2,3
#                   $topic                  - Topic Name
#
# Returns         : This function does not return
#
sub SubCommandHelp
{
    my ($help_level, $topic) = @_;
    my @sections;
    #
    #   Spell out the section we want to display
    #
    #   Note:
    #   Due to bug in pod2usage can't use 'head1' by itself
    #   Each one needs a subsection.
    #
    push @sections, qw( NAME SYNOPSIS ) ;
    push @sections, qw( ARGUMENTS OPTIONS )     if ( $help_level > 1 );
    push @sections, qw( DESCRIPTION EXAMPLES )  if ( $help_level > 2 );

    #
    #   Extract section from the POD
    #
    pod2usage({-verbose => 99,
               -noperldoc => 1,
               -sections => $topic . '/' . join('|', @sections) } );
}

#-------------------------------------------------------------------------------
#   Documentation
#   NOTE
#
#   Each subcommand MUST have
#   head1 section as used by the subcommand
#       This should be empty, as the contents will NOT be displayed
#   head2 sections called
#       NAME SYNOPSIS ARGUMENTS OPTIONS DESCRIPTION EXAMPLES
#
#=head1 xxxxxx
#=head2 NAME
#=head2 SYNOPSIS
#=head2 ARGUMENTS
#=head2 OPTIONS
#=head2 DESCRIPTION
#=head2 EXAMPLES
#

=pod

=head1 NAME

jats_sandbox - Build in a Development Sandbox

=head1 SYNOPSIS

  jats sandbox [options] command [command options]

 Options:
    -help[=n]                  - Display help with specified detail
    -help -help                - Detailed help message
    -man                       - Full documentation

 Options for recursion control:
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
 Options common to all commands:
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore

 Commands:
    help                - Same as -help
    create              - Create a sandbox in the current directory
    populate            - Populate the sandbox with packages
    delete              - Delete the sandbox
    info [[-v]-v]       - Sandbox information. -v: Be more verbose
    buildfilter         - Modify and display sandbox buildfilter
    [un]skip            - Mark a package to be skipped during the build
    fingerprint         - Various fingerprint operations
    testlinks           - Test / Delete broken package symlinks
    scandepth           - Set/Display the build file scan depth
    cmd                 - Do commands in all sandbox components
    all                 - Do 'build', if required, then a make in all components
    build               - Force 'build and make' in all sandbox components
    make                - Do 'make' in all sandbox components
    clean               - Do 'make clean' in all sandbox components
    clobber             - Do 'build clobber' is all sandbox components
    cache               - Cache external dependent packages

 Use the command
    jats sandbox 'command' -h
 for command specific help

=head1 OPTIONS

=over 8

=item B<-help[=n]>

Print a brief help message and exits.
There are three levels of help

=over 8

=item   1

Brief synopsis

=item   2

Synopsis and option summary

=item   3

Detailed help in man format

=back 8

=item B<-help -help>

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

=item B<-man>

Prints the manual page and exits. This is the same a -help=3

=item B<-onlyLevel=number>

This option is available in all commands that process multiple packages.
Package processing will be limited to the specified level. 

This can be used to perform a multi-machine build. Level-1 builds are performed 
on all machines and the results merged into the common package store, then Level-2 builds 
are performed on all machines.

Level-1 packages have no dependencies.
 
Level-2 packages only have dependancies on Level-1 packages

Level-N packages only have dependancies on packages below Level N-1

=item B<-toPackage=name>

This option is available in all commands that process multiple packages.
Package processing will stop at the named package.

The package name can be specified in one of three forms:

=over 8

=item 1 

Just the package name. ie: MyPackage

=item 2 

The package name and the project suffix. ie: MyPackage.prj

=item 3 

The package name and version, joined with an underscore: ie: MyPackage_1.0.0000.prj

=back 8

=item B<-fromPackage=name>

This option is available in all commands that process multiple packages.
Package processing will start at the named package.

The package name can be specified in one of the three forms described under the '-toPackage' option.

=item B<-justPackage=name[,name]>

This option is available in all commands that process multiple packages. The
named packages will be processed in the correct build order. Packages that are
not named will be skipped, unless the package is being processed due to
being in the 'fromPackage' to 'toPackage' range.

Multiple packages can be named either by separating names with a comma, or
with multiple options.

The package names can be specified as a mix of the three forms described under the '-toPackage' option.

=item B<-ignorePackage=name[,name]>

This option is available in all commands that process multiple packages. The
named packages will not be processed.

Multiple packages can be named either by separating names with a comma, or
with multiple options.

The exclusion of a package takes precedence over its inclusion.

The package names can be specified as a mix of the three forms described under the '-toPackage' option.

=item B<-[no]entireSandbox>

This option will override the automatic package localisation that will occur if the user starts the command
within a subdirectory of a package within the sandbox and will process the entire sanbbox.

If the user start the command within a subdirectory of a package then the sandbox commands will be localised
to the current package and the dependencies of the package.

=item B<-[no]users>

This option will completely change the packages considered to be built. The normal operation is to consider
the current package and all packages that it depends upon.

This option will consider all packages that 'use' the current package, either directly or indirectly. It does not 
include the 'current' pakage in this list. The assumption is that the current package has been sucessfully built 
and needs to tested.

This option will work when they is a current package to be processed and not the entire sandbox.

The intended purpose of this option is to simplify regression testing.

=item B<-[no]keepgoing>

This options controls the behaviour of the command when an error is encountered.

The default operation is to terminate the command on the package with the
error. This can be modified so that errors are ignored.

=item B<-[no]reScan>

This option controls the process of locating build files within the sandbox.

The default operation is to scan the sandbox and to cache the location of the build files.

If a package is added or removed from the sandbox, then the sandbox will need to be rescanned.
Jats will detect when a package has been added or removed, but if the internal structure of the
packages has changed the cached data may be incorrect. 

=item B<-multiBuilders=mode>

If a package-name can be built by multiple packages then the sandbox processing will
normally report an error.

This option allow the error to be reported as a warning or to be ignored altogether. The named package will be 
excluded from the build set.

Valid values for 'mode' are: error, report and ignore.  The default mode is 'error'.

One workaround is to use a 'stop' file. Any directory that contains 'stop' file will not be scanned for
build files. If a 'stop' file is added then it may defeat caching of build information. The information will 
need to be refreshed with the '--rescan' option. The 'stop' file should not be version controlled.

=back

=head1 DESCRIPTION

This program is the primary tool for the maintenance of Development Sandboxes.

More documentation will follow.

=head2 SANDBOX DIRECTORY

The sandbox directory is marked as being a sandbox through the use of the
'sandbox create' command. This will create a suitable structure within the
current directory.

Several JATS commands operate differently within a sandbox. The 'extract' and
'release' commands will create static views within the sandbox and not the
normal directory. The 'sandbox' sub commands can only be used within a sandbox.

The sandbox directory contains sub directories, each should contain a single
package. Sub directories may be created with the 'jats extract' command or with the
'jats sandbox populate' command.

Note: Symbolic links are not supported. They cannot work as the sandbox mechanism
requires that all the packages be contained within a sub directory tree so
that the root of the sandbox can be located by a simple scan of the directory
tree.

If a package subdirectory contains a file called 'stop' or 'stop.
<GBE_MACHTYPE>', then that package will not be considered as a part of the
build-set. A 'stop' file will prevent consideration for all build platforms. The 'stop.
<GBE_MACHTYPE>' will only prevent consideration if being built on a GBE_MACHTYPE
type of computer.

If the sandbox contains a file called 'buildfilter', then the contents of the
file will be read and used a buildfilter. The file is processed by reading each
line and:

=over 4

=item * 

Removing white space at both ends of the line

=item * 

Removing empty lines

=item * 

Lines that start with a # are comments and are removed

=item * 

Remaining lines are joined together to form a buildfilter

=back

=for comment ==================================================================

=head1 Create Sandbox

=head2 NAME

Create Sandbox

=head2 SYNOPSIS

jats sandbox create [command options]

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -verbose[=n]            - Verbose operation
    -exact                  - Create sandbox to reproduce exact versions
    -[no]force              - Force creation of nested sandbox

=head2 OPTIONS

The 'create' command takes the following options:

=over 8

=item -exact

When this option is specified the sandbox is marked for exact processing of
package versions. In this mode the version numbers of the packages in the
sandbox are significant. This is ideal for recreating a package-version.

The default is for in-exact processing, in which the version numbers of packages
within the sandbox are not significant. The is ideal for development.

=item -[no]force

Normally a sandbox should not be created within another sandbox. 

The use of this option overrides this limitation.

A sandbox with a sandbox will be ignored by the parent sandbox.

=back

=head2 DESCRIPTION

The 'create' command will create a sandbox in the users current directory. It is
not possible to create a sandbox within a sandbox.

A sandbox can be created in a directory that contains files and subdirectories.

The create command simply places a known directory in the current directory.
This directory is used by the sandboxing process. It may be manually deleted, or
deleted with the 'delete' command.

=for comment ==================================================================

=head1 Populate Sandbox

=head2 NAME

Populate a Sandbox

=head2 SYNOPSIS

jats sandbox populate [command options] [packageName packageVersion]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure
 Command Specific Options
    -excludePackage=name[,name]- Do not extract named package
    -recurse[=n]               - Locate dependencies within packages
    -all                       - Populate with all dependencies
    -missing                   - Locate missing packages
    -show                      - Show packages that would be extracted
    -test                      - Do not extract packages
    -<Other>                   - Pass options to jats extract

=head2 ARGUMENTS

The 'populate' command can take a package name and version as arguments. It will
then populate the sandbox with this package. See 'DESCRIPTION' for details.

=head2 OPTIONS

The 'populate' command takes the following options:

=over 4

=item -excludePackage=name[,name]

This option prevents one, or more, packages from populating the sandbox.
Packages specified with this option will not be extracted from version control
and added to the sandbox.

Packages can be identified in three ways:

=over 4

=item 1. Package Name

All package versions matching the named package will be excluded.

=item 2. Package Name and Version

Only the specified version of the named package will be excluded. The
user specifies the package name and version as a single string separated with
an underscore. ie: core_devl_2.100.5000.cr

=item 3. Package Name and Suffix

All packages matching the named package and project will be excluded. The
user specifies the package name and project as a single string separated with
a dot. ie: core_devl.cr


=back

=item -recurse[=N]

This option will modify the operation of the command such that dependencies
of named packages can also be extracted into the sandbox.

The default operation is to only extract named packages. If the option is
specified then all dependent packages are processed. An optional numeric argument
can be specified to limit the depth of the recursion.

=item -all

This option will populate the sandbox will all dependencies of packages that are
currently in the sandbox.

The global options that control recursion will affect the packages that are
processed.

This option cannot be used with the '-missing' option.

=item -missing

This option will modify the operation of the dependency recursion scanning such
that dependent packages that exist in a package archive will not be extracted.

Use of this option allows a sandbox to be populated with packages that are
required by packages in the sandbox, but are not available in a package archive.

The global options that control recursion will affect the packages that are
processed.

This option cannot be used with the '-all' option.

=item -show

This option will prevent the command from performing the extraction. It will
simply display the names of the packages that would be extracted.

=item -test

This option will prevent the command from performing the extraction. It will
simply display the JATS commands that can be used to perform the extraction.

=item -<Other>

Options not understood by the 'populate' sub command will be passed through
the package extraction program. Useful options include:

=over 4

=item *

-extractfiles

=item *

-branch=<branch name>

=back

=back

=head2 DESCRIPTION

The 'populate' command can be used to assist in populating the sandbox. It has
two modes of operation.

=over 4

=item 1

Named Package

If the user specifies both a package name and a package version then the command
will populate the sandbox with that package and optionally its dependencies.

=item 2

Determine missing dependencies

If the user does not specify a package name and version, but does specify
the '-missing' option,  then the command will examine the current sandbox and
determine missing dependent packages. It will then populate the sandbox with
these packages and optionally there dependencies.

=back

=head2 EXAMPLES

=over 4

=item *

jats sandbox populate package1 version1

This command will populate the sandbox with version1 of package1, if it does not
already exist in the sandbox.

=item *

jats sandbox populate package1 version1 -recurse -missing

This command will populate the sandbox with version1 of package1, if it does not
already exist in the sandbox, together will all the packages dependencies that
are not available in a package archive.

=item *

jats sandbox populate -recurse -missing

This command will examine the current sandbox and populate the sandbox with
packages that are required to build the packages in the sandbox and the
dependencies of these packages, provide the dependent package is not in a
package archive.

=item *

jats sandbox populate

This command will examine the current sandbox and populate the sandbox with
packages that are required to build the packages in the sandbox. It will not
examine the dependents of these packages.

=back

=for comment ==================================================================

=head1 Delete Sandbox

=head2 NAME

Delete a sandbox

=head2 SYNOPSIS

jats sandbox [options] delete

 Options:
    -help[=n]               - Help message, [n=1,2,3]
    -man                    - Full documentation [-help=3]
    -verbose[=n]            - Verbose command operation

=head2 DESCRIPTION

The 'delete' command will delete the sandbox's marker directory. The command may
be executed anywhere within the sandbox.

Once the sandbox has been deleted, the user must remove the components within the
sandbox.

=for comment ==================================================================

=head1 Sandbox Information

=head2 NAME

Display Sandbox Information

=head2 SYNOPSIS

jats sandbox info [command options]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure
 Command Specific Options
    -verbose[=n]               - Display more information
    -usedby                    - Display package usage information
    -fingerprint               - Display fingerprint information
    -[no]dependencies          - Display external dependencies (default)
    -[no]buildorder            - Display build order (default)
    -[no]path                  - Display path to package

=head2 OPTIONS

=over

=item B<-verbose[=n]>

This options will increase the verbosity of the information being displayed.
Values 1 and 2 are described in the detailed 'DESCRIPTION'. Other values are
reserved for diagnostic use.

=item B<-usedby>

This option will list all packages that use the current package, both directly and
indirectly. These are packages that should be tested whan changes are made to the 
package.

=item B<-fingerprint>

This option will cause the information display to include that status of each packages fingerprint.

This will slow down the display as the calculation can be time consuming.

=item B<-[no]dependencies>

This option will cause the information display to include all the external dependencies.

=item B<-[no]buildorder>

This option will cause the information display to include all the build order.

=item B<-[no]path>

This option will cause the information display to include all the path to the build file

=back

=head2 DESCRIPTION

The 'info' command will display information about the build order and the
dependencies of packages that it finds within the sandbox.

The command works within various levels of verbosity:

=over 8

=item *

No Verbosity

The basic command will display the build order and the external
dependencies. External dependencies may be prefixed with one of the
following indicators:

=over 8

=item   '+' Multiple versions of this package are being used by sandboxed components.

=item   '*' The package cannot be found in any of the package archives.

=back

=item *

Verbosity of 1

This level of verbosity will display the build order and detailed information
on the dependencies. The dependencies will be prefixed with:

=over 8

=item   E Dependent Package is external to the sandbox

=item   I Dependent Package is internal to the sandbox

=back

External dependencies may be prefixed with one of the indicators described for
no-verbosity. Additionally the internal consumer of the external package is also
shown. These are prefixed with a 'U'.

=item *

Verbosity of 2

Usage information will also be displayed. This is the same as invoking the 
'-usedby' option.

=item *

Verbosity over 2

This should be considered a debug option. Undocumented internal information will
be displayed.

=back

=for comment ==================================================================

The build infomation display will show the state of each package as a cryptic set of four characters within square brackets.

The first character may be one of:

=over 8

=item S - Build is being skipped

=item s - Build is being suppressed

=item blank - Build is active

=back

The second character may be one of:

=over 8

=item * - This is the current package

=item B - Has been build

=item - - A 'noBuild'. Will not be built due to build filters

=back

The third character may be one of:

=over 8

=item E - The package only exists in dpkg_archive

=item L - The package has been built and exists within the sandbox

=item M - The package has been built and exists in both the sandbox and dpkg_archive

=back

The fourth character may be one of (requires the -fingerprint option):

=over 8

=item G - A good fingerprint

=item B - A bad fingerprint. Files in the package have been modified since the last build.

=item blank - The package has not been built and no fingerprint has been calculated.

=back

=head1 Buildfilter

=head2 NAME

Display and Modify Sandbox buildfilter

=head2 SYNOPSIS

jats sandbox buildfilter [command options] [TARGETS]+

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -man                    - Same as -help=3

 Target Names
    -TARGET                 - Remove target from the current buildfilter
    +TARGET                 - Add target to current buildfilter
    TARGET                  - If first target, then reset buildfilter 
                              and add target, otherwise add target.

=head2 OPTIONS

The 'buildfilter' command takes the following options:

=over 8

=item -TARGET

If a target name starts with a '-' and is not an option, then that target will be
removed from the current buildfilter. 

If the named target is not a part of the current buildfilter then nothing will happen.

=item +TARGET

If a target name starts with a '+' then that target will be added to the current buildfilter.

If the named target is already a part of the current buildfilter then nothing will happen.


=item TARGET

If a target name does not start with either a '-' or a '+' then the target will be added to the
current buildfilter.

If this is the first named target then the build filter will be set to this one target.

=back

=head2 DESCRIPTION

The 'buildfilter' command will display and optionally modify the build filter used within
the sandbox.

=head2 EXAMPLES

The command

    jats sandbox buildfilter 

will simply display the current buildfilter.

The command

    jats sandbox buildfilter +COBRA +PPC_603E

will append the build targets COBRA and PPC_603E to the current buildfilter.

The command

    jats sandbox buildfilter -COBRA

will remove the build target COBRA from the current buildfilter.

The command

    jats sandbox buildfilter COBRA +PPC_603E
 or jats sandbox buildfilter COBRA PPC_603E

will set the buildfilter to be COBRA and PPC_603E

=for comment ==================================================================

=head1 Skip Build

=head2 NAME

Mark a package to be skipped during the build

=head2 SYNOPSIS

jats sandbox [un]skip [command options] [PackageName]+

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -man                    - Same as -help=3
    -[no]machine            - Skip on on this type of machine

=head2 ARGUMENTS

Arguments to the 'skip' command are the names of packages to be marked.

If no packages are named then the command will display all packages that are marked to be skipped.

If the named package is '.', then the current package will be excluded.

=head2 OPTIONS

The 'skip' command takes the following options:

=over 8

=item -[no]machine

This option will flag that the package will be skipped only on this type of build machine.

=back

=head2 DESCRIPTION

The 'skip' command marked the named packages to be skipped during the build, or the mark will be removed.

=head2 EXAMPLES

The command

    jats sandbox skip package1 

will mark package1 to be skipped during the following builds.

=head1 Sandbox Finger Print

=head2 NAME

Various fingerprint operations

=head2 SYNOPSIS

jats sandbox finger[print] [options]

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -man                    - Same as -help=3
    -[no]generate           - Generate a fingerprint over a package
    -[no]delete             - Delete the fingerprint information

=head2 ARGUMENTS

Arguments to the 'fingerprint' command are the names of packages to be processed.

If no packages are named then the command will process the current package, if any.

=head2 OPTIONS

The 'fingerprint' command takes the following options:

=over 8

=item -[no]generate

This option will cause the fingerprint of the package to be regenerated.

=item -[no]delete

This option will delete the fingerprint information associated wit a package.

=back

=head2 DESCRIPTION

The 'fingerprint' command, will by default, examine the packages fingerprint and report
if the package has been modified since the fingerprint was created.

Options allow different modes of operation.

A fingerprint may only be created after the 'build.pl' file has been created. It requires the
build process to generate metadata about the package.

=head2 EXAMPLES

The command

    jats sandbox fingerprint -generate

will regenerate the fingerprint of the current package. Useful after trivial edits to 
enable the sandbox builder to bypass the package and not to rebuild it and all of its dependents.

=for comment ==================================================================

=head1 Command all

=head2 NAME

Build packages in the sandbox

=head2 SYNOPSIS

jats sandbox all [command options] [arguments]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure
 Command Specific Options
    -[no]skip                  - Skip if no source change (default:skip)


=head2 ARGUMENTS

Arguments are passed to the 'make' phase of the process.

=head2 OPTIONS

=over

=item B<-[no]skip>

This operation overides the default smart building mechanism.

By default, a package will not be built if the last build was successful and 
there has not been any change to the source of the package, since the last 
succesful build.

=back

=head2 DESCRIPTION

The 'all' command will perform build, if the build files are out of date,
followed by a make in each of the packages within the sandbox, in the correct
build order.

Any arguments are passed to the 'make' phase of the process.

This command may be used to:

=over 8

=item *

Pickup any build file changes.

=item *

Resume a failed build.

=back

=for comment ==================================================================

=head1 Command build

=head2 NAME

Build packages in the sandbox

=head2 SYNOPSIS

jats sandbox build [command options] [arguments]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure
 Command Specific Options
    -[no]skip                  - Skip if no source change (default:skip)

=head2 ARGUMENTS

Arguments are passed to the 'make' phase of the process.

=head2 OPTIONS

=over

=item B<-[no]skip>

This operation overides the default smart building mechanism.

By default, a package will not be built if the last build was successful and 
there has not been any change to the source of the package, since the last 
succesful build.

=back

=head2 DESCRIPTION

The 'build' command will force a build followed by a make in each of the packages
within the sandbox, in the correct build order.

Any arguments are passed to the 'make' phase of the process.

In practice, the 'sandbox all' command is quicker.

=for comment ==================================================================

=head1 Clean

=head2 NAME

Clean all sandbox components

=head2 SYNOPSIS

jats sandbox clean|clobber [command options]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure

=head2 ARGUMENTS

None

=head2 OPTIONS

The are no command specific options.

=head2 DESCRIPTION

The 'clean' command will perform a 'jats make clean' in all components in the
sandbox.

The 'clobber' command will perform a 'jats clobber' in all components in the
sandbox.

=for comment ==================================================================

=head1 make

=head2 NAME

Make packages in the sandbox

=head2 SYNOPSIS

jats sandbox make [command options] [arguments]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure

=head2 ARGUMENTS

Arguments are passed to the 'make' phase of the process.

=head2 OPTIONS

The are no command specific options.

=head2 DESCRIPTION

The 'make' command will perform a 'make' operation in each of the packages
within the sandbox, in the correct build order.

Any arguments are passed to the 'make'.

=for comment ==================================================================

=head1 cmd

=head2 NAME

Process each package with a specified command.

=head2 SYNOPSIS

jats sandbox cmd [command options] [arguments]

 Common Options:
    -help[=n], -man            - Command specific help, [n=1,2,3]
    -onlyLevel=number          - Limit building to one level
    -toPackage=name            - Stop building after package
    -fromPackage=name          - Start building from package
    -justPackage=name[,name]   - Build named packages
    -ignorePackage=name[,name] - Do not build named packages
    -entireSandbox             - Process the entire sandbox
    -users                     - Process package users, not dependencies
    -multiBuilders=mode        - Handle conflicting packages. error|report|ignore
    -[no]keepgoing             - Ignore errors
    -[no]reScan                - Recalculate and cache sandbox structure
 Command Specific Options
    -[no]reverse               - Reverse the processing order

=head2 ARGUMENTS

Arguments are passed to a JATS command.

=head2 OPTIONS

The 'cmd' command takes the following options:

=over 8

=item -[no]reverse

This option will controlls the order in which the packages will be processed.

The default option is 'noreverse'. Packages will be processed in the build order.

=back

=head2 DESCRIPTION

The 'cmd' command will pass all of its arguments to JATS in the build directory
of each of the packages within the sandbox, in the package build order.

=head2 EXAMPLES

The following command will update all the Subversion-based packages in the sandbox.

    jats sandbox cmd eprog svn update

Note the use of 'eprog' in the command string. This tells JATS to run the external
(to JATS) program. Without this the command would run the JATS-internal command
called 'svn' - with different results.

The following command will update the dependencies in the build.pl files to match
those of a nominated release. This will only affect the package versions
external to the sandbox, although all version information in the build.pl
files will be updated.

    jats sandbox cmd upddep -rtagid=12345

=for comment ==================================================================

=head1 Cache

=head2 NAME

Cache dependent packages

jats sandbox [options] cache [command options]

 Options:
    -help[=n]               - Help message, [n=1,2,3]
    -man                    - Full documentation [-help=3]
    -verbose[=n]            - Verbose command operation

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]

=head2 ARGUMENTS

The are no command specific arguments.

=head2 OPTIONS

The are no command specific options.

=head2 DESCRIPTION

The 'cache' command will cache all external dependent packages into the users
dpkg_archive_cache as defined through the EnvVar GBE_DPKG_CACHE. The result is
similar to the command 'jats sandbox build -cache', without the overhead of
building the sandbox components.

This command allows the simple creation of a small development environment that
is not tied to the larger Development Environment. It may then be used in a
disconnected mode to perform development.

=for comment ==================================================================

=head1 Sandbox Test Links

=head2 NAME

Test and delete sandbox link files

=head2 SYNOPSIS

jats sandbox testlinks [options]

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -man                    - Same as -help=3
    -[no]delete             - Delete bad links

=head2 ARGUMENTS

This command does not take any arguments

=head2 OPTIONS

The 'fingerprint' command takes the following options:

=over 8

=item -[no]delete

This option will delete the link files if they are bad.

=back

=head2 DESCRIPTION

The 'testlinks' command, will by default, examine symbolic links within the sandbox and report on
broken links.

An option allows the broken links to be deleted.

Each package in the sandbox will have a symbolic link to the packages 'package' area. If a package is removed
from the sandbox the link file may be left in the sandbox and cause a 'build' to fail.

=head2 EXAMPLES

The command

    jats sandbox testlinks

will test the symbolic links in the sandbox metadata.

=for comment ==================================================================

=head1 Sandbox Scan Depth

=head2 NAME

Set and Display the build file scanner depth

=head2 SYNOPSIS

jats sandbox scandepth [options] [depth]

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -man                    - Same as -help=3

=head2 ARGUMENTS

This command takes one optional argument. A positive number that control the 
depth of the build filter scanner.

The default value is '3'. Deeper scans may be required for sandboxes containing badly formed 
packages. The tradoff is speed for all sandbox operations.

Setting the scan depth will force a rescan of the sandbox build files on the next command that
requires the information.

=head2 OPTIONS

The scandepth command only accepts the standard help and man options.

=head2 DESCRIPTION

The scandepth command, will by default, display the current value for the build file scanner.

If a numeric argument is provided this will set the future build file scan depth.

=head2 EXAMPLES

The command

    jats sandbox scandepth 5

will set future build file scan to a maximum of 5 directories below the root of the sandbox.

=cut