1#!/usr/bin/perl -w 2# 3# Copyright (C) 2011 Google Inc. All rights reserved. 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Library General Public 7# License as published by the Free Software Foundation; either 8# version 2 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Library General Public License for more details. 14# 15# You should have received a copy of the GNU Library General Public License 16# along with this library; see the file COPYING.LIB. If not, write to 17# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18# Boston, MA 02110-1301, USA. 19# 20 21use strict; 22 23use File::Basename; 24use Getopt::Long; 25use Cwd; 26 27my $defines; 28my $preprocessor; 29my $idlFilesList; 30my $supplementalDependencyFile; 31my $windowConstructorsFile; 32my $workerGlobalScopeConstructorsFile; 33my $sharedWorkerGlobalScopeConstructorsFile; 34my $dedicatedWorkerGlobalScopeConstructorsFile; 35my $supplementalMakefileDeps; 36 37GetOptions('defines=s' => \$defines, 38 'preprocessor=s' => \$preprocessor, 39 'idlFilesList=s' => \$idlFilesList, 40 'supplementalDependencyFile=s' => \$supplementalDependencyFile, 41 'windowConstructorsFile=s' => \$windowConstructorsFile, 42 'workerGlobalScopeConstructorsFile=s' => \$workerGlobalScopeConstructorsFile, 43 'sharedWorkerGlobalScopeConstructorsFile=s' => \$sharedWorkerGlobalScopeConstructorsFile, 44 'dedicatedWorkerGlobalScopeConstructorsFile=s' => \$dedicatedWorkerGlobalScopeConstructorsFile, 45 'supplementalMakefileDeps=s' => \$supplementalMakefileDeps); 46 47die('Must specify #define macros using --defines.') unless defined($defines); 48die('Must specify an output file using --supplementalDependencyFile.') unless defined($supplementalDependencyFile); 49die('Must specify an output file using --windowConstructorsFile.') unless defined($windowConstructorsFile); 50die('Must specify an output file using --workerGlobalScopeConstructorsFile.') unless defined($workerGlobalScopeConstructorsFile); 51die('Must specify an output file using --sharedWorkerGlobalScopeConstructorsFile.') unless defined($sharedWorkerGlobalScopeConstructorsFile); 52die('Must specify an output file using --dedicatedWorkerGlobalScopeConstructorsFile.') unless defined($dedicatedWorkerGlobalScopeConstructorsFile); 53die('Must specify the file listing all IDLs using --idlFilesList.') unless defined($idlFilesList); 54 55open FH, "< $idlFilesList" or die "Cannot open $idlFilesList\n"; 56my @idlFiles = <FH>; 57chomp(@idlFiles); 58close FH; 59 60my %interfaceNameToIdlFile; 61my %idlFileToInterfaceName; 62my %supplementalDependencies; 63my %supplementals; 64my $windowConstructorsCode = ""; 65my $workerGlobalScopeConstructorsCode = ""; 66my $sharedWorkerGlobalScopeConstructorsCode = ""; 67my $dedicatedWorkerGlobalScopeConstructorsCode = ""; 68 69# Get rid of duplicates in idlFiles array. 70my %idlFileHash = map { $_, 1 } @idlFiles; 71 72# Populate $idlFileToInterfaceName and $interfaceNameToIdlFile. 73foreach my $idlFile (keys %idlFileHash) { 74 my $fullPath = Cwd::realpath($idlFile); 75 my $interfaceName = fileparse(basename($idlFile), ".idl"); 76 $idlFileToInterfaceName{$fullPath} = $interfaceName; 77 $interfaceNameToIdlFile{$interfaceName} = $fullPath; 78} 79 80# Parse all IDL files. 81foreach my $idlFile (sort keys %idlFileHash) { 82 my $fullPath = Cwd::realpath($idlFile); 83 my $idlFileContents = getFileContents($fullPath); 84 # Handle partial interfaces. 85 my $partialInterfaceName = getPartialInterfaceNameFromIDL($idlFileContents); 86 if ($partialInterfaceName) { 87 $supplementalDependencies{$fullPath} = [$partialInterfaceName]; 88 next; 89 } 90 my $interfaceName = fileparse(basename($idlFile), ".idl"); 91 # Handle implements statements. 92 my $implementedInterfaces = getImplementedInterfacesFromIDL($idlFileContents, $interfaceName); 93 foreach my $implementedInterface (@{$implementedInterfaces}) { 94 my $implementedIdlFile = $interfaceNameToIdlFile{$implementedInterface}; 95 die "Could not find a the IDL file where the following implemented interface is defined: $implementedInterface" unless $implementedIdlFile; 96 if ($supplementalDependencies{$implementedIdlFile}) { 97 push(@{$supplementalDependencies{$implementedIdlFile}}, $interfaceName); 98 } else { 99 $supplementalDependencies{$implementedIdlFile} = [$interfaceName]; 100 } 101 } 102 # Handle [NoInterfaceObject]. 103 unless (isCallbackInterfaceFromIDL($idlFileContents)) { 104 my $extendedAttributes = getInterfaceExtendedAttributesFromIDL($idlFileContents); 105 unless ($extendedAttributes->{"NoInterfaceObject"}) { 106 my @globalContexts = split("&", $extendedAttributes->{"GlobalContext"} || "DOMWindow"); 107 my $attributeCode = GenerateConstructorAttribute($interfaceName, $extendedAttributes); 108 $windowConstructorsCode .= $attributeCode if grep(/^DOMWindow$/, @globalContexts); 109 $workerGlobalScopeConstructorsCode .= $attributeCode if grep(/^WorkerGlobalScope$/, @globalContexts); 110 $sharedWorkerGlobalScopeConstructorsCode .= $attributeCode if grep(/^SharedWorkerGlobalScope$/, @globalContexts); 111 $dedicatedWorkerGlobalScopeConstructorsCode .= $attributeCode if grep(/^DedicatedWorkerGlobalScope$/, @globalContexts); 112 } 113 } 114 $supplementals{$fullPath} = []; 115} 116 117# Generate partial interfaces for Constructors. 118GeneratePartialInterface("DOMWindow", $windowConstructorsCode, $windowConstructorsFile); 119GeneratePartialInterface("WorkerGlobalScope", $workerGlobalScopeConstructorsCode, $workerGlobalScopeConstructorsFile); 120GeneratePartialInterface("SharedWorkerGlobalScope", $sharedWorkerGlobalScopeConstructorsCode, $sharedWorkerGlobalScopeConstructorsFile); 121GeneratePartialInterface("DedicatedWorkerGlobalScope", $dedicatedWorkerGlobalScopeConstructorsCode, $dedicatedWorkerGlobalScopeConstructorsFile); 122 123# Resolves partial interfaces and implements dependencies. 124foreach my $idlFile (keys %supplementalDependencies) { 125 my $baseFiles = $supplementalDependencies{$idlFile}; 126 foreach my $baseFile (@{$baseFiles}) { 127 my $targetIdlFile = $interfaceNameToIdlFile{$baseFile}; 128 push(@{$supplementals{$targetIdlFile}}, $idlFile); 129 } 130 delete $supplementals{$idlFile}; 131} 132 133# Outputs the dependency. 134# The format of a supplemental dependency file: 135# 136# DOMWindow.idl P.idl Q.idl R.idl 137# Document.idl S.idl 138# Event.idl 139# ... 140# 141# The above indicates that DOMWindow.idl is supplemented by P.idl, Q.idl and R.idl, 142# Document.idl is supplemented by S.idl, and Event.idl is supplemented by no IDLs. 143# The IDL that supplements another IDL (e.g. P.idl) never appears in the dependency file. 144my $dependencies = ""; 145foreach my $idlFile (sort keys %supplementals) { 146 $dependencies .= "$idlFile @{$supplementals{$idlFile}}\n"; 147} 148WriteFileIfChanged($supplementalDependencyFile, $dependencies); 149 150if ($supplementalMakefileDeps) { 151 my $makefileDeps = ""; 152 foreach my $idlFile (sort keys %supplementals) { 153 my $basename = $idlFileToInterfaceName{$idlFile}; 154 155 my @dependencies = map { basename($_) } @{$supplementals{$idlFile}}; 156 157 $makefileDeps .= "JS${basename}.h: @{dependencies}\n"; 158 $makefileDeps .= "DOM${basename}.h: @{dependencies}\n"; 159 $makefileDeps .= "WebDOM${basename}.h: @{dependencies}\n"; 160 foreach my $dependency (@dependencies) { 161 $makefileDeps .= "${dependency}:\n"; 162 } 163 } 164 165 WriteFileIfChanged($supplementalMakefileDeps, $makefileDeps); 166} 167 168sub WriteFileIfChanged 169{ 170 my $fileName = shift; 171 my $contents = shift; 172 173 if (-f $fileName) { 174 open FH, "<", $fileName or die "Couldn't open $fileName: $!\n"; 175 my @lines = <FH>; 176 my $oldContents = join "", @lines; 177 close FH; 178 return if $contents eq $oldContents; 179 } 180 open FH, ">", $fileName or die "Couldn't open $fileName: $!\n"; 181 print FH $contents; 182 close FH; 183} 184 185sub GeneratePartialInterface 186{ 187 my $interfaceName = shift; 188 my $attributesCode = shift; 189 my $destinationFile = shift; 190 191 my $contents = "partial interface ${interfaceName} {\n$attributesCode};\n"; 192 WriteFileIfChanged($destinationFile, $contents); 193 194 my $fullPath = Cwd::realpath($destinationFile); 195 $supplementalDependencies{$fullPath} = [$interfaceName] if $interfaceNameToIdlFile{$interfaceName}; 196} 197 198sub GenerateConstructorAttribute 199{ 200 my $interfaceName = shift; 201 my $extendedAttributes = shift; 202 203 my $code = " "; 204 my @extendedAttributesList; 205 foreach my $attributeName (keys %{$extendedAttributes}) { 206 next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledBySetting"); 207 my $extendedAttribute = $attributeName; 208 $extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING"; 209 push(@extendedAttributesList, $extendedAttribute); 210 } 211 $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList; 212 213 my $originalInterfaceName = $interfaceName; 214 $interfaceName = $extendedAttributes->{"InterfaceName"} if $extendedAttributes->{"InterfaceName"}; 215 $code .= "attribute " . $originalInterfaceName . "Constructor $interfaceName;\n"; 216 217 # In addition to the regular property, for every [NamedConstructor] extended attribute on an interface, 218 # a corresponding property MUST exist on the ECMAScript global object. 219 if ($extendedAttributes->{"NamedConstructor"}) { 220 my $constructorName = $extendedAttributes->{"NamedConstructor"}; 221 $constructorName =~ s/\(.*//g; # Extract function name. 222 $code .= " "; 223 $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList; 224 $code .= "attribute " . $originalInterfaceName . "NamedConstructor $constructorName;\n"; 225 } 226 return $code; 227} 228 229sub getFileContents 230{ 231 my $idlFile = shift; 232 233 open FILE, "<", $idlFile; 234 my @lines = <FILE>; 235 close FILE; 236 237 # Filter out preprocessor lines. 238 @lines = grep(!/^\s*#/, @lines); 239 240 return join('', @lines); 241} 242 243sub getPartialInterfaceNameFromIDL 244{ 245 my $fileContents = shift; 246 247 if ($fileContents =~ /partial\s+interface\s+(\w+)/gs) { 248 return $1; 249 } 250} 251 252# identifier-A implements identifier-B; 253# http://www.w3.org/TR/WebIDL/#idl-implements-statements 254sub getImplementedInterfacesFromIDL 255{ 256 my $fileContents = shift; 257 my $interfaceName = shift; 258 259 my @implementedInterfaces = (); 260 while ($fileContents =~ /^\s*(\w+)\s+implements\s+(\w+)\s*;/mg) { 261 die "Identifier on the left of the 'implements' statement should be $interfaceName in $interfaceName.idl, but found $1" if $1 ne $interfaceName; 262 push(@implementedInterfaces, $2); 263 } 264 return \@implementedInterfaces 265} 266 267sub isCallbackInterfaceFromIDL 268{ 269 my $fileContents = shift; 270 return ($fileContents =~ /callback\s+interface\s+\w+/gs); 271} 272 273sub trim 274{ 275 my $string = shift; 276 $string =~ s/^\s+|\s+$//g; 277 return $string; 278} 279 280sub getInterfaceExtendedAttributesFromIDL 281{ 282 my $fileContents = shift; 283 284 my $extendedAttributes = {}; 285 286 if ($fileContents =~ /\[(.*)\]\s+(interface|exception)\s+(\w+)/gs) { 287 my @parts = split(',', $1); 288 foreach my $part (@parts) { 289 my @keyValue = split('=', $part); 290 my $key = trim($keyValue[0]); 291 next unless length($key); 292 my $value = "VALUE_IS_MISSING"; 293 $value = trim($keyValue[1]) if @keyValue > 1; 294 $extendedAttributes->{$key} = $value; 295 } 296 } 297 298 return $extendedAttributes; 299} 300