diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 6ff1f977e85e9b..baf1b1bc0a6cd6 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -92,7 +92,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@d6e286fa45544157a02d45a43742857ebbc25d12 # v2.68.16 + - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 4bc959d6d66144..6c529ed6bc40c4 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -119,7 +119,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@d6e286fa45544157a02d45a43742857ebbc25d12 # v2.68.16 + - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/NEWS.md b/NEWS.md index 54d875f92457e5..d9aaa08be6c754 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,12 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* ENV + + * `ENV.fetch_values` is added. It returns an array of values for the + given names, raising `KeyError` for missing names unless a block is + given. [[Feature #21781]] + * Kernel * `Kernel#autoload_relative` and `Module#autoload_relative` are added. diff --git a/compile.c b/compile.c index c9806947fd8669..ecc19f6b651d72 100644 --- a/compile.c +++ b/compile.c @@ -1029,75 +1029,44 @@ rb_iseq_original_iseq(const rb_iseq_t *iseq) /* cold path */ /* definition of data structure for compiler */ /*********************************************/ -/* - * On 32-bit SPARC, GCC by default generates SPARC V7 code that may require - * 8-byte word alignment. On the other hand, Oracle Solaris Studio seems to - * generate SPARCV8PLUS code with unaligned memory access instructions. - * That is why the STRICT_ALIGNMENT is defined only with GCC. - */ -#if defined(__sparc) && SIZEOF_VOIDP == 4 && defined(__GNUC__) - #define STRICT_ALIGNMENT +#if defined(HAVE_TRUE_LONG_LONG) && SIZEOF_LONG_LONG > SIZEOF_VALUE +# define ALIGNMENT_SIZE SIZEOF_LONG_LONG +#else +# define ALIGNMENT_SIZE SIZEOF_VALUE #endif +#define PADDING_SIZE_MAX ((size_t)((ALIGNMENT_SIZE) - 1)) -/* - * Some OpenBSD platforms (including sparc64) require strict alignment. - */ -#if defined(__OpenBSD__) - #include - #ifdef __STRICT_ALIGNMENT - #define STRICT_ALIGNMENT - #endif -#endif +#define ALIGNMENT_SIZE_OF(type) alignment_size_assert(RUBY_ALIGNOF(type), #type) -#ifdef STRICT_ALIGNMENT - #if defined(HAVE_TRUE_LONG_LONG) && SIZEOF_LONG_LONG > SIZEOF_VALUE - #define ALIGNMENT_SIZE SIZEOF_LONG_LONG - #else - #define ALIGNMENT_SIZE SIZEOF_VALUE - #endif - #define PADDING_SIZE_MAX ((size_t)((ALIGNMENT_SIZE) - 1)) - #define ALIGNMENT_SIZE_MASK PADDING_SIZE_MAX - /* Note: ALIGNMENT_SIZE == (2 ** N) is expected. */ -#else - #define PADDING_SIZE_MAX 0 -#endif /* STRICT_ALIGNMENT */ +static inline size_t +alignment_size_assert(size_t align, const char *type) +{ + RUBY_ASSERT((align & (align - 1)) == 0, + "ALIGNMENT_SIZE_OF(%s):%zd == (2 ** N) is expected", type, align); + return align; +} -#ifdef STRICT_ALIGNMENT /* calculate padding size for aligned memory access */ -static size_t -calc_padding(void *ptr, size_t size) +static inline size_t +calc_padding(void *ptr, size_t align) { size_t mis; size_t padding = 0; - mis = (size_t)ptr & ALIGNMENT_SIZE_MASK; + mis = (size_t)ptr & (align - 1); if (mis > 0) { - padding = ALIGNMENT_SIZE - mis; + padding = align - mis; } -/* - * On 32-bit sparc or equivalents, when a single VALUE is requested - * and padding == sizeof(VALUE), it is clear that no padding is needed. - */ -#if ALIGNMENT_SIZE > SIZEOF_VALUE - if (size == sizeof(VALUE) && padding == sizeof(VALUE)) { - padding = 0; - } -#endif return padding; } -#endif /* STRICT_ALIGNMENT */ static void * -compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t size) +compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t size, size_t align) { void *ptr = 0; struct iseq_compile_data_storage *storage = *arena; -#ifdef STRICT_ALIGNMENT - size_t padding = calc_padding((void *)&storage->buff[storage->pos], size); -#else - const size_t padding = 0; /* expected to be optimized by compiler */ -#endif /* STRICT_ALIGNMENT */ + size_t padding = calc_padding((void *)&storage->buff[storage->pos], align); if (size >= INT_MAX - padding) rb_memerror(); if (storage->pos + size + padding > storage->size) { @@ -1113,14 +1082,10 @@ compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t s storage->next = 0; storage->pos = 0; storage->size = alloc_size; -#ifdef STRICT_ALIGNMENT - padding = calc_padding((void *)&storage->buff[storage->pos], size); -#endif /* STRICT_ALIGNMENT */ + padding = calc_padding((void *)&storage->buff[storage->pos], align); } -#ifdef STRICT_ALIGNMENT storage->pos += (int)padding; -#endif /* STRICT_ALIGNMENT */ ptr = (void *)&storage->buff[storage->pos]; storage->pos += (int)size; @@ -1128,51 +1093,60 @@ compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t s } static void * -compile_data_alloc(rb_iseq_t *iseq, size_t size) +compile_data_alloc(rb_iseq_t *iseq, size_t size, size_t align) { struct iseq_compile_data_storage ** arena = &ISEQ_COMPILE_DATA(iseq)->node.storage_current; - return compile_data_alloc_with_arena(arena, size); + return compile_data_alloc_with_arena(arena, size, align); } +#define compile_data_alloc_type(iseq, type) \ + (type *)compile_data_alloc(iseq, sizeof(type), ALIGNMENT_SIZE_OF(type)) + static inline void * -compile_data_alloc2(rb_iseq_t *iseq, size_t x, size_t y) +compile_data_alloc2(rb_iseq_t *iseq, size_t elsize, size_t num, size_t align) { - size_t size = rb_size_mul_or_raise(x, y, rb_eRuntimeError); - return compile_data_alloc(iseq, size); + size_t size = rb_size_mul_or_raise(elsize, num, rb_eRuntimeError); + return compile_data_alloc(iseq, size, align); } +#define compile_data_alloc2_type(iseq, type, num) \ + (type *)compile_data_alloc2(iseq, sizeof(type), num, ALIGNMENT_SIZE_OF(type)) + static inline void * -compile_data_calloc2(rb_iseq_t *iseq, size_t x, size_t y) +compile_data_calloc2(rb_iseq_t *iseq, size_t elsize, size_t num, size_t align) { - size_t size = rb_size_mul_or_raise(x, y, rb_eRuntimeError); - void *p = compile_data_alloc(iseq, size); + size_t size = rb_size_mul_or_raise(elsize, num, rb_eRuntimeError); + void *p = compile_data_alloc(iseq, size, align); memset(p, 0, size); return p; } +#define compile_data_calloc2_type(iseq, type, num) \ + (type *)compile_data_calloc2(iseq, sizeof(type), num, ALIGNMENT_SIZE_OF(type)) + static INSN * compile_data_alloc_insn(rb_iseq_t *iseq) { struct iseq_compile_data_storage ** arena = &ISEQ_COMPILE_DATA(iseq)->insn.storage_current; - return (INSN *)compile_data_alloc_with_arena(arena, sizeof(INSN)); + return (INSN *)compile_data_alloc_with_arena(arena, sizeof(INSN), ALIGNMENT_SIZE_OF(INSN)); } static LABEL * compile_data_alloc_label(rb_iseq_t *iseq) { - return (LABEL *)compile_data_alloc(iseq, sizeof(LABEL)); + return compile_data_alloc_type(iseq, LABEL); } static ADJUST * compile_data_alloc_adjust(rb_iseq_t *iseq) { - return (ADJUST *)compile_data_alloc(iseq, sizeof(ADJUST)); + return compile_data_alloc_type(iseq, ADJUST); } static TRACE * compile_data_alloc_trace(rb_iseq_t *iseq) { - return (TRACE *)compile_data_alloc(iseq, sizeof(TRACE)); + return compile_data_alloc_type(iseq, TRACE); } /* @@ -1433,7 +1407,7 @@ new_insn_body(rb_iseq_t *iseq, int line_no, int node_id, enum ruby_vminsn_type i if (argc > 0) { int i; va_start(argv, argc); - operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + operands = compile_data_alloc2_type(iseq, VALUE, argc); for (i = 0; i < argc; i++) { VALUE v = va_arg(argv, VALUE); operands[i] = v; @@ -1451,7 +1425,7 @@ insn_replace_with_operands(rb_iseq_t *iseq, INSN *iobj, enum ruby_vminsn_type in if (argc > 0) { int i; va_start(argv, argc); - operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + operands = compile_data_alloc2_type(iseq, VALUE, argc); for (i = 0; i < argc; i++) { VALUE v = va_arg(argv, VALUE); operands[i] = v; @@ -1491,7 +1465,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, unsigned int flag, struct rb_cal static INSN * new_insn_send(rb_iseq_t *iseq, int line_no, int node_id, ID id, VALUE argc, const rb_iseq_t *blockiseq, VALUE flag, struct rb_callinfo_kwarg *keywords) { - VALUE *operands = compile_data_calloc2(iseq, sizeof(VALUE), 2); + VALUE *operands = compile_data_calloc2_type(iseq, VALUE, 2); VALUE ci = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), FIX2INT(flag), keywords, blockiseq != NULL); operands[0] = ci; operands[1] = (VALUE)blockiseq; @@ -4552,7 +4526,7 @@ new_unified_insn(rb_iseq_t *iseq, } if (argc > 0) { - ptr = operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + ptr = operands = compile_data_alloc2_type(iseq, VALUE, argc); } /* copy operands */ @@ -6483,7 +6457,7 @@ add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange, LABEL *lstart, LABEL *lend) { struct ensure_range *ne = - compile_data_alloc(iseq, sizeof(struct ensure_range)); + compile_data_alloc_type(iseq, struct ensure_range); while (erange->next != 0) { erange = erange->next; @@ -10765,8 +10739,10 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa ADD_INSN1(anchor, node, newarray, INT2FIX(RNODE_LIST(node)->as.nd_alen)); } else if (nd_type(node) == NODE_HASH) { - int len = (int)RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; - ADD_INSN1(anchor, node, newhash, INT2FIX(len)); + long len = RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; + RBIMPL_ASSERT_OR_ASSUME(len >= 0); + RBIMPL_ASSERT_OR_ASSUME(RB_POSFIXABLE(len)); + ADD_INSN1(anchor, node, newhash, LONG2FIX(len)); } *value_p = Qundef; *shareable_literal_p = 0; @@ -10780,8 +10756,10 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa ADD_INSN1(anchor, node, newarray, INT2FIX(RNODE_LIST(node)->as.nd_alen)); } else if (nd_type(node) == NODE_HASH) { - int len = (int)RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; - ADD_INSN1(anchor, node, newhash, INT2FIX(len)); + long len = RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; + RBIMPL_ASSERT_OR_ASSUME(len >= 0); + RBIMPL_ASSERT_OR_ASSUME(RB_POSFIXABLE(len)); + ADD_INSN1(anchor, node, newhash, LONG2FIX(len)); } CHECK(compile_make_shareable_node(iseq, ret, anchor, node, false)); *value_p = Qundef; @@ -12077,7 +12055,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, } if (argc > 0) { - argv = compile_data_calloc2(iseq, sizeof(VALUE), argc); + argv = compile_data_calloc2_type(iseq, VALUE, argc); // add element before operand setup to make GC root ADD_ELEM(anchor, @@ -12304,23 +12282,18 @@ rb_iseq_mark_and_move_insn_storage(struct iseq_compile_data_storage *storage) { INSN *iobj = 0; size_t size = sizeof(INSN); + size_t align = ALIGNMENT_SIZE_OF(INSN); unsigned int pos = 0; while (storage) { -#ifdef STRICT_ALIGNMENT - size_t padding = calc_padding((void *)&storage->buff[pos], size); -#else - const size_t padding = 0; /* expected to be optimized by compiler */ -#endif /* STRICT_ALIGNMENT */ + size_t padding = calc_padding((void *)&storage->buff[pos], align); size_t offset = pos + size + padding; if (offset > storage->size || offset > storage->pos) { pos = 0; storage = storage->next; } else { -#ifdef STRICT_ALIGNMENT pos += (int)padding; -#endif /* STRICT_ALIGNMENT */ iobj = (INSN *)&storage->buff[pos]; diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 4832916ce6ea2f..21d97b81efa611 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -48,6 +48,8 @@ struct objspace { unsigned int fork_hook_vm_lock_lev; }; +#define OBJ_FREE_BUF_CAPACITY 128 + struct MMTk_ractor_cache { struct ccan_list_node list_node; @@ -55,6 +57,11 @@ struct MMTk_ractor_cache { bool gc_mutator_p; MMTk_BumpPointer *bump_pointer; + + MMTk_ObjectReference obj_free_parallel_buf[OBJ_FREE_BUF_CAPACITY]; + size_t obj_free_parallel_count; + MMTk_ObjectReference obj_free_non_parallel_buf[OBJ_FREE_BUF_CAPACITY]; + size_t obj_free_non_parallel_count; }; struct MMTk_final_job { @@ -143,6 +150,8 @@ rb_mmtk_resume_mutators(void) } } +static void mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache); + static void rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) { @@ -173,6 +182,11 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) rb_gc_vm_barrier(); + struct MMTk_ractor_cache *rc; + ccan_list_for_each(&objspace->ractor_caches, rc, list_node) { + mmtk_flush_obj_free_buffer(rc); + } + objspace->world_stopped = true; pthread_cond_broadcast(&objspace->cond_world_stopped); @@ -584,7 +598,7 @@ rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor) } objspace->live_ractor_cache_count++; - struct MMTk_ractor_cache *cache = malloc(sizeof(struct MMTk_ractor_cache)); + struct MMTk_ractor_cache *cache = calloc(1, sizeof(struct MMTk_ractor_cache)); ccan_list_add(&objspace->ractor_caches, &cache->list_node); cache->mutator = mmtk_bind_mutator(cache); @@ -601,6 +615,8 @@ rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache_ptr) ccan_list_del(&cache->list_node); + mmtk_flush_obj_free_buffer(cache); + if (ruby_free_at_exit_p()) { MMTK_ASSERT(objspace->live_ractor_cache_count > 0); } @@ -801,6 +817,42 @@ obj_can_parallel_free_p(VALUE obj) } } +static void +mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache) +{ + if (cache->obj_free_parallel_count > 0) { + mmtk_add_obj_free_candidates(cache->obj_free_parallel_buf, + cache->obj_free_parallel_count, true); + cache->obj_free_parallel_count = 0; + } + if (cache->obj_free_non_parallel_count > 0) { + mmtk_add_obj_free_candidates(cache->obj_free_non_parallel_buf, + cache->obj_free_non_parallel_count, false); + cache->obj_free_non_parallel_count = 0; + } +} + +static inline void +mmtk_buffer_obj_free_candidate(struct MMTk_ractor_cache *cache, VALUE obj) +{ + if (obj_can_parallel_free_p(obj)) { + cache->obj_free_parallel_buf[cache->obj_free_parallel_count++] = (MMTk_ObjectReference)obj; + if (cache->obj_free_parallel_count >= OBJ_FREE_BUF_CAPACITY) { + mmtk_add_obj_free_candidates(cache->obj_free_parallel_buf, + cache->obj_free_parallel_count, true); + cache->obj_free_parallel_count = 0; + } + } + else { + cache->obj_free_non_parallel_buf[cache->obj_free_non_parallel_count++] = (MMTk_ObjectReference)obj; + if (cache->obj_free_non_parallel_count >= OBJ_FREE_BUF_CAPACITY) { + mmtk_add_obj_free_candidates(cache->obj_free_non_parallel_buf, + cache->obj_free_non_parallel_count, false); + cache->obj_free_non_parallel_count = 0; + } + } +} + VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { @@ -837,7 +889,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size, MMTK_ALLOCATION_SEMANTICS_DEFAULT); // TODO: only add when object needs obj_free to be called - mmtk_add_obj_free_candidate(alloc_obj, obj_can_parallel_free_p((VALUE)alloc_obj)); + mmtk_buffer_obj_free_candidate(ractor_cache, (VALUE)alloc_obj); objspace->total_allocated_objects++; @@ -1277,6 +1329,11 @@ rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr) unsigned int lev = RB_GC_VM_LOCK(); { + struct MMTk_ractor_cache *rc; + ccan_list_for_each(&objspace->ractor_caches, rc, list_node) { + mmtk_flush_obj_free_buffer(rc); + } + struct MMTk_RawVecOfObjRef registered_candidates = mmtk_get_all_obj_free_candidates(); for (size_t i = 0; i < registered_candidates.len; i++) { VALUE obj = (VALUE)registered_candidates.ptr[i]; diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index ffbad1a025cce0..20d268419ddea0 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -123,7 +123,9 @@ void mmtk_post_alloc(MMTk_Mutator *mutator, size_t bytes, MMTk_AllocationSemantics semantics); -void mmtk_add_obj_free_candidate(MMTk_ObjectReference object, bool can_parallel_free); +void mmtk_add_obj_free_candidates(const MMTk_ObjectReference *objects, + size_t count, + bool can_parallel_free); void mmtk_declare_weak_references(MMTk_ObjectReference object); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 5eac068672b549..b9797f6fe2df6f 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -297,12 +297,16 @@ pub unsafe extern "C" fn mmtk_post_alloc( memory_manager::post_alloc::(unsafe { &mut *mutator }, refer, bytes, semantics) } -// TODO: Replace with buffered mmtk_add_obj_free_candidates #[no_mangle] -pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference, can_parallel_free: bool) { +pub unsafe extern "C" fn mmtk_add_obj_free_candidates( + objects: *const ObjectReference, + count: usize, + can_parallel_free: bool, +) { + let objects = unsafe { std::slice::from_raw_parts(objects, count) }; binding() .weak_proc - .add_obj_free_candidate(object, can_parallel_free) + .add_obj_free_candidates_batch(objects, can_parallel_free) } // =============== Weak references =============== diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index f103822b737272..d38dbe04a4f15f 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -1,5 +1,3 @@ -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; use std::sync::Mutex; use mmtk::scheduler::GCWork; @@ -15,7 +13,6 @@ use crate::Ruby; pub struct WeakProcessor { non_parallel_obj_free_candidates: Mutex>, parallel_obj_free_candidates: Vec>>, - parallel_obj_free_candidates_counter: AtomicUsize, /// Objects that needs `obj_free` called when dying. /// If it is a bottleneck, replace it with a lock-free data structure, @@ -34,7 +31,6 @@ impl WeakProcessor { Self { non_parallel_obj_free_candidates: Mutex::new(Vec::new()), parallel_obj_free_candidates: vec![Mutex::new(Vec::new())], - parallel_obj_free_candidates_counter: AtomicUsize::new(0), weak_references: Mutex::new(Vec::new()), } } @@ -48,27 +44,34 @@ impl WeakProcessor { } } - /// Add an object as a candidate for `obj_free`. + /// Add a batch of objects as candidates for `obj_free`. /// - /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidate(&self, object: ObjectReference, can_parallel_free: bool) { + /// Amortizes mutex acquisition over the entire batch. Called when a + /// mutator's local buffer is flushed (buffer full or stop-the-world). + pub fn add_obj_free_candidates_batch( + &self, + objects: &[ObjectReference], + can_parallel_free: bool, + ) { + if objects.is_empty() { + return; + } + if can_parallel_free { - // Newly allocated objects are placed in parallel_obj_free_candidates using - // round-robin. This may not be ideal for load balancing. - let idx = self - .parallel_obj_free_candidates_counter - .fetch_add(1, Ordering::Relaxed) - % self.parallel_obj_free_candidates.len(); - - self.parallel_obj_free_candidates[idx] - .lock() - .unwrap() - .push(object); + let num_buckets = self.parallel_obj_free_candidates.len(); + for idx in 0..num_buckets { + let mut bucket = self.parallel_obj_free_candidates[idx].lock().unwrap(); + for (i, &obj) in objects.iter().enumerate() { + if i % num_buckets == idx { + bucket.push(obj); + } + } + } } else { self.non_parallel_obj_free_candidates .lock() .unwrap() - .push(object); + .extend_from_slice(objects); } } diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index 994b415e9c81bc..4f2414e33d3265 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -50,7 +50,7 @@ def dependency_specs(gem_names) def unmarshalled_dep_gems(gem_names) gem_list = [] - gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| + gem_names.each_slice(api_request_size) do |names| marshalled_deps = downloader.fetch(dependency_api_uri(names)).body gem_list.concat(Bundler.safe_load_marshal(marshalled_deps)) end @@ -74,6 +74,12 @@ def dependency_api_uri(gem_names = []) uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any? uri end + + private + + def api_request_size + Bundler.settings[:api_request_size]&.to_i || Source::Rubygems::API_REQUEST_SIZE + end end end end diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index f5975c70230cdf..89771d343340b9 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ADD" "1" "February 2026" "" +.TH "BUNDLE\-ADD" "1" "March 2026" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 314d4ef6130892..2a78f530ccefd0 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-BINSTUBS" "1" "February 2026" "" +.TH "BUNDLE\-BINSTUBS" "1" "March 2026" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 0d20662b7b69dc..a2b0fc6dff194e 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CACHE" "1" "February 2026" "" +.TH "BUNDLE\-CACHE" "1" "March 2026" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index 154fb06818f474..d03b4dc6bdd2ea 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CHECK" "1" "February 2026" "" +.TH "BUNDLE\-CHECK" "1" "March 2026" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 47fb8f92aa2c75..13bd586f486551 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CLEAN" "1" "February 2026" "" +.TH "BUNDLE\-CLEAN" "1" "March 2026" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 2a342298aeba45..000fe664da6c3f 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONFIG" "1" "February 2026" "" +.TH "BUNDLE\-CONFIG" "1" "March 2026" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" @@ -70,6 +70,9 @@ 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 +\fBapi_request_size\fR (\fBBUNDLE_API_REQUEST_SIZE\fR) +Configure how many dependencies to fetch when resolving the specifications\. This configuration is only used when fetchig specifications from RubyGems servers that didn't implement the Compact Index API\. Defaults to 100\. +.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 eb5a3b045b3e57..a8670a36709d49 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -106,6 +106,11 @@ 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). +* `api_request_size` (`BUNDLE_API_REQUEST_SIZE`): + Configure how many dependencies to fetch when resolving the specifications. + This configuration is only used when fetchig specifications from RubyGems + servers that didn't implement the Compact Index API. + Defaults to 100. * `auto_install` (`BUNDLE_AUTO_INSTALL`): Automatically run `bundle install` when gems are missing. * `bin` (`BUNDLE_BIN`): diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 3e3db532e43478..4594cb74be4470 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONSOLE" "1" "February 2026" "" +.TH "BUNDLE\-CONSOLE" "1" "March 2026" "" .SH "NAME" \fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index f5b2be400722bd..e94ebbd8342ba1 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-DOCTOR" "1" "February 2026" "" +.TH "BUNDLE\-DOCTOR" "1" "March 2026" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1 index a6d11a3d3c087f..c57bec014eff16 100644 --- a/lib/bundler/man/bundle-env.1 +++ b/lib/bundler/man/bundle-env.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ENV" "1" "February 2026" "" +.TH "BUNDLE\-ENV" "1" "March 2026" "" .SH "NAME" \fBbundle\-env\fR \- Print information about the environment Bundler is running under .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 62c9245b003ac3..36fed764aca840 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-EXEC" "1" "February 2026" "" +.TH "BUNDLE\-EXEC" "1" "March 2026" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1 index a09a4ed97cfc17..96f182e05f9fbc 100644 --- a/lib/bundler/man/bundle-fund.1 +++ b/lib/bundler/man/bundle-fund.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-FUND" "1" "February 2026" "" +.TH "BUNDLE\-FUND" "1" "March 2026" "" .SH "NAME" \fBbundle\-fund\fR \- Lists information about gems seeking funding assistance .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 33cacbe5323bff..68c77a03ce187a 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-GEM" "1" "February 2026" "" +.TH "BUNDLE\-GEM" "1" "March 2026" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index 3a97e56a1fcbbd..17e1d4a90449cb 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-HELP" "1" "February 2026" "" +.TH "BUNDLE\-HELP" "1" "March 2026" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 39ff74a7d97580..50f5e36f18c18d 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INFO" "1" "February 2026" "" +.TH "BUNDLE\-INFO" "1" "March 2026" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index a053ac4c4c35cc..14fd0a73cb4cbf 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INIT" "1" "February 2026" "" +.TH "BUNDLE\-INIT" "1" "March 2026" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 21997fb290917b..1d52335644f46a 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INSTALL" "1" "February 2026" "" +.TH "BUNDLE\-INSTALL" "1" "March 2026" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1 index f5033d6e076ca5..7e2fcaf0fa5e6e 100644 --- a/lib/bundler/man/bundle-issue.1 +++ b/lib/bundler/man/bundle-issue.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ISSUE" "1" "February 2026" "" +.TH "BUNDLE\-ISSUE" "1" "March 2026" "" .SH "NAME" \fBbundle\-issue\fR \- Get help reporting Bundler issues .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1 index 39dcf0bd38b943..9170fecd73518f 100644 --- a/lib/bundler/man/bundle-licenses.1 +++ b/lib/bundler/man/bundle-licenses.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LICENSES" "1" "February 2026" "" +.TH "BUNDLE\-LICENSES" "1" "March 2026" "" .SH "NAME" \fBbundle\-licenses\fR \- Print the license of all gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index 34d25ae12c2648..165a99bb58a24a 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LIST" "1" "February 2026" "" +.TH "BUNDLE\-LIST" "1" "March 2026" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index eb5c8732cc0115..426e80ec77e1bc 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LOCK" "1" "February 2026" "" +.TH "BUNDLE\-LOCK" "1" "March 2026" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index 1d9463d12b1b3e..caeb223844391a 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OPEN" "1" "February 2026" "" +.TH "BUNDLE\-OPEN" "1" "March 2026" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index b1ddd0d31530a6..744be279c90128 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OUTDATED" "1" "February 2026" "" +.TH "BUNDLE\-OUTDATED" "1" "March 2026" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index cb53cd192dcf43..b859ead72f8c5f 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLATFORM" "1" "February 2026" "" +.TH "BUNDLE\-PLATFORM" "1" "March 2026" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 5c019b305d80b7..450d3e0862ea65 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLUGIN" "1" "February 2026" "" +.TH "BUNDLE\-PLUGIN" "1" "March 2026" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 0973d63e4edd49..f8722bff3eace1 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PRISTINE" "1" "February 2026" "" +.TH "BUNDLE\-PRISTINE" "1" "March 2026" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 3398d6cd330c86..df00b8dbdcf0f1 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-REMOVE" "1" "February 2026" "" +.TH "BUNDLE\-REMOVE" "1" "March 2026" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 05ff3205766f72..4f6109a4a6c081 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-SHOW" "1" "February 2026" "" +.TH "BUNDLE\-SHOW" "1" "March 2026" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index db078d74fc8473..9e6076a2c2bf06 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-UPDATE" "1" "February 2026" "" +.TH "BUNDLE\-UPDATE" "1" "March 2026" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 8f9088451bb076..bc0cf692b3da65 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VERSION" "1" "February 2026" "" +.TH "BUNDLE\-VERSION" "1" "March 2026" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 8613924602a045..c69f0e26bc6f9f 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE" "1" "February 2026" "" +.TH "BUNDLE" "1" "March 2026" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 4fefd12a58eec2..2818b122103045 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "GEMFILE" "5" "February 2026" "" +.TH "GEMFILE" "5" "March 2026" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 3c68e5181224db..faca6bea53069f 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -113,7 +113,7 @@ def gemfile_install(gemfile = nil, &inline) return if definition.dependencies.empty? - plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p } + plugins = definition.dependencies.map(&:name) installed_specs = Installer.new.install_definition(definition) save_plugins plugins, installed_specs, builder.inferred_plugins @@ -258,7 +258,7 @@ def save_plugins(plugins, specs, optional_plugins = []) # It's possible that the `plugin` found in the Gemfile don't appear in the specs. For instance when # calling `BUNDLE_WITHOUT=default bundle install`, the plugins will not get installed. next if spec.nil? - next if index.installed?(name) + next if index.up_to_date?(spec) save_plugin(name, spec, optional_plugins.include?(name)) end diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb index 0682d37772b2ef..94683a5e544f49 100644 --- a/lib/bundler/plugin/index.rb +++ b/lib/bundler/plugin/index.rb @@ -119,6 +119,12 @@ def installed?(name) @plugin_paths[name] end + def up_to_date?(spec) + path = installed?(spec.name) + + path == spec.full_gem_path + end + def installed_plugins @plugin_paths.keys end diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 9116785231ea10..e817ee57042d10 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -2,6 +2,7 @@ require_relative "vendored_net_http" require_relative "user_interaction" +require_relative "uri_formatter" class Gem::Request extend Gem::UserInteraction diff --git a/prism/prism.c b/prism/prism.c index ebd86b01fe089b..a131f3791277b2 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -21619,26 +21619,6 @@ pm_call_node_command_p(const pm_call_node_t *node) { ); } -/** - * Determine if a given write node has a command call as its right-hand side. We - * need this because command calls as the values of writes cannot be extended by - * infix operators. - */ -static inline bool -pm_write_node_command_p(const pm_node_t *node) { - pm_node_t *value; - switch (PM_NODE_TYPE(node)) { - case PM_CLASS_VARIABLE_WRITE_NODE: value = ((pm_class_variable_write_node_t *) node)->value; break; - case PM_CONSTANT_PATH_WRITE_NODE: value = ((pm_constant_path_write_node_t *) node)->value; break; - case PM_CONSTANT_WRITE_NODE: value = ((pm_constant_write_node_t *) node)->value; break; - case PM_GLOBAL_VARIABLE_WRITE_NODE: value = ((pm_global_variable_write_node_t *) node)->value; break; - case PM_INSTANCE_VARIABLE_WRITE_NODE: value = ((pm_instance_variable_write_node_t *) node)->value; break; - case PM_LOCAL_VARIABLE_WRITE_NODE: value = ((pm_local_variable_write_node_t *) node)->value; break; - default: return false; - } - return PM_NODE_TYPE_P(value, PM_CALL_NODE) && pm_call_node_command_p((pm_call_node_t *) value); -} - /** * Parse an expression at the given point of the parser using the given binding * power to parse subsequent chains. If this function finds a syntax error, it @@ -21723,13 +21703,9 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc case PM_INSTANCE_VARIABLE_WRITE_NODE: case PM_LOCAL_VARIABLE_WRITE_NODE: // These expressions are statements, by virtue of the right-hand - // side of their write being an implicit array or a command call. - // This mirrors parse.y's behavior where `lhs = command_call` - // reduces to stmt (not expr), preventing and/or from following. - if (pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { - if (PM_NODE_FLAG_P(node, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY) || pm_write_node_command_p(node)) { - return node; - } + // side of their write being an implicit array. + if (PM_NODE_FLAG_P(node, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY) && pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { + return node; } break; case PM_CALL_NODE: diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb index 61e32acfd969ee..501bc269a55610 100644 --- a/spec/bundler/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/bundler/fetcher/dependency_spec.rb @@ -222,6 +222,18 @@ expect(Bundler).to receive(:safe_load_marshal).with(fetch_response.body).and_return([unmarshalled_gems]) expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems]) end + + it "should fetch as many dependencies as specified" do + allow(subject).to receive(:dependency_api_uri).with([%w[foo bar]]).and_return(dep_api_uri) + allow(subject).to receive(:dependency_api_uri).with([%w[bundler rubocop]]).and_return(dep_api_uri) + + expect(downloader).to receive(:fetch).twice.with(dep_api_uri).and_return(fetch_response) + expect(Bundler).to receive(:safe_load_marshal).twice.with(fetch_response.body).and_return([unmarshalled_gems]) + + Bundler.settings.temporary(api_request_size: 1) do + expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems, unmarshalled_gems]) + end + end end describe "#get_formatted_specs_and_deps" do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index e416772a367240..b379594c6f9a90 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -65,8 +65,8 @@ end it "passes the name and options to installer" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(installer).to receive(:install).with(["new-plugin"], opts) do { "new-plugin" => spec } end.once @@ -75,8 +75,8 @@ end it "validates the installed plugin" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(subject). to receive(:validate_plugin!).with(lib_path("new-plugin")).once @@ -84,8 +84,8 @@ end it "registers the plugin with index" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(index).to receive(:register_plugin). with("new-plugin", lib_path("new-plugin").to_s, [lib_path("new-plugin").join("lib").to_s], []).once subject.install ["new-plugin"], opts @@ -102,7 +102,7 @@ end.once allow(subject).to receive(:validate_plugin!).twice - allow(index).to receive(:installed?).twice + allow(index).to receive(:up_to_date?).twice allow(index).to receive(:register_plugin).twice subject.install ["new-plugin", "another-plugin"], opts end @@ -138,7 +138,7 @@ end before do - allow(index).to receive(:installed?) { nil } + allow(index).to receive(:up_to_date?) { nil } allow(definition).to receive(:dependencies) { [Bundler::Dependency.new("new-plugin", ">=0"), Bundler::Dependency.new("another-plugin", ">=0")] } allow(installer).to receive(:install_definition) { plugin_specs } end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 36672f0085e617..dc3a12913eefe4 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -265,6 +265,31 @@ def exec(command, args) plugin_should_be_installed("foo") end + it "overrides the index with the new plugin version" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "1.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + update_repo2 do + build_plugin "foo", "2.0.0" + end + + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "2.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + expected = local_plugin_gem("foo-2.0.0", "lib").to_s + expect(Bundler::Plugin.index.load_paths("foo")).to eq([expected]) + end + it "respects bundler groups" do gemfile <<-G source 'https://gem.repo2' diff --git a/test/prism/errors/command_call_in.txt b/test/prism/errors/command_call_in.txt index e9e8e82d3a64ac..2fdcf0973897b7 100644 --- a/test/prism/errors/command_call_in.txt +++ b/test/prism/errors/command_call_in.txt @@ -2,6 +2,4 @@ foo 1 in a ^~ unexpected 'in', expecting end-of-input ^~ unexpected 'in', ignoring it a = foo 2 in b - ^~ unexpected 'in', expecting end-of-input - ^~ unexpected 'in', ignoring it diff --git a/test/prism/errors/write_command.txt b/test/prism/errors/write_command.txt deleted file mode 100644 index 5024f8452a276f..00000000000000 --- a/test/prism/errors/write_command.txt +++ /dev/null @@ -1,4 +0,0 @@ -a = b c and 1 - ^~~ unexpected 'and', expecting end-of-input - ^~~ unexpected 'and', ignoring it - diff --git a/test/prism/fixtures/write_command_operator.txt b/test/prism/fixtures/write_command_operator.txt new file mode 100644 index 00000000000000..d719d24f873027 --- /dev/null +++ b/test/prism/fixtures/write_command_operator.txt @@ -0,0 +1,3 @@ +foo = 123 | '456' or return + +foo = 123 | '456' in BAR diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index d17e300bceb2f1..dd526544af3aad 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -281,6 +281,26 @@ def test_values_at assert_equal(["foo", "foo"], ENV.values_at("test", "test")) end + def test_fetch_values + ENV["test"] = "foo" + ENV["test2"] = "bar" + assert_equal(["foo", "bar"], ENV.fetch_values("test", "test2")) + assert_equal(["foo", "foo"], ENV.fetch_values("test", "test")) + assert_equal([], ENV.fetch_values) + + ENV.delete("test2") + assert_raise(KeyError) { ENV.fetch_values("test", "test2") } + + assert_equal(["foo", "default"], ENV.fetch_values("test", "test2") { "default" }) + assert_equal(["foo", "TEST2"], ENV.fetch_values("test", "test2") { |k| k.upcase }) + + e = assert_raise(KeyError) { ENV.fetch_values("test2") } + assert_same(ENV, e.receiver) + assert_equal("test2", e.key) + + assert_invalid_env {|v| ENV.fetch_values(v)} + end + def test_select ENV["test"] = "foo" h = ENV.select {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }