Subversion Repositories DevTools

Rev

Rev 4192 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#
# Module name   : CSHARP
# Module type   : Makefile system
# Compiler(s)   : ANSI C
# Environment(s): WIN32
#
# Description:
#       CSHARP for Windows
#
#............................................................................#
use strict;
use warnings;
use File::Basename;
use File::Spec::Functions;
use MakeEntry;

#
#   External data
#
our %SRC_DEPEND;

#
#   Global data
#
my %resource_files;
my $pdb_none;

my $toolset_name = 'csharp';                           # Toolset name : Error reporting
my $toolset_info;
my $toolset_version = '1.1';
my %ToolsetVersion =
    (
    '1.1' => { 'def'      => 'CSHARP.DEF',              # Def file to use
               'pragma'   => 0,                         # True: Compiler supports #pragma
               'warnings' => '',                        # Comma sep list of warnings to ignore
             },

    '2.0' => { 'def'      => 'CSHARP2005.DEF',
               'pragma'   => 1,
               'warnings' => '1668',
             },

    '3.5' => { 'def'      => 'CSHARP2008.DEF',
               'pragma'   => 1,
               'warnings' => '1668',
             },
             
    '4.0' => { 'def'      => 'CSHARP2010.DEF',
               'pragma'   => 1,
               'warnings' => '1668',
               'platform' => 'x86',
             },

     '4.5' => { 'def'      => 'CSHARP2012.DEF',
                'pragma'   => 1,
                'warnings' => '1668',
                'platform' => 'x86',
              },

     '4.6' => { 'def'      => 'CSHARP2015.DEF',
                'pragma'   => 1,
                'warnings' => '1668',
                'platform' => 'x86',
              },

    );


##############################################################################
#   ToolsetInit()
#       Runtime initialisation
#
##############################################################################

ToolsetInit();

sub ToolsetInit
{

    #.. Parse arguments (Toolset arguments)
    #
    Debug( "$toolset_name(@::ScmToolsetArgs)" );

    foreach $_ ( @::ScmToolsetArgs ) {
        if (/^--Version=(.*)/) {                # MS SDK Version
            $toolset_version = $1;

        } else {
            Message( "$toolset_name toolset: unknown option $_ -- ignored\n" );
        }
    }

    #.. Parse arguments (platform arguments)
    #
    Debug( "$toolset_name(@::ScmPlatformArgs)" );

    foreach $_ ( @::ScmPlatformArgs ) {
        if (/^--product=(.*)/) {                # GBE product

        } elsif (/^--Version=(.*)/) {           # MS SDK Version
            $toolset_version = $1;

        } else {
            Message( "$toolset_name toolset: unknown platform argument $_ -- ignored\n" );
        }
    }

    #.. Validate SDK version
    #   Currently supported versions are described in a HASH
    #
    $toolset_info = $ToolsetVersion{$toolset_version};
    Error( "$toolset_name toolset: Unknown version: $toolset_version" ) unless ( defined $toolset_info );
    
    #.. Standard.rul requirements
    #
    $::s    = undef;
    $::o    = '';
    $::a    = 'netmodule';
    $::so   = 'dll';
    $::exe  = '.exe';

    #.. Toolset configuration
    #
    $::ScmToolsetVersion = "1.0.0";             # our version
    $::ScmToolsetGenerate = 0;                  # generate optional
    $::ScmToolsetProgDependancies = 0;          # handle Prog dependancies myself
    %::ScmToolsetProgSource = (                 # handle these files directly
            '.cs'       => '',                  # Will be flagged as "CSRCS"
            '.resx'     => '--Resource=',       # Will be passed with prefix
            '.dtd'      => '--Dtd=',            # Will be passed with prefix
            );

    #.. define Visual C/C+ environment
    Init( "csharp" );
    ToolsetDefines( $toolset_info->{'def'} );
    ToolsetRules( "csharp.rul" );
#    ToolsetRules( "standard.rul" );


    #.. 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 =
    (
        #
        #   Control the thread model to use
        #   This will affect the compiler options and the linker options
        #
        'noaddlibs'          => { 'ADDLINKLIBS' , undef },      # Don't add link libs
        'addlibs'            => { 'ADDLINKLIBS' , '1' },        # default
        'nowarn='            => { 'NOWARNLIST'  ,\&NoWarns },   # Suppress warnings
        'nopdb'              => { 'PDB_NONE', 1 },              # Disable all PDB files
        'pdb'                => { 'PDB_NONE', undef },          # Enable PDB files: Default
        'subsystem:windows'  => { 'LDSUBSYSTEM' , 'winexe' },
        'subsystem:console'  => { 'LDSUBSYSTEM' , 'exe' },
        'platform:32'        => { 'NET_PLATFORM', 'x86' },
        'platform:64'        => { 'NET_PLATFORM', 'x64' },
        'platform:any'       => { 'NET_PLATFORM', undef },
        'noversiondll'       => { 'NO_VERSIONED_DLLS', 1 },
    );

    #
    #   Set default options
    #
    $::ScmCompilerOpts{'ADDLINKLIBS'} = '1';
    $::ScmCompilerOpts{'NOWARNLIST'} = $toolset_info->{'warnings'};
    $::ScmCompilerOpts{'LDSUBSYSTEM'} = 'winexe';
    $::ScmCompilerOpts{'NET_PLATFORM'} = $toolset_info->{'platform'};
    $::ScmCompilerOpts{'NO_VERSIONED_DLLS'} = undef;
}


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

