Use typed identifier for WebSocketChannel identifiers
[WebKit-https.git] / Source / WTF / Scripts / generate-unified-source-bundles.rb
1 # Copyright (C) 2017 Apple Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1. Redistributions of source code must retain the above copyright
7 #    notice, this list of conditions and the following disclaimer.
8 # 2. Redistributions in binary form must reproduce the above copyright
9 #    notice, this list of conditions and the following disclaimer in the
10 #    documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
13 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
14 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
16 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
22 # THE POSSIBILITY OF SUCH DAMAGE.
23
24 require 'digest'
25 require 'fileutils'
26 require 'pathname'
27 require 'getoptlong'
28
29 SCRIPT_NAME = File.basename($0)
30 COMMENT_REGEXP = /\/\//
31
32 def usage(message)
33     if message
34         puts "Error: #{message}"
35         puts
36     end
37
38     puts "usage: #{SCRIPT_NAME} [options] <sources-list-file>..."
39     puts "<sources-list-file> may be separate arguments or one semicolon separated string"
40     puts "--help                          (-h) Print this message"
41     puts "--verbose                       (-v) Adds extra logging to stderr."
42     puts
43     puts "Required arguments:"
44     puts "--source-tree-path              (-s) Path to the root of the source directory."
45     puts "--derived-sources-path          (-d) Path to the directory where the unified source files should be placed."
46     puts
47     puts "Optional arguments:"
48     puts "--print-bundled-sources              Print bundled sources rather than generating sources"
49     puts "--print-all-sources                  Print all sources rather than generating sources"
50     puts "--generate-xcfilelists               Generate .xcfilelist files"
51     puts "--input-xcfilelist-path              Path of the generated input .xcfilelist file"
52     puts "--output-xcfilelist-path             Path of the generated output .xcfilelist file"
53     puts
54     puts "Generation options:"
55     puts "--max-cpp-bundle-count               Use global sequential numbers for cpp bundle filenames and set the limit on the number"
56     puts "--max-obj-c-bundle-count             Use global sequential numbers for Obj-C bundle filenames and set the limit on the number"
57     puts "--dense-bundle-filter                Densely bundle files matching the given path glob"
58     exit 1
59 end
60
61 MAX_BUNDLE_SIZE = 8
62 MAX_DENSE_BUNDLE_SIZE = 64
63 $derivedSourcesPath = nil
64 $unifiedSourceOutputPath = nil
65 $sourceTreePath = nil
66 $verbose = false
67 $mode = :GenerateBundles
68 $inputXCFilelistPath = nil
69 $outputXCFilelistPath = nil
70 $maxCppBundleCount = nil
71 $maxObjCBundleCount = nil
72 $denseBundleFilters = []
73
74 def log(text)
75     $stderr.puts text if $verbose
76 end
77
78 GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT],
79                ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
80                ['--derived-sources-path', '-d', GetoptLong::REQUIRED_ARGUMENT],
81                ['--source-tree-path', '-s', GetoptLong::REQUIRED_ARGUMENT],
82                ['--print-bundled-sources', GetoptLong::NO_ARGUMENT],
83                ['--print-all-sources', GetoptLong::NO_ARGUMENT],
84                ['--generate-xcfilelists', GetoptLong::NO_ARGUMENT],
85                ['--input-xcfilelist-path', GetoptLong::REQUIRED_ARGUMENT],
86                ['--output-xcfilelist-path', GetoptLong::REQUIRED_ARGUMENT],
87                ['--max-cpp-bundle-count', GetoptLong::REQUIRED_ARGUMENT],
88                ['--max-obj-c-bundle-count', GetoptLong::REQUIRED_ARGUMENT],
89                ['--dense-bundle-filter', GetoptLong::REQUIRED_ARGUMENT]).each {
90     | opt, arg |
91     case opt
92     when '--help'
93         usage(nil)
94     when '--verbose'
95         $verbose = true
96     when '--derived-sources-path'
97         $derivedSourcesPath = Pathname.new(arg)
98     when '--source-tree-path'
99         $sourceTreePath = Pathname.new(arg)
100         usage("Source tree #{arg} does not exist.") if !$sourceTreePath.exist?
101     when '--print-bundled-sources'
102         $mode = :PrintBundledSources
103     when '--print-all-sources'
104         $mode = :PrintAllSources
105     when '--generate-xcfilelists'
106         $mode = :GenerateXCFilelists
107     when '--input-xcfilelist-path'
108         $inputXCFilelistPath = arg
109     when '--output-xcfilelist-path'
110         $outputXCFilelistPath = arg
111     when '--max-cpp-bundle-count'
112         $maxCppBundleCount = arg.to_i
113     when '--max-obj-c-bundle-count'
114         $maxObjCBundleCount = arg.to_i
115     when '--dense-bundle-filter'
116         $denseBundleFilters.push(arg)
117     end
118 }
119
120 $unifiedSourceOutputPath = $derivedSourcesPath + Pathname.new("unified-sources")
121 FileUtils.mkpath($unifiedSourceOutputPath) if !$unifiedSourceOutputPath.exist? && $mode != :GenerateXCFilelists
122
123 usage("--derived-sources-path must be specified.") if !$unifiedSourceOutputPath
124 usage("--source-tree-path must be specified.") if !$sourceTreePath
125 log("Putting unified sources in #{$unifiedSourceOutputPath}")
126
127 usage("At least one source list file must be specified.") if ARGV.length == 0
128 # Even though CMake will only pass us a single semicolon separated arguemnts, we separate all the arguments for simplicity.
129 sourceListFiles = ARGV.to_a.map { | sourceFileList | sourceFileList.split(";") }.flatten
130 log("Source files: #{sourceListFiles}")
131 $generatedSources = []
132 $inputSources = []
133 $outputSources = []
134
135 class SourceFile
136     attr_reader :unifiable, :fileIndex, :path
137     def initialize(file, fileIndex)
138         @unifiable = true
139         @fileIndex = fileIndex
140
141         attributeStart = file =~ /@/
142         if attributeStart
143             # We want to make sure we skip the first @ so split works correctly
144             attributesText = file[(attributeStart + 1)..file.length]
145             attributesText.split(/\s*@/).each {
146                 | attribute |
147                 case attribute.strip
148                 when "no-unify"
149                     @unifiable = false
150                 else
151                     raise "unknown attribute: #{attribute}"
152                 end
153             }
154             file = file[0..(attributeStart-1)]
155         end
156
157         @path = Pathname.new(file.strip)
158     end
159
160     def <=>(other)
161         return @path.dirname <=> other.path.dirname if @path.dirname != other.path.dirname
162         return @path.basename <=> other.path.basename if @fileIndex == other.fileIndex
163         @fileIndex <=> other.fileIndex
164     end
165
166     def derived?
167         return @derived if @derived != nil
168         @derived = !($sourceTreePath + self.path).exist?
169     end
170
171     def to_s
172         if $mode == :GenerateXCFilelists
173             if derived?
174                 ($derivedSourcesPath + @path).to_s
175             else
176                 '$(SRCROOT)/' + @path.to_s
177             end
178         elsif $mode == :GenerateBundles || !derived?
179             @path.to_s
180         else
181             ($derivedSourcesPath + @path).to_s
182         end
183     end
184 end
185
186 class BundleManager
187     attr_reader :bundleCount, :extension, :fileCount, :currentBundleText, :maxCount, :extraFiles
188
189     def initialize(extension, max)
190         @extension = extension
191         @fileCount = 0
192         @bundleCount = 0
193         @currentBundleText = ""
194         @maxCount = max
195         @extraFiles = []
196         @currentDirectory = nil
197         @lastBundlingPrefix = nil
198     end
199
200     def writeFile(file, text)
201         bundleFile = $unifiedSourceOutputPath + file
202         if $mode == :GenerateXCFilelists
203             $outputSources << bundleFile
204             return
205         end
206         if (!bundleFile.exist? || IO::read(bundleFile) != @currentBundleText)
207             log("Writing bundle #{bundleFile} with: \n#{@currentBundleText}")
208             IO::write(bundleFile, @currentBundleText)
209         end
210     end
211
212     def bundleFileName()
213         id =
214             if @maxCount
215                 @bundleCount.to_s
216             else
217                 # The dash makes the filenames more clear when using a hash.
218                 hash = Digest::SHA1.hexdigest(@currentDirectory.to_s)[0..7]
219                 "-#{hash}-#{@bundleCount}"
220             end
221         @extension == "cpp" ? "UnifiedSource#{id}.#{extension}" : "UnifiedSource#{id}-#{extension}.#{extension}"
222     end
223
224     def flush
225         @bundleCount += 1
226         bundleFile = bundleFileName
227         $generatedSources << $unifiedSourceOutputPath + bundleFile
228         @extraFiles << bundleFile if @maxCount and @bundleCount > @maxCount
229
230         writeFile(bundleFile, @currentBundleText)
231         @currentBundleText = ""
232         @fileCount = 0
233     end
234
235     def flushToMax
236         raise if !@maxCount
237         while @bundleCount < @maxCount
238             flush
239         end
240     end
241
242     def addFile(sourceFile)
243         path = sourceFile.path
244         raise "wrong extension: #{path.extname} expected #{@extension}" unless path.extname == ".#{@extension}"
245         bundlePrefix, bundleSize = BundlePrefixAndSizeForPath(path)
246         if (@lastBundlingPrefix != bundlePrefix)
247             log("Flushing because new top level directory; old: #{@currentDirectory}, new: #{path.dirname}")
248             flush
249             @lastBundlingPrefix = bundlePrefix
250             @currentDirectory = path.dirname
251             @bundleCount = 0 unless @maxCount
252         end
253         if @fileCount >= bundleSize
254             log("Flushing because new bundle is full (#{@fileCount} sources)")
255             flush
256         end
257         @currentBundleText += "#include \"#{sourceFile}\"\n"
258         @fileCount += 1
259     end
260 end
261
262 def BundlePrefixAndSizeForPath(path)
263     topLevelDirectory = TopLevelDirectoryForPath(path.dirname)
264     $denseBundleFilters.each { |filter|
265         if path.fnmatch(filter)
266             return filter, MAX_DENSE_BUNDLE_SIZE
267         end
268     }
269     return topLevelDirectory, MAX_BUNDLE_SIZE
270 end
271
272 def TopLevelDirectoryForPath(path)
273     if !path
274         return nil
275     end
276     while path.dirname != path.dirname.dirname
277         path = path.dirname
278     end
279     return path
280 end
281
282 def ProcessFileForUnifiedSourceGeneration(sourceFile)
283     path = sourceFile.path
284     $inputSources << sourceFile.to_s
285
286     bundle = $bundleManagers[path.extname]
287     if !bundle
288         log("No bundle for #{path.extname} files, building #{path} standalone")
289         $generatedSources << sourceFile
290     elsif !sourceFile.unifiable
291         log("Not allowed to unify #{path}, building standalone")
292         $generatedSources << sourceFile
293     else
294         bundle.addFile(sourceFile)
295     end
296 end
297
298 $bundleManagers = {
299     ".cpp" => BundleManager.new("cpp", $maxCppBundleCount),
300     ".mm" => BundleManager.new("mm", $maxObjCBundleCount)
301 }
302
303 seen = {}
304 sourceFiles = []
305
306 sourceListFiles.each_with_index {
307     | path, sourceFileIndex |
308     log("Reading #{path}")
309     result = []
310     File.read(path).lines.each {
311         | line |
312         commentStart = line =~ COMMENT_REGEXP
313         log("Before: #{line}")
314         if commentStart != nil
315             line = line.slice(0, commentStart)
316             log("After: #{line}")
317         end
318         line.strip!
319
320         next if line.empty?
321
322         if seen[line]
323             next if $mode == :GenerateXCFilelists
324             raise "duplicate line: #{line} in #{path}"
325         end
326         seen[line] = true
327         result << SourceFile.new(line, sourceFileIndex)
328     }
329
330     log("Found #{result.length} source files in #{path}")
331     sourceFiles += result
332 }
333
334 log("Found sources: #{sourceFiles.sort}")
335
336 sourceFiles.sort.each {
337     | sourceFile |
338     case $mode
339     when :GenerateBundles, :GenerateXCFilelists
340         ProcessFileForUnifiedSourceGeneration(sourceFile)
341     when :PrintAllSources
342         $generatedSources << sourceFile
343     when :PrintBundledSources
344         $generatedSources << sourceFile if $bundleManagers[sourceFile.path.extname] && sourceFile.unifiable
345     end
346 }
347
348 if $mode != :PrintAllSources
349     $bundleManagers.each_value {
350         | manager |
351         manager.flush
352
353         maxCount = manager.maxCount
354         next if !maxCount
355
356         manager.flushToMax
357
358         unless manager.extraFiles.empty?
359             extension = manager.extension
360             bundleCount = manager.bundleCount
361             filesToAdd = manager.extraFiles.join(", ")
362             raise "number of bundles for #{extension} sources, #{bundleCount}, exceeded limit, #{maxCount}. Please add #{filesToAdd} to Xcode then update UnifiedSource#{extension.capitalize}FileCount"
363         end
364     }
365 end
366
367 if $mode == :GenerateXCFilelists
368     IO::write($inputXCFilelistPath, $inputSources.sort.join("\n") + "\n") if $inputXCFilelistPath
369     IO::write($outputXCFilelistPath, $outputSources.sort.join("\n") + "\n") if $outputXCFilelistPath
370 end
371
372 # We use stdout to report our unified source list to CMake.
373 # Add trailing semicolon and avoid a trailing newline for CMake's sake.
374
375 log($generatedSources.join(";") + ";")
376 print($generatedSources.join(";") + ";")