1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
#!/usr/bin/ruby
# tool/update-deps verify makefile dependencies.
# Requirements:
# gcc 4.5 (for -save-temps=obj option)
# GNU make (for -p option)
#
# Usage:
# 1. Compile ruby with -save-temps=obj option.
# Ex. ./configure debugflags='-save-temps=obj -g' && make all golf
# 2. Run tool/update-deps to show dependency problems.
# Ex. ruby tool/update-deps
require 'pathname'
require 'pp'
ENV['LC_ALL'] = 'C'
def read_make_deps(cwd)
dependencies = {}
make_p = `make -p all miniruby ruby golf 2> /dev/null`
dirstack = [cwd]
curdir = nil
make_p.scan(%r{Entering directory ['`](.*)'|^\# (GNU Make) |^CURDIR := (.*)|^([/0-9a-zA-Z._-]+):(.*)|^# (Finished Make data base on) |Leaving directory ['`](.*)'}) {
directory_enter = $1
data_base_start = $2
data_base_curdir = $3
rule_target = $4
rule_sources = $5
data_base_end = $6
directory_leave = $7
#p $~
if directory_enter
enter_dir = Pathname(directory_enter)
#p [:enter, enter_dir]
dirstack.push enter_dir
elsif data_base_start
curdir = nil
elsif data_base_curdir
curdir = Pathname(data_base_curdir)
elsif rule_target && rule_sources
target = rule_target
deps = rule_sources
deps = deps.scan(%r{[/0-9a-zA-Z._-]+})
next if /\.o\z/ !~ target.to_s
next if /\A\./ =~ target.to_s # skip rules such as ".c.o"
#p [curdir, target, deps]
dependencies[(curdir||dirstack.last) + target] ||= []
dependencies[(curdir||dirstack.last) + target] |= deps.map {|dep| (curdir||dirstack.last) + dep }
elsif data_base_end
curdir = nil
elsif directory_leave
leave_dir = Pathname(directory_leave)
#p [:leave, leave_dir]
if leave_dir != dirstack.last
warn "unexpected leave_dir : #{dirstack.last.inspect} != #{leave_dir.inspect}"
end
dirstack.pop
end
}
dependencies
end
#def guess_compiler_wd(filename, hint0)
# hint = hint0
# begin
# guess = hint + filename
# if guess.file?
# return hint
# end
# hint = hint.parent
# end while hint.to_s != '.'
# raise ArgumentError, "can not find #{filename} (hint: #{hint0})"
#end
def read_single_actual_deps(path_i, cwd)
files = {}
path_i.each_line.with_index {|line, lineindex|
next if /\A\# \d+ "(.*)"/ !~ line
files[$1] = lineindex
}
# gcc emits {# 1 "/absolute/directory/of/the/source/file//"} at 2nd line.
compiler_wd = files.keys.find {|f| %r{\A/.*//\z} =~ f }
if compiler_wd
files.delete compiler_wd
compiler_wd = Pathname(compiler_wd.sub(%r{//\z}, ''))
else
raise "compiler working directory not found"
end
deps = []
files.each_key {|dep|
next if %r{\A<.*>\z} =~ dep # omit <command-line>, etc.
dep = Pathname(dep)
if dep.relative?
dep = compiler_wd + dep
end
if !dep.file?
warn "file not found: #{dep}"
next
end
next if !dep.to_s.start_with?(cwd.to_s) # omit system headers.
deps << dep
}
deps
end
def read_actual_deps(cwd)
deps = {}
Pathname.glob('**/*.o').sort.each {|fn_o|
fn_i = fn_o.sub_ext('.i')
next if !fn_i.exist?
path_o = cwd + fn_o
path_i = cwd + fn_i
deps[path_o] = read_single_actual_deps(path_i, cwd)
}
deps
end
def concentrate(dependencies, cwd)
deps = {}
dependencies.keys.sort.each {|target|
sources = dependencies[target]
target = target.relative_path_from(cwd)
sources = sources.map {|s|
rel = s.relative_path_from(cwd)
rel
}
if %r{\A\.\.(/|\z)} =~ target.to_s
warn "out of tree target: #{target}"
next
end
sources = sources.reject {|s|
if %r{\A\.\.(/|\z)} =~ s.to_s
warn "out of tree source: #{s}"
true
else
false
end
}
deps[target] = sources
}
deps
end
def compare_deps(make_deps, actual_deps)
targets = actual_deps.keys.sort_by {|t|
ary = t.to_s.split(%r{/})
ary.map.with_index {|e, i| i == ary.length-1 ? [0, e] : [1, e] } # regular file first, directories last.
}
targets.each {|target|
actual_sources = actual_deps[target]
if !make_deps.has_key?(target)
warn "no makefile dependency for #{target}"
else
make_sources = make_deps[target]
lacks = actual_sources - make_sources
puts "#{target} lacks: #{lacks.join(" ")}" if !lacks.empty?
unused = make_sources - actual_sources
puts "#{target} unuse: #{unused.join(" ")}" if !unused.empty?
end
}
end
def main
cwd = Pathname.pwd
make_deps = read_make_deps(cwd)
#pp make_deps
make_deps = concentrate(make_deps, cwd)
#pp make_deps
actual_deps = read_actual_deps(cwd)
actual_deps = concentrate(actual_deps, cwd)
#pp actual_deps
compare_deps(make_deps, actual_deps)
end
main
|