Subversion Repositories DevTools

Rev

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

########################################################################
# Copyright (C) 2008 ERG Limited, 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 Pod::Usage;                             # required for help support
use Getopt::Long qw(:config require_order); # Stop on non-option
my $VERSION = "1.0.0";                      # Update this

#
#   Options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_help = 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

#
#   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_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_BUILDFILTER = $ENV{'GBE_BUILDFILTER'};

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


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

#
#   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$/  );

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
{
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "exact"         => \$opt_exact,
                ) || 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 );

    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        : info
#
# Description     : Display Sandbox information
#
# Inputs          : Command line args
#                   -v  - Be more verbose
#
# Returns         : Will exit
#
sub info
{
    my (@cmd_opts ) = @_;
    my $show = 0;

    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts,
                           'verbose:+'  => \$show,
                        ) || Error ("Invalid command line" );
    SubCommandHelp( $opt_help, "Sandbox Information") if ($opt_help || $#cmd_opts >=0 );

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

    #
    #   Display information
    #
    Message ("Type       : " . ($opt_exact ? 'Exact' : 'Development') );
    Message ("Base       : $GBE_SANDBOX");
    Message ("Archive    : $GBE_DPKG_SBOX");
    Message ("BuildFilter: $GBE_BUILDFILTER" . ( (-f $GBE_SANDBOX . '/buildfilter')  ? ' - Local to sandbox' : ''));


    Message ("Build Order");
    foreach my $pname ( @stopped )
    {
        Message( "    Level:" . "-"  . " Name: " . $pname . ' (Stopped)');
    }
    foreach my $fe ( @build_order )
    {
        Message( "    Level:" . $fe->{level} . " Name: " . $fe->{dname} . ($fe->{buildActive} ? '' : ' (Build Suppressed)'));
        Message( DisplayPath ("        Path: $fe->{dir}" )) if $show;

        if ( $show )
        {
            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");
            }

        }
    }

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

    Message("External Dependencies");
    foreach my $de ( sort keys %extern_deps )
    {
        my @vlist = keys %{$extern_deps{$de}};
        my $flag = $#vlist ? '+' : '';
        foreach my $pve ( @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 )
            {
                foreach my $pkg ( @{$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        : 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,
                       $GBE_DPLY,
                       $GBE_DPKG_STORE )
    {
        next unless ( $dpkg );
        if ( -d "$dpkg/$name/$ver" )
        {
            return 1;
        }
    }
   return 0;
}


#-------------------------------------------------------------------------------
# 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
#
# Inputs          : info                - True: Just for info
#                                               Keep supressed packages
#
# Returns         : Will exit if not in a sandbox
#                   Populates global variables
#                       @build_order - build ordered array of build entries
#
sub calc_sandbox_info
{
    my ($info) = @_;

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

    #
    #   Locate all packages within the sandbox
    #   These will be top-level directories - one per package
    #
    my @dirlist;
    my @build_list;
    foreach my $pname ( glob("*") )
    {
        next if ( $pname =~ m~^\.~ );
        next if ( $pname =~ m~dpkg_archive$~ );
        next if ( $pname =~ m~^CVS$~ );
        next unless ( -d $pname );
        Verbose ("Package discovered: $pname");

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

        push @dirlist, $pname;

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

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

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

        #
        #   Add into dependency struct
        #
        $depends{$be->{tag}} = $be;
    }

    foreach my $dname ( sort keys %multi )
    {
        ReportError ("Multiple builders for : $dname", @{$multi{$dname}} )
            if ( scalar @{$multi{$dname}} > 1 );
    }
    ErrorDoExit();

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

    #
    #   Determine the build order
    #
    @build_order = ();
    my $more = 1;
    my $level = 0;

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

            $fe->{level} = $level;
            $fe->{buildActive} = $scan_add;
            $packages{$build} = $fe;
            push (@build_order, $fe) if ( $scan_add || $info );
            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();

    #
    #   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");
        %extern_deps = ();

        foreach my $key ( keys %packages )
        {
                next unless ( $packages{$key}{buildActive} );
                next unless ( $packages{$key}{'edeps'} );
                foreach ( keys %{$packages{$key}{'edeps'}} )
                {
                    push @{$extern_deps{$packages{$key}{'edeps'}{$_}} {$_} }, $key;
                }
        }
    }

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

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

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

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

    #
    #   Determine Sandbox information
    #   Populate global variables
    #
    calc_sandbox_info();
    foreach my $fe ( @build_order )
    {
        my $dir = $fe->{dir};
        Message( "Level:" . $fe->{level} . " Name: " . $fe->{dname} ,
                  DisplayPath ("        Path: $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;

    #
    #   Extract and options
    #
    Getopt::Long::Configure('pass_through');
    getOptionsFromArray ( \@cmd_opts ) || 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 $dir = $fe->{dir};
        Message( "Level:" . $fe->{level} . " Name: " . $fe->{dname} ,
                  DisplayPath ("        Path: $fe->{dir}" ));

        JatsCmd( "-cd=$dir", 'build', @build_opts) && Error ("Build Cmd failure") if ( $result );
        JatsCmd( "-cd=$dir", 'make',  @make_opts)  && Error ("Make Cmd failure")  if ( $result );
    }

    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 $dir = $fe->{dir};
        Message( "Level:" . $fe->{level} . " Name: " . $fe->{dname} ,
                  DisplayPath ("        Path: $fe->{dir}" ));

        my $result = JatsCmd( "-cd=$dir", @cmd, @cmd_opts);
        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");
    calc_sandbox_info();

    #
    #   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
    #
    @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
    #
    Message('BuildFilter:', @filter_list);

    #
    #   Write out a new file
    #
    if ($modified)
    {
        FileCreate($GBE_DPKG_SBOX . '/buildfilter', @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) = @_;

    #
    #   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, @_ )},
        );

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

 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
    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<-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 specicied 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 descibed 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 descibed 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 descibed under the '-toPackage' option.

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

=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

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

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

=head1 Populate Sandbox

=head2 NAME

Populate a Sandbox

=head2 SYNOPSIS

jats sandbox populate [command options] [packageName packageVersion]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -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
    -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 itentified 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

=head1 Delete Sandbox

=head2 NAME

Delete a 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.

=head1 Sandbox Information

=head2 NAME

Display Sandbox Information

=head2 SYNOPSIS

jats sandbox info [command options]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -verbose[=n]               - Display more information
    -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

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

=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

Reserved for future use

=item *

Verbosity over 2

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

=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

=head1 Command all

=head2 NAME

Build packages in the sandbox

=head2 SYNOPSIS

jats sandbox all [command options] [arguments]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -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

=head2 ARGUMENTS

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

=head2 OPTIONS

The are command specific options.

=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

=head1 Command build

=head2 NAME

Build packages in the sandbox

=head2 SYNOPSIS

jats sandbox build [command options] [arguments]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -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

=head2 ARGUMENTS

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

=head2 OPTIONS

The are no command specific options.

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

=head1 Clean

=head2 NAME

Clean all sandbox components

=head2 SYNOPSIS

jats sandbox clean|clobber [command options]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -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

=head2 ARGUMENTS

None

=head2 OPTIONS

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.

=head1 make

=head2 NAME

Make packages in the sandbox

=head2 SYNOPSIS

jats sandbox make [command options] [arguments]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -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

=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'.

=head1 cmd

=head2 NAME

Process each package with a specified command.

=head2 SYNOPSIS

jats sandbox cmd [command options] [arguments]

 Command Options
    -help[=n]                  - Command specific help, [n=1,2,3]
    -[no]keepgoing             - Ignore errors
    -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

=head2 ARGUMENTS

Arguments are passed to a JATS command.

=head2 OPTIONS

=over

=item B<-[no]keepgoing>

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

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

=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

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

=cut