Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup/directories/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ runs:
path: ${{ inputs.srcdir }}
fetch-depth: ${{ inputs.fetch-depth }}

- uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
- uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ${{ inputs.srcdir }}/.downloaded-cache
key: ${{ runner.os }}-${{ runner.arch }}-downloaded-cache
Expand Down
10 changes: 10 additions & 0 deletions bootstraptest/test_yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4889,6 +4889,16 @@ def tests
tests
}

# regression test for splat with &proc{} when the target has rest (Bug #21266)
assert_equal '[]', %q{
def foo(args) = bar(*args, &proc { _1 })
def bar(_, _, _, _, *rest) = yield rest

GC.stress = true
foo([1,2,3,4])
foo([1,2,3,4])
}

# regression test for invalidating an empty block
assert_equal '0', %q{
def foo = (* = 1).pred
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def cli_help
primary_commands = ["install", "update", "cache", "exec", "config", "help"]

list = self.class.printable_commands(true)
by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] }
by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] }
utilities = by_name.keys.sort - primary_commands
primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first }
utilities.map! {|name| by_name[name].first }
Expand Down
27 changes: 15 additions & 12 deletions lib/bundler/ruby_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,21 @@ def ruby(*ruby_version)
# Loads the file relative to the dirname of the Gemfile itself.
def normalize_ruby_file(filename)
file_content = Bundler.read_file(gemfile.dirname.join(filename))
# match "ruby-3.2.2", ruby = "3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment
if /^ # Start of line
ruby # Literal "ruby"
[\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format)
(?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format)
"? # Optional opening quote
( # Start capturing group
[^\s#"]+ # One or more chars that aren't spaces, #, or quotes
) # End capturing group
"? # Optional closing quote
/x.match(file_content)
$1
# match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment
version_match = /^ # Start of line
ruby # Literal "ruby"
[\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format)
(?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format)
(?:
"([^"]+)" # Double quoted version
|
'([^']+)' # Single quoted version
|
([^\s#"']+) # Unquoted version
)
/x.match(file_content)
if version_match
version_match[1] || version_match[2] || version_match[3]
else
file_content.strip
end
Expand Down
4 changes: 4 additions & 0 deletions lib/bundler/rubygems_gem_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ def generate_bin_script(filename, bindir)
end
end

def build_jobs
Bundler.settings[:jobs] || super
end

def build_extensions
extension_cache_path = options[:bundler_extension_cache_path]
extension_dir = spec.extension_dir
Expand Down
2 changes: 2 additions & 0 deletions lib/rubygems/dependency_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def initialize(options = {})
@user_install = options[:user_install]
@wrappers = options[:wrappers]
@build_args = options[:build_args]
@build_jobs = options[:build_jobs]
@build_docs_in_background = options[:build_docs_in_background]
@dir_mode = options[:dir_mode]
@data_mode = options[:data_mode]
Expand Down Expand Up @@ -154,6 +155,7 @@ def install(dep_or_name, version = Gem::Requirement.default)
options = {
bin_dir: @bin_dir,
build_args: @build_args,
build_jobs: @build_jobs,
document: @document,
env_shebang: @env_shebang,
force: @force,
Expand Down
19 changes: 15 additions & 4 deletions lib/rubygems/ext/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def self.class_name
end

def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"],
target_rbconfig: Gem.target_rbconfig)
target_rbconfig: Gem.target_rbconfig, n_jobs: nil)
unless File.exist? File.join(make_dir, "Makefile")
# No makefile exists, nothing to do.
raise NoMakefileError, "No Makefile found in #{make_dir}"
Expand All @@ -34,8 +34,18 @@ def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = [
make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make"
make_program = shellsplit(make_program_name)

is_nmake = /\bnmake/i.match?(make_program_name)
# The installation of the bundled gems is failed when DESTDIR is empty in mswin platform.
destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : ""
destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : ""

# nmake doesn't support parallel build
unless is_nmake
have_make_arguments = make_program.size > 1

if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs
make_program << "-j#{n_jobs}"
end
end

env = [destdir]

Expand Down Expand Up @@ -147,11 +157,12 @@ def self.shelljoin(command)
# have build arguments, saved, set +build_args+ which is an ARGV-style
# array.

def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig)
def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil)
@spec = spec
@build_args = build_args
@gem_dir = spec.full_gem_path
@target_rbconfig = target_rbconfig
@build_jobs = build_jobs

@ran_rake = false
end
Expand Down Expand Up @@ -208,7 +219,7 @@ def build_extension(extension, dest_path) # :nodoc:
FileUtils.mkdir_p dest_path

results = builder.build(extension, dest_path,
results, @build_args, lib_dir, extension_dir, @target_rbconfig)
results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs)

verbose { results.join("\n") }

Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/ext/cargo_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize
end

def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd,
target_rbconfig = Gem.target_rbconfig)
target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
require "tempfile"
require "fileutils"

Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/ext/cmake_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def initialize
end

def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd,
target_rbconfig = Gem.target_rbconfig)
target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
if target_rbconfig.path
warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring"
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rubygems/ext/configure_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd,
target_rbconfig = Gem.target_rbconfig)
target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
if target_rbconfig.path
warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring"
end
Expand All @@ -19,7 +19,7 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, configur
run cmd, results, class_name, configure_dir
end

make dest_path, results, configure_dir, target_rbconfig: target_rbconfig
make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs

