diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 9c7c1217fbf8bd..b8a8be82c18530 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -438,7 +438,7 @@ def cache map aliases_for("cache") desc "exec [OPTIONS]", "Run the command in context of the bundle" - method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is deprecated" + method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)." method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile" long_desc <<-D Exec runs a command, providing it access to the gems in the bundle. While using diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6cae8964d8145a..3c8c13b1303171 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -540,7 +540,18 @@ def add_checksums setup_domain!(add_checksums: true) - specs # force materialization to real specifications, so that checksums are fetched + # force materialization to real specifications, so that checksums are fetched + specs.each do |spec| + next unless spec.source.is_a?(Bundler::Source::Rubygems) + # Checksum was fetched from the compact index API. + next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec) + # The gem isn't installed, can't compute the checksum. + next unless spec.loaded_from + + package = Gem::Package.new(spec.source.cached_built_in_gem(spec)) + checksum = Checksum.from_gem_package(package) + spec.source.checksum_store.register(spec, checksum) + end end private diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 8ec62fc1c9e638..dea8abedba393c 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -2,29 +2,6 @@ module Bundler class FeatureFlag - def self.settings_flag(flag, &default) - unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s) - raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key" - end - - settings_method("#{flag}?", flag, &default) - end - private_class_method :settings_flag - - def self.settings_option(key, &default) - settings_method(key, key, &default) - end - private_class_method :settings_option - - def self.settings_method(name, key, &default) - define_method(name) do - value = Bundler.settings[key] - value = instance_eval(&default) if value.nil? - value - end - end - private_class_method :settings_method - (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } def removed_major?(target_major_version) diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 93788a850f463d..24c84889b5bf39 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" -\fBbundle exec\fR [\-\-keep\-file\-descriptors] [\-\-gemfile=GEMFILE] \fIcommand\fR +\fBbundle exec\fR [\-\-gemfile=GEMFILE] \fIcommand\fR .SH "DESCRIPTION" This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\. .P @@ -13,9 +13,6 @@ Essentially, if you would normally have run something like \fBrspec spec/my_spec Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\. .SH "OPTIONS" .TP -\fB\-\-keep\-file\-descriptors\fR -Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\. -.TP \fB\-\-gemfile=GEMFILE\fR Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\. .SH "BUNDLE INSTALL \-\-BINSTUBS" diff --git a/lib/bundler/man/bundle-exec.1.ronn b/lib/bundler/man/bundle-exec.1.ronn index 3d3f0eed7b89da..e51a66a0840c54 100644 --- a/lib/bundler/man/bundle-exec.1.ronn +++ b/lib/bundler/man/bundle-exec.1.ronn @@ -3,7 +3,7 @@ bundle-exec(1) -- Execute a command in the context of the bundle ## SYNOPSIS -`bundle exec` [--keep-file-descriptors] [--gemfile=GEMFILE] +`bundle exec` [--gemfile=GEMFILE] ## DESCRIPTION @@ -20,10 +20,6 @@ available on your shell's `$PATH`. ## OPTIONS -* `--keep-file-descriptors`: - Passes all file descriptors to the new processes. Default is true from - bundler version 2.2.26. Setting it to false is now deprecated. - * `--gemfile=GEMFILE`: Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)]. diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 283719bedf53b7..1e90f01ce7f8c9 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -207,6 +207,17 @@ expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end + it "prints an error when using legacy windows rubies" do + gemfile <<-D + source "https://gem.repo1" + gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] + D + + bundle "cache --all-platforms", raise_on_error: false + expect(err).to include("removed") + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).not_to exist + end + it "does not attempt to install gems in without groups" do build_repo4 do build_gem "uninstallable", "2.0" do |s| diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index f2cd8868935d7b..6b678d0aa5451b 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -220,7 +220,7 @@ def should_not_have_gems(*gems) expect(bundled_app("symlink-path/#{Bundler.ruby_scope}/bundler/gems/foo-#{revision[0..11]}")).to exist end - it "removes old git gems" do + it "removes old git gems on bundle update" do build_git "foo-bar", path: lib_path("foo-bar") revision = revision_for(lib_path("foo-bar")) @@ -383,7 +383,7 @@ def should_not_have_gems(*gems) expect(out).to include("myrack (1.0.0)").and include("thin (1.0)") end - it "automatically cleans when path has not been set", bundler: "5" do + it "does not clean on bundle update when path has not been set" do build_repo2 install_gemfile <<-G @@ -398,8 +398,43 @@ def should_not_have_gems(*gems) bundle "update", all: true - files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*")) - files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") } + files = Pathname.glob(default_bundle_path("*", "*")) + files.map! {|f| f.to_s.sub(default_bundle_path.to_s, "") } + expected_files = %W[ + /bin/bundle + /bin/bundler + /cache/bundler-#{Bundler::VERSION}.gem + /cache/foo-1.0.1.gem + /cache/foo-1.0.gem + /gems/bundler-#{Bundler::VERSION} + /gems/foo-1.0 + /gems/foo-1.0.1 + /specifications/bundler-#{Bundler::VERSION}.gemspec + /specifications/foo-1.0.1.gemspec + /specifications/foo-1.0.gemspec + ] + expected_files += ["/bin/bundle.bat", "/bin/bundler.bat"] if Gem.win_platform? + + expect(files.sort).to eq(expected_files.sort) + end + + it "will automatically clean on bundle update when path has not been set", bundler: "5" do + build_repo2 + + install_gemfile <<-G + source "https://gem.repo2" + + gem "foo" + G + + update_repo2 do + build_gem "foo", "1.0.1" + end + + bundle "update", all: true + + files = Pathname.glob(local_gem_path("*", "*")) + files.map! {|f| f.to_s.sub(local_gem_path.to_s, "") } expect(files.sort).to eq %w[ /cache/foo-1.0.1.gem /gems/foo-1.0.1 diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 65903b3e780024..8506005746a25b 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -327,21 +327,6 @@ end end - describe "doing bundle install foo" do - before do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - it "works" do - bundle "config set --local path vendor" - bundle "install" - expect(the_bundle).to include_gems "myrack 1.0" - end - 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/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index a7460ed695a456..36493e108a2cf4 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -2174,6 +2174,97 @@ L end + it "add checksums for gems installed on disk" do + build_repo4 do + build_gem "warning", "18.0.0" + end + + bundle "config lockfile_checksums false" + + simulate_platform "x86_64-linux" do + install_gemfile(<<-G, artifice: "endpoint") + source "https://gem.repo4" + + gem "warning" + G + + bundle "config --delete lockfile_checksums" + bundle("lock --add-checksums", artifice: "endpoint") + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "warning", "18.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + warning + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "doesn't add checksum for gems not installed on disk" do + lockfile(<<~L) + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile(<<~G) + source "https://gem.repo4" + + gem "warning" + G + + build_repo4 do + build_gem "warning", "18.0.0" + end + + FileUtils.rm_rf("#{gem_repo4}/gems") + + bundle("lock --add-checksums", artifice: "endpoint") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + CHECKSUMS + warning (18.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + context "when re-resolving to include prereleases" do before do build_repo4 do diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 216548cf27ccee..faf938383269ac 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -70,7 +70,7 @@ it "caches the git repo" do install_base_gemfile - expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes size: 1 + expect(Dir["#{default_cache_path}/git/foo-1.0-*"]).to have_attributes size: 1 end it "does not write to cache on bundler/setup" do diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index d701c4008dbf4c..1b04af76325795 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -605,6 +605,21 @@ end end + context "with a global path source" do + before do + build_lib "foo" + + install_gemfile <<-G, raise_on_error: false + path "#{lib_path("foo-1.0")}" + gem 'foo' + G + end + + it "shows an error" do + expect(err).to include("You can no longer specify a path source by itself") + end + end + context "when Bundler.setup is run in a ruby script" do before do create_file "gems.rb", "source 'https://gem.repo1'" diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index aad5600f569767..6efb3e1259e4e6 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -4,9 +4,9 @@ use std::mem::take; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary}; use crate::hir::SideExitReason; -use crate::options::{debug, get_option}; +use crate::options::{debug, get_option, TraceExits}; use crate::cruby::VALUE; -use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, CompileError}; +use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; use crate::state::rb_zjit_record_exit_stack; @@ -1644,8 +1644,22 @@ impl Assembler } } - if get_option!(trace_side_exits) { - asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8)); + if get_option!(trace_side_exits).is_some() { + // Get the corresponding `Counter` for the current `SideExitReason`. + let side_exit_counter = side_exit_counter(reason); + + // Only record the exit if `trace_side_exits` is defined and the counter is either the one specified + let should_record_exit = get_option!(trace_side_exits) + .map(|trace| match trace { + TraceExits::All => true, + TraceExits::Counter(counter) if counter == side_exit_counter => true, + _ => false, + }) + .unwrap_or(false); + + if should_record_exit { + asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8)); + } } asm_comment!(self, "exit to the interpreter"); diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 4ef998acede36c..b7b20e63c4484d 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -3,6 +3,7 @@ use std::{ffi::{CStr, CString}, ptr::null}; use std::os::raw::{c_char, c_int, c_uint}; use crate::cruby::*; +use crate::stats::Counter; use std::collections::HashSet; /// Default --zjit-num-profiles @@ -72,7 +73,7 @@ pub struct Options { pub dump_disasm: bool, /// Trace and write side exit source maps to /tmp for stackprof. - pub trace_side_exits: bool, + pub trace_side_exits: Option, /// Frequency of tracing side exits. pub trace_side_exits_sample_interval: usize, @@ -102,7 +103,7 @@ impl Default for Options { dump_hir_graphviz: None, dump_lir: false, dump_disasm: false, - trace_side_exits: false, + trace_side_exits: None, trace_side_exits_sample_interval: 0, perf: false, allowed_iseqs: None, @@ -125,12 +126,20 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), ("--zjit-log-compiled-iseqs=path", "Log compiled ISEQs to the file. The file will be truncated."), - ("--zjit-trace-exits", - "Record Ruby source location when side-exiting."), - ("--zjit-trace-exits-sample-rate", + ("--zjit-trace-exits[=counter]", + "Record source on side-exit. `Counter` picks specific counter."), + ("--zjit-trace-exits-sample-rate=num", "Frequency at which to record side exits. Must be `usize`.") ]; +#[derive(Copy, Clone, Debug)] +pub enum TraceExits { + // Trace all exits + All, + // Trace exits for a specific `Counter` + Counter(Counter), +} + #[derive(Clone, Copy, Debug)] pub enum DumpHIR { // Dump High-level IR without Snapshot @@ -249,13 +258,18 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> { options.print_stats = false; } - ("trace-exits", "") => { - options.trace_side_exits = true; + ("trace-exits", exits) => { + options.trace_side_exits = match exits { + "" => Some(TraceExits::All), + name => Counter::get(name).map(TraceExits::Counter), + } } ("trace-exits-sample-rate", sample_interval) => { - // Even if `trace_side_exits` is already set, set it. - options.trace_side_exits = true; + // If not already set, then set it to `TraceExits::All` by default. + if options.trace_side_exits.is_none() { + options.trace_side_exits = Some(TraceExits::All); + } // `sample_interval ` must provide a string that can be validly parsed to a `usize`. options.trace_side_exits_sample_interval = sample_interval.parse::().ok()?; } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 1b766d5bc4b5aa..c0e9e0b77ca909 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -82,7 +82,7 @@ impl ZJITState { let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap(); let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap(); - let exit_locations = if get_option!(trace_side_exits) { + let exit_locations = if get_option!(trace_side_exits).is_some() { Some(SideExitLocations::default()) } else { None @@ -369,7 +369,7 @@ fn try_increment_existing_stack( /// Record a backtrace with ZJIT side exits #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { - if !zjit_enabled_p() || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() { return; } @@ -425,7 +425,7 @@ pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) { /// Mark `raw_samples` so they can be used by rb_zjit_add_frame. pub fn gc_mark_raw_samples() { // Return if ZJIT is not enabled - if !zjit_enabled_p() || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() { return; } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index d902c69b795c4b..6faa328a1ca736 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -57,6 +57,17 @@ macro_rules! make_counters { $( Counter::$counter_name => stringify!($counter_name), )+ } } + + pub fn get(name: &str) -> Option { + match name { + $( stringify!($default_counter_name) => Some(Counter::$default_counter_name), )+ + $( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+ + $( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+ + $( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+ + $( stringify!($counter_name) => Some(Counter::$counter_name), )+ + _ => None, + } + } } /// Map a counter to a pointer @@ -298,11 +309,11 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { } } -pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { +pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { use crate::hir::SideExitReason::*; use crate::hir::CallType::*; use crate::stats::Counter::*; - let counter = match reason { + match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, UnhandledCallType(Tailcall) => exit_unhandled_tailcall, UnhandledCallType(Splat) => exit_unhandled_splat, @@ -324,7 +335,11 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { StackOverflow => exit_stackoverflow, BlockParamProxyModified => exit_block_param_proxy_modified, BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc, - }; + } +} + +pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { + let counter = side_exit_counter(reason); counter_ptr(counter) } @@ -563,7 +578,7 @@ pub struct SideExitLocations { #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { // Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set. - if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits) { + if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits.is_some()) { Qtrue } else { Qfalse @@ -574,7 +589,7 @@ pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: /// into raw, lines, and frames hash for RubyVM::YJIT.exit_locations. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - if !zjit_enabled_p() || !get_option!(trace_side_exits) { + if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() { return Qnil; }