Subversion Repositories DevTools

Rev

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

########################################################################
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
#
# Module name   : ManifestFiles.pm
# Module type   : Makefile system
# Environment(s): JATS Build System
# Documents     : MASS-00232 Format of the Linux App Upgrade Manifest File
#
# Description   : This package extends the JATS toolset at build time
#                 It provides additional directives to the JATS makefiles
#                 to simplify the directives.
#
#                 This directive does all its work at 'build' time
#                 It uses makefile.pl directives
#
# Operation     : This package adds the JATS directive ManifestFiles
#                 This is used to create linux manifest files
#
# Syntax        : ManifestFiles (<platforms>, Options+);
#                 See the function header for details
#
#......................................................................#

require 5.6.1;
use strict;
use warnings;
use ArrayHashUtils;

#
#   Globals
#   These are exposed to all of JATS, so:
#       Limit the number (ideally none)
#       Be away of the potential name conflicts
#

#-------------------------------------------------------------------------------
# Function        : BEGIN
#
# Description     : Setup directive hooks
#                   Register a function to be called just before we start to
#                   generate makefiles. This will be used to process the data
#                   collected in all the ManifestFiles directives
#
# Inputs          : None
#
# Returns         : None
#
BEGIN
{
    RegisterMakefileGenerate (\&ManifestFiles::Generate);
}

#-------------------------------------------------------------------------------
# Function        : ManifestFiles
#
# Description     : Create a ManifestFiles entry
#
# Inputs          : Platform             - Active Platform Selector
#                   Options              - One or more options
#                       --Name=Name             - Mandatory
#                       --Tier=Name             - Mandatory
#                       --Architecture=xxxx     - Default actitecture is the current target
#                       --Product=yyyy          - Product Family
#                       --Debian=BaseName[,--Prod,--Debug,--Arch=xxxx,--Product=yyyy]
#                       --Apk=BaseName[,--Prod,--Debug,--Platform=pppp]
#                                                   Default platform = ANDROID
#                                                   Default type = Production
#                       --MugPackage=Package[,--Subdir=subdir[,subdir]+]
#                       --SrcFile=xxx
#                       --SrcFileNoCopy=xxx
#                       --Comment=xxx           - Add comment to Manifst
#                       --NoManifestVersion     - Create unversioned Manifest File
#                       --PkgSubdir=xxx         - Specifies packaging subdir
#                       --LocalInstall          - Output manifest to the 'local' directory, not the output package
#                       --ImportManifest=Package[,--Subdir=subdir,--ReWrite] - Import Manifest from package
#                       --Md5                   - Add MD5 checksums to the Manifest File
#                       --Dmf                   - Generate the Device Management Framework
#                                                 combined archive ZIP file.
#                       --DmfVersion=xxxx       - Generate the Device Management Framework
#                                                 combined archive ZIP using a modified
#                                                 version number; only for testing!
#                       --LineLength=nnn        - Limit line length. Default is 79
#                       --SubManifest           - Create a sub-manifest (not installable). Manifest will have no version.
#                                                 Gives a default name, tier and subdir if not explicitly provided.
#                       --[No]Signatures        - Control generation of .sig files
#                                                 Default is to not generate .sigFiles
#                       --ManifestInsertionPoint[,--All,--Name,--Name=xxxx,--Tier,--Tier=Name,--Current]
#                                                 Create template insertion hook
#                       --Template=Package[,--Subdir=subdir,--File=name]
#                                                 Use Specified template                                                                           
#                       --NoWarn                - Supress warnings about unused packages
#                                                 Will supress sanity test. Useful when generating multiple
#                                                 manifests.                          
#                       --UseFullVersion        - Use the full version (with the project extention) when building the manifest file
#                                                 Will only modify the content of the manifest file
#                                                 
# Notes: On sig file generation
#   This tool will use an internal key file to generate signatures
#   UNLESS the tool is being run on a special build machine with a controlled signature file
#   the generated signatures will not work on-target.
#   
#
# Returns         : Nothing
#
sub ManifestFiles
{
    my( $platforms, @elements ) = @_;
    Debug2( "ManifestFiles($platforms, @elements)" );
    return if ( ! ActivePlatform($platforms) );

    my $name;
    my $tier;
    my @files;
    my %fileVersions;
    my $mug_dir;
    my $default_arch = $::ScmPlatform;
    my $default_prod = '';
    my $imported_manifest = 0;
    my $include_md5 = 0;
    my $generate_dmf = 0;
    my $dmf_version = $::ScmBuildVersionFull;
    my $useDefaultLineWidth = 1;
    my $is_sub_manifest = 0;

    #
    #   Collect user options
    #
    foreach ( @elements )
    {
        if ( m~^--Name=(.+)~ ) {
            if ( $name ) {
                ReportError ("ManifestFiles:--Name option is only allowed once");
                next;
            }
            $name = $1;

        } elsif ( m~^--Tier=(.+)~ ) {
            if ( $tier ) {
                ReportError ("ManifestFiles:--Tier option is only allowed once");
                next;
            }
            $tier = $1;

            
        } elsif ( m~^--LocalInstall~ ) {
            $ManifestFiles::localInstall = 1;

        } elsif ( m~^--Comment=~ ) {
            my $cmt = $_;
            $cmt =~ s~.+=~~;
            $cmt =~ s~\s*\n\s*~\n~g;
            push @files, {'cmt' => $cmt };

        } elsif ( m~^--Debian=(.+)~ ) {
            push @files, {'file' => ManifestFiles::_LocateDebianFile($1, $default_arch, $default_prod)};

        } elsif ( m~^--Apk=(.+)~ ) {
            my $apkData = ManifestFiles::_LocateApkFile($1, $default_arch);
            my ($fname, $fversion) = split($;, $apkData);
            $fileVersions{$fname} = $fversion;
            push @files, {'file' => $fname };
            $useDefaultLineWidth = 0;

        } elsif ( m~^--SrcFile=(.+)~ ) {
            push @files, {'file' => LocatePreReq($1)};

        } elsif ( m~^--SrcFileNoCopy=(.+)~ ) {
            push @files, {'filenocopy' => $1};

        } elsif ( m~^--MugPackage=(.+)~ ) {
            if ( $mug_dir ) {
                ReportError ("ManifestFiles:--MugPackage option is only allowed once");
                next;
            }
            $mug_dir = ManifestFiles::_LocateMugDir($1);

        } elsif ( m/^--Arch(.*)=(.+)/ ) {
            $default_arch = $2;

        } elsif ( m/^--Product=(.+)/ ) {
            $default_prod = $1;

        } elsif ( m/^--NoManifestVersion/i ) {
            $ManifestFiles::Manifest_has_version = 0;

        } elsif ( m/^--PkgSubdir=(.+)/i ) {
            if ( $ManifestFiles::pkg_subdir ) {
                ReportError ("ManifestFiles:--PkgSubdir option is only allowed once");
                next;
            }
            $ManifestFiles::pkg_subdir = $1;

        } elsif ( m/^--ImportManifest=(.+)/i ) {
            my $import_info = ManifestFiles::_ImportManifest($1, $tier, $name);
#DebugDumpData("ImportInfo", $import_info );
            push @files, {'manifest' => $import_info };

            #
            #   Fill in details unless already provided
            #
            $tier = $import_info->{'tier'} unless ( defined $tier );
            $name = $import_info->{'name'} unless ( defined $name );
            $imported_manifest = 1;

        } elsif ( m/^--ManifestInsertionPoint(.*)/i ) {
            my $insert_info = ManifestFiles::_InsertPoint($1, $tier, $name);
#DebugDumpData("InsertPoint", $insert_info );
            push @files, {'insertPoint' => $insert_info };

        } elsif ( m/^--Template=(.+)/i ) {
            my $template_info = ManifestFiles::_Template($1, $tier, $name);
#DebugDumpData("TemplateInfo", $template_info );

        } elsif (m/^--Md5/i) {
            $include_md5 = 1;
            $useDefaultLineWidth = 0;

        } elsif (m/^--Dmf/i) {
            $generate_dmf = 1

        } elsif ( m/^--DmfVersion=(.+)/ ) {
            $generate_dmf = 1;
            $dmf_version = $1;

        } elsif ( m/^--LineLength=(\d+)$/i ) {
            $ManifestFiles::ManifestLineWidth = $1;
            $useDefaultLineWidth = 0;

        } elsif (m/^--SubManifest/i) {
            $is_sub_manifest = 1;
            $ManifestFiles::Manifest_has_version = 0;
            $name = $::ScmPlatform unless $name;
            $ManifestFiles::pkg_subdir = $name unless $ManifestFiles::pkg_subdir;
            $tier = 0 unless defined($tier);

        } elsif (m/^--(No)?Signatures/i) {
           $ManifestFiles::noSigs = !! $1;

        } elsif (m/^--(No)?Warn/i) {
           $ManifestFiles::noWarn = !! $1;

        } elsif (m/^--UseFullVersion/i) {
            $ManifestFiles::useFullVersion = 1;

        } else {
            ReportError ("ManifestFiles: Unknown option or argument: $_");

        }
    }

    #
    #   Sanity test the user options
    #
    ReportError ("ManifestFiles: No name specified")
        unless $name;
    ReportError ("ManifestFiles: No tier specified")
        unless defined ($tier);
    ReportError ("ManifestFiles: Cannot mix --Debian/-Apk/--SrcFile with --MugPackage in one directive")
        if ( $mug_dir && (@files || $imported_manifest) );
    ReportError ("ManifestFiles: Must specify files to add to Manifest")
        unless ( $mug_dir ||  @files || $imported_manifest);
    ErrorDoExit();

    #
    #   Set ManifestLineWidth
    #   The default is largely historical - for MOS
    #   
    unless (defined $ManifestFiles::ManifestLineWidth) {
        $ManifestFiles::ManifestLineWidth = $useDefaultLineWidth ? 79 : 0;
    }
    Verbose("ManifestLineWidth:$ManifestFiles::ManifestLineWidth");

    #
    #   Save information for processing at the end of the parsing phase
    #   Data collected from ALL the ManifestFiles directives will be collected
    #   and processed into one Manifest file
    #
    my %data;
    $data{tier} = $tier;
    $data{name} = $name;
    $data{files} = \@files;
    $data{fileVersions} = \%fileVersions;
    $data{mugdir} = $mug_dir;
    $data{pkgsubdir} = $ManifestFiles::pkg_subdir;
    $data{md5} = $include_md5;
    $data{dmf} = $generate_dmf;
    $data{arch} = $default_arch;
    $data{dmf_version} = $dmf_version;
    $data{is_sub_manifest} = $is_sub_manifest;

#DebugDumpData("DirectiveData", \%data );

    push @ManifestFiles::Manifests, \%data;
}

