bldsystemtools/sysdeftools/validate/checklinks.pl
changeset 63 d706e2bc01db
parent 25 85578ba0aa08
--- a/bldsystemtools/sysdeftools/validate/checklinks.pl	Fri Jun 11 14:46:46 2010 +0300
+++ b/bldsystemtools/sysdeftools/validate/checklinks.pl	Wed Jun 23 19:29:20 2010 +0300
@@ -1,4 +1,4 @@
-# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# Copyright (c) 2010 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"
@@ -12,97 +12,524 @@
 #
 # Description:
 # Script to validate the unit links in a system definition or package definition XML file
+#!/usr/bin/perl
 
 use strict;
 
-if (! scalar @ARGV) {&help()}
+
+use FindBin;		# for FindBin::Bin
+use lib $FindBin::Bin;
+use lib "$FindBin::Bin/lib";
+
+use Cwd;
+use Cwd 'abs_path';
+use Getopt::Long;
+use File::Basename;
+use File::Spec;
+use XML::DOM;
+
+my $output;
+my $path;
+my %defineParams;
+my %defines;
+my $defaultns = 'http://www.symbian.org/system-definition';	# needed if no DTD
+my $realloc;
+
+# need to add options for controlling which metas are filtered out and which are included inline
+GetOptions
+	(
+	'path=s' => $path,
+	 'effective-sysdef=s' => \$realloc
+	);
+
+# -path specifies the full system-model path to the file which is being processed. 
+#	This must be an absolute path if you're processing a root sysdef.
+#	If processing a pkgdef file, you can use "./package_definition.xml" to leave all links relative. Though I can't really see the use case for this.
+
+
+# if config is not set, no confguration will be done.
+# If it is set, all configuration metadata will be processed and stripped from the output, even if the confguration data is empty
+
+ if($path eq '') {$path = '/os/deviceplatformrelease/foundation_system/system_model/system_definition.xml'}
+
+($#ARGV == -1 ) && &help();
+my $sysdef = &abspath(shift);	# resolve the location of the root sysdef
+
+$realloc = $realloc || $sysdef;
+
+my %unitmap;
+my @p1=reverse(split(/[\\\/]/,$path));
+my @p2=reverse(split(/[\\\/]/,$realloc));
+
+shift(@p1);shift(@p2); # don't care abt file name
+while(lc($p1[0]) eq lc($p2[0])) {shift(@p1);shift(@p2)}
+
+$unitmap{join('/',reverse(@p1))} = join("/",reverse(@p2));
+
+my @p1=reverse(split(/[\\\/]/,$sysdef));
+my @p2=reverse(split(/[\\\/]/,$realloc));
+
+shift(@p1);shift(@p2); # don't care abt file name
+while(lc($p1[0]) eq lc($p2[0])) {shift(@p1);shift(@p2)}
+
+$unitmap{join('/',reverse(@p1))} = join("/",reverse(@p2));
+
+
+# rootmap is a mapping from the filesystem to the paths in the doc
+my %rootmap = &rootMap($path,$sysdef);	
+my %nsmap;
+my %urimap;
+
+my $parser = new XML::DOM::Parser;
+my   $sysdefdoc = $parser->parsefile ($sysdef);
+
+
+my $maxschema = $sysdefdoc->getDocumentElement()->getAttribute('schema');	# don't check value, just store it.
+
+my $docroot =  $sysdefdoc->getDocumentElement;
+
+my $ns = $docroot->getAttribute('id-namespace');
+if(!$ns && $nsmap{''})
+	{
+	$docroot->setAttribute('id-namespace',$nsmap{''});
+	}
+
+$docroot->setAttribute('schema',$maxschema);	# output has the largest syntax version of all includes
+
+
+while(my($pre,$uri) = each(%nsmap))
+	{
+	$pre ne '' || next ;
+	$docroot->setAttribute("xmlns:$pre",$uri);
+	}
+
+&walk($sysdef,$docroot);	# process the XML
+
+ 
+sub abspath
+	{ 	# normalize the path into an absolute one
+	my  ($name,$path) = fileparse($_[0]);
+	$path=~tr,\\,/,;
+	if( -e $path)
+		{
+		return abs_path($path)."/$name";
+		}
+	my @dir = split('/',$_[0]);
+	my @new;
+	foreach my $d (@dir)
+		{
+		if($d eq '.') {next}
+		if($d eq '..')
+			{
+			pop(@new);
+			next;
+			}
+		push(@new,$d)
+		}
+	return join('/',@new);
+	}
+
+sub rootMap {
+	my @pathdirs = split(/\//,$_[0]);
+	my @rootdirs = split(/\//,$_[1]);
+
+	while(lc($rootdirs[$#rootdirs])  eq lc($pathdirs[$#pathdirs])  )
+		{
+		pop(@rootdirs);
+		pop(@pathdirs);
+		}
+	return (join('/',@rootdirs)  => join('/',@pathdirs) );
+	}
+
+sub rootMapMeta {
+	# find all the explict path mapping from the link-mapping metadata
+	my $node = shift;
+	foreach my $child (@{$node->getChildNodes})
+			{
+			if ($child->getNodeType==1 && $child->getTagName eq 'map-prefix')
+				{
+				my $from = $child->getAttribute('link');
+				my $to = $child->getAttribute('to');		# optional, but blank if not set
+				$rootmap{$from} = $to;
+				}
+			}
+	# once this is processed we have no more need for it. Remove from output
+	$node->getParentNode->removeChild($node);
+	}
 
 
-my $debug = 0;
-my $skipfilter;	# skip anything with a named filter
-my $xslt = "../../../buildtools/bldsystemtools/buildsystemtools/joinsysdef.xsl";
-my $xalan = "../../../buildtools/devlib/devlibhelp/tools/doc_tree/lib/apache/xalan.jar";
-my $sysdef = shift;
-while($sysdef=~/^-/) { #arguments
-	 if($sysdef eq '-nofilter') {$skipfilter = shift}
-	 elsif($sysdef eq '-v') {$debug = 1}
-	 else { &help("Invalid command line option $sysdef")} 
-	 $sysdef = shift; 
-}
-my $dir = $sysdef;
-$dir =~ s,[^\\/]+$,,;
-my $root="../../../..";
- my $full;
- 
-if($sysdef=~/system_definition\.xml/) {	# if running on a sysdef, ensure it's joined before continuing
-	($full = `java -jar $dir$xalan -in $sysdef -xsl $dir$xslt`) || die "bad XML syntax";
-}else {	# assume any other file has no hrefs to include (valid by convention)
-	$root='';
-	open S, $sysdef;
-	$full=join('',<S>);
-	close S;
-}
-$full=~s/<!--.*?-->//sg; # remove all comments;
-my $count=1;
+sub walk
+	{ 	# walk through the doc, resolving all links
+	my $file = shift;
+	my $node = shift;
+	my $type = $node->getNodeType;
+	if($type!=1) {return}
+	my $tag = $node->getTagName;
+	if($tag=~/^(layer|package|collection|component)$/ )
+		{
+		my $link= $node->getAttribute('href');
+		if($link)
+			{
+			my $file = &resolvePath($file,$link); 
+			if(-e $file)
+				{
+				&combineLink($node,$file);
+				}
+			else
+				{
+				print  "Note: $file not found\n";
+				$node->removeAttribute('href');
+				}
+			return;
+			}
+		}
+	elsif($tag=~/^(SystemDefinition|systemModel)$/ )
+		{
+		}
+	elsif($tag eq 'unit')
+		{
+		my %at = &atts($node);
+		my $pro;
+		foreach my $o (keys(%at)) 
+			{
+			if($o eq 'proFile' || $o=~/:proFile$/)
+				{
+				$pro = $at{$o};
+				last;
+				}
+			}
+		my $filter=$node->getParentNode()->getAttribute('filter');
+		if($filter ne '' && $at{'filter'}) {$filter.=','.$at{'filter'}}
+		elsif($at{'filter'}) {$filter=$at{'filter'}}
+		if($filter ne '') {$filter="\t($filter)"}
+		foreach my $atr ('bldFile','mrp','base')
+			{
+			my $ext;
+			my $link= $at{$atr};
+			if($atr eq 'bldFile') {
+				$ext = ($pro ne '') ? "/$pro" : '/bld.inf'
+			}
+			if($link ne '')
+				{
+				my $ok = 0;
+				my $trylink;
+				if($link && !($link=~/^\//))
+					{
+					$link= &abspath(File::Basename::dirname($file)."/$link");
+					$ok = (-e "$link$ext");
+					if(!$ok)	
+						{
+						foreach my $a (keys %rootmap)
+							{
+							$link=~s,^$a,$rootmap{$a},ie;
+							# remove leading ./  which is used to indicate that paths should remain relative
+							$link=~s,^\./([^/]),$1,; 
+							}
 
-my $filter = '';
-foreach (split(/</,$full)) {	# loop through all elements
-	my $found = 0;
-	if(/^component/) {		# save the current filter so we know if we need to skip the named filter
-		$filter='';
-		if(/filter="([^"]+)"/) {$filter=$1}
+						}
+					}
+				if(!$ok)
+					{
+					foreach my $a (keys %unitmap) {
+						if($a eq substr($link,0,length($a))) {
+							$trylink = $unitmap{$a}.substr($link,length($a));
+							if(-e "$trylink$ext") {
+								$ok=1;
+								$link = $trylink;
+								last;
+							}
+						}
+					}
+					}
+				if(!$ok)
+					{
+					print "Error: $atr not found in ",($trylink ne '') ? $trylink : $link,"$filter\n";
+					}				
+				}
+			}
+		}
+	elsif($tag eq 'meta')
+		{
+		my $rel= $node->getAttribute('rel') || 'Generic';
+		my $link= $node->getAttribute('href');
+		$link=~s,^file://(/([a-z]:/))?,$2,; # convert file URI to absolute path
+		if ($link ne '' ) 
+			{ 
+			if($link=~/^[\/]+:/)
+				{
+				print  "Note: Remote URL $link not validated\n";
+				next; # do not alter children
+				}
+			if(! ($link=~/^\//))
+				{
+				$link= &abspath(File::Basename::dirname($file)."/$link");
+				}
+			if(! -e $link) 
+				{
+				if(! -e &realPath($link)) {
+					print  "Warning: Local metadata file not found: $link\n";
+				}
+				next; # do not alter children
+				}
+			}
+		if($node->getAttribute('rel') eq 'link-mapping')
+			{# need to process this now
+			&rootMapMeta($node);
+			}
+		return;
+		}
+	else {return}
+	my $checkversion=0;
+	foreach my $item (@{$node->getChildNodes})
+		{
+		#print $item->getNodeType,"\n";
+		&walk($file,$item);
+		}
+
+
+
 	}
-	elsif(s/^unit//) {
-		my $f=",$filter,";		# commas are the separators - safe to have extra ones for testing
-		if(/filter="([^"]+)"/) {$f.=",$1,"}
-		if($skipfilter ne '' && $f=~/,$filter,/) {next}	# don't test anything with s60 filter
-		if(/\smrp="(.*?)"/) {
-			my $file = &fileLocation($1);
-			if($debug) {print "MRP ",-s $file," $file\n"}		# debug code		
-			if(!(-s $file)){
-				print  STDERR "$count: Cannot find MRP file $file\n";	
-				$found=1;
+
+
+sub realPath
+	{
+	my $link = shift;
+	foreach my $a (keys %unitmap)
+		{
+		if($a eq substr($link,0,length($a))) 
+			{
+			my $trylink = $unitmap{$a}.substr($link,length($a));
+			if(-e $trylink) {return $trylink}
 			}
 		}
-		if(/\sbldFile="(.*?)"/) {
-			my $file = &fileLocation("$1/bld.inf");
-			if($debug) {print "Bld ",-s $file ," $file\n"}		# debug code		
-			if(!(-s $file) ){
-				print  STDERR "$count: Cannot find bld.inf file $file\n";
-				$found=1;
+	}
+
+sub combineLink
+	{
+	# combine data from linked sysdef fragment w/ equivalent element in parent document
+	my $node = shift;
+	my $file = shift;
+	my $getfromfile = &localfile($file);
+	$getfromfile eq '' && return;  # already raised warning, no need to repeat
+	my  $doc = $parser->parsefile ($getfromfile);
+	my $item =&firstElement($doc->getDocumentElement);
+	$item || die "badly formatted $file";	
+	&fixIDs($item);
+	my %up = &atts($node);
+	my %down = &atts($item);
+	$up{'id'} eq $down{'id'}  || die "$up{id} differs from $down{id}";
+	$node->removeAttribute('href');
+	foreach my $v (keys %up) {delete $down{$v}}
+	foreach my $v (keys %down)
+		{
+		$node->setAttribute($v,$down{$v})
+		}
+	foreach my $child (@{$item->getChildNodes})
+		{
+		&copyInto($node,$child);
+		}
+	&walk($file,$node);
+	}
+
+
+sub copyInto
+	{
+	# make a deep copy the node (2nd arg) into the element (1st arg)
+	my $parent=shift;
+	my $item = shift;
+	my $doc = $parent->getOwnerDocument;
+	my $type = $item->getNodeType;
+	my $new;
+	if($type==1) 
+		{
+		&fixIDs($item);
+		$new = $doc->createElement($item->getTagName);
+		my %down = &atts($item);
+		foreach my $ordered ('id','name','bldFile','mrp','level','levels','introduced','deprecated','filter')
+			{
+			if($down{$ordered})
+				{
+				$new->setAttribute($ordered,$down{$ordered});
+				delete $down{$ordered}
+				}
+			}
+		while(my($a,$b) = each(%down))
+			{
+			$new->setAttribute($a,$b);
+			}
+		foreach my $child (@{$item->getChildNodes})
+			{
+			&copyInto($new,$child);
 			}
 		}
-		if(/\sbase="(.*?)"/) {
-			my $file = &fileLocation($1);
-			if($debug) {print "Base $file\n"}		# debug code		
-			if(!(-d $file) ){
-				print  STDERR "$count: Cannot find base dir $file\n";
-				$found=1;
-			}
+	elsif($type==3) 
+		{
+		$new = $doc->createTextNode ($item->getData);
+		}
+	elsif($type==8) 
+		{
+		$new = $doc->createComment  ($item->getData);
+		}
+	if($new)
+		{
+		$parent->appendChild($new);
 		}
-	}	
-	$count+=$found;	
+	}
+
+sub getNs
+	{
+	# find the namespace URI that applies to the specified prefix.
+	my $node = shift;
+	my $pre = shift;
+	my $uri = $node->getAttribute("xmlns:$pre");
+	if($uri) {return $uri}
+	my $parent = $node->getParentNode;
+	if($parent && $parent->getNodeType==1)
+		{
+		return getNs($parent,$pre);
+		}
+	}
+
+
+sub fixIDs
+	{
+	# translate the ID to use the root doc's namespaces 
+	my $node = shift;
+	foreach my $id ('id','before')
+		{
+		&fixID($node,$id);
+		}
 }
 
-exit $count;
+sub fixID
+	{
+	# translate the ID to use the root doc's namespaces 
+	my $node = shift;
+	my $attr = shift || 'id';
+	my $id = $node->getAttribute($attr);
+	if($id eq '') {return}
+	my $ns;
+	if($id=~s/^(.*)://)
+		{ # it's got a ns, find out what it is
+		my $pre = $1;
+		$ns=&getNs($node,$pre);
+		}
+	else
+		{
+		$ns = $node->getOwnerDocument->getDocumentElement->getAttribute("id-namespace") ||
+			$defaultns;
+		}
+	$ns = $urimap{$ns};
+	$id = ($ns eq '') ? $id : "$ns:$id";
+	return $node->setAttribute($attr,$id);
+}
 
-sub fileLocation {
-	my $file = "$dir$root$_[0]";
-	$file=~tr/\//\\/;
-	while($file=~s/\\[^\\.]+\\\.\.\\/\\/){}
-	return $file;
+sub firstElement {
+	# return the first element in this node
+	my $node = shift;
+	foreach my $item (@{$node->getChildNodes}) {
+		if($item->getNodeType==1) {return $item}
+	}
+}
+
+
+sub atts {
+	# return a hash of all attribtues defined for this element
+	my $node = shift;
+	my %at = $node->getAttributes;
+	my %list;
+	foreach my $a (keys %{$node->getAttributes}) 
+		{
+		if($a ne '')
+			{
+			$list{$a} = $node->getAttribute ($a);
+			}
+		}
+	return %list;
 }
-sub help {
-	print "$0: ",($_[0] eq '' ? "syntax"  : $_[0]); 
-	print "\nSyntax: [-v] [-nofilter filter] system_definition.xml 
-Validate the unit links in a system definition or package definition XML
-file. This only prints errors in the files. If it exits silently, the links
-are all valid.
-	Call with -nos60 filter to skip checking presence of fitler=\"s60\" units
-	Requires system definition files to be in the standard location
-	in deviceplatformrelease,
-	and the presence of joinsysdef.xsl and xalan.jar in their expected
-	locations.
-	Package definition files can be anywhere.";
-exit 1;
-}
+
+
+sub ns 
+	{
+	# return a hash of ns prefix and uri -- the xmlns: part is stripped off
+	my $node = shift;
+	my %list;
+	foreach my $a (keys %{$node->getAttributes}) 
+		{
+		my $pre = $a;
+		if($pre=~s/^xmlns://)
+			{
+			$list{$pre} = $node->getAttribute ($a);
+			}
+		}
+	return %list;
+	}
+
+
+sub resolvePath
+	{
+	# return full path to 2nd arg relative to first (path or absolute URI)
+	my $base = shift;
+	my $path = shift;
+	if($path=~m,^/,) {return $path } # path is absolute, but has no drive. Let OS deal with it.
+	if($path=~s,^file:///([a-zA-Z]:/),$1,) {return $path } # file URI with drive letter
+	if($path=~m,^file://,) {return $path } # file URI with no drive letter (unit-style). Just pass on as is with leading / and let OS deal with it
+	if($path=~m,^[a-z0-9][a-z0-9]+:,i) {return $path } # absolute URI -- no idea how to handle, so just return
+	return &abspath(File::Basename::dirname($base)."/$path");
+	}
+
+
+sub resolveURI
+	{
+	# return full path to 2nd arg relative to first (path or absolute URI)
+	my $base = shift;
+	my $path = shift;
+	if($path=~m,[a-z0-9][a-z0-9]+:,i) {return $path } # absolute URI -- just return
+	if($path=~m,^/,) {return $path } # path is absolute, but has no drive. Let OS deal with it.
+	return &abspath(File::Basename::dirname($base)."/$path");
+	}
+
+sub localfile
+	{
+	my $file = shift;
+	if($file=~s,file:///([a-zA-Z]:/),$1,) {return $file } # file URI with drive letter
+	if($file=~m,file://,) {return $file } # file URI with no drive letter (unit-style). Just pass on as is with leading / and let OS deal with it
+	if($file=~m,^([a-z0-9][a-z0-9]+):,i)
+		{
+		print  "ERROR: $1 scheme not supported\n";
+		return;  # return empty string if not supported.
+		} 
+	return $file
+	}
+
+
+
+	
+
+sub help
+	{
+	my $name= $0; $name=~s,^.*[\\/],,;
+my $text;
+format STDERR =
+ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+  $text,
+     ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
+    $text
+.
+print STDERR "usage: $name  [options...] sysdef\n  valid options are:\n\n";
+	foreach (
+		"-path [sm-path]\tspecifies the full system-model path to the file which is being processed. By default this is  \"/os/deviceplatformrelease/foundation_system/system_model/system_definition.xml\"",
+			"   This must be an absolute path if you're processing a root sysdef.",
+			"   If processing a pkgdef file, you can use \"./package_definition.xml\" to leave all links relative.",
+		"effective-sysdef [local-file]\tspecifies another local filesystem location the sysdef should be considered when resolving linked metas and unit paths, but not system model item hrefs. This is mainly used for testing system-wide changes to pkgdefs since it allows the pkgdefs to exist in a separate location to the rest of the codeline"
+		) {
+		$text = $_;
+		write STDERR;
+		print STDERR "\n";
+	}
+
+	exit(1);
+	}
+
+
+