From 23c04a9be7a842c3897ee7a8c926353b607bf7dc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Sep 2025 23:18:17 +0900 Subject: [PATCH 01/32] [DOC] Markup as markdown --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7157700816c3e8..7534539a21d2c9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -63,7 +63,7 @@ Note: We're only listing outstanding class updates. * IO - * `IO.select` accepts +Float::INFINITY+ as a timeout argument. + * `IO.select` accepts `Float::INFINITY` as a timeout argument. [[Feature #20610]] * Math From 6c34880111a0978407224fc353c35f2a3a0a981a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 15 Sep 2025 10:18:46 -0400 Subject: [PATCH 02/32] [ruby/prism] Fix character literal forced encoding If a character literal was followed by a string concatenation, then the forced encoding of the string concatenation could accidentally overwrite the explicit encoding of the character literal. We now handle this properly. https://github.com/ruby/prism/commit/125c375d74 --- prism/prism.c | 30 ++++++++++++++--------- test/prism/fixtures/character_literal.txt | 2 ++ test/prism/ruby/ruby_parser_test.rb | 1 + 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 test/prism/fixtures/character_literal.txt diff --git a/prism/prism.c b/prism/prism.c index 06419d13789e45..2e202c37456ea5 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -18491,20 +18491,28 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b return (pm_node_t *) node; } case PM_TOKEN_CHARACTER_LITERAL: { - parser_lex(parser); - - pm_token_t opening = parser->previous; - opening.type = PM_TOKEN_STRING_BEGIN; - opening.end = opening.start + 1; - - pm_token_t content = parser->previous; - content.type = PM_TOKEN_STRING_CONTENT; - content.start = content.start + 1; - pm_token_t closing = not_provided(parser); - pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &content, &closing); + pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string( + parser, + &(pm_token_t) { + .type = PM_TOKEN_STRING_BEGIN, + .start = parser->current.start, + .end = parser->current.start + 1 + }, + &(pm_token_t) { + .type = PM_TOKEN_STRING_CONTENT, + .start = parser->current.start + 1, + .end = parser->current.end + }, + &closing + ); + pm_node_flag_set(node, parse_unescaped_encoding(parser)); + // Skip past the character literal here, since now we have handled + // parser->explicit_encoding correctly. + parser_lex(parser); + // Characters can be followed by strings in which case they are // automatically concatenated. if (match1(parser, PM_TOKEN_STRING_BEGIN)) { diff --git a/test/prism/fixtures/character_literal.txt b/test/prism/fixtures/character_literal.txt new file mode 100644 index 00000000000000..920332123f13ac --- /dev/null +++ b/test/prism/fixtures/character_literal.txt @@ -0,0 +1,2 @@ +# encoding: Windows-31J +p ?\u3042"" diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index bcaed7979150bc..b21ad81391ed1e 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -16,6 +16,7 @@ module Prism class RubyParserTest < TestCase todos = [ + "character_literal.txt", "encoding_euc_jp.txt", "regex_char_width.txt", "seattlerb/masgn_colon3.txt", From b08573c8150c97822b05b743d5ebb8c4fff5315f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:43:34 +0200 Subject: [PATCH 03/32] [ruby/prism] Fix back reference for ruby_parser on Ruby 2.7 Symbol#name is only a thing since Ruby 3.0 https://github.com/ruby/prism/commit/2de82b15fc --- lib/prism/translation/ruby_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index ac538a2e97ae43..2ca7da0bf2a5d5 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -152,7 +152,7 @@ def visit_assoc_splat_node(node) # ^^ # ``` def visit_back_reference_read_node(node) - s(node, :back_ref, node.name.name.delete_prefix("$").to_sym) + s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym) end # ``` From 61df125325c4f5cad0fd63a831f7afd4c3e71dba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 00:11:14 +0900 Subject: [PATCH 04/32] [DOC] Markup code in `Float::MIN` document --- numeric.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index 89cff8a730fc9c..de5b02aaf9eb41 100644 --- a/numeric.c +++ b/numeric.c @@ -6455,7 +6455,7 @@ Init_Numeric(void) * * If the platform supports denormalized numbers, * there are numbers between zero and Float::MIN. - * 0.0.next_float returns the smallest positive floating point number + * +0.0.next_float+ returns the smallest positive floating point number * including denormalized numbers. */ rb_define_const(rb_cFloat, "MIN", DBL2NUM(DBL_MIN)); From 1e3e04cd657c35fdd8d95096195d6b72b64e516c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 15 Sep 2025 10:47:14 -0400 Subject: [PATCH 05/32] Move rb_imemo_tmpbuf_new to imemo.c --- imemo.c | 6 ++++++ internal/imemo.h | 7 +------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/imemo.c b/imemo.c index 1bef7a71a69e77..1fc49c434af75e 100644 --- a/imemo.c +++ b/imemo.c @@ -48,6 +48,12 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) return (VALUE)obj; } +VALUE +rb_imemo_tmpbuf_new(void) +{ + return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); +} + void * rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) { diff --git a/internal/imemo.h b/internal/imemo.h index 3673190809e86a..de617d94c1bf5c 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -132,6 +132,7 @@ struct MEMO { #ifndef RUBY_RUBYPARSER_H typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif +VALUE rb_imemo_tmpbuf_new(void); rb_imemo_tmpbuf_t *rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); @@ -198,12 +199,6 @@ rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data) return rb_vm_ifunc_new(func, data, 0, UNLIMITED_ARGUMENTS); } -static inline VALUE -rb_imemo_tmpbuf_new(void) -{ - return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); -} - static inline void * RB_IMEMO_TMPBUF_PTR(VALUE v) { From 7dd9c76ad46af63f76e0df243f76a1720f54d50d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 15 Sep 2025 10:50:01 -0400 Subject: [PATCH 06/32] Make imemo_tmpbuf not write-barrier protected imemo_tmpbuf is not write-barrier protected and uses mark maybe to mark the buffer it holds. The normal rb_imemo_new creates a write-barrier protected object which can make the tmpbuf miss marking references. --- imemo.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imemo.c b/imemo.c index 1fc49c434af75e..7cec33bc1edf00 100644 --- a/imemo.c +++ b/imemo.c @@ -51,7 +51,10 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) VALUE rb_imemo_tmpbuf_new(void) { - return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); + VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); + NEWOBJ_OF(obj, rb_imemo_tmpbuf_t, 0, flags, sizeof(rb_imemo_tmpbuf_t), NULL); + + return (VALUE)obj; } void * From 061f9b8bfdde47371df97a1d3d6a4c4eec06b723 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 15 Sep 2025 17:48:56 -0500 Subject: [PATCH 07/32] [ruby/erb] [DOC] More on class ERB (https://github.com/ruby/erb/pull/69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [DOC] More on class …ERB * [DOC] More on class …ERB * More * More * More https://github.com/ruby/erb/commit/d9d73ed58e --- lib/erb.rb | 121 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/lib/erb.rb b/lib/erb.rb index d4f43e0772341b..f3f2ae3f471811 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -189,7 +189,17 @@ # # When you call method #result, # the method executes the code and removes the entire execution tag -# (generating no text in the result). +# (generating no text in the result): +# +# ``` +# ERB.new('foo <% Dir.chdir("C:/") %> bar').result # => "foo bar" +# ``` +# +# Whitespace before and after the embedded code is optional: +# +# ``` +# ERB.new('foo <%Dir.chdir("C:/")%> bar').result # => "foo bar" +# ``` # # You can interleave text with execution tags to form a control structure # such as a conditional, a loop, or a `case` statements. @@ -261,7 +271,7 @@ # # #### Shorthand Format for Execution Tags # -# You can give `trim_mode: '%'` to enable a shorthand format for execution tags; +# You can use keyword argument `trim_mode: '%'` to enable a shorthand format for execution tags; # this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: # # ``` @@ -283,6 +293,90 @@ # Note that in the shorthand format, the character `'%'` must be the first character in the code line # (no leading whitespace). # +# #### Suppressing Unwanted Blank Lines +# +# With keyword argument `trim_mode` not given, +# all blank lines go into the result: +# +# ``` +# s = < +# <%= RUBY_VERSION %> +# <% end %> +# EOT +# ERB.new(s).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "\n" +# ``` +# +# You can give `trim_mode: '-'`, you can suppress each blank line +# whose source line ends with `-%>` (instead of `%>`): +# +# ``` +# s = < +# <%= RUBY_VERSION %> +# <% end -%> +# EOT +# ERB.new(s, trim_mode: '-').result.lines.each {|line| puts line.inspect } +# "3.4.5\n" +# ``` +# +# It is an error to use the trailing `'-%>'` notation without `trim_mode: '-'`: +# +# ``` +# ERB.new(s).result.lines.each {|line| puts line.inspect } # Raises SyntaxError. +# ``` +# +# #### Suppressing Unwanted Newlines +# +# Consider this input string: +# +# ``` +# s = < +# <%= RUBY_VERSION %> +# foo <% RUBY_VERSION %> +# foo <%= RUBY_VERSION %> +# EOT +# ``` +# +# With keyword argument `trim_mode` not given, all newlines go into the result: +# +# ``` +# ERB.new(s).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "foo \n" +# "foo 3.4.5\n" +# ``` +# +# You can give `trim_mode: '>'` to suppress the trailing newline +# for each line that ends with `'%<'` (regardless of its beginning): +# +# ``` +# ERB.new(s, trim_mode: '>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo foo 3.4.5" +# ``` +# +# You can give `trim_mode: '<>'` to suppress the trailing newline +# for each line that both begins with `'<%'` and ends with `'%<'`: +# +# ``` +# ERB.new(s, trim_mode: '<>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo \n" +# "foo 3.4.5\n" +# ``` +# +# #### Combining Trim Modes +# +# You can combine certain trim modes: +# +# - `'%-'`: Enable shorthand and omit each blank line ending with `'%>'`. +# - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. +# - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. +# # ### Comment Tags # # You can embed a comment in a template using a *comment tag*; @@ -537,19 +631,19 @@ def self.version # # **Keyword Argument `trim_mode`** # - # When keyword argument `trim_mode` has a string value, - # that value may be one of: + # You can use keyword argument `trim_mode: '%'` + # to enable the [shorthand format][shorthand format] for execution tags. + # + # This value allows [blank line control][blank line control]: # - # - `'%'`: Enable [shorthand format][shorthand format] for execution tags. # - `'-'`: Omit each blank line ending with `'%>'`. + # + # Other values allow [newline control][newline control]: + # # - `'>'`: Omit newline for each line ending with `'%>'`. # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # The value may also be certain combinations of the above. - # - # - `'%-'`: Enable shorthand and omit each blank line ending with `'%>'`. - # - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. - # - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. + # You can also [combine trim modes][combine trim modes]. # # **Keyword Argument `eoutvar`** # @@ -578,9 +672,12 @@ def self.version # However, their values, if given, are handled thus: # # - `safe_level`: ignored. - # - `legacy_trim_mode: overrides keyword argument `trim_mode`. - # - `legacy_eoutvar: overrides keyword argument `eoutvar`. + # - `legacy_trim_mode`: overrides keyword argument `trim_mode`. + # - `legacy_eoutvar`: overrides keyword argument `eoutvar`. # + # [blank line control]: rdoc-ref:ERB@Suppressing+Unwanted+Blank+Lines + # [combine trim modes]: rdoc-ref:ERB@Combining+Trim+Modes + # [newline control]: rdoc-ref:ERB@Suppressing+Unwanted+Newlines # [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags # def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') From e4f09a8c94e6e6d21a6dfa43f71d52e4096234d6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 15 Sep 2025 14:30:33 -0700 Subject: [PATCH 08/32] Remove next field and unused method from tmpbuf These used to be used by the parser --- imemo.c | 15 +-------------- internal/imemo.h | 2 -- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/imemo.c b/imemo.c index 7cec33bc1edf00..74eae5678f3532 100644 --- a/imemo.c +++ b/imemo.c @@ -94,17 +94,6 @@ rb_free_tmp_buffer(volatile VALUE *store) } } -rb_imemo_tmpbuf_t * -rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt) -{ - rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); - tmpbuf->ptr = buf; - tmpbuf->next = old_heap; - tmpbuf->cnt = cnt; - - return tmpbuf; -} - static VALUE imemo_fields_new(VALUE owner, size_t capa) { @@ -478,9 +467,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) const rb_imemo_tmpbuf_t *m = (const rb_imemo_tmpbuf_t *)obj; if (!reference_updating) { - do { - rb_gc_mark_locations(m->ptr, m->ptr + m->cnt); - } while ((m = m->next) != NULL); + rb_gc_mark_locations(m->ptr, m->ptr + m->cnt); } break; diff --git a/internal/imemo.h b/internal/imemo.h index de617d94c1bf5c..eee3dd71854a8e 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -94,7 +94,6 @@ struct vm_ifunc { struct rb_imemo_tmpbuf_struct { VALUE flags; VALUE *ptr; /* malloc'ed buffer */ - struct rb_imemo_tmpbuf_struct *next; /* next imemo */ size_t cnt; /* buffer size in VALUE */ }; @@ -133,7 +132,6 @@ struct MEMO { typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif VALUE rb_imemo_tmpbuf_new(void); -rb_imemo_tmpbuf_t *rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); static inline int imemo_type_p(VALUE imemo, enum imemo_type imemo_type); From 6c5960ae1955b66b4a13e03012d53853ee1fd1de Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 15 Sep 2025 17:43:41 -0700 Subject: [PATCH 09/32] ZJIT: Support compiling block args (#14537) --- test/ruby/test_zjit.rb | 15 ++++++++ zjit/src/backend/lir.rs | 7 +--- zjit/src/hir.rs | 80 +++++++++++++++++++++++------------------ zjit/src/stats.rs | 4 +-- 4 files changed, 63 insertions(+), 43 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 87d8b06ece2a8d..fc79bdda6ea231 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -439,6 +439,21 @@ def entry = [test(1), test(3, 4)] }, call_threshold: 2 end + def test_send_nil_block_arg + assert_compiles 'false', %q{ + def test = block_given? + def entry = test(&nil) + test + } + end + + def test_send_symbol_block_arg + assert_compiles '["1", "2"]', %q{ + def test = [1, 2].map(&:to_s) + test + } + end + def test_forwardable_iseq assert_compiles '1', %q{ def test(...) = 1 diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index c67bf8c9ba01e8..5f96d0718a9d9f 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -6,7 +6,7 @@ use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_ use crate::hir::SideExitReason; use crate::options::{debug, get_option}; use crate::cruby::VALUE; -use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_call_type, exit_counter_ptr_for_opcode, CompileError}; +use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; @@ -1601,11 +1601,6 @@ impl Assembler self.load_into(SCRATCH_OPND, Opnd::const_ptr(exit_counter_ptr_for_opcode(opcode))); self.incr_counter_with_reg(Opnd::mem(64, SCRATCH_OPND, 0), 1.into(), C_RET_OPND); } - if let SideExitReason::UnhandledCallType(call_type) = reason { - asm_comment!(self, "increment an unknown call type counter"); - self.load_into(SCRATCH_OPND, Opnd::const_ptr(exit_counter_ptr_for_call_type(call_type))); - self.incr_counter_with_reg(Opnd::mem(64, SCRATCH_OPND, 0), 1.into(), C_RET_OPND); - } } asm_comment!(self, "exit to the interpreter"); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3726d8ec0e44d5..46bf38dcc6a4d4 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -447,10 +447,10 @@ impl PtrPrintMap { #[derive(Debug, Clone, Copy)] pub enum SideExitReason { UnknownNewarraySend(vm_opt_newarray_send_type), - UnhandledCallType(CallType), UnknownSpecialVariable(u64), UnhandledHIRInsn(InsnId), UnhandledYARVInsn(u32), + UnhandledTailCall, FixnumAddOverflow, FixnumSubOverflow, FixnumMultOverflow, @@ -2986,10 +2986,8 @@ fn num_locals(iseq: *const rb_iseq_t) -> usize { } /// If we can't handle the type of send (yet), bail out. -fn unknown_call_type(flag: u32) -> Result<(), CallType> { - if (flag & VM_CALL_ARGS_BLOCKARG) != 0 { return Err(CallType::BlockArg); } - if (flag & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } - Ok(()) +fn is_tailcall(flags: u32) -> bool { + (flags & VM_CALL_TAILCALL) != 0 } /// We have IseqPayload, which keeps track of HIR Types in the interpreter, but this is not useful @@ -3501,10 +3499,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // NB: opt_neq has two cd; get_arg(0) is for eq and get_arg(1) is for neq let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if is_tailcall(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3522,10 +3521,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // NB: these instructions have the recv for the call at get_arg(0) let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if is_tailcall(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3580,10 +3580,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if is_tailcall(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3598,15 +3599,17 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let blockiseq: IseqPtr = get_arg(pc, 1).as_iseq(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if is_tailcall(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; + let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; - let args = state.stack_pop_n(argc as usize)?; + let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq, args, state: exit_id }); @@ -3624,14 +3627,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_invokesuper => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) } & !VM_CALL_SUPER & !VM_CALL_ZSUPER) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if is_tailcall(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; - let args = state.stack_pop_n(argc as usize)?; + let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; + let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let recv = state.stack_pop()?; let blockiseq: IseqPtr = get_arg(pc, 1).as_ptr(); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); @@ -3652,14 +3657,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_invokeblock => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if is_tailcall(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; - let args = state.stack_pop_n(argc as usize)?; + let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; + let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); let result = fun.push_insn(block, Insn::InvokeBlock { cd, args, state: exit_id }); state.stack_push(result); @@ -3767,11 +3774,6 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_objtostring => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); - let call_info = unsafe { rb_get_call_data_ci(cd) }; - - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - panic!("objtostring should not have unknown call type {call_type:?}"); - } let argc = unsafe { vm_ci_argc((*cd).ci) }; assert_eq!(0, argc, "objtostring should not have args"); @@ -5115,14 +5117,17 @@ mod tests { } #[test] - fn test_cant_compile_block_arg() { + fn test_compile_block_arg() { eval(" def test(a) = foo(&a) "); assert_snapshot!(hir_string("test"), @r" fn test@:2: bb0(v0:BasicObject, v1:BasicObject): - SideExit UnhandledCallType(BlockArg) + v6:BasicObject = Send v0, 0x1000, :foo, v1 + v7:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v6 "); } @@ -5194,7 +5199,9 @@ mod tests { fn test@:2: bb0(v0:BasicObject): v4:NilClass = Const Value(nil) - SideExit UnhandledCallType(BlockArg) + v6:BasicObject = InvokeSuper v0, 0x1000, v4 + CheckInterrupts + Return v6 "); } @@ -8074,7 +8081,10 @@ mod opt_tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v6:BasicObject = GetBlockParamProxy l0 - SideExit UnhandledCallType(BlockArg) + v8:BasicObject = Send v0, 0x1000, :tap, v6 + v9:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v8 "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 8a7073dd977c94..69748f4ebc9cbd 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -92,7 +92,7 @@ make_counters! { // exit_: Side exits reasons exit_compile_error, exit_unknown_newarray_send, - exit_unhandled_call_type, + exit_unhandled_tailcall, exit_unknown_special_variable, exit_unhandled_hir_insn, exit_unhandled_yarv_insn, @@ -209,7 +209,7 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { use crate::stats::Counter::*; let counter = match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, - UnhandledCallType(_) => exit_unhandled_call_type, + UnhandledTailCall => exit_unhandled_tailcall, UnknownSpecialVariable(_) => exit_unknown_special_variable, UnhandledHIRInsn(_) => exit_unhandled_hir_insn, UnhandledYARVInsn(_) => exit_unhandled_yarv_insn, From 20d4e27d3f52c801932b1da8387cad9f3ec0f38e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 15 Sep 2025 18:17:01 -0700 Subject: [PATCH 10/32] ZJIT: Revert VM_CALL_ARGS_SPLAT and VM_CALL_KWARG support (#14565) --- test/ruby/test_zjit.rb | 16 +++++++++++ zjit.rb | 1 - zjit/src/hir.rs | 60 +++++++++++++++++++----------------------- zjit/src/stats.rs | 18 +++++-------- 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index fc79bdda6ea231..42d10490c5cee6 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -454,6 +454,22 @@ def test = [1, 2].map(&:to_s) } end + def test_send_splat + assert_runs '[1, 2]', %q{ + def test(a, b) = [a, b] + def entry(arr) = test(*arr) + entry([1, 2]) + } + end + + def test_send_kwarg + assert_runs '[1, 2]', %q{ + def test(a:, b:) = [a, b] + def entry = test(a: 1, b: 2) + entry + } + end + def test_forwardable_iseq assert_compiles '1', %q{ def test(...) = 1 diff --git a/zjit.rb b/zjit.rb index d6f7a4069267b8..44bce453fff827 100644 --- a/zjit.rb +++ b/zjit.rb @@ -40,7 +40,6 @@ def stats_string stats = self.stats # Show exit reasons, ordered by the typical amount of exits for the prefix at the time - print_counters_with_prefix(prefix: 'unhandled_call_', prompt: 'unhandled call types', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 46bf38dcc6a4d4..1a268e62da97f7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -450,7 +450,7 @@ pub enum SideExitReason { UnknownSpecialVariable(u64), UnhandledHIRInsn(InsnId), UnhandledYARVInsn(u32), - UnhandledTailCall, + UnhandledCallType(CallType), FixnumAddOverflow, FixnumSubOverflow, FixnumMultOverflow, @@ -2968,7 +2968,8 @@ fn compute_bytecode_info(iseq: *const rb_iseq_t) -> BytecodeInfo { #[derive(Debug, PartialEq, Clone, Copy)] pub enum CallType { - BlockArg, + Splat, + Kwarg, Tailcall, } @@ -2986,8 +2987,11 @@ fn num_locals(iseq: *const rb_iseq_t) -> usize { } /// If we can't handle the type of send (yet), bail out. -fn is_tailcall(flags: u32) -> bool { - (flags & VM_CALL_TAILCALL) != 0 +fn unhandled_call_type(flags: u32) -> Result<(), CallType> { + if (flags & VM_CALL_ARGS_SPLAT) != 0 { return Err(CallType::Splat); } + if (flags & VM_CALL_KWARG) != 0 { return Err(CallType::Kwarg); } + if (flags & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } + Ok(()) } /// We have IseqPayload, which keeps track of HIR Types in the interpreter, but this is not useful @@ -3500,10 +3504,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; let flags = unsafe { rb_vm_ci_flag(call_info) }; - if is_tailcall(flags) { - // Can't handle tailcall; side-exit into the interpreter + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle the call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3522,10 +3526,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; let flags = unsafe { rb_vm_ci_flag(call_info) }; - if is_tailcall(flags) { - // Can't handle tailcall; side-exit into the interpreter + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle the call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3581,10 +3585,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; let flags = unsafe { rb_vm_ci_flag(call_info) }; - if is_tailcall(flags) { + if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3600,10 +3604,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let blockiseq: IseqPtr = get_arg(pc, 1).as_iseq(); let call_info = unsafe { rb_get_call_data_ci(cd) }; let flags = unsafe { rb_vm_ci_flag(call_info) }; - if is_tailcall(flags) { + if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3628,10 +3632,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; let flags = unsafe { rb_vm_ci_flag(call_info) }; - if is_tailcall(flags) { + if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -3658,10 +3662,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; let flags = unsafe { rb_vm_ci_flag(call_info) }; - if is_tailcall(flags) { + if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledTailCall }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -5110,9 +5114,7 @@ mod tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v6:ArrayExact = ToArray v1 - v8:BasicObject = SendWithoutBlock v0, :foo, v6 - CheckInterrupts - Return v8 + SideExit UnhandledCallType(Splat) "); } @@ -5140,9 +5142,7 @@ mod tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v5:Fixnum[1] = Const Value(1) - v7:BasicObject = SendWithoutBlock v0, :foo, v5 - CheckInterrupts - Return v7 + SideExit UnhandledCallType(Kwarg) "); } @@ -5264,9 +5264,7 @@ mod tests { v6:ArrayExact = ToNewArray v1 v7:Fixnum[1] = Const Value(1) ArrayPush v6, v7 - v11:BasicObject = SendWithoutBlock v0, :foo, v6 - CheckInterrupts - Return v11 + SideExit UnhandledCallType(Splat) "); } @@ -7862,9 +7860,7 @@ mod opt_tests { fn test@:3: bb0(v0:BasicObject): v4:Fixnum[1] = Const Value(1) - v6:BasicObject = SendWithoutBlock v0, :foo, v4 - CheckInterrupts - Return v6 + SideExit UnhandledCallType(Kwarg) "); } @@ -7880,9 +7876,7 @@ mod opt_tests { fn test@:3: bb0(v0:BasicObject): v4:Fixnum[1] = Const Value(1) - v6:BasicObject = SendWithoutBlock v0, :foo, v4 - CheckInterrupts - Return v6 + SideExit UnhandledCallType(Kwarg) "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 69748f4ebc9cbd..0eb2b8687bdd48 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -93,6 +93,8 @@ make_counters! { exit_compile_error, exit_unknown_newarray_send, exit_unhandled_tailcall, + exit_unhandled_splat, + exit_unhandled_kwarg, exit_unknown_special_variable, exit_unhandled_hir_insn, exit_unhandled_yarv_insn, @@ -162,17 +164,6 @@ pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 { unsafe { exit_counters.get_unchecked_mut(opcode as usize) } } -/// Return a raw pointer to the exit counter for a given call type -pub fn exit_counter_ptr_for_call_type(call_type: crate::hir::CallType) -> *mut u64 { - use crate::hir::CallType::*; - use crate::stats::Counter::*; - let counter = match call_type { - BlockArg => unhandled_call_block_arg, - Tailcall => unhandled_call_tailcall, - }; - counter_ptr(counter) -} - /// Reason why ZJIT failed to produce any JIT code #[derive(Clone, Debug, PartialEq)] pub enum CompileError { @@ -206,10 +197,13 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { use crate::hir::SideExitReason::*; + use crate::hir::CallType::*; use crate::stats::Counter::*; let counter = match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, - UnhandledTailCall => exit_unhandled_tailcall, + UnhandledCallType(Tailcall) => exit_unhandled_tailcall, + UnhandledCallType(Splat) => exit_unhandled_splat, + UnhandledCallType(Kwarg) => exit_unhandled_kwarg, UnknownSpecialVariable(_) => exit_unknown_special_variable, UnhandledHIRInsn(_) => exit_unhandled_hir_insn, UnhandledYARVInsn(_) => exit_unhandled_yarv_insn, From 02fd62895d9a13f56253521dedd80e39d4eafbb5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 10:15:00 +0900 Subject: [PATCH 11/32] CI: Re-enable Ubuntu arm with CAPI check ubuntu-24.04-arm jobs look working as of 2025-09-16. --- .github/workflows/ubuntu.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6249418a9abafb..4358e4c3a7e8ce 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -40,9 +40,9 @@ jobs: - test_task: check os: ubuntu-24.04 extra_checks: [capi] - # ubuntu-24.04-arm jobs don't start on ruby/ruby as of 2025-09-04 - #- test_task: check - # os: ubuntu-24.04-arm + - test_task: check + os: ubuntu-24.04-arm + extra_checks: [capi] fail-fast: false env: From 214bae31215d06cf0611992d90c7a13e1e399090 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 11:08:09 +0900 Subject: [PATCH 12/32] CI: Save CAPI extensions if the cache not found --- .github/actions/capiext/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index c17069f97dae20..4ec6a27f3fc837 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -63,7 +63,6 @@ runs: key: ${{ steps.config.outputs.key }} if: >- ${{true - && steps.cache.outcome == 'success' && ! steps.cache.outputs.cache-hit && github.ref_name == 'master' }} From d5f31dcff322fa2f49841056b7825bc4cb01ba64 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 12:09:14 +0900 Subject: [PATCH 13/32] CI: Strip CAPI extensions before caching --- .github/actions/capiext/action.yml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index 4ec6a27f3fc837..147c5387b3183e 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -50,23 +50,34 @@ runs: touch spec/ruby/optional/capi/ext/*.$DLEXT [ ! -f spec/ruby/optional/capi/ext/\*.$DLEXT ] ${{ inputs.make }} SPECOPTS=optional/capi test-spec - rm -f spec/ruby/optional/capi/ext/*.c env: DLEXT: ${{ steps.config.outputs.DLEXT }} working-directory: ${{ inputs.builddir }} if: ${{ steps.cache.outputs.cache-hit }} - - name: Save CAPI extensions - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ - key: ${{ steps.config.outputs.key }} + - name: Strip CAPI extensions + id: strip + shell: bash + run: | + rm -f spec/ruby/optional/capi/ext/*.c + [ "$DLEXT" = bundle ] || # separated to .dSYM directories + strip spec/ruby/optional/capi/ext/*.$DLEXT + env: + DLEXT: ${{ steps.config.outputs.DLEXT }} + working-directory: ${{ inputs.builddir }} if: >- ${{true && ! steps.cache.outputs.cache-hit && github.ref_name == 'master' }} + - name: Save CAPI extensions + uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ + key: ${{ steps.config.outputs.key }} + if: ${{ steps.strip.outcome == 'success' }} + - shell: bash run: | echo "::error::Change from ${prev} detected; bump up ABI version" From 674e1d2a5ffe8bfe4b0b691e151492af8287a558 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 14:33:33 +0900 Subject: [PATCH 14/32] Fix a function name typo in the description [ci skip] --- spec/ruby/optional/capi/regexp_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ruby/optional/capi/regexp_spec.rb b/spec/ruby/optional/capi/regexp_spec.rb index 4b130ff66a067c..49ac79f5c48637 100644 --- a/spec/ruby/optional/capi/regexp_spec.rb +++ b/spec/ruby/optional/capi/regexp_spec.rb @@ -110,7 +110,7 @@ end end - describe "rb_memicmp" do + describe "rb_memcicmp" do it "returns 0 for identical strings" do @p.rb_memcicmp('Hello', 'Hello').should == 0 end From ca9c0f91311e01625e10272798ff2cc831bbee60 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 11 Sep 2025 17:27:02 +0900 Subject: [PATCH 15/32] [rubygems/rubygems] Use File#chmod rather than FileUtils.chmod We already have the open file descriptor, so we can avoid the overhead of resolving the filepath (as well as the overhead inside `FileUtils`) by just calling `chmod` on the file descriptor itself. https://github.com/rubygems/rubygems/commit/60c14bbeee --- lib/rubygems/package.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index c855423ed764de..99be691ec7f7ea 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -451,8 +451,14 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: end if entry.file? - File.open(destination, "wb") {|out| copy_stream(entry, out) } - FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination + File.open(destination, "wb") do |out| + copy_stream(entry, out) + # Flush needs to happen before chmod because there could be data + # in the IO buffer that needs to be written, and that could be + # written after the chmod (on close) which would mess up the perms + out.flush + out.chmod file_mode(entry.header.mode) & ~File.umask + end end verbose destination From a71b339477a291a93fc9f83266eaad3423df6acc Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 12 Sep 2025 16:29:51 +0900 Subject: [PATCH 16/32] [rubygems/rubygems] Pass the file size to IO.copy_stream When extracting tar files, the tar header actually knows the exact size of the file we need to extract. Before this commit, we would read the file from the tar file until it returned `nil`. We can be a little more efficient when copying by passing the size to copy_stream https://github.com/rubygems/rubygems/commit/8927533b0a --- lib/rubygems/package.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 99be691ec7f7ea..08730402062f93 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -267,7 +267,7 @@ def add_files(tar) # :nodoc: tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| - copy_stream(src_io, dst_io) + copy_stream(src_io, dst_io, stat.size) end end end @@ -452,7 +452,7 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: if entry.file? File.open(destination, "wb") do |out| - copy_stream(entry, out) + copy_stream(entry, out, entry.size) # Flush needs to happen before chmod because there could be data # in the IO buffer that needs to be written, and that could be # written after the chmod (on close) which would mess up the perms @@ -721,12 +721,12 @@ def verify_gz(entry) # :nodoc: end if RUBY_ENGINE == "truffleruby" - def copy_stream(src, dst) # :nodoc: - dst.write src.read + def copy_stream(src, dst, size) # :nodoc: + dst.write src.read(size) end else - def copy_stream(src, dst) # :nodoc: - IO.copy_stream(src, dst) + def copy_stream(src, dst, size) # :nodoc: + IO.copy_stream(src, dst, size) end end From 9b45a25c4897b0e9565dad9fa7a0765b68732679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:18:31 +0200 Subject: [PATCH 17/32] [rubygems/rubygems] Fix `rubocop` config removal message In Bundler 4, configuration will no longer be updated. https://github.com/rubygems/rubygems/commit/33a4718d7a --- lib/bundler/cli/gem.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 5b71d71c67133b..b63617d53604c3 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -431,7 +431,7 @@ def deprecated_rubocop_option elsif !Bundler.settings["gem.rubocop"].nil? Bundler::SharedHelpers.major_deprecation 2, "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead", - removed_message: "config gem.rubocop has been removed; we've updated your config to use gem.linter instead" + removed_message: "config gem.rubocop has been removed; use gem.linter instead" Bundler.settings["gem.rubocop"] ? "rubocop" : false end end From 1c7fd141f911d97d1f5a2737d0a36cb608100bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:19:15 +0200 Subject: [PATCH 18/32] [rubygems/rubygems] Fix `--no-rubocop` deprecation message https://github.com/rubygems/rubygems/commit/2c16b0e11e --- lib/bundler/cli/gem.rb | 4 ++-- spec/bundler/other/major_deprecation_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index b63617d53604c3..71207bc5da7548 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -424,8 +424,8 @@ def deprecated_rubocop_option "rubocop" else Bundler::SharedHelpers.major_deprecation 2, - "--no-rubocop is deprecated, use --linter", - removed_message: "--no-rubocop has been removed, use --linter" + "--no-rubocop is deprecated, use --no-linter", + removed_message: "--no-rubocop has been removed, use --no-linter" false end elsif !Bundler.settings["gem.rubocop"].nil? diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 65fcd43c393dfa..0a8d1d112eb336 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -735,7 +735,7 @@ it "prints a deprecation warning" do expect(deprecations).to include \ - "--no-rubocop is deprecated, use --linter" + "--no-rubocop is deprecated, use --no-linter" end end From 190a235464398c5b8f556cbf221bfaec8680e3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:19:23 +0200 Subject: [PATCH 19/32] [rubygems/rubygems] Complete rubocop flags and settings removal If the CLI flags are used, we abort early as usual. As per the settings, I decided to ignore them. We've been migrating them automatically to the new name for a long time and we don't yet have a standard way to deprecate and remove settings (we should probably use the existing setting validators). So I think it's fine for now to do what we normally do (ignore the setting). https://github.com/rubygems/rubygems/commit/8311de6e69 --- lib/bundler/cli.rb | 6 +- lib/bundler/cli/gem.rb | 22 ----- lib/bundler/man/bundle-gem.1 | 3 - lib/bundler/man/bundle-gem.1.ronn | 3 - spec/bundler/commands/newgem_spec.rb | 99 -------------------- spec/bundler/other/major_deprecation_spec.rb | 36 ++----- 6 files changed, 12 insertions(+), 157 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index e59265e8b2ed13..8151de713ae1f7 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -525,7 +525,7 @@ def viz method_option :ext, type: :string, banner: "Generate the boilerplate for C extension code.", enum: EXTENSIONS method_option :git, type: :boolean, default: true, banner: "Initialize a git repo inside your library." method_option :mit, type: :boolean, banner: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." - method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`." + method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true` (removed)." method_option :changelog, type: :boolean, banner: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", enum: %w[rspec minitest test-unit], desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], banner: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" @@ -535,6 +535,10 @@ def viz def gem(name) require_relative "cli/gem" + + raise InvalidOption, "--rubocop has been removed, use --linter=rubocop" if ARGV.include?("--rubocop") + raise InvalidOption, "--no-rubocop has been removed, use --no-linter" if ARGV.include?("--no-rubocop") + cmd_args = args + [self] cmd_args.unshift(options) diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 71207bc5da7548..56d23d9e9fc293 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -382,7 +382,6 @@ def ask_and_set_ci def ask_and_set_linter return if skip?(:linter) linter_template = options[:linter] || Bundler.settings["gem.linter"] - linter_template = deprecated_rubocop_option if linter_template.nil? if linter_template.to_s.empty? Bundler.ui.info "\nDo you want to add a code linter and formatter to your gem? " \ @@ -415,27 +414,6 @@ def ask_and_set_linter linter_template end - def deprecated_rubocop_option - if !options[:rubocop].nil? - if options[:rubocop] - Bundler::SharedHelpers.major_deprecation 2, - "--rubocop is deprecated, use --linter=rubocop", - removed_message: "--rubocop has been removed, use --linter=rubocop" - "rubocop" - else - Bundler::SharedHelpers.major_deprecation 2, - "--no-rubocop is deprecated, use --no-linter", - removed_message: "--no-rubocop has been removed, use --no-linter" - false - end - elsif !Bundler.settings["gem.rubocop"].nil? - Bundler::SharedHelpers.major_deprecation 2, - "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead", - removed_message: "config gem.rubocop has been removed; use gem.linter instead" - Bundler.settings["gem.rubocop"] ? "rubocop" : false - end - end - def bundler_dependency_version v = Gem::Version.new(Bundler::VERSION) req = v.segments[0..1] diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 884badb584ebd2..670a69d67e317b 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -92,9 +92,6 @@ When Bundler is unconfigured, an interactive prompt will be displayed and the an \fB\-\-no\-linter\fR Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. .TP -\fB\-\-rubocop\fR -Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\. -.TP \fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. .TP diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index b1327aa3422b86..b71bde9f6506f1 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -135,9 +135,6 @@ configuration file using the following names: * `--no-linter`: Do not add a linter (overrides `--linter` specified in the global config). -* `--rubocop`: - Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`. - * `--edit=EDIT`, `-e=EDIT`: Open the resulting GEM_NAME.gemspec in EDIT, or the default editor if not specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`. diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 40bc1c3ff48641..0b13344f994154 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -175,75 +175,6 @@ def ignore_paths end end - shared_examples_for "--rubocop flag" do - context "is deprecated" do - before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "gem #{gem_name} --rubocop" - end - - it "generates a gem skeleton with rubocop" do - gem_skeleton_assertions - expect(bundled_app("#{gem_name}/Rakefile")).to read_as( - include("# frozen_string_literal: true"). - and(include('require "rubocop/rake_task"'). - and(include("RuboCop::RakeTask.new"). - and(match(/default:.+:rubocop/)))) - ) - end - - it "includes rubocop in generated Gemfile" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } - expect(rubocop_dep).not_to be_nil - end - - it "generates a default .rubocop.yml" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end - - it "includes .rubocop.yml into ignore list" do - expect(ignore_paths).to include(".rubocop.yml") - end - end - end - - shared_examples_for "--no-rubocop flag" do - context "is deprecated" do - define_negated_matcher :exclude, :include - - before do - bundle "gem #{gem_name} --no-rubocop" - end - - it "generates a gem skeleton without rubocop" do - gem_skeleton_assertions - expect(bundled_app("#{gem_name}/Rakefile")).to read_as(exclude("rubocop")) - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) - end - - it "does not include rubocop in generated Gemfile" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } - expect(rubocop_dep).to be_nil - end - - it "doesn't generate a default .rubocop.yml" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - end - - it "does not add .rubocop.yml into ignore list" do - expect(ignore_paths).not_to include(".rubocop.yml") - end - end - end - shared_examples_for "--linter=rubocop flag" do before do bundle "gem #{gem_name} --linter=rubocop" @@ -1335,32 +1266,6 @@ def create_temporary_dir(dir) end end - context "gem.rubocop setting set to true" do - before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "config set gem.rubocop true" - bundle "gem #{gem_name}" - end - - it "generates rubocop config" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end - - it "includes .rubocop.yml into ignore list" do - expect(ignore_paths).to include(".rubocop.yml") - end - - it "unsets gem.rubocop" do - bundle "config gem.rubocop" - expect(out).to include("You have not configured a value for `gem.rubocop`") - end - - it "sets gem.linter=rubocop instead" do - bundle "config gem.linter" - expect(out).to match(/Set for the current user .*: "rubocop"/) - end - end - context "gem.linter set to rubocop and --linter with no arguments" do before do bundle "config set gem.linter rubocop" @@ -1558,8 +1463,6 @@ def create_temporary_dir(dir) it_behaves_like "--linter=rubocop flag" it_behaves_like "--linter=standard flag" it_behaves_like "--no-linter flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop flag" end context "with rubocop option in bundle config settings set to false" do @@ -1569,8 +1472,6 @@ def create_temporary_dir(dir) it_behaves_like "--linter=rubocop flag" it_behaves_like "--linter=standard flag" it_behaves_like "--no-linter flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop flag" end context "with linter option in bundle config settings set to rubocop" do diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 0a8d1d112eb336..bf0fca5ddd6baa 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -711,7 +711,7 @@ pending "fails with a helpful message", bundler: "4" end - describe "deprecating rubocop" do + describe "removing rubocop" do before do global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false" @@ -722,9 +722,9 @@ bundle "gem my_new_gem --rubocop", raise_on_error: false end - it "prints a deprecation warning" do - expect(deprecations).to include \ - "--rubocop is deprecated, use --linter=rubocop" + it "prints an error" do + expect(err).to include \ + "--rubocop has been removed, use --linter=rubocop" end end @@ -733,31 +733,9 @@ bundle "gem my_new_gem --no-rubocop", raise_on_error: false end - it "prints a deprecation warning" do - expect(deprecations).to include \ - "--no-rubocop is deprecated, use --no-linter" - end - end - - context "bundle gem with gem.rubocop set to true" do - before do - bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "true" }, raise_on_error: false - end - - it "prints a deprecation warning" do - expect(deprecations).to include \ - "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" - end - end - - context "bundle gem with gem.rubocop set to false" do - before do - bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "false" }, raise_on_error: false - end - - it "prints a deprecation warning" do - expect(deprecations).to include \ - "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" + it "prints an error" do + expect(err).to include \ + "--no-rubocop has been removed, use --no-linter" end end end From 6b0af3135264351d0bbaaf5cf6b207a9c84b6e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:18:54 +0200 Subject: [PATCH 20/32] [rubygems/rubygems] Remove `allow_offline_install` setting And let the feature always be enabled, so I'm not sure why we'd need this configurable. https://github.com/rubygems/rubygems/commit/5a27f0c1e3 --- lib/bundler/feature_flag.rb | 1 - lib/bundler/fetcher/compact_index.rb | 2 +- lib/bundler/man/bundle-config.1 | 3 --- lib/bundler/man/bundle-config.1.ronn | 2 -- lib/bundler/settings.rb | 1 - lib/bundler/source/git.rb | 1 - spec/bundler/install/allow_offline_install_spec.rb | 6 +----- 7 files changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index ec61dd521184c6..73e6ddcc68beb5 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -27,7 +27,6 @@ def self.settings_method(name, key, &default) (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - settings_flag(:allow_offline_install) { bundler_4_mode? } settings_flag(:cache_all) { bundler_4_mode? } settings_flag(:global_gem_cache) { bundler_5_mode? } settings_flag(:lockfile_checksums) { bundler_4_mode? } diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index 6c82d570114cde..52168111fea775 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -110,7 +110,7 @@ def client_fetcher def call(path, headers) fetcher.downloader.fetch(fetcher.fetch_uri + path, headers) rescue NetworkDownError => e - raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] + raise unless headers["If-None-Match"] ui.warn "Using the cached data for the new index because of a network error: #{e}" Gem::Net::HTTPNotModified.new(nil, nil, nil) end diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index e3ce81155ca2e1..b7276daa89195a 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -70,9 +70,6 @@ Any periods in the configuration keys must be replaced with two underscores when .SH "LIST OF AVAILABLE KEYS" The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. .TP -\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR) -Allow Bundler to use cached data when installing without network access\. -.TP \fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR) Automatically run \fBbundle install\fR when gems are missing\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index b2f0f462f83c8f..18260c6c931f09 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -106,8 +106,6 @@ the environment variable `BUNDLE_LOCAL__RACK`. The following is a list of all configuration keys and their purpose. You can learn more about their operation in [bundle install(1)](bundle-install.1.html). -* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): - Allow Bundler to use cached data when installing without network access. * `auto_install` (`BUNDLE_AUTO_INSTALL`): Automatically run `bundle install` when gems are missing. * `bin` (`BUNDLE_BIN`): diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index cefbf0b1f0691e..ecc3ee8080a239 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -7,7 +7,6 @@ class Settings autoload :Validator, File.expand_path("settings/validator", __dir__) BOOL_KEYS = %w[ - allow_offline_install auto_install cache_all cache_all_platforms diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index d57944ee1244d9..ddea7714a16f5d 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -416,7 +416,6 @@ def git_proxy def fetch git_proxy.checkout rescue GitError => e - raise unless Bundler.feature_flag.allow_offline_install? Bundler.ui.warn "Using cached git data because of network errors:\n#{e}" end diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb index abe6009c08e4d3..4889dbc943551a 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle install with :allow_offline_install" do - before do - bundle "config set allow_offline_install true" - end - +RSpec.describe "bundle install allows offline install" do context "with no cached data locally" do it "still installs" do install_gemfile <<-G, artifice: "compact_index" From 12aa9e7457458245c9452ca5f786f6191742edf2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 13 Sep 2025 07:34:53 +0900 Subject: [PATCH 21/32] [rubygems/rubygems] Use `IO.copy_stream` with IO object directly Before this patch we would use `IO.copy_stream` with the tar entry object rather than just straight to the IO. That means every time copy_stream wanted data, we would have to proxy the call. The reason we did this is because every tar entry object _shares_ the same IO object, and previous to https://github.com/rubygems/rubygems/commit/8927533b0a47 we would call `IO.copy_stream` _without_ a size. Without passing a size, copy_stream will just read until there is nothing left to read, so these proxy object emulate finding "the end of the file" (where "end of file" means "end of tar chunk"). Without emulating this "end of file" behavior, copy_stream would just keep reading past the end of the tar chunk. However, now that we're passing the size to copy_stream, we can bypass the proxy object overhead and just use the IO object directly because copy_stream knows exactly the number of bytes it needs to read and will stop when it reaches the goal. https://github.com/rubygems/rubygems/commit/857002c135 --- lib/rubygems/package.rb | 2 +- lib/rubygems/package/tar_reader.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 08730402062f93..a8eba81ea06f4a 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -452,7 +452,7 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: if entry.file? File.open(destination, "wb") do |out| - copy_stream(entry, out, entry.size) + copy_stream(tar.io, out, entry.size) # Flush needs to happen before chmod because there could be data # in the IO buffer that needs to be written, and that could be # written after the chmod (on close) which would mess up the perms diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb index 25f9b2f9450a95..b66a8a62bc9f75 100644 --- a/lib/rubygems/package/tar_reader.rb +++ b/lib/rubygems/package/tar_reader.rb @@ -30,6 +30,8 @@ def self.new(io) nil end + attr_reader :io # :nodoc: + ## # Creates a new tar file reader on +io+ which needs to respond to #pos, # #eof?, #read, #getc and #pos= From 0a5a0eeab45fb2db176b681159b328b3fdf95408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:32:26 +0200 Subject: [PATCH 22/32] [rubygems/rubygems] Handle locked sources more simillarly to locked specs https://github.com/rubygems/rubygems/commit/0a2f5ed717 --- lib/bundler/definition.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index e177b6e39673c7..bd424609c9968c 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -107,6 +107,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti @locked_ruby_version = @locked_gems.ruby_version @locked_deps = @locked_gems.dependencies @originally_locked_specs = SpecSet.new(@locked_gems.specs) + @originally_locked_sources = @locked_gems.sources @locked_checksums = @locked_gems.checksums if @unlocking_all @@ -114,7 +115,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti @locked_sources = [] else @locked_specs = @originally_locked_specs - @locked_sources = @locked_gems.sources + @locked_sources = @originally_locked_sources end else @locked_gems = nil @@ -123,8 +124,9 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti @platforms = [] @locked_deps = {} @locked_specs = SpecSet.new([]) - @originally_locked_specs = @locked_specs @locked_sources = [] + @originally_locked_specs = @locked_specs + @originally_locked_sources = @locked_sources @locked_checksums = Bundler.feature_flag.lockfile_checksums? end @@ -954,7 +956,7 @@ def converge_sources sources.all_sources.each do |source| # has to be done separately, because we want to keep the locked checksum # store for a source, even when doing a full update - if @locked_checksums && @locked_gems && locked_source = @locked_gems.sources.find {|s| s == source && !s.equal?(source) } + if @locked_checksums && @locked_gems && locked_source = @originally_locked_sources.find {|s| s == source && !s.equal?(source) } source.checksum_store.merge!(locked_source.checksum_store) end # If the source is unlockable and the current command allows an unlock of @@ -1137,7 +1139,7 @@ def lockfiles_equal?(current, proposed, preserve_unknown_sections) end def additional_base_requirements_to_prevent_downgrades(resolution_base) - return resolution_base unless @locked_gems && !sources.expired_sources?(@locked_gems.sources) + return resolution_base unless @locked_gems && !sources.expired_sources?(@originally_locked_sources) @originally_locked_specs.each do |locked_spec| next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed? From 26f9911c7190d5343fb775f54fc2472cd6fe632b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:43:30 +0200 Subject: [PATCH 23/32] [rubygems/rubygems] Multisource checks are only relevant when there's a lockfile https://github.com/rubygems/rubygems/commit/4c110d3289 --- lib/bundler/definition.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index bd424609c9968c..dbfed9f67f428a 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -117,6 +117,19 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti @locked_specs = @originally_locked_specs @locked_sources = @originally_locked_sources end + + locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } + @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? + + if @multisource_allowed + unless sources.aggregate_global_source? + msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + + Bundler::SharedHelpers.major_deprecation 2, msg + end + + @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) + end else @locked_gems = nil @locked_platforms = [] @@ -130,19 +143,6 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti @locked_checksums = Bundler.feature_flag.lockfile_checksums? end - locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } - @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? - - if @multisource_allowed - unless sources.aggregate_global_source? - msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." - - Bundler::SharedHelpers.major_deprecation 2, msg - end - - @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) - end - @unlocking_ruby ||= if @ruby_version && locked_ruby_version_object @ruby_version.diff(locked_ruby_version_object) end From 9878060181d2592c26dce0c314a8b4b6e4826646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:55:06 +0200 Subject: [PATCH 24/32] [rubygems/rubygems] Simplify an edge case of not adding lower bound requirements https://github.com/rubygems/rubygems/commit/1bc5e74281 --- lib/bundler/definition.rb | 8 ++++---- lib/bundler/source_list.rb | 21 +-------------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index dbfed9f67f428a..ab488e046b0ac8 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -118,10 +118,10 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti @locked_sources = @originally_locked_sources end - locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } - @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? + locked_gem_sources = @originally_locked_sources.select {|s| s.is_a?(Source::Rubygems) } + @multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? - if @multisource_allowed + if @multisource_lockfile && Bundler.frozen_bundle? unless sources.aggregate_global_source? msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." @@ -1139,7 +1139,7 @@ def lockfiles_equal?(current, proposed, preserve_unknown_sections) end def additional_base_requirements_to_prevent_downgrades(resolution_base) - return resolution_base unless @locked_gems && !sources.expired_sources?(@originally_locked_sources) + return resolution_base unless @locked_gems && !@multisource_lockfile @originally_locked_specs.each do |locked_spec| next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed? diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 2f16281045a5e8..70b74f6a91cf32 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -129,16 +129,7 @@ def replace_sources!(replacement_sources) @rubygems_sources, @path_sources, @git_sources, @plugin_sources = map_sources(replacement_sources) @global_rubygems_source = global_replacement_source(replacement_sources) - different_sources?(lock_sources, replacement_sources) - end - - # Returns true if there are changes - def expired_sources?(replacement_sources) - return false if replacement_sources.empty? - - lock_sources = dup_with_replaced_sources(replacement_sources).lock_sources - - different_sources?(lock_sources, replacement_sources) + !equivalent_sources?(lock_sources, replacement_sources) end def prefer_local! @@ -165,12 +156,6 @@ def remote! private - def dup_with_replaced_sources(replacement_sources) - new_source_list = dup - new_source_list.replace_sources!(replacement_sources) - new_source_list - end - def map_sources(replacement_sources) rubygems = @rubygems_sources.map do |source| replace_rubygems_source(replacement_sources, source) @@ -224,10 +209,6 @@ def replace_path_source(replacement_sources, gemfile_source) end end - def different_sources?(lock_sources, replacement_sources) - !equivalent_sources?(lock_sources, replacement_sources) - end - def rubygems_aggregate_class Source::Rubygems end From 6adcc5596884619e386d5412e94ef4443868b94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:18:42 +0200 Subject: [PATCH 25/32] [rubygems/rubygems] Completely remove multisources support https://github.com/rubygems/rubygems/commit/8f9d6c54a1 --- lib/bundler/cli/install.rb | 16 - lib/bundler/definition.rb | 16 +- lib/bundler/dsl.rb | 22 +- lib/bundler/errors.rb | 5 - lib/bundler/index.rb | 7 - lib/bundler/installer.rb | 6 - lib/bundler/source/rubygems.rb | 13 - lib/bundler/source_list.rb | 24 +- lib/bundler/source_map.rb | 7 +- spec/bundler/bundler/definition_spec.rb | 4 - spec/bundler/commands/install_spec.rb | 18 - spec/bundler/commands/outdated_spec.rb | 66 -- spec/bundler/commands/update_spec.rb | 62 -- spec/bundler/install/gemfile/sources_spec.rb | 908 ------------------ .../install/gems/compact_index_spec.rb | 73 +- .../install/gems/dependency_api_spec.rb | 92 +- spec/bundler/other/major_deprecation_spec.rb | 67 +- 17 files changed, 73 insertions(+), 1333 deletions(-) diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index a676e2d6f1f573..c4063f808ab64f 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -69,8 +69,6 @@ def run Bundler::CLI::Common.output_post_install_messages installer.post_install_messages - warn_ambiguous_gems - if CLI::Common.clean_after_install? require_relative "clean" Bundler::CLI::Clean.new(options).run @@ -126,19 +124,5 @@ def normalize_settings options[:force] = options[:redownload] if options[:redownload] end - - def warn_ambiguous_gems - # TODO: remove this when we drop Bundler 1.x support - Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris| - Bundler.ui.warn "Warning: the gem '#{name}' was found in multiple sources." - Bundler.ui.warn "Installed from: #{installed_from_uri}" - Bundler.ui.warn "Also found in:" - also_found_in_uris.each {|uri| Bundler.ui.warn " * #{uri}" } - Bundler.ui.warn "You should add a source requirement to restrict this gem to your preferred source." - Bundler.ui.warn "For example:" - Bundler.ui.warn " gem '#{name}', :source => '#{installed_from_uri}'" - Bundler.ui.warn "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again." - end - end end end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index ab488e046b0ac8..32dd13399ec597 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -119,16 +119,12 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti end locked_gem_sources = @originally_locked_sources.select {|s| s.is_a?(Source::Rubygems) } - @multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? + multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? - if @multisource_lockfile && Bundler.frozen_bundle? - unless sources.aggregate_global_source? - msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + if multisource_lockfile + msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." - Bundler::SharedHelpers.major_deprecation 2, msg - end - - @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) + Bundler::SharedHelpers.feature_removed! msg end else @locked_gems = nil @@ -765,7 +761,7 @@ def start_resolution end def precompute_source_requirements_for_indirect_dependencies? - sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? + sources.non_global_rubygems_sources.all?(&:dependency_api_available?) end def current_platform_locked? @@ -1139,7 +1135,7 @@ def lockfiles_equal?(current, proposed, preserve_unknown_sections) end def additional_base_requirements_to_prevent_downgrades(resolution_base) - return resolution_base unless @locked_gems && !@multisource_lockfile + return resolution_base unless @locked_gems @originally_locked_specs.each do |locked_spec| next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed? diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 0e36f52269abfa..3bf5dbc1153cf7 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -521,24 +521,10 @@ def check_rubygems_source_safety end def multiple_global_source_warning - if Bundler.feature_flag.bundler_4_mode? - msg = "This Gemfile contains multiple global sources. " \ - "Each source after the first must include a block to indicate which gems " \ - "should come from that source" - raise GemfileEvalError, msg - else - message = - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." - removed_message = - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this error, use " \ - "a block to indicate which gems should come from the secondary source." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - end + msg = "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" + raise GemfileEvalError, msg end class DSLError < GemfileError diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 28da892d31d5ac..8cd1336356079e 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -77,11 +77,6 @@ def message def mismatch_resolution_instructions removable, remote = [@existing, @checksum].partition(&:removable?) case removable.size - when 0 - msg = +"Mismatched checksums each have an authoritative source:\n" - msg << " 1. #{@existing.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n" - msg << " 2. #{@checksum.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n" - msg << "You may need to alter your Gemfile sources to resolve this issue.\n" when 1 msg = +"If you trust #{remote.first.sources.first}, to resolve this issue you can:\n" msg << removable.first.removal_instructions diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index d591b34cc7f5e5..9aef2dfa1218c5 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -46,13 +46,6 @@ def empty? true end - def search_all(name, &blk) - return enum_for(:search_all, name) unless blk - specs_by_name(name).each(&blk) - @duplicates[name]&.each(&blk) - @sources.each {|source| source.search_all(name, &blk) } - end - # Search this index's specs, and any source indexes that this index knows # about, returning all of the results. def search(query) diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index d41740a411249f..c5fd75431f41e6 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -7,12 +7,6 @@ module Bundler class Installer - class << self - attr_accessor :ambiguous_gems - - Installer.ambiguous_gems = [] - end - attr_reader :post_install_messages, :definition # Begins the installation process for Bundler. diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 19800e9c585cd5..fdc3a77b248072 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -168,12 +168,6 @@ def install(spec, options = {}) return nil # no post-install message end - if spec.remote - # Check for this spec from other sources - uris = [spec.remote, *remotes_for_spec(spec)].map(&:anonymized_uri).uniq - Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1 - end - path = fetch_gem_if_possible(spec, options[:previous_spec]) raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path @@ -332,13 +326,6 @@ def credless_remotes remotes.map(&method(:remove_auth)) end - def remotes_for_spec(spec) - specs.search_all(spec.name).inject([]) do |uris, s| - uris << s.remote if s.remote - uris - end - end - def cached_gem(spec) global_cache_path = download_cache_path(spec) caches << global_cache_path if global_cache_path diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 70b74f6a91cf32..6dba3f35e8f288 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -21,19 +21,9 @@ def initialize @rubygems_sources = [] @metadata_source = Source::Metadata.new - @merged_gem_lockfile_sections = false @local_mode = true end - def merged_gem_lockfile_sections? - @merged_gem_lockfile_sections - end - - def merged_gem_lockfile_sections!(replacement_source) - @merged_gem_lockfile_sections = true - @global_rubygems_source = replacement_source - end - def aggregate_global_source? global_rubygems_source.multiple_remotes? end @@ -90,10 +80,6 @@ def non_global_rubygems_sources @rubygems_sources end - def rubygems_remotes - rubygems_sources.flat_map(&:remotes).uniq - end - def all_sources path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source] end @@ -115,11 +101,7 @@ def lock_other_sources end def lock_rubygems_sources - if merged_gem_lockfile_sections? - [combine_rubygems_sources] - else - rubygems_sources.sort_by(&:identifier) - end + rubygems_sources.sort_by(&:identifier) end # Returns true if there are changes @@ -228,10 +210,6 @@ def source_list_for(source) end end - def combine_rubygems_sources - Source::Rubygems.new("remotes" => rubygems_remotes) - end - def warn_on_git_protocol(source) return if Bundler.settings["git.allow_insecure"] diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb index a8e12d08c325df..cb88caf1bd1124 100644 --- a/lib/bundler/source_map.rb +++ b/lib/bundler/source_map.rb @@ -23,15 +23,12 @@ def all_requirements if previous_source.nil? requirements[indirect_dependency_name] = source else - no_ambiguous_sources = Bundler.feature_flag.bundler_4_mode? - msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."] msg.concat [previous_source, source].map {|s| " * #{s}" }.sort - msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from." + msg << "You must add this gem to the source block for the source you wish it to be installed from." msg = msg.join("\n") - raise SecurityError, msg if no_ambiguous_sources - Bundler.ui.warn "Warning: #{msg}" + raise SecurityError, msg end end diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 8f796877fda30c..67fc51e86af008 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -299,10 +299,6 @@ def path_sources [] end - def rubygems_remotes - [] - end - def replace_sources!(arg) nil end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index ae2e84766a63cc..65903b3e780024 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -342,24 +342,6 @@ end end - it "finds gems in multiple sources" do - build_repo2 do - build_gem "myrack", "1.2" do |s| - s.executables = "myrackup" - end - end - - install_gemfile <<-G, artifice: "compact_index_extra" - source "https://gemserver.test" - source "https://gemserver.test/extra" - - gem "activesupport", "1.2.3" - gem "myrack", "1.2" - G - - expect(the_bundle).to include_gems "myrack 1.2", "activesupport 1.2.3" - end - it "gives useful errors if no global sources are set, and gems not installed locally, with and without a lockfile" do install_gemfile <<-G, raise_on_error: false gem "myrack" diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index c7d285cd369593..3eeac7f624b6f2 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -151,72 +151,6 @@ end end - describe "with multiple, duplicated sources, with lockfile in old format" do - before do - build_repo2 do - build_gem "dotenv", "2.7.6" - - build_gem "oj", "3.11.3" - build_gem "oj", "3.11.5" - - build_gem "vcr", "6.0.0" - end - - build_repo3 do - build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| - s.add_dependency "oj" - end - end - - gemfile <<~G - source "https://gem.repo2" - - gem "dotenv" - - source "https://gem.repo3" do - gem 'pkg-gem-flowbyte-with-dep' - end - - gem "vcr",source: "https://gem.repo2" - G - - lockfile <<~L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo3/ - specs: - dotenv (2.7.6) - oj (3.11.3) - pkg-gem-flowbyte-with-dep (1.0.0) - oj - vcr (6.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - dotenv - pkg-gem-flowbyte-with-dep! - vcr! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "works" do - bundle :install, artifice: "compact_index" - bundle :outdated, artifice: "compact_index", raise_on_error: false - - expected_output = <<~TABLE - Gem Current Latest Requested Groups - oj 3.11.3 3.11.5 - TABLE - - expect(out).to include(expected_output.strip) - end - end - describe "with --group option" do before do build_repo2 do diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index ef856edf3799aa..f81c5578841dca 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -1078,68 +1078,6 @@ L end end - - context "with multiple, duplicated sources, with lockfile in old format" do - before do - build_repo2 do - build_gem "dotenv", "2.7.6" - - build_gem "oj", "3.11.3" - build_gem "oj", "3.11.5" - - build_gem "vcr", "6.0.0" - end - - build_repo3 do - build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| - s.add_dependency "oj" - end - end - - gemfile <<~G - source "https://gem.repo2" - - gem "dotenv" - - source "https://gem.repo3" do - gem 'pkg-gem-flowbyte-with-dep' - end - - gem "vcr",source: "https://gem.repo2" - G - - lockfile <<~L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo3/ - specs: - dotenv (2.7.6) - oj (3.11.3) - pkg-gem-flowbyte-with-dep (1.0.0) - oj - vcr (6.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - dotenv - pkg-gem-flowbyte-with-dep! - vcr! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "works" do - bundle :install, artifice: "compact_index" - bundle "update oj", artifice: "compact_index" - - expect(out).to include("Bundle updated!") - expect(the_bundle).to include_gems "oj 3.11.5" - end - end end RSpec.describe "bundle update in more complicated situations" do diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 406d881541adf0..c2a0905d215128 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -4,153 +4,6 @@ # repo1 is built automatically before all of the specs run # it contains myrack-obama 1.0.0 and myrack 0.9.1 & 1.0.0 amongst other gems - context "without source affinity" do - before do - # Oh no! Someone evil is trying to hijack myrack :( - # need this to be broken to check for correct source ordering - build_repo3 do - build_gem "myrack", repo3_myrack_version do |s| - s.write "lib/myrack.rb", "MYRACK = 'FAIL'" - end - end - end - - context "with multiple toplevel sources" do - let(:repo3_myrack_version) { "1.0.0" } - - before do - gemfile <<-G - source "https://gem.repo3" - source "https://gem.repo1" - gem "myrack-obama" - gem "myrack" - G - end - - it "refuses to install mismatched checksum because one gem has been tampered with" do - lockfile <<~L - GEM - remote: https://gem.repo3/ - remote: https://gem.repo1/ - specs: - myrack (1.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - depends_on_myrack! - - BUNDLED WITH - #{Bundler::VERSION} - L - - bundle :install, artifice: "compact_index", raise_on_error: false - - expect(exitstatus).to eq(37) - expect(err).to eq <<~E.strip - [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. - Bundler found mismatched checksums. This is a potential security risk. - #{checksum_to_lock(gem_repo1, "myrack", "1.0.0")} - from the API at https://gem.repo1/ - #{checksum_to_lock(gem_repo3, "myrack", "1.0.0")} - from the API at https://gem.repo3/ - - Mismatched checksums each have an authoritative source: - 1. the API at https://gem.repo1/ - 2. the API at https://gem.repo3/ - You may need to alter your Gemfile sources to resolve this issue. - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - end - - context "when checksum validation is disabled" do - before do - bundle "config set --local disable_checksum_validation true" - end - - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first" do - bundle :install, artifice: "compact_index" - - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1") - end - - it "does not use the full index unnecessarily" do - bundle :install, artifice: "compact_index", verbose: true - - expect(out).to include("https://gem.repo1/versions") - expect(out).to include("https://gem.repo3/versions") - expect(out).not_to include("https://gem.repo1/quick/Marshal.4.8/") - expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/") - end - - it "fails", bundler: "4" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - end - - context "when different versions of the same gem are in multiple sources" do - let(:repo3_myrack_version) { "1.2" } - - before do - gemfile <<-G - source "https://gem.repo3" - source "https://gem.repo1" - gem "myrack-obama" - gem "myrack", "1.0.0" # force it to install the working version in repo1 - G - end - - it "warns about ambiguous gems, but installs anyway" do - bundle :install, artifice: "compact_index" - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1") - end - - it "fails", bundler: "4" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - end - - context "without source affinity, and a stdlib gem present in one of the sources", :ruby_repo do - let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } - - before do - build_repo2 do - build_gem "json", default_json_version - end - - build_repo4 do - build_gem "foo" do |s| - s.add_dependency "json", default_json_version - end - end - - gemfile <<-G - source "https://gem.repo2" - source "https://gem.repo4" - - gem "foo" - G - end - - it "works in standalone mode" do - gem_checksum = checksum_digest(gem_repo4, "foo", "1.0") - bundle "install --standalone", artifice: "compact_index", env: { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum } - end - end - context "with source affinity" do context "with sources given by a block" do before do @@ -313,189 +166,6 @@ expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") end end - - context "and in yet another source" do - before do - gemfile <<-G - source "https://gem.repo1" - source "https://gem.repo2" - source "https://gem.repo3" do - gem "depends_on_myrack" - end - G - end - - it "fails when the two sources don't have the same checksum" do - bundle :install, artifice: "compact_index", raise_on_error: false - - expect(err).to eq(<<~E.strip) - [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. - Bundler found mismatched checksums. This is a potential security risk. - #{checksum_to_lock(gem_repo2, "myrack", "1.0.0")} - from the API at https://gem.repo2/ - #{checksum_to_lock(gem_repo1, "myrack", "1.0.0")} - from the API at https://gem.repo1/ - - Mismatched checksums each have an authoritative source: - 1. the API at https://gem.repo2/ - 2. the API at https://gem.repo1/ - You may need to alter your Gemfile sources to resolve this issue. - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - expect(exitstatus).to eq(37) - end - - it "fails when the two sources agree, but the local gem calculates a different checksum" do - myrack_checksum = "c0ffee11" * 8 - bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => myrack_checksum }, raise_on_error: false - - expect(err).to eq(<<~E.strip) - [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. - Bundler found mismatched checksums. This is a potential security risk. - myrack (1.0.0) sha256=#{myrack_checksum} - from the API at https://gem.repo2/ - and the API at https://gem.repo1/ - #{checksum_to_lock(gem_repo2, "myrack", "1.0.0")} - from the gem at #{default_bundle_path("cache", "myrack-1.0.0.gem")} - - If you trust the API at https://gem.repo2/, to resolve this issue you can: - 1. remove the gem at #{default_bundle_path("cache", "myrack-1.0.0.gem")} - 2. run `bundle install` - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - expect(exitstatus).to eq(37) - end - - it "installs from the other source and warns about ambiguous gems when the sources have the same checksum" do - gem_checksum = checksum_digest(gem_repo2, "myrack", "1.0.0") - bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } - - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo2") - - checksums = checksums_section_when_enabled do |c| - c.checksum gem_repo3, "depends_on_myrack", "1.0.1" - c.checksum gem_repo2, "myrack", "1.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo2/ - specs: - myrack (1.0.0) - - GEM - remote: https://gem.repo3/ - specs: - depends_on_myrack (1.0.1) - myrack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - depends_on_myrack! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - - previous_lockfile = lockfile - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - expect(lockfile).to eq(previous_lockfile) - end - - it "installs from the other source and warns about ambiguous gems when checksum validation is disabled" do - bundle "config set --local disable_checksum_validation true" - bundle :install, artifice: "compact_index" - - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo2") - - checksums = checksums_section_when_enabled do |c| - c.no_checksum "depends_on_myrack", "1.0.1" - c.no_checksum "myrack", "1.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo2/ - specs: - myrack (1.0.0) - - GEM - remote: https://gem.repo3/ - specs: - depends_on_myrack (1.0.1) - myrack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - depends_on_myrack! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - - previous_lockfile = lockfile - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - expect(lockfile).to eq(previous_lockfile) - end - - it "fails", bundler: "4" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - - context "and only the dependency is pinned" do - before do - # need this to be broken to check for correct source ordering - build_repo gem_repo2 do - build_gem "myrack", "1.0.0" do |s| - s.write "lib/myrack.rb", "MYRACK = 'FAIL'" - end - end - - gemfile <<-G - source "https://gem.repo3" # contains depends_on_myrack - source "https://gem.repo2" # contains broken myrack - - gem "depends_on_myrack" # installed from gem_repo3 - gem "myrack", :source => "https://gem.repo1" - G - end - - it "installs the dependency from the pinned source without warning" do - bundle :install, artifice: "compact_index" - - expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - - # In https://github.com/rubygems/bundler/issues/3585 this failed - # when there is already a lockfile, and the gems are missing, so try again - system_gems [] - bundle :install, artifice: "compact_index" - - expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - end - - it "fails", bundler: "4" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end end context "when a top-level gem can only be found in an scoped source" do @@ -524,39 +194,6 @@ end end - context "when an indirect dependency can't be found in the aggregate rubygems source" do - before do - build_repo2 - - build_repo3 do - build_gem "depends_on_missing", "1.0.1" do |s| - s.add_dependency "missing" - end - end - - gemfile <<-G - source "https://gem.repo2" - - source "https://gem.repo3" - - gem "depends_on_missing" - G - end - - it "fails" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to end_with <<~E.strip - Could not find compatible versions - - Because every version of depends_on_missing depends on missing >= 0 - and missing >= 0 could not be found in any of the sources, - depends_on_missing cannot be used. - So, because Gemfile depends on depends_on_missing >= 0, - version solving has failed. - E - end - end - context "when a top-level gem has an indirect dependency" do before do build_repo gem_repo2 do @@ -714,337 +351,6 @@ end end - context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do - before do - build_repo gem_repo2 do - build_gem "activesupport", "6.0.3.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 0.7", "< 2" - s.add_dependency "minitest", "~> 5.1" - s.add_dependency "tzinfo", "~> 1.1" - s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" - end - - build_gem "activesupport", "6.1.2.1" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 1.6", "< 2" - s.add_dependency "minitest", ">= 5.1" - s.add_dependency "tzinfo", "~> 2.0" - s.add_dependency "zeitwerk", "~> 2.3" - end - - build_gem "concurrent-ruby", "1.1.8" - build_gem "concurrent-ruby", "1.1.9" - build_gem "connection_pool", "2.2.3" - - build_gem "i18n", "1.8.9" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end - - build_gem "minitest", "5.14.3" - build_gem "myrack", "2.2.3" - build_gem "redis", "4.2.5" - - build_gem "sidekiq", "6.1.3" do |s| - s.add_dependency "connection_pool", ">= 2.2.2" - s.add_dependency "myrack", "~> 2.0" - s.add_dependency "redis", ">= 4.2.0" - end - - build_gem "thread_safe", "0.3.6" - - build_gem "tzinfo", "1.2.9" do |s| - s.add_dependency "thread_safe", "~> 0.1" - end - - build_gem "tzinfo", "2.0.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end - - build_gem "zeitwerk", "2.4.2" - end - - build_repo3 do - build_gem "sidekiq-pro", "5.2.1" do |s| - s.add_dependency "connection_pool", ">= 2.2.3" - s.add_dependency "sidekiq", ">= 6.1.0" - end - end - - gemfile <<-G - # frozen_string_literal: true - - source "https://gem.repo2" - - gem "activesupport" - - source "https://gem.repo3" do - gem "sidekiq-pro" - end - G - - @locked_checksums = checksums_section_when_enabled do |c| - c.checksum gem_repo2, "activesupport", "6.0.3.4" - c.checksum gem_repo2, "concurrent-ruby", "1.1.8" - c.checksum gem_repo2, "connection_pool", "2.2.3" - c.checksum gem_repo2, "i18n", "1.8.9" - c.checksum gem_repo2, "minitest", "5.14.3" - c.checksum gem_repo2, "myrack", "2.2.3" - c.checksum gem_repo2, "redis", "4.2.5" - c.checksum gem_repo2, "sidekiq", "6.1.3" - c.checksum gem_repo3, "sidekiq-pro", "5.2.1" - c.checksum gem_repo2, "thread_safe", "0.3.6" - c.checksum gem_repo2, "tzinfo", "1.2.9" - c.checksum gem_repo2, "zeitwerk", "2.4.2" - end - - lockfile <<~L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo3/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - activesupport - sidekiq-pro! - #{@locked_checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "does not install newer versions but updates the lockfile format when running bundle install in non frozen mode, and doesn't warn" do - bundle :install, artifice: "compact_index" - expect(err).to be_empty - - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo2/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - activesupport - sidekiq-pro! - #{@locked_checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns" do - initial_lockfile = lockfile - - bundle "config set --local frozen true" - bundle :install, artifice: "compact_index" - - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") - - expect(lockfile).to eq(initial_lockfile) - end - - it "fails when running bundle install in frozen mode", bundler: "4" do - initial_lockfile = lockfile - - bundle "config set --local frozen true" - bundle :install, artifice: "compact_index", raise_on_error: false - - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - - expect(lockfile).to eq(initial_lockfile) - end - - it "splits sections and upgrades gems when running bundle update, and doesn't warn" do - bundle "update --all", artifice: "compact_index" - expect(err).to be_empty - - expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") - expect(the_bundle).to include_gems("activesupport 6.1.2.1") - @locked_checksums.checksum gem_repo2, "activesupport", "6.1.2.1" - - expect(the_bundle).not_to include_gems("tzinfo 1.2.9") - expect(the_bundle).to include_gems("tzinfo 2.0.4") - @locked_checksums.checksum gem_repo2, "tzinfo", "2.0.4" - @locked_checksums.delete "thread_safe" - - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") - @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo2/ - specs: - activesupport (6.1.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - concurrent-ruby (1.1.9) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - tzinfo (2.0.4) - concurrent-ruby (~> 1.0) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - activesupport - sidekiq-pro! - #{@locked_checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "upgrades the lockfile format and upgrades the requested gem when running bundle update with an argument" do - bundle "update concurrent-ruby", artifice: "compact_index" - expect(err).to be_empty - - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") - - @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo2/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.9) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - activesupport - sidekiq-pro! - #{@locked_checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - end - end - context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do before do build_lib "activesupport", "7.0.0.alpha", path: lib_path("rails/activesupport") @@ -1207,112 +513,6 @@ end end - context "with a lockfile with aggregated rubygems sources" do - let(:aggregate_gem_section_lockfile) do - <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo3/ - specs: - myrack (0.9.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - myrack! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - let(:split_gem_section_lockfile) do - <<~L - GEM - remote: https://gem.repo1/ - specs: - - GEM - remote: https://gem.repo3/ - specs: - myrack (0.9.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - myrack! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - before do - build_repo3 do - build_gem "myrack", "0.9.1" - end - - gemfile <<-G - source "https://gem.repo1" - source "https://gem.repo3" do - gem 'myrack' - end - G - - lockfile aggregate_gem_section_lockfile - end - - it "installs the existing lockfile but prints a warning when checksum validation is disabled" do - bundle "config set --local deployment true" - bundle "config set --local disable_checksum_validation true" - - bundle "install", artifice: "compact_index" - - expect(lockfile).to eq(aggregate_gem_section_lockfile) - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(the_bundle).to include_gems("myrack 0.9.1", source: "remote3") - end - - it "prints a checksum warning when the checksums from both sources do not match" do - bundle "config set --local deployment true" - - bundle "install", artifice: "compact_index", raise_on_error: false - - api_checksum1 = checksum_digest(gem_repo1, "myrack", "0.9.1") - api_checksum3 = checksum_digest(gem_repo3, "myrack", "0.9.1") - - expect(exitstatus).to eq(37) - expect(err).to eq(<<~E.strip) - [DEPRECATED] Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure. - Bundler found mismatched checksums. This is a potential security risk. - myrack (0.9.1) sha256=#{api_checksum3} - from the API at https://gem.repo3/ - myrack (0.9.1) sha256=#{api_checksum1} - from the API at https://gem.repo1/ - - Mismatched checksums each have an authoritative source: - 1. the API at https://gem.repo3/ - 2. the API at https://gem.repo1/ - You may need to alter your Gemfile sources to resolve this issue. - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - end - - it "refuses to install the existing lockfile and prints an error", bundler: "4" do - bundle "config set --local deployment true" - - bundle "install", artifice: "compact_index", raise_on_error: false - - expect(lockfile).to eq(aggregate_gem_section_lockfile) - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(out).to be_empty - end - end - context "with a path gem in the same Gemfile" do before do build_lib "foo" @@ -1584,37 +784,6 @@ end context "when an indirect dependency is available from multiple ambiguous sources" do - it "succeeds but warns, suggesting a source block" do - build_repo4 do - build_gem "depends_on_myrack" do |s| - s.add_dependency "myrack" - end - build_gem "myrack" - end - - install_gemfile <<-G, artifice: "compact_index_extra_api", raise_on_error: false - source "https://global.source" - - source "https://scoped.source/extra" do - gem "depends_on_myrack" - end - - source "https://scoped.source" do - gem "thin" - end - G - expect(err).to eq <<~EOS.strip - Warning: The gem 'myrack' was found in multiple relevant sources. - * rubygems repository https://scoped.source/ - * rubygems repository https://scoped.source/extra/ - You should add this gem to the source block for the source you wish it to be installed from. - EOS - expect(last_command).to be_success - expect(the_bundle).to be_locked - end - end - - context "when an indirect dependency is available from multiple ambiguous sources", bundler: "4" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_myrack" do |s| @@ -1645,83 +814,6 @@ end end - context "when upgrading a lockfile suffering from dependency confusion" do - before do - build_repo4 do - build_gem "mime-types", "3.0.0" - end - - build_repo2 do - build_gem "capybara", "2.5.0" do |s| - s.add_dependency "mime-types", ">= 1.16" - end - - build_gem "mime-types", "3.3.1" - end - - gemfile <<~G - source "https://gem.repo2" - - gem "capybara", "~> 2.5.0" - - source "https://gem.repo4" do - gem "mime-types", "~> 3.0" - end - G - - lockfile <<-L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo4/ - specs: - capybara (2.5.0) - mime-types (>= 1.16) - mime-types (3.3.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - capybara (~> 2.5.0) - mime-types (~> 3.0)! - - CHECKSUMS - L - end - - it "upgrades the lockfile correctly" do - bundle "lock --update", artifice: "compact_index" - - checksums = checksums_section_when_enabled do |c| - c.checksum gem_repo2, "capybara", "2.5.0" - c.checksum gem_repo4, "mime-types", "3.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo2/ - specs: - capybara (2.5.0) - mime-types (>= 1.16) - - GEM - remote: https://gem.repo4/ - specs: - mime-types (3.0.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - capybara (~> 2.5.0) - mime-types (~> 3.0)! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - end - end - context "when default source includes old gems with nil required_ruby_version" do before do build_repo2 do diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 3e4b18a188e8cc..64c59d4826f357 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -324,24 +324,6 @@ def require(*args) FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "compact_index_extra" - expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources with source blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] - end - install_gemfile <<-G, artifice: "compact_index_extra", verbose: true source "#{source_uri}" source "#{source_uri}/extra" do @@ -375,11 +357,13 @@ def require(*args) expect(the_bundle).to include_gems "myrack 1.2" end - it "considers all possible versions of dependencies from all api gem sources" do - # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that - # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 - # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other - # repo and installs it. + it "resolves indirect dependencies to the most scoped source that includes them" do + # In this scenario, the gem "somegem" only exists in repo4. It depends on + # specific version of activesupport that exists only in repo1. There + # happens also be a version of activesupport in repo4, but not the one that + # version 1.0.0 of somegem wants. This test makes sure that bundler tries to + # use the version in the most scoped source, even if not compatible, and + # gives a resolution error build_repo4 do build_gem "activesupport", "1.2.0" build_gem "somegem", "1.0.0" do |s| @@ -389,14 +373,14 @@ def require(*args) gemfile <<-G source "#{source_uri}" - source "#{source_uri}/extra" - gem 'somegem', '1.0.0' + source "#{source_uri}/extra" do + gem 'somegem', '1.0.0' + end G - bundle :install, artifice: "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api", raise_on_error: false - expect(the_bundle).to include_gems "somegem 1.0.0" - expect(the_bundle).to include_gems "activesupport 1.2.3" + expect(err).to include("Could not find compatible versions") end it "prints API output properly with back deps" do @@ -479,26 +463,6 @@ def require(*args) FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "compact_index_extra" - bundle "config --set local deployment true" - bundle :install, artifice: "compact_index_extra" - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] - end - gemfile <<-G source "#{source_uri}" source "#{source_uri}/extra" do @@ -583,19 +547,6 @@ def require(*args) expect(the_bundle).to include_gems "myrack 1.0.0" end - it "strips http basic auth creds when warning about ambiguous sources" do - gemfile <<-G - source "#{basic_auth_source_uri}" - source "#{file_uri_for(gem_repo1)}" - gem "myrack" - G - - bundle :install, artifice: "compact_index_basic_authentication" - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - it "does not pass the user / password to different hosts on redirect" do gemfile <<-G source "#{basic_auth_source_uri}" diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 012e2d3995b4a2..1650df3dfb6ae6 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -262,24 +262,6 @@ def require(*args) FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "endpoint_extra" - expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] - end - gemfile <<-G source "#{source_uri}" source "#{source_uri}/extra" do @@ -313,11 +295,13 @@ def require(*args) expect(the_bundle).to include_gems "myrack 1.2" end - it "considers all possible versions of dependencies from all api gem sources" do - # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that - # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 - # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other - # repo and installs it. + it "resolves indirect dependencies to the most scoped source that includes them" do + # In this scenario, the gem "somegem" only exists in repo4. It depends on + # specific version of activesupport that exists only in repo1. There + # happens also be a version of activesupport in repo4, but not the one that + # version 1.0.0 of somegem wants. This test makes sure that bundler tries to + # use the version in the most scoped source, even if not compatible, and + # gives a resolution error build_repo4 do build_gem "activesupport", "1.2.0" build_gem "somegem", "1.0.0" do |s| @@ -327,14 +311,14 @@ def require(*args) gemfile <<-G source "#{source_uri}" - source "#{source_uri}/extra" - gem 'somegem', '1.0.0' + source "#{source_uri}/extra" do + gem 'somegem', '1.0.0' + end G - bundle :install, artifice: "endpoint_extra_api" + bundle :install, artifice: "compact_index_extra_api", raise_on_error: false - expect(the_bundle).to include_gems "somegem 1.0.0" - expect(the_bundle).to include_gems "activesupport 1.2.3" + expect(err).to include("Could not find compatible versions") end it "prints API output properly with back deps" do @@ -368,25 +352,6 @@ def require(*args) FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end - install_gemfile <<-G, artifice: "endpoint_extra_missing" - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "does not fetch every spec when doing back deps using blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - build_gem "missing" - - FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] - end - install_gemfile <<-G, artifice: "endpoint_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do @@ -405,26 +370,6 @@ def require(*args) FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "endpoint_extra" - bundle "config set --local deployment true" - bundle :install, artifice: "endpoint_extra" - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] - end - gemfile <<-G source "#{source_uri}" source "#{source_uri}/extra" do @@ -546,19 +491,6 @@ def require(*args) expect(out).not_to include("#{user}:#{password}") end - it "strips http basic auth creds when warning about ambiguous sources" do - gemfile <<-G - source "#{basic_auth_source_uri}" - source "#{file_uri_for(gem_repo1)}" - gem "myrack" - G - - bundle :install, artifice: "endpoint_basic_authentication" - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - it "does not pass the user / password to different hosts on redirect" do gemfile <<-G source "#{basic_auth_source_uri}" diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index bf0fca5ddd6baa..e9d62bc3b87df5 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -484,53 +484,62 @@ context "bundle install with multiple sources" do before do - install_gemfile <<-G + install_gemfile <<-G, raise_on_error: false source "https://gem.repo3" source "https://gem.repo1" G end - it "shows a deprecation" do - expect(deprecations).to include( - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." + it "fails with a helpful error" do + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) end it "doesn't show lockfile deprecations if there's a lockfile" do - bundle "install" + lockfile <<~L + GEM + remote: https://gem.repo3/ + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES - expect(deprecations).to include( - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." + BUNDLED WITH + #{Bundler::VERSION} + L + bundle "install", raise_on_error: false + + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) - expect(deprecations).not_to include( + expect(err).not_to include( "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \ "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." ) bundle "config set --local frozen true" - bundle "install" + bundle "install", raise_on_error: false - expect(deprecations).to include( - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) - expect(deprecations).not_to include( + expect(err).not_to include( "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \ "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." ) end - - pending "fails with a helpful error", bundler: "4" end - context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do + context "bundle install with a lockfile with a single rubygems section with multiple remotes" do before do build_repo3 do build_gem "myrack", "0.9.1" @@ -559,17 +568,13 @@ BUNDLED WITH #{Bundler::VERSION} L - - bundle "config set --local frozen true" end - it "shows a deprecation" do - bundle "install" + it "shows an error" do + bundle "install", raise_on_error: false - expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.") + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.") end - - pending "fails with a helpful error", bundler: "4" end context "when Bundler.setup is run in a ruby script" do From db027afebd3ee8ef3fd70ffea2ef7756f48003ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:19:01 +0200 Subject: [PATCH 26/32] [rubygems/rubygems] Remove aggregate source mentions It's a term from times with multiple remote sources, let's move on :) https://github.com/rubygems/rubygems/commit/6439b8944e --- lib/bundler/plugin/source_list.rb | 2 +- lib/bundler/source_list.rb | 4 ++-- spec/bundler/bundler/source_list_spec.rb | 26 ++++++++++++------------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb index 746996de5548ac..d929ade29e7ee4 100644 --- a/lib/bundler/plugin/source_list.rb +++ b/lib/bundler/plugin/source_list.rb @@ -23,7 +23,7 @@ def all_sources private - def rubygems_aggregate_class + def source_class Plugin::Installer::Rubygems end end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 6dba3f35e8f288..38fa0972e64ebc 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -9,7 +9,7 @@ class SourceList :metadata_source def global_rubygems_source - @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + @global_rubygems_source ||= source_class.new("allow_local" => true) end def initialize @@ -191,7 +191,7 @@ def replace_path_source(replacement_sources, gemfile_source) end end - def rubygems_aggregate_class + def source_class Source::Rubygems end diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb index 13453cb2a33a0d..6e0be6c92fccb3 100644 --- a/spec/bundler/bundler/source_list_spec.rb +++ b/spec/bundler/bundler/source_list_spec.rb @@ -11,7 +11,7 @@ subject(:source_list) { Bundler::SourceList.new } - let(:rubygems_aggregate) { Bundler::Source::Rubygems.new } + let(:global_rubygems_source) { Bundler::Source::Rubygems.new } let(:metadata_source) { Bundler::Source::Metadata.new } describe "adding sources" do @@ -118,11 +118,11 @@ describe "#add_global_rubygems_remote" do let!(:returned_source) { source_list.add_global_rubygems_remote("https://rubygems.org/") } - it "returns the aggregate rubygems source" do + it "returns the global rubygems source" do expect(returned_source).to be_instance_of(Bundler::Source::Rubygems) end - it "adds the provided remote to the beginning of the aggregate source" do + it "adds the provided remote to the beginning of the global source" do source_list.add_global_rubygems_remote("https://othersource.org") expect(returned_source.remotes).to eq [ Gem::URI("https://othersource.org/"), @@ -156,21 +156,21 @@ end describe "#all_sources" do - it "includes the aggregate rubygems source when rubygems sources have been added" do + it "includes the global rubygems source when rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) source_list.add_path_source("path" => "/path/to/gem") source_list.add_plugin_source("new_source", "uri" => "https://some.url/a") - expect(source_list.all_sources).to include rubygems_aggregate + expect(source_list.all_sources).to include global_rubygems_source end - it "includes the aggregate rubygems source when no rubygems sources have been added" do + it "includes the global rubygems source when no rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_path_source("path" => "/path/to/gem") source_list.add_plugin_source("new_source", "uri" => "https://some.url/a") - expect(source_list.all_sources).to include rubygems_aggregate + expect(source_list.all_sources).to include global_rubygems_source end it "returns sources of the same type in the reverse order that they were added" do @@ -204,7 +204,7 @@ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]), - rubygems_aggregate, + global_rubygems_source, metadata_source, ] end @@ -297,19 +297,19 @@ end describe "#rubygems_sources" do - it "includes the aggregate rubygems source when rubygems sources have been added" do + it "includes the global rubygems source when rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) source_list.add_path_source("path" => "/path/to/gem") - expect(source_list.rubygems_sources).to include rubygems_aggregate + expect(source_list.rubygems_sources).to include global_rubygems_source end - it "returns only the aggregate rubygems source when no rubygems sources have been added" do + it "returns only the global rubygems source when no rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_path_source("path" => "/path/to/gem") - expect(source_list.rubygems_sources).to eq [rubygems_aggregate] + expect(source_list.rubygems_sources).to eq [global_rubygems_source] end it "returns rubygems sources in the reverse order that they were added" do @@ -331,7 +331,7 @@ Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]), - rubygems_aggregate, + global_rubygems_source, ] end end From 1213adfe5526d65cce81a9fb127074130c8faea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 15 Sep 2025 16:26:43 +0200 Subject: [PATCH 27/32] [ruby/json] Better handle missing ostruct In the Ruby test suite, this test class is causing trouble because ostruct is not available. Having an autoload for JSON::GenericObject but causing it not to define the constant causes a warning. See https://github.com/ruby/json/commit/0dc1cd407e77 and https://github.com/ruby/json/commit/caa5d8cdd748 in ruby. We can skip defining the test class entirely instead when ostruct is not available. https://github.com/ruby/json/commit/6f6a4cdfd7 --- test/json/json_generic_object_test.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/json/json_generic_object_test.rb b/test/json/json_generic_object_test.rb index 995d57edafe2dd..71d105976d0cfa 100644 --- a/test/json/json_generic_object_test.rb +++ b/test/json/json_generic_object_test.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true require_relative 'test_helper' -class JSONGenericObjectTest < Test::Unit::TestCase +# ostruct is required to test JSON::GenericObject +begin + require "ostruct" +rescue LoadError + return +end +class JSONGenericObjectTest < Test::Unit::TestCase def setup if defined?(JSON::GenericObject) @go = JSON::GenericObject[ :a => 1, :b => 2 ] From 971174054a26be0a4becbe7c67a7cc980158abf2 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 4 Sep 2025 18:59:42 +0100 Subject: [PATCH 28/32] Add a macro to manage the condition of no-inline version rb_current_ec Add the macro `RB_THREAD_CURRENT_EC_NOINLINE` to manage the condition to use no-inline version rb_current_ec for a better maintainability. Note that the `vm_core.h` includes the `THREAD_IMPL_H` by the `#include THREAD_IMPL_H`. The `THREAD_IMPL_H` can be `thread_none.h`, `thread_pthread.h` or `thread_win32.h` according to the `tool/m4/ruby_thread.m4` creating the `THREAD_IMPL_H`. The change in this commit only defining the `RB_THREAD_CURRENT_EC_NOINLINE` in the `thread_pthread.h` is okay in this situation. Because in the `thread_none.h` case, the thread feature is not used at all, including Thread-Local Storage (TLS), and in the `thread_win32.h` case, the `RB_THREAD_LOCAL_SPECIFIER` is not defined. In the `thread_pthread.h` case, the `RB_THREAD_LOCAL_SPECIFIER` is defined in the `configure.ac`. In the `thread_none.h` case, the `RB_THREAD_LOCAL_SPECIFIER` is defined in the `thread_none.h`. --- thread_pthread.h | 8 ++++++-- vm.c | 2 +- vm_core.h | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/thread_pthread.h b/thread_pthread.h index 20a3876759c713..d635948c4bbb7c 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -17,6 +17,11 @@ #define RB_NATIVETHREAD_LOCK_INIT PTHREAD_MUTEX_INITIALIZER #define RB_NATIVETHREAD_COND_INIT PTHREAD_COND_INITIALIZER +// TLS can not be accessed across .so on arm64 and perhaps ppc64le too. +#if defined(__arm64__) || defined(__aarch64__) || defined(__powerpc64__) +# define RB_THREAD_CURRENT_EC_NOINLINE +#endif + // this data should be protected by timer_th.waiting_lock struct rb_thread_sched_waiting { enum thread_sched_waiting_flag { @@ -133,8 +138,7 @@ struct rb_thread_sched { #ifdef RB_THREAD_LOCAL_SPECIFIER NOINLINE(void rb_current_ec_set(struct rb_execution_context_struct *)); - # if defined(__arm64__) || defined(__aarch64__) || defined(__powerpc64__) - // TLS can not be accessed across .so on arm64 and perhaps ppc64le too. + # ifdef RB_THREAD_CURRENT_EC_NOINLINE NOINLINE(struct rb_execution_context_struct *rb_current_ec(void)); # else RUBY_EXTERN RB_THREAD_LOCAL_SPECIFIER struct rb_execution_context_struct *ruby_current_ec; diff --git a/vm.c b/vm.c index e97619cc14b1c3..da92c5bd009f83 100644 --- a/vm.c +++ b/vm.c @@ -594,7 +594,7 @@ rb_current_ec_set(rb_execution_context_t *ec) } -#if defined(__arm64__) || defined(__aarch64__) || defined(__powerpc64__) +#ifdef RB_THREAD_CURRENT_EC_NOINLINE rb_execution_context_t * rb_current_ec(void) { diff --git a/vm_core.h b/vm_core.h index 1d02298c6cd439..9156b7286891a7 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1999,7 +1999,7 @@ static inline rb_execution_context_t * rb_current_execution_context(bool expect_ec) { #ifdef RB_THREAD_LOCAL_SPECIFIER - #if defined(__arm64__) || defined(__aarch64__) || defined(__powerpc64__) + #ifdef RB_THREAD_CURRENT_EC_NOINLINE rb_execution_context_t * volatile ec = rb_current_ec(); #else rb_execution_context_t * volatile ec = ruby_current_ec; From a2849239db9a92c3cde75724f92688e230718cc6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 17:20:46 +0900 Subject: [PATCH 29/32] Expect `git -C ` to work This option is available since git 1.8.5 that was released in 2013. --- common.mk | 12 +++++++---- defs/gmake.mk | 50 ++++++++++++++++++++++---------------------- template/Makefile.in | 3 +-- win32/Makefile.sub | 6 +++--- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/common.mk b/common.mk index ef7eb6ab58ca1e..0f157c4d56566b 100644 --- a/common.mk +++ b/common.mk @@ -44,6 +44,10 @@ RUBYLIB = $(PATH_SEPARATOR) RUBYOPT = - RUN_OPTS = --disable-gems +GIT_IN_SRC = $(GIT) -C $(srcdir) +GIT_LOG = $(GIT_IN_SRC) log +GIT_LOG_FORMAT = $(GIT_LOG) --pretty=format: + # GITPULLOPTIONS = --no-tags PRISM_SRCDIR = $(srcdir)/prism @@ -1514,8 +1518,8 @@ update-bundled_gems: PHONY $(tooldir)/update-bundled_gems.rb \ "$(srcdir)/gems/bundled_gems" | \ $(IFCHANGE) "$(srcdir)/gems/bundled_gems" - - $(GIT) -C "$(srcdir)" diff --no-ext-diff --ignore-submodules --exit-code || \ - $(GIT) -C "$(srcdir)" commit -m "Update bundled_gems" gems/bundled_gems + $(GIT_IN_SRC) diff --no-ext-diff --ignore-submodules --exit-code || \ + $(GIT_IN_SRC) commit -m "Update bundled_gems" gems/bundled_gems PRECHECK_BUNDLED_GEMS = yes test-bundled-gems-precheck: $(TEST_RUNNABLE)-test-bundled-gems-precheck @@ -1899,8 +1903,8 @@ nightly: yesterday $(DOT_WAIT) install yesterday: rewindable rewindable: - $(GIT) -C $(srcdir) status --porcelain - $(GIT) -C $(srcdir) diff --quiet + $(GIT_IN_SRC) status --porcelain + $(GIT_IN_SRC) diff --quiet HELP_EXTRA_TASKS = "" diff --git a/defs/gmake.mk b/defs/gmake.mk index 795320ce2d20aa..1173a147a2a118 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -220,8 +220,8 @@ post-commit: $(if $(DOT_WAIT),,do-commit) GITHUB_RUBY_URL = https://github.com/ruby/ruby PR = -COMMIT_GPG_SIGN = $(shell $(GIT) -C "$(srcdir)" config commit.gpgsign) -REMOTE_GITHUB_URL = $(shell $(GIT) -C "$(srcdir)" config remote.github.url) +COMMIT_GPG_SIGN = $(shell $(GIT_IN_SRC) config commit.gpgsign) +REMOTE_GITHUB_URL = $(shell $(GIT_IN_SRC) config remote.github.url) COMMITS_NOTES = commits .PHONY: fetch-github @@ -236,19 +236,19 @@ define fetch-github $(eval REMOTE_GITHUB_URL := $(REMOTE_GITHUB_URL)) $(if $(REMOTE_GITHUB_URL),, echo adding $(GITHUB_RUBY_URL) as remote github - $(GIT) -C "$(srcdir)" remote add github $(GITHUB_RUBY_URL) - $(GIT) -C "$(srcdir)" config --add remote.github.fetch +refs/notes/$(COMMITS_NOTES):refs/notes/$(COMMITS_NOTES) + $(GIT_IN_SRC) remote add github $(GITHUB_RUBY_URL) + $(GIT_IN_SRC) config --add remote.github.fetch +refs/notes/$(COMMITS_NOTES):refs/notes/$(COMMITS_NOTES) $(eval REMOTE_GITHUB_URL := $(GITHUB_RUBY_URL)) ) - $(if $(shell $(GIT) -C "$(srcdir)" rev-parse "github/pull/$(1)/head" -- 2> /dev/null), - $(GIT) -C "$(srcdir)" branch -f "gh-$(1)" "github/pull/$(1)/head", - $(GIT) -C "$(srcdir)" fetch -f github "pull/$(1)/head:gh-$(1)" + $(if $(shell $(GIT_IN_SRC) rev-parse "github/pull/$(1)/head" -- 2> /dev/null), + $(GIT_IN_SRC) branch -f "gh-$(1)" "github/pull/$(1)/head", + $(GIT_IN_SRC) fetch -f github "pull/$(1)/head:gh-$(1)" ) endef .PHONY: checkout-github checkout-github: fetch-github - $(GIT) -C "$(srcdir)" checkout "gh-$(PR)" + $(GIT_IN_SRC) checkout "gh-$(PR)" .PHONY: update-github update-github: fetch-github @@ -261,25 +261,25 @@ update-github: fetch-github $(eval PR_BRANCH := $(word 2,$(PULL_REQUEST_FORK_BRANCH))) $(eval GITHUB_UPDATE_WORKTREE := $(shell mktemp -d "$(srcdir)/gh-$(PR)-XXXXXX")) - $(GIT) -C "$(srcdir)" worktree add $(notdir $(GITHUB_UPDATE_WORKTREE)) "gh-$(PR)" + $(GIT_IN_SRC) worktree add $(notdir $(GITHUB_UPDATE_WORKTREE)) "gh-$(PR)" $(GIT) -C "$(GITHUB_UPDATE_WORKTREE)" merge master --no-edit @$(BASERUBY) -e 'print "Are you sure to push this to PR=$(PR)? [Y/n]: "; exit(gets.chomp != "n")' - $(GIT) -C "$(srcdir)" remote add fork-$(PR) git@github.com:$(FORK_REPO).git + $(GIT_IN_SRC) remote add fork-$(PR) git@github.com:$(FORK_REPO).git $(GIT) -C "$(GITHUB_UPDATE_WORKTREE)" push fork-$(PR) gh-$(PR):$(PR_BRANCH) - $(GIT) -C "$(srcdir)" remote rm fork-$(PR) - $(GIT) -C "$(srcdir)" worktree remove $(notdir $(GITHUB_UPDATE_WORKTREE)) - $(GIT) -C "$(srcdir)" branch -D gh-$(PR) + $(GIT_IN_SRC) remote rm fork-$(PR) + $(GIT_IN_SRC) worktree remove $(notdir $(GITHUB_UPDATE_WORKTREE)) + $(GIT_IN_SRC) branch -D gh-$(PR) .PHONY: pull-github pull-github: fetch-github $(call pull-github,$(PR)) define pull-github - $(eval GITHUB_MERGE_BASE := $(shell $(GIT) -C "$(srcdir)" log -1 --format=format:%H)) - $(eval GITHUB_MERGE_BRANCH := $(shell $(GIT) -C "$(srcdir)" symbolic-ref --short HEAD)) + $(eval GITHUB_MERGE_BASE := $(shell $(GIT_LOG_FORMAT):%H -1) + $(eval GITHUB_MERGE_BRANCH := $(shell $(GIT_IN_SRC) symbolic-ref --short HEAD)) $(eval GITHUB_MERGE_WORKTREE := $(shell mktemp -d "$(srcdir)/gh-$(1)-XXXXXX")) - $(GIT) -C "$(srcdir)" worktree prune - $(GIT) -C "$(srcdir)" worktree add $(notdir $(GITHUB_MERGE_WORKTREE)) "gh-$(1)" + $(GIT_IN_SRC) worktree prune + $(GIT_IN_SRC) worktree add $(notdir $(GITHUB_MERGE_WORKTREE)) "gh-$(1)" $(GIT) -C "$(GITHUB_MERGE_WORKTREE)" rebase $(GITHUB_MERGE_BRANCH) $(eval COMMIT_GPG_SIGN := $(COMMIT_GPG_SIGN)) $(if $(filter true,$(COMMIT_GPG_SIGN)), \ @@ -294,7 +294,7 @@ fetch-github-%: .PHONY: checkout-github-% checkout-github-%: fetch-github-% - $(GIT) -C "$(srcdir)" checkout "gh-$*" + $(GIT_IN_SRC) checkout "gh-$*" .PHONY: pr-% pull-github-% pr-% pull-github-%: fetch-github-% @@ -433,7 +433,7 @@ ifneq ($(DOT_WAIT),) endif ifeq ($(HAVE_GIT),yes) -REVISION_LATEST := $(shell $(CHDIR) $(srcdir) && $(GIT) log -1 --format=%H 2>/dev/null) +REVISION_LATEST := $(shell $(GIT_LOG_FORMAT)%H -1 2>/dev/null) else REVISION_LATEST := update endif @@ -495,7 +495,7 @@ endif update-deps: $(eval update_deps := $(shell date +update-deps-%Y%m%d)) $(eval deps_dir := $(shell mktemp -d)/$(update_deps)) - $(eval GIT_DIR := $(shell $(GIT) -C $(srcdir) rev-parse --absolute-git-dir)) + $(eval GIT_DIR := $(shell $(GIT_IN_SRC) rev-parse --absolute-git-dir)) $(GIT) --git-dir=$(GIT_DIR) worktree add $(deps_dir) cp $(tooldir)/config.guess $(tooldir)/config.sub $(deps_dir)/tool [ -f config.status ] && cp config.status $(deps_dir) @@ -543,13 +543,13 @@ matz: up $(eval NEW := $(MAJOR).$(MINOR).0) $(eval message := Development of $(NEW) started.) $(eval files := include/ruby/version.h include/ruby/internal/abi.h) - $(GIT) -C $(srcdir) mv -f NEWS.md doc/NEWS/NEWS-$(OLD).md - $(GIT) -C $(srcdir) commit -m "[DOC] Flush NEWS.md" + $(GIT_IN_SRC) mv -f NEWS.md doc/NEWS/NEWS-$(OLD).md + $(GIT_IN_SRC) commit -m "[DOC] Flush NEWS.md" sed -i~ \ -e "s/^\(#define RUBY_API_VERSION_MINOR\) .*/\1 $(MINOR)/" \ -e "s/^\(#define RUBY_ABI_VERSION\) .*/\1 0/" \ $(files:%=$(srcdir)/%) - $(GIT) -C $(srcdir) add $(files) + $(GIT_IN_SRC) add $(files) $(BASERUBY) -C $(srcdir) -p -00 \ -e 'BEGIN {old, new = ARGV.shift(2); STDOUT.reopen("NEWS.md")}' \ -e 'case $$.' \ @@ -559,8 +559,8 @@ matz: up -e 'next if /^[\[ *]/ =~ $$_' \ -e '$$_.sub!(/\n{2,}\z/, "\n\n")' \ $(OLD) $(NEW) doc/NEWS/NEWS-$(OLD).md - $(GIT) -C $(srcdir) add NEWS.md - $(GIT) -C $(srcdir) commit -m "$(message)" + $(GIT_IN_SRC) add NEWS.md + $(GIT_IN_SRC) commit -m "$(message)" tags: $(MAKE) GIT="$(GIT)" -C "$(srcdir)" -f defs/tags.mk diff --git a/template/Makefile.in b/template/Makefile.in index 68d4240c4b56fa..bf5281e0df8498 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -748,5 +748,4 @@ yes-test-syntax-suggest: $(PREPARE_SYNTAX_SUGGEST) no-test-syntax-suggest: yesterday: - $(GIT) -C $(srcdir) reset --hard \ - `$(GIT) -C $(srcdir) log -1 --before=00:00+0900 --format=%H` + $(GIT_IN_SRC) reset --hard `$(GIT_LOG_FORMAT):%H -1 --before=00:00+0900` diff --git a/win32/Makefile.sub b/win32/Makefile.sub index c91c05584e6331..180dfbb8feb7f4 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1336,7 +1336,7 @@ $(RCFILES): $(RBCONFIG) $(srcdir)/revision.h $(srcdir)/win32/resource.rb update-benchmark-driver: $(GIT) clone https://github.com/benchmark-driver/benchmark-driver $(srcdir)/benchmark/benchmark-driver || \ - $(GIT) -C $(srcdir)/benchmark/benchmark-driver pull origin master + $(GIT_IN_SRC)/benchmark/benchmark-driver pull origin master $(ruby_pc): $(RBCONFIG) @$(BOOTSTRAPRUBY) $(tooldir)/expand-config.rb \ @@ -1507,5 +1507,5 @@ exts: rubyspec-capiext yesterday: for /f "usebackq" %H in \ - (`$(GIT) -C $(srcdir) log -1 "--before=00:00+0900" "--format=%H"`) do \ - $(GIT) -C $(srcdir) reset --hard %%H + (`$(GIT_LOG_FORMAT):%H -1 "--before=00:00+0900"`) do \ + $(GIT_IN_SRC) reset --hard %%H From 5480a9c34457fc93a913cecddf361e0531ee1f26 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 17:38:23 +0900 Subject: [PATCH 30/32] Reject git command that does not accept `-C` option --- configure.ac | 10 ++++++++-- win32/Makefile.sub | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 8cb30429dcddca..2d1c99bfb66702 100644 --- a/configure.ac +++ b/configure.ac @@ -107,10 +107,16 @@ HAVE_GIT=yes AC_ARG_WITH(git, AS_HELP_STRING([--without-git], [never use git]), [AS_CASE([$withval], - [no], [GIT=never-use HAVE_GIT=no], + [no], [HAVE_GIT=no], [yes], [], [GIT=$withval])]) -AS_IF([test x"$HAVE_GIT" = xyes], [command -v "$GIT" > /dev/null || HAVE_GIT=no]) +{ + test x"$HAVE_GIT" = xyes && + command -v "$GIT" > /dev/null && + # git 1.8.4 fails with `-C` as unknown option, but git 1.8.5 + # succeeds and exits by `--version`. + $GIT -C . --version > /dev/null 2>&1 +} || HAVE_GIT=no GIT=never-use AC_SUBST(GIT) AC_SUBST(HAVE_GIT) diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 180dfbb8feb7f4..89d1ac4c929925 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -506,17 +506,18 @@ VPATH = $(arch_hdrdir)/ruby;$(hdrdir)/ruby;$(srcdir);$(srcdir)/missing;$(win_src !ifndef GIT GIT = git !endif +GIT_TO_CHECK_C_OPTION = -C . --version >nul 2>&1 !if "$(HAVE_GIT)" == "yes" || "$(HAVE_GIT)" == "no" !else if "$(GIT)" == "" HAVE_GIT = no !else if [for %I in ($(GIT)) do @if not "%~xI" == "" exit 1] -! if [for %I in ($(GIT)) do @if not "%~$$PATH:I" == "" exit 1] +! if [%I $(GIT_TO_CHECK_C_OPTION)] == 0 HAVE_GIT = yes ! else HAVE_GIT = no ! endif !else -! if [for %x in (%PATHEXT:;= %) do @for %I in ($(GIT)%x) do @if not "%~$$PATH:I" == "" exit 1] +! if [for %x in (%PATHEXT:;= %) do @($(GIT)%x $(GIT_TO_CHECK_C_OPTION) && exit 0)] == 0 HAVE_GIT = yes ! else HAVE_GIT = no From a6a5fe322289c6daf1b1a8a96f4f244ed1f0e814 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Sep 2025 20:28:16 +0900 Subject: [PATCH 31/32] Suppress verification messages `log.showSignature` configuration and `--no-show-signature` option was added at git 2.10.0. --- common.mk | 2 +- configure.ac | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common.mk b/common.mk index 0f157c4d56566b..1b68736a18e398 100644 --- a/common.mk +++ b/common.mk @@ -45,7 +45,7 @@ RUBYOPT = - RUN_OPTS = --disable-gems GIT_IN_SRC = $(GIT) -C $(srcdir) -GIT_LOG = $(GIT_IN_SRC) log +GIT_LOG = $(GIT_IN_SRC) log --no-show-signature GIT_LOG_FORMAT = $(GIT_LOG) --pretty=format: # GITPULLOPTIONS = --no-tags diff --git a/configure.ac b/configure.ac index 2d1c99bfb66702..da5afc00185f95 100644 --- a/configure.ac +++ b/configure.ac @@ -113,9 +113,11 @@ AC_ARG_WITH(git, { test x"$HAVE_GIT" = xyes && command -v "$GIT" > /dev/null && - # git 1.8.4 fails with `-C` as unknown option, but git 1.8.5 - # succeeds and exits by `--version`. - $GIT -C . --version > /dev/null 2>&1 + # `git -C`: 1.8.5 + # `git log --no-show-signature`: 2.10.0 + AS_CASE([`$GIT -C . --version 2> /dev/null | sed 's/.* //'`], + [0.*|1.*|2.@<:@0-9@:>@.*], [false], + [true]) } || HAVE_GIT=no GIT=never-use AC_SUBST(GIT) AC_SUBST(HAVE_GIT) From 809dfb861ef0599c2572f10236aaf25c2093f6af Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 15 Sep 2025 11:24:23 -0400 Subject: [PATCH 32/32] Don't export rb_imemo_new Nothing needs rb_imemo_new exported anymore. --- internal/imemo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/imemo.h b/internal/imemo.h index eee3dd71854a8e..3b91ef4b818f93 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -131,6 +131,7 @@ struct MEMO { #ifndef RUBY_RUBYPARSER_H typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif +VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size); VALUE rb_imemo_tmpbuf_new(void); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); @@ -147,7 +148,6 @@ void rb_imemo_mark_and_move(VALUE obj, bool reference_updating); void rb_imemo_free(VALUE obj); RUBY_SYMBOL_EXPORT_BEGIN -VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size); const char *rb_imemo_name(enum imemo_type type); RUBY_SYMBOL_EXPORT_END