diff -r 7c11c3d8d025 -r 60be34e1b006 deprecated/buildtools/buildsystemtools/GenXml.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/deprecated/buildtools/buildsystemtools/GenXml.pm Wed Oct 27 16:03:51 2010 +0800 @@ -0,0 +1,2133 @@ +# Copyright (c) 2003-2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# + +package GenXml; + + use strict; + + use FindBin; + use lib "$FindBin::Bin/lib"; + use XML::DOM; + use XML::DOM::ValParser; + + # produces the "Use of uninitialized value in concatenation (.) or string" warning + use XML::XQL; + use XML::XQL::DOM; + + # Variable to indicate the version of xml file used. It will be set by subroutine Parse_xml + my $iVer = 1; + + # Used by debug prints + #my $count; + +my ($gHiResTimer) = 0; #Flag - true (1) if HiRes Timer module available +my ($gLogFileH); # Log file handle +my ($gEmbeddedLog) = 0; # Flag false (0) if logging must include scanlog headers etc +my ($gValidateFailed) = 0; # Flag true (1) if the XML validation has failed +my ($gValidate) = 0; # Flag true (1) if to do XML validation only + + #assign STDERR to STDOUT so both are printed in the same file, without overwriting lines + open (STDERR, ">&STDOUT") or die("ERROR: Unable to redirect STDERR to STDOUT: $!"); + select((select(STDOUT), $|=1)[0]); + select((select(STDERR), $|=1)[0]); + + +# Check if HiRes Timer is available +if (eval "require Time::HiRes;") { + $gHiResTimer = 1; +} else { + print "Cannot load HiResTimer Module, install the Perl module Time-HiRes for more accurate timing data\n"; +} + +# Start +# +# Inputs +# $iXMLSource - ref to array of XML filenames, to be merged into one +# $iLogFile - name of logfile +# $iSourceDir - root of the current source tree +# $iEffectiveDir - root of source tree in which output files will be used +# $iValidate - if true, validate the input and then stop +# +# $iFilter - (optional) filter the merged file against this value +# $iMergedXml - (optional) create file of this name containing the merged XML +# $iConfName - name of the configuration: needed by subsequent arguments +# $iDataOutput - (optional) create file of this name containing the XML build commands +# $iTextOutput - (optional) create file of this name containing the list of components +# $iCBROutput - (optional) create file of this name containing the list of MRP files +# +# Description +# This function merges multiple XML files into one document, then optionally outputs various +# files. +# +sub Start +{ + my ($iXmlSource, $iDataOutput, $iLogFile, $iSourceDir, $iConfName, $iMergedXml, $iValidate, $iTextOutput, $iCBROutput, $iFilter, $iEffectiveDir) = @_; + + # Set global validation Flag + $GenXml::gValidate = $iValidate; + + my $doc; + + if ($iLogFile) + { + # Open Log file + $GenXml::gLogFileH = IO::File->new("> $iLogFile") + or die "ERROR: RealTimeBuild: Couldn't open $iLogFile for writing: $!\n"; + $gEmbeddedLog = 0; # Generate scanlog-compatible log format + } else { + $GenXml::gLogFileH = *STDOUT; + $gEmbeddedLog = 1; # Assume that we are embedded in a scanlog-format log file + } + + if (!$gEmbeddedLog) + { + # Logfile headers + print $GenXml::gLogFileH "===-------------------------------------------------\n"; + print $GenXml::gLogFileH "=== Genxml\n"; + print $GenXml::gLogFileH "===-------------------------------------------------\n"; + print $GenXml::gLogFileH "=== Genxml started ".localtime()."\n"; + } + + # $iSourceDir must end in a \ + # Add a \ if not present + # And make sure they are in windows style + if ($iSourceDir !~ /\\$/) + { + $iSourceDir =~ s/\//\\/g; + $iSourceDir .= "\\"; + } + if ($iEffectiveDir !~ /\\$/) + { + $iEffectiveDir =~ s/\//\\/g; + $iEffectiveDir .= "\\"; + } + + # Parse all the files into one DOM doc + $doc = &Parse_files($iXmlSource, \$iVer); + # ... XML::DOM::Document was created sucessfully ... + + # Exit here if validating only + exit if ($GenXml::gValidate); + + # Try normalising it + $doc->normalize; + + # filter it, if desired + if ($iFilter && $iVer == 1) { + &logfileHeader("Filtering model against $iFilter"); + &Filter_doc($doc, $iFilter); + &logfileFooter(); + } elsif ($iFilter && $iVer == 2) { + &logfileHeader("Filtering model against $iFilter"); + &Filter_doc2($doc, $iFilter); + &logfileFooter(); + } + + + # Debug dump new doc to file + #~ $doc->printToFile("$iMergedXml") if ($iMergedXml); + + #################write only non-empty lines################ + if ($iMergedXml) + { + open(HANDLE, "+> $iMergedXml") or die "Error: Can't open $iMergedXml: $!"; + my $MergedXMLString = $doc->toString; + my @lines = split(/\n/,$MergedXMLString); + my @tempLines = (); + foreach (@lines) + { + push @tempLines, $_ if $_ !~ /^[\s]*$/; + } + + $MergedXMLString = join("\n",@tempLines); + seek(HANDLE,0,0); + print HANDLE $MergedXMLString; + truncate(HANDLE,tell(HANDLE)); + close HANDLE; + } + ################################# + if ($iConfName) + { + # Process the configuration to get the lists of units, tasks and options + &logfileHeader("Processing configuration $iConfName"); + my ($topunits, $subunits, $options, $tasks) = &process_configuration($doc,$iConfName, $iVer); + my @topbldList = &compute_bldList($iSourceDir,$iEffectiveDir,$topunits, $iVer); + + &logfileFooter(); + + if ($iTextOutput) + { + &logfileHeader("Generating text output $iTextOutput"); + + # Generate old-style text output + &write_component_list($doc, $iTextOutput, $iConfName, \@topbldList, $options, $tasks, $iEffectiveDir, $iVer); + + &logfileFooter(); + } + + if ($iCBROutput) + { + &logfileHeader("Generating CBR component list $iCBROutput"); + + # Generate list of CBR components for "makecbr" + my @allunits; + #if ($iVer == 1) { + @allunits = (@$topunits, @$subunits); + #} else { + # @allunits = (@$topunits); # No subunits required for the new version of system_definition.xml + #} + my @fullbldList = &compute_bldList($iSourceDir,$iEffectiveDir,\@allunits, $iVer); + + &write_CBR_list($iCBROutput, \@fullbldList); + + &logfileFooter(); + } + + if ($iDataOutput) + { + &logfileHeader("Generating output XML $iDataOutput"); + + # Generate the output document by applying the tasks to the bldList + + my $ID = 1; # Execute Element ID counter + my $Stage = 1; # Execute Element Stage counter + + my ($outDoc, $docElem, $commands) = &start_output_doc($iConfName, $iVer); + + process_prebuilt(\$outDoc, \$commands, \$ID, \$Stage, $topunits, 'N', $iVer); + foreach my $task (@{$tasks}) + { + &process_task($task, $doc, \$outDoc, \$commands, \$ID, \$Stage, \@topbldList, $options, $iSourceDir, $iEffectiveDir, $iVer); + } + process_prebuilt(\$outDoc, \$commands, \$ID, \$Stage, $topunits, 'Y', $iVer); + + $docElem->appendChild($commands); + $docElem->addText("\n"); + #print $outDoc->toString; + $outDoc->printToFile($iDataOutput); + $outDoc->dispose; + + &logfileFooter(); + } + } + + if (!$gEmbeddedLog) + { + # Print Genxml log footer + print $GenXml::gLogFileH "=== Genxml finished ".localtime()."\n"; + } + + # Close file handles + close($GenXml::gLogFileH); + + $doc->dispose; + +} + +# Error Processing function for the XML Validation +# +# Throws an exception (with die) when an error is encountered, this +# will stop the parsing process. +# Don't die if a warning or info message is encountered, just print a message. +sub my_fail +{ + my $code = shift; + + if ($code < 200) + { + print $GenXml::gLogFileH "ERROR: ".XML::Checker::error_string ($code, @_); + # Set Flag so all the errors are reported before dieing + $GenXml::gValidateFailed = 1; + } + + # Useful debug output + print $GenXml::gLogFileH XML::Checker::error_string ($code, @_) if ($GenXml::gValidate); +} + +sub my_Unparsed_handler +{ + my ($Parser, $Entity, $Base, $Sysid, $Pubid, $Notation) = @_; + print $GenXml::gLogFileH "$Entity Unparsed"; + die "ERROR: RealTimeBuild: Processing error\n"; +} + +# Parse_files +# +# Inputs +# $iXMLSource - ref to array of filenames +# $iVersion - Version of xml file (new or old) ? +# +# Outputs +# $doc - XML DOM doc +# +# Description +# This function merges multiple XML files into one document +sub Parse_files +{ + my ($iXmlSource, $iVersion) = @_; # Version info passed for conditional processing of xml files + my (@docs); + + # Load the XML document + my %expat_options = (KeepCDATA => 1, + Handlers => [ Unparsed => \&my_Unparsed_handler ]); + + for (my $i = 0; $i < scalar(@$iXmlSource); $i++) + { + # Create header for parsing each file + &logfileHeader(@$iXmlSource[$i]); + + my $parser = new XML::DOM::ValParser (%expat_options); + XML::DOM::ignoreReadOnly (1); + local $XML::Checker::FAIL = \&my_fail; + + # Useful debug output + #print "Parsing ".@$iXmlSource[$i]."\n"; + + $docs[$i] = $parser->parsefile (@$iXmlSource[$i]); + + # Create footer for parsing each file + &logfileFooter(); + } + + # Check to see if any of the XML files failed validation and die + die "ERROR: RealTimeBuild: Validation failed\n" if ($GenXml::gValidateFailed); + +# Set the appropriate version number + for (my $i = 0; $i < scalar(@docs); $i++) + { if((scalar(@docs))>1) { + if ($docs[$i]->getDocumentElement->getAttribute('schema') =~ /^2\./ && + $docs[$i]->getDocumentElement->getTagName eq "SystemDefinition" && + $docs[1]->getDocumentElement->getTagName eq "SystemBuild") + { + $$iVersion = 2; + last; + } + } + else + { + if ($docs[$i]->getDocumentElement->getAttribute('schema') =~ /^2\./ && + $docs[$i]->getDocumentElement->getTagName eq "SystemDefinition") + { + $$iVersion = 2; + last; + } + } + } + + if ($$iVersion == 1) { # Docs load now merge into $docs[0] if $iVersion is 1 (i.e. old version of xml file) + for (my $i = 1; $i < scalar(@docs); $i++) { + # Create header for merging each file + &logfileHeader("Merging in XML file ".@$iXmlSource[$i]); + &process_node(\($docs[0]->getElementsByTagName("SystemDefinition")),\($docs[$i]->getElementsByTagName("SystemDefinition")), \($docs[0])); + + # Re-validate merged file + local $XML::Checker::FAIL = \&my_fail; + $docs[0]->check(); + + # Create footer for merging each file + &logfileFooter(); + # Check to see if any of the XML files failed validation and die + die "ERROR: RealTimeBuild: Merged Validation failed\n" if ($GenXml::gValidateFailed); + } + } elsif ($$iVersion == 2) { # Docs load now merge into $docs[$#docs + 1] if $iVersion is 2 (i.e. new version of xml file) + for (my $i = 1; $i < scalar(@docs); $i++) { + # Create header for merging each file + &logfileHeader("Merging in XML file ".@$iXmlSource[$i]); + my $mergedDoc = &process_node2(\($docs[0]), \($docs[$i])); + + # Re-validate merged file + local $XML::Checker::FAIL = \&my_fail; + $mergedDoc->check(); + + # Create footer for merging each file + &logfileFooter(); + # Check to see if any of the XML files failed validation and die + die "ERROR: RealTimeBuild: Merged Validation failed\n" if ($GenXml::gValidateFailed); + + $docs[0] = $mergedDoc; + } + } + return $docs[0]; +} + +# process_node +# +# Inputs +# $node1 - ref to a node from the master +# $node2 - ref to a node from the slave +# $doc1 - ref to the doc of node1 so we can set the doc owner to the (not DOM spec) to get around WRONG_DOCUMENT_ERR restriction +# +# Outputs +# +# Description +# This function processes a node in two DOM documents, if any children match then it calls itself to process +# the children nodes further +sub process_node +{ + my ($node1, $node2, $doc1) = @_; + + # Some nodes need special processing e.g. SystemDefinition + # This is because there can only be a certain number of these nodes + # child node / element rules outlined below, this rules are applied to the children of the node in question + # Child Node / element tag Rule + # ------------------------ ---- + # SystemDefinition Merge the name and revision/schema CDATA as there can be only one of this element + # systemModel Always processed further as there can only be 1 or 0 of these + # layer Same name process further otherwise append child + # logicalset Same name process further otherwise append child + # logicalsubset Same name process further otherwise append child + # module Same name process further otherwise append child + # component Same name process further otherwise append child + # unit Same unitID generate ERROR and not replace child, otherwise append child + # sub elements of unit No processing needed as these cannot be merged + # package Same name process further otherwise append child + + # build Always processed further as there can only be 1 or 0 of these + # unitList Same name process further otherwise append child + # unitRef Same unit ignore, different unit append child + # targetList Same name generate ERROR and not replace child, otherwise append child + # target Same name generate ERROR and not replace child, otherwise append child + # option Same name generate ERROR and not replace child, otherwise append child + # configuration Same name generate ERROR and not replace child, otherwise append child + # sub elements of configuration No processing needed as these cannot be merged + + + # All other nodes Append child + + # Useful debug stuff + #$GenXml::count++; + #print "enter $GenXml::count\n"; + + # Handle the special case for the first call to this function with the node containing the SystemDefinition + if (($$node1->getTagName eq "SystemDefinition") && ($$node2->getTagName eq "SystemDefinition")) + { + # Get the name attributes + my ($name1) = $$node1->getAttribute('name'); + my ($name2) = $$node2->getAttribute('name'); + # Combine the two and set the attribute into the merged file + $$node1->setAttribute('name',$name1." + ".$name2); + + # Get the revision attributes + my ($revision1) = $$node1->getAttribute('revision'); + my ($revision2) = $$node2->getAttribute('revision'); + # Get the schema attributes + my ($schema1) = $$node1->getAttribute('schema'); + my ($schema2) = $$node2->getAttribute('schema'); + # If both schema attributes are defined, combine the two and set the attribute into the merged file + # Note that even if an attribute does not exist in the XML file, XML::DOM returns an empty string (not undef) + if (($schema1) and ($schema2)) + { # Both files have "new DTD". + if (($revision1) or ($revision2)) + { + print $GenXml::gLogFileH "ERROR: Cannot define both schema and revison attributes in same file. Merged file will probably not be usable.\n"; + } + if ($schema1 eq $schema2) + { # Both files have same schema attribute. Assign it to merged file + $$node1->setAttribute('schema',$schema1); + } + else + { # Files have different schema attributes. Combine and assign it to merged file. Warn!! + print $GenXml::gLogFileH "WARNING: Source file schema attribute values differ ($schema1 vs $schema2). Merged file may not be usable.\n"; + $$node1->setAttribute('schema',$schema1." + ".$schema2); + } + } + # If both revision attributes are defined, combine the two and set the attribute into the merged file + elsif (($revision1) and ($revision2)) + { # Both files have "old DTD". Retain this code for compatibility + print $GenXml::gLogFileH "REMARK: Both source files have \"old style\" DTDs. See SystemDefinition \"revision\" attribute.\n"; + $$node1->setAttribute('revision',$revision1." + ".$revision2); + } + else + { # Files have different DTDs. Use attribute found in first file. report as ERROR!! + print $GenXml::gLogFileH "ERROR: Source file schema/revison attributes conflict. Merged file will probably not be usable.\n"; + if ($schema1) + { # First file had new DTD and had a schema attribute + $$node1->setAttribute('schema',$schema1); + } + elsif ($revision1) + { # First file had old DTD and had a revision attribute (not a schema) + $$node1->setAttribute('revision',$revision1); + } + } + } + + # Get the children of the parent nodes + + my $nodelist1 = $$node1->getChildNodes; + my $nodelist2 = $$node2->getChildNodes; + + # Useful debug stuff + #print "has ".$nodelist2->getLength." children\n"; + + # Itterate throught the children of node2 check to see if they are present / rule match in node 1 + my $ni = $nodelist2->getLength; + for (my $i = 0; $i < $ni; $i++) + { + # Useful debug stuff + #print "node $i ".$nodelist2->item($i)->getNodeTypeName."\n"; + if ($nodelist2->item($i)->getNodeTypeName eq "ELEMENT_NODE") + { + # Handle rule match on ELEMENTS + my $tagname2 = $nodelist2->item($i)->getTagName; + + # Useful debug stuff + # print "Tagname = $tagname\n"; + if (($tagname2 eq "systemModel") || ($tagname2 eq "build") ) + { + my $iBuildIndx; + # find the $node1 for this elements + my $nj = $nodelist1->getLength; + my $match = 0; + for (my $j = 0; $j < $nj; $j++) + { + if ($nodelist1->item($j)->getNodeTypeName eq "ELEMENT_NODE") + { + my $tagname1 = $nodelist1->item($j)->getTagName; + if ($tagname1 eq $tagname2) + { + # process further + + # Useful debug stuff + #print "processing $tagname further\n"; + &process_node(\($nodelist1->item($j)), \($nodelist2->item($i)), $doc1); + $match = 1; + } + else + { + if ($tagname1 eq 'build') + { + $iBuildIndx = $j; + } + if ((($tagname2 eq 'systemModel') and ($tagname1 ne 'systemModel')) or ((($tagname2 eq 'build') and ($tagname1 ne 'build')))) + { + next; + } + # no systemModel or build element found so append child + &append_child($node1, \($nodelist2->item($i)), $doc1) + } + } + } + unless ($match) + { + # no systemModel or build element found so append child + # In the special case of adding an instance of 'systemModel' we must specify that this goes before any instance of 'build' + my $iRefChildRef = ($tagname2 eq 'systemModel')? $nodelist1->item($iBuildIndx): undef; + &append_child($node1, \($nodelist2->item($i)), $doc1, $iRefChildRef); + } + } elsif (($tagname2 eq "layer") || ($tagname2 eq "logicalset") || ($tagname2 eq "logicalsubset") || ($tagname2 eq "module") || ($tagname2 eq "component") || ($tagname2 eq "package") || ($tagname2 eq "unitList")) + { + # Check the $node1 for elements with the same "name" + my $match; # Flag for matching element found + my $nj = $nodelist1->getLength; + for (my $j = 0; $j < $nj; $j++) + { + # Only look at element nodes in node1 + if ($nodelist1->item($j)->getNodeTypeName eq "ELEMENT_NODE") + { + if ($nodelist2->item($i)->getAttribute('name') eq $nodelist1->item($j)->getAttribute('name')) + { + # Process further match found + $match = 1; + + # Useful debug stuff + #print "processing j=$j $tagname2 further ".$nodelist2->item($i)->getAttribute('name')."\n"; + + &process_node(\($nodelist1->item($j)), \($nodelist2->item($i)), $doc1); + } + } + } + # If no match found Append child + + # Useful debug stuff + #print "new $tagname2 added\n" if (!$match); + + &append_child($node1, \($nodelist2->item($i)), $doc1) if (!$match); + + } elsif (($tagname2 eq "unit") || ($tagname2 eq "targetList") || ($tagname2 eq "target") || ($tagname2 eq "option") || ($tagname2 eq "configuration")) { + # Check the $node1 for elements with the same ID attribute (Global check for ID clashes) + my $idAttrib; + if ($tagname2 eq "unit") + { + # Special case of the unit element as this has uses the attribute of unitID instead of name + $idAttrib = "unitID"; + } else { + $idAttrib = "name"; + } + + my $ID = $nodelist2->item($i)->getAttribute($idAttrib); + # Search for the XML ID in $doc1 + if( scalar(XML::XQL::solve ("//*[\@$idAttrib = '$ID']", $$doc1))) + { + print $GenXml::gLogFileH "REMARK: $ID already exists, not merging this $tagname2 element\n"; + } else { + # unitID not found so append elememnt + + # Useful debug stuff + # print "new $tagname2 added\n"; + + &append_child($node1, \($nodelist2->item($i)), $doc1); + } + } elsif ($tagname2 eq "unitRef") { + # Check the $node1 for elements with the same "name" + my $match; # Flag for matching element found + my $nj = $nodelist1->getLength; + for (my $j = 0; $j < $nj; $j++) + { + # Only look at element nodes in node1 + if ($nodelist1->item($j)->getNodeTypeName eq "ELEMENT_NODE") + { + if ($nodelist2->item($i)->getAttribute('unit') eq $nodelist1->item($j)->getAttribute('unit')) + { + # Ignore the unitRef element as it is a duplicate + $match = 1; + print $GenXml::gLogFileH "WARNING: Duplicate unitRef ".$nodelist2->item($i)->getAttribute('unit')." not merging\n"; + } + } + } + # No match found Append Child + + # Useful debug stuff + # print "New unitRef\n" if (!$match); + + &append_child($node1, \($nodelist2->item($i)), $doc1) if (!$match); + + } else { + # Element not recognised so append child + &append_child($node1, \($nodelist2->item($i)), $doc1); + } + } else { + # Handle non element nodes (append child of node2 to node 1) + # At the moment adding in non element nodes adds a lot of whitespace + # TODO: correctly handle non element nodes + # This is not important at the moment as there are no important non element nodes for the merge + #&append_child($node1, \($nodelist2->item($i)), $doc1); + } + } + + #print "return $GenXml::count\n"; + #$GenXml::count--; +} + +# append_child +# +# Inputs +# $node1 - is already a ref of the node to append to +# $node2 - ref of node from nodelist2 to append to $node1 +# $doc1 - ref to document to merge the node into (need for non DOM operation of changing owner of node) +# $refnode - ref to node in fromt of which to insert node2 (If undef, append node2) +# +# Description +# ??? +sub append_child +{ + my ($node1, $node2, $doc1, $refnode) = @_; + + # Clone the node + my $clone = $$node2->cloneNode(1); + # Fix the owner of node + $clone->setOwnerDocument($$doc1); + # Append a line return for more tidy xml + $$node1->addText("\n"); + # Append (or insert) the node + # Note: it seems that insertBefore($clone,undef) is identical to appendChild($clone) + $$node1->insertBefore($clone,$refnode); +} + +# write_component_list +# +# Inputs +# $doc - Reference to input document +# $iTextOutput - Name of output file +# $iConfName - Name of configuration being described +# $bldList - Reference to the bldList array +# $options - Reference to the options array +# $tasks - Reference to the tasks array +# $iEffectiveDir - Root of source tree in which file will be used +# $iVersion - Version of xml file (new or old) ? +# +# Description: +# Write out old-style "list of components" build description for the configuration +# +sub write_component_list + { + my ($doc, $iTextOutput, $iConfName, $bldList, $options, $tasks, $iEffectiveDir, $iVersion) = @_; + + # process list of tasks to find build targets and bootstrap info + my %targets; + my $bootflag = 0; + + foreach my $task (@$tasks) + { + # Read all the task + my @children = $task->getChildNodes; + foreach my $child (@children) + { + next if ($child->getNodeTypeName ne "ELEMENT_NODE"); + if ($child->getTagName eq "specialInstructions") + { + # "setupprj" in the command is taken to mean "bootstrap E32ToolP" + $bootflag = 1 if ($child->getAttribute("command") =~ /setupprj/i); + next; + } + my $targetlist = $child->getAttributeNode("targetList"); + if (defined $targetlist) + { + my @targetnames = &find_targetList_by_ID($doc, $targetlist->getValue); + foreach my $target (@targetnames) + { + $targets{$target}= 1; + } + } + } + } + + # create output file + open TEXTFILE, "> $iTextOutput" or die "ERROR: RealTimeBuild: Couldn't open $iTextOutput for writing: $!\n"; + + print TEXTFILE < $column2pos) { $column2pos += 8; } + printf TEXTFILE "%-*s\t# use abld %s\n", $column2pos, $name, $option; + } + + $column2pos = 8; + foreach my $target (sort keys %targets) { + # abld targets are only one word + next if ($target =~ /\w+\s+\w+/); + my $name; + if ($target =~ /(misa|mint|mcot|mtemplate|meig)/i) { + $name = "