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