1 # Copyright (C) 2017 Apple Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
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.
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.
28 SCRIPT_NAME = File.basename($0)
29 COMMENT_REGEXP = /\/\//
32 puts "usage: #{SCRIPT_NAME} [options] <sources-list-file>..."
33 puts "<sources-list-file> may be separate arguments or one semicolon separated string"
34 puts "--help (-h) Print this message"
35 puts "--verbose (-v) Adds extra logging to stderr."
36 puts "Required arguments:"
37 puts "--source-tree-path (-s) Path to the root of the source directory."
38 puts "--derived-sources-path (-d) Path to the directory where the unified source files should be placed."
40 puts "Optional arguments:"
41 puts "--print-bundled-sources Print bundled sources rather than generating sources"
42 puts "--feature-flags (-f) Space or semicolon separated list of enabled feature flags"
44 puts "Generation options:"
45 puts "--max-cpp-bundle-count Sets the limit on the number of cpp bundles that can be generated"
46 puts "--max-obj-c-bundle-count Sets the limit on the number of Obj-C bundles that can be generated"
51 $derivedSourcesPath = nil
52 $unifiedSourceOutputPath = nil
56 $mode = :GenerateBundles
57 $maxCppBundleCount = nil
58 $maxObjCBundleCount = nil
61 $stderr.puts text if $verbose
64 GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT],
65 ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
66 ['--derived-sources-path', '-d', GetoptLong::REQUIRED_ARGUMENT],
67 ['--source-tree-path', '-s', GetoptLong::REQUIRED_ARGUMENT],
68 ['--feature-flags', '-f', GetoptLong::REQUIRED_ARGUMENT],
69 ['--print-bundled-sources', GetoptLong::NO_ARGUMENT],
70 ['--max-cpp-bundle-count', GetoptLong::REQUIRED_ARGUMENT],
71 ['--max-obj-c-bundle-count', GetoptLong::REQUIRED_ARGUMENT]).each {
78 when '--derived-sources-path'
79 $derivedSourcesPath = Pathname.new(arg)
80 $unifiedSourceOutputPath = $derivedSourcesPath + Pathname.new("unified-sources")
81 FileUtils.mkpath($unifiedSourceOutputPath) if !$unifiedSourceOutputPath.exist?
82 when '--source-tree-path'
83 $sourceTreePath = Pathname.new(arg)
84 usage if !$sourceTreePath.exist?
85 when '--feature-flags'
86 arg.gsub(/\s+/, ";").split(";").map { |x| $featureFlags[x] = true }
87 when '--print-bundled-sources'
88 $mode = :PrintBundledSources
89 when '--max-cpp-bundle-count'
90 $maxCppBundleCount = arg.to_i
91 when '--max-obj-c-bundle-count'
92 $maxObjCBundleCount = arg.to_i
96 usage if !$unifiedSourceOutputPath || !$sourceTreePath
97 log("putting unified sources in #{$unifiedSourceOutputPath}")
98 log("Active Feature flags: #{$featureFlags.keys.inspect}")
100 usage if ARGV.length == 0
101 # Even though CMake will only pass us a single semicolon separated arguemnts, we separate all the arguments for simplicity.
102 sourceListFiles = ARGV.to_a.map { | sourceFileList | sourceFileList.split(";") }.flatten
103 log("source files: #{sourceListFiles}")
104 $generatedSources = []
107 attr_reader :unifiable, :fileIndex, :path
108 def initialize(file, fileIndex)
110 @fileIndex = fileIndex
112 attributeStart = file =~ /@/
114 # We want to make sure we skip the first @ so split works correctly
115 attributesText = file[(attributeStart + 1)..file.length]
116 attributesText.split(/\s*@/).each {
122 raise "unknown attribute: #{attribute}"
125 file = file[0..(attributeStart-1)]
128 @path = Pathname.new(file.strip)
132 return @path.dirname <=> other.path.dirname if @path.dirname != other.path.dirname
133 return @path.basename <=> other.path.basename if @fileIndex == other.fileIndex
134 @fileIndex <=> other.fileIndex
138 return @derived if @derived != nil
139 @derived = !($sourceTreePath + self.path).exist?
143 if $mode == :GenerateBundles || !derived?
146 ($derivedSourcesPath + @path).to_s
152 attr_reader :bundleCount, :extension, :fileCount, :currentBundleText, :maxCount
154 def initialize(extension, max)
155 @extension = extension
158 @currentBundleText = ""
162 def writeFile(file, text)
163 bundleFile = $unifiedSourceOutputPath + file
164 if (!bundleFile.exist? || IO::read(bundleFile) != @currentBundleText)
165 log("writing bundle #{bundleFile} with: \n#{@currentBundleText}")
166 IO::write(bundleFile, @currentBundleText)
170 def bundleFileName(number)
171 "UnifiedSource#{number}.#{extension}"
175 # No point in writing an empty bundle file
176 return if @currentBundleText == ""
179 bundleFile = bundleFileName(@bundleCount)
180 $generatedSources << $unifiedSourceOutputPath + bundleFile
182 writeFile(bundleFile, @currentBundleText)
183 @currentBundleText = ""
189 ((@bundleCount+1)..@maxCount).each {
191 writeFile(bundleFileName(index), "")
195 def addFile(sourceFile)
196 path = sourceFile.path
197 raise "wrong extension: #{path.extname} expected #{@extension}" unless path.extname == ".#{@extension}"
198 if @fileCount == MAX_BUNDLE_SIZE
199 log("flushing because new bundle is full #{@fileCount}")
202 @currentBundleText += "#include \"#{sourceFile}\"\n"
207 def ProcessFileForUnifiedSourceGeneration(sourceFile)
208 path = sourceFile.path
209 if ($currentDirectory != path.dirname)
210 log("flushing because new dirname old: #{$currentDirectory}, new: #{path.dirname}")
211 $bundleManagers.each_value { |x| x.flush }
212 $currentDirectory = path.dirname
215 bundle = $bundleManagers[path.extname]
216 if !bundle || !sourceFile.unifiable
217 log("No bundle for #{path.extname} files building #{path} standalone")
218 $generatedSources << sourceFile
220 bundle.addFile(sourceFile)
225 ".cpp" => BundleManager.new("cpp", $maxCppBundleCount),
226 ".mm" => BundleManager.new("mm", $maxObjCBundleCount)
232 sourceListFiles.each_with_index {
233 | path, sourceFileIndex |
234 log("reading #{path}")
236 inDisabledLines = false
237 File.read(path).lines.each {
239 commentStart = line =~ COMMENT_REGEXP
240 log("before: #{line}")
241 if commentStart != nil
242 line = line.slice(0, commentStart)
243 log("after: #{line}")
247 inDisabledLines = false
251 next if line.empty? || inDisabledLines
254 raise "malformed #if" unless line =~ /\A#if\s+(\S+)/
255 inDisabledLines = !$featureFlags[$1]
257 raise "duplicate line: #{line} in #{path}" if seen[line]
259 result << SourceFile.new(line, sourceFileIndex)
262 raise "Couldn't find closing \"#endif\"" if inDisabledLines
264 log("found #{result.length} source files in #{path}")
265 sourceFiles += result
268 log("Found sources: #{sourceFiles.sort}")
270 sourceFiles.sort.each {
273 when :GenerateBundles
274 ProcessFileForUnifiedSourceGeneration(sourceFile)
275 when :PrintBundledSources
276 $generatedSources << sourceFile if $bundleManagers[sourceFile.path.extname] && sourceFile.unifiable
280 $bundleManagers.each_value {
284 maxCount = manager.maxCount
288 bundleCount = manager.bundleCount
289 extension = manager.extension
290 if bundleCount > maxCount
291 filesToAdd = ((maxCount+1)..bundleCount).map { |x| manager.bundleFileName(x) }.join(", ")
292 raise "number of bundles for #{extension} sources, #{bundleCount}, exceeded limit, #{maxCount}. Please add #{filesToAdd} to Xcode then update UnifiedSource#{extension.capitalize}FileCount"
296 # We use stdout to report our unified source list to CMake.
297 # Add trailing semicolon since CMake seems dislikes not having it.
298 # Also, make sure we use print instead of puts because CMake will think the \n is a source file and fail to build.
300 log($generatedSources.join(";") + ";")
301 print($generatedSources.join(";") + ";")