Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 439 lines (342 sloc) 9.218 kB
affb3f0 @brixen 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 @brixen Abstract /dev/null for Windows (luislavena).
brixen authored
23 DEV_NULL = RUBY_PLATFORM =~ /mingw|mswin/ ? 'NUL' : '/dev/null'
24
affb3f0 @brixen 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 @brixen Fixed rebuilding melbourne from dependencies.
brixen authored
91 def initialize(name, parser, objects_dir=nil)
affb3f0 @brixen Added DependencyGrapher.
brixen authored
92 super parser
93 @name = name
94 @object_name = name.sub(/((c(pp)?)|S)$/, 'o')
fc85189 @brixen Fixed rebuilding melbourne from dependencies.
brixen authored
95 @objects_dir = objects_dir
affb3f0 @brixen 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 @brixen Fixed rebuilding melbourne from dependencies.
brixen authored
114 str = "#{@objects_dir}/#{str}" if @objects_dir
affb3f0 @brixen Added DependencyGrapher.
brixen authored
115 out.print str
116
117 width = str.size
118 @dependencies.each do |name|
179f7ea @brixen Omit drive letter on Windows for dependencies.
brixen authored
119 # Omit drive letter on Windows.
120 name = name[2..-1] if name[1] == ?:
9527512 @brixen Emit paths without drive letters in dependency grapher.
brixen authored
121
affb3f0 @brixen 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 @brixen 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 @brixen 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 @brixen 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 @brixen Fixed rebuilding melbourne from dependencies.
brixen authored
395 attr_accessor :file_names, :directories, :defines, :system_defines, :objects_dir
affb3f0 @brixen 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 @brixen Fixed rebuilding melbourne from dependencies.
brixen authored
403 @objects_dir = nil
affb3f0 @brixen Added DependencyGrapher.
brixen authored
404 @system_defines = {}
405 @sources = []
406 end
407
408 def get_system_defines
32cf036 @brixen Abstract /dev/null for Windows (luislavena).
brixen authored
409 lines = `cpp -dM #{@defines} #{DEV_NULL}`.split("\n")
affb3f0 @brixen 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 @brixen Fixed rebuilding melbourne from dependencies.
brixen authored
422 source = SourceFile.new name, self, @objects_dir
affb3f0 @brixen 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.