########################################################################
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
#
# Module name   : jats.sh
# Module type   : Makefile system
# Compiler(s)   : n/a
# Environment(s): jats
#
# Description   : Top level Makefile supervisor
#                 The function of this program is to call the target makefiles
#                 in a suitable manner.
#
# History       : Once upon a time this was done with another makefile and
#                 some really ugly shell, embedded within the makefile
#                 This had a number of limitations, including
#                   - Limited to makefile command line syntax
#                   - Overly complex makefile
#                   - Very slow execution
#
# Usage         : See POD
#
#......................................................................#

require 5.006_001;
use strict;
use warnings;

use Pod::Usage;                             # For help support
use JatsError;
use JatsSystem;
use JatsEnv;
use JatsMakeInfo;
use ReadBuildConfig qw(:All);
use Getopt::Long;
use FileUtils;
use ArrayHashUtils;
use JatsDPackage;

my $VERSION = "1.0.0";                      # Update this

#
#   Default values
#   
use constant GBE_MAXMAKE_DEFAULT => '4h';
#
#   Options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_help = 0;
my $opt_silent = 1;
my $opt_default;
my $opt_max_make;

#
#   Global variables
#
our %cf_info;
our $GBE_TOOLS;                             # Base of tools
our $GBE_ABT;                               # Optional ABT indication
our $GBE_PLATFORM;                          # Optional GBE_PLATFORM
our $GBE_MAXMAKE;                           # Optional GBE_MAXMAKE
our $ScmInterface;                          # Current interface directory
our $ScmRoot;                               # Build root
our $ScmNoBuild;                            # Dummy Build and Make

my $Tags;                                   # Reference to hash of tags
my %defs;                                   # Make definitions
my @defs;                                   # Make definitions
my @targets;                                # Make targets
my $makelib;                                # Path to makelib.pl
my $update = 0;                             # Makefiles updated
my $build_error = 0;                        # Build Error encountered
my $tlen = 4;                               # Max length of targets
my %common_cmd;                             # Short circuit some commands
my %gbe_platform;                           # Nice form of GBE_PLATFORM

#
#   Known commands
#   A hash of known commands and information about the commands
#       common      - Command operation modifiers
#                     Flags: 1 - Command not executed for debug+prod
#                     Flags: 2 - Command is only executed ONCE per platform
#       local       - The command is handled locally.
#       nomakecheck - Don't check for makefile being out of date
#       IFLAG       - iteration flag,
#                       0   No external dependencies.
#                       1   Source dependency list.
#                       2   Shared library dependency list.
#                       3   Application dependency list.
#   Note:   gmake attempts to build includes prior to invoking other
#   rules when the source is non-existent, which can/do fail as a
#   result of the prereq rules not being executed, the IFLAG inhabits
#   includes during these prereq rules.
#
my %composite_commands = (
    #
    #   Composite commands
    #
    'lint'                  => [ 'mkdepends', 'generate_debug', 'install_hdr_debug',
                                 'lint_lib', 'lint_shlib', 'lint_prog' ] ,
    'ctags'                 => [ 'mkdepends', 'generate_debug', 'ctags_debug' ] ,
    'test'                  => [ 'make_test' ] ,
    'mkdepends'             => [ 'makefiles', 'make_init'] ,
    'run_tests'             => [ 'preprocess_tests', 'exec_tests', 
                                 'postprocess_tests', 'collate_test_results' ] ,
    'run_unit_tests'        => [ 'preprocess_tests', 'exec_unit_tests', 
                                 'postprocess_tests', 'collate_test_results' ] ,
    # Convenient phases of the build
    'hdrs'                  => [ 'mkdepends', 'generate', 'install_hdr'],
    'libs'                  => [ 'hdrs', 'depend', 'make_lib', 'install_lib'],
    'slibs'                 => [ 'libs', 'make_install_shlib'],
    'progs'                 => [ 'slibs', 'make_prog', 'make_test', 'install_prog', 'install_class'],
    'build'                 => [ 'progs','install_dirs'],
    'install'               => [ 'build' ] ,
    'all'                   => [ 'install', 'package', 'deploy' ] ,
    );

