daedalus-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE +22 -0
- data/README.md +13 -0
- data/daedalus-core.gemspec +28 -0
- data/lib/daedalus.rb +1016 -0
- data/lib/daedalus/dependency_grapher.rb +448 -0
- data/lib/daedalus/version.rb +3 -0
- metadata +86 -0
@@ -0,0 +1,448 @@
|
|
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
|
+
module Daedalus
|
23
|
+
class DependencyGrapher
|
24
|
+
DEV_NULL = RUBY_PLATFORM =~ /mingw|mswin/ ? 'NUL' : '/dev/null'
|
25
|
+
|
26
|
+
class ExpressionEvaluator
|
27
|
+
def initialize(expression)
|
28
|
+
@expression = expression
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: when melbourne is running in MRI, use it to parse
|
32
|
+
# the expression and then evaluate it.
|
33
|
+
def evaluate(defines)
|
34
|
+
@expression == "0" ? false : true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Node
|
39
|
+
def initialize(parser)
|
40
|
+
@parser = parser
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
message = "unbalanced \#endif for #{@parser.stack_top.class} at line #{@parser.line}"
|
45
|
+
raise ParseError, message
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_else
|
49
|
+
"invalid \#else for #{@parser.stack_top.class} at line #{@parser.line}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO: remove
|
53
|
+
def execute(defines, node)
|
54
|
+
puts "#execute not implemented for #{self.class}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module Container
|
59
|
+
attr_accessor :body
|
60
|
+
|
61
|
+
def initialize(parser)
|
62
|
+
super parser
|
63
|
+
@body = []
|
64
|
+
end
|
65
|
+
|
66
|
+
def close
|
67
|
+
@parser.stack_pop
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute_body(defines, node)
|
71
|
+
body.each { |x| x.execute(defines, node) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module Conditional
|
76
|
+
def initialize(parser)
|
77
|
+
super parser
|
78
|
+
@else = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_else(node)
|
82
|
+
@else = node
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
class SourceFile < Node
|
88
|
+
include Container
|
89
|
+
|
90
|
+
attr_reader :name, :object_name, :includes, :dependencies
|
91
|
+
|
92
|
+
def initialize(name, parser)
|
93
|
+
super parser
|
94
|
+
@name = name
|
95
|
+
@object_name = name.sub(/((c(pp)?)|S)$/, 'o')
|
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}:"
|
114
|
+
out.print str
|
115
|
+
|
116
|
+
width = str.size
|
117
|
+
@dependencies.each do |name|
|
118
|
+
size = name.size + 1
|
119
|
+
if width + size > max
|
120
|
+
width = 0
|
121
|
+
out.print " \\\n "
|
122
|
+
end
|
123
|
+
|
124
|
+
out.print " ", name
|
125
|
+
width += size
|
126
|
+
end
|
127
|
+
|
128
|
+
out.print "\n"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class IncludedFile < Node
|
133
|
+
include Container
|
134
|
+
|
135
|
+
attr_reader :name, :includes
|
136
|
+
|
137
|
+
def self.cache
|
138
|
+
@cache ||= {}
|
139
|
+
end
|
140
|
+
|
141
|
+
def initialize(name, parser)
|
142
|
+
super parser
|
143
|
+
@name = name
|
144
|
+
@includes = []
|
145
|
+
end
|
146
|
+
|
147
|
+
def expand_filename(node)
|
148
|
+
return if File.exist? @name
|
149
|
+
|
150
|
+
@parser.directories.each do |dir|
|
151
|
+
path = File.join dir, @name
|
152
|
+
return @name = path if File.file? path
|
153
|
+
end
|
154
|
+
|
155
|
+
# Try to find the file in the same directory as where we're looking from
|
156
|
+
dir = File.dirname(node.name)
|
157
|
+
path = File.join dir, @name
|
158
|
+
|
159
|
+
return @name = path if File.file?(path)
|
160
|
+
|
161
|
+
raise Errno::ENOENT, "unable to find file to include: #{@name} from #{node.name}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def execute(defines, node)
|
165
|
+
expand_filename(node)
|
166
|
+
|
167
|
+
if cached = self.class.cache[@name.to_sym]
|
168
|
+
@body = cached.body
|
169
|
+
else
|
170
|
+
parser = FileParser.new self, @parser.directories
|
171
|
+
parser.parse_file @name
|
172
|
+
self.class.cache[@name.to_sym] = self
|
173
|
+
end
|
174
|
+
|
175
|
+
execute_body defines, self
|
176
|
+
|
177
|
+
node.includes << self
|
178
|
+
end
|
179
|
+
|
180
|
+
def collect_dependencies(set)
|
181
|
+
return if set.include? @name
|
182
|
+
|
183
|
+
set << @name
|
184
|
+
@includes.each { |x| x.collect_dependencies(set) }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class IfDefined < Node
|
189
|
+
include Conditional, Container
|
190
|
+
|
191
|
+
def initialize(macro, parser)
|
192
|
+
super parser
|
193
|
+
@macro = macro.strip
|
194
|
+
end
|
195
|
+
|
196
|
+
def execute(defines, node)
|
197
|
+
if defines.key? @macro
|
198
|
+
execute_body defines, node
|
199
|
+
elsif @else
|
200
|
+
@else.execute(defines, node)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class IfNotDefined < Node
|
206
|
+
include Conditional, Container
|
207
|
+
|
208
|
+
def initialize(macro, parser)
|
209
|
+
super parser
|
210
|
+
@macro = macro.strip
|
211
|
+
end
|
212
|
+
|
213
|
+
def execute(defines, node)
|
214
|
+
if !defines.key? @macro
|
215
|
+
execute_body defines, node
|
216
|
+
elsif @else
|
217
|
+
@else.execute(defines, node)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class If < Node
|
223
|
+
include Conditional, Container
|
224
|
+
|
225
|
+
def initialize(expression, parser)
|
226
|
+
super parser
|
227
|
+
@value = nil
|
228
|
+
@expression = expression.strip
|
229
|
+
end
|
230
|
+
|
231
|
+
def execute(defines, node)
|
232
|
+
@value = ExpressionEvaluator.new(@expression).evaluate defines
|
233
|
+
|
234
|
+
if @value
|
235
|
+
execute_body(defines, node)
|
236
|
+
elsif @else
|
237
|
+
@else.execute(defines, node)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
class Else < Node
|
243
|
+
include Container
|
244
|
+
|
245
|
+
def execute(defines, node)
|
246
|
+
execute_body defines, node
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
class ElseIf < If; end
|
251
|
+
|
252
|
+
class Define < Node
|
253
|
+
def initialize(macro, parser)
|
254
|
+
super parser
|
255
|
+
|
256
|
+
macro.strip!
|
257
|
+
if index = macro.index(" ")
|
258
|
+
@name = macro[0..index-1]
|
259
|
+
@value = macro[index+1..-1]
|
260
|
+
@name, @value = macro.strip.split
|
261
|
+
else
|
262
|
+
@name = macro
|
263
|
+
@value = "1"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def execute(defines, node)
|
268
|
+
defines[@name] = @value
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class Undefine < Node
|
273
|
+
def initialize(macro, parser)
|
274
|
+
super parser
|
275
|
+
@macro = macro.strip
|
276
|
+
end
|
277
|
+
|
278
|
+
def execute(defines, node)
|
279
|
+
defines.delete @macro
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class ParseError < Exception; end
|
284
|
+
|
285
|
+
# Parses a file for all preprocessor control directives into a tree of Node
|
286
|
+
# objects. The parser can operate on a file or an array of lines.
|
287
|
+
class FileParser
|
288
|
+
attr_reader :line, :directories
|
289
|
+
|
290
|
+
def initialize(root, directories)
|
291
|
+
@stack = [root]
|
292
|
+
@directories = directories
|
293
|
+
end
|
294
|
+
|
295
|
+
# Parser operations
|
296
|
+
|
297
|
+
def add_body(node)
|
298
|
+
@stack.last.body << node
|
299
|
+
end
|
300
|
+
|
301
|
+
def add_else(node)
|
302
|
+
@stack.last.add_else node
|
303
|
+
end
|
304
|
+
|
305
|
+
def stack_push(node)
|
306
|
+
@stack.push node
|
307
|
+
end
|
308
|
+
|
309
|
+
def stack_pop
|
310
|
+
@stack.pop
|
311
|
+
end
|
312
|
+
|
313
|
+
def stack_top
|
314
|
+
@stack.last
|
315
|
+
end
|
316
|
+
|
317
|
+
def close
|
318
|
+
@stack.last.close
|
319
|
+
end
|
320
|
+
|
321
|
+
# Events
|
322
|
+
|
323
|
+
def process_include(name)
|
324
|
+
# We do not process any <files>. This could be enabled as
|
325
|
+
# an option, but we don't need it or want it now.
|
326
|
+
name =~ /\s*"([^"]+)".*$/
|
327
|
+
return unless $1
|
328
|
+
|
329
|
+
node = IncludedFile.new $1, self
|
330
|
+
add_body node
|
331
|
+
end
|
332
|
+
|
333
|
+
def process_ifdef(macro)
|
334
|
+
node = IfDefined.new macro, self
|
335
|
+
add_body node
|
336
|
+
stack_push node
|
337
|
+
end
|
338
|
+
|
339
|
+
def process_ifndef(macro)
|
340
|
+
node = IfNotDefined.new macro, self
|
341
|
+
add_body node
|
342
|
+
stack_push node
|
343
|
+
end
|
344
|
+
|
345
|
+
def process_endif(rest)
|
346
|
+
close
|
347
|
+
end
|
348
|
+
|
349
|
+
def process_else(rest)
|
350
|
+
node = Else.new self
|
351
|
+
add_else node
|
352
|
+
end
|
353
|
+
|
354
|
+
def process_define(macro)
|
355
|
+
node = Define.new macro, self
|
356
|
+
add_body node
|
357
|
+
end
|
358
|
+
|
359
|
+
def process_undef(macro)
|
360
|
+
node = Undefine.new macro, self
|
361
|
+
add_body node
|
362
|
+
end
|
363
|
+
|
364
|
+
def process_if(expression)
|
365
|
+
node = If.new expression, self
|
366
|
+
add_body node
|
367
|
+
stack_push node
|
368
|
+
end
|
369
|
+
|
370
|
+
def process_elif(expression)
|
371
|
+
node = ElseIf.new expression, self
|
372
|
+
add_else node
|
373
|
+
add_body node
|
374
|
+
stack_push node
|
375
|
+
end
|
376
|
+
|
377
|
+
# Parse methods
|
378
|
+
|
379
|
+
if defined? Encoding
|
380
|
+
def parse_file(name)
|
381
|
+
parse IO.readlines(name, :encoding => "ascii-8bit")
|
382
|
+
end
|
383
|
+
else
|
384
|
+
def parse_file(name)
|
385
|
+
parse IO.readlines(name)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def parse(lines)
|
390
|
+
@line = 0
|
391
|
+
|
392
|
+
lines.each do |line|
|
393
|
+
@line += 1
|
394
|
+
m = line.match(/^\s*#(include|ifdef|ifndef|endif|else|define|undef|if|elif)(.*)$/)
|
395
|
+
|
396
|
+
# TODO: continue reading if line ends in \
|
397
|
+
|
398
|
+
send :"process_#{m[1]}", m[2] if m
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
attr_accessor :file_names, :directories, :defines, :system_defines
|
404
|
+
attr_reader :sources
|
405
|
+
|
406
|
+
def initialize(cc, files, directories=[], defines=nil)
|
407
|
+
@cc = cc
|
408
|
+
@file_names = files
|
409
|
+
@directories = directories
|
410
|
+
@defines = defines
|
411
|
+
|
412
|
+
@system_defines = {}
|
413
|
+
@sources = []
|
414
|
+
end
|
415
|
+
|
416
|
+
def get_system_defines
|
417
|
+
lines = `#{@cc} -dM -E #{@defines} - < #{DEV_NULL}`.split("\n")
|
418
|
+
|
419
|
+
source = SourceFile.new "sytem_defines", self
|
420
|
+
parser = FileParser.new source, @directories
|
421
|
+
parser.parse lines
|
422
|
+
|
423
|
+
source.execute @system_defines
|
424
|
+
end
|
425
|
+
|
426
|
+
def process
|
427
|
+
get_system_defines
|
428
|
+
|
429
|
+
@file_names.each do |name|
|
430
|
+
source = SourceFile.new name, self
|
431
|
+
parser = FileParser.new source, @directories
|
432
|
+
|
433
|
+
parser.parse_file name
|
434
|
+
source.execute @system_defines.dup
|
435
|
+
source.collect_dependencies
|
436
|
+
|
437
|
+
@sources << source
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def print_dependencies(out, max=72)
|
442
|
+
@sources.each do |source|
|
443
|
+
source.print_dependencies out, max
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|