#==================================================================================================
#   End of externally exposed methods
#       The remainder is package-internal
#
package ManifestFiles;

use strict;
use warnings;
use Digest::file qw(digest_file_hex);
use Digest::file qw(digest_file_base64);
use File::Spec::Functions qw(rel2abs);
use File::Basename qw(dirname);

use JatsError;
use FileUtils;
use ReadBuildConfig;
use ArrayHashUtils;
use JatsSystem;

#
#   Variables
#   Config - must be 'our'
#
our @Manifests;                     # Manifest entries
our $Manifest_has_version = 1;      # Create Manifest_xxxx by default
our $pkg_subdir;                    # Alternate packaging
our $ManifestLineWidth;             # Max length of lines
   
our %package_dirs;                  # Package dirs discovered and used
our $ManifestFiles;                 # Files in the Manifest
our $noWarn = 0;                    # Control unused package warnings
our $useFullVersion = 0;            # Use the full version to build the manifest contents
our $pkgBase;                       # Where the package is being sourced from
our $noSigs = 1;                    # Control signature generation (Force noSigs. could use use undef)
our $templateMode;                  # In templating mode
our $localInstall;                  # Install or Package the manifest

#
#   Internal
#   Used while generating a manifest
#
my $certName;                       # Signing Cert Name
my $certFile;                       # Path to the certificate
my $keyFile;                        # Path to the keyfile
my $opensslProg;                    # Path to openssl program
my $opensslConf;                    # Path to openssl config
my $manifestName;                   # Name of the current manifest file

BEGIN {
    my $path = rel2abs( __FILE__ );
    $pkgBase = dirname( $path );
}

#-------------------------------------------------------------------------------
# Bring in the DMF build requirements.
use lib $pkgBase;
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use JSON;

