summaryrefslogtreecommitdiff
path: root/tool/update-deps
blob: bb2403498979f89b876a94d871c983bbc66cfa1e (plain)
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
#!/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]
  make_p.scan(%r{Entering directory `(.*)'|Leaving directory `(.*)'|^([/0-9a-zA-Z._-]+):(.*)}) {
    if $1
      enter_dir = Pathname($1)
      #p [:enter, enter_dir]
      dirstack.push enter_dir
    elsif $2
      leave_dir = Pathname($2)
      #p [:leave, leave_dir]
      if leave_dir != dirstack.last
        warn "unexpected leave_dir : #{dirstack.last.inspect} != #{leave_dir.inspect}"
      end
      dirstack.pop
    else
      target = $3
      deps = $4
      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"
      dependencies[dirstack.last + target] ||= []
      dependencies[dirstack.last + target] |= deps.map {|dep| dirstack.last + dep }
    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| s.relative_path_from(cwd) }
    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)
  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