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;