##############################################################################
#
# Module name   : TOOLSET/KeilArmV3
# Module type   : Makefile system
# Environment(s):
#
# Description:
#   Keil MDK Arm V4.3 toolset
#   A very simple toolset to support
#       1) C compiliation
#       2) Generation of libaries
#       3) Merging of libraries
#       4) Executables
#       5) Assembler files
#
#   Does not support ( because its not needed yet )
#       a) Shared libraries
#
##############################################################################

our @ScmToolsetArgs;

##############################################################################
#   ToolsetInit()
#       Runtime initialisation
#
##############################################################################

ToolsetInit();

sub ToolsetInit
{
    my $GenThumb;
    my $GenDevice;
    #
    #   Version Specific
    #
    my $KeilVersion = "MDK Arm V4.3";
    my $Version = '3.40';
    my $DefFile = 'KeilArmV3.def';

#.. Parse arguments
#
    foreach $_ ( @ScmToolsetArgs ) {
        if ( m~^--Thumb~ ) {
            $GenThumb=1;

        } elsif ( m~^--Device=(.*)~ ) {
            $GenDevice = $1;

        } elsif ( m~^--Version=(.*)~ ) {
            $Version = $1;

        } else {
            Message( "KeilArmV3: unknown option $_ -- ignored\n" );
        }
    }
    Error ("KeilArmV3: Toolset error. --Device must be specified")
        unless ( $GenDevice );

    #
    #   If using a specified version, then ensure that the required
    #   def file is present. This is used to setup the environment
    #   for this family of compilers
    #
    if ( $Version =~ m/4.03a/i)
    {
        $DefFile = 'KeilArmV43a.def';
        $KeilVersion = "MDK Arm V4.03a";
    }
    Error ("Unsupported compiler version: $Version")
        unless (Exists( "$::GBE_CONFIG/TOOLSET", $DefFile));

#.. Standard.rul requirements
#
    $s = 's';
    $o = 'o';
    $a = 'lib';
    $exe = '.bin';

    AddSourceType( ".$s", '.asm' );

#.. Define environment
#
    Init( "KeilArm" );

    ToolsetDefine( "#################################################" );
    ToolsetDefine( "# Compiler version" );
    ToolsetDefine( "#" );
    ToolsetDefine( "KeilC Version      = \"$KeilVersion\"" );
    ToolsetDefine( "USE_THUMB = 1" ) if $GenThumb;
    ToolsetDefine( "USE_DEVICE = $GenDevice" );
    ToolsetDefine( "" );
    ToolsetDefine( "#" );

    ToolsetDefines( $DefFile );
    ToolsetRules  ( "KeilArmV3.rul" );
    ToolsetRules  ( "standard.rul" );

    #
    #   Extend the cleanup rule
    #
    ToolsetGenerate( '*.lst' );

#.. Extend the CompilerOption directive
#   Create a standard data structure
#   This is a hash of hashes
#       The first hash is keyed by CompileOption keyword
#       The second hash contains pairs of values to set or remove
#
    %::ScmToolsetCompilerOptions =
    (
        'nowarn='             => { 'NOWARNLIST'   ,\&NoWarns }, # Suppress warnings
        'timeoptimization'    => { 'OPT_MODE' , 'time'  },      # Time optimize
        'spaceoptimization'   => { 'OPT_MODE' , 'space' },      # Space optimize
        'defaultoptimization' => { 'OPT_MODE' , undef },        # Default (?space)
    );


    #
    #   Set default options
    #       $::ScmCompilerOpts{'xxxx'} = 'yyy';
    #
    $::ScmCompilerOpts{'NOWARNLIST'}  = '';
    $::ScmCompilerOpts{'OPT_MODE'}    = 'space';

}

#-------------------------------------------------------------------------------
# Function        : NoWarns
#
# Description     : ScmToolsetCompilerOptions  extension function
#                   Accumulates the NoWarn options as a comma seperated list
#
# Inputs          : $key        - Name of the Option
#                   $value      - Option Value. Comma sep list of numbers
#                   $ukey       - User key (within $::ScmCompilerOpts)
#
# Returns         : New sting to save
#
sub NoWarns
{
    my ($key, $value, $ukey) = @_;
    my @NoWarnList =  split (',', $::ScmCompilerOpts{$ukey});
    UniquePush ( \@NoWarnList, split (',', $value) );
    return join ',', @NoWarnList;
}


###############################################################################
#   ToolsetAS( $source, $obj, \@args )
#       This subroutine takes the user options and builds the rule(s)
#       required to compile the source file 'source' to 'obj'
#
###############################################################################

