ruby/tool/update-deps

158 строки
4.1 KiB
Ruby
Executable File

#!/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