Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 439 lines (342 sloc) 9.218 kb
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
1 # A simple processor to create a list of dependencies for a given source code
2 # file based on the #include files in the source file.
3 #
4 # The process involves three phases:
5 #
6 # 1. Parsing the source file for all C/C++ preprocessor control directives
7 # (#include, #ifdef, #ifndef, #else, #endif, #define, #undef, #if, #elif)
8 # 2. Executing the tree derived from step 1 in the context of a set of
9 # macro definitions.
10 # 3. Collecting the dependency list for each source file.
11 #
12 # In step 1, the tree resulting from parsing each included file is cached. If
13 # a later file of the same expanded relative path is included, the parse tree
14 # from the cache is returned.
15 #
16 # The system level macro definitions are retrieved using `cpp -dM`. This
17 # system set may be updated with new definitions or removed definitions while
18 # the parse tree is executed.
19
20 require 'set'
21
22 class DependencyGrapher
32cf036 Brian Shirai Abstract /dev/null for Windows (luislavena).
brixen authored
23 DEV_NULL = RUBY_PLATFORM =~ /mingw|mswin/ ? 'NUL' : '/dev/null'
24
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
25 class ExpressionEvaluator
26 def initialize(expression)
27 @expression = expression
28 end
29
30 # TODO: when melbourne is running in MRI, use it to parse
31 # the expression and then evaluate it.
32 def evaluate(defines)
33 @expression == "0" ? false : true
34 end
35 end
36
37 class Node
38 def initialize(parser)
39 @parser = parser
40 end
41
42 def close
43 message = "unbalanced \#endif for #{@parser.stack_top.class} at line #{@parser.line}"
44 raise ParseError, message
45 end
46
47 def add_else
48 message = "invalid \#else for #{@parser.stack_top.class} at line #{@parser.line}"
49 end
50
51 # TODO: remove
52 def execute(defines, node)
53 puts "#execute not implemented for #{self.class}"
54 end
55 end
56
57 module Container
58 attr_accessor :body
59
60 def initialize(parser)
61 super parser
62 @body = []
63 end
64
65 def close
66 @parser.stack_pop
67 end
68
69 def execute_body(defines, node)
70 body.each { |x| x.execute(defines, node) }
71 end
72 end
73
74 module Conditional
75 def initialize(parser)
76 super parser
77 @else = nil
78 end
79
80 def add_else(node)
81 @else = node
82 end
83 end
84
85
86 class SourceFile < Node
87 include Container
88
89 attr_reader :name, :object_name, :includes, :dependencies
90
fc85189 Brian Shirai Fixed rebuilding melbourne from dependencies.
brixen authored
91 def initialize(name, parser, objects_dir=nil)
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
92 super parser
93 @name = name
94 @object_name = name.sub(/((c(pp)?)|S)$/, 'o')
fc85189 Brian Shirai Fixed rebuilding melbourne from dependencies.
brixen authored
95 @objects_dir = objects_dir
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
96 @includes = []
97 end
98
99 def execute(defines)
100 execute_body defines, self
101 end
102
103 def collect_dependencies
104 set = Set.new
105
106 set << @name
107 @includes.each { |x| x.collect_dependencies(set) }
108
109 @dependencies = set.to_a.sort
110 end
111
112 def print_dependencies(out, max)
113 str = "#{@object_name}:"
fc85189 Brian Shirai Fixed rebuilding melbourne from dependencies.
brixen authored
114 str = "#{@objects_dir}/#{str}" if @objects_dir
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
115 out.print str
116
117 width = str.size
118 @dependencies.each do |name|
179f7ea Brian Shirai Omit drive letter on Windows for dependencies.
brixen authored
119 # Omit drive letter on Windows.
120 name = name[2..-1] if name[1] == ?:
9527512 Brian Shirai Emit paths without drive letters in dependency grapher.
brixen authored
121
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
122 size = name.size + 1
123 if width + size > max
124 width = 0
125 out.print " \\\n "
126 end
127
128 out.print " ", name
129 width += size
130 end
131
132 out.print "\n"
133 end
134 end
135
136 class IncludedFile < Node
137 include Container
138
139 attr_reader :name, :includes
140
141 def self.cache
142 @cache ||= {}
143 end
144
145 def initialize(name, parser)
146 super parser
147 @name = name
148 @includes = []
149 end
150
4e59bdd Report the file has a bad include
Evan Phoenix authored
151 def expand_filename(node)
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
152 return if File.exist? @name
153
154 @parser.directories.each do |dir|
155 path = File.join dir, @name
156 return @name = path if File.file? path
157 end
158
4e59bdd Report the file has a bad include
Evan Phoenix authored
159 raise Errno::ENOENT, "unable to find file to include: #{@name} from #{node.name}"
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
160 end
161
162 def execute(defines, node)
4e59bdd Report the file has a bad include
Evan Phoenix authored
163 expand_filename(node)
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
164
165 if cached = self.class.cache[@name.to_sym]
166 @body = cached.body
167 else
168 parser = FileParser.new self, @parser.directories
169 parser.parse_file @name
170 self.class.cache[@name.to_sym] = self
171 end
172
173 execute_body defines, self
174
175 node.includes << self
176 end
177
178 def collect_dependencies(set)
179 return if set.include? @name
180
181 set << @name
182 @includes.each { |x| x.collect_dependencies(set) }
183 end
184 end
185
186 class IfDefined < Node
187 include Conditional, Container
188
189 def initialize(macro, parser)
190 super parser
191 @macro = macro.strip
192 end
193
194 def execute(defines, node)
195 if defines.key? @macro
196 execute_body defines, node
197 elsif @else
198 @else.execute(defines, node)
199 end
200 end
201 end
202
203 class IfNotDefined < Node
204 include Conditional, Container
205
206 def initialize(macro, parser)
207 super parser
208 @macro = macro.strip
209 end
210
211 def execute(defines, node)
212 if !defines.key? @macro
213 execute_body defines, node
214 elsif @else
215 @else.execute(defines, node)
216 end
217 end
218 end
219
220 class If < Node
221 include Conditional, Container
222
223 def initialize(expression, parser)
224 super parser
225 @value = nil
226 @expression = expression.strip
227 end
228
229 def execute(defines, node)
230 @value = ExpressionEvaluator.new(@expression).evaluate defines
231
232 if @value
233 execute_body(defines, node)
234 elsif @else
235 @else.execute(defines, node)
236 end
237 end
238 end
239
240 class Else < Node
241 include Container
242
243 def execute(defines, node)
244 execute_body defines, node
245 end
246 end
247
248 class ElseIf < If; end
249
250 class Define < Node
251 def initialize(macro, parser)
252 super parser
253
254 macro.strip!
255 if index = macro.index(" ")
256 @name = macro[0..index-1]
257 @value = macro[index+1..-1]
258 @name, @value = macro.strip.split
259 else
260 @name = macro
261 @value = "1"
262 end
263 end
264
265 def execute(defines, node)
266 defines[@name] = @value
267 end
268 end
269
270 class Undefine < Node
271 def initialize(macro, parser)
272 super parser
273 @macro = macro.strip
274 end
275
276 def execute(defines, node)
277 defines.delete @macro
278 end
279 end
280
281 class ParseError < Exception; end
282
283 # Parses a file for all preprocessor control directives into a tree of Node
284 # objects. The parser can operate on a file or an array of lines.
285 class FileParser
286 attr_reader :line, :directories
287
288 def initialize(root, directories)
289 @stack = [root]
290 @directories = directories
291 end
292
293 # Parser operations
294
295 def add_body(node)
296 @stack.last.body << node
297 end
298
299 def add_else(node)
300 @stack.last.add_else node
301 end
302
303 def stack_push(node)
304 @stack.push node
305 end
306
307 def stack_pop
308 @stack.pop
309 end
310
311 def stack_top
312 @stack.last
313 end
314
315 def close
316 @stack.last.close
317 end
318
319 # Events
320
321 def process_include(name)
322 # We do not process any <files>. This could be enabled as
323 # an option, but we don't need it or want it now.
324 name =~ /\s*"([^"]+)".*$/
325 return unless $1
326
327 node = IncludedFile.new $1, self
328 add_body node
329 end
330
331 def process_ifdef(macro)
332 node = IfDefined.new macro, self
333 add_body node
334 stack_push node
335 end
336
337 def process_ifndef(macro)
338 node = IfNotDefined.new macro, self
339 add_body node
340 stack_push node
341 end
342
343 def process_endif(rest)
344 close
345 end
346
347 def process_else(rest)
348 node = Else.new self
349 add_else node
350 end
351
352 def process_define(macro)
353 node = Define.new macro, self
354 add_body node
355 end
356
357 def process_undef(macro)
358 node = Undefine.new macro, self
359 add_body node
360 end
361
362 def process_if(expression)
363 node = If.new expression, self
364 add_body node
365 stack_push node
366 end
367
368 def process_elif(expression)
369 node = ElseIf.new expression, self
370 add_else node
371 add_body node
372 stack_push node
373 end
374
375 # Parse methods
376
377 def parse_file(name)
378 parse IO.readlines(name)
379 end
380
381 def parse(lines)
382 @line = 0
383
384 lines.each do |line|
385 @line += 1
386 m = line.match(/^\s*#(include|ifdef|ifndef|endif|else|define|undef|if|elif)(.*)$/)
387
388 # TODO: continue reading if line ends in \
389
390 send :"process_#{m[1]}", m[2] if m
391 end
392 end
393 end
394
fc85189 Brian Shirai Fixed rebuilding melbourne from dependencies.
brixen authored
395 attr_accessor :file_names, :directories, :defines, :system_defines, :objects_dir
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
396 attr_reader :sources
397
398 def initialize(files, directories=[], defines=nil)
399 @file_names = files
400 @directories = directories
401 @defines = defines
402
fc85189 Brian Shirai Fixed rebuilding melbourne from dependencies.
brixen authored
403 @objects_dir = nil
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
404 @system_defines = {}
405 @sources = []
406 end
407
408 def get_system_defines
32cf036 Brian Shirai Abstract /dev/null for Windows (luislavena).
brixen authored
409 lines = `cpp -dM #{@defines} #{DEV_NULL}`.split("\n")
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
410
411 source = SourceFile.new "sytem_defines", self
412 parser = FileParser.new source, @directories
413 parser.parse lines
414
415 source.execute @system_defines
416 end
417
418 def process
419 get_system_defines
420
421 @file_names.each do |name|
fc85189 Brian Shirai Fixed rebuilding melbourne from dependencies.
brixen authored
422 source = SourceFile.new name, self, @objects_dir
affb3f0 Brian Shirai Added DependencyGrapher.
brixen authored
423 parser = FileParser.new source, @directories
424
425 parser.parse_file name
426 source.execute @system_defines.dup
427 source.collect_dependencies
428
429 @sources << source
430 end
431 end
432
433 def print_dependencies(out, max=72)
434 @sources.each do |source|
435 source.print_dependencies out, max
436 end
437 end
438 end
Something went wrong with that request. Please try again.