#-------------------------------------------------------------------------------
# Function        : _LocateDebianFile
#
# Description     : Locate a debian file
#                   Internal Function
#
#                   Scan packages for the Debian package specified
#                   The user provides the base name of the package
#                   A Debian Package name has several fields
#                   These are:
#                       1) Base Name - Provided by the user
#                       2) Version - Default Version will be wildcarded if it hadn't been provided by user
#                       3) Architecture - Wildcarded. Uses bin/arch directory
#
#                   Expect to find Debian Packages in the bin/PLATFORM subdir
#
# Inputs          : Debian base name, complete with suboptions
#
# Returns         : Full path of the file
#
sub _LocateDebianFile
{
    my ($arg, $arch, $product) = @_;
    Verbose("LocateDebianFile: Processing: $arg");

    my @type = qw( P D );
    my @debian_file_path;
    my @searchPath;
    my $version='*';

    #
    #   Extract sub-options
    #       --Prod[uction]
    #       --Debug
    #       --Arch[itecture]=yyy
    #       --Product=yyy
    #       --Version=yy.yy.yyyyy  default value is wildcard 
    #
    my ($base_name, @opts) = split( ',', $arg );
    foreach ( @opts )
    {
        if ( m/^--Arch(.*)=(.+)/ ) {
            $arch=$2;
        } elsif ( m/^--Product=(.+)/ ) {
            $product=$1;
        } elsif ( m/^--Prod/ ) {
            @type = 'P';
        } elsif ( m/^--Debug/ ) {
            @type = 'D';
        } elsif ( m/^--Version=(.+)/ ) {
            $version=$1;
        } else {
            Warning ('--Debian: Unknown Option: ' . $_);
        }
    }

    #
    #   Create a list of products
    #   ie: PRODUCT_ARCH
    #
    my @products;
    push @products, $product . '_' . $arch if ( $product );
    push @products, $arch;

    #
    #   Scan all packages for the specified debian package
    #
    foreach my $package_dir ( getPackagePaths ('--All') )
    {
        foreach my $type ( @type )
        {
            foreach my $prd ( @products )
            {
                foreach my $joiner ( qw(/ .) )
                {
                    my $dir = "$package_dir/bin$joiner$prd$type";
                    UniquePush(\@searchPath, $dir);
                    next unless ( -d $dir );
                    my @files = glob ( "$dir/${base_name}_${version}*.deb" );
                    next unless ( @files );
                    push @debian_file_path, @files;
                    $package_dirs{$package_dir}{used} = 1;
                }
            }
        }
        foreach my $type ( @type )
        {
            foreach my $prd ( @products )
            {
                my $dir = "$package_dir";
                UniquePush(\@searchPath, $dir);
                next unless ( -d $dir ); 
                if ( $version eq "*" ){
                    my @files = glob ( "$dir/${base_name}_${version}_${prd}_${type}.deb" );
                    if ( @files ){
                        push @debian_file_path, @files;
                        $package_dirs{$package_dir}{used} = 1;
                    }
                }
                else{
                    if( -e "$dir/${base_name}_${version}_${prd}_${type}.deb" ) {
                        my $file = "$dir/${base_name}_${version}_${prd}_${type}.deb";
                        push(@debian_file_path, $file);
                        $package_dirs{$package_dir}{used} = 1;
                    } elsif ( -e "$dir/${base_name}_${version}_${prd}.deb") {
                        my $file = "$dir/${base_name}_${version}_${prd}.deb";
                        push(@debian_file_path, $file);
                        $package_dirs{$package_dir}{used} = 1;
                    }
                }
            }
        }
    }

    #
    #   Keep user informed
    #   Report errors and provide useful information
    #
    if (IsVerbose(1) || IsDebug(1) || $#debian_file_path != 0)
    {
        Message ("Search for ($base_name). In search Path", @searchPath);
    }
    
    ReportError ("Required Debian package not found: $base_name") unless @debian_file_path;
    ReportError ("Multiple matching Debian Packages located: $base_name", @debian_file_path ) if ( $#debian_file_path > 0 );
    return $debian_file_path[0];
}

#-------------------------------------------------------------------------------
# Function        : _LocateApkFile
#
# Description     : Locate a APK file
#                   Internal Function
#
#                   Scan packages for the APK package specified
#                   The user provides the base name of the package
#                   APK ( Android Packages )
#                   Expect to have a '-release' or '-debug' suffix, except those provided via a
#                   3rd party SDK.
#                   Expect to find APK Packages in the bin/PLATFORM(P/D) subdir
#                   Expect to find -debug in the <PLATFORM>D directory
#                   Expect to find -release' in the <PLATFORM>P directory
#                   
#                   Allow for:
#                       Full path to .apk file
#                       .apk in package root directory
#
# Inputs          : Apk base name, complete with suboptions
#
# Returns         : Full path of the file $; PackageVersion
#                   apk packages do not appear to have version numbers in the file name
#                   Retain package version number for later processing
#
sub _LocateApkFile
{
    my ($arg, $arch) = @_;
    Verbose("LocateApkFile: Processing: $arg");

    my @type = qw( P );
    my @apk_file_path;
    my %type = ('P' => '-release', 'D' => '-debug' );
    my @searchPath;

    #
    #   Extract sub-options
    #       --Prod[uction]
    #       --Debug
    #       --Architecture=yyy
    #
    my ($base_name, @opts) = split( ',', $arg );
    foreach ( @opts )
    {
        if ( m/^--Arch(.*)=(.+)/ ) {
            $arch=$2;
        } elsif ( m/^--Prod/ ) {
            @type = 'P';
        } elsif ( m/^--Debug/ ) {
            @type = 'D';
        } else {
            Warning ('--Apk: Unknown Option: ' . $_);
        }
    }

    #
    #   Scan all packages for the specified APK package
    #   Try:
    #       Raw name - for apks from the SDK or 3rd parties
    #       PLATFORM(P|D)/baseName-(release|debug) - Expected
    #       baseName-(release|debug) - Repackaged badly
    # 
    foreach my $pkgEntry ( getPackageList() )
    {
        next if ($pkgEntry->getType() eq 'interface');
        my $pkgVersion = $pkgEntry->getVersion();
        
        my $pkgLocal = $pkgEntry->getBase(2);
        my $pkgRoot = $pkgEntry->getDir();

        #
        #   Helper function
        #   Uses closure
        #   Notes: Test the package in dpkg_archive so that we can retain the package-version
        #          Use the version in the interface directory if BuildPkgArchive
        #   $pkgLocal - Local base of the package. May in the interface directory
        #   $pkgRoot  - Directory in dpkg_achive. Will have version info
        #   $subdir   - subdir within the package
        #   $fname    - File to look for
        #   
        #   Returns: Nothing
        #   Maintains: apk_file_path. Tupple: filename, PackageVersion
        #
        my $testOneFile = sub {
            my ( $subdir, $fname) = @_;
            my $testFile = "$pkgRoot/$subdir";
            $testFile =~ s~//~/~g;
            $testFile =~ s~/$~~;
            UniquePush(\@searchPath, $testFile);
            return unless (-d $testFile);

            $testFile .= '/' . $fname;
            if (-f $testFile ) {
                if ($pkgLocal ne $pkgRoot) {
                    my $testFile2 = "$pkgLocal/$subdir/$fname";
                    $testFile2 =~ s~//~/~g;
                    if ( -f $testFile2 ) {
                        $testFile = $testFile2;
                    }
                }

             $testFile = join($;, $testFile, $pkgVersion);
             push @apk_file_path, $testFile;
            }
        };

        #
        #   Test for the specified file in the package root
        #
        $testOneFile->("", "${base_name}.apk");

        #
        #   Test for BIN/PLATFORM
        #   
        foreach my $type ( @type )
        {
            my $typeSuffix = $type{$type};
            foreach my $joiner ( qw(/ .) ) {
                $testOneFile->("bin$joiner$arch$type","${base_name}${typeSuffix}.apk");
            }
        }

        foreach my $type ( @type )
        {
            my $typeSuffix = $type{$type};
            $testOneFile->("","${base_name}${typeSuffix}.apk");
        }
        $package_dirs{$pkgRoot}{used} = 1 if (@apk_file_path) ;
    }

    #
    #   Keep user informed
    #   Report errors and provide useful information
    #
    if (IsVerbose(1) || IsDebug(1) || $#apk_file_path != 0)
    {
        Message ("Search for ($base_name). In search Path", @searchPath);
    }

    ReportError ("Required APK package not found: $base_name") unless @apk_file_path;
    ReportError ("Multiple matching APK Packages located: $base_name", @apk_file_path ) if ( $#apk_file_path > 0 );

#DebugDumpData("apk_file_path", \@apk_file_path);
    return $apk_file_path[0];
}

#-------------------------------------------------------------------------------
# Function        : _LocateMugDir
#
# Description     : Locate the directory containing the mugfiles
#                   Internal Function
#
# Inputs          : Mufile package, with embedded options
#
# Returns         : Full path
#
sub _LocateMugDir
{
    my ($mug_package) = @_;

    #
    #   Locate the mugfile subdir
    #
    my $package_name = $mug_package;
    my @dirs = 'mug';
    my $mug_dir;

    #
    #   Extract sub options
    #       --Subdir=xxxx,yyyy,zzzz
    #
    if ( $package_name =~ m/(.*?),--Subdir=(.*)/ )
    {
        $package_name = $1;
        @dirs = split( ',', $2 );
    }

    my $package = ::GetPackageEntry( $package_name );
    unless ( $package )
    {
        ReportError ("ManifestFiles: Package not known to build: $package_name");
        return undef;
    }

    foreach my $subdir ( @dirs )
    {
        my $dir = "$package->{'ROOT'}/$subdir";
        if ( -d $dir )
        {
            Warning ("Multiple Mugfile directories located. Only the first will be used",
                     "Ignoring: $subdir" )if ( $mug_dir );
            $mug_dir = $dir;
        }
    }
    ReportError ("Mugfile directory not found in package: $package_name")
        unless $mug_dir;

    return $mug_dir;
}


#-------------------------------------------------------------------------------
# Function        : _ImportManifest
#
# Description     : Import an existing manifest
#
# Inputs          : Args    - PackageName[,Subdir=name,--ReWrite]
#                   tier    - May be null
#                   name    - May be null
#
# Returns         : A hash of data to be used later
#
sub _ImportManifest
{
    my ($args, $tier, $name) = @_;
    my @file_contents;
    my @item_list;

    #
    #   Locate the mugfile subdir
    #
    my $package_name = $args;
    my @dirs = 'mug';
    my $pkg_dir;
    my $pkg_root;
    my $manifest;
    my $first_tier;
    my $first_name;
    my $rewrite;

    #
    #   Extract sub options
    #       --Subdir=xxxx,yyyy,zzzz
    #       --ReWrite
    #
    if ( $package_name =~ m/(.*?)(,.*)/ )
    {
        $package_name = $1;
        my @subargs = split(',--', $2);
        foreach ( @subargs)
        {
            next unless (length($_) > 0);
            if (m~^Subdir=(.*)~i){
                @dirs = split( ',', $1 );

            } elsif (m~^ReWrite~i) {
                $rewrite = 1;

            } else {
                ReportError("ManifestFiles: Unknown suboption to ImportManifest:" . $_);
            }
        }
    }

    my $package = ::GetPackageEntry( $package_name );
    unless ( $package )
    {
        ReportError ("ManifestFiles: Package not known to build: $package_name");
        return undef;
    }

    if (defined ($rewrite) && ( !defined($tier) || !defined($name)))
    {
        ReportError ("ManifestFiles: ImportManifest. --ReWrite cannot be used unless tier and name are specified");
        return undef;
    }

    foreach my $subdir ( @dirs )
    {
        my $dir = "$package->{'ROOT'}/$subdir";
        my $root = $package->{'ROOT'};
        if ( -d $dir )
        {
            Warning ("Multiple Package directories located. Only the first will be used",
                     "Ignoring: $subdir" )if ( $pkg_dir );
            $pkg_dir = $dir;
            $pkg_root = $root;
        }
    }

    unless ($pkg_dir)
    {
        ReportError ("Package directory not found in package: $package_name");
        return undef;
    }

    #
    #   Determine Manifest File name
    #
    foreach my $file ( glob ($pkg_dir . '/Manifest*' ) )
    {
            next unless ( -f $file );
            next if ( $file  =~ m~\.sig~i );
            Warning ("Multiple Manifest Files find. Only the first will be used",
                     "Using: $manifest",
                     "Ignoring: $file" ) if ( $manifest );
            $manifest = $file;
    }

    unless ($manifest)
    {
        ReportError ("ImportManifest. No Manifest found: $package_name");
        return undef;
    }


    #
    #
    #
    open (MF, '<', $manifest ) || Error ("Cannot open the Manifest file: $manifest", $!);
    while ( <MF> )
    {
        #
        #   Clean trailing whitespace ( line-feed and new lines )
        #   Comment out [Version] data
        #
        s~\s+$~~;
        s~(\s*\[Version])~#$1~;

        #
        #   Parse lines and determine files
        #
        next unless ( $_ );
        if (( m~\s*#~ ) || ( m~\s*\[~ )) {
            push @item_list, { 'comment' => $_ };
            next;
        }
        my( $aname, $atier, $afile, @additionnal_info) = split(/\s*\,\s*/, $_);
#            print "---------- $_\n";
#            print "T: $atier, N:$aname, F:$afile\n";
        my $file =  { 'file_name' => $afile
                    , 'file_info' => \@additionnal_info
                    };
        push @item_list, $file;

        #
        #   Rewrite the name and tier
        #
        if ($rewrite)
        {
            $_ = join(',', $name, $tier, $afile);
            $first_tier = $tier;
            $first_name = $name;
        }
        else
        {
            #
            #   Capture first tier and name
            #
            $first_tier = $atier unless ( defined $first_tier );
            $first_name = $aname unless ( defined $first_name );
        }
    }
    continue
    {
        push @file_contents, $_;
    }
    close MF;

    #
    #   Create a hash of data that describes the manifest that has
    #   just been read in.
    #
    $package_dirs{$pkg_root}{used} = 1;
    $manifest =~ s~.*/~~;
    return { 'contents' => \@file_contents,
              'items' => \@item_list,
              'file_base' => $pkg_dir,
              'manifest' => $manifest,
              'pkg_dir' => $pkg_root,
              'tier' => $first_tier,
              'name' => $first_name,
              'rewrite' => $rewrite,
            };
}

#-------------------------------------------------------------------------------
# Function        : _InsertPoint
#
# Description     : Create a Manifest Insertion Point
#
# Inputs          : Args    - [,--All,--Name,--Name=xxxx,--Tier,--Tier=Name, --Current]
#                   tier    - May be null
#                   name    - May be null
#
# Returns         : A hash of data to be used later
#
sub _InsertPoint
{
    my ($args, $tier, $name) = @_;
    my @item_list;

    ReportError("ManifestFiles: ManifestInsertionPoint. Name not yet specified") unless defined $name;
    ReportError("ManifestFiles: ManifestInsertionPoint. Tier not yet specified") unless defined $tier;

    my $argName = $name;
    my $argTier = $tier;

    #
    #   Extract sub options
    #
    my @subargs = split(',--', $args);
    foreach ( @subargs)
    {
        next unless (length($_) > 0);
        if (m~^All$~i){
            $argName = '*';
            $argTier = '*';

        } elsif (m~^Name(=(.*))?~i){
            if ( $1 ){
                $argName = $2;
            } else {
                $argName = $name;
            }

        } elsif (m~^Tier(=(.*))?~i){
            if ( $1 ){
                $argTier = $2;
            } else {
                $argTier = $tier;
            }

        } elsif (m~^Current$~i){
            $argName = $name;
            $argTier = $tier;

        } else {
            ReportError("ManifestFiles: Unknown suboption to ManifestInsertionPoint:" . $_);
        }
    }

    return { 'tier' => $argTier,
             'name' => $argName,
           };
}

#-------------------------------------------------------------------------------
# Function        : _Template
#
# Description     : Capture templating information
#
# Inputs          : --Template=Package[,--Subdir=subdir,File=name] 
#                   tier    - May be null
#                   name    - May be null
#
# Returns         : A hash of data to be used later
#
sub _Template
{
    my ($args, $tier, $name) = @_;
    my $subdir = 'mug';
    my $manifest;

    #
    #   Can only use the Template directive once
    #   
    if ($templateMode) {
        ReportError ("ManifestFiles:--Template option is only allowed once");
        return undef;
    }
    $templateMode = 1;


    #
    #   Locate the mugfile subdir
    #
    my $package_name = $args;

    #
    #   Extract sub options
    #       --Subdir=xxxx
    #       --File=name
    #       
    if ( $package_name =~ m/(.*?)(,.*)/ )
    {
        $package_name = $1;
        my @subargs = split(',--', $2);
        foreach ( @subargs)
        {
            next unless (length($_) > 0);
            if (m~^Subdir=(.*)~i){
                $subdir = $1;

            } elsif (m~^File=(.*)~i) {
                $manifest = $1;

            } else {
                ReportError("ManifestFiles: Unknown suboption to Template:" . $_);
            }
        }
    }

    my $package = ::GetPackageEntry( $package_name );
    unless ( $package )
    {
        ReportError ("ManifestFiles: Package not known to build: $package_name");
        return undef;
    }

    my $pkg_root = $package->{'ROOT'};
    my $pkgDir = CatPaths( $pkg_root, $subdir);
    unless (-d $pkgDir) {
        ReportError ("ManifestFiles: Template subdir not found in $package_name: $subdir");
        return undef;
    }

    #
    #   Determine Manifest File name, unless specified by user
    #
    unless ($manifest) {
        foreach my $file ( glob ($pkgDir . '/Manifest*' ) )
        {
                next unless ( -f $file );
                next if ( $file  =~ m~\.sig~i );
                if ( $manifest ) {
                    Warning ("Multiple Manifest Files find. Only the first will be used",
                             "Using: $manifest",
                             "Ignoring: $file" ) ;
                    next;
                }
                $manifest = $file;
        }
        unless ($manifest)
        {
            ReportError ("ImportManifest. No Manifest template found: $package_name");
            return undef;
        }
    } else { 
        $manifest = CatPaths($pkgDir, $manifest);
    }

    unless (-f $manifest) {
        ReportError ("ImportManifest. No Manifest template found: $package_name, $subdir, $manifest");
        return undef;
    }

    #
    #   Return paramters for later processing
    #

    $package_dirs{$pkg_root}{used} = 1;
    $templateMode = { 'manifest' => $manifest,
             'pkg_dir' => $pkg_root,
             'subdir' => $subdir,
             'manifest_dir' => $pkgDir,
             'package' => $package_name,
    };
    return $templateMode; 
}

#-------------------------------------------------------------------------------
# Function        : Generate
#
# Description     : Internal Function
#                   Process all the collected data and create directives
#                   for the creation of the manifest
#
#                   This function will be called, just before the Makefile
#                   is created. The function will:
#                       1) Create the Manifest File
#                       2) Package the Manifest File
#                       3) Package the manifest file contents
#
#                   using (mostly) normal makefile.pl directives.
#
# Inputs          : None
#
# Returns         : Nothing
#
sub Generate
{
    Debug ("Generate");
    Message ("Generating Manifest File");

    #
    #   Need at least one Manifest Entry
    #
#DebugDumpData ( "Manifests", \@Manifests );
    return unless ( @Manifests );

    #
    #   Init the signing subsystem
    # 
    GenSigInit();

    #
    #   Determine the target packaging directory
    #   Default is .../mug
    #
    $pkg_subdir = 'mug';
    if ( exists $Manifests[0]->{pkgsubdir} && defined $Manifests[0]->{pkgsubdir} )
    {
        my $subdir = $Manifests[0]->{pkgsubdir};
        $pkg_subdir .= '/' . $subdir;
        $pkg_subdir =~ s~^mug/mug~mug~;
    }

    #
    #   Create the Manifest File as we process the lists
    #   Place this in the 'lib' directory:
    #       - So that it will be deleted on clobber
    #       - So that it can be placed in a target-specific subdir
    #       - So that we can have one per makefile.pl
    #
    Error ("ManifestFiles: Needs local directory specified in build.pl") unless ( $::ScmLocal );

    my $manifest_dir = "$::ScmPlatform.LIB";
    System( "$::GBE_BIN/mkdir -p $manifest_dir" );

    $manifestName = $manifest_dir . '/Manifest';
    $manifestName .= '_' . $::ScmBuildVersion if ( $Manifest_has_version );
    PackageFile ('*', $manifestName, '--Subdir=' . $pkg_subdir, '--Strip' );
    ::ToolsetGenerate( $manifestName );

    Verbose ("Generate: File: $manifestName");

    open (MF, '>', $manifestName ) || Error ("Cannot create the Manifest file: $manifestName");
    binmode (MF);
    my $version;
    if ($useFullVersion == 1) {
        $version = $::ScmBuildVersionFull;
    } else {
        $version = $::ScmBuildVersion;
    }
    
    if ($Manifests[0]->{is_sub_manifest} == 1) {
        print_mf ("# Package $::ScmBuildPackage $version built: $::CurrentTime");
    } else {
        print_mf ("# PackageName: $::ScmBuildPackage");
        print_mf ("# PackageVersion: $version");
        print_mf ("# BuildDate: $::CurrentTime");
        print_mf ("#");
        print_mf ("[Version],$version");
        print_mf ("#");
    }

    #
    #   Insert the certificate used to verify the files
    #       Currently we only handle one certificate
    #       Certificate name must end in .crt
    #   In production the .crt file MUST be signed by the VixManifestRoot-CA
    #   The assumtion is that the public key for VixManifestRoot-CA is on the device
    #
    unless (($Manifests[0]->{is_sub_manifest} == 1) || $noSigs) {
        print_mf ("");
        print_mf ("#");
        print_mf ("# Signing Certificate");
        print_mf ("[Certificate],$certName");

        PackageFile ('*', $certFile, '--Subdir=' . $pkg_subdir, '--Strip' );
    }

    #
    #   If in templating mode, then process the base template
    #
    if ($templateMode) {
        templateProcessing();

    } else {

        #
        #   Process each tier in the order presented in the source file
        #
        foreach my $entry ( @Manifests )
        {
            DmfGenerate($entry) if ( $entry->{dmf} );
            processManifestEntry($entry);
        }
    }

    #
    #   Complete the creation of the Manifest File
    #
    print_mf ("# end of $::ScmBuildPackage");
    close MF;
    ErrorDoExit();

    #
    #   Post process files from the manifest
    #   May include signature generation
    GenSignatures();

    #
    #   Sanity test of packages that did not provide a debian file
    #   Just a hint that something may have been missed
    #
    unless ($noWarn) {
        my @not_used_packages;
        foreach my $package_dir ( getPackagePaths ('--All') ) {
            next if ( $package_dir =~ m~/manifest-tool/~ );
            unless ( exists $package_dirs{$package_dir}{used} ) {
                push @not_used_packages, $package_dir;
            }
        }
        if ( @not_used_packages ) {
            Warning ("Packages that did not contribute packages to the manifest:",
                      @not_used_packages );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : processManifestEntry 
#
# Description     : Process a single Manifest entry 
#
# Inputs          : Globals
#
# Returns         : 
#
sub processManifestEntry
{
    my ($entry) = @_;
#DebugDumpData ( "Manifest Entry", $entry );

    my $tier = $entry->{tier};
    my $name = $entry->{name};
    my $include_md5 = $entry->{md5};
    my $last_was_comment = 0;
    my $last_was_insert = 0;

    #
    #   Flag that this entry has been processed
    #
    $entry->{wasProcessed}++;

    #
    #   Insert all the files that have been specified
    #   The user specified order is preserved
    #
    #   Entries may be either a file or a comment
    #   Comments: Merge multiple comments and create blocks
    #
    #
    my @files = @{ $entry->{files} };
    foreach my $fentry ( @files ) {
        if ( my $cmt = $fentry->{'cmt'} ) {
            print_mf ('') unless ( $last_was_comment ) ;
            print_mf ( map (('# ' . $_) , split ("\n", $cmt) ));
            $last_was_comment = 1;
            next;
        }

        print_mf ('#') if ( $last_was_comment);
        $last_was_comment = 0;

        if ( my $ip = $fentry->{'insertPoint'} ) {
           #
           #   Create an insertion point
           #   Looks like a comment for the purposes of backward compatability
           #

           print_mf ('') unless $last_was_insert;
           print_mf ("##INSERT POINT [Name:$ip->{name}] [Tier:$ip->{tier}]");
           $last_was_insert = 1;
           next;
        }
        print_mf ('#') if ( $last_was_insert);
        $last_was_insert = 0;

        if ( my $file = $fentry->{'file'} ) {
            my $base_file = StripDir( $file );
            my @items = ($name, $tier, $base_file);
            if ($include_md5) {
                my $md5 = digest_file_hex($file, 'MD5');
                push @items, "MD5=$md5" ;
            }
            if (exists $entry->{fileVersions} && exists $entry->{fileVersions}{$file} ) {
                push @items, "VERSION=" . $entry->{fileVersions}{$file};
            }
            print_mf (join (',', @items));
            PackageManifestFile ($name, $tier, $file );
        }

        if ( my $file = $fentry->{'filenocopy'} ) {
            print_mf ("$name,$tier,$file");
        }

        if ( my $emf = $fentry->{'manifest'} ) {
            #
            #   Insert the entire manifest
            #   Items are:
            #        contents
            #        items:
            #               file_name + arrays of file_info
            #           or  comment line to copy
            #        file_base
            #        manifest
            #
#DebugDumpData ( "Embedded Manifest Entry", $emf );
            if ($emf->{'rewrite'}) {
                foreach my $item ( @{$emf->{'items'}}) {
                    if (defined($item->{'file_name'}))
                    {
                        my @items = ($name, $tier, $item->{'file_name'});
                        my $md5_added = 0;
                        foreach my $info (@{$item->{'file_info'}}) {
                            push @items, $info;
                            $md5_added = 1 if ($info =~ m~^MD5=~i);
                        }
                        if ($include_md5 && $md5_added == 0) { # add md5 if requested and not already added from submanifest
                            my $md5 = digest_file_hex($emf->{'file_base'} . '/' . $item->{'file_name'}, 'MD5');
                            push @items, "MD5=$md5";
                        }
                        print_mf (join (',', @items));
                        PackageManifestFile ( $name, $tier, $emf->{'file_base'} . '/' . $item->{'file_name'} );
                    }
                    elsif (defined($item->{'comment'})) {
                        print_mf($item->{'comment'});
                    }
                }
                print_mf('#');
            }
            else {
                print_mf ($_) foreach  ( @{$emf->{'contents'}} );
                foreach my $item ( @{$emf->{'items'}}) {
                    if (defined($item->{'file_name'})) {
                        PackageManifestFile ( $name, $tier, $emf->{'file_base'} . '/' . $item->{'file_name'} );
                    }
                }
                print_mf('#');
            }
        }
    }

    #
    #   Expand out the entire MUG directory
    #   All .mug files in the MUG directory will be added to the manifest
    #   The assumption is that the MUG directory has been created by
    #   something that knows what its doing
    #
    if ( my $mugdir = $entry->{mugdir} )
    {
        foreach my $file ( glob ($mugdir . '/*.mug' ) )
        {
            next unless ( -f $file );
            my $base_file = StripDir($file);

            my @items = ($name, $tier, $base_file);

            if ($include_md5) {
                my $md5 = digest_file_hex($file, 'MD5');
                push @items, "MD5=$md5" ;
            }
            print_mf (join (',', @items));
            PackageManifestFile ($name, $tier, $file );
        }
    }
}


#-------------------------------------------------------------------------------
# Function        : templateProcessing 
#
# Description     : The manifest is created from a povided manifest with MUST
#                   have one or more insertion points
#                   
#                   Purpose: Pulse can provide a template and SI teams can
#                            insert there own packages 
#
# Inputs          : Globals
#
# Returns         : 
#
sub templateProcessing
{
    my $insertFound;
    my $last_was_comment;

    #
    #   Open the template manifest and process each line
    #
    print_mf( '# Start of Template from: ' . $templateMode->{'package'} );
    my $tfile;
    my $line;
    open( $tfile, '<', $templateMode->{'manifest'} ) || Error ("Cound not open template manifest: $?", "File: $templateMode->{'manifest'}");
    while ( <$tfile> ) {
        $line = $_;
        $line =~ s~\s+$~~;
        next unless ($line);

        #
        #   Detect insertion point
        #
        if ($line =~ m~^##INSERT POINT~) {
            $line =~ m~\[Name:(.+)\].*\[Tier:(.+)\]~i;
            my $iname = $1;
            my $itier = $2;
            print_mf ("# Start of Template Insert: $iname, $itier");
            templateInsert($iname,$itier);
            print_mf ("# End of Template Insert: $iname, $itier");
            $insertFound = 1;
            next;
        
        } elsif( $line =~ m~^#$~) {
            # Ignore 'empty' comment lines
            # They will be regenerated
            next;

        } elsif( $line =~ m~^#~) {
            # Non empty comments are transferred
            #   Prepend another '#' marker as some moron decided that they could parse comments for data
            #   A double # should defeat that
            #   
            print_mf ('') unless ( $last_was_comment ) ;
            print_mf( '#' . $line);
            $last_was_comment = 1;
            next;

        } elsif ($line =~ m~^\[~ ) {
            # Metadata from the template
            # Insert it as a comment
            print_mf( '#' . $line);
            next;
        }
        print_mf ('#') if ( $last_was_comment);
        $last_was_comment = 0;

        my( $aname, $atier, $afile, @additionnal_info) = split(/\s*\,\s*/, $line);
        my $test = CatPaths($templateMode->{manifest_dir}, $afile);
        ReportError("Template processing: File not found with template: $afile", $test) unless -f $test;

        print_mf( $line);
        PackageManifestFile ( $aname, $atier, $test );

    }
    close $tfile;
    print_mf( '# End of Template from: ' . $templateMode->{'package'} );

    #
    #   Sanity Checks
    #       Was an insertion point found
    #       Were all manifest bits used
    #
    ReportError ("No insertion points in template") unless $insertFound;
    
    my @notUsed;
    my @overUsed;

    foreach my $entry ( @Manifests ) {
        if ( !exists $entry->{wasProcessed}) {
            push @notUsed, "Name: $entry->{name}, Tier: $entry->{tier}";
        } elsif ($entry->{wasProcessed} > 1) {
            push @overUsed, "Name: $entry->{name}, Tier: $entry->{tier}";
        }
    }
    Warning ("Template did not used data from: ", @notUsed)  if @notUsed;
    Warning ("Template used data multiple times from: ", @overUsed)  if @overUsed;
    ErrorDoExit();
}

#-------------------------------------------------------------------------------
# Function        : templateInsert 
#
# Description     : Helper routine to insert required bits into the template
#                   Need to scan the manifest entries and determine which need
#                   to be inserted at this point in the template.
#
# Inputs          : $iname  - Insert name
#                   $itier  - Insert Tier
#
# Returns         : Nothing
#
sub templateInsert
{
    my ($iname, $itier) = @_;
    foreach my $entry ( @Manifests ) {
        next unless ((($iname eq '*') || ($iname eq $entry->{name}) ) && (($itier eq '*') || ($itier eq $entry->{tier})) );
        processManifestEntry($entry);
    }
}

#-------------------------------------------------------------------------------
# Function        : print_mf
#
# Description     : Internal Function
#                   Print one line to the Manifest File
#                   Checks the length of the line being created
#
# Inputs          : $line
#
# Returns         :
#

sub print_mf
{
    foreach  ( @_ )
    {
        my $ll = length ($_);
        ReportError ( "Manifest line too long: $ll. Max is $ManifestLineWidth.",
                "Line: $_" ) if ( $ManifestLineWidth && $ll > $ManifestLineWidth);
        print MF $_ . "\n";
    }
}

#-------------------------------------------------------------------------------
# Function        : PackageManifestFile  
#
# Description     : Process and package a manifest file
#                       Retain a list of files in the manifest to allow postprocessing
#                       such as signature generation
#                   
#
# Inputs          : $name           - Target platform name
#                   $tier           - Target tier
#                   $srcFile        - Full path to the source file
#
# Returns         : Nothing
#
sub PackageManifestFile {
    my ($name, $tier, $srcFile ) = @_;
    push @{$ManifestFiles}, ({srcFile => $srcFile, name =>$name, tier => $tier});

    PackageFile ('*', $srcFile, '--Subdir=' . $pkg_subdir, '--Strip' );
}

#-------------------------------------------------------------------------------
# Function        : GenSigInit 
#
# Description     : Initialise the Siganture Generation process
#                   Populate some globals for use in creating the manifest
#                   Bail if there are errors
#
# Inputs          : 
#
# Returns         : 
#
sub GenSigInit
{
    Debug("GenSigInit:", $noSigs);
    return if $noSigs;

    my @warnings;

    #
    #   Locate and setup openssl
    #   Should be provided by a package
    #   set OPENSSL_CONF=...../openssl.cfg 
    #   
    $opensslProg = ::ToolExtensionProgram( 'openssl', '', '.exe');

    #   Generate  a dummy openssl.cnf - to keep the uitility happy (quiet)
    $opensslConf = FullPath(CatPaths( $::ScmRoot, $::ScmInterface, 'openssl.cfg'));
    TouchFile($opensslConf);
    ::ToolsetGenerate($opensslConf);
    $ENV{OPENSSL_CONF}= $opensslConf;

    #
    #   Figure out default operation if openssl cannot be found
    #       If --signature - then report error
    #       If not specified, then default is -NoSig
    #
    unless ($opensslProg) {
        if (defined $noSigs) {
            ReportError ("The openssl utility is not available. Must be provided by a package") unless $opensslProg;  
        } else {
            Verbose("openssl not found in a package. Default to no signature");
            $noSigs = 1;
            return;
        }
    }

    #
    #   Signatures 'should' be generated for a target paltform of 'MANSIG'
    #       In the automated build system this will be a controlled machine
    #   Warn if attemting to create signatures for a different target    
    #
    push @warnings, "# Package build for platform other than MANSIG - will generate test signatures" if ($::ScmPlatform ne 'MANSIG');


    #
    #   Locate the signature file
    #   Use an internal key that unless being run on a controlled build machine
    #   The controlled build machine will have
    #       GBE_MANIFEST_KEY set to a path that contains the keyfile
    #           Yes, a user can fudge this, but not with the real file
    #          
    #  Internal cert created with:
    #  openssl req -nodes -x509 -sha256 -newkey rsa:4096 -keyout VixPulseManifestTest.key -out VixPulseManifestTest.crt -days 10000 -subj "/C=AU/CN=Vix.Pulse.Manifest.Test"
    #
    $keyFile = CatPaths( $pkgBase, 'VixPulseManifestTest.key');
    my $localKey = 1;
    if (exists $ENV{GBE_MANIFEST_KEY} ) {
        $keyFile = $ENV{GBE_MANIFEST_KEY};
        $localKey = 0;
    }
    $certFile = $keyFile;
    $certFile =~ s~\.key$~.crt~;
    $certName = $certFile;
    $certName =~ s~.*[\\/]~~;

    ReportError ("Manifest signing key not found", $keyFile) unless -f $keyFile;
    ReportError ("Manifest signing key not found", $certFile) unless -f $certFile;

    push @warnings, "#  Using uncontrolled test key to sign the manifest" if ($localKey);
    Warning("#############################################################", "#",
             @warnings,
            "#", "#############################################################") if (@warnings);

    ErrorDoExit();
    Message("OpenSsl Prog: $opensslProg");
    Message("OpenSsl Conf: $opensslConf");
    Message("Signing with: $keyFile");

}

#-------------------------------------------------------------------------------
# Function        : GenSignatures 
#
# Description     : Generate signatures on all packages
#
# Inputs          : Used the array of files in $ManifestFiles 
#
# Returns         : Nothing 
#
sub GenSignatures
{
    Debug("GenSignatures:", $noSigs);
    return if $noSigs;

    my %sigManifest;

    foreach my $entry ( @{$ManifestFiles}) {
        Verbose("PostProcess: $entry->{srcFile}");

        #
        #   Generate the name of the signature file
        #   Generate Packaging directives for the signature
        #   
        my $sigFileName = CatPaths( "$::ScmPlatform.LIB", StripDir($entry->{srcFile}) . '.sig' );
        PackageFile ('*', $sigFileName, '--Subdir=' . $pkg_subdir, '--Strip' );
        ::ToolsetGenerate($sigFileName);

        #
        #   Generate the name/tier for the manifest signature
        #       ie: Manifest_<name>_<tier>.sig
        #   Generate the name for the manifest signature
        #       ie: Manifest_<name>.sig
        #   Will generate signature of signatures
        #
        my $sigManifest =  join('_', $entry->{name}, $entry->{tier});
        push @{$sigManifest{$sigManifest}}, $sigFileName;
        push @{$sigManifest{$entry->{name}}}, $sigFileName;


        # Generate the signature over the file
        genSigForFile($entry->{srcFile}, $sigFileName);
    }

    #
    #   Create a file of signatures
    #   Create one for each platform/tier
    #
    foreach my $nameTier ( keys %sigManifest)
    {
        Verbose("PostProcessSig: $nameTier");
        my $sigSigTemp = $manifestName . '_' . $nameTier . '.sig';
        my $sigSigTempCat = $sigSigTemp . '.content';
        ::ToolsetGenerate($sigSigTemp, $sigSigTempCat);
        PackageFile ('*', $sigSigTemp, '--Subdir=' . $pkg_subdir, '--Strip' );

        #
        #   Concatenate the contents of the required .sig files
        #   Treat them as binary files. 
        #
        open  (my $ofh, '>', $sigSigTempCat ) || Error( "Cannot create file: $sigSigTempCat", "Reason: $!" );
        binmode ($ofh);
        foreach my $entry (@{$sigManifest{$nameTier}}) {
            Verbose("PostProcessSig: $entry");
            open( my $file, '<', $entry ) || Error("Cannot open '$entry'. $!");
            binmode ($file);
            my @lines = <$file>;
            close $file;

            foreach my $entry ( @lines ) {
                print $ofh $entry;
            }
        }
        close $ofh;

        #
        #   Generate a signature over the file
        #
        genSigForFile($sigSigTempCat, $sigSigTemp);
        #unlink $sigSigTempCat;
    }
    
    unlink $opensslConf;
}

#-------------------------------------------------------------------------------
# Function        : genSigForFile 
#
# Description     : Generate a .sig file for a named file (internal)
#                   Will be smart and only 'touch' the output file if the input has chnaged
#                       It will maintain a fingerprint file to do this.
#                   
#                   Generate package signature
#                   The required custom(vix) 'sig' file has the format:
#                       line-1 : name of the certificate to decode
#                                Assume its the keyname with .key -> .crt - with no path elements
#                       line-2 : Text representation of the signature (base64)
#                                The openssl dgst command generates a binary output by default
#                                This is then converted into base64 with -A option
#                   
# Inputs          : $srcFile        - Source file to process
#                   $tgtFile        - Output file name
#
# Returns         : 
#
sub genSigForFile
{
    my ($srcFile, $tgtFile) = @_;

    my $tmp1 = CatPaths( "$::ScmPlatform.LIB", 'temp1');
    my $tmp2 = CatPaths( "$::ScmPlatform.LIB", 'temp2');
    ::ToolsetGenerate($tmp1, $tmp2);

    Verbose("Signing: " . StripDir($srcFile));
    System( '--Exit', '--NoShell', $opensslProg, 'dgst', '-sha256', '-sign', $keyFile, '-out', $tmp1, $srcFile );
    System( '--Exit', '--NoShell', $opensslProg, 'base64', '-A', '-in', $tmp1, '-out', $tmp2 );
    my $base64 = TagFileRead($tmp2);

    #
    #   In a development environment we don't want to create the signature file every time we run. Its noisy. 
    #   Solution: Only generate a signature file if:
    #               It doesn't exist
    #               A copy of the signed digest doesn't exist (.fpp)
    #               The signed digest has changed
    #   
    my $fppFileName = CatPaths( "$::ScmPlatform.LIB", StripDir($srcFile) . '.fpp' );
    ::ToolsetGenerate($fppFileName);
    my $genSig = 0;
    $genSig = 1 unless ( -e $tgtFile &&  -e $fppFileName);
    unless ($genSig) {
        my $ffp = TagFileRead($fppFileName);
        $genSig = ($ffp ne $base64);
    }

    if ($genSig) {
        FileCreate($tgtFile, $certName, $base64);
        rename $tmp2, $fppFileName ;
    }

    unlink $tmp1;
    unlink $tmp2;
}

#-------------------------------------------------------------------------------
# Function        : DmfGenerate
#
# Description     : Import an existing manifest
#
# Inputs          : entry   - The manifest that is being processed.
#
# Returns         : Nothing
#
sub DmfGenerate
{
    my ($entry) = @_;

    # Get the generation time.
    my $gen_time = time();

    my $work_dir = "$::ScmPlatform.BIN/";
    System( "$::GBE_BIN/mkdir -p $work_dir" );

    my $name = $entry->{name};
    my $version = $entry->{dmf_version};

    # Configure base manifest information.
    my %manifest;
    $manifest{'mugsetId'} = $name . '_' . $version;
    $manifest{'name'} = $name;
    $manifest{'version'} = $version;
    $manifest{'packageName'} = $::ScmBuildPackage;
    $manifest{'packageVersion'} = $::ScmBuildVersionFull;
    $manifest{'datetime'} = localtime($gen_time);
    $gen_time *= 1000;  # Make to milliseconds
    $manifest{'timestamp'} = $gen_time;
    $manifest{'tier'} = $entry->{tier};

    # Process each file.
    my @files = @{ $entry->{files} };
    my $zip = Archive::Zip->new();
    my $i = 0;
    foreach my $fentry ( @files )
    {
        if ( my $file = $fentry->{'file'} )
        {
            my $order = $i + 1;
            my $base_file = StripDir( $file );
            my $publish_file = $name . '_' . $version . '_' . $order . '.aup';
            my $aup_file = $work_dir . $publish_file;

            GenerateCesFile($file, $aup_file, 0x3, $gen_time, $publish_file);

            my $file_member = $zip->addFile( $aup_file, $publish_file );

            $manifest{'tasks'}[$i]{'order'} = 1 * $order;
            $manifest{'tasks'}[$i]{'filename'} = $base_file;
            $manifest{'tasks'}[$i]{'download'} = $publish_file;
            $manifest{'tasks'}[$i]{'sha256'} = digest_file_base64($file, 'SHA-256');
            $manifest{'tasks'}[$i]{'size'} = -s $file;

            if ($base_file =~ /\.sh$/)
            {
                $manifest{'tasks'}[$i]{'action'} = 'exec-shell';
            }
            elsif ($base_file =~ /\.deb$/)
            {
                $manifest{'tasks'}[$i]{'action'} = 'dpkg-install';

                my ($pkg_name, $pkg_version, $pkg_arch) = ($base_file =~ /([^_]*)_([^_]*)_(.*)/);
                $manifest{'tasks'}[$i]{'arch'} = $pkg_arch;
                $manifest{'tasks'}[$i]{'name'} = $pkg_name;
                $manifest{'tasks'}[$i]{'version'} = $pkg_version;
            }
            else
            {
                ReportError ("Manifest entry $base_file does not have a supported DMF install action");
            }

            $i = $i + 1;
        }
    }

    # Encode and commit the JSON.
    my $json_encoder = JSON->new->allow_nonref;
    my $json = $json_encoder->pretty->encode( \%manifest );

    my $manifest_filename = $name . '_' . $version;
    my $aum_filename = $manifest_filename . '_0.aum';
    my $manifest_file = $work_dir . $manifest_filename . '.json';
    my $aum_file = $work_dir . $aum_filename;

    # Save our manifest.
    open (J, '>', $manifest_file ) || Error ("Cannot create the DMF Manifest file");
    binmode (J);
    print J $json;
    close J;

    GenerateCesFile($manifest_file, $aum_file, 0x2, $gen_time, $aum_filename);

    $zip->addFile($aum_file, $aum_filename);

    my $zip_filename = $work_dir . $name . '_' . $version . '.zip';
    if ( $zip->writeToFileNamed($zip_filename) != AZ_OK )
    {
        ReportError("DMF ZIP file creation failed");
    }
    PackageFile('*', $zip_filename, '--Strip');
    PackageFile('*', $manifest_file, '--Strip');

}

#-------------------------------------------------------------------------------
# Function        : PackageFile  
#
# Description     : Package or Install a manifest file
#
# Inputs          : Same as PackageFile
#
# Returns         : 
#
sub PackageFile
{
    if ($localInstall) {
        ::InstallHdr(@_);
    } else {
        ::PackageFile(@_);
    }
}

#-------------------------------------------------------------------------------
# Function        : GenerateCesFile
#
# Description     : Import an existing manifest
#
# Inputs          : src_file     - The input file.
#                   dst_file     - The output CES file.
#                   content_type - The content type to report.
#                   gen_time     - The generation time for the file.
#                   filename     - The filename to embed in the CES file.
#
#
# Returns         : Nothing
#
sub GenerateCesFile
{
    my ($src_file, $dst_file, $content_type, $gen_time, $filename) = @_;

    open (INF, '<', $src_file ) || Error ("Cannot open file $src_file for reading");
    binmode (INF);

    open (OUTF, '>', $dst_file ) || Error ("Cannot open file $dst_file for writing");
    binmode (OUTF);

    my $signing_key_name = "";
    my $signature_size = 0;
    my $format_version = 0xCE500000;
    my $compression_method = 0;
    my $encryption_method = 0;
    my $kek_name = "";
    my $encryption_key_size = 0;
    my $filename_size = length($filename);

    print OUTF pack("Z32", $signing_key_name);
    print OUTF pack("n", $signature_size);
    print OUTF pack("N", $format_version);
    print OUTF pack("N", $content_type);
    print OUTF pack("Q>", $gen_time);
    print OUTF pack("N", $compression_method);
    print OUTF pack("N", $encryption_method);
    print OUTF pack("Z32", $kek_name);
    print OUTF pack("n", $encryption_key_size);
    print OUTF pack("n", $filename_size);
    # Encryption key HERE
    print OUTF pack("A$filename_size", $filename);

    my $buf;
    while (read(INF,$buf,65536))
    {
        print OUTF $buf;
    }
    print OUTF $buf;
    close INF;

    # Signature HERE

    # Finish with file.
    close OUTF;
}

1;