b8a8e2fe7bf6da338a8f17881691b2abd3bcfd41
[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 'fileutils'
25 require 'pathname'
26 require 'getoptlong'
27
28 SCRIPT_NAME = File.basename($0)
29 COMMENT_REGEXP = /#/
30
31 def usage
32     puts "usage: #{SCRIPT_NAME} [options] <sources-file>"
33     puts "--help                          (-h) Print this message"
34     puts "--verbose                       (-v) Adds extra logging to stderr."
35     puts "Required arguments:"
36     puts "--source-tree-path              (-s) Path to the root of the source directory."
37     puts "--derived-sources-path          (-d) Path to the directory where the unified source files should be placed."
38     puts
39     puts "Optional arguments:"
40     puts "--print-bundled-sources              Print bundled sources rather than generating sources"
41     puts
42     puts "Generation options:"
43     puts "--max-cpp-bundle-count               Sets the limit on the number of cpp bundles that can be generated"
44     puts "--max-obj-c-bundle-count             Sets the limit on the number of Obj-C bundles that can be generated"
45     exit 1
46 end
47
48 MAX_BUNDLE_SIZE = 8
49 $derivedSourcesPath = nil
50 $unifiedSourceOutputPath = nil
51 $sourceTreePath = nil
52 $verbose = false
53 $mode = :GenerateBundles
54 $maxCppBundleCount = 100000
55 $maxObjCBundleCount = 100000
56
57 def log(text)
58     $stderr.puts text if $verbose
59 end
60
61 GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT],
62                ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
63                ['--derived-sources-path', '-d', GetoptLong::REQUIRED_ARGUMENT],
64                ['--source-tree-path', '-s', GetoptLong::REQUIRED_ARGUMENT],
65                ['--print-bundled-sources', GetoptLong::NO_ARGUMENT],
66                ['--max-cpp-bundle-count', GetoptLong::REQUIRED_ARGUMENT],
67                ['--max-obj-c-bundle-count', GetoptLong::REQUIRED_ARGUMENT]).each {
68     | opt, arg |
69     case opt
70     when '--help'
71         usage
72     when '--verbose'
73         $verbose = true
74     when '--derived-sources-path'
75         $derivedSourcesPath = Pathname.new(arg)
76         $unifiedSourceOutputPath = $derivedSourcesPath + Pathname.new("unified-sources")
77         FileUtils.mkdir($unifiedSourceOutputPath) if !$unifiedSourceOutputPath.exist?
78     when '--source-tree-path'
79         $sourceTreePath = Pathname.new(arg)
80         usage if !$sourceTreePath.exist?
81     when '--print-bundled-sources'
82         $mode = :PrintBundledSources
83     when '--max-cpp-bundle-count'
84         $maxCppBundleCount = arg.to_i
85     when '--max-obj-c-bundle-count'
86         $maxObjCBundleCount = arg.to_i
87     end
88 }
89
90 usage if !$unifiedSourceOutputPath || !$sourceTreePath
91 log("putting unified sources in #{$unifiedSourceOutputPath}")
92
93 usage if ARGV.length == 0
94 $generatedSources = []
95
96 class SourceFile < Pathname
97     attr_reader :unifiable
98     def initialize(file)
99         @unifiable = true
100
101         attributeStart = file =~ COMMENT_REGEXP
102         if attributeStart
103             # attributes start with @ so we want skip the comment character and the first @.
104             attributesText = file[(attributeStart + 2)..file.length]
105             attributesText.split(/\s*@/).each {
106                 | attribute |
107                 case attribute
108                 when "no-unify"
109                     @unifiable = false
110                 end
111             }
112             file = file.split(" ")[0]
113         end
114
115         super(file)
116     end
117
118     def derived?
119         return @derived if @derived != nil
120         @derived = !($sourceTreePath + self).exist?
121     end
122
123     def display
124         if $mode == :GenerateBundles || !derived?
125             self.to_s
126         else
127             ($derivedSourcesPath + self).to_s
128         end
129     end
130 end
131
132 class BundleManager
133     attr_reader :bundleCount, :extension, :fileCount, :currentBundleText, :maxCount
134
135     def initialize(extension, max)
136         @extension = extension
137         @fileCount = 0
138         @bundleCount = 0
139         @currentBundleText = ""
140         @maxCount = max
141     end
142
143     def bundleFileName(number)
144         "UnifiedSource#{number}.#{extension}"
145     end
146
147     def flush
148         # No point in writing an empty bundle file
149         return if @currentBundleText == ""
150
151         @bundleCount += 1
152         bundleFile = $unifiedSourceOutputPath + bundleFileName(@bundleCount)
153         $generatedSources << bundleFile
154
155         if (!bundleFile.exist? || IO::read(bundleFile) != @currentBundleText)
156             log("writing bundle #{bundleFile} with: \n#{@currentBundleText}")
157             IO::write(bundleFile, @currentBundleText)
158         end
159
160         @currentBundleText = ""
161         @fileCount = 0
162     end
163
164     def addFile(file)
165         raise "wrong extension: #{file.extname} expected #{@extension}" unless file.extname == ".#{@extension}"
166         if @fileCount == MAX_BUNDLE_SIZE
167             log("flushing because new bundle is full #{@fileCount}")
168             flush
169         end
170         @currentBundleText += "#include \"#{file}\"\n"
171         @fileCount += 1
172     end
173 end
174
175 def ProcessFileForUnifiedSourceGeneration(path)
176     if ($currentDirectory != path.dirname)
177         log("flushing because new dirname old: #{$currentDirectory}, new: #{path.dirname}")
178         $bundleManagers.each_value { |x| x.flush }
179         $currentDirectory = path.dirname
180     end
181
182     bundle = $bundleManagers[path.extname]
183     if !bundle || !path.unifiable
184         log("No bundle for #{path.extname} files building #{path} standalone")
185         $generatedSources << path
186     else
187         bundle.addFile(path)
188     end
189 end
190
191 $bundleManagers = {
192     ".cpp" => BundleManager.new("cpp", $maxCppBundleCount),
193     ".mm" => BundleManager.new("mm", $maxObjCBundleCount)
194 }
195
196 ARGV.each {
197     | sourcesFile |
198     log("reading #{sourcesFile}")
199     sources = File.read(sourcesFile).split($/).keep_if {
200         | line |
201         # Only strip lines if they start with a comment since sources we don't
202         # want to bundle have an attribute, which starts with a comment.
203         !((line =~ COMMENT_REGEXP) == 0 || line.empty?)
204     }
205
206     log("found #{sources.length} source files in #{sourcesFile}")
207
208     sources.sort.each {
209         | file |
210
211         path = SourceFile.new(file)
212         case $mode
213         when :GenerateBundles
214             ProcessFileForUnifiedSourceGeneration(path)
215         when :PrintBundledSources
216             $generatedSources << path if $bundleManagers[path.extname] && path.unifiable
217         end
218     }
219
220     $bundleManagers.each_value { |x| x.flush } if $mode == :GenerateBundles
221 }
222
223 $bundleManagers.each_value {
224     | manager |
225
226     maxCount = manager.maxCount
227     bundleCount = manager.bundleCount
228     extension = manager.extension
229     if bundleCount > maxCount
230         filesToAdd = ((maxCount+1)..bundleCount).map { |x| manager.bundleFileName(x) }.join(", ")
231         raise "number of bundles for #{extension} sources, #{bundleCount}, exceeded limit, #{maxCount}. Please add #{filesToAdd} to Xcode then update UnifiedSource#{extension.capitalize}FileCount"
232     end
233 }
234
235 # We use stdout to report our unified source list to CMake.
236 # Add trailing semicolon since CMake seems dislikes not having it.
237 # Also, make sure we use print instead of puts because CMake will think the \n is a source file and fail to build.
238
239 $generatedSources.map! { |path| path.display } if $mode == :PrintBundledSources
240 print($generatedSources.join(";") + ";")