my %commands = (
    #
    #   Basic commands / phases of the build process
    #   This should be a list of all known makefile commands
    #
    'help'                  => { 'tag' => 0, 'local' => \&DoHelp },
    'show'                  => { 'tag' => 0, 'local' => \&ShowMakefiles },
    'rebuild'               => { 'tag' => 0, 'local' => \&TestMakeFiles },
    'makefiles'             => { 'tag' => 0, 'local' => \&TestMakeFiles },
    'unmakefiles'           => { 'tag' => 0, 'local' => \&UnMakeFiles },
    'make_init'             => { 'tag' => 1, 'nomakecheck' => 1, 'common' => 3, },
    'generate'              => { 'tag' => 1 },
    'install_hdr'           => { 'tag' => 1 },
    'depend'                => { 'tag' => 1, 'unless' => ['NODEPEND','EXPERT'] },
    'make_lib'              => { 'tag' => 1, 'IFLAG' => 1 },
    'make_mlib'             => { 'tag' => 1, 'IFLAG' => 1 },
    'install_lib'           => { 'tag' => 1, 'IFLAG' => 1 },
    'make_install_shlib'    => { 'tag' => 1, 'IFLAG' => 2 },
    'make_prog'             => { 'tag' => 1, 'IFLAG' => 3 },
    'make_test'             => { 'tag' => 1, 'IFLAG' => 3 },
    'install_prog'          => { 'tag' => 1, 'IFLAG' => 3 },
    'install_class'         => { 'tag' => 1 },
    'install_dirs'          => { 'tag' => 1 },
    'package'               => { 'tag' => 1, 'IFLAG' => 3 },
    'deploy'                => { 'tag' => 1, 'IFLAG' => 1 },

    #
    #   Special
    #   make_dir is directly invoked by jmake. It doesn't need
    #   to be a part of a command sequence
    #
    'make_dir'              => { 'nomakecheck' => 1, 'tag' => 1 },

    #
    #   Basic commands, that appear to be unused in composite commands
    #   Not too sure why
    #
    'make_script'           => { 'commands' => [], 'tag' => 1 },

    #
    #   Lint related targets
    #
    'lint_lib',             => { 'tag' => 'make_lib'            , 'IFLAG' => 1 },
    'lint_shlib',           => { 'tag' => 'make_install_shlib'  , 'IFLAG' => 2 },
    'lint_prog'             => { 'tag' => 'make_prog'           , 'IFLAG' => 3 },

    #
    #   Unit testing related commands
    #
    'exec_tests'            => { 'nomakecheck' => 1, 'tag' => 1, 'IFLAG' => 3},
    'exec_unit_tests'       => { 'nomakecheck' => 1, 'tag' => 1, 'IFLAG' => 3},
    'preprocess_tests'      => { 'nomakecheck' => 1, 'tag' => 'process_tests'},
    'postprocess_tests'     => { 'nomakecheck' => 1, 'tag' => 'process_tests'},
    'collate_test_results'  => { 'nomakecheck' => 1, 'tag' => 'process_tests', 'common' => 2},
    
    #
    #   Following commands should NOT be a part of a composite command
    #
    'clean'                 => { 'nomakecheck' => 1},
    'clobber'               => { 'nomakecheck' => 1},
    'rmlitter'              => { 'nomakecheck' => 1},
    'unbuild'               => { 'nomakecheck' => 1},
    'undepend'              => { 'nomakecheck' => 1 , 'tag' => 'depend'},
    'ungenerate'            => { 'nomakecheck' => 1 , 'tag' => 'generate'},
    'uninstall'             => { 'nomakecheck' => 1},
    'uninstall_dirs'        => { 'nomakecheck' => 1},
    'uninstall_class'       => { 'nomakecheck' => 1},
    'uninstall_hdr'         => { 'nomakecheck' => 1 , 'tag' => 'install_hdr'},
    'uninstall_lib'         => { 'nomakecheck' => 1 , 'tag' => 'install_lib'},
    'uninstall_prog'        => { 'nomakecheck' => 1 , 'tag' => 'install_prog'},
    'unmake_lib'            => { 'nomakecheck' => 1 , 'tag' => 'make_mlib'},
    'unmake_mlib'           => { 'nomakecheck' => 1 , 'tag' => 'make_mlib'},
    'unmake_prog'           => { 'nomakecheck' => 1 , 'tag' => 'make_prog'},
    'unmake_script'         => { 'nomakecheck' => 1 , 'tag' => 'make_script'},
    'unmake_test'           => { 'nomakecheck' => 1 , 'tag' => 'make_test'},
    'unobj'                 => { 'nomakecheck' => 1},
    'unpackage'             => { 'nomakecheck' => 1 , 'tag' => 'package'},
    'unmake_dir'            => { 'nomakecheck' => 1 , 'tag' => 'make_dir'},

    );
#    DebugDumpData ("Commands", \%commands);

#-------------------------------------------------------------------------------
# Function        : Mainline Entry Point
#
# Description     :
#
# Inputs          :
#

#
#   Parse the user options
#
my $result = GetOptions (
                "help:+"        => \$opt_help,              # flag, multiple use allowed
                "manual:3"      => \$opt_help,              # flag, multiple use allowed
                "verbose:+"     => \$opt_verbose,           # flag, multiple use allowed
                "d|debug:+"     => \$opt_debug,             # flag, multiple use allowed
                "default=s"     => \$opt_default,           # string
                "maxTime=s"     => \$opt_max_make,          # string
                );

                #
                #   UPDATE THE DOCUMENTATION AT THE END OF THIS FILE !!!
                #

#
#   Process help and manual options
#
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'    => 'Make',
             'verbose' => $opt_verbose,
             'debug'   => $opt_debug );

# Global information
#
EnvImportOptional ( 'GBE_ABT' );
EnvImportOptional ( 'GBE_PLATFORM' );
EnvImportOptional ( 'GBE_MAXMAKE', GBE_MAXMAKE_DEFAULT );

#
#   Sanity check arguments
#
$opt_max_make = $GBE_MAXMAKE unless defined $opt_max_make;
Error ("Invalid maxTime: $opt_max_make") 
    unless ($opt_max_make =~ m~^[0-9]*\.?[0-9]+[smhd]?$~);
Warning("Time limit is disabled ") unless $opt_max_make;
Verbose("Time limit: $opt_max_make");

#
#   Configure the 'System' process so that it will terminate on any error
#   and not report the complete command line
#
SystemConfig ( 'ExitOnError' => 2);

#
#   Extract non-option commands
#   Much of this is for backward compatability
#   Support the following arguments
#       XXXXX=yyyy      Makefile Definition
#       -XXXX           Flags passed to make
#       XXXX            Raw target name
#
foreach my $arg ( @ARGV )
{
    Verbose2 ("Arg: $arg");
    if ( $arg =~ m~(\w+)=(.+)~ ) {
        $defs{uc($1)} = $2;

    } elsif ( $arg =~ m/^-/ ) {
        push @defs, $arg;

    } else {
        push @targets, $arg;

    }
}

#
#   Misc. Initialisation
#
InitFileUtils();

EnvImport( 'GBE_TOOLS' );
$makelib = "$GBE_TOOLS/makelib.pl";
Error ("Cannot locate JATS file: $makelib") unless ( -f $makelib );

#
#   Validate user options
#
@targets = ($opt_default)
    if ( $opt_default && ! @targets);
Error ("No make targets specified" ) unless ( @targets );

#
#   Build the @defs array
#   This is a set of definitions passed to the makefiles
#   It is created on the fly to allow command line options to modify the definitions
#
foreach  ( sort keys %defs )
{
    next if ( m~^DEBUG$~i );                    # Naughty user
    push @defs, "$_=$defs{$_}";                 # Makefile defs are XXXX=aaaa
}

#
#   Look for OPTIONS=*args* and disable the silent make operation
#   This will allow the make to output messages
#
if (( exists $defs{OPTIONS} && $defs{OPTIONS} =~ m/args/ ) || $opt_verbose || $opt_debug )
{
    $opt_silent = 0;
}

#
#   Read in the local Makefile.gbe file
#   This will provide a path to the interface directory
#
ReadMakeInfo();

#
#   Read in the global build variables
#
ReadBuildConfig( "$::ScmRoot/$::ScmInterface" );

#
#   Process GBE_PLATFORM and create a usable hash
#
if ( $GBE_PLATFORM )
{
    Verbose2 ("GBE_PLATFORM: $GBE_PLATFORM");
    $gbe_platform{$_} = 1 foreach ( split( / /, $GBE_PLATFORM ) );

    # Insert targets that are GENERIC, there should only be one
    foreach my $target (keys %BUILDINFO) {
        if ($BUILDINFO{$target}{IS_GENERIC})
        {
            $gbe_platform{$target} = 1;
        }
    }
}

