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)
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."
39 puts "Optional arguments:"
40 puts "--print-bundled-sources Print bundled sources rather than generating sources"
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"
49 $derivedSourcesPath = nil
50 $unifiedSourceOutputPath = nil
53 $mode = :GenerateBundles
54 $maxCppBundleCount = 100000
55 $maxObjCBundleCount = 100000
58 $stderr.puts text if $verbose
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 {
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
90 usage if !$unifiedSourceOutputPath || !$sourceTreePath
91 log("putting unified sources in #{$unifiedSourceOutputPath}")
93 usage if ARGV.length == 0
94 $generatedSources = []
96 class SourceFile < Pathname
97 attr_reader :unifiable
101 attributeStart = file =~ COMMENT_REGEXP
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 {
112 file = file.split(" ")[0]
119 return @derived if @derived != nil
120 @derived = !($sourceTreePath + self).exist?
124 if $mode == :GenerateBundles || !derived?
127 ($derivedSourcesPath + self).to_s
133 attr_reader :bundleCount, :extension, :fileCount, :currentBundleText, :maxCount
135 def initialize(extension, max)
136 @extension = extension
139 @currentBundleText = ""
143 def bundleFileName(number)
144 "UnifiedSource#{number}.#{extension}"
148 # No point in writing an empty bundle file
149 return if @currentBundleText == ""
152 bundleFile = $unifiedSourceOutputPath + bundleFileName(@bundleCount)
153 $generatedSources << bundleFile
155 if (!bundleFile.exist? || IO::read(bundleFile) != @currentBundleText)
156 log("writing bundle #{bundleFile} with: \n#{@currentBundleText}")
157 IO::write(bundleFile, @currentBundleText)
160 @currentBundleText = ""
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}")
170 @currentBundleText += "#include \"#{file}\"\n"
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
182 bundle = $bundleManagers[path.extname]
183 if !bundle || !path.unifiable
184 log("No bundle for #{path.extname} files building #{path} standalone")
185 $generatedSources << path
192 ".cpp" => BundleManager.new("cpp", $maxCppBundleCount),
193 ".mm" => BundleManager.new("mm", $maxObjCBundleCount)
198 log("reading #{sourcesFile}")
199 sources = File.read(sourcesFile).split($/).keep_if {
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?)
206 log("found #{sources.length} source files in #{sourcesFile}")
211 path = SourceFile.new(file)
213 when :GenerateBundles
214 ProcessFileForUnifiedSourceGeneration(path)
215 when :PrintBundledSources
216 $generatedSources << path if $bundleManagers[path.extname] && path.unifiable
220 $bundleManagers.each_value { |x| x.flush } if $mode == :GenerateBundles
223 $bundleManagers.each_value {
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"
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.
239 $generatedSources.map! { |path| path.display } if $mode == :PrintBundledSources
240 print($generatedSources.join(";") + ";")