From 989199f223e7dcd5e09186501fc2453d8d5a8794 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 16 Aug 2026 16:05:26 +0900 Subject: [PATCH 1/5] [Misc #22206] Remove source-content tests Remove tests that only mirror committed build frontend contents without exercising their behavior. --- tool/test/test_mkdepend.rb | 62 -------------------------------------- 1 file changed, 62 deletions(-) diff --git a/tool/test/test_mkdepend.rb b/tool/test/test_mkdepend.rb index 7cb48630259c2b..b4e5f1eaa62a87 100644 --- a/tool/test/test_mkdepend.rb +++ b/tool/test/test_mkdepend.rb @@ -1027,68 +1027,6 @@ def test_run_writes_unchanged_dependencies_to_output_directory end end - def test_build_frontends_use_selected_dependency_directory - common = File.read(File.join(TOP_SRCDIR, 'common.mk')) - configure = File.read(File.join(TOP_SRCDIR, 'configure.ac')) - makefile = File.read(File.join(TOP_SRCDIR, 'template/Makefile.in')) - gnumakefile = File.read(File.join(TOP_SRCDIR, 'template/GNUmakefile.in')) - prereq = File.read(File.join(TOP_SRCDIR, 'tool/prereq.status')) - win32 = File.read(File.join(TOP_SRCDIR, 'win32/Makefile.sub')) - setup = File.read(File.join(TOP_SRCDIR, 'win32/setup.mak')) - mkmf = File.read(File.join(TOP_SRCDIR, 'lib/mkmf.rb')) - configure_ext = File.read( - File.join(TOP_SRCDIR, 'template/configure-ext.mk.tmpl'), - ) - - assert_include(common, '!include $(DEPENDENCIES_DIR)/depend') - assert_include(configure, 'AC_SUBST(X_DEPENDENCIES_DIR)') - assert_include(configure, "X_DEPENDENCIES_DIR='\$X_DEPENDENCIES_DIR'") - assert_not_include(configure, 'AC_SUBST(DEPENDENCIES_DIR)') - assert_include(configure, '--root="$srcdir"') - assert_include(configure, '--scope=core') - assert_include(makefile, 'DEPENDENCIES_DIR = @X_DEPENDENCIES_DIR@') - assert_include(prereq, 's,@X_DEPENDENCIES_DIR@,$(srcdir),g') - assert_include(gnumakefile, 'include $(DEPENDENCIES_DIR)/depend') - assert_match(/filter-out .*DEPENDENCIES_DIR.*common_mk_includes/, gnumakefile) - assert_include(win32, 'DEPENDENCIES_DIR = .deps') - assert_include(win32, 'DEPENDENCIES_DIR = $(srcdir)') - assert_include(setup, '--scope=core') - assert_include(mkmf, 'if arg_config("--update-depend")') - assert_include(mkmf, 'MakeMakefile::Depend.new') - assert_include(configure_ext, '--scope=extensions') - assert_include(configure_ext, '--thread-model=$(THREAD_MODEL)') - assert_match( - %r{tool/mkdepend\.rb.*\n.*--output=\.deps}, - configure_ext, - ) - end - - def test_common_dependency_maintenance_targets - common = File.read(File.join(TOP_SRCDIR, 'common.mk')) - gmake = File.read(File.join(TOP_SRCDIR, 'defs/gmake.mk')) - setup = File.read(File.join(TOP_SRCDIR, 'win32/setup.mak')) - snapshot = File.read(File.join(TOP_SRCDIR, 'tool/make-snapshot')) - - assert_match(/^fix-depends: PHONY$/, common) - assert_match(/^check-depends: PHONY$/, common) - assert_match(/^distclean-local::.*\n\t-\$\(Q\)\$\(RMALL\) \.deps$/, common) - assert_not_match(/^fix-depends:/, gmake) - assert_not_match(/^check-depends:/, gmake) - assert_match(/rm\.bat -f -r \.deps/, setup) - assert_include(setup, '--root=$(srcdir)') - assert_match( - %r{tool[\\/]mkdepend\.rb --root=\$\(srcdir\) --scope=core --nmake --output=\.deps}, - setup, - ) - assert_include(snapshot, 'args["MKDEPEND_FILES"] = "--scope=core"') - assert_include(snapshot, 'args["MKDEPEND_OPTIONS"] = ""') - assert_match( - %r{if File\.file\?\("tool/mkdepend\.rb"\).*make\.run\("fix-depends"\)}m, - snapshot, - ) - assert_not_include(snapshot, 'File.read("defs/gmake.mk")') - end - def test_file_without_markers_is_not_updated Dir.mktmpdir('mkdepend') do |dir| path = File.join(dir, 'depend') From 8268969efb03458deed72c0a5ab79e87e863e148 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 25 Jul 2026 00:57:54 +0900 Subject: [PATCH 2/5] [Misc #22206] Regenerate build dependencies through make Keep the core dependency generation rule in `depend` so configure and Windows setup share the same entry point. Preserve NMake output when the generated dependency file is rebuilt. --- configure.ac | 24 ++++++++++++------------ depend | 9 ++++++++- template/GNUmakefile.in | 2 +- tool/test/test_mkdepend.rb | 4 ++++ win32/Makefile.sub | 1 + win32/setup.mak | 4 ++-- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/configure.ac b/configure.ac index 6fbbb11178d219..0b7bdad5c8cc32 100644 --- a/configure.ac +++ b/configure.ac @@ -4756,12 +4756,6 @@ AS_IF([test ! -f "$srcdir/revision.h"], [ AS_IF([test "x$HAVE_BASERUBY" = xyes], [ X_DEPENDENCIES_DIR=.deps - AC_MSG_NOTICE([generating build dependencies]) - rm -rf "$ac_pwd/.deps" - AS_MKDIR_P([.deps]) - RUBYOPT=- $BASERUBY "$srcdir/tool/mkdepend.rb" \ - --root="$srcdir" --scope=core --output="$ac_pwd/.deps" || - AC_MSG_ERROR([failed to generate build dependencies]) ], [ X_DEPENDENCIES_DIR='$(srcdir)' ]) @@ -4794,14 +4788,19 @@ AC_CONFIG_FILES(Makefile:template/Makefile.in, [ done sed '/^MISSING/s/\$U\././g;/^VCS *=/s#@VCS@#'"$VCS"'#;/^VCSUP *=/s#@VCSUP@#'"$VCSUP"'#' Makefile echo; test x"$EXEEXT" = x || echo 'miniruby: miniruby$(EXEEXT)' + AS_IF([test "x$X_DEPENDENCIES_DIR" = x.deps -a "x$HAVE_BASERUBY" = xyes], [ + AC_MSG_NOTICE([generating build dependencies]) + rm -rf .deps + ${MAKE-make} -f "$srcdir/depend" BASERUBY="$BASERUBY" \ + srcdir="$srcdir" >&AS_MESSAGE_FD || + AC_MSG_ERROR([failed to generate build dependencies]) + dependency_dir="$ac_pwd/.deps" + ], [ + dependency_dir=. + ]) AS_IF([test "$gnumake" != yes], [ # extract NMake-style include list set = `sed -n 's/^!include *//p' ${srcdir}/common.mk` - AS_IF([test "x$X_DEPENDENCIES_DIR" = x.deps], [ - dependency_dir="$ac_pwd/.deps" - ], [ - dependency_dir=. - ]) echo common_mk_includes "@S|@*" # generate the macro assignment shift common_mk_includes="`echo \"@S|@*\" | sed \ @@ -4819,7 +4818,8 @@ AC_CONFIG_FILES(Makefile:template/Makefile.in, [ echo; echo '$(srcdir)/$(CONFIGURE):RUBY_M4_INCLUDED \ $(empty)' } > $tmpmk && mv -f $tmpmk Makefile], -[EXEEXT='$EXEEXT' MAKE='${MAKE-make}' gnumake='$gnumake' GIT='$GIT' YJIT_SUPPORT='$YJIT_SUPPORT' X_DEPENDENCIES_DIR='$X_DEPENDENCIES_DIR']) +[EXEEXT='$EXEEXT' MAKE='${MAKE-make}' gnumake='$gnumake' GIT='$GIT' YJIT_SUPPORT='$YJIT_SUPPORT' + X_DEPENDENCIES_DIR='$X_DEPENDENCIES_DIR' BASERUBY='$BASERUBY' HAVE_BASERUBY='$HAVE_BASERUBY']) AC_ARG_WITH([ruby-pc], AS_HELP_STRING([--with-ruby-pc=FILENAME], [pc file basename]), diff --git a/depend b/depend index b6fd76f3276eb9..c0ce199413539c 100644 --- a/depend +++ b/depend @@ -1,6 +1,7 @@ # mkdepend: scan prism/*.c => prism/templates/src/*.c.erb # mkdepend: scan prism/*.c => prism/templates/ext/prism/*.c.erb -# mkdepend: scan **/*.h => prism/templates/include/**/*.h.erb +# mkdepend: scan prism/*.h => prism/templates/include/prism/*.h.erb +# mkdepend: scan prism/**/*.h => prism/templates/include/prism/**/*.h.erb # mkdepend: scan enc/trans/*.c => enc/trans/*.trans # mkdepend: scan THREAD_IMPL_H => thread_$(THREAD_MODEL).h # mkdepend: scan THREAD_IMPL_SRC => thread_$(THREAD_MODEL).c @@ -35,6 +36,12 @@ # mkdepend: depends vmtc.inc => {$(VPATH)}vmtc.inc # mkdepend: undef parse.y => RIPPER +.deps/depend: $(srcdir)/depend \ + $(srcdir)/tool/mkdepend.rb \ + $(srcdir)/lib/mkmf/depend.rb + $(BASERUBY) "$(srcdir)/tool/mkdepend.rb" --root="$(srcdir)" \ + $(nmake:yes=--nmake) --scope=core --output=$(@D) + # AUTOGENERATED DEPENDENCIES START addr2line.$(OBJEXT): {$(VPATH)}addr2line.c array.$(OBJEXT): {$(VPATH)}array.c diff --git a/template/GNUmakefile.in b/template/GNUmakefile.in index 5ce7ff047c9575..8d85b7b0040bc4 100644 --- a/template/GNUmakefile.in +++ b/template/GNUmakefile.in @@ -35,6 +35,6 @@ endif -include uncommon.mk ifeq ($(DEPENDENCIES_DIR),.deps) -include $(DEPENDENCIES_DIR)/depend +-include $(DEPENDENCIES_DIR)/depend endif include $(srcdir)/defs/gmake.mk diff --git a/tool/test/test_mkdepend.rb b/tool/test/test_mkdepend.rb index b4e5f1eaa62a87..4b8e35bc2bf4c3 100644 --- a/tool/test/test_mkdepend.rb +++ b/tool/test/test_mkdepend.rb @@ -260,6 +260,10 @@ def test_virtual_dependencies_are_declared 'prism/templates/include/prism/ast.h.erb', declarations.scan['prism/ast.h'], ) + assert_equal( + 'prism/templates/include/prism/internal/diagnostic.h.erb', + declarations.scan['prism/internal/diagnostic.h'], + ) assert_equal('thread_pthread.h', declarations.scan['THREAD_IMPL_H']) assert_equal('thread_pthread.c', declarations.scan['THREAD_IMPL_SRC']) assert_equal( diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 237db48ccdda9d..d24e43fcbb9f20 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -12,6 +12,7 @@ TZ = # skip timezone tests PWD = $(MAKEDIR) empty = tooldir = $(srcdir)/tool +nmake = yes PROMPT = +$$S diff --git a/win32/setup.mak b/win32/setup.mak index 7bb2c65784d237..8b3304850964d3 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -57,8 +57,8 @@ prefix = $(prefix:\=/) -dependencies-: -baseruby- !if "$(HAVE_BASERUBY)" != "no" - @$(WIN32DIR:/=\)\rm.bat -f -r .deps - @$(BASERUBY:/=\) $(srcdir)/tool/mkdepend.rb --root=$(srcdir) --scope=core --nmake --output=.deps + @if exist .deps (rd /s /q .deps) + $(MAKE) -f $(srcdir)/depend BASERUBY=$(BASERUBY) srcdir=$(srcdir) nmake=yes !endif -gmp-: From 701f3072028e743434b864903a473b1ad8b7f917 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 15 Aug 2026 01:59:44 +0900 Subject: [PATCH 3/5] [Misc #22206] Cache dependency source directives Avoid repeatedly reading common headers while scanning multiple source files, especially when the source tree is accessed over a network. --- lib/mkmf/depend.rb | 91 ++++++++++++++++++++++++-------------- tool/test/test_mkdepend.rb | 40 +++++++++++++++++ 2 files changed, 98 insertions(+), 33 deletions(-) diff --git a/lib/mkmf/depend.rb b/lib/mkmf/depend.rb index d6c2bd814cc92e..a11cfa93f6c03c 100644 --- a/lib/mkmf/depend.rb +++ b/lib/mkmf/depend.rb @@ -74,7 +74,7 @@ class Scanner # :nodoc: def initialize(root:, include_dirs:, quote_dirs: [], macros: {}, targets: [], aliases: {}, dependencies: {}, defined: [], - undefined: []) + undefined: [], cache: {}) @root = File.expand_path(root) @include_dirs = include_dirs.map {|dir| File.expand_path(dir, @root)} @quote_dirs = quote_dirs.map {|dir| File.expand_path(dir, @root)} @@ -85,6 +85,7 @@ def initialize(root:, include_dirs:, quote_dirs: [], macros: {}, @defined = defined.to_set @undefined = undefined.to_set @unresolved = Set.new + @cache = cache end def scan(source) @@ -97,46 +98,68 @@ def scan(source) def visit(path, dependencies, visited, record: true) path = File.expand_path(path) - return unless File.file?(path) return if visited.include?(path) + directives = source_directives(path) + return unless directives visited.add(path) dependencies.add(relative(path)) if record conditions = [] - File.open(path, "rb") do |file| - file.each_line do |line| - directive = line[/\A\s*#\s*(.*)/, 1] - next unless directive - + directives.each do |directive| + case directive + when /\Aifdef\s+(\w+)/ + conditions << symbol_condition($1) + when /\Aifndef\s+(\w+)/ + condition = symbol_condition($1) + conditions << (condition == :all ? :all : !condition) + when /\Aif\s+(.+)/ + conditions << expression_condition($1) + when /\Aelif\s+(.+)/ + conditions[-1] = if conditions[-1] == true + false + elsif conditions[-1] == false + expression_condition($1) + else + :all + end + when /\Aelse\b/ + conditions[-1] = !conditions[-1] if [true, false].include?(conditions[-1]) + when /\Aendif\b/ + conditions.pop + else + next if conditions.include?(false) case directive - when /\Aifdef\s+(\w+)/ - conditions << symbol_condition($1) - when /\Aifndef\s+(\w+)/ - condition = symbol_condition($1) - conditions << (condition == :all ? :all : !condition) - when /\Aif\s+(.+)/ - conditions << expression_condition($1) - when /\Aelif\s+(.+)/ - conditions[-1] = if conditions[-1] == true - false - elsif conditions[-1] == false - expression_condition($1) - else - :all + when /\A(?:line\s+)?\d+\s+"([^"<>]+)"/ + dependencies.add($1.delete_prefix("./")) + when /\Ainclude(?:_next)?\s+(.+)/ + scan_include($1, path, dependencies, visited) + end + end + end + end + + def source_directives(path) + return @cache[path] if @cache.key?(path) + + @cache[path] = if File.file?(path) + File.open(path, "rb") do |file| + directives = [] + logical_line = "".b + file.each_line do |line| + logical_line << line + next if logical_line.sub!(/\\\r?\n\z/, "") + + if directive = logical_line[/\A\s*#\s*\K.*/] + directives << directive end - when /\Aelse\b/ - conditions[-1] = !conditions[-1] if [true, false].include?(conditions[-1]) - when /\Aendif\b/ - conditions.pop - else - next if conditions.include?(false) - case directive - when /\A(?:line\s+)?\d+\s+"([^"<>]+)"/ - dependencies.add($1.delete_prefix("./")) - when /\Ainclude(?:_next)?\s+(.+)/ - scan_include($1, path, dependencies, visited) + logical_line.clear + end + unless logical_line.empty? + if directive = logical_line[/\A\s*#\s*\K.*/] + directives << directive end end + directives end end end @@ -210,7 +233,7 @@ def resolve(name, current, quoted) dirs.concat(@include_dirs) dirs.each do |dir| path = File.expand_path(name, dir) - return path if File.file?(path) + return path if source_directives(path) end nil end @@ -337,6 +360,7 @@ def initialize(root: DEFAULT_ROOT) @dependency_declarations = {} @dependency_targets = {} @dependency_contents = {} + @source_cache = {} end private @@ -708,6 +732,7 @@ def dependency_scanner(src, declarations, input) dependencies: declarations.dependencies, defined: defined, undefined: undefined, + cache: @source_cache, ) end diff --git a/tool/test/test_mkdepend.rb b/tool/test/test_mkdepend.rb index 4b8e35bc2bf4c3..8dbc352fdf269d 100644 --- a/tool/test/test_mkdepend.rb +++ b/tool/test/test_mkdepend.rb @@ -209,6 +209,46 @@ def test_scanner_prefers_quoted_path end end + def test_scanners_share_source_file_cache + Dir.mktmpdir('mkdepend-scanner') do |dir| + %w[first.c second.c].each do |source| + File.write(File.join(dir, source), "#include \"common.h\"\n") + end + header = File.join(dir, 'common.h') + File.write(header, "#include \"nested.h\"\n") + File.write(File.join(dir, 'nested.h'), "") + + cache = {} + opened = Hash.new(0) + original_open = File.method(:open) + File.define_singleton_method(:open) do |path, *args, **kwargs, &block| + opened[File.expand_path(path)] += 1 + original_open.call(path, *args, **kwargs, &block) + end + + %w[first.c second.c].each do |source| + scanner = Scanner.new(root: dir, include_dirs: [dir], cache: cache) + assert_include(scanner.scan(source), 'common.h') + end + assert_equal(1, opened[header]) + ensure + File.define_singleton_method(:open, original_open) if original_open + end + end + + def test_scanner_joins_continued_directives + Dir.mktmpdir('mkdepend-scanner') do |dir| + File.write(File.join(dir, 'main.c'), <<~'C') + #include \ + "continued.h" + C + File.write(File.join(dir, 'continued.h'), "") + + scanner = Scanner.new(root: dir, include_dirs: [dir]) + assert_include(scanner.scan('main.c'), 'continued.h') + end + end + def test_project_internal_headers_precede_public_headers Dir.mktmpdir('mkdepend-scanner') do |root| %w[ext/example internal include/ruby/internal].each do |dir| From 8b81626c346a7398477d850155cd624066af2f76 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 15 Aug 2026 19:01:48 +0900 Subject: [PATCH 4/5] [Misc #22206] Cache generated dependency freshness Record scanned paths beside generated dependency files so repeated configuration can avoid rescanning unchanged source trees. --- configure.ac | 3 +- depend | 8 +-- lib/mkmf/depend.rb | 99 +++++++++++++++++++++++++++++++++++--- tool/test/test_mkdepend.rb | 90 ++++++++++++++++++++++++++++++++++ win32/setup.mak | 4 +- 5 files changed, 190 insertions(+), 14 deletions(-) diff --git a/configure.ac b/configure.ac index 0b7bdad5c8cc32..125dd098147a4a 100644 --- a/configure.ac +++ b/configure.ac @@ -4790,9 +4790,8 @@ AC_CONFIG_FILES(Makefile:template/Makefile.in, [ echo; test x"$EXEEXT" = x || echo 'miniruby: miniruby$(EXEEXT)' AS_IF([test "x$X_DEPENDENCIES_DIR" = x.deps -a "x$HAVE_BASERUBY" = xyes], [ AC_MSG_NOTICE([generating build dependencies]) - rm -rf .deps ${MAKE-make} -f "$srcdir/depend" BASERUBY="$BASERUBY" \ - srcdir="$srcdir" >&AS_MESSAGE_FD || + srcdir="$srcdir" update-dependencies >&AS_MESSAGE_FD || AC_MSG_ERROR([failed to generate build dependencies]) dependency_dir="$ac_pwd/.deps" ], [ diff --git a/depend b/depend index c0ce199413539c..166d1266703288 100644 --- a/depend +++ b/depend @@ -36,11 +36,11 @@ # mkdepend: depends vmtc.inc => {$(VPATH)}vmtc.inc # mkdepend: undef parse.y => RIPPER -.deps/depend: $(srcdir)/depend \ - $(srcdir)/tool/mkdepend.rb \ - $(srcdir)/lib/mkmf/depend.rb +.deps/depend update-dependencies: $(srcdir)/depend \ + $(srcdir)/tool/mkdepend.rb \ + $(srcdir)/lib/mkmf/depend.rb $(BASERUBY) "$(srcdir)/tool/mkdepend.rb" --root="$(srcdir)" \ - $(nmake:yes=--nmake) --scope=core --output=$(@D) + $(nmake:yes=--nmake) --scope=core --output=.deps # AUTOGENERATED DEPENDENCIES START addr2line.$(OBJEXT): {$(VPATH)}addr2line.c diff --git a/lib/mkmf/depend.rb b/lib/mkmf/depend.rb index a11cfa93f6c03c..55133259ce7d63 100644 --- a/lib/mkmf/depend.rb +++ b/lib/mkmf/depend.rb @@ -70,7 +70,7 @@ class MakeMakefile::Depend private_constant :DEFAULT_ROOT, :Declarations class Scanner # :nodoc: - attr_reader :unresolved + attr_reader :accessed, :unresolved def initialize(root:, include_dirs:, quote_dirs: [], macros: {}, targets: [], aliases: {}, dependencies: {}, defined: [], @@ -84,6 +84,7 @@ def initialize(root:, include_dirs:, quote_dirs: [], macros: {}, @dependencies = dependencies @defined = defined.to_set @undefined = undefined.to_set + @accessed = Set.new @unresolved = Set.new @cache = cache end @@ -139,6 +140,8 @@ def visit(path, dependencies, visited, record: true) end def source_directives(path) + path = File.expand_path(path) + @accessed.add(path) return @cache[path] if @cache.key?(path) @cache[path] = if File.file?(path) @@ -190,7 +193,7 @@ def expression_condition(expression) def scan_include(argument, current, dependencies, visited) case argument.sub(%r{//.*\z}, '').strip - when /\A([<"])([^>"]+)[>"]/ + when /\A([<"])([^<>"]+)[>"]/ quoted = $1 == '"' name = $2 when /\A([A-Za-z_]\w*)/ @@ -361,6 +364,7 @@ def initialize(root: DEFAULT_ROOT) @dependency_targets = {} @dependency_contents = {} @source_cache = {} + @stat_cache = {} end private @@ -737,7 +741,8 @@ def dependency_scanner(src, declarations, input) end # Appends Make dependency rules for +src+ to +out+ and returns +out+. - def makedepend(src, out = [], target: nil, input: nil, project: false) + def makedepend(src, out = [], target: nil, input: nil, project: false, + accessed: nil) src = relative_dependency(src) declaration_input = input || dependency_input(src) declarations = dependency_declarations(declaration_input, source: src) @@ -750,6 +755,7 @@ def makedepend(src, out = [], target: nil, input: nil, project: false) src end files = scanner.scan(scan_source) + accessed&.merge(scanner.accessed) if dependencies = declaration(declarations.dependencies, src, declaration_input) files.concat(dependencies[1]) @@ -920,7 +926,7 @@ def minimize_deps(rules, input) end # Regenerates dependency +rules+ for the sources identified in +input+. - def update_deps(rules, input, group: true, verbose: false) + def update_deps(rules, input, group: true, verbose: false, accessed: nil) sources = dependency_source_map(rules, input) targets = rules.scan(%r[^([-.\w/]+)\.(?:\$\(OBJEXT\)|o):]).flatten.uniq unless (missing = targets - sources.keys).empty? @@ -930,7 +936,9 @@ def update_deps(rules, input, group: true, verbose: false) sources.each do |target, src| src = resolve_dependency_source(src, input) warn "dependencies for #{src}:" if verbose - makedepend(src, generated, target: target, input: input) + makedepend( + src, generated, target: target, input: input, accessed: accessed, + ) end compact_dependencies(generated.join, group: group) end @@ -991,6 +999,68 @@ def replace_file(path, content) end end + DEPENDENCY_CACHE_VERSION = 1 # :nodoc: + private_constant :DEPENDENCY_CACHE_VERSION + + # Returns the cache signature for options affecting generated dependencies. + def dependency_cache_signature(nmake:, sources:) + [DEPENDENCY_CACHE_VERSION, nmake, sources, @make_variables.sort].inspect + end + + # Returns cached stat information, or +false+ when +path+ does not exist. + def dependency_stat(path) + path = File.expand_path(path) + return @stat_cache[path] if @stat_cache.key?(path) + + @stat_cache[path] = File.stat(path) + rescue Errno::ENOENT, Errno::ENOTDIR + @stat_cache[path] = false + end + + # Returns whether +destination+ and all recorded scan inputs are unchanged. + def dependency_output_fresh?(destination, signature) + cache_path = destination + ".mkdepend" + return false unless (output_stat = dependency_stat(destination)) + return false unless File.file?(cache_path) + + lines = File.readlines(cache_path, chomp: true) + header = lines.shift&.split("\t", 5) + return false unless header == [ + "mkdepend", signature, output_stat.size.to_s, + output_stat.mtime.to_i.to_s, output_stat.mtime.nsec.to_s, + ] + + lines.all? do |line| + state, path = line.split("\t", 2) + next false unless path + + stat = dependency_stat(path) + state == "+" ? stat && stat.mtime <= output_stat.mtime : !stat + end + rescue Errno::ENOENT + false + end + + # Records scan inputs used to produce +destination+. + def write_dependency_cache(destination, signature, paths) + output_stat = File.stat(destination) + lines = [ + [ + "mkdepend", signature, output_stat.size, + output_stat.mtime.to_i, output_stat.mtime.nsec, + ].join("\t") + "\n", + ] + paths.sort.each do |path| + exists = if @source_cache.key?(path) + @source_cache[path] + else + File.file?(path) + end + lines << "#{exists ? '+' : '-'}\t#{path}\n" + end + replace_file(destination + ".mkdepend", lines.join) + end + # Reports how dependency rules in +input+ differ from +expected+. def report_outdated_dependencies(input, current, expected, err: $stderr) current_lines = current.lines @@ -1021,6 +1091,8 @@ def report_outdated_dependencies(input, current, expected, err: $stderr) def run(inputs = ARGV, out: $stdout, err: $stderr, mode: :stdout, output: nil, make_variables: {}, nmake: false, sources: false, scope: nil, thread_model: nil, verbose: false) + @source_cache.clear + @stat_cache.clear case mode when :output raise ArgumentError, "output directory is missing" unless output @@ -1040,6 +1112,9 @@ def run(inputs = ARGV, out: $stdout, err: $stderr, mode: :stdout, inputs = dependency_files(scope).map {|file| File.join(@root, file)} end output = File.expand_path(output) if output + cache_signature = dependency_cache_signature( + nmake: nmake, sources: sources, + ) changed = false inputs.each do |input| if input.end_with?(".c", ".y") @@ -1052,15 +1127,27 @@ def run(inputs = ARGV, out: $stdout, err: $stderr, mode: :stdout, next unless match raise "missing #{MARK_END} in #{input}" unless match.begin(1) + destination = File.join(output, relative_source(input)) if output + if destination && dependency_output_fresh?(destination, cache_signature) + next + end + current_rules = match[0] + accessed = Set.new expected = update_deps( current_rules, input, group: !nmake, verbose: verbose, + accessed: accessed, ) expected = minimize_deps(expected, input) if sources updated = match.pre_match + expected + match.post_match if mode == :output updated = normalize_dependency_rules(updated) unless nmake - replace_file(File.join(output, relative_source(input)), updated) + replace_file(destination, updated) + accessed.add(File.expand_path(input)) + accessed.add(File.join(@root, "depend")) + accessed.add(File.expand_path(__FILE__)) + accessed.add(File.join(@root, "tool/mkdepend.rb")) + write_dependency_cache(destination, cache_signature, accessed) elsif same_dependency_rules?(current_rules, expected) next elsif mode == :inplace diff --git a/tool/test/test_mkdepend.rb b/tool/test/test_mkdepend.rb index 8dbc352fdf269d..e33a861e5c20c4 100644 --- a/tool/test/test_mkdepend.rb +++ b/tool/test/test_mkdepend.rb @@ -249,6 +249,17 @@ def test_scanner_joins_continued_directives end end + def test_scanner_ignores_template_include + Dir.mktmpdir('mkdepend-scanner') do |dir| + File.write(File.join(dir, 'main.c'), "#include <<%= header %>>\n") + + scanner = Scanner.new(root: dir, include_dirs: [dir]) + + assert_equal(%w[main.c], scanner.scan('main.c')) + assert_empty(scanner.unresolved) + end + end + def test_project_internal_headers_precede_public_headers Dir.mktmpdir('mkdepend-scanner') do |root| %w[ext/example internal include/ruby/internal].each do |dir| @@ -994,6 +1005,85 @@ def test_run_writes_expanded_dependency_to_output_directory end end + def test_run_reuses_unchanged_output_dependencies + Dir.mktmpdir('mkdepend-input') do |root| + File.write(File.join(root, 'one.c'), "#include \"common.h\"\n") + header = File.join(root, 'common.h') + File.write(header, "") + input = File.join(root, 'depend') + File.write(input, <<~DEPEND) + #{MARK_START} + one.$(OBJEXT): one.c + #{MARK_END} + DEPEND + output = File.join(root, '.deps') + destination = File.join(output, 'depend') + + updater = TestDepend.new(root: root) + assert_true(updater.run([input], mode: :output, output: output)) + assert_path_exist(destination + '.mkdepend') + + opened = [] + original_open = File.method(:open) + File.define_singleton_method(:open) do |path, *args, **kwargs, &block| + opened << File.expand_path(path) + original_open.call(path, *args, **kwargs, &block) + end + updater = TestDepend.new(root: root) + assert_true(updater.run([input], mode: :output, output: output)) + assert_empty(opened & [File.join(root, 'one.c'), header]) + + future = Time.now + 2 + File.utime(future, future, header) + updater = TestDepend.new(root: root) + assert_true(updater.run([input], mode: :output, output: output)) + assert_include(opened, header) + ensure + File.define_singleton_method(:open, original_open) if original_open + end + end + + def test_run_invalidates_output_when_missing_header_appears + Dir.mktmpdir('mkdepend-input') do |root| + File.write(File.join(root, 'one.c'), "#include \"appeared.h\"\n") + input = File.join(root, 'depend') + File.write(input, <<~DEPEND) + #{MARK_START} + one.$(OBJEXT): one.c + #{MARK_END} + DEPEND + output = File.join(root, '.deps') + destination = File.join(output, 'depend') + updater = TestDepend.new(root: root) + + assert_true(updater.run([input], mode: :output, output: output)) + assert_not_include(File.read(destination), 'appeared.h') + + File.write(File.join(root, 'appeared.h'), "") + assert_true(updater.run([input], mode: :output, output: output)) + assert_include(File.read(destination), 'one.$(OBJEXT): appeared.h') + end + end + + def test_run_reuses_output_with_non_directory_include_prefix + Dir.mktmpdir('mkdepend-input') do |root| + File.write(File.join(root, 'ruby'), "") + File.write(File.join(root, 'one.c'), "#include \"ruby/assert.h\"\n") + input = File.join(root, 'depend') + File.write(input, <<~DEPEND) + #{MARK_START} + one.$(OBJEXT): one.c + #{MARK_END} + DEPEND + output = File.join(root, '.deps') + updater = TestDepend.new(root: root) + + assert_true(updater.run([input], mode: :output, output: output)) + updater = TestDepend.new(root: root) + assert_true(updater.run([input], mode: :output, output: output)) + end + end + def test_run_preserves_vpath_notation_for_nmake_output Dir.mktmpdir('mkdepend-output') do |dir| source = File.join(TOP_SRCDIR, 'ext/-test-/thread/id/depend') diff --git a/win32/setup.mak b/win32/setup.mak index 8b3304850964d3..cb3bc7f7c41739 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -57,8 +57,8 @@ prefix = $(prefix:\=/) -dependencies-: -baseruby- !if "$(HAVE_BASERUBY)" != "no" - @if exist .deps (rd /s /q .deps) - $(MAKE) -f $(srcdir)/depend BASERUBY=$(BASERUBY) srcdir=$(srcdir) nmake=yes + @$(BASERUBY:/=\) $(srcdir)/tool/mkdepend.rb --root=$(srcdir) \ + --scope=core --nmake --output=.deps !endif -gmp-: From fb99639ed91923105cd5c1224e6c002773b40a7d Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 16 Aug 2026 11:54:48 +0200 Subject: [PATCH 5/5] Get rid of `rb_hash_new_with_size` It's redundant with `rb_hash_new_capa` which is public API, might as well use that one. --- array.c | 4 ++-- box.c | 8 ++++---- compile.c | 2 +- enumerator.c | 2 +- gc/default/default.c | 6 +++--- hash.c | 18 ++++++------------ internal/hash.h | 3 +-- iseq.c | 2 +- marshal.c | 6 +++--- re.c | 10 +++++----- set.c | 6 +++--- struct.c | 8 ++++---- time.c | 4 ++-- vm.c | 2 +- vm_args.c | 4 ++-- vm_insnhelper.c | 4 ++-- yjit/bindgen/src/main.rs | 2 +- yjit/src/codegen.rs | 8 ++++---- yjit/src/cruby_bindings.inc.rs | 3 +-- zjit/bindgen/src/main.rs | 2 +- zjit/src/codegen.rs | 4 ++-- zjit/src/cruby_bindings.inc.rs | 3 +-- 22 files changed, 51 insertions(+), 60 deletions(-) diff --git a/array.c b/array.c index b6344fbdca7677..50f4b3ead89b58 100644 --- a/array.c +++ b/array.c @@ -3303,7 +3303,7 @@ static VALUE rb_ary_to_h(VALUE ary) { long i; - VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary)); + VALUE hash = rb_hash_new_capa(RARRAY_LEN(ary)); int block_given = rb_block_given_p(); for (i=0; iloaded_features_realpaths = rb_hash_dup(master->loaded_features_realpaths); box->loaded_features_realpath_map = rb_hash_dup(master->loaded_features_realpath_map); box->loading_table = st_init_strtable(); - box->ruby_dln_libmap = rb_hash_new_with_size(0); - box->gvar_tbl = rb_hash_new_with_size(0); + box->ruby_dln_libmap = rb_hash_new(); + box->gvar_tbl = rb_hash_new(); box->classext_cow_classes = st_init_numtable(); box->is_user = true; @@ -902,8 +902,8 @@ initialize_master_box(void) master->loaded_features_realpath_map = rb_hash_new(); rb_obj_hide(master->loaded_features_realpath_map); - master->ruby_dln_libmap = rb_hash_new_with_size(0); - master->gvar_tbl = rb_hash_new_with_size(0); + master->ruby_dln_libmap = rb_hash_new(); + master->gvar_tbl = rb_hash_new(); master->classext_cow_classes = NULL; // classext CoW never happen on the master box vm->master_box = master; diff --git a/compile.c b/compile.c index db02361d018c4a..5cc888bcc39cf2 100644 --- a/compile.c +++ b/compile.c @@ -14428,7 +14428,7 @@ static VALUE ibf_load_object_hash(const struct ibf_load *load, const struct ibf_object_header *header, ibf_offset_t offset) { long len = (long)ibf_load_small_value(load, &offset); - VALUE obj = header->frozen ? rb_hash_alloc_fixed_size(rb_cHash, len) : rb_hash_new_with_size(len); + VALUE obj = header->frozen ? rb_hash_alloc_fixed_size(rb_cHash, len) : rb_hash_new_capa(len); int i; for (i = 0; i < len; i++) { diff --git a/enumerator.c b/enumerator.c index cf2716716b9b89..94c0fa3a03a05d 100644 --- a/enumerator.c +++ b/enumerator.c @@ -4671,7 +4671,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "with_index", lazy_with_index, -1); rb_define_method(rb_cLazy, "tap_each", lazy_tap_each, 0); - lazy_use_super_method = rb_hash_new_with_size(18); + lazy_use_super_method = rb_hash_new_capa(18); rb_hash_aset(lazy_use_super_method, sym("map"), sym("_enumerable_map")); rb_hash_aset(lazy_use_super_method, sym("collect"), sym("_enumerable_collect")); rb_hash_aset(lazy_use_super_method, sym("flat_map"), sym("_enumerable_flat_map")); diff --git a/gc/default/default.c b/gc/default/default.c index 21833210446f8e..349afb0030528c 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -10042,7 +10042,7 @@ type_name(int type, VALUE obj) static void gc_count_add_each_types(VALUE hash, const char *name, const size_t *types) { - VALUE result = rb_hash_new_with_size(T_MASK); + VALUE result = rb_hash_new_capa(T_MASK); int i; for (i=0; imem)) #define SET_COMPILE_OPTION_NUM(o, h, mem) \ diff --git a/marshal.c b/marshal.c index 3175ce4c9bf0ea..f86e50faffcb31 100644 --- a/marshal.c +++ b/marshal.c @@ -1874,7 +1874,7 @@ r_object0(struct load_arg *arg, bool partial, int *ivp, VALUE extmod) static VALUE r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE klass, VALUE extmod, int type) { - VALUE (*hash_new_with_size)(st_index_t) = rb_hash_new_with_size; + VALUE (*hash_new_capa)(long) = rb_hash_new_capa; VALUE v = Qnil; long id; st_data_t link; @@ -1950,7 +1950,7 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE klass, VALUE ex if ((c == rb_cHash) && /* Hack for compare_by_identity */ (type == TYPE_HASH || type == TYPE_HASH_DEF)) { - hash_new_with_size = rb_ident_hash_new_with_size; + hash_new_capa = rb_ident_hash_new_capa; goto type_hash; } v = r_object_for(arg, partial, 0, c, extmod, type); @@ -2126,7 +2126,7 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE klass, VALUE ex { long len = r_keep_readable(arg, r_long(arg), 2); - v = hash_new_with_size(len); + v = hash_new_capa(len); v = r_entry(v, arg); arg->readable += (len - 1) * 2; while (len--) { diff --git a/re.c b/re.c index 3e4114976e952d..332b850d10287c 100644 --- a/re.c +++ b/re.c @@ -893,7 +893,7 @@ static VALUE rb_reg_named_captures(VALUE re) { regex_t *reg = (rb_reg_check(re), RREGEXP_PTR(re)); - VALUE hash = rb_hash_new_with_size(onig_number_of_names(reg)); + VALUE hash = rb_hash_new_capa(onig_number_of_names(reg)); onig_foreach_name(reg, reg_named_captures_iter, (void*)hash); return hash; } @@ -2669,11 +2669,11 @@ match_deconstruct_keys(VALUE match, VALUE keys) match_check(match); if (NIL_P(RMATCH(match)->regexp)) { - return rb_hash_new_with_size(0); + return rb_hash_new(); } if (NIL_P(keys)) { - h = rb_hash_new_with_size(onig_number_of_names(RREGEXP_PTR(RMATCH(match)->regexp))); + h = rb_hash_new_capa(onig_number_of_names(RREGEXP_PTR(RMATCH(match)->regexp))); struct named_captures_data data = { h, match, 1 }; @@ -2685,10 +2685,10 @@ match_deconstruct_keys(VALUE match, VALUE keys) Check_Type(keys, T_ARRAY); if (onig_number_of_names(RREGEXP_PTR(RMATCH(match)->regexp)) < RARRAY_LEN(keys)) { - return rb_hash_new_with_size(0); + return rb_hash_new(); } - h = rb_hash_new_with_size(RARRAY_LEN(keys)); + h = rb_hash_new_capa(RARRAY_LEN(keys)); for (i=0; ivtm.yday != 0); if (NIL_P(keys)) { - h = rb_hash_new_with_size(11); + h = rb_hash_new_capa(11); rb_hash_aset(h, sym_year, tobj->vtm.year); rb_hash_aset(h, sym_month, INT2FIX(tobj->vtm.mon)); @@ -5215,7 +5215,7 @@ time_deconstruct_keys(VALUE time, VALUE keys) } - h = rb_hash_new_with_size(RARRAY_LEN(keys)); + h = rb_hash_new_capa(RARRAY_LEN(keys)); for (i=0; idefault_params.name)); SET(thread_vm_stack_size); SET(thread_machine_stack_size); diff --git a/vm_args.c b/vm_args.c index 1b8d10a159e858..2610d99c229b5e 100644 --- a/vm_args.c +++ b/vm_args.c @@ -192,7 +192,7 @@ args_kw_argv_to_hash(struct args_info *args) const struct rb_callinfo_kwarg *kw_arg = args->kw_arg; const VALUE *const passed_keywords = kw_arg->keywords; const int kw_len = kw_arg->keyword_len; - VALUE h = rb_hash_new_with_size(kw_len); + VALUE h = rb_hash_new_capa(kw_len); const int kw_start = args->argc - kw_len; const VALUE * const kw_argv = args->argv + kw_start; int i; @@ -293,7 +293,7 @@ static VALUE make_rest_kw_hash(const VALUE *passed_keywords, int passed_keyword_len, const VALUE *kw_argv) { int i; - VALUE obj = rb_hash_new_with_size(passed_keyword_len); + VALUE obj = rb_hash_new_capa(passed_keyword_len); for (i=0; ikeywords; const int kw_len = vm_ci_kwarg(ci)->keyword_len; - const VALUE h = rb_hash_new_with_size(kw_len); + const VALUE h = rb_hash_new_capa(kw_len); VALUE *sp = cfp->sp; int i; @@ -6529,7 +6529,7 @@ vm_opt_newarray_pack_buffer(rb_execution_context_t *ec, rb_num_t array_len, cons int argc = 1; if (!UNDEF_P(buffer)) { - args[1] = rb_hash_new_with_size(1); + args[1] = rb_hash_new_capa(1); rb_hash_aset(args[1], ID2SYM(idBuffer), buffer); kw_splat = RB_PASS_KEYWORDS; argc++; diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 36f25be53edc0e..c5b5fbf451e5eb 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -98,7 +98,7 @@ fn main() { .allowlist_function("rb_obj_frozen_p") .allowlist_type("ruby_encoding_consts") .allowlist_function("rb_hash_new") - .allowlist_function("rb_hash_new_with_size") + .allowlist_function("rb_hash_new_capa") .allowlist_function("rb_hash_resurrect") .allowlist_function("rb_to_hash_type") .allowlist_type("st_retval") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 70c342e1440730..556181e3e36bf2 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2725,9 +2725,9 @@ fn gen_newhash( jit_prepare_call_with_gc(jit, asm); if num != 0 { - // val = rb_hash_new_with_size(num / 2); + // val = rb_hash_new_capa(num / 2); let new_hash = asm.ccall( - rb_hash_new_with_size as *const u8, + rb_hash_new_capa as *const u8, vec![Opnd::UImm(num / 2)] ); @@ -6770,7 +6770,7 @@ fn c_method_tracing_currently_enabled(jit: &JITState) -> bool { unsafe extern "C" fn build_kwhash(ci: *const rb_callinfo, sp: *const VALUE) -> VALUE { let kw_arg = vm_ci_kwarg(ci); let kw_len: usize = get_cikw_keyword_len(kw_arg).try_into().unwrap(); - let hash = rb_hash_new_with_size(kw_len as u64); + let hash = rb_hash_new_capa(kw_len as i64); for kwarg_idx in 0..kw_len { let key = get_cikw_keywords_idx(kw_arg, kwarg_idx.try_into().unwrap()); @@ -8588,7 +8588,7 @@ fn gen_iseq_kw_call( // Use the total number of supplied keywords as a size upper bound let keyword_len = unsafe { (*keywords).keyword_len } as usize; - let hash = unsafe { rb_hash_new_with_size(keyword_len as u64) }; + let hash = unsafe { rb_hash_new_capa(keyword_len as i64) }; // Put pairs into the kwrest hash as the mask describes for kwarg_idx in 0..keyword_len { diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 1b3e707de2468a..9a915a14ebc2ef 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -257,7 +257,6 @@ pub const RSTRING_NOEMBED: ruby_rstring_flags = 8192; pub const RSTRING_FSTR: ruby_rstring_flags = 536870912; pub type ruby_rstring_flags = u32; pub type st_data_t = ::std::os::raw::c_ulong; -pub type st_index_t = st_data_t; pub const ST_CONTINUE: st_retval = 0; pub const ST_STOP: st_retval = 1; pub const ST_DELETE: st_retval = 2; @@ -1055,6 +1054,7 @@ extern "C" { pub fn rb_ary_push(ary: VALUE, elem: VALUE) -> VALUE; pub fn rb_ary_clear(ary: VALUE) -> VALUE; pub fn rb_hash_new() -> VALUE; + pub fn rb_hash_new_capa(capa: ::std::os::raw::c_long) -> VALUE; pub fn rb_hash_aref(hash: VALUE, key: VALUE) -> VALUE; pub fn rb_hash_aset(hash: VALUE, key: VALUE, val: VALUE) -> VALUE; pub fn rb_hash_bulk_insert(argc: ::std::os::raw::c_long, argv: *const VALUE, hash: VALUE); @@ -1158,7 +1158,6 @@ extern "C" { chilled: bool, ) -> VALUE; pub fn rb_to_hash_type(obj: VALUE) -> VALUE; - pub fn rb_hash_new_with_size(size: st_index_t) -> VALUE; pub fn rb_hash_resurrect(hash: VALUE) -> VALUE; pub fn rb_hash_stlike_lookup( hash: VALUE, diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 84096a86bc6db2..782df9f2dd7812 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -139,7 +139,7 @@ fn main() { .allowlist_function("rb_class_real") .allowlist_type("ruby_encoding_consts") .allowlist_function("rb_hash_new") - .allowlist_function("rb_hash_new_with_size") + .allowlist_function("rb_hash_new_capa") .allowlist_function("rb_hash_resurrect") .allowlist_function("rb_hash_stlike_foreach") .allowlist_function("rb_to_hash_type") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8c20f84789d0e7..77086268f2775e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2556,10 +2556,10 @@ fn gen_new_hash( asm.store(Opnd::mem(VALUE_BITS, hash, RUBY_OFFSET_RHASH_IFNONE), Qnil.into()); }, |asm| { - asm_ccall!(asm, rb_hash_new_with_size, num_pairs.into()) + asm_ccall!(asm, rb_hash_new_capa, num_pairs.into()) }) } else { - asm_ccall!(asm, rb_hash_new_with_size, num_pairs.into()) + asm_ccall!(asm, rb_hash_new_capa, num_pairs.into()) }; let argv = gen_push_opnds(jit, asm, &elements); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index ac52ab8a7a32fe..3513f5dabe051d 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -326,7 +326,6 @@ pub const RSTRING_NOEMBED: ruby_rstring_flags = 8192; pub const RSTRING_FSTR: ruby_rstring_flags = 536870912; pub type ruby_rstring_flags = u32; pub type st_data_t = ::std::os::raw::c_ulong; -pub type st_index_t = st_data_t; pub const ST_CONTINUE: st_retval = 0; pub const ST_STOP: st_retval = 1; pub const ST_DELETE: st_retval = 2; @@ -2134,6 +2133,7 @@ unsafe extern "C" { pub fn rb_ary_clear(ary: VALUE) -> VALUE; pub fn rb_ary_concat(lhs: VALUE, rhs: VALUE) -> VALUE; pub fn rb_hash_new() -> VALUE; + pub fn rb_hash_new_capa(capa: ::std::os::raw::c_long) -> VALUE; pub fn rb_hash_aref(hash: VALUE, key: VALUE) -> VALUE; pub fn rb_hash_aset(hash: VALUE, key: VALUE, val: VALUE) -> VALUE; pub fn rb_hash_bulk_insert(argc: ::std::os::raw::c_long, argv: *const VALUE, hash: VALUE); @@ -2262,7 +2262,6 @@ unsafe extern "C" { func: st_foreach_callback_func, arg: st_data_t, ) -> ::std::os::raw::c_int; - pub fn rb_hash_new_with_size(size: st_index_t) -> VALUE; pub fn rb_hash_new_with_bulk_insert(argc: ::std::os::raw::c_long, argv: *const VALUE) -> VALUE; pub fn rb_hash_resurrect(hash: VALUE) -> VALUE; pub fn rb_hash_stlike_lookup(