sub ToolsetAS
{
    MakePrint( "\n\t\$(AS)\n" );
}

sub ToolsetASDepend
{
}

###############################################################################
#   ToolsetCC( $source, $obj, \@args )
#       This subroutine takes the user options and builds the rule(s)
#       required to compile the source file 'source' to 'obj'
#
###############################################################################

sub ToolsetCC
{
    my( $source, $obj, $pArgs ) = @_;

    Debug( "CC:  $source -> $obj" );
    foreach ( @$pArgs ) {
        Debug( "option:    $_" );
        if ( /--Shared$/ ) {                    # Building a 'shared' object
            $cflags = "$cflags \$(SHCFLAGS)";
            Debug( "CC:    as shared object" );
        } else {                                # unknown option
            Message( "CC: unknown option $_ -- ignored\n" );
        }
    }

    MakePrint( "\n\t\$(CC)\n" );
    MakePrint( "\$(OBJDIR)/$i.${o}:\tCFLAGS +=$cflags\n" )
        if ( $cflags );
}

###############################################################################
#   ToolsetCCDepend( $depend, \@sources )
#       This subroutine takes the user options and builds the
#       rule(s) required to build the dependencies for the source
#       files 'sources' to 'depend'.
#
###############################################################################

sub ToolsetCCDepend
{
    MakePrint( "\t\$(CCDEPEND)\n" );
}

###############################################################################
#   ToolsetAR( $name, \@args, \@objs )
#       This subroutine takes the user options and builds the rules
#       required to build the library 'name'.
#
#   Arguments:
#       n/a
#
#   Output:
#       [ $(BINDIR)/name$.${a}:   .... ]
#           $(AR)
#
###############################################################################

sub ToolsetAR
{
    my( $name, $pArgs, $pObjs ) = @_;

#.. Parse arguments
#
    foreach $_ ( @$pArgs ) 
    {
        Message( "AR: unknown option $_ -- ignored\n" );
    }

#
    MakeEntry( "\$(LIBDIR)/$name\$(GBE_TYPE).${a}:\t", "", " \\\n\t\t", ".${o}", @$pObjs );

#.. Build library rule (just append to standard rule)
#
    MakePrint( "\n\t\$(AR)\n\n" );
}


###############################################################################
#   ToolsetARMerge()
#       Generate the recipe to merge libraries.
#       The dependency list is created by the caller.
#
###############################################################################

sub ToolsetARMerge
{
    MakePrint( "\n\t\$(ARMERGE)\n\n" );
}


#-------------------------------------------------------------------------------
#   ToolsetLD( $name, \@pArgs, \@pObjs, \@pLibs )
#       This subroutine takes the user options and builds the rules
#       required to link the program 'name'.
#
#   Toolset is configured to suppress partial creation of the Package
#   Rules. This function must create the complete rule and recipe set.
#
#   Arguments:
#       $name           - Name of the output
#       $pArgs          - Ref to array of args
#       $pObjs          - Ref to array of objects
#       $pLibs          - Ref to array of libs
#
#  Options:
#       --Map           - Create a MAP file
#       --NoMap         - Don't create a MAP file
#       --Scatter=file  - Names a scatter file to be used
#       --ro-base=text  - Names the ReadOnly base
#       --rw-base=text  - Names the ReadWrite base
#       --Script=file   - Additional Linker commands
#
#   Output:
#       Generates makefile rules and recipes to create a program
#

