Rev 7197 | 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/^--UseFullVerion/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 entriesour $Manifest_has_version = 1; # Create Manifest_xxxx by defaultour $pkg_subdir; # Alternate packagingour $ManifestLineWidth; # Max length of linesour %package_dirs; # Package dirs discovered and usedour $ManifestFiles; # Files in the Manifestour $noWarn = 0; # Control unused package warningsour $useFullVersion = 0; # Use the full version to build the manifest contentsour $pkgBase; # Where the package is being sourced fromour $noSigs = 1; # Control signature generation (Force noSigs. could use use undef)our $templateMode; # In templating modeour $localInstall; # Install or Package the manifest## Internal# Used while generating a manifest#my $certName; # Signing Cert Namemy $certFile; # Path to the certificatemy $keyFile; # Path to the keyfilemy $opensslProg; # Path to openssl programmy $opensslConf; # Path to openssl configmy $manifestName; # Name of the current manifest fileBEGIN {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 generationGenSignatures();## 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 submanifestmy $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 regeneratednext;} 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 commentprint_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 filegenSigForFile($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 HEREprint 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;