#
#   Process the user specified targets
#
ProcessTargets();

#
#   All done
#
exit (0);

#-------------------------------------------------------------------------------
# Function        : ProcessTargets
#
# Description     : Process the user specified targets
#
# Inputs          :
#
# Returns         : 
#
sub ProcessTargets
{
    if($ScmNoBuild)
    {
            Verbose2 ("Processing Skipped in noBuild");
            return;
    }

    #
    #   Determine the max size of the target name
    #   Makes a nice display
    #
    foreach ( @BUILDPLATFORMS )
    {
        my $len = length ($_);
        $tlen = $len if ( $len > $tlen );
    }

    #-------------------------------------------------------------------------------
    #   Scan the list of targets and perform 'alias' expansion
    #   targets are of the form
    #       platform_command_type
    #       Where:
    #           type        may be _prod or _debug or not present
    #           command     may be either a composite command or a simple command
    #           platform    may be a platform or an alias
    #
    #
    foreach my $arg ( @targets )
    {
        my $suffix = '';
        my @commands;
        my @platforms;

        #
        #   Remove the only available suffix ( _debug | _prod )
        #   Also detect commnads of debug or prod
        #
        if ( $arg =~ m~(.+)_(debug)$~ ) {
            $suffix = $2;
            $arg = $1;
        } elsif ( $arg =~ m~(.+)_(prod)$~ ) {
            $suffix = $2;
            $arg = $1;
        } elsif ( $arg eq 'debug' ) {
            $suffix = $arg;
            $arg = '';
        } elsif ( $arg eq 'prod' ) {
            $suffix = $arg;
            $arg = '';
        }

        #
        #   Remove valid make commands from the remaining argument
        #   Have a hash of all known makefile commands
        #
        foreach my $cmd ( keys %composite_commands, keys %commands )
        {
            if ( $arg eq $cmd )
            {
                @commands = ExpandComposite($cmd);
                $arg = '';
            }
            elsif ( $arg =~ m~(.+)_($cmd)$~ )
            {
                $arg = $1;
                @commands = ExpandComposite($2);
                last;
            }
        }

        #
        #   See of we can extract an platform alias from the command
        #   Rip into target + command
        #
        if ( $arg )
        {
            if ( exists $ScmBuildAliases{$arg}  )
            {
                @platforms =  split (/ /, $ScmBuildAliases{$arg} );
            }
            elsif ( exists $ScmBuildPlatforms{$arg} )
            {
                @platforms = ($arg);
            }
            else
            {
                if ( @commands )
                {
                    Error ("Unknown target: $arg");
                }
                else
                {
                    Message ("Unknown command. Passed to make: $arg");
                    @commands = $arg;
                }
            }
        }

        #
        #   Insert defaults
        #
        @platforms = @DEFBUILDPLATFORMS
            unless ( @platforms );
        @commands = ExpandComposite('all')
            unless ( @commands);

        #
        #   Perform GBE_PLATFORM filtering
        #
        @platforms = grep ( exists $gbe_platform{$_} , @platforms )
            if ( %gbe_platform );

        Error ("No platforms to be processed. Check GBE_PLATFORM")
            unless ( @platforms );

        #
        #   Iterate commands over platforms
        #
        foreach my $cmd ( @commands )
        {
            PerformCommand (\@platforms, $cmd, $suffix );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : PerformCommand
#
# Description     : Perform the make on a platform command
#
# Inputs          : $platform_ref   - platforms to process
#                   $cmd            - simple make command
#                   $suffix         - debug,prod,none
#
# Returns         : 
#
sub PerformCommand
{
    my ($platform_ref, $cmd, $suffix) = @_;
    my $do_prod = 1;
    my $do_debug = 1;

    #
    #   Clean up some ugly commands from lint and ctags
    #
    if ( $cmd =~ m~(.+)_(debug)$~ )
    {
        $cmd = $1;
        $suffix = $2;
    }
    Verbose2 ("Processing Target: $cmd, $suffix");

    #
    #   Limit command to production or debug
    #
    if ( $suffix eq 'debug' ) {
        $do_prod  = 0;
    } elsif ( $suffix eq 'prod' ) {
        $do_debug  = 0;
    }

    #
    #   Some commands can be suppressed by options
    #   These are an array of options
    #
    if ( exists $commands{$cmd}{'unless'}  )
    {
        foreach my $check ( @{$commands{$cmd}{'unless'}} )
        {
            if ( exists $defs{$check} && $defs{$check} )
            {
                Verbose2 ("Skipping: $cmd because $check");
                return;
            }
        }
    }

    #
    #   Interecpt commands that are handled locally
    #
    if ( exists $commands{$cmd}{'local'} )
    {
        $commands{$cmd}{'local'}( $cmd, $platform_ref );
        return;
    }

    #
    #   Process and recurse directory tree
    #
    Verbose ("Processing: $cmd, @{$platform_ref}");

    #
    #   Determine the local tag name and state
    #   The tag is used to short circuit makefile commands in recursive makes
    #   It greatly reduces the work required
    #
    #   Many commands have a tag that is the same as the command, but a few don't
    #
    my $tag = 0;
    if ( exists $commands{$cmd}{tag}  )
    {
        $tag = $commands{$cmd}{tag};
        $tag = $cmd if ( $tag eq '1' );
    }

    #   Ensure this element is defined
    #       Simplifies testing in the remainder of the code
    $commands{$cmd}{'common'} = 0 
        unless exists($commands{$cmd}{'common'});

    if ( $commands{$cmd}{'common'} & 1 )
    {
        InvokeSubMake( $::Cwd, $platform_ref ,$cmd, $tag, 1, 'C' );
    }
    else
    {
        InvokeSubMake( $::Cwd, $platform_ref ,$cmd, $tag, 1, 'D' ) if $do_debug;
        InvokeSubMake( $::Cwd, $platform_ref ,$cmd, $tag, 0, 'P' ) if $do_prod;
    }
}

#-------------------------------------------------------------------------------
# Function        : InvokeSubMake
#
# Description     : Build recursion
#                   Determine if there is anything to be done for the current
#                   target within a specified directory
#
# Inputs          : $dir
#                   $pref                   - Ref to an array of platforms
#                   $cmd
#                   $tag
#                   $do_debug               - 0:Production, 1:Debug
#                   $text                   - Single Char display text (C, P or D)
#
# Returns         : 
#
sub InvokeSubMake
{
    my ( $dir, $pref, $cmd, $tag, $do_debug, $text) = @_;

    #
    #   Ensure the current directory is known to the Tags database
    #
    $Tags = ReadMaketags( $dir );
    Error ("Directory not in tag database", $dir ) unless ( $Tags );

    #
    #   Process commands in the current directory
    #   If the command is tagged, then we may be able to skip it
    #   Otherwise we need to do it
    #
    InvokeMake( $dir, $pref, $cmd, $tag, $do_debug, $text );

    #
    #   Recurse into any subdirectories that may be a part of the build
    #
    unless ( exists $defs{NORECURSE} && $defs{NORECURSE}  )
    {
        #
        #   Process subdirectories for the current command
        #   If the command is tagged, then we may be able to skip it
        #   Otherwise we need to do it
        #
        foreach my $subdir (@{$Tags->{$dir}{subdirs}} )
        {

            InvokeSubMake( CleanDirName( "$dir/$subdir"), $pref, $cmd, $tag, $do_debug, $text );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : InvokeMake
#
# Description     : Actually invoke the make for debug and production as required
#
# Inputs          : $dir
#                   $pref
#                   $cmd
#                   $tag
#                   $do_debug               - 0:Production, 1:Debug
#                   $text                   - Single Char display text (C, P or D)
#
#
# Returns         : 
#
sub InvokeMake
{
    my ( $dir, $pref, $cmd, $tag, $do_debug, $text ) = @_;

    Verbose2 ("InvokeMake: $dir");
    #
    #   Ensure that the current directory actually has makefile targets
    #   Not all makefile.pl create .mk files
    #       1) Top-level makefile.pl
    #       2) Makefiles that use --NoPlatformBuilds
    #
    if ( $Tags->{$dir}{root} )
    {
        Verbose2 "Root directory has no makefiles: $dir";
        return;
    }

    if ( $Tags->{$dir}{noplatforms} )
    {
        Verbose2 "No make targets in $dir";
        return;
    }

    #
    #   Process each platform
    #
    foreach my $target ( @{$pref} )
    {
        unless ( exists $Tags->{$dir}{platforms}{$target} )
        {
            Verbose2 "No make targets in $dir, for $target";
            next;
        }

        #
        #   Do we need to do any thing for this target / tag
        #
        if ( $tag )
        {
            unless ( exists $Tags->{$dir}{full}{$target}{'%MakeTags'}{$tag} )
            {
                Verbose2 ("Skipping $cmd in $dir for $tag");
                next;
            }
        }

        #
        #   common modifier mask=2, (Bit-1)
        #       Only process target ONCE for each platform
        #       Don't care when its used
        #
        if ( $commands{$cmd}{'common'} & 2 )
        {
            if ( $common_cmd{$cmd}{$text}{$target} )
            {
                Verbose2 "Already performed $cmd for $target in mode $text";
                next;
            }
            $common_cmd{$cmd}{$text}{$target} = 1;
        }

        #
        #   Is the build limited to only debug or production
        #
        unless ( $commands{$cmd}{'common'} & 1 )
        {
            if ( $do_debug == 0 ) {
                unless ( $Tags->{$dir}{platforms}{$target}{prod} )
                {
                    Verbose("Skip debug build in Production Only build");
                    next;
                }

            } elsif  ( $do_debug == 1 ) {
                unless ( $Tags->{$dir}{platforms}{$target}{debug} )
                {
                    Verbose("Skip prod build in Debug Only build");
                    next;
                }
            }
        }

        #
        #   Final sanity test
        #   Must have the makefile. We should have detected this error before now
        #
        Error ("Makefile not found - $target") unless ( -f "$dir/$target.mk" );

        #
        #   Export data into the user environment
        #   Allows underlying tools to locate data files with little effort
        #
        $ENV{'GBE_MAKE_TYPE'} = $text;                  # P or D or C
        $ENV{'GBE_MAKE_TARGET'} = $target;              # Target platform
        $ENV{'GBE_MAKE_CFG'} = $Tags->{$dir}{config};   # Ref to config data
        $ENV{'GBE_MAKE_CMD'} = $cmd;                    # Make phase

        #
        #   Build up the make command line
        #   Examine command specfic flags
        #
        my @args = @defs;
        push @args, "IFLAG=$commands{$cmd}{IFLAG}" if ( exists $commands{$cmd}{IFLAG} );

        my $cdir = CleanDirName ($dir);
        my @make_command;
        push @make_command, "timeout", "-Time:$opt_max_make" if ($opt_max_make); 
        push @make_command, "$ENV{GBE_BIN}/xmake";
        push @make_command, "-s" if $opt_silent;
        push @make_command, "--no-print-directory";
        push @make_command, "-C", $cdir unless ( $cdir eq $::Cwd );
        push @make_command, "-f", "$target.mk";
        push @make_command, @args;
        push @make_command, 'make_dir' unless exists $commands{$cmd}{nomakecheck} ;
        push @make_command, $cmd;

        Message ( sprintf ("[$text] %-${tlen}s, $cmd, $cdir", $target ));
        System (@make_command, "DEBUG=$do_debug" );
    }
}

#-------------------------------------------------------------------------------
# Function        : TestMakeFiles
#
# Description     : Test all the makefile dependent files to see if any of the
#                   makefiles need to be rebuilt.
#
#                   Walk the directory tree looking for makefiles to be
#                   rebuilt.
#
# Inputs          : $cmd            - Current command
#                   $pref           - Ref to an array of platforms
#
# Returns         : 
#
sub TestMakeFiles
{
    my ( $cmd, $pref ) = @_;
    Verbose ("Test makefile dependencies");

    #
    #   Read in the Tag file for the current directory
    #   This will provide the current list of subdirectories
    #
    #   Process Test the current makefiles, then test any subdirs
    #
    TestSubMake( $cmd, $Cwd, $pref );

    #
    #   Report build errors
    #   Done after all makefiles have been processed
    #
    if ( $build_error )
    {
        Error ("Error creating makefiles. Errors previously reported");
    }

    #
    #   Post makefile build processing
    #   Only required if a files have been re-built
    #
    if ( $update )
    {
        #
        #   Sanity test the makefile structure
        #   Ensure that makefiles have only one parent.
        #
        TestParents();

        #
        #   Sanity test the installed and packaged files
        #   Generate warnings and errors if a file if packaged from two
        #   locations
        #
        TestPackages();

        #
        #   Generate DPACKAGE file if required
        #
        JatsDPackage::DPackageGenerate( $::ScmRoot, $::ScmInterface  );
    }

    #
    #
    #   Check that the build.pl file is not newer that the tags file
    #   This will not be an error, but it will be a nagging warning
    #
    my @build_warn;
    my $bstamp = -M "$::ScmRoot/$ScmBuildSrc";
    my $tstamp = -M "$::ScmRoot/Makefile.gbe";

    push @build_warn, "Missing: Makefile.gbe" unless ( defined $tstamp );
    push @build_warn, "Modified build file ($ScmBuildSrc)" if ( $tstamp && $bstamp < $tstamp );
    if ( @build_warn )
    {
        Warning ("The build file is newer than the generated files",
                 "The project needs to be built for these changes to be included",
                 @build_warn );
    }
}

#-------------------------------------------------------------------------------
# Function        : TestSubMake
#
# Description     : Test the makefiles in the current directory
#                   Recurse into subdirectories
#
# Inputs          : $cmd            - Current command
#                   $dir            - Directory to test
#                   $pref           - Ref to an array of platforms
#
# Returns         : 
#
sub TestSubMake
{
    my ($cmd, $dir, $pref ) = @_;

    $Tags = ReadMaketags( $dir, 1 );
    unless ( $Tags )
    {
        Verbose2 ("TestSubMake :Directory not in tag database", $dir );
        MakeBuild( $dir);
    }
    else
    {
        TestMake( $cmd, $dir, $pref );
    }

    #
    #   Recurse into any subdirectories that may be a part of the build
    #
    unless ( exists $defs{NORECURSE} && $defs{NORECURSE}  )
    {
        foreach my $subdir (@{$Tags->{$dir}{subdirs}} )
        {
            TestSubMake( $cmd, CleanDirName( "$dir/$subdir"), $pref );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : ShowMakefiles
#
# Description     : Show the makefiles used in the build
#
# Inputs          : $cmd            - Current command
#                   $pref           - Ref to an array of platforms
#
# Returns         : 
#
sub ShowMakefiles
{
    my ( $cmd, $pref ) = @_;
    Verbose ("Show makefiles");
    ShowTargetsAlaises();

    #
    #   Read in the Tag file for the current directory
    #   This will provide the current list of subdirectories
    #
    #   Process Test the current makefiles, then any subdirs
    #
    ShowSubMake( $cmd, $Cwd, $pref, 0 );
}

#-------------------------------------------------------------------------------
# Function        : ShowSubMake
#
# Description     : Test the makefiles in the current directory
#                   Recurse into subdirectories
#
# Inputs          : $cmd            - Current command
#                   $dir            - Directory to test
#                   $pref           - Ref to an array of platforms
#                   $level          - Recursion depth
#
# Returns         : 
#
sub ShowSubMake
{
    my ($cmd, $dir, $pref, $level ) = @_;

    $Tags = ReadMaketags( $dir, 1 );
    unless ( $Tags )
    {
        Verbose2 ("ShowSubMake :Directory not in tag database", $dir );
        MakeBuild( $dir);
    }
    elsif ( $level )
    {
        print ("Makefile : " . ' ' x (4 * ($level - 1) ) . DisplayPath(RelPath($dir)) ,"\n");
    } else {
        print ("\nBuildfile: ", DisplayPath($dir), "\n" );
    }

    #
    #   Recurse into any subdirectories that may be a part of the build
    #
    foreach my $subdir (@{$Tags->{$dir}{subdirs}} )
    {
        ShowSubMake( $cmd, CleanDirName( "$dir/$subdir"), $pref, $level + 1 );
    }
}

#-------------------------------------------------------------------------------
# Function        : TestMake
#
# Description     : Test the makefiles component files
#                   and determine if the makefile(s) need to be rebuilt
#
# Inputs          : $cmd            - Current command
#                   $dir            - Directory to test
#                   $pref           - Ref to an array of platforms
#
# Returns         : 
#
sub TestMake
{
    my ( $cmd, $dir, $pref ) = @_;
    my @must_rebuild;

    if ( $cmd eq 'rebuild' )
    {
        MakeBuild( $dir);
        return;
    }

    #
    #   Check that the local makefile.pl is not newer than
    #   the Tags file. Not all makefile.pl's create .mk files
    #
    my $mstamp = -M "$dir/makefile.pl";
    my $tstamp = -M "$dir/Makefile.gbe";

    unless ( defined $tstamp )
    {
        UniquePush (\@must_rebuild, "Missing: $dir/Makefile.gbe");
    }

    if ( $mstamp && $mstamp < $tstamp  )
    {
        UniquePush (\@must_rebuild, "Updated: $dir/makefile.pl");
    }
    else
    {
        if ( $Tags->{$dir}{root} )
        {
            Verbose2 "TestMake: Root directory has no makefiles: $dir";
            return;
        }

        if ( $Tags->{$dir}{noplatforms} )
        {
            Verbose2 "TestMake: No make targets in $dir";
            return;
        }

        #
        #   Process only the required build targets
        #
        foreach my $tgt ( keys %{$Tags->{$dir}{platforms}}   )
        {
            next if ( %gbe_platform && ! exists $gbe_platform{$tgt} );
            #
            #   Locate all the makefile dependent files
            #
            my $data = $Tags->{$dir}{full}{$tgt}{'@ScmDepends'};
            if ( $data )
            {
                Verbose2 ("Processing: $dir : $tgt");
                my $base_stamp = -M "$dir/$tgt.mk";
                unless ( defined $base_stamp )
                {
                    UniquePush (\@must_rebuild, "Missing: $dir/$tgt.mk");
                }
                else
                {
                    foreach my $file ( "$dir/makefile.pl" ,@$data )
                    {
                        $file = FullPath ( $file, $dir );
                        my $stamp = -M $file;
                        if ( defined $stamp )
                        {
                            Verbose3 ("Timestamp: $stamp, $file");
                            if ( $stamp < $base_stamp  )
                            {
                                UniquePush (\@must_rebuild, $file);
                            }
                        }
                        else
                        {
                            UniquePush (\@must_rebuild, "Missing: $file");
                        }
                    }
                }
            }
            else
            {
                Warning ("No dependency information for: $tgt in $dir");
            }
        }
    }

    if ( @must_rebuild )
    {
        my @display;
        push (@display, CleanDirName($_) ) foreach ( @must_rebuild );
        Message ("One or more JATS source or internal files have changed, Makefiles will be rebuilt",
                  "Target is: $dir",
                  @display );
        MakeBuild ($dir);
    }
    return;
}

#-------------------------------------------------------------------------------
# Function        : MakeBuild
#
# Description     : Rebuild the makefiles in the specified directory
#                   This does not involve recursion - thats already been
#                   done.
#
#                   Once the makefiles have been re-built the tags database
#                   must be read in again as it may have changed.
#
# Inputs          : 
#
# Returns         : 
#
sub MakeBuild
{
    my ($dir) = @_;

    #
    #   No makefiles to build in the root directory
    #   Can't rebuild the top-level Makefile.gbe file
    #
    return if ( $Tags->{$dir}{root} );

    #
    #   Try to rebuild the makefile
    #
    chdir $dir || Error ("Cannot change directory to $dir");
    my $result = System ('--NoExit', $ENV{GBE_PERL}, 'makefile.pl',
                            $::ScmRoot, $makelib, "--interface=$::ScmInterface" );
    chdir $::Cwd || Error ("Cannot change directory to $::Cwd");

    #
    #   Re-read the tags database
    #
    $Tags = ReadMaketags( $dir, 2 );

    #
    #   Flag that makefiles have been modified
    #
    $update = 1;
    $build_error = 1 if ( $result );
}

#-------------------------------------------------------------------------------
# Function        : UnMakeFiles
#
# Description     : Delete generated makefiles and control files
#
# Inputs          : $cmd            - Current command
#                   $pref           - Ref to an array of platforms
#
# Returns         : 
#
sub UnMakeFiles
{
    #
    #   Recurse down the tree
    #   Recurse, then performs operations in the current directory
    #
    sub UnSubMakeFiles
    {
        my ($dir) = @_;
        $Tags = ReadMaketags( $dir, 1 );
        foreach my $subdir (@{$Tags->{$dir}{subdirs}} )
        {
            UnSubMakeFiles( CleanDirName( "$dir/$subdir") );
        }
        UnMake( $dir );
    }

    #
    #   Delete makefiles in the current directory
    #
    sub UnMake
    {
        my ($dir) = @_;
        Verbose("Unmake in $dir");
        foreach my $tgt ( keys %{$Tags->{$dir}{platforms}}   )
        {
            Verbose2("Unmake. Delete ${tgt}.mk");
            unlink ("$dir/${tgt}.mk");
        }
        unlink ("$dir/Makefile.gbe");
        Verbose2("Unmake. Delete Makefile.gbe");
    }

    #
    #   Depth first recursion through the tree
    #
    UnSubMakeFiles ( $Cwd );
}

#-------------------------------------------------------------------------------
# Function        : TestParents
#
# Description     : Ensure that each makefile node has exactly one parent
#                   Prevent the user from creating recursive loops
#
# Inputs          : 
#
# Returns         : 
#
my %parents;
sub TestParents
{
    Verbose ("Test makefile parents");
    #
    #   Init the hash. Command may be invoked multiple times
    #
    %parents = ();

    #
    #   Recurse down the tree
    #   Recurse, then performs operations in the current directory
    #
    sub RecurseDown
    {
        my ($dir) = @_;
        $Tags = ReadMaketags( $dir, 1 );
        Verbose2("TestParents in $dir");
        foreach my $subdir (@{$Tags->{$dir}{subdirs}} )
        {
            my $subdir = CleanDirName( "$dir/$subdir");
            push @{$parents{$subdir}}, $dir;
            RecurseDown( $subdir );
        }
    }

    #
    #   Depth first recursion through the tree
    #
    RecurseDown ( $Cwd );

    #
    #   Test for only one parent
    #   The root makefile won't have any
    #
    foreach my $dir ( sort keys %{$Tags} )
    {
        my $count = $#{$parents{$dir}};
        if ( $count > 0 )
        {
            if ( defined($GBE_ABT) )
            {
                Warning ("Error supressed by ABT");
                Warning ("makefile.pl with multiple parents",
                         "makefile.pl in: $dir",
                         "Parented by:", @{$parents{$dir}} );
            }
            else
            {
                unlink $Tags->{$dir}{config};
                Error ("makefile.pl with multiple parents",
                       "makefile.pl in: $dir",
                       "Parented by:", @{$parents{$dir}} );
            }
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : TestPackages
#
# Description     : Ensure that files that are packaged and installed are
#                   only done from one makefile. Warn if the same file
#                   is being packaged from multiple makefiles.
#
# Inputs          : 
#
# Returns         : 
#
my %test_packages;
sub TestPackages
{
    Verbose ("Test packaged files");
    #
    #   Init the hash. Command may be invoked multiple times
    #
    %test_packages = ();

    #
    #   Recurse down the tree
    #   Recurse, then performs operations in the current directory
    #
    sub TRecurseDown
    {
        my ($dir) = @_;
        Verbose2("TestPackages in $dir");
        $Tags = ReadMaketags( $dir, 1 );
        #
        #   Process makefile
        #   Examine all the %PACKAGE_xxx and %INSTALL_xxx fields thare listed
        #   in the @PACKAGE_VARS and @INSTALL_VARS array
        #
        foreach my $target ( keys %{$Tags->{$dir}{platforms}}   )
        {
            next if ( %gbe_platform && ! exists $gbe_platform{$target} );
            foreach my $field ( @{$Tags->{$dir}{full}{$target}{'@PACKAGE_VARS'}},
                                @{$Tags->{$dir}{full}{$target}{'@INSTALL_VARS'}} )
            {
                foreach my $entry ( keys %{$Tags->{$dir}{full}{$target}{$field}} )
                {
                    Verbose3("TestPackages: $target, File: $entry");
                    push @{$test_packages{$target}{$entry}}, $dir;
                }
            }
        }

        #
        #   Process subdirectories too
        #
        foreach my $subdir (@{$Tags->{$dir}{subdirs}} )
        {
            TRecurseDown( CleanDirName( "$dir/$subdir") );
        }
    }



    #
    #   Depth first recursion through the tree
    #
    TRecurseDown ( $Cwd );

    #
    #   Test and report files that are packaged in different makefiles
    #   There are two issues:
    #       1) Not good practice
    #       2) May be different files
    #   The warning message is a bit ugly - but it shouldn't pop up often.
    #
#    DebugDumpData ("test_packages", \%test_packages);

    foreach my $target ( sort keys %test_packages )
    {
        foreach my $file ( sort keys %{$test_packages{$target}} )
        {
            #
            #   The descpkg file is known to be packaged from multiple dirs
            #
            next if ( $file =~ m~/descpkg~ );
            if ( $#{$test_packages{$target}{$file}} > 0 )
            {
                Warning ("File is packaged or installed from multiple makefiles",
                         "Target: $target, Packaged file: $file",
                         "Packaged from the following directories:", @{$test_packages{$target}{$file}} );
            }
        }
    }
}


#-------------------------------------------------------------------------------
# Function        : DoHelp
#
# Description     : Display basic help information
#
# Inputs          : None
#
# Returns         :
#
sub DoHelp
{

#
#   Display basic usage information
#
    pod2usage(-verbose => 0,
              -message => "Version: $VERSION",
              -exitval => 'NOEXIT');

    ShowTargetsAlaises();
}

#-------------------------------------------------------------------------------
# Function        : ShowTargetsAlaises
#
# Description     : Display make targets and aliases
#                   Used by 'show' and 'help'
#
# Inputs          : None
#
# Returns         : Nothing
#                   Output via 'print'
#
sub ShowTargetsAlaises
{
    my @targets = sort keys %BUILDINFO;
    my @alias = sort keys %ScmBuildAliases;
    
    #
    #   Determine the max length of target or alias
    #   Used to make pretty display
    #
    my $flen = 6;
    foreach ( @alias, @targets )
    {
        my $len = length $_;
        $flen = $len if ( $len > $flen );
    }

    print "\nActive Platform targets\n";
    foreach (@targets)
    {
        print "    $_\n";
    }

    if ( $#alias >=0 )
    {
        print "\nAlias targets\n";

        #   Display aliases
        #   Indent list by 4 spaces
        #
        foreach ( @alias )
        {
            print PrintList("    $_",4+$flen, split(' ', $ScmBuildAliases{$_}));
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : PrintList
#
# Description     : Pretty format an array to fit within 100 char line
#                   Perform wrapping as required
#
# Inputs          : $title          - Title string
#                   $len            - Desired length of title area
#                   @list           - An array of items to print
#
# Returns         : string
#
sub PrintList
{
    my ($title, $llen, @list) = @_;
    my $string = sprintf ("%-${llen}s :", $title);

    my $nlen = length($string);
    my $nl = "\n" . (' ' x  $nlen );
    $llen = $nlen;

    for my $k (sort @list)
    {
        my $klen = length($k);
        if ($llen + $klen >= 100 )
        {
            $string .= $nl;
            $llen = $nlen;
        }
        $string .= ' ' .  $k;
        $llen += $klen + 1;
    }
    return $string . "\n";
}

#-------------------------------------------------------------------------------
# Function        : ReadMaketags
#
# Description     : Read in the global make tags data base
#                   This contains information on all the components within
#                   the build. The tags allow recursive makes to be pruned.
#
#                   The file may change while the build is running
#                   It can be read multiple times
#
# Inputs          : $dir            - Directory entry to read
#                   $test           - 0: Read. Error if not available (default)
#                                     1: Read if available
#                                     2: Force read
#
# Returns         : Reference to the tag data for the requested directory
#                   Will error if no available
#
our %cf_minfo;
our %cf_minfo2;
my %tag_data;
sub ReadMaketags
{
    my ($dir, $test) = @_;
    $test ||= 0;

    #
    #   Force data re-aquisition
    #
    delete $tag_data{$dir}
        if ( $test == 2 );

    #
    #   Do we already have the entry
    #
    return \%tag_data
        if ( exists ($tag_data{$dir} ));

    #
    #   If the entry is not known, then re-read the index file
    #
#DebugDumpData ("ReadMaketags", \%::cf_filelist);
    unless ( $::cf_filelist{$dir} )
    {
        my $index_file = "$::ScmRoot/$::ScmInterface/Makefile.cfg";
        Error ("Makefile index missing. Rebuild required") unless ( -f $index_file );

        #
        #   Kill the entry in %INC to force the file to be read in
        #
        $::cf_filelist = ();
        delete $INC{ $index_file };
        require $index_file;
    }

    my $cfg_filen = $::cf_filelist{$dir};
    unless ( $cfg_filen )
    {
        return if ( $test );
        Error ("Makefile index entry missing: $dir. Rebuild required");
    }


    #
    #   Read in all the Makefile_x.cfg files
    #
    %cf_minfo = ();
    %cf_minfo2 = ();
    my $cfg_file = "$::ScmRoot/$::ScmInterface/$cfg_filen";

    unless ( -f $cfg_file )
    {
        return if ( $test );
        Error ("Make data file missing: $cfg_file. Rebuild required");
    }

    delete $INC{ $cfg_file };
    require $cfg_file;

    Error ("Makefile info2 not present")
        unless ( keys %::cf_info2 );
    
    Error ("Makefile info2 incorrect version. Rebuild required")
        unless ( exists $::cf_info2{version} && $::cf_info2{version} == 1 );

    %{$tag_data{$dir}} = %::cf_info2;
    $tag_data{$dir}{config} = $cfg_file;
    %{$tag_data{$dir}{full}} = %::cf_info;

#DebugDumpData ("ReadMaketags", $tag_data{$dir});
    return \%tag_data;

}

#-------------------------------------------------------------------------------
# Function        : ExpandComposite
#
# Description     : Expand a command via composite command expansion
#                   A composite command may contain other composite commands
#                   Detect, as an error, recursive expansions
#
# Inputs          : $cmd    - command to expand
#
# Returns         : An array of commands
#
sub ExpandComposite
{
    my @scmds = $_[0];
    my @cmds;
    my %seen;
    while ( @scmds )
    {
        my $cmd = shift @scmds;
        if ( exists $composite_commands{$cmd} )
        {

            Error ("Internal. Recursion in composite command definitions: $cmd")
                if ($seen{$cmd});

            @scmds = (@{$composite_commands{$cmd}}, @scmds);
            $seen{$cmd} = 1;
        }
        else
        {
            push @cmds, $cmd;
        }
    }

    return @cmds;
}

#-------------------------------------------------------------------------------
#   Documentation
#

=pod

=for htmltoc    JATS::make

=head1 NAME

jmake - JATS make support tool

=head1 SYNOPSIS

 Usage: jats make [options][targets][flags]

 Where options:
    -h, -h -h, -man     - Help messages with increasing verbosity
    -verbose            - Verbose operation
    -debug              - Debug operation
    -default=target     - Default 'target' if none is supplied
    -maxTime=duration   - Maximum allowed duration of any make command
    -- make options     - [Advanced] Options passed directly to make

 Where flags are of the form Name=Value
    SHOWENV=1           - Show make environment
    LEAVETMP=1          - Leave temp working files
    NODEPEND=1          - Ignore dependency checking
    EXPERT=1            - Ignore dependency on makefiles
    UTF_POSTPROCESS=1   - Enable Unit Test Post Processing
    OPTIONS=[opt]       - Maketime options [args,allargs,filter...]

 Valid targets include:
    all                 - build and install everything(p*)
    build               - build everything (pu)
    debug               - build all things for debug (pu)
    prod                - build all things for production (pu)
    install             - install public components (pu*)
    ctags               - Generate ctags over the source (assumes debug)(p)
    lint                - lints the source (assumes debug)(p)
    package             - build all packages (pu*)
    package-[set]       - build specific package (see below) (pu*)
    run_tests           - Run the tests specified in the makefile
    run_unit_tests      - Run the automatic unit tests specified in the makefile
    deploy              - Run the deployment scripts (p*)
    rebuild             - recursively rebuild makefiles
    makefiles           - recursively rebuild makefiles, if needed (u)
    depend              - construct the dependencies (u*)
    rmlitter            - remove build litter (core, tmp and err) (*)
    clean               - delete generate, obj, libraries and programs (p*)
    clobber             - delete everything which can be remade (p*)
    help                - A list of platforms and aliases
    show                - A list of platforms, alias, and makefile paths
 Partial Builds
    hdrs                - build to the headers phase (p*)
    libs                - build to the static library stage (p*)
    slibs               - build to the shared librray stage (p*)
    progs               - build to the progs stage (p*)

      (u) undo target available (ie uninstall)
      (p) optional [platform_] prefix targets (ie XXX_build)
      (*) optional [_debug|_prod] postfix targets (ie clean_debug)

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exits.

=item B<-help -help>

Print a detailed help message with an explanation for each option.

=item B<-man>

Prints the manual page and exits.

=item B<-verbose>

Increase the level of verbosity of the program execution

=item B<-debug>

Increase the debug output during program execution

=item B<-default=target>

This options specifies the default target if none is provided on the command
line. Used by the jats wrapper to simplify processing.

=item B<-maxTime=duration>

This option specifies the maximum allowed duration of any make sub command.

Some commands are composed of several subcommands, as are the production and 
debug variants. The time lime is imposed on a sub commands and not the total
collection of commands. The intent of the time limit is to prevent stalled builds 
from hijacking the build process.

The default value will be taken from the EnvVar L<GBE_MAXMAKE>, if present, or 4 hours
if the EnvVar is not present. A value of 0 (zero) will disable this feature.

The duration is specified as a positive floating point number with an optional
modifing character. Valid modifiers are:

=over 4

=item   

B<s> - Seconds (default)

=item   

B<m> - Minutes

=item   

B<h> - hours

=item   

B<d> - Days

=back

Example: C<-MaxTime=5.h>. Will set a Maximum make time of 30 minutes.

=item B<-- Make Options>

Command line arguments that follow a '--' are passed directly to the GNU make
program. This may be useful for advanced debugging of makefiles. These options
include:

=over 8

=item

B<-h>     - Display Make's own help

=item

B<-d>     - Display diagnostic information

=item

B<-p>     - Print make's internal database.

=item

B<-k>     - Keep going when some targets can't be made.

=back

=back

=head1 TARGETS

Targets are described above.

Most targets support three modifiers. These can be used in conjunction with each
other.

=over 8

=item undo

Many target operation can be undone by prefixing the tarhet with 'un' ie:
uninstall.

=item platform prefix

Many target operations can be limited to one platform by prefixing the target
with a valid platform name and an underscore. ie WIN32_all

=item build postfix. prod or debug

Many targets operations can be limited toi either production of debug by
adding '_prod' or '_debug' to the target. ie 'install_debug'

=back

=head1 PARTIAL BUILDS

Partial build targets allow the build process to be partialy completed. This may be of use during development.

The partial build phases are:

=over 4

=item hdrs

Generate header files. Install header files locally.

=item libs

Previous step plus build static libraries, merge static librarys and install static libraries locally.

=item slib

Previous step plus build shared libraries and install shared libraries.

=item progs

Previous step plus build programs and install programs (including test programs). This step is the same as 'build'.

=back

=head1 FLAGS

Flags are in the form TAG=value. This is a format that it used as they will be
passed directly to the underlying makefiles. The recognised flags are:

=over 8

=item B<SHOWENV=1>

This flag will force the 'Environment' to be displayed before commands are executed

=item B<LEAVETMP=1>

This flag will cause temp files, created by the build process, to notr be deleted.

=item B<NODEPEND=1>

This flag will supress dependency checking. Makefiles will not be created when
the makefile.pl is changed. Source files will not be scanned for header files.

=item B<EXPERT=1>

This flag will supress dependency checking between object files and the
generated makefile. This option can be used while test building a large build
when the makefile has been rebuilt but the user does not wish all the object
files to be rebuilt.

=item B<UTF_POSTPROCESS=1>

This flag will enable post processing of test results.

Post processing allows the details of the test results to be captured by the build system.

Post processing is normally only performed within the automated build environment. This flag 
will force the processing within a user development environment.

UTF_POSTPROCESS=1   - Enable Unit Test Post Processing

=item B<OPTIONS=list,list>

This flags passes a list of comma seperated options into the makefile. The exact
set of available options is target specific. Refer to the JATS manual.

=back

=head1 EXAMPLES

=over 4

=item jats all

This command will perform a B<jats build> if the build.pl file has been modified
since the last build. It will then C<make> all the build targets.

=item jats make all

Same as C<jats all>

=item jats make debug

This command will create the B<debug> version of the make
components in the current directory - and referenced sub components.

=item jats make debug NODEPEND=1

Same as C<jats make debug> but the dependency generation phase will be
skipped. In a large build this will speed up the build, but there is an implicit
assumption that file changes do not result in more header or library files being
required. It will use the results of the previous C<make depend> phase.

=item jats make clean

This command will remove all object files, libraries, programs and any other
build artifacts created by the B<make> process.

=item jats make clobber

Similar to C<jats make clean> but it will also delete build artifacts from the
packaging directory.

=back

=cut

