diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 2ed27e278b1dd4..b8e5af7f0f6e65 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -208,7 +208,7 @@ jobs: matrix: include: # Using the same setup as ZJIT jobs - - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' + - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,shipit' runs-on: ubuntu-24.04 diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index aa3c08b9d8dbc6..820fa91f1a1e90 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' + bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,shipit' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index f79c1c5b1d2121..d3e954bd723cb8 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=lobsters' + bench_opts: '--warmup=1 --bench=1 --excludes=lobsters,shipit' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index e2a3e8dd5beff1..9242dce7dcb6b4 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -803,7 +803,7 @@ def ractor_local_globals } # given block Proc will be isolated, so can not access outer variables. -assert_equal 'ArgumentError', %q{ +assert_equal 'Ractor::IsolationError', %q{ begin a = true r = Ractor.new do diff --git a/doc/language/ractor.md b/doc/language/ractor.md index a0acaf3a918e1a..1592656217678f 100644 --- a/doc/language/ractor.md +++ b/doc/language/ractor.md @@ -97,10 +97,10 @@ This isolation occurs at Ractor creation time (when `Ractor.new` is called). If begin a = true r = Ractor.new do - a #=> ArgumentError because this block accesses outer variable `a`. + a #=> Ractor::IsolationError because this block accesses outer variable `a`. end r.join # wait for ractor to finish -rescue ArgumentError +rescue Ractor::IsolationError end ``` diff --git a/doc/stringio/each_byte.rdoc b/doc/stringio/each_byte.rdoc index 65e81c53a7cd0f..708432b69e0892 100644 --- a/doc/stringio/each_byte.rdoc +++ b/doc/stringio/each_byte.rdoc @@ -7,10 +7,7 @@ returns +self+: strio.each_byte {|byte| bytes.push(byte) } strio.eof? # => true bytes # => [104, 101, 108, 108, 111] - bytes = [] - strio = StringIO.new('тест') # Four 2-byte characters. - strio.each_byte {|byte| bytes.push(byte) } - bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + bytes = [] strio = StringIO.new('こんにちは') # Five 3-byte characters. strio.each_byte {|byte| bytes.push(byte) } diff --git a/doc/stringio/each_char.rdoc b/doc/stringio/each_char.rdoc index d0b5e4082cc4d3..bec5ecac3ff454 100644 --- a/doc/stringio/each_char.rdoc +++ b/doc/stringio/each_char.rdoc @@ -7,10 +7,7 @@ returns +self+: strio.each_char {|char| chars.push(char) } strio.eof? # => true chars # => ["h", "e", "l", "l", "o"] - chars = [] - strio = StringIO.new('тест') - strio.each_char {|char| chars.push(char) } - chars # => ["т", "е", "с", "т"] + chars = [] strio = StringIO.new('こんにちは') strio.each_char {|char| chars.push(char) } diff --git a/doc/stringio/each_codepoint.rdoc b/doc/stringio/each_codepoint.rdoc index ede16de599cf0c..0d10831142ab64 100644 --- a/doc/stringio/each_codepoint.rdoc +++ b/doc/stringio/each_codepoint.rdoc @@ -9,10 +9,7 @@ Each codepoint is the integer value for a character; returns self: strio.each_codepoint {|codepoint| codepoints.push(codepoint) } strio.eof? # => true codepoints # => [104, 101, 108, 108, 111] - codepoints = [] - strio = StringIO.new('тест') - strio.each_codepoint {|codepoint| codepoints.push(codepoint) } - codepoints # => [1090, 1077, 1089, 1090] + codepoints = [] strio = StringIO.new('こんにちは') strio.each_codepoint {|codepoint| codepoints.push(codepoint) } diff --git a/doc/stringio/getbyte.rdoc b/doc/stringio/getbyte.rdoc index 5e524941bca4ae..148455abf48f23 100644 --- a/doc/stringio/getbyte.rdoc +++ b/doc/stringio/getbyte.rdoc @@ -14,13 +14,6 @@ Returns +nil+ if at end-of-stream: Returns a byte, not a character: - s = 'Привет' - s.bytes - # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - strio = StringIO.new(s) - strio.getbyte # => 208 - strio.getbyte # => 159 - s = 'こんにちは' s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] diff --git a/doc/stringio/getc.rdoc b/doc/stringio/getc.rdoc index b2ab46843c8466..58ce47c33790ce 100644 --- a/doc/stringio/getc.rdoc +++ b/doc/stringio/getc.rdoc @@ -12,10 +12,6 @@ Returns +nil+ if at end-of-stream: Returns characters, not bytes: - strio = StringIO.new('Привет') - strio.getc # => "П" - strio.getc # => "р" - strio = StringIO.new('こんにちは') strio.getc # => "こ" strio.getc # => "ん" diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc index bbefeb008ae245..4152152a25239e 100644 --- a/doc/stringio/gets.rdoc +++ b/doc/stringio/gets.rdoc @@ -19,10 +19,10 @@ With no arguments given, reads a line using the default record separator strio.eof? # => true strio.gets # => nil - strio = StringIO.new('Привет') # Six 2-byte characters + strio = StringIO.new('こんにちは') # Five 3-byte characters. strio.pos # => 0 - strio.gets # => "Привет" - strio.pos # => 12 + strio.gets # => "こんにちは" + strio.pos # => 15 Argument +sep+ @@ -67,11 +67,11 @@ but in other cases the position may be anywhere: The position need not be at a character boundary: - strio = StringIO.new('Привет') # Six 2-byte characters. - strio.pos = 2 # At beginning of second character. - strio.gets # => "ривет" - strio.pos = 3 # In middle of second character. - strio.gets # => "\x80ивет" + strio = StringIO.new('こんにちは') # Five 3-byte characters. + strio.pos = 3 # At beginning of second character. + strio.gets # => "んにちは" + strio.pos = 4 # Within second character. + strio.gets # => "\x82\x93にちは" Special Record Separators diff --git a/doc/stringio/size.rdoc b/doc/stringio/size.rdoc index 9323adf8c3783a..253c612c438fab 100644 --- a/doc/stringio/size.rdoc +++ b/doc/stringio/size.rdoc @@ -1,5 +1,4 @@ Returns the number of bytes in the string in +self+: StringIO.new('hello').size # => 5 # Five 1-byte characters. - StringIO.new('тест').size # => 8 # Four 2-byte characters. StringIO.new('こんにちは').size # => 15 # Five 3-byte characters. diff --git a/doc/stringio/stringio.md b/doc/stringio/stringio.md index ce7872f98af401..f81f79cfea728a 100644 --- a/doc/stringio/stringio.md +++ b/doc/stringio/stringio.md @@ -409,13 +409,15 @@ strio.pos = 24 strio.gets # => "Fourth line\n" strio.pos # => 36 -strio = StringIO.new('тест') # Four 2-byte characters. -strio.pos = 0 # At first byte of first character. -strio.read # => "тест" -strio.pos = 1 # At second byte of first character. -strio.read # => "\x82ест" -strio.pos = 2 # At first of second character. -strio.read # => "ест" +strio = StringIO.new('こんにちは') # Five 3-byte characters. +strio.pos = 0 # At first byte of first character. +strio.read # => "こんにちは" +strio.pos = 1 # At second byte of first character. +strio.read # => "\x81\x93んにちは" +strio.pos = 2 # At third byte of first character. +strio.read # => "\x93んにちは" +strio.pos = 3 # At first byte of second character. +strio.read # => "んにちは" strio = StringIO.new(TEXT) strio.pos = 15 diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 1f6a65ca57aedd..0f234f26b956b1 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -155,6 +155,10 @@ def self.default_command(meth = nil) def help(cli = nil) cli = self.class.all_aliases[cli] if self.class.all_aliases[cli] + if Bundler.settings[:plugins] && Bundler::Plugin.command?(cli) && !self.class.all_commands.key?(cli) + return Bundler::Plugin.exec_command(cli, ["--help"]) + end + case cli when "gemfile" then command = "gemfile" when nil then command = "bundle" diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 236ce530eccb75..79344314fe4950 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -158,6 +158,9 @@ def run case config[:ci] when "github" templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") + if extension == "rust" + templates.merge!("github/workflows/build-gems.yml.tt" => ".github/workflows/build-gems.yml") + end config[:ignore_paths] << ".github/" when "gitlab" templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") @@ -228,6 +231,7 @@ def run templates.merge!( "Cargo.toml.tt" => "Cargo.toml", "ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml", + "ext/newgem/build.rs.tt" => "ext/#{name}/build.rs", "ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb", "ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs", ) @@ -435,6 +439,10 @@ def ensure_safe_gem_name(name, constant_array) exit 1 end + if /[A-Z]/.match?(name) + Bundler.ui.warn "Gem names with capital letters are not recommended. Please use only lowercase letters, numbers, and hyphens." + end + constant_name = constant_array.join("::") existing_constant = constant_array.inject(Object) do |c, s| diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 3cf9fbe8bf03aa..4c0c33753526b5 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1125,11 +1125,24 @@ def preload_git_source_worker end def preload_git_sources - sources.git_sources.each {|source| preload_git_source_worker.enq(source) } + needed_git_sources.each {|source| preload_git_source_worker.enq(source) } ensure preload_git_source_worker.stop end + # Git sources needed for the requested groups (excludes sources only used by --without groups) + def needed_git_sources + needed_deps = dependencies_for(requested_groups) + sources.git_sources.select do |source| + needed_deps.any? {|d| d.source == source } + end + end + + # Git sources that should be excluded (only used by --without groups) + def excluded_git_sources + sources.git_sources - needed_git_sources + end + def find_source_requirements if Gem.ruby_version >= Gem::Version.new("3.3") # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to @@ -1141,10 +1154,10 @@ def find_source_requirements # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) source_requirements = if precompute_source_requirements_for_indirect_dependencies? - all_requirements = source_map.all_requirements + all_requirements = source_map.all_requirements(excluded_git_sources) { default: default_source }.merge(all_requirements) else - { default: Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements) + { default: Source::RubygemsAggregate.new(sources, source_map, excluded_git_sources) }.merge(source_map.direct_requirements) end source_requirements.merge!(source_map.locked_requirements) if nothing_changed? metadata_dependencies.each do |dep| diff --git a/lib/bundler/source/rubygems_aggregate.rb b/lib/bundler/source/rubygems_aggregate.rb index 99ef81ad54bded..8aeaa375fa37b7 100644 --- a/lib/bundler/source/rubygems_aggregate.rb +++ b/lib/bundler/source/rubygems_aggregate.rb @@ -5,9 +5,10 @@ class Source class RubygemsAggregate attr_reader :source_map, :sources - def initialize(sources, source_map) + def initialize(sources, source_map, excluded_sources = []) @sources = sources @source_map = source_map + @excluded_sources = excluded_sources @index = build_index end @@ -31,6 +32,8 @@ def build_index dependency_names = source_map.pinned_spec_names sources.all_sources.each do |source| + next if @excluded_sources.include?(source) + source.dependency_names = dependency_names - source_map.pinned_spec_names(source) idx.add_source source.specs dependency_names.concat(source.unmet_deps).uniq! diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb index cb88caf1bd1124..513eb37f8bf347 100644 --- a/lib/bundler/source_map.rb +++ b/lib/bundler/source_map.rb @@ -14,10 +14,14 @@ def pinned_spec_names(skip = nil) direct_requirements.reject {|_, source| source == skip }.keys end - def all_requirements + def all_requirements(excluded_sources = []) requirements = direct_requirements.dup - unmet_deps = sources.non_default_explicit_sources.map do |source| + explicit_sources = sources.non_default_explicit_sources.reject do |source| + excluded_sources.include?(source) + end + + unmet_deps = explicit_sources.map do |source| (source.spec_names - pinned_spec_names).each do |indirect_dependency_name| previous_source = requirements[indirect_dependency_name] if previous_source.nil? diff --git a/lib/bundler/templates/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/Cargo.toml.tt index f5a460c9bbf72e..cd00f97e5a8247 100644 --- a/lib/bundler/templates/newgem/Cargo.toml.tt +++ b/lib/bundler/templates/newgem/Cargo.toml.tt @@ -5,3 +5,9 @@ [workspace] members = ["./ext/<%= config[:name] %>"] resolver = "2" + +[profile.release] +# By default, debug symbols are stripped from the final binary which makes it +# harder to debug if something goes wrong. It's recommended to keep debug +# symbols in the release build so that you can debug the final binary if needed. +debug = true diff --git a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt index c0dc63fbfa3abd..a06166aee7cbe0 100644 --- a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt +++ b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt @@ -13,3 +13,10 @@ crate-type = ["cdylib"] [dependencies] magnus = { version = "0.8.2" } +rb-sys = { version = "0.9", features = ["stable-api-compiled-fallback"] } + +[build-dependencies] +rb-sys-env = "0.2.2" + +[dev-dependencies] +rb-sys-test-helpers = { version = "0.2.2" } diff --git a/lib/bundler/templates/newgem/ext/newgem/build.rs.tt b/lib/bundler/templates/newgem/ext/newgem/build.rs.tt new file mode 100644 index 00000000000000..80a78427533f8e --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/build.rs.tt @@ -0,0 +1,5 @@ +pub fn main() -> Result<(), Box> { + let _ = rb_sys_env::activate()?; + + Ok(()) +} diff --git a/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt b/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt index ba234529a3e8c5..09ce97682d316b 100644 --- a/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt +++ b/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt @@ -1,7 +1,7 @@ use magnus::{function, prelude::*, Error, Ruby}; -fn hello(subject: String) -> String { - format!("Hello from Rust, {subject}!") +pub fn hello(subject: String) -> String { + format!("Hello {subject}, from Rust!") } #[magnus::init] @@ -10,3 +10,14 @@ fn init(ruby: &Ruby) -> Result<(), Error> { module.define_singleton_method("hello", function!(hello, 1))?; Ok(()) } + +#[cfg(test)] +mod tests { + use rb_sys_test_helpers::ruby_test; + use super::hello; + + #[ruby_test] + fn test_hello() { + assert_eq!("Hello world, from Rust!", hello("world".to_string())); + } +} diff --git a/lib/bundler/templates/newgem/github/workflows/build-gems.yml.tt b/lib/bundler/templates/newgem/github/workflows/build-gems.yml.tt new file mode 100644 index 00000000000000..c3fca937f6124e --- /dev/null +++ b/lib/bundler/templates/newgem/github/workflows/build-gems.yml.tt @@ -0,0 +1,65 @@ +--- +name: Build gems + +on: + push: + tags: + - "v*" + - "cross-gem/*" + workflow_dispatch: + +jobs: + ci-data: + runs-on: ubuntu-latest + outputs: + result: ${{ steps.fetch.outputs.result }} + steps: + - uses: oxidize-rb/actions/fetch-ci-data@v1 + id: fetch + with: + supported-ruby-platforms: | + exclude: ["arm-linux", "x64-mingw32"] + stable-ruby-versions: | + exclude: ["head"] + + source-gem: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Build gem + run: bundle exec rake build + + - uses: actions/upload-artifact@v3 + with: + name: source-gem + path: pkg/*.gem + + cross-gem: + name: Compile native gem for ${{ matrix.platform }} + runs-on: ubuntu-latest + needs: ci-data + strategy: + matrix: + platform: ${{ fromJSON(needs.ci-data.outputs.result).supported-ruby-platforms }} + steps: + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - uses: oxidize-rb/actions/cross-gem@v1 + id: cross-gem + with: + platform: ${{ matrix.platform }} + ruby-versions: ${{ join(fromJSON(needs.ci-data.outputs.result).stable-ruby-versions, ',') }} + + - uses: actions/upload-artifact@v3 + with: + name: cross-gem + path: ${{ steps.cross-gem.outputs.gem-path }} diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index 513875fd63ef07..7799dbfd32f021 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -15,10 +15,6 @@ Gem::Specification.new do |spec| spec.license = "MIT" <%- end -%> spec.required_ruby_version = ">= <%= config[:required_ruby_version] %>" -<%- if config[:ext] == 'rust' -%> - spec.required_rubygems_version = ">= <%= config[:rust_builder_required_rubygems_version] %>" -<%- end -%> - spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "<%= config[:source_code_uri] %>" @@ -44,9 +40,6 @@ Gem::Specification.new do |spec| # Uncomment to register a new dependency of your gem # spec.add_dependency "example-gem", "~> 1.0" -<%- if config[:ext] == 'rust' -%> - spec.add_dependency "rb_sys", "~> 0.9.91" -<%- end -%> <%- if config[:ext] == 'go' -%> spec.add_dependency "go_gem", "~> 0.2" <%- end -%> diff --git a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt index 82cada988cd3a8..dd39342f788590 100644 --- a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +++ b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt @@ -5,7 +5,15 @@ RSpec.describe <%= config[:constant_name] %> do expect(<%= config[:constant_name] %>::VERSION).not_to be nil end +<%- if config[:ext] == 'rust' -%> + it "can call into Rust" do + result = <%= config[:constant_name] %>.hello("world") + + expect(result).to be("Hello earth, from Rust!") + end +<%- else -%> it "does something useful" do expect(false).to eq(true) end +<%- end -%> end diff --git a/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt b/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt index 4b35a630714d33..9a859fa4d1eda2 100644 --- a/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt +++ b/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt @@ -7,7 +7,13 @@ class <%= config[:minitest_constant_name] %> < Minitest::Test refute_nil ::<%= config[:constant_name] %>::VERSION end +<%- if config[:ext] == 'rust' -%> + def test_hello_world + assert_equal "Hello earth, from Rust!", <%= config[:constant_name] %>.hello("world") + end +<%- else -%> def test_it_does_something_useful assert false end +<%- end -%> end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index f2c90cce3cb76a..d34480f4743871 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -47,8 +47,7 @@ class Gem::ConfigFile DEFAULT_CONCURRENT_DOWNLOADS = 8 DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365 DEFAULT_IPV4_FALLBACK_ENABLED = false - # TODO: Use false as default value for this option in RubyGems 4.0 - DEFAULT_INSTALL_EXTENSION_IN_LIB = true + DEFAULT_INSTALL_EXTENSION_IN_LIB = false DEFAULT_GLOBAL_GEM_CACHE = false ## diff --git a/ractor.rb b/ractor.rb index ac04237fc6e4f1..2dc60f5ff64926 100644 --- a/ractor.rb +++ b/ractor.rb @@ -15,7 +15,7 @@ # a = 1 # r = Ractor.new {puts "I am in Ractor! a=#{a}"} # # fails immediately with -# # ArgumentError (can not isolate a Proc because it accesses outer variables (a).) +# # Ractor::IsolationError (can not isolate a Proc because it accesses outer variables (a).) # # The object must be explicitly shared: # a = 1 @@ -657,7 +657,7 @@ def unmonitor port # # a = 42 # Ractor.shareable_proc{ p a } - # #=> can not isolate a Proc because it accesses outer variables (a). (ArgumentError) + # #=> can not isolate a Proc because it accesses outer variables (a). (Ractor::IsolationError) # # The value of `self` in the Proc must be a shareable object. # diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 06c226f9e56a17..754dd3f31f3f24 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -1733,12 +1733,11 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/ext/#{gem_name}/Cargo.toml")).to exist expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist expect(bundled_app("#{gem_name}/ext/#{gem_name}/src/lib.rs")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/build.rs")).to exist end - it "includes rake-compiler, rb_sys gems and required_rubygems_version constraint" do + it "includes rake-compiler constraint" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "rb_sys"') - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.required_rubygems_version = ">= ') end it "depends on compile task for build" do @@ -1761,6 +1760,30 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + + it "configures the crate such that `cargo test` works", :ruby_repo, :mri_only do + env = setup_rust_env + gem_path = bundled_app(gem_name) + result = sys_exec("cargo test", env: env, dir: gem_path) + + expect(result).to include("1 passed") + end + + def setup_rust_env + skip "rust toolchain of mingw is broken" if RUBY_PLATFORM.match?("mingw") + + env = { + "CARGO_HOME" => ENV.fetch("CARGO_HOME", File.join(ENV["HOME"], ".cargo")), + "RUSTUP_HOME" => ENV.fetch("RUSTUP_HOME", File.join(ENV["HOME"], ".rustup")), + "RUSTUP_TOOLCHAIN" => ENV.fetch("RUSTUP_TOOLCHAIN", "stable"), + } + + system(env, "cargo", "-V", out: IO::NULL, err: [:child, :out]) + skip "cargo not present" unless $?.success? + # Hermetic Cargo setup + RbConfig::CONFIG.each {|k, v| env["RBCONFIG_#{k}"] = v } + env + end end context "--ext parameter set with go" do @@ -1982,6 +2005,19 @@ def create_temporary_dir(dir) it { expect(err).to include("Invalid gem name #{subject}") } end + context "starting with a number" do + subject { "1gem" } + it { expect(err).to include("Invalid gem name #{subject}") } + end + + context "including capital letter" do + subject { "CAPITAL" } + it "should warn but not error" do + expect(err).to include("Gem names with capital letters are not recommended") + expect(bundled_app("#{subject}/#{subject}.gemspec")).to exist + end + end + context "starting with an existing const name" do subject { "gem-somenewconstantname" } it { expect(err).not_to include("Invalid gem name #{subject}") } diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index aa707a022290e7..b711f6e614082f 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -290,4 +290,32 @@ end end end + + describe "with excluded groups" do + it "works if you exclude a group with a git gem", ruby: ">= 3.3" do + build_git "production_gem", "1.0" + build_git "development_gem", "1.0" + + gemfile <<-G + source "https://gem.repo1" + + gem "production_gem", :git => "#{lib_path("production_gem-1.0")}" + + group :development do + gem "development_gem", :git => "#{lib_path("development_gem-1.0")}" + end + G + + # First install all groups to create lockfile + bundle :install + + # Set without and reinstall + bundle "config set --local without development" + bundle :install + + # Verify only production gem is available + expect(the_bundle).to include_gems("production_gem 1.0") + expect(the_bundle).not_to include_gems("development_gem 1.0") + end + end end diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb index f8dacb0e51eec6..05d535a70cefc3 100644 --- a/spec/bundler/plugins/command_spec.rb +++ b/spec/bundler/plugins/command_spec.rb @@ -53,6 +53,40 @@ def exec(command, args) expect(out).to eq("You gave me tacos, tofu, lasange") end + it "passes help flag to plugin" do + update_repo2 do + build_plugin "helpful" do |s| + s.write "plugins.rb", <<-RUBY + module Helpful + class Command + Bundler::Plugin::API.command "greet", self + + def exec(command, args) + if args.include?("--help") || args.include?("-h") + puts "Usage: bundle greet [NAME]" + else + puts "Hello" + end + end + end + end + RUBY + end + end + + bundle "plugin install helpful --source https://gem.repo2" + expect(out).to include("Installed plugin helpful") + + bundle "greet --help" + expect(out).to eq("Usage: bundle greet [NAME]") + + bundle "greet -h" + expect(out).to eq("Usage: bundle greet [NAME]") + + bundle "help greet" + expect(out).to eq("Usage: bundle greet [NAME]") + end + it "raises error on redeclaration of command" do update_repo2 do build_plugin "copycat" do |s| diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 6e94b10e32c845..c6b60b5d52fe3a 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -29,6 +29,7 @@ def inspect config.filter_run_excluding jruby_only: RUBY_ENGINE != "jruby" config.filter_run_excluding truffleruby_only: RUBY_ENGINE != "truffleruby" config.filter_run_excluding man: Gem.win_platform? + config.filter_run_excluding mri_only: RUBY_ENGINE != "ruby" config.filter_run_when_matching :focus unless ENV["CI"] diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index fa716787fe9841..6846f7958b5a55 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -157,7 +157,7 @@ def (Object.new).touch(&) # :nodoc: def test_ractor_unshareable_outer_variable name = "\u{2603 26a1}" - assert_raise_with_message(ArgumentError, /\(#{name}\)/) do + assert_raise_with_message(Ractor::IsolationError, /\(#{name}\)/) do eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}") end diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 6ae511217aca09..36467a35918981 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -213,6 +213,24 @@ def test_ifunc_proc_not_shareable assert_unshareable(pr, /not supported yet/, exception: RuntimeError) end + def test_ractor_new_raises_isolation_error_if_outer_variables_are_accessed + assert_raise(Ractor::IsolationError) do + channel = Ractor::Port.new + Ractor.new(channel) do + inbound_work = Ractor::Port.new + channel << inbound_work + end + end + end + + def test_ractor_new_raises_isolation_error_if_proc_uses_yield + assert_raise(Ractor::IsolationError) do + Ractor.new do + yield + end + end + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) diff --git a/test/rubygems/test_gem_config_file.rb b/test/rubygems/test_gem_config_file.rb index 79bf5f582c942c..6ed51f2704b8b6 100644 --- a/test/rubygems/test_gem_config_file.rb +++ b/test/rubygems/test_gem_config_file.rb @@ -43,6 +43,7 @@ def test_initialize assert_equal [@gem_repo], Gem.sources assert_equal 365, @cfg.cert_expiration_length_days assert_equal false, @cfg.ipv4_fallback_enabled + assert_equal false, @cfg.install_extension_in_lib File.open @temp_conf, "w" do |fp| fp.puts ":backtrace: true" diff --git a/vm.c b/vm.c index a9e952f705f131..a078f9e7344e4c 100644 --- a/vm.c +++ b/vm.c @@ -1521,10 +1521,10 @@ proc_shared_outer_variables(struct rb_id_table *outer_variables, bool isolate, c } if (*sep == ',') rb_str_cat_cstr(str, ")"); rb_str_cat_cstr(str, data.yield ? " and uses 'yield'." : "."); - rb_exc_raise(rb_exc_new_str(rb_eArgError, str)); + rb_exc_raise(rb_exc_new_str(rb_eRactorIsolationError, str)); } else if (data.yield) { - rb_raise(rb_eArgError, "can not %s because it uses 'yield'.", message); + rb_raise(rb_eRactorIsolationError, "can not %s because it uses 'yield'.", message); } return data.read_only;