##############################################################################
#   ToolsetPreprocess()
#       Process collected data before the makefile is generated
#       This, optional, routine is called from within MakefileGenerate()
#       It allows the toolset to massage any of the collected data before
#       the makefile is created
#
##############################################################################
sub ToolsetPreprocess
{
    #
    #   Extract the current state of PDB_NONE
    #   Are PDB files to be constructed.
    #
    $pdb_none = $::ScmCompilerOpts{'PDB_NONE'};
}

##############################################################################
#   ToolsetPostprocess
#       Process collected data as the makefile is generated
#       This, optional, routine is called from within MakefileGenerate()
#       It allows the toolset to massage any of the collected data before
#       the makefile is finally closed created
#
##############################################################################

sub ToolsetPostprocess
{
    #
    #   Generate Recipes to create Resource Files
    #   This is done outside of the Prog and Lib routines
    #   so that they can be agregated
    #
    #   Note: don't make the makefile a dependant as changes to the
    #         makefile won't affect the file
    #
    for my $resource ( sort keys %resource_files )
    {
        my $src  = $resource_files{$resource}{src};
        my $root = $resource_files{$resource}{root};

        my $me = MakeEntry::New (*MAKEFILE, $resource );
        $me->AddComment ("Build Resource: $root" );
#        $me->AddDependancy ( '$(SCM_MAKEFILE)' );
        $me->AddDependancy ( $src );
        if ( exists $SRC_DEPEND{$src} )
        {
            $me->AddDependancy ( split( /$;/, $SRC_DEPEND{$src} ) );
        }
        $me->AddRecipe ( '$(RESGEN)' );
        $me->Print();

        #
        #   Add to the deletion list
        #
        ToolsetGenerate( $resource );
    }
}