results
end
Expand Down
7 changes: 2 additions & 5 deletions lib/rubygems/ext/ext_conf_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd,
target_rbconfig = Gem.target_rbconfig)
target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
require "fileutils"
require "tempfile"

Expand Down Expand Up @@ -40,11 +40,8 @@ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extensio
end

ENV["DESTDIR"] = nil
unless RUBY_PLATFORM.include?("mswin") && RbConfig::CONFIG["configure_args"]&.include?("nmake")
ENV["MAKEFLAGS"] ||= "-j#{Etc.nprocessors + 1}"
end

make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig
make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs

full_tmp_dest = File.join(extension_dir, tmp_dest_relative)

Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/ext/rake_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class Gem::Ext::RakeBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd,
target_rbconfig = Gem.target_rbconfig)
target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
if target_rbconfig.path
warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring"
end
Expand Down
9 changes: 9 additions & 0 deletions lib/rubygems/install_update_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ def add_install_update_options
options[:bin_dir] = File.expand_path(value)
end

add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer,
"Specify the number of jobs to pass to `make` when installing",
"gems with native extensions.",
"Defaults to the number of processors.",
"This option is ignored on the mswin platform or",
"if the MAKEFLAGS environment variable is set.") do |value, options|
options[:build_jobs] = value
end

add_option(:"Install/Update", "--document [TYPES]", Array,
"Generate documentation for installed gems",
"List the documentation types you wish to",
Expand Down
7 changes: 6 additions & 1 deletion lib/rubygems/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ def process_options # :nodoc:
@build_root = options[:build_root]

@build_args = options[:build_args]
@build_jobs = options[:build_jobs]

@gem_home = @install_dir || user_install_dir || Gem.dir

Expand Down Expand Up @@ -803,7 +804,7 @@ def windows_stub_script(bindir, bin_file_name)
# configure scripts and rakefiles or mkrf_conf files.

def build_extensions
builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig
builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs

builder.build_extensions
end
Expand Down Expand Up @@ -941,6 +942,10 @@ def build_args
end
end

def build_jobs
@build_jobs ||= Etc.nprocessors + 1
end

def rb_config
Gem.target_rbconfig
end
Expand Down
11 changes: 11 additions & 0 deletions spec/bundler/bundler/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,15 @@ def out_with_macos_man_workaround
bundler "--version"
expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)")
end

it "shows cli_help when bundler install and no Gemfile is found" do
bundler "install", raise_on_error: false
expect(err).to include("Could not locate Gemfile")

expect(out).to include("Bundler version #{Bundler::VERSION}").
and include("\n\nBundler commands:\n\n").
and include("\n\n Primary commands:\n").
and include("\n\n Utilities:\n").
and include("\n\nOptions:\n")
end
end
27 changes: 25 additions & 2 deletions spec/bundler/bundler/ruby_dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,34 @@ class MockDSL
let(:file_content) do
<<~TOML
[tools]
ruby = "#{version}"
ruby = #{quote}#{version}#{quote}
TOML
end

it_behaves_like "it stores the ruby version"
context "with double quotes" do
let(:quote) { '"' }

it_behaves_like "it stores the ruby version"
end

context "with single quotes" do
let(:quote) { "'" }

it_behaves_like "it stores the ruby version"
end

context "with mismatched quotes" do
let(:file_content) do
<<~TOML
[tools]
ruby = "#{version}'
TOML
end

it "raises an error" do
expect { subject }.to raise_error(Bundler::InvalidArgumentError, "= is not a valid requirement on the Ruby version")
end
end
end

context "with a .tool-versions file format" do
Expand Down
73 changes: 73 additions & 0 deletions spec/bundler/commands/install_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,79 @@ def run
end
end

describe "parallel make" do
before do
unless Gem::Installer.private_method_defined?(:build_jobs)
skip "This example is runnable when RubyGems::Installer implements `build_jobs`"
end

@old_makeflags = ENV["MAKEFLAGS"]
@gemspec = nil

extconf_code = <<~CODE
require "mkmf"
create_makefile("foo")
CODE

build_repo4 do
build_gem "mypsych", "4.0.6" do |s|
@gemspec = s
extension = "ext/mypsych/extconf.rb"
s.extensions = extension

s.write(extension, extconf_code)
end
end
end

after do
if @old_makeflags
ENV["MAKEFLAGS"] = @old_makeflags
else
ENV.delete("MAKEFLAGS")
end
end

it "doesn't pass down -j to make when MAKEFLAGS is set" do
ENV["MAKEFLAGS"] = "-j1"

install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" })
source "https://gem.repo4"
gem "mypsych"
G

gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out"))

expect(gem_make_out).not_to include("make -j8")
end

it "pass down the BUNDLE_JOBS to RubyGems when running the compilation of an extension" do
ENV.delete("MAKEFLAGS")

install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" })
source "https://gem.repo4"
gem "mypsych"
G

gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out"))

expect(gem_make_out).to include("make -j8")
end

it "uses nprocessors by default" do
ENV.delete("MAKEFLAGS")

install_gemfile(<<~G)
source "https://gem.repo4"
gem "mypsych"
G

gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out"))

expect(gem_make_out).to include("make -j#{Etc.nprocessors + 1}")
end
end

describe "when configured path is UTF-8 and a file inside a gem package too" do
let(:app_path) do
path = tmp("♥")
Expand Down
Loading