sub ToolsetLD
{
    my( $name, $pArgs, $pObjs, $pLibs ) = @_;
    my $map_file;
    my $scatter_file;
    my $ro_base;
    my $rw_base;
    my @script_files;

#.. Parse arguments
#

    foreach ( @$pArgs )
    {
        if ( m~^--Map~i ) {
            $map_file = 1;

        } elsif ( m~^--NoMap~i ) {
            $map_file = 0;

        } elsif ( m~^--Scatter=(.+)~i ) {
            Src ('*', $1 );
            $scatter_file = MakeSrcResolve($1);

        } elsif ( m~^--Script=(.+)~i ) {
            Src ('*', $1 );
            push @script_files, MakeSrcResolve($1);

        } elsif ( /^--ro-base=(.+)/i ) {
            $ro_base = $1;

        } elsif ( m~^--rw-base=(.+)~i ) {
            $rw_base = $1;
            
        } else {
            Error( "LD: unknown option $_ -- ignored\n" );
        }
    }

    #
    #   Sanity test
    #
    Error ("Can't use --scatter in conjunction with -ro-base or -rw-base")
        if ( $scatter_file && ( $ro_base || $rw_base) );

    #
    #   Determine the target output name
    #
    my $root = "\$(BINDIR)/$name";
    my $full = $root . $::exe;
    my $axf  = $root . '.axf';
    my $map  = $root . '.map';
    my $call = $root . '.htm';

    #.. Packageing
    #   Have supressed default Prog building
    #   Need to specify the files to Package
    #
    PackageProgAddFiles ( $name, $full );
    PackageProgAddFiles ( $name, $map, 'Class=map' ) if $map_file;


    #.. Cleanup rules
    #
    ToolsetGenerate( $map );
    ToolsetGenerate( $axf );
    ToolsetGenerate( $call );

    #.. Build rules
    #
    my ($io) = ToolsetPrinter::New();
    my $dep = $io->SetLdTarget( $name );

    #.. Dependency link,
    #   Create a library dependency file
    #       Create command file to build applicaton dependency list
    #       from the list of dependent libraries
    #
    #       Create makefile directives to include the dependency
    #       list into the makefile.
    #
    $io->DepRules( $pLibs, \&ToolsetLibRecipe );
    $io->LDDEPEND();

    #
    #   List the object files
    #   Create a definition for the objects
    #
    $io->Label( "Object files", $name );            # label
    $io->StartDef ("${name}_obj");
    $io->ObjList( $name, $pObjs, \&ToolsetObjRecipe );
    $io->EndDef ();
    $io->Newline();

    #
    #   Define the program and its dependencies
    #   Place the .dep file first - this will ensure that failure
    #   to create this file will create an error before the object
    #   files are compiled. This makes it obvious what the error is.
    #
    $io->Label( "Program", $name );                     # label
    $io->Prt( "$axf : \t$dep" );                        # Dependencies
    $io->Prt( " \\\n\t$scatter_file" ) if ($scatter_file);
    $io->Prt( " \\\n\t$_" ) foreach (@script_files);
    $io->Prt( " \\\n\t\$(${name}_obj)" .
              " \\\n\t\$(${name}_lib)" );
    $io->Newline();

    #
    #   Recipe to build the program
    #
    $io->PrtLn( "\t\$(LD)" );


    #.. Linker command file
    #
    #       Create a variable $(name_ld) which will be the linker
    #       command line. Use previosly defined values
    #
    $io->Label( "Linker commands", $name );         # label
    $io->StartDef ("${name}_ld");
    $io->Def( "\$(${name}_obj)" );                  # Object file list variable
    $io->Def( "\$(${name}_lib)" );                  # Library list variable
    $io->Def( "--scatter=$scatter_file" ) if ($scatter_file);
    $io->Def( "--ro-base=$ro_base" ) if defined ($ro_base);
    $io->Def( "--rw-base=$rw_base" ) if defined ($rw_base);
    $io->Def( "--list=$map" ) if ( $map_file );
    $io->Def( "--via=$_" ) foreach (@script_files);
    $io->EndDef ();
    $io->Newline();

    #
    #   Create rules to convert the .axf file into a bin file
    #   Done as a seperate step
    #
    #
    $io->Label( "Elf Converion ", $name );             # label
    $io->Prt( "$full : \t$axf\n" );                    # Dependencies

    #
    #   Recipe to build the program
    #
    $io->PrtLn( "\t\$(FROMELF)" );

}


########################################################################
#
#   Generate a linker object recipe.  This is a helper function used 
#   within this toolset.
#
#   Arguments:
#       $io         I/O stream
#
#       $target     Name of the target
#
#       $obj        Library specification
#
########################################################################

sub ToolsetObjRecipe
{
    my ($io, $target, $obj) = @_;

    $io->Def( "$obj.$::o" );
}


###############################################################################
#
#   Parse a linker lib list
#   This is a helper function used within this toolset
#
#   Used to create a variable that will be fedd into 'cmdfile'
#   The output will then be included in the makefile
#   The output extends the definitions of the program being built
#   to contain the absolute pathnames to the libraries being consumed.
#
#   Arguments:
#       $io         io printer class
#
#       $target     Name of the target
#
#       $lib        Library specification
#
###############################################################################

sub ToolsetLibRecipe
{
    my ($io, $target, $lib) = @_;

    $io->Cmd( "${target}_lib += @(vglob2,$lib.$::a,LIB)" );
}

#.. Successful termination
1;