#-------------------------------------------------------------------------------
# Function        : Toolset_genres
#
# Description     : Internal function to assist in the creation of a resource
#                   In many cases it will create an entry for later processing
#
# Inputs          : $subdir         - Root of the target directory for the generated
#                                     resource file
#                   $src            - Path to the source resource file
#
# Returns         : Path to the generated resource file
#                   This will be FQN named file
#                   Path to the associated .CS file
#
# Notes           : Create and maintain the %resource_files hash
#                   Key is the path to the compiled file
#                   Values are:
#                       {src}   - Path to the source file
#                       {root}  - Basic file name (Display Purposes Only)
#
#                   Need to create a '.resource' file with a FQN name
#                   This is not that simple. Need to
#                       1) Extract the (optional) ThisName from the .resx file
#                          If not specified then the ThisName is the rootfilename
#                          without any .as[pca]x extension.
#                       2) Extract the namespace from the associated .cs file
#                       3) FQN = NameSpace.ThisName
#
#
sub Toolset_genres
{
    my ($subdir, $src ) = @_;

    #
    #   Ensure that the .cs file also exists
    #   We may have a NAME.Designer.cs or a NAME.cs file - Different VS versions do it differently
    #   The file 'should' be in the same directory, but may be in the parent
    #
    my $csfile;
    my @csNames = qw(.Designer.cs .cs);
    my @csDirs = qw( . ..);
    my($csfilename, $csdirectories, $cssuffix) = fileparse($src, '.resx');
    csScan:
    foreach my $dir (@csDirs){
        foreach my $name (@csNames) {
            my $testPath = catfile($csdirectories, $dir ,$csfilename . $name);
            if (-f $testPath)
            {
                $csfile = $testPath;
                $csfile =~ s~\\~/~g;
                last csScan;
            }
        }
    }

    # Warn if we can't find one.
    # Create a dummy name - may fail later
    #
    unless ($csfile)
    {
        Warning ("$toolset_name toolset: Resx File without a .cs or Designer.cs file", "File: $src");
        ($csfile = $src) =~ s~\.resx$~.cs~;
    }
    
    #
    #   Scan the .resx file looking for the ThisName element
    #   A very simple and crude parser
    #
    my $ThisName;
    my $ThisNameGuess;
    open ( SCAN, '<', $src ) || Error ("Cannot open file for reading: $!", "File: $src" );
    while ( <SCAN> )
    {
        if ( m~\<data name=\"\$this\.Name\"\>~ )
        {
            # Next line will contain the needed data item
            my $element = <SCAN>;
            $element =~ m~\<.+\>(.+)\</.+\>~;
            $ThisName = $1;
            Error ("$toolset_name toolset: Resx parsing: Bad this.Name", "File: $src") unless $ThisName;
            $ThisName =~ s~\s+~~g;
            last;
        }
    }
    close SCAN;

    #
    #   Name not found
    #   Use a default. Filename with any .aspx, .asax, .ascx removed
    #
    unless ( $ThisName )
    {
        $ThisNameGuess = $ThisName;
        $ThisNameGuess = StripDirExt($src);
        $ThisNameGuess =~ s~\.as[pac]x~~i;
    }

    #
    #   Scan the.cs file looking for the namespace and class
    #   A very simple and crude parser
    #
    my $NameSpace;
    my $ClassName;

    open ( SCAN, '<', $csfile ) || Error ("Cannot open file for reading: $!", "File: $csfile" );
    while ( <SCAN> )
    {
        next if ( m ~\s*//~);

        if ( m~namespace\s+(\S+)~ ) {
            $NameSpace = $1;

        } elsif ( m~\s+class\s+(\S+)~ ) {
            $ClassName = $1;

        }
        last if ( defined($NameSpace) && defined($ClassName) );

    }
    close SCAN;
    Error ("$toolset_name toolset: Resx parsing: NameSpace not found", "File: $csfile") unless $NameSpace;

    #
    #   Need to create an output file name that is a function of the FQN
    #   To be backwardly compatible
    #       Use the ClassName - if it was found
    #       Else Use the ThisName - if it was found
    #       Else Use the Guessed ThisName
    #
    if ( !defined($ClassName)) {
        $ClassName = $ThisName;
        if ( !defined($ClassName)) {
            $ClassName = $ThisNameGuess;
        }
    }

    my $root = "$NameSpace.$ClassName.resources";
    my $resource = $subdir . '/' . $root;
    $resource_files{$resource}{src} = $src;
    $resource_files{$resource}{root} = $root;

    return $resource, $csfile;
}


#-------------------------------------------------------------------------------
# Function        : Toolset_gensnk
#
# Description     : Function to create a wrapper file for the processing
#                   of a StrongNameKey file
#
#                   Create only one wrapper per SNK file
#
# Inputs          : $name       - Name of component
#                   $snk        - Path to the SNK file
#
# Returns         : Path to the wrapper file
#
my %snk_data;
sub Toolset_gensnk
{
    my ($name, $snk ) = @_;
    my $file = StripDirExt( $snk );

    #
    #   Only create the file once
    #   Otherwise we will get nasty make messages
    #

    if ( exists $snk_data{$snk} )
    {
        return $snk_data{$snk}{output};
    }

    #
    #   Determine the target name
    #   Create the source file in the currentt directory
    #   If we build it in the OBJ directory we get two files
    #
    my $snk_file = '$(OBJDIR)/' . "Jats_${file}.cs";
    $snk_data{$snk}{output} = $snk_file;
    ToolsetGenerate( $snk_file );

    #
    #   Determine the Tag Name
    #   Used to conatin information in the makefile
    #
    my $tag = "${file}_snk";

    #
    #   Create Rules and Recipes to create the SNK wrapper file
    #
    my $me = MakeEntry::New (*MAKEFILE, $snk_file );
    $me->AddComment ("Build Strong Name Key File Wrapper: $snk" );
    $me->AddDependancy ( $snk );
    $me->AddDependancy ( '$(SCM_MAKEFILE)' );
    $me->AddRecipe ( '$(call GenSnkWrapper,' . $tag .  ')' );
    $me->Print();

    #
    #   Create the data t be placed into the wrapper file
    #
    my ($io) = ToolsetPrinter::New();

    my $ms_snk = $snk;

    $io->Label( "SNK Wrapper file content", $tag );    # label
    $io->SetTag( $tag );                                # macro tag
    $io->Cmd( '// This is JATS GENERATED FILE' );
    $io->Cmd( '//    Do not edit' );
    $io->Cmd( '//    Do not version control' );

    $io->Cmd( 'using System.Reflection;' );
    $io->Cmd( 'using System.Runtime.CompilerServices;' );

    $io->Cmd( '//' );
    $io->Cmd( '// In order to sign your assembly you must specify a key to use. Refer to the' );
    $io->Cmd( '// Microsoft .NET Framework documentation for more information on assembly signing.' );
    $io->Cmd( '//' );
    $io->Cmd( '// Use the attributes below to control which key is used for signing.' );
    $io->Cmd( '//' );
    $io->Cmd( '// Notes:' );
    $io->Cmd( '//   (*) If no key is specified, the assembly is not signed.' );
    $io->Cmd( '//   (*) KeyName refers to a key that has been installed in the Crypto Service' );
    $io->Cmd( '//       Provider (CSP) on your machine. KeyFile refers to a file which contains' );
    $io->Cmd( '//       a key.' );
    $io->Cmd( '//   (*) If the KeyFile and the KeyName values are both specified, the' );
    $io->Cmd( '//       following processing occurs:' );
    $io->Cmd( '//       (1) If the KeyName can be found in the CSP, that key is used.' );
    $io->Cmd( '//       (2) If the KeyName does not exist and the KeyFile does exist, the key' );
    $io->Cmd( '//           in the KeyFile is installed into the CSP and used.' );
    $io->Cmd( '//   (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.' );
    $io->Cmd( '//       When specifying the KeyFile, the location of the KeyFile should be' );
    $io->Cmd( '//       relative to the project output directory which is' );
    $io->Cmd( '//       %Project Directory%\obj\<configuration>. For example, if your KeyFile is' );
    $io->Cmd( '//       located in the project directory, you would specify the AssemblyKeyFile' );
    $io->Cmd( '//       attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]' );
    $io->Cmd( '//   (*) Delay Signing is an advanced option - see the Microsoft .NET Framework' );
    $io->Cmd( '//       documentation for more information on this.' );
    $io->Cmd( '//' );

    $io->Cmd( '[assembly: AssemblyDelaySign(false)]' );
    $io->Cmd( '#pragma warning disable 1699' ) if ($toolset_info->{'pragma'});
    $io->Cmd( '[assembly: AssemblyKeyFile(@"'. $snk  .'")]' );
    $io->Cmd( '#pragma warning restore 1699' ) if ($toolset_info->{'pragma'});
    $io->Cmd( '[assembly: AssemblyKeyName("")]' );
    $io->Newline();

    #
    #   Return the path to where the file will be created
    #
    return $snk_file;
}


###############################################################################
#   ToolsetLD( $name, \@args, \@objs, \@libraries )
#       This subroutine takes the user options and builds the rules
#       required to link the program 'name'.
#
#   Arguments:
#       $name       - Name of the target program
#       $pArgs      - Ref to an array of argumennts
#       $pObjs      - Ref to an array of object files
#       $pLibs      - Ref to an array of libraries
#
#   Output:
#       Makefile recipes to create the Program
#
#   Notes:
#       This Program Builder will handle its own dependancies
#       It will also create rules and recipes to construct various
#       parts directly fromm source
#
#   Options:
#       --Resource=file.resx
#       --Dtd=file.dtd
#       --Icon=file
#       --Entry=xxxxx                   # Entry point
#       --Console                       # Console app
#       --Windows                       # Windows app (default)
#       --DLL                           # As a DLL (No P|D)
#       --Doc
#       --NoPDB
#       CSharpSourceFile
#
#
###############################################################################

sub ToolsetLD
{
    my ( $name, $pArgs, $pObjs, $pLibs ) = @_;
    my ( @reslist, @resources, @csource, @dtd );
    my $no_pdb = $pdb_none;
    my $entry;
    my $noaddlibs;
    my $icon;
    my $docFile;
    my ($base, $root, $full );
    my $link_target = $::ScmCompilerOpts{'LDSUBSYSTEM'};
    my $snk;
    my $is_a_dll;

    #.. Parse arguments
    #
    foreach ( @$pArgs ) {
        if (/^--Resource=(.+)/) {               # Resource definition
            push @reslist, MakeSrcResolve($1);

        } elsif (/^--Dtd=(.+)/) {               # dtd definition
            push @dtd, MakeSrcResolve($1);
            
        } elsif (/^--Icon=(.+)/) {
            Error ("$toolset_name LD: Only one Icon file allowed") if ( $icon );
            $icon = MakeSrcResolve($1);

        } elsif (/^--StrongNameKey=(.+)/) {
            Error ("$toolset_name LD: Only one SNK file allowed") if ( $snk );
            $snk = MakeSrcResolve($1);

        } elsif (/^--Entry=(.+)/) {
            $entry = $1;
            
        } elsif (/^--Doc/) {
            $docFile = 1;

        } elsif (/^--Windows/) {
            $link_target = 'winexe';

        } elsif (/^--Console/) {
            $link_target = 'exe';

        } elsif (/^--DLL/) {
            $is_a_dll = 1;

        } elsif (/^--NoPDB$/) {
            $no_pdb = 1;

        } elsif ( !/^-/ ) {
            push @csource, MakeSrcResolve($_);

        } else {
            Message( "$toolset_name LD: unknown option $_ -- ignored\n" );

        }
    }

    #
    #   Determine the target output name
    #
    $base = $name;
    $root = "\$(BINDIR)/$base";
    $full = $root . $::exe;
    $docFile = "$root.xml" if ( $docFile );

    #
    #   Special case for DLLs that need to be created without a D or P mangled
    #   into the name. Create them as Progs just to fool the system
    #   Used when creating specialised web services
    #
    if ( $is_a_dll )
    {
        #
        #   Create a phony target
        #   The EXE name is not actually created, but the EXE target needs to be retained
        #
        my $exe_name = $root . $::exe;
        my $dll_name = $root . '.' . $::so;
        $full = $dll_name;
        $link_target = 'library';

        my $me = MakeEntry::New (*MAKEFILE, $exe_name, '--Phony' );
        $me->AddComment ("Build Program: $name as a DLL" );
        $me->AddDependancy ( $dll_name );
        $me->Print();

        #
        #   Need to specifically clean this up, since we have fiddled with the
        #   name of the generated file
        #
        ToolsetGenerate( $dll_name );
    }

    #
    #   Create Rules and Recipes to convert the .resx files to .resource files
    #
    foreach my $res ( @reslist )
    {
        my ($res, $cs) = Toolset_genres ('$(OBJDIR)', $res );

        UniquePush ( \@resources, $res );
        UniquePush ( \@csource, $cs );
    }

    #
    #   Create Rules and Recipes to provide Assembly instructions
    #   for the creation of a StrongNameKey
    #
    if ( $snk )
    {
        UniquePush ( \@csource, Toolset_gensnk ($name, $snk ) );
    }

    my ($io) = ToolsetPrinter::New();
    my $dep = $io->SetLdTarget( $name );

    #
    #   Create Rules and Recipes to create the Program
    #   This will be a combination of source, libraries and resources
    #
    my $me = MakeEntry::New (*MAKEFILE, $full );
    $me->AddComment ("Build Program: $name" );
    $me->AddName    ( $docFile ) if ( $docFile );
    $me->AddDependancy ( $dep );
    $me->AddDependancy ( '$(SCM_MAKEFILE)' );
    $me->AddDependancy ( @resources );
    $me->AddDependancy ( @csource );
    $me->AddDependancy ( @dtd );
    $me->AddDependancy ( $icon );
    $me->AddRecipe ( '$(CSC)' );
    $me->Print();


    #
    #.. Compiler command file
    #       Now piece together a variable $(name_ld) which ends up in
    #       the command file linking the application.
    #
    $io->Label( "Linker commands", $name );     # label
    $io->SetTag( "${name}_ld" );                # macro tag

    $io->Label( "Linker Command File", $name ); # label

    #
    #   Basic options
    #
    $io->Cmd( "/target:$link_target" );
    $io->Cmd("/doc:$docFile") if ( $docFile ) ;
    $io->Cmd( "/win32icon:\$(subst /,\\\\,$icon)" ) if $icon;
    $io->Cmd( "/main:$entry" ) if $entry;

    #
    #   Add in the Resource Files
    #          the source files
    #          the libraries
    #
    $io->Cmd( "/res:\$(subst /,\\\\,$_)" ) foreach @resources;
    $io->Cmd( "/res:\$(subst /,\\\\,$_)" ) foreach @dtd;
    $io->Cmd( "\$(subst /,\\\\,$_)" ) foreach @csource;
    $io->LibList( $name, $pLibs, \&ToolsetLibRecipe );
    $io->Newline();


    #.. 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, $full );
    $io->LDDEPEND();

    #
    #   Files to clean up
    #
    ToolsetGenerate( "$root.ld" );
    ToolsetGenerate( "$root.pdb" );
    ToolsetGenerate( $docFile ) if $docFile;


    #.. Package up files that are a part of the program
    #
    PackageProgAddFiles ( $name, $full );
    PackageProgAddFiles ( $name, "$root.pdb", "Class=debug" ) unless ( $no_pdb );
    PackageProgAddFiles ( $name, $docFile, "Class=map" ) if ( $docFile );
}

###############################################################################
#   ToolsetSHLD( $name, \@args, \@objs, \@libraries, $ver )
#       This subroutine takes the user options and builds the rules
#       required to link the program 'name'.
#
#   Arguments:
#       $name       - Name of the target program
#       $pArgs      - Ref to an array of argumennts
#       $pObjs      - Ref to an array of object files
#       $pLibs      - Ref to an array of libraries
#       $ver        - Library Version string
#
#   Output:
#       Makefile recipes to create the DLL
#       Will create both versioned and unversioned DLLs
#
#   Notes:
#       This Library Builder will handle its own dependancies
#       It will also create rules and recipes to construct various
#       parts directly from source
#
#       This is SO close to the ToolsetLD function that its not funny
#
#   Options:
#       --Resource=file.resx
#       --Icon=file
#       --StrongNameKey=file
#       --Doc
#       --NoPDB
#       --NoVersionDll
#       CSharpSourceFile
#
#
###############################################################################
sub ToolsetSHLD
{
    #
    #   Note: Use globals to kill warnings from internal sub
    #         Use _ prefix so that they don't get saved in Makefile_x.cfg
    #         Init as they are global
    #
    our ( $_name, $_pArgs, $_pObjs, $_pLibs, $_ver ) = @_;
    our ( @_reslist, @_resources, @_csource, @_dtd ) = ();
    our $_no_pdb = $pdb_none;
    our $_noaddlibs = 0;
    our $_icon = undef;
    our $_docFile = undef;
    our $_snk = undef;

    my $noVersionedDlls = $::ScmCompilerOpts{'NO_VERSIONED_DLLS'};

    #.. Parse arguments
    #
    foreach ( @$_pArgs ) {
        if (/^--Resource=(.+)/i) {               # Resource definition
            push @_reslist, MakeSrcResolve($1);

        } elsif (/^--Dtd=(.+)/i) {               # dtd definition
            push @_dtd, MakeSrcResolve($1);

        } elsif (/^--Icon=(.+)/i) {
            Error ("$toolset_name SHLD: Only one Icon file allowed") if ( $_icon );
            $_icon = MakeSrcResolve($1);

        } elsif (/^--StrongNameKey=(.+)/i) {
            Error ("$toolset_name SHLD: Only one SNK file allowed") if ( $_snk );
            $_snk = MakeSrcResolve($1);

        } elsif (/^--Doc/i) {
            $_docFile = 1;

        } elsif (/^--NoPDB$/i) {
            $_no_pdb = 1;

        } elsif (/^--NoVersionDll/i) {
            $noVersionedDlls = 1;
            
        } elsif ( !/^-/ ) {
            push @_csource, MakeSrcResolve($_);

        } else {
            Message( "$toolset_name SHLD: unknown option $_ -- ignored\n" );

        }
    }

    #
    #   Create Rules and Recipes to convert the .resx files to .resource files
    #
    foreach my $res ( @_reslist )
    {
        my ($res, $cs) = Toolset_genres ('$(OBJDIR)/' . $_name, $res );

        UniquePush ( \@_resources, $res );
        UniquePush ( \@_csource, $cs );
    }

    #
    #   Create Rules and Recipes to provide Assembly instructions
    #   for the creation of a StrongNameKey
    #
    if ( $_snk )
    {
        UniquePush ( \@_csource, Toolset_gensnk ($_name, $_snk ) );
    }

    #
    #   Build Rules
    #   $1  - Base Name
    #   $2  - Name of the output DLL
    #
    sub BuildSHLD
    {
        my ($name, $lib ) = @_;
        my ($root, $full, $link_target);

        #
        #   Determine the target output name
        #
        $root = "\$(LIBDIR)/$lib";
        $full = "$root.$::so";
        $link_target = "library";
        $_docFile = "$full.xml" if ($_docFile);

        my ($io) = ToolsetPrinter::New();
        my $dep = $io->SetShldTarget( $lib );
        
        #
        #   Create Rules and Recipes to create the Program
        #   This will be a combination of source, libraries and resources
        #
        my $me = MakeEntry::New (*MAKEFILE, $full );
        $me->AddComment ("Build Shared Library: $name" );
        $me->AddName    ( $_docFile ) if ( $_docFile );
        $me->AddDependancy ( '$(SCM_MAKEFILE)' );
        $me->AddDependancy ( $dep );
        $me->AddDependancy ( @_resources );
        $me->AddDependancy ( @_csource );
        $me->AddDependancy ( @_dtd );
        $me->AddDependancy ( $_icon );
        $me->AddRecipe ( '$(CSC)' );
        $me->Print();


        #
        #.. Compiler command file
        #       Now piece together a variable $(name_ld) which ends up in
        #       the command file linking the application.
        #

        $io->Label( "Linker commands", $name );     # label
        $io->SetTag( "${lib}_ld" );                 # macro tag

        $io->Label( "Linker Command File", $lib ); # label

        #
        #   Basic options
        #
        $io->Cmd( "/target:$link_target" );
        $io->Cmd( "/doc:$_docFile")                    if ( $_docFile ) ;
        $io->Cmd( "/win32icon:\$(subst /,\\\\,$_icon)" ) if $_icon;

        #
        #   Add in the Resource Files
        #          the source files
        #          the libraries
        #
        $io->Cmd( "/res:\$(subst /,\\\\,$_)" ) foreach @_resources;
        $io->Cmd( "/res:\$(subst /,\\\\,$_)" ) foreach @_dtd;
        $io->Cmd( "\$(subst /,\\\\,$_)" ) foreach @_csource;
        $io->LibList( $name, $_pLibs, \&ToolsetLibRecipe );
        $io->Newline();

        #.. 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, $full );
        $io->SHLDDEPEND( $name, $lib  );

        #
        #   Files to clean up
        #
        ToolsetGenerate( "$root.ld" );
        ToolsetGenerate( "$root.pdb" );
        ToolsetGenerate( $_docFile ) if $_docFile;


        #.. Package up files that are a part of the Library
        #
        PackageShlibAddFiles ( $name, $full );
        PackageShlibAddFiles ( $name, "$root.pdb", "Class=debug" ) unless ( $_no_pdb );
        PackageShlibAddFiles ( $name, $_docFile  , "Class=map" ) if ( $_docFile );

        #
        #   Return the full name of the created DLL.
        #
        return $full;
    }

    #
    #   Generate DLLs
    #
    #       a) Unversioned DLL  $_name$(GBE_TYPE).dll
    #       b) Versioned DLL    $_name$(GBE_TYPE).xx.xx.xx.dll
    #
    my $funver = BuildSHLD( "$_name", "$_name\$(GBE_TYPE)" );
    unless ($noVersionedDlls)
    {
        my $fver   = BuildSHLD( "$_name", "$_name\$(GBE_TYPE).$_ver" );

        #
        #   Create a dependancy between the version and unversioned DLLs
        #
        my $me = MakeEntry::New (*MAKEFILE, $fver );
        $me->AddComment ("Link Version and Unversioned Images: $_name" );
        $me->AddDependancy ( $funver );
        $me->Print();
    }
}

########################################################################
#
#   Generate a linker/depend library recipe.  This is a helper function
#   used within this toolset.
#
#   Arguments:
#       $io         I/O stream
#
#       $target     Name of the target
#
#       $lib        Library specification
#
#       $dp         If building a depend list, the full target name.
#
########################################################################

sub ToolsetLibRecipe
{
    my ($io, $target, $lib, $dp) = @_;

    if ( !defined($dp) ) {                      # linker
        $io->Cmd( "/reference:\$(subst /,\\\\,\$(strip $lib)).$::so" );

    } else {                                    # depend
        $io->Cmd( "$dp:\t@(vglob2,$lib.$::so,CS_LIB)" );
    }
}

########################################################################
#
#   Generate a project from the provided project solution file
#   This is aimed at .NET work
#
#   Arguments   : $name             - Base name of the project
#                 $solution         - Path to the solutionn file
#                 $pArgs            - Project specific options
#
########################################################################

my $project_defines_done = 0;
sub ToolsetPROJECT
{
    my( $name, $solution ,$pArgs ) = @_;
    my $buildcmd = 'devenv =DSW= /build =TYPE= /useenv /out =LOG=';
    my $cleancmd = 'devenv =DSW= /clean =TYPE= /useenv';
    my $release = 'RELEASE';
    my $debug = 'DEBUG';

    #
    #   Process options
    #
    foreach ( @$pArgs ) {
        if ( m/^--TargetProd*=(.+)/ ) {
            $release = $1;

        } elsif ( m/^--TargetDebug=(.+)/ ) {
            $debug = $1;
            
        } else {
            Message( "$toolset_name PROJECT: unknown option $_ -- ignored\n" );
        }
    }

    my ($io) = ToolsetPrinter::New();

    #
    #   Setup toolset specific difinitions. Once
    #
    unless( $project_defines_done )
    {
        $project_defines_done = 1;
        $io->PrtLn( 'project_target = $(if $(findstring 1,$(DEBUG)),$2,$1)' );
        $io->Newline();
    }

    #
    #   Process the build and clean commands
    #       Substitute arguments
    #           =TYPE=
    #           =LOG=
    #           =DSW=
    #
    $buildcmd =~ s~=TYPE=~"\$(call project_target,$release,$debug)"~g;
    $buildcmd =~ s~=LOG=~$name\$(GBE_TYPE).log~g;
    $buildcmd =~ s~=DSW=~$solution~g;

    $cleancmd =~ s~=TYPE=~"\$(call project_target,$release,$debug)"~g;
    $cleancmd =~ s~=LOG=~$name\$(GBE_TYPE).log~g;
    $cleancmd =~ s~=DSW=~$solution~g;
    
    #
    #   Generate the recipe to create the project
    #   Use the set_<PLATFORM>.sh file to extend the DLL search path
    #
    $io->Label( "Build project", $name );
    $io->PrtLn( "Project_$name: $solution \$(INTERFACEDIR)/set_$::ScmPlatform.sh" );

    $io->PrtLn( "\t\$(XX_PRE)( \$(rm) -f $name\$(GBE_TYPE).log; \\" );
    $io->PrtLn( "\t. \$(INTERFACEDIR)/set_$::ScmPlatform.sh; \\" );
    $io->PrtLn( "\t\$(show_environment); \\" );
    $io->PrtLn( "\t$buildcmd; \\" );
    $io->PrtLn( "\tret=\$\$?; \\" );
    $io->PrtLn( "\t\$(GBE_BIN)/cat $name\$(GBE_TYPE).log; \\" );
    $io->PrtLn( "\texit \$\$ret )" );
    $io->Newline();

    #
    #   Generate the recipe to clean the project
    #
    $io->Label( "Clean project", $name );
    $io->PrtLn( "ProjectClean_$name: $solution" );
    $io->PrtLn( "\t-\$(XX_PRE)$cleancmd" );
    $io->PrtLn( "\t-\$(XX_PRE)\$(rm) -f $name\$(GBE_TYPE).log" );
    $io->Newline();

}

#-------------------------------------------------------------------------------
# Function        : ToolsetTESTFRAMEWORK_NUNIT
#
# Description     : Toolset specfic support for the NUNIT Test FrameWork
#                   Accessed with RunTest ('*', --FrameWork=nunit, ... );
#
#                   Manipulates the pEntry structure to allow JATS to
#                   construct a test entry to run Nunit tests
#
# Inputs          : $pEntry                 - Unit Test Hash
#
# Returns         : Modified Hash
#
sub ToolsetTESTFRAMEWORK_NUNIT
{
    my ($pEntry) = @_;
    my $test_dll_name;
    my @copy_dlls;
    my %copy_dll_flags;

    #
    #   Extract base name of DLL under test
    #   Thsi will not have any extension.
    #
    $test_dll_name = $pEntry->{'prog'};
    Error ("Nunit Framework. No TestDLL specified") unless $test_dll_name;

    #
    #   Process the FrameWork Options
    #
    foreach  ( @{$pEntry->{'framework_opts'}} )
    {
        if ( m/^--Uses=(.+)/ ) {
            my ($dll, @opts) = split (',', $1 );
            push @copy_dlls, $dll;
            foreach  ( @opts )
            {
                if ( m~^--NonJats~i ) {
                    $copy_dll_flags{$dll}{'NonJats'} = 1;
                } elsif ( m~--Jats~ ) {
                    $copy_dll_flags{$dll}{'NonJats'} = 0;
                } else {
                    Error ("Nunit Framework. Unknown sub option to --Uses: $_");
                }
            }
        } else {
            Error ("Nunit Framework. Unknown option: $_");
        }
    }

    #
    #   Locate the Nunit essentials
    #       This list may change with each version of nunit
    #       Look for a known file and use its contents
    #       Format:
    #           One file name per line
    #           Line comments only
    #           Comment marker is a #
    #           First one MUST be the executable
    #
    my @nunit_files;

    my $mfile = 'nunit-jats-manifest.txt';
    my $nunit_file = ToolExtensionProgram ( $mfile );
    Error ("Cannot find Nunit Jats Manifest: $mfile") unless ( $nunit_file );
    open (JM, $nunit_file ) || Error( "Cannot open file: $nunit_file", "Reason: $!" );
    while ( <JM> )
    {
        s~\s+$~~;                   # Remove trailing white space
        s~^\s+~~;                   # Remove Leading whitespace
        next unless ( $_ );         # Skip block lines
        next if ( m~^#~ );          # Skip comments
        Verbose ("Nunit File: $_");
        push @nunit_files, $_;
    }
    close JM;
    
    #
    #   Locate all the required files
    #   The first one will be the console executable
    #
    my @nunit_framework;
    foreach my $file ( @nunit_files )
    {
        my $path = ToolExtensionProgram ($file );
        Error ("Cannot locate nunit file: $file") unless ( $path );
        push @nunit_framework, $path;
    }
    my $nunit_console = $nunit_framework[0];
    Error ("Nunit console executable not specified") unless ( $nunit_console );

    #
    #   Locate the test DLL.
    #   This will be created locally within this makefile
    #   It will be a known shared library
    #
    Errror( "TestDLL does not appear to be locally created: $test_dll_name" )
        unless ( $::SHLIBS->Get($test_dll_name) );

    #
    #   Hard bit. Determine the name/path of the DLL under test
    #   It will have been created within this makefile
    #   This is not a physical file.
    #
    $test_dll_name = $test_dll_name . '$(GBE_TYPE).' . $::so;
    my $test_dll = '$(LIBDIR)/' . $test_dll_name;

    #
    #   Other hard bit
    #   Locate the other dll's needed by this test
    #   Need to use P in production and D in Debug unless otherwise specified
    #   These might be in:
    #       an external package
    #       within the local directory
    #       the current makefile
    #   ie: We can only determine the location of the files at run-time
    #
    #   The mechanism used is:
    #       Create makefile recipe entries to
    #           Use cmdfile to create a command file with copy command
    #           Execute the command file
    #
    #   Complications include:
    #       The windows shell used does not honour 'set -e', so we need to
    #       detect error conditions ourselves
    #
    my $ofile = "\$(TESTDIR)/$pEntry->{test_name}.cmd";
    push @{$pEntry->{'ShellRecipe'}}, "rm -f $ofile";

    my @cmds;
    push @cmds, "\$(cmdfile) -wk1W1o$ofile";
    foreach my $dll ( @copy_dlls )
    {
        #
        #   Generate in-line commands to locate and copy in the required
        #   DLL's. The 'cmdfile' utility can be used to do this at runtime
        #
        my $dll_name = $dll;
        $dll_name .= '$(GBE_TYPE)' unless ( $copy_dll_flags{$dll}{'NonJats'} );

        push @cmds, '"' . "cp -f @(vglob2,$dll_name.$::so,PATH,/) \$(TESTDIR) || exit 98" . '\n"';
    }
    push @cmds, "|| exit 99";
    push @{$pEntry->{'ShellRecipe'}}, \@cmds;
    push @{$pEntry->{'ShellRecipe'}}, ". $ofile";


    #
    #   Add items to the Unit Test Hash
    #       command     - command to execute to run the test program
    #       prog        - test command/script that must be in the test dir
    #       copyprog    - Prog must be copied in
    #       args        - Arguments to the test
    #       copyin      - Array of files to copy into the test directory
    #       copyonce    - Array of files to copy only once
    #       prereq      - Prerequiste conditions
    #       testdir     - Symbolic name of the test directory
    #       ShellRecipe - Recipe Bits to add
    #
    $pEntry->{'command'}  = $nunit_console . ' ' . $test_dll_name;
    unshift @{$pEntry->{args}}, "/xml=$pEntry->{test_name}.xml";
    $pEntry->{'prog'} = $test_dll_name;
    $pEntry->{'copyprog'} = 0;
    push @{$pEntry->{'copyin'}}, $test_dll;
    push @{$pEntry->{'copyonce'}}, @nunit_framework;
    $pEntry->{'testdir'}  = 'TESTDIR';

    #   
    #   Create the Test Directory
    #   Tests will be done in a .NUNIT subdir
    #
    MkdirRule( "\$(TESTDIR)", 'TESTDIR', '--Path=$(GBE_PLATFORM)$(GBE_TYPE).NUNIT', '--RemoveAll' );

    #
    #   Files created by the Unit Test
    #
    ToolsetGenerate ( "\$(TESTDIR)/$pEntry->{test_name}.xml"  );
    ToolsetGenerate ( $ofile  );

}

#.. Successful termination
1;