2011-06-18 03:15:47 +04:00
#!/usr/bin/ruby
2013-04-11 16:03:23 +04:00
# tool/update-deps verify makefile dependencies.
2011-06-18 03:15:47 +04:00
2013-04-11 16:03:23 +04:00
# Requirements:
# gcc 4.5 (for -save-temps=obj option)
# GNU make (for -p option)
2011-06-18 03:15:47 +04:00
#
2014-11-16 12:22:43 +03:00
# Warning: ccache (and similar tools) must be disabled for
# -save-temps=obj to work properly.
#
2013-04-11 16:03:23 +04:00
# 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.
2018-09-17 04:31:06 +03:00
# Ex. ./ruby tool/update-deps
2014-11-15 08:15:49 +03:00
# 3. Use --fix to fix makefiles.
2018-09-17 04:31:06 +03:00
# Ex. ./ruby tool/update-deps --fix
2014-11-15 08:15:49 +03:00
#
# Other usages:
# * Fix makefiles using previously detected dependency problems
# Ex. ruby tool/update-deps --actual-fix [file]
# "ruby tool/update-deps --fix" is same as "ruby tool/update-deps | ruby tool/update-deps --actual-fix".
2011-06-18 03:15:47 +04:00
2014-11-15 08:15:49 +03:00
require 'optparse'
require 'stringio'
2013-04-11 16:03:23 +04:00
require 'pathname'
2014-11-20 17:18:37 +03:00
require 'open3'
2013-04-11 16:03:23 +04:00
require 'pp'
2011-06-18 03:15:47 +04:00
2016-07-03 00:01:04 +03:00
# When out-of-place build, files may be built in source directory or
2014-11-27 15:51:04 +03:00
# build directory.
# Some files are always built in the source directory.
# Some files are always built in the build directory.
# Some files are built in the source directory for tarball but build directory for repository (svn).
=begin
How to build test directories.
VER=2.2.0
REV=48577
tar xf ruby-$VER-r$REV.tar.xz
cp -a ruby-$VER-r$REV tarball_source_dir_original
mv ruby-$VER-r$REV tarball_source_dir_after_build
2018-07-28 13:00:27 +03:00
svn co -q -r$REV https://svn.ruby-lang.org/repos/ruby/trunk ruby
2014-11-27 15:51:04 +03:00
(cd ruby; autoconf)
cp -a ruby repo_source_dir_original
mv ruby repo_source_dir_after_build
mkdir tarball_build_dir repo_build_dir tarball_install_dir repo_install_dir
(cd tarball_build_dir; ../tarball_source_dir_after_build/configure --prefix=$(cd ../tarball_install_dir; pwd) && make all golf install) > tarball.log 2>&1
(cd repo_build_dir; ../repo_source_dir_after_build/configure --prefix=$(cd ../repo_install_dir; pwd) && make all golf install) > repo.log 2>&1
ruby -rpp -rfind -e '
ds = %w[
repo_source_dir_original
repo_source_dir_after_build
repo_build_dir
tarball_source_dir_original
tarball_source_dir_after_build
tarball_build_dir
]
files = {}
ds.each {|d|
files[d] = {}
Dir.chdir(d) { Find.find(".") {|f| files[d][f] = true if %r{\.(c|h|inc|dmyh)\z} =~ f } }
}
result = {}
files_union = files.values.map {|h| h.keys }.flatten.uniq.sort
files_union.each {|f|
k = files.map {|d,h| h[f] ? d : nil }.compact.sort
next if k == %w[repo_source_dir_after_build repo_source_dir_original tarball_source_dir_after_build tarball_source_dir_original]
next if k == %w[repo_build_dir tarball_build_dir] && File.basename(f) == "extconf.h"
result[k] ||= []
result[k] << f
}
result.each {|k,v|
k.each {|d|
puts d
}
v.each {|f|
puts " " + f.sub(%r{\A\./}, "")
}
puts
}
' | tee compare.log
=end
# Files built in the source directory.
# They can be referenced as $(top_srcdir)/filename.
# % ruby -e 'def g(d) Dir.chdir(d) { Dir["**/*.{c,h,inc,dmyh}"] } end; puts((g("repo_source_dir_after_build") - g("repo_source_dir_original")).sort)'
FILES_IN_SOURCE_DIRECTORY = %w[
revision.h
]
# Files built in the build directory (except extconf.h).
# They can be referenced as $(topdir)/filename.
# % ruby -e 'def g(d) Dir.chdir(d) { Dir["**/*.{c,h,inc,dmyh}"] } end; puts(g("tarball_build_dir").reject {|f| %r{/extconf.h\z} =~ f }.sort)'
FILES_IN_BUILD_DIRECTORY = %w[
2014-11-21 12:12:56 +03:00
encdb.h
2014-11-27 15:51:04 +03:00
ext/etc/constdefs.h
ext/socket/constdefs.c
ext/socket/constdefs.h
probes.h
transdb.h
verconf.h
]
# They are built in the build directory if the source is obtained from the repository.
# However they are pre-built for tarball and they exist in the source directory extracted from the tarball.
# % ruby -e 'def g(d) Dir.chdir(d) { Dir["**/*.{c,h,inc,dmyh}"] } end; puts((g("repo_build_dir") & g("tarball_source_dir_original")).sort)'
FILES_NEED_VPATH = %w[
ext/rbconfig/sizeof/sizes.c
ext/ripper/eventids1.c
ext/ripper/eventids2table.c
ext/ripper/ripper.c
golf_prelude.c
2014-11-21 12:12:56 +03:00
id.c
id.h
2014-11-27 15:51:04 +03:00
insns.inc
insns_info.inc
known_errors.inc
2014-11-21 12:12:56 +03:00
lex.c
miniprelude.c
mjit_compile.c: merge initial JIT compiler
which has been developed by Takashi Kokubun <takashikkbn@gmail> as
YARV-MJIT. Many of its bugs are fixed by wanabe <s.wanabe@gmail.com>.
This JIT compiler is designed to be a safe migration path to introduce
JIT compiler to MRI. So this commit does not include any bytecode
changes or dynamic instruction modifications, which are done in original
MJIT.
This commit even strips off some aggressive optimizations from
YARV-MJIT, and thus it's slower than YARV-MJIT too. But it's still
fairly faster than Ruby 2.5 in some benchmarks (attached below).
Note that this JIT compiler passes `make test`, `make test-all`, `make
test-spec` without JIT, and even with JIT. Not only it's perfectly safe
with JIT disabled because it does not replace VM instructions unlike
MJIT, but also with JIT enabled it stably runs Ruby applications
including Rails applications.
I'm expecting this version as just "initial" JIT compiler. I have many
optimization ideas which are skipped for initial merging, and you may
easily replace this JIT compiler with a faster one by just replacing
mjit_compile.c. `mjit_compile` interface is designed for the purpose.
common.mk: update dependencies for mjit_compile.c.
internal.h: declare `rb_vm_insn_addr2insn` for MJIT.
vm.c: exclude some definitions if `-DMJIT_HEADER` is provided to
compiler. This avoids to include some functions which take a long time
to compile, e.g. vm_exec_core. Some of the purpose is achieved in
transform_mjit_header.rb (see `IGNORED_FUNCTIONS`) but others are
manually resolved for now. Load mjit_helper.h for MJIT header.
mjit_helper.h: New. This is a file used only by JIT-ed code. I'll
refactor `mjit_call_cfunc` later.
vm_eval.c: add some #ifdef switches to skip compiling some functions
like Init_vm_eval.
win32/mkexports.rb: export thread/ec functions, which are used by MJIT.
include/ruby/defines.h: add MJIT_FUNC_EXPORTED macro alis to clarify
that a function is exported only for MJIT.
array.c: export a function used by MJIT.
bignum.c: ditto.
class.c: ditto.
compile.c: ditto.
error.c: ditto.
gc.c: ditto.
hash.c: ditto.
iseq.c: ditto.
numeric.c: ditto.
object.c: ditto.
proc.c: ditto.
re.c: ditto.
st.c: ditto.
string.c: ditto.
thread.c: ditto.
variable.c: ditto.
vm_backtrace.c: ditto.
vm_insnhelper.c: ditto.
vm_method.c: ditto.
I would like to improve maintainability of function exports, but I
believe this way is acceptable as initial merging if we clarify the
new exports are for MJIT (so that we can use them as TODO list to fix)
and add unit tests to detect unresolved symbols.
I'll add unit tests of JIT compilations in succeeding commits.
Author: Takashi Kokubun <takashikkbn@gmail.com>
Contributor: wanabe <s.wanabe@gmail.com>
Part of [Feature #14235]
---
* Known issues
* Code generated by gcc is faster than clang. The benchmark may be worse
in macOS. Following benchmark result is provided by gcc w/ Linux.
* Performance is decreased when Google Chrome is running
* JIT can work on MinGW, but it doesn't improve performance at least
in short running benchmark.
* Currently it doesn't perform well with Rails. We'll try to fix this
before release.
---
* Benchmark reslts
Benchmarked with:
Intel 4.0GHz i7-4790K with 16GB memory under x86-64 Ubuntu 8 Cores
- 2.0.0-p0: Ruby 2.0.0-p0
- r62186: Ruby trunk (early 2.6.0), before MJIT changes
- JIT off: On this commit, but without `--jit` option
- JIT on: On this commit, and with `--jit` option
** Optcarrot fps
Benchmark: https://github.com/mame/optcarrot
| |2.0.0-p0 |r62186 |JIT off |JIT on |
|:--------|:--------|:--------|:--------|:--------|
|fps |37.32 |51.46 |51.31 |58.88 |
|vs 2.0.0 |1.00x |1.38x |1.37x |1.58x |
** MJIT benchmarks
Benchmark: https://github.com/benchmark-driver/mjit-benchmarks
(Original: https://github.com/vnmakarov/ruby/tree/rtl_mjit_branch/MJIT-benchmarks)
| |2.0.0-p0 |r62186 |JIT off |JIT on |
|:----------|:--------|:--------|:--------|:--------|
|aread |1.00 |1.09 |1.07 |2.19 |
|aref |1.00 |1.13 |1.11 |2.22 |
|aset |1.00 |1.50 |1.45 |2.64 |
|awrite |1.00 |1.17 |1.13 |2.20 |
|call |1.00 |1.29 |1.26 |2.02 |
|const2 |1.00 |1.10 |1.10 |2.19 |
|const |1.00 |1.11 |1.10 |2.19 |
|fannk |1.00 |1.04 |1.02 |1.00 |
|fib |1.00 |1.32 |1.31 |1.84 |
|ivread |1.00 |1.13 |1.12 |2.43 |
|ivwrite |1.00 |1.23 |1.21 |2.40 |
|mandelbrot |1.00 |1.13 |1.16 |1.28 |
|meteor |1.00 |2.97 |2.92 |3.17 |
|nbody |1.00 |1.17 |1.15 |1.49 |
|nest-ntimes|1.00 |1.22 |1.20 |1.39 |
|nest-while |1.00 |1.10 |1.10 |1.37 |
|norm |1.00 |1.18 |1.16 |1.24 |
|nsvb |1.00 |1.16 |1.16 |1.17 |
|red-black |1.00 |1.02 |0.99 |1.12 |
|sieve |1.00 |1.30 |1.28 |1.62 |
|trees |1.00 |1.14 |1.13 |1.19 |
|while |1.00 |1.12 |1.11 |2.41 |
** Discourse's script/bench.rb
Benchmark: https://github.com/discourse/discourse/blob/v1.8.7/script/bench.rb
NOTE: Rails performance was somehow a little degraded with JIT for now.
We should fix this.
(At least I know opt_aref is performing badly in JIT and I have an idea
to fix it. Please wait for the fix.)
*** JIT off
Your Results: (note for timings- percentile is first, duration is second in millisecs)
categories_admin:
50: 17
75: 18
90: 22
99: 29
home_admin:
50: 21
75: 21
90: 27
99: 40
topic_admin:
50: 17
75: 18
90: 22
99: 32
categories:
50: 35
75: 41
90: 43
99: 77
home:
50: 39
75: 46
90: 49
99: 95
topic:
50: 46
75: 52
90: 56
99: 101
*** JIT on
Your Results: (note for timings- percentile is first, duration is second in millisecs)
categories_admin:
50: 19
75: 21
90: 25
99: 33
home_admin:
50: 24
75: 26
90: 30
99: 35
topic_admin:
50: 19
75: 20
90: 25
99: 30
categories:
50: 40
75: 44
90: 48
99: 76
home:
50: 42
75: 48
90: 51
99: 89
topic:
50: 49
75: 55
90: 58
99: 99
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62197 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-04 14:22:28 +03:00
mjit_compile.inc
2014-11-21 12:12:56 +03:00
newline.c
2014-11-27 15:51:04 +03:00
node_name.inc
opt_sc.inc
optinsn.inc
optunifs.inc
2014-11-21 12:12:56 +03:00
parse.c
parse.h
prelude.c
2014-11-27 15:51:04 +03:00
probes.dmyh
vm.inc
vmtc.inc
enc/trans/big5.c
enc/trans/chinese.c
enc/trans/emoji.c
enc/trans/emoji_iso2022_kddi.c
enc/trans/emoji_sjis_docomo.c
enc/trans/emoji_sjis_kddi.c
enc/trans/emoji_sjis_softbank.c
enc/trans/escape.c
enc/trans/gb18030.c
enc/trans/gbk.c
enc/trans/iso2022.c
enc/trans/japanese.c
enc/trans/japanese_euc.c
enc/trans/japanese_sjis.c
enc/trans/korean.c
enc/trans/single_byte.c
enc/trans/utf8_mac.c
enc/trans/utf_16_32.c
2014-11-21 12:12:56 +03:00
]
2014-11-27 15:51:04 +03:00
# Multiple files with same filename.
2014-11-21 12:12:56 +03:00
# It is not good idea to refer them using VPATH.
2014-12-19 16:34:37 +03:00
# Files in FILES_SAME_NAME_INC is referenced using $(hdrdir).
# Files in FILES_SAME_NAME_TOP is referenced using $(top_srcdir).
FILES_SAME_NAME_INC = %w[
2019-02-08 05:46:22 +03:00
include/ruby.h
2014-11-21 12:12:56 +03:00
include/ruby/ruby.h
include/ruby/version.h
]
2011-06-18 03:15:47 +04:00
2014-12-19 16:34:37 +03:00
FILES_SAME_NAME_TOP = %w[
version.h
]
2014-11-27 15:51:04 +03:00
# Other source files exist in the source directory.
2014-11-21 12:12:56 +03:00
def in_makefile(target, source)
target = target.to_s
source = source.to_s
case target
2018-12-03 16:12:34 +03:00
when %r{\A[^/]*\z}, %r{\Acoroutine/}
2014-11-21 12:12:56 +03:00
target2 = "#{target.sub(/\.o\z/, '.$(OBJEXT)')}"
case source
2014-11-27 15:51:04 +03:00
when *FILES_IN_SOURCE_DIRECTORY then source2 = "$(top_srcdir)/#{source}"
when *FILES_IN_BUILD_DIRECTORY then source2 = "{$(VPATH)}#{source}" # VPATH is not used now but it may changed in future.
2014-11-21 12:12:56 +03:00
when *FILES_NEED_VPATH then source2 = "{$(VPATH)}#{source}"
2014-12-19 16:34:37 +03:00
when *FILES_SAME_NAME_INC then source2 = "$(hdrdir)/#{source.sub(%r{\Ainclude/},'')}"
when *FILES_SAME_NAME_TOP then source2 = "$(top_srcdir)/#{source}"
2014-11-21 12:12:56 +03:00
when 'thread_pthread.c' then source2 = '{$(VPATH)}thread_$(THREAD_MODEL).c'
when 'thread_pthread.h' then source2 = '{$(VPATH)}thread_$(THREAD_MODEL).h'
when %r{\A[^/]*\z} then source2 = "{$(VPATH)}#{File.basename source}"
when %r{\A\.ext/include/[^/]+/ruby/} then source2 = "{$(VPATH)}#{$'}"
when %r{\Ainclude/ruby/} then source2 = "{$(VPATH)}#{$'}"
when %r{\Aenc/} then source2 = "{$(VPATH)}#{$'}"
when %r{\Amissing/} then source2 = "{$(VPATH)}#{$'}"
when %r{\Accan/} then source2 = "$(CCAN_DIR)/#{$'}"
when %r{\Adefs/} then source2 = "{$(VPATH)}#{source}"
2018-12-03 16:12:34 +03:00
when %r{\Acoroutine/} then source2 = "{$(VPATH)}$(COROUTINE_H)"
2014-11-21 12:12:56 +03:00
else source2 = "$(top_srcdir)/#{source}"
end
["common.mk", target2, source2]
when %r{\Aenc/}
target2 = "#{target.sub(/\.o\z/, '.$(OBJEXT)')}"
case source
2014-11-27 15:51:04 +03:00
when *FILES_IN_SOURCE_DIRECTORY then source2 = "$(top_srcdir)/#{source}"
when *FILES_IN_BUILD_DIRECTORY then source2 = source
2014-11-21 12:12:56 +03:00
when *FILES_NEED_VPATH then source2 = source
2014-12-19 16:34:37 +03:00
when *FILES_SAME_NAME_INC then source2 = "$(hdrdir)/#{source.sub(%r{\Ainclude/},'')}"
when *FILES_SAME_NAME_TOP then source2 = "$(top_srcdir)/#{source}"
2014-11-21 12:12:56 +03:00
when %r{\A\.ext/include/[^/]+/ruby/} then source2 = $'
when %r{\Ainclude/ruby/} then source2 = $'
2019-02-12 15:15:30 +03:00
when %r{\Aenc/unicode/[\d.]+/} then source2 = '$(UNICODE_HDR_DIR)/' + $'
2014-11-21 12:12:56 +03:00
when %r{\Aenc/} then source2 = source
else source2 = "$(top_srcdir)/#{source}"
end
["enc/depend", target2, source2]
when %r{\Aext/}
unless File.exist?("#{File.dirname(target)}/extconf.rb")
warn "warning: not found: #{File.dirname(target)}/extconf.rb"
end
target2 = File.basename(target)
2014-11-27 15:51:04 +03:00
relpath = Pathname(source).relative_path_from(Pathname(target).dirname).to_s
2014-11-21 12:12:56 +03:00
case source
2014-11-27 15:51:04 +03:00
when *FILES_IN_SOURCE_DIRECTORY then source2 = "$(top_srcdir)/#{source}"
when *FILES_IN_BUILD_DIRECTORY then source2 = relpath
when *FILES_NEED_VPATH then source2 = "{$(VPATH)}#{File.basename source}"
2014-12-19 16:34:37 +03:00
when *FILES_SAME_NAME_INC then source2 = "$(hdrdir)/#{source.sub(%r{\Ainclude/},'')}"
when *FILES_SAME_NAME_TOP then source2 = "$(top_srcdir)/#{source}"
2014-11-21 12:12:56 +03:00
when %r{\A\.ext/include/[^/]+/ruby/} then source2 = "$(arch_hdrdir)/ruby/#{$'}"
when %r{\Ainclude/} then source2 = "$(hdrdir)/#{$'}"
when %r{\A#{Regexp.escape File.dirname(target)}/extconf\.h\z} then source2 = "$(RUBY_EXTCONF_H)"
when %r{\A#{Regexp.escape File.dirname(target)}/} then source2 = $'
else source2 = "$(top_srcdir)/#{source}"
end
["#{File.dirname(target)}/depend", target2, source2]
else
raise "unexpected target: #{target}"
end
end
2014-11-15 08:15:49 +03:00
2014-11-20 17:18:37 +03:00
DEPENDENCIES_SECTION_START_MARK = "\# AUTOGENERATED DEPENDENCIES START\n"
DEPENDENCIES_SECTION_END_MARK = "\# AUTOGENERATED DEPENDENCIES END\n"
2014-11-21 12:12:56 +03:00
def init_global
ENV['LC_ALL'] = 'C'
2018-11-19 02:29:40 +03:00
if mkflag0 = ENV['GNUMAKEFLAGS'] and (mkflag = mkflag0.sub(/(\A|\s+)-j\d*(?=\s+|\z)/, '')) != mkflag0
2018-01-09 09:55:54 +03:00
mkflag.strip!
ENV['GNUMAKEFLAGS'] = mkflag
end
2014-11-21 12:12:56 +03:00
$opt_fix = false
$opt_a = false
$opt_actual_fix = false
$i_not_found = false
end
2014-11-15 08:15:49 +03:00
def optionparser
op = OptionParser.new
op.banner = 'Usage: ruby tool/update-deps'
op.def_option('-a', 'show valid dependencies') { $opt_a = true }
op.def_option('--fix') { $opt_fix = true }
op.def_option('--actual-fix') { $opt_actual_fix = true }
op
end
2013-04-11 16:03:23 +04:00
def read_make_deps(cwd)
dependencies = {}
2014-11-20 17:18:37 +03:00
make_p, make_p_stderr, make_p_status = Open3.capture3("make -p all miniruby ruby golf")
2014-11-27 17:42:05 +03:00
File.open('update-deps.make.out.log', 'w') {|f| f.print make_p }
File.open('update-deps.make.err.log', 'w') {|f| f.print make_p_stderr }
2014-11-20 17:18:37 +03:00
if !make_p_status.success?
puts make_p_stderr
raise "make failed"
end
2013-04-11 16:03:23 +04:00
dirstack = [cwd]
2014-11-14 11:37:47 +03:00
curdir = nil
2014-11-15 08:15:49 +03:00
make_p.scan(%r{Entering\ directory\ ['`](.*)'|
^\#\ (GNU\ Make)\ |
^CURDIR\ :=\ (.*)|
^([/0-9a-zA-Z._-]+):(.*)\n((?:\#.*\n)*)|
^\#\ (Finished\ Make\ data\ base\ on)\ |
Leaving\ directory\ ['`](.*)'}x) {
2014-11-14 11:37:47 +03:00
directory_enter = $1
data_base_start = $2
data_base_curdir = $3
rule_target = $4
rule_sources = $5
2014-11-15 08:15:49 +03:00
rule_desc = $6
data_base_end = $7
directory_leave = $8
2014-11-14 11:37:47 +03:00
#p $~
if directory_enter
enter_dir = Pathname(directory_enter)
2013-04-11 16:03:23 +04:00
#p [:enter, enter_dir]
dirstack.push enter_dir
2014-11-14 11:37:47 +03:00
elsif data_base_start
curdir = nil
elsif data_base_curdir
curdir = Pathname(data_base_curdir)
2014-11-15 08:15:49 +03:00
elsif rule_target && rule_sources && rule_desc &&
/Modification time never checked/ !~ rule_desc # This pattern match eliminates rules which VPATH is not expanded.
2014-11-14 11:37:47 +03:00
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]
2014-11-15 08:15:49 +03:00
dir = curdir || dirstack.last
dependencies[dir + target] ||= []
dependencies[dir + target] |= deps.map {|dep| dir + dep }
2014-11-14 11:37:47 +03:00
elsif data_base_end
curdir = nil
elsif directory_leave
leave_dir = Pathname(directory_leave)
2013-04-11 16:03:23 +04:00
#p [:leave, leave_dir]
if leave_dir != dirstack.last
warn "unexpected leave_dir : #{dirstack.last.inspect} != #{leave_dir.inspect}"
2011-06-18 03:15:47 +04:00
end
2013-04-11 16:03:23 +04:00
dirstack.pop
end
2011-06-18 03:15:47 +04:00
}
2013-04-11 16:03:23 +04:00
dependencies
end
2011-06-18 03:15:47 +04:00
2013-04-11 16:03:23 +04:00
#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
2014-11-20 17:18:37 +03:00
def read_single_cc_deps(path_i, cwd)
2013-04-11 16:03:23 +04:00
files = {}
2016-07-06 08:37:13 +03:00
compiler_wd = nil
path_i.each_line {|line|
2013-04-11 16:03:23 +04:00
next if /\A\# \d+ "(.*)"/ !~ line
2016-07-06 08:37:13 +03:00
dep = $1
next if %r{\A<.*>\z} =~ dep # omit <command-line>, etc.
compiler_wd ||= dep
files[dep] = true
2011-06-18 03:15:47 +04:00
}
2013-04-11 16:03:23 +04:00
# gcc emits {# 1 "/absolute/directory/of/the/source/file//"} at 2nd line.
2016-07-06 08:37:13 +03:00
if %r{\A/.*//\z} =~ compiler_wd
2013-04-11 16:03:23 +04:00
files.delete compiler_wd
compiler_wd = Pathname(compiler_wd.sub(%r{//\z}, ''))
2016-07-06 08:37:13 +03:00
elsif !(compiler_wd = yield)
2015-06-14 16:35:05 +03:00
raise "compiler working directory not found: #{path_i}"
2013-04-11 16:03:23 +04:00
end
deps = []
files.each_key {|dep|
dep = Pathname(dep)
if dep.relative?
dep = compiler_wd + dep
end
if !dep.file?
2014-11-20 17:18:37 +03:00
warn "warning: file not found: #{dep}"
2011-06-18 03:15:47 +04:00
next
end
2013-04-11 16:03:23 +04:00
next if !dep.to_s.start_with?(cwd.to_s) # omit system headers.
deps << dep
}
deps
end
2011-06-18 03:15:47 +04:00
2014-11-20 17:18:37 +03:00
def read_cc_deps(cwd)
2013-04-11 16:03:23 +04:00
deps = {}
Pathname.glob('**/*.o').sort.each {|fn_o|
fn_i = fn_o.sub_ext('.i')
2014-11-15 08:15:49 +03:00
if !fn_i.exist?
2018-12-03 16:12:34 +03:00
next if fn_o.sub_ext('.S').exist?
2014-11-20 17:18:37 +03:00
warn "warning: not found: #{fn_i}"
2014-11-16 12:22:43 +03:00
$i_not_found = true
2014-11-15 08:15:49 +03:00
next
end
2013-04-11 16:03:23 +04:00
path_o = cwd + fn_o
path_i = cwd + fn_i
2016-07-06 08:37:13 +03:00
deps[path_o] = read_single_cc_deps(path_i, cwd) do
if fn_o.to_s.start_with?("enc/")
cwd
else
path_i.parent
end
end
2013-04-11 16:03:23 +04:00
}
deps
end
def concentrate(dependencies, cwd)
deps = {}
dependencies.keys.sort.each {|target|
sources = dependencies[target]
target = target.relative_path_from(cwd)
2014-11-14 11:37:47 +03:00
sources = sources.map {|s|
rel = s.relative_path_from(cwd)
rel
}
2013-04-11 16:03:23 +04:00
if %r{\A\.\.(/|\z)} =~ target.to_s
2014-11-20 17:18:37 +03:00
warn "warning: out of tree target: #{target}"
2013-04-11 16:03:23 +04:00
next
end
sources = sources.reject {|s|
if %r{\A\.\.(/|\z)} =~ s.to_s
2014-11-20 17:18:37 +03:00
warn "warning: out of tree source: #{s}"
2013-04-11 16:03:23 +04:00
true
2016-07-06 08:37:14 +03:00
elsif %r{/\.time\z} =~ s.to_s
true
2013-04-11 16:03:23 +04:00
else
false
2011-06-18 03:15:47 +04:00
end
2013-04-11 16:03:23 +04:00
}
deps[target] = sources
}
deps
end
2014-11-15 08:15:49 +03:00
def sort_paths(paths)
paths.sort_by {|t|
2013-04-11 16:03:23 +04:00
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.
}
2014-11-15 08:15:49 +03:00
end
2014-11-20 17:18:37 +03:00
def show_deps(tag, deps)
targets = sort_paths(deps.keys)
targets.each {|t|
sources = sort_paths(deps[t])
sources.each {|s|
puts "#{tag} #{t}: #{s}"
}
2013-04-11 16:03:23 +04:00
}
end
2011-06-18 03:15:47 +04:00
2014-11-20 17:18:37 +03:00
def detect_dependencies(out=$stdout)
2013-04-11 16:03:23 +04:00
cwd = Pathname.pwd
make_deps = read_make_deps(cwd)
2014-11-14 11:37:47 +03:00
#pp make_deps
2013-04-11 16:03:23 +04:00
make_deps = concentrate(make_deps, cwd)
#pp make_deps
2014-11-20 17:18:37 +03:00
cc_deps = read_cc_deps(cwd)
#pp cc_deps
cc_deps = concentrate(cc_deps, cwd)
#pp cc_deps
return make_deps, cc_deps
2014-11-15 08:15:49 +03:00
end
2014-11-20 17:18:37 +03:00
def compare_deps(make_deps, cc_deps, out=$stdout)
targets = make_deps.keys | cc_deps.keys
makefiles = {}
make_lines_hash = {}
make_deps.each {|t, sources|
sources.each {|s|
makefile, t2, s2 = in_makefile(t, s)
makefiles[makefile] = true
make_lines_hash[makefile] ||= Hash.new(false)
make_lines_hash[makefile]["#{t2}: #{s2}"] = true
}
2014-11-15 08:15:49 +03:00
}
2014-11-20 17:18:37 +03:00
cc_lines_hash = {}
cc_deps.each {|t, sources|
sources.each {|s|
makefile, t2, s2 = in_makefile(t, s)
makefiles[makefile] = true
cc_lines_hash[makefile] ||= Hash.new(false)
cc_lines_hash[makefile]["#{t2}: #{s2}"] = true
}
}
2014-11-15 14:30:59 +03:00
2014-11-20 17:18:37 +03:00
makefiles.keys.sort.each {|makefile|
cc_lines = cc_lines_hash[makefile] || Hash.new(false)
make_lines = make_lines_hash[makefile] || Hash.new(false)
2014-11-15 14:30:59 +03:00
content = begin
File.read(makefile)
2014-11-15 08:15:49 +03:00
rescue Errno::ENOENT
2014-11-15 14:30:59 +03:00
''
end
2014-11-20 17:18:37 +03:00
if /^#{Regexp.escape DEPENDENCIES_SECTION_START_MARK}
((?:.*\n)*)
#{Regexp.escape DEPENDENCIES_SECTION_END_MARK}/x =~ content
2014-11-15 14:30:59 +03:00
pre_post_part = [$`, $']
2014-11-20 17:18:37 +03:00
current_lines = Hash.new(false)
$1.each_line {|line| current_lines[line.chomp] = true }
(cc_lines.keys | current_lines.keys | make_lines.keys).sort.each {|line|
status = [cc_lines[line], current_lines[line], make_lines[line]]
case status
when [true, true, true]
# no problem
when [true, true, false]
out.puts "warning #{makefile} : #{line} (make doesn't detect written dependency)"
when [true, false, true]
out.puts "add_auto #{makefile} : #{line} (harmless)" # This is automatically updatable.
when [true, false, false]
out.puts "add_auto #{makefile} : #{line} (harmful)" # This is automatically updatable.
when [false, true, true]
out.puts "del_cc #{makefile} : #{line}" # Not automatically updatable because build on other OS may need the dependency.
when [false, true, false]
out.puts "del_cc #{makefile} : #{line} (Curious. make doesn't detect this dependency.)" # Not automatically updatable because build on other OS may need the dependency.
when [false, false, true]
out.puts "del_make #{makefile} : #{line}" # Not automatically updatable because the dependency is written manually.
else
raise "unexpected status: #{status.inspect}"
end
}
2014-11-15 14:30:59 +03:00
else
2014-11-20 17:18:37 +03:00
(cc_lines.keys | make_lines.keys).sort.each {|line|
status = [cc_lines[line], make_lines[line]]
case status
when [true, true]
# no problem
when [true, false]
out.puts "add_manual #{makefile} : #{line}" # Not automatically updatable because makefile has no section to update automatically.
when [false, true]
out.puts "del_manual #{makefile} : #{line}" # Not automatically updatable because makefile has no section to update automatically.
else
raise "unexpected status: #{status.inspect}"
end
}
2014-11-15 08:15:49 +03:00
end
2014-11-20 17:18:37 +03:00
}
end
2014-11-15 14:30:59 +03:00
2014-11-20 17:18:37 +03:00
def main_show(out=$stdout)
make_deps, cc_deps = detect_dependencies(out)
compare_deps(make_deps, cc_deps, out)
end
2014-11-15 14:30:59 +03:00
2014-11-20 17:18:37 +03:00
def extract_deplines(problems)
adds = {}
others = {}
problems.each_line {|line|
case line
when /\Aadd_auto (\S+) : ((\S+): (\S+))/
(adds[$1] ||= []) << [line, "#{$2}\n"]
when /\A(?:del_cc|del_make|add_manual|del_manual|warning) (\S+) : /
(others[$1] ||= []) << line
else
raise "unexpected line: #{line.inspect}"
2014-11-15 08:15:49 +03:00
end
2014-11-20 17:18:37 +03:00
}
return adds, others
end
2014-11-15 14:30:59 +03:00
2014-11-20 17:18:37 +03:00
def main_actual_fix(problems)
adds, others = extract_deplines(problems)
(adds.keys | others.keys).sort.each {|makefile|
content = begin
File.read(makefile)
rescue Errno::ENOENT
nil
2014-11-15 14:30:59 +03:00
end
2014-11-20 17:18:37 +03:00
if content &&
/^#{Regexp.escape DEPENDENCIES_SECTION_START_MARK}
((?:.*\n)*)
#{Regexp.escape DEPENDENCIES_SECTION_END_MARK}/x =~ content
pre_dep_post = [$`, $1, $']
2014-11-15 14:30:59 +03:00
else
2014-11-20 17:18:37 +03:00
pre_dep_post = nil
end
if pre_dep_post && adds[makefile]
pre_lines, dep_lines, post_lines = pre_dep_post
dep_lines = dep_lines.lines.to_a
add_lines = adds[makefile].map(&:last)
new_lines = (dep_lines | add_lines).sort.uniq
2014-11-15 14:30:59 +03:00
new_content = [
2014-11-20 17:18:37 +03:00
pre_lines,
2014-11-15 14:30:59 +03:00
DEPENDENCIES_SECTION_START_MARK,
2014-11-20 17:18:37 +03:00
*new_lines,
2014-11-15 14:30:59 +03:00
DEPENDENCIES_SECTION_END_MARK,
2014-11-20 17:18:37 +03:00
post_lines
2014-11-15 14:30:59 +03:00
].join
2014-11-20 17:18:37 +03:00
if content != new_content
puts "modified: #{makefile}"
tmp_makefile = "#{makefile}.new.#{$$}"
File.write(tmp_makefile, new_content)
File.rename tmp_makefile, makefile
2014-11-20 17:27:44 +03:00
(add_lines - dep_lines).each {|line| puts " added #{line}" }
2014-11-15 14:30:59 +03:00
else
2014-11-20 17:18:37 +03:00
puts "not modified: #{makefile}"
end
if others[makefile]
others[makefile].each {|line| puts " #{line}" }
end
else
if pre_dep_post
2015-04-20 06:45:35 +03:00
puts "no additional lines: #{makefile}"
2014-11-20 17:18:37 +03:00
elsif content
2014-11-15 14:30:59 +03:00
puts "no dependencies section: #{makefile}"
2014-11-20 17:18:37 +03:00
else
puts "no makefile: #{makefile}"
end
if adds[makefile]
puts " warning: dependencies section was exist at previous phase."
end
if adds[makefile]
adds[makefile].map(&:first).each {|line| puts " #{line}" }
end
if others[makefile]
others[makefile].each {|line| puts " #{line}" }
2014-11-15 14:30:59 +03:00
end
2014-11-15 08:15:49 +03:00
end
}
end
def main_fix
problems = StringIO.new
main_show(problems)
2014-11-15 14:30:59 +03:00
main_actual_fix(problems.string)
2014-11-15 08:15:49 +03:00
end
def run
op = optionparser
op.parse!(ARGV)
if $opt_actual_fix
main_actual_fix(ARGF.read)
elsif $opt_fix
main_fix
else
main_show
end
2013-04-11 16:03:23 +04:00
end
2011-06-18 03:15:47 +04:00
2014-11-21 12:12:56 +03:00
init_global
2014-11-15 08:15:49 +03:00
run
2014-11-16 12:22:43 +03:00
if $i_not_found
2014-11-20 17:18:37 +03:00
warn "warning: missing *.i files, see help in #$0 and ensure ccache is disabled"
2014-11-16 12:22:43 +03:00
end