f5eb4cbd79e7b751f2d833e4302517c4a9f2b151
[WebKit-https.git] / Source / WebCore / bindings / scripts / preprocess-idls.pl
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
21 use strict;
22 use FindBin;
23 use lib $FindBin::Bin;
24
25 use File::Basename;
26 use Getopt::Long;
27 use Cwd;
28 use Config;
29
30 my $defines;
31 my $preprocessor;
32 my $idlFilesList;
33 my $supplementalDependencyFile;
34 my $windowConstructorsFile;
35 my $workerGlobalScopeConstructorsFile;
36 my $dedicatedWorkerGlobalScopeConstructorsFile;
37 my $supplementalMakefileDeps;
38
39 GetOptions('defines=s' => \$defines,
40            'preprocessor=s' => \$preprocessor,
41            'idlFilesList=s' => \$idlFilesList,
42            'supplementalDependencyFile=s' => \$supplementalDependencyFile,
43            'windowConstructorsFile=s' => \$windowConstructorsFile,
44            'workerGlobalScopeConstructorsFile=s' => \$workerGlobalScopeConstructorsFile,
45            'dedicatedWorkerGlobalScopeConstructorsFile=s' => \$dedicatedWorkerGlobalScopeConstructorsFile,
46            'supplementalMakefileDeps=s' => \$supplementalMakefileDeps);
47
48 die('Must specify #define macros using --defines.') unless defined($defines);
49 die('Must specify an output file using --supplementalDependencyFile.') unless defined($supplementalDependencyFile);
50 die('Must specify an output file using --windowConstructorsFile.') unless defined($windowConstructorsFile);
51 die('Must specify an output file using --workerGlobalScopeConstructorsFile.') unless defined($workerGlobalScopeConstructorsFile);
52 die('Must specify an output file using --dedicatedWorkerGlobalScopeConstructorsFile.') unless defined($dedicatedWorkerGlobalScopeConstructorsFile);
53 die('Must specify the file listing all IDLs using --idlFilesList.') unless defined($idlFilesList);
54
55 $supplementalDependencyFile = CygwinPathIfNeeded($supplementalDependencyFile);
56 $windowConstructorsFile = CygwinPathIfNeeded($windowConstructorsFile);
57 $workerGlobalScopeConstructorsFile = CygwinPathIfNeeded($workerGlobalScopeConstructorsFile);
58 $dedicatedWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($dedicatedWorkerGlobalScopeConstructorsFile);
59 $supplementalMakefileDeps = CygwinPathIfNeeded($supplementalMakefileDeps);
60
61 open FH, "< $idlFilesList" or die "Cannot open $idlFilesList\n";
62 my @idlFilesIn = <FH>;
63 chomp(@idlFilesIn);
64 my @idlFiles = ();
65 foreach (@idlFilesIn) {
66     push @idlFiles, CygwinPathIfNeeded($_);
67 }
68 close FH;
69
70 my %interfaceNameToIdlFile;
71 my %idlFileToInterfaceName;
72 my %supplementalDependencies;
73 my %supplementals;
74 my $windowConstructorsCode = "";
75 my $workerGlobalScopeConstructorsCode = "";
76 my $dedicatedWorkerGlobalScopeConstructorsCode = "";
77
78 # Get rid of duplicates in idlFiles array.
79 my %idlFileHash = map { $_, 1 } @idlFiles;
80
81 # Populate $idlFileToInterfaceName and $interfaceNameToIdlFile.
82 foreach my $idlFile (sort keys %idlFileHash) {
83     my $fullPath = Cwd::realpath($idlFile);
84     my $interfaceName = fileparse(basename($idlFile), ".idl");
85     $idlFileToInterfaceName{$fullPath} = $interfaceName;
86     $interfaceNameToIdlFile{$interfaceName} = $fullPath;
87 }
88
89 # Parse all IDL files.
90 foreach my $idlFile (sort keys %idlFileHash) {
91     my $fullPath = Cwd::realpath($idlFile);
92     my $idlFileContents = getFileContents($fullPath);
93     # Handle partial interfaces.
94     my $partialInterfaceName = getPartialInterfaceNameFromIDL($idlFileContents);
95     if ($partialInterfaceName) {
96         $supplementalDependencies{$fullPath} = [$partialInterfaceName];
97         next;
98     }
99
100     $supplementals{$fullPath} = [];
101
102     # Skip if the IDL file does not contain an interface, a callback interface or an exception.
103     # The IDL may contain a dictionary.
104     next unless containsInterfaceOrExceptionFromIDL($idlFileContents);
105
106     my $interfaceName = fileparse(basename($idlFile), ".idl");
107     # Handle implements statements.
108     my $implementedInterfaces = getImplementedInterfacesFromIDL($idlFileContents, $interfaceName);
109     foreach my $implementedInterface (@{$implementedInterfaces}) {
110         my $implementedIdlFile = $interfaceNameToIdlFile{$implementedInterface};
111         die "Could not find a the IDL file where the following implemented interface is defined: $implementedInterface" unless $implementedIdlFile;
112         if ($supplementalDependencies{$implementedIdlFile}) {
113             push(@{$supplementalDependencies{$implementedIdlFile}}, $interfaceName);
114         } else {
115             $supplementalDependencies{$implementedIdlFile} = [$interfaceName];
116         }
117     }
118
119     # For every interface that is exposed in a given ECMAScript global environment and:
120     # - is a callback interface that has constants declared on it, or
121     # - is a non-callback interface that is not declared with the [NoInterfaceObject] extended attribute, a corresponding
122     #   property must exist on the ECMAScript environment's global object.
123     # See https://heycam.github.io/webidl/#es-interfaces
124     my $extendedAttributes = getInterfaceExtendedAttributesFromIDL($idlFileContents);
125     unless ($extendedAttributes->{"NoInterfaceObject"}) {
126         if (!isCallbackInterfaceFromIDL($idlFileContents) || interfaceHasConstantAttribute($idlFileContents)) {
127             my $exposedAttribute = $extendedAttributes->{"Exposed"} || "Window";
128             $exposedAttribute = substr($exposedAttribute, 1, -1) if substr($exposedAttribute, 0, 1) eq "(";
129             my @globalContexts = split(",", $exposedAttribute);
130             my ($attributeCode, $windowAliases) = GenerateConstructorAttributes($interfaceName, $extendedAttributes);
131             foreach my $globalContext (@globalContexts) {
132                 if ($globalContext eq "Window") {
133                     $windowConstructorsCode .= $attributeCode;
134                 } elsif ($globalContext eq "Worker") {
135                     $workerGlobalScopeConstructorsCode .= $attributeCode;
136                 } elsif ($globalContext eq "DedicatedWorker") {
137                     $dedicatedWorkerGlobalScopeConstructorsCode .= $attributeCode;
138                 } else {
139                     die "Unsupported global context '$globalContext' used in [Exposed] at $idlFile";
140                 }
141             }
142             $windowConstructorsCode .= $windowAliases if $windowAliases;
143         }
144     }
145 }
146
147 # Generate partial interfaces for Constructors.
148 GeneratePartialInterface("DOMWindow", $windowConstructorsCode, $windowConstructorsFile);
149 GeneratePartialInterface("WorkerGlobalScope", $workerGlobalScopeConstructorsCode, $workerGlobalScopeConstructorsFile);
150 GeneratePartialInterface("DedicatedWorkerGlobalScope", $dedicatedWorkerGlobalScopeConstructorsCode, $dedicatedWorkerGlobalScopeConstructorsFile);
151
152 # Resolves partial interfaces and implements dependencies.
153 foreach my $idlFile (sort keys %supplementalDependencies) {
154     my $baseFiles = $supplementalDependencies{$idlFile};
155     foreach my $baseFile (@{$baseFiles}) {
156         my $targetIdlFile = $interfaceNameToIdlFile{$baseFile};
157         push(@{$supplementals{$targetIdlFile}}, $idlFile);
158     }
159     delete $supplementals{$idlFile};
160 }
161
162 # Outputs the dependency.
163 # The format of a supplemental dependency file:
164 #
165 # DOMWindow.idl P.idl Q.idl R.idl
166 # Document.idl S.idl
167 # Event.idl
168 # ...
169 #
170 # The above indicates that DOMWindow.idl is supplemented by P.idl, Q.idl and R.idl,
171 # Document.idl is supplemented by S.idl, and Event.idl is supplemented by no IDLs.
172 # The IDL that supplements another IDL (e.g. P.idl) never appears in the dependency file.
173 my $dependencies = "";
174 foreach my $idlFile (sort keys %supplementals) {
175     $dependencies .= "$idlFile @{$supplementals{$idlFile}}\n";
176 }
177 WriteFileIfChanged($supplementalDependencyFile, $dependencies);
178
179 if ($supplementalMakefileDeps) {
180     my $makefileDeps = "";
181     foreach my $idlFile (sort keys %supplementals) {
182         my $basename = $idlFileToInterfaceName{$idlFile};
183
184         my @dependencies = map { basename($_) } @{$supplementals{$idlFile}};
185
186         $makefileDeps .= "JS${basename}.h: @{dependencies}\n";
187         $makefileDeps .= "DOM${basename}.h: @{dependencies}\n";
188         $makefileDeps .= "WebDOM${basename}.h: @{dependencies}\n";
189         foreach my $dependency (@dependencies) {
190             $makefileDeps .= "${dependency}:\n";
191         }
192     }
193
194     WriteFileIfChanged($supplementalMakefileDeps, $makefileDeps);
195 }
196
197 my $cygwinPathAdded;
198 sub CygwinPathIfNeeded
199 {
200     my $path = shift;
201     if ($path && $Config{osname} eq "cygwin") {
202         if (not $cygwinPathAdded) {
203             $ENV{PATH} = "$ENV{PATH}:/cygdrive/c/cygwin/bin";
204             $cygwinPathAdded = 1; 
205         }
206         chomp($path = `cygpath -u '$path'`);
207         $path =~ s/[\r\n]//;
208     }
209     return $path;
210 }
211
212 sub WriteFileIfChanged
213 {
214     my $fileName = shift;
215     my $contents = shift;
216
217     if (-f $fileName) {
218         open FH, "<", $fileName or die "Couldn't open $fileName: $!\n";
219         my @lines = <FH>;
220         my $oldContents = join "", @lines;
221         close FH;
222         return if $contents eq $oldContents;
223     }
224     open FH, ">", $fileName or die "Couldn't open $fileName: $!\n";
225     print FH $contents;
226     close FH;
227 }
228
229 sub GeneratePartialInterface
230 {
231     my $interfaceName = shift;
232     my $attributesCode = shift;
233     my $destinationFile = shift;
234
235     my $contents = "partial interface ${interfaceName} {\n$attributesCode};\n";
236     WriteFileIfChanged($destinationFile, $contents);
237
238     my $fullPath = Cwd::realpath($destinationFile);
239     $supplementalDependencies{$fullPath} = [$interfaceName] if $interfaceNameToIdlFile{$interfaceName};
240 }
241
242 sub GenerateConstructorAttributes
243 {
244     my $interfaceName = shift;
245     my $extendedAttributes = shift;
246
247     my $code = "    ";
248     my @extendedAttributesList;
249     foreach my $attributeName (sort keys %{$extendedAttributes}) {
250       next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledForWorld"
251         || $attributeName eq "EnabledBySetting" || $attributeName eq "SecureContext" || $attributeName eq "PrivateIdentifier"
252         || $attributeName eq "PublicIdentifier");
253       my $extendedAttribute = $attributeName;
254       $extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING";
255       push(@extendedAttributesList, $extendedAttribute);
256     }
257     $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
258
259     my $originalInterfaceName = $interfaceName;
260     $interfaceName = $extendedAttributes->{"InterfaceName"} if $extendedAttributes->{"InterfaceName"};
261     $code .= "attribute " . $originalInterfaceName . "Constructor $interfaceName;\n";
262
263     # In addition to the regular property, for every [NamedConstructor] extended attribute on an interface,
264     # a corresponding property MUST exist on the ECMAScript global object.
265     if ($extendedAttributes->{"NamedConstructor"}) {
266         my $constructorName = $extendedAttributes->{"NamedConstructor"};
267         $constructorName =~ s/\(.*//g; # Extract function name.
268         $code .= "    ";
269         $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
270         $code .= "attribute " . $originalInterfaceName . "NamedConstructor $constructorName;\n";
271     }
272     
273     my $windowAliasesCode;
274     if ($extendedAttributes->{"LegacyWindowAlias"}) {
275         my $attributeValue = $extendedAttributes->{"LegacyWindowAlias"};
276         $attributeValue = substr($attributeValue, 1, -1) if substr($attributeValue, 0, 1) eq "(";
277         my @windowAliases = split(",", $attributeValue);
278         foreach my $windowAlias (@windowAliases) {
279             $windowAliasesCode .= "    ";
280             $windowAliasesCode .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
281             $windowAliasesCode .= "attribute " . $originalInterfaceName . "Constructor $windowAlias; // Legacy Window alias.\n";
282         }
283     }
284     
285     return ($code, $windowAliasesCode);
286 }
287
288 sub getFileContents
289 {
290     my $idlFile = shift;
291
292     open FILE, "<", $idlFile;
293     my @lines = <FILE>;
294     close FILE;
295
296     # Filter out preprocessor lines.
297     @lines = grep(!/^\s*#/, @lines);
298
299     return join('', @lines);
300 }
301
302 sub getPartialInterfaceNameFromIDL
303 {
304     my $fileContents = shift;
305
306     if ($fileContents =~ /partial\s+interface\s+(\w+)/gs) {
307         return $1;
308     }
309 }
310
311 # identifier-A implements identifier-B;
312 # http://www.w3.org/TR/WebIDL/#idl-implements-statements
313 sub getImplementedInterfacesFromIDL
314 {
315     my $fileContents = shift;
316     my $interfaceName = shift;
317
318     my @implementedInterfaces = ();
319     while ($fileContents =~ /^\s*(\w+)\s+implements\s+(\w+)\s*;/mg) {
320         die "Identifier on the left of the 'implements' statement should be $interfaceName in $interfaceName.idl, but found $1" if $1 ne $interfaceName;
321         push(@implementedInterfaces, $2);
322     }
323     return \@implementedInterfaces
324 }
325
326 sub isCallbackInterfaceFromIDL
327 {
328     my $fileContents = shift;
329     return ($fileContents =~ /callback\s+interface\s+\w+/gs);
330 }
331
332 sub containsInterfaceOrExceptionFromIDL
333 {
334     my $fileContents = shift;
335
336     return 1 if $fileContents =~ /\bcallback\s+interface\s+\w+/gs;
337     return 1 if $fileContents =~ /\binterface\s+\w+/gs;
338     return 1 if $fileContents =~ /\bexception\s+\w+/gs;
339     return 0;
340 }
341
342 sub trim
343 {
344     my $string = shift;
345     $string =~ s/^\s+|\s+$//g;
346     return $string;
347 }
348
349 sub getInterfaceExtendedAttributesFromIDL
350 {
351     my $fileContents = shift;
352
353     my $extendedAttributes = {};
354
355     # Remove comments from fileContents before processing.
356     # FIX: Preference to use Regex::Common::comment, however it is not available on
357     # all build systems.
358     $fileContents =~ s/(?:(?:(?:\/\/)(?:[^\n]*)(?:\n))|(?:(?:\/\*)(?:(?:[^\*]+|\*(?!\/))*)(?:\*\/)))//g;
359
360     if ($fileContents =~ /\[(.*)\]\s+(callback interface|interface|exception)\s+(\w+)/gs) {
361         my @parts = split(m/,(?![^()]*\))/, $1);
362         foreach my $part (@parts) {
363             my @keyValue = split('=', $part);
364             my $key = trim($keyValue[0]);
365             next unless length($key);
366             my $value = "VALUE_IS_MISSING";
367             $value = trim($keyValue[1]) if @keyValue > 1;
368             $extendedAttributes->{$key} = $value;
369         }
370     }
371
372     return $extendedAttributes;
373 }
374
375 sub interfaceHasConstantAttribute
376 {
377     my $fileContents = shift;
378
379     return $fileContents =~ /\s+const[\s\w]+=\s+[\w]+;/gs;
380 }