diff options
author | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2023-02-01 12:05:19 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-01 12:05:19 +0900 |
commit | f4e6e78410136100ef5f285136a66df8d6004a61 (patch) | |
tree | ed1f754dbe1c4b37e3736265a99122e7a12c3b5b | |
parent | 40e0b1e123503805c16a2a9aafae0a5c302c20d1 (diff) |
Merge RubyGems 3.4.6 and Bundler 2.4.6 (#7214)
Merge RubyGems-3.4.6 and Bundler-2.4.6
30 files changed, 948 insertions, 717 deletions
diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index 639c01ff39..88d071571f 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -40,7 +40,11 @@ module Bundler end if options[:standalone] - next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler" + if gem_name == "bundler" + Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") unless options[:all] + next + end + Bundler.settings.temporary(:path => (Bundler.settings[:path] || Bundler.root)) do installer.generate_standalone_bundler_executable_stubs(spec, installer_opts) end diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index 6664d3ebc4..5c184f67a1 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -31,6 +31,7 @@ # def gemfile(install = false, options = {}, &gemfile) require_relative "../bundler" + Bundler.reset! opts = options.dup ui = opts.delete(:ui) { Bundler::UI::Shell.new } @@ -38,9 +39,8 @@ def gemfile(install = false, options = {}, &gemfile) Bundler.ui = ui raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty? - begin + Bundler.with_unbundled_env do Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir)) - old_gemfile = ENV["BUNDLE_GEMFILE"] Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? @@ -65,11 +65,9 @@ def gemfile(install = false, options = {}, &gemfile) runtime = Bundler::Runtime.new(nil, definition) runtime.setup.require end - ensure - if old_gemfile - ENV["BUNDLE_GEMFILE"] = old_gemfile - else - ENV["BUNDLE_GEMFILE"] = "" - end + end + + if ENV["BUNDLE_GEMFILE"].nil? + ENV["BUNDLE_GEMFILE"] = "" end end diff --git a/lib/bundler/installer/standalone.rb b/lib/bundler/installer/standalone.rb index 2756626f8a..2efef61648 100644 --- a/lib/bundler/installer/standalone.rb +++ b/lib/bundler/installer/standalone.rb @@ -84,13 +84,17 @@ module Bundler def reverse_rubygems_kernel_mixin <<~END - kernel = (class << ::Kernel; self; end) - [kernel, ::Kernel].each do |k| - if k.private_method_defined?(:gem_original_require) - private_require = k.private_method_defined?(:require) - k.send(:remove_method, :require) - k.send(:define_method, :require, k.instance_method(:gem_original_require)) - k.send(:private, :require) if private_require + if Gem.respond_to?(:discover_gems_on_require=) + Gem.discover_gems_on_require = false + else + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |k| + if k.private_method_defined?(:gem_original_require) + private_require = k.private_method_defined?(:require) + k.send(:remove_method, :require) + k.send(:define_method, :require, k.instance_method(:gem_original_require)) + k.send(:private, :require) if private_require + end end end END diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index d14075c96b..3857948511 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -227,10 +227,14 @@ module Bundler def reverse_rubygems_kernel_mixin # Disable rubygems' gem activation system - kernel = (class << ::Kernel; self; end) - [kernel, ::Kernel].each do |k| - if k.private_method_defined?(:gem_original_require) - redefine_method(k, :require, k.instance_method(:gem_original_require)) + if Gem.respond_to?(:discover_gems_on_require=) + Gem.discover_gems_on_require = false + else + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |k| + if k.private_method_defined?(:gem_original_require) + redefine_method(k, :require, k.instance_method(:gem_original_require)) + end end end end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 2aba18e298..d3bd162e48 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.4.5".freeze + VERSION = "2.4.6".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 520fd4951b..26b0b1da7e 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -8,7 +8,7 @@ require "rbconfig" module Gem - VERSION = "3.4.5".freeze + VERSION = "3.4.6".freeze end # Must be first since it unloads the prelude from 1.9.2 @@ -181,6 +181,8 @@ module Gem @default_source_date_epoch = nil + @discover_gems_on_require = true + ## # Try to activate a gem containing +path+. Returns true if # activation succeeded or wasn't needed because it was already @@ -1163,9 +1165,17 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # RubyGems distributors (like operating system package managers) can # disable RubyGems update by setting this to error message printed to # end-users on gem update --system instead of actual update. + attr_accessor :disable_system_update_message ## + # Whether RubyGems should enhance builtin `require` to automatically + # check whether the path required is present in installed gems, and + # automatically activate them and add them to `$LOAD_PATH`. + + attr_accessor :discover_gems_on_require + + ## # Hash of loaded Gem::Specification keyed by name attr_reader :loaded_specs diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index b92d6f9965..6dcc4a06e4 100644 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -34,137 +34,141 @@ module Kernel # that file has already been loaded is preserved. def require(path) # :doc: - if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) - monitor_owned = RUBYGEMS_ACTIVATION_MONITOR.mon_owned? - end - RUBYGEMS_ACTIVATION_MONITOR.enter - - path = path.to_path if path.respond_to? :to_path - - if spec = Gem.find_unresolved_default_spec(path) - # Ensure -I beats a default gem - resolved_path = begin - rp = nil - load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths - Gem.suffixes.each do |s| - $LOAD_PATH[0...load_path_check_index].each do |lp| - safe_lp = lp.dup.tap(&Gem::UNTAINT) - begin - if File.symlink? safe_lp # for backward compatibility - next + return gem_original_require(path) unless Gem.discover_gems_on_require + + begin + if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) + monitor_owned = RUBYGEMS_ACTIVATION_MONITOR.mon_owned? + end + RUBYGEMS_ACTIVATION_MONITOR.enter + + path = path.to_path if path.respond_to? :to_path + + if spec = Gem.find_unresolved_default_spec(path) + # Ensure -I beats a default gem + resolved_path = begin + rp = nil + load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths + Gem.suffixes.each do |s| + $LOAD_PATH[0...load_path_check_index].each do |lp| + safe_lp = lp.dup.tap(&Gem::UNTAINT) + begin + if File.symlink? safe_lp # for backward compatibility + next + end + rescue SecurityError + RUBYGEMS_ACTIVATION_MONITOR.exit + raise end - rescue SecurityError - RUBYGEMS_ACTIVATION_MONITOR.exit - raise - end - full_path = File.expand_path(File.join(safe_lp, "#{path}#{s}")) - if File.file?(full_path) - rp = full_path - break + full_path = File.expand_path(File.join(safe_lp, "#{path}#{s}")) + if File.file?(full_path) + rp = full_path + break + end end + break if rp end - break if rp + rp end - rp + + begin + Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease) + rescue Exception + RUBYGEMS_ACTIVATION_MONITOR.exit + raise + end unless resolved_path end - begin - Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease) - rescue Exception - RUBYGEMS_ACTIVATION_MONITOR.exit - raise - end unless resolved_path - end + # If there are no unresolved deps, then we can use just try + # normal require handle loading a gem from the rescue below. - # If there are no unresolved deps, then we can use just try - # normal require handle loading a gem from the rescue below. + if Gem::Specification.unresolved_deps.empty? + RUBYGEMS_ACTIVATION_MONITOR.exit + return gem_original_require(path) + end - if Gem::Specification.unresolved_deps.empty? - RUBYGEMS_ACTIVATION_MONITOR.exit - return gem_original_require(path) - end + # If +path+ is for a gem that has already been loaded, don't + # bother trying to find it in an unresolved gem, just go straight + # to normal require. + #-- + # TODO request access to the C implementation of this to speed up RubyGems - # If +path+ is for a gem that has already been loaded, don't - # bother trying to find it in an unresolved gem, just go straight - # to normal require. - #-- - # TODO request access to the C implementation of this to speed up RubyGems + if Gem::Specification.find_active_stub_by_path(path) + RUBYGEMS_ACTIVATION_MONITOR.exit + return gem_original_require(path) + end - if Gem::Specification.find_active_stub_by_path(path) - RUBYGEMS_ACTIVATION_MONITOR.exit - return gem_original_require(path) - end + # Attempt to find +path+ in any unresolved gems... + + found_specs = Gem::Specification.find_in_unresolved path + + # If there are no directly unresolved gems, then try and find +path+ + # in any gems that are available via the currently unresolved gems. + # For example, given: + # + # a => b => c => d + # + # If a and b are currently active with c being unresolved and d.rb is + # requested, then find_in_unresolved_tree will find d.rb in d because + # it's a dependency of c. + # + if found_specs.empty? + found_specs = Gem::Specification.find_in_unresolved_tree path + + found_specs.each do |found_spec| + found_spec.activate + end - # Attempt to find +path+ in any unresolved gems... - - found_specs = Gem::Specification.find_in_unresolved path - - # If there are no directly unresolved gems, then try and find +path+ - # in any gems that are available via the currently unresolved gems. - # For example, given: - # - # a => b => c => d - # - # If a and b are currently active with c being unresolved and d.rb is - # requested, then find_in_unresolved_tree will find d.rb in d because - # it's a dependency of c. - # - if found_specs.empty? - found_specs = Gem::Specification.find_in_unresolved_tree path - - found_specs.each do |found_spec| - found_spec.activate - end + # We found +path+ directly in an unresolved gem. Now we figure out, of + # the possible found specs, which one we should activate. + else - # We found +path+ directly in an unresolved gem. Now we figure out, of - # the possible found specs, which one we should activate. - else + # Check that all the found specs are just different + # versions of the same gem + names = found_specs.map(&:name).uniq - # Check that all the found specs are just different - # versions of the same gem - names = found_specs.map(&:name).uniq + if names.size > 1 + RUBYGEMS_ACTIVATION_MONITOR.exit + raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}" + end - if names.size > 1 - RUBYGEMS_ACTIVATION_MONITOR.exit - raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}" - end + # Ok, now find a gem that has no conflicts, starting + # at the highest version. + valid = found_specs.find {|s| !s.has_conflicts? } - # Ok, now find a gem that has no conflicts, starting - # at the highest version. - valid = found_specs.find {|s| !s.has_conflicts? } + unless valid + le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" + le.name = names.first + RUBYGEMS_ACTIVATION_MONITOR.exit + raise le + end - unless valid - le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" - le.name = names.first - RUBYGEMS_ACTIVATION_MONITOR.exit - raise le + valid.activate end - valid.activate - end - - RUBYGEMS_ACTIVATION_MONITOR.exit - return gem_original_require(path) - rescue LoadError => load_error - if load_error.path == path - RUBYGEMS_ACTIVATION_MONITOR.enter + RUBYGEMS_ACTIVATION_MONITOR.exit + return gem_original_require(path) + rescue LoadError => load_error + if load_error.path == path + RUBYGEMS_ACTIVATION_MONITOR.enter + + begin + require_again = Gem.try_activate(path) + ensure + RUBYGEMS_ACTIVATION_MONITOR.exit + end - begin - require_again = Gem.try_activate(path) - ensure - RUBYGEMS_ACTIVATION_MONITOR.exit + return gem_original_require(path) if require_again end - return gem_original_require(path) if require_again - end - - raise load_error - ensure - if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) - if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?) - STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $! - raise "CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before #{monitor_owned} -> after #{ow}" + raise load_error + ensure + if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) + if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?) + STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $! + raise "CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before #{monitor_owned} -> after #{ow}" + end end end end diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 98d354183c..7fb8958d7f 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -131,8 +131,7 @@ class Gem::Ext::Builder when /CMakeLists.txt/ then Gem::Ext::CmakeBuilder when /Cargo.toml/ then - # We use the spec name here to ensure we invoke the correct init function later - Gem::Ext::CargoBuilder.new(@spec) + Gem::Ext::CargoBuilder.new else build_error("No builder for extension '#{extension}'") end diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 60ab5544fe..022aabf481 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -6,30 +6,60 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder attr_accessor :spec, :runner, :profile - def initialize(spec) + def initialize require_relative "../command" require_relative "cargo_builder/link_flag_converter" - @spec = spec @runner = self.class.method(:run) @profile = :release end - def build(_extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd) + def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd) + require "tempfile" require "fileutils" - require "shellwords" - build_crate(dest_path, results, args, cargo_dir) - validate_cargo_build!(dest_path) - rename_cdylib_for_ruby_compatibility(dest_path) - finalize_directory(dest_path, lib_dir, cargo_dir) - results - end + # Where's the Cargo.toml of the crate we're building + cargo_toml = File.join(cargo_dir, "Cargo.toml") + # What's the crate's name + crate_name = cargo_crate_name(cargo_dir, cargo_toml, results) + + begin + # Create a tmp dir to do the build in + tmp_dest = Dir.mktmpdir(".gem.", cargo_dir) + + # Run the build + cmd = cargo_command(cargo_toml, tmp_dest, args, crate_name) + runner.call(cmd, results, "cargo", cargo_dir, build_env) + + # Where do we expect Cargo to write the compiled library + dylib_path = cargo_dylib_path(tmp_dest, crate_name) - def build_crate(dest_path, results, args, cargo_dir) - env = build_env - cmd = cargo_command(cargo_dir, dest_path, args) - runner.call cmd, results, "cargo", cargo_dir, env + # Helpful error if we didn't find the compiled library + raise DylibNotFoundError, tmp_dest unless File.exist?(dylib_path) + + # Cargo and Ruby differ on how the library should be named, rename from + # what Cargo outputs to what Ruby expects + dlext_name = "#{crate_name}.#{makefile_config("DLEXT")}" + dlext_path = File.join(File.dirname(dylib_path), dlext_name) + FileUtils.cp(dylib_path, dlext_path) + + nesting = extension_nesting(extension) + + # TODO: remove in RubyGems 4 + if Gem.install_extension_in_lib && lib_dir + nested_lib_dir = File.join(lib_dir, nesting) + FileUtils.mkdir_p nested_lib_dir + FileUtils.cp_r dlext_path, nested_lib_dir, remove_destination: true + end + + # move to final destination + nested_dest_path = File.join(dest_path, nesting) + FileUtils.mkdir_p nested_dest_path + FileUtils.cp_r dlext_path, nested_dest_path, remove_destination: true + ensure + # clean up intermediary build artifacts + FileUtils.rm_rf tmp_dest if tmp_dest + end results end @@ -42,39 +72,59 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder build_env end - def cargo_command(cargo_dir, dest_path, args = []) - manifest = File.join(cargo_dir, "Cargo.toml") - cargo = ENV.fetch("CARGO", "cargo") + def cargo_command(cargo_toml, dest_path, args = [], crate_name = nil) + require "shellwords" cmd = [] cmd += [cargo, "rustc"] cmd += ["--crate-type", "cdylib"] cmd += ["--target", ENV["CARGO_BUILD_TARGET"]] if ENV["CARGO_BUILD_TARGET"] cmd += ["--target-dir", dest_path] - cmd += ["--manifest-path", manifest] + cmd += ["--manifest-path", cargo_toml] cmd += ["--lib"] cmd += ["--profile", profile.to_s] cmd += ["--locked"] cmd += Gem::Command.build_args cmd += args cmd += ["--"] - cmd += [*cargo_rustc_args(dest_path)] + cmd += [*cargo_rustc_args(dest_path, crate_name)] cmd end private + def cargo + ENV.fetch("CARGO", "cargo") + end + + # returns the directory nesting of the extension, ignoring the first part, so + # "ext/foo/bar/Cargo.toml" becomes "foo/bar" + def extension_nesting(extension) + parts = extension.to_s.split(Regexp.union([File::SEPARATOR, File::ALT_SEPARATOR].compact)) + + parts = parts.each_with_object([]) do |segment, final| + next if segment == "." + if segment == ".." + raise Gem::InstallError, "extension outside of gem root" if final.empty? + next final.pop + end + final << segment + end + + File.join(parts[1...-1]) + end + def rb_config_env result = {} RbConfig::CONFIG.each {|k, v| result["RBCONFIG_#{k}"] = v } result end - def cargo_rustc_args(dest_dir) + def cargo_rustc_args(dest_dir, crate_name) [ *linker_args, *mkmf_libpath, - *rustc_dynamic_linker_flags(dest_dir), + *rustc_dynamic_linker_flags(dest_dir, crate_name), *rustc_lib_flags(dest_dir), *platform_specific_rustc_args(dest_dir), ] @@ -134,42 +184,70 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder makefile_config("ENABLE_SHARED") == "no" end - # Ruby expects the dylib to follow a file name convention for loading - def rename_cdylib_for_ruby_compatibility(dest_path) - new_path = final_extension_path(dest_path) - FileUtils.cp(cargo_dylib_path(dest_path), new_path) - new_path + def cargo_dylib_path(dest_path, crate_name) + prefix = so_ext == "dll" ? "" : "lib" + path_parts = [dest_path] + path_parts << ENV["CARGO_BUILD_TARGET"] if ENV["CARGO_BUILD_TARGET"] + path_parts += ["release", "#{prefix}#{crate_name}.#{so_ext}"] + File.join(*path_parts) end - def validate_cargo_build!(dir) - dylib_path = cargo_dylib_path(dir) + def cargo_crate_name(cargo_dir, manifest_path, results) + require "open3" + Gem.load_yaml - raise DylibNotFoundError, dir unless File.exist?(dylib_path) + output, status = + begin + Open3.capture2e(cargo, "metadata", "--no-deps", "--format-version", "1", :chdir => cargo_dir) + rescue => error + raise Gem::InstallError, "cargo metadata failed #{error.message}" + end - dylib_path - end + unless status.success? + if Gem.configuration.really_verbose + puts output + else + results << output + end - def final_extension_path(dest_path) - dylib_path = cargo_dylib_path(dest_path) - dlext_name = "#{spec.name}.#{makefile_config("DLEXT")}" - dylib_path.gsub(File.basename(dylib_path), dlext_name) - end + exit_reason = + if status.exited? + ", exit code #{status.exitstatus}" + elsif status.signaled? + ", uncaught signal #{status.termsig}" + end - def cargo_dylib_path(dest_path) - prefix = so_ext == "dll" ? "" : "lib" - path_parts = [dest_path] - path_parts << ENV["CARGO_BUILD_TARGET"] if ENV["CARGO_BUILD_TARGET"] - path_parts += ["release", "#{prefix}#{cargo_crate_name}.#{so_ext}"] - File.join(*path_parts) + raise Gem::InstallError, "cargo metadata failed#{exit_reason}" + end + + # cargo metadata output is specified as json, but with the + # --format-version 1 option the output is compatible with YAML, so we can + # avoid the json dependency + metadata = Gem::SafeYAML.safe_load(output) + package = metadata["packages"].find {|pkg| normalize_path(pkg["manifest_path"]) == manifest_path } + unless package + found = metadata["packages"].map {|md| "#{md["name"]} at #{md["manifest_path"]}" } + raise Gem::InstallError, <<-EOF +failed to determine cargo package name + +looking for: #{manifest_path} + +found: +#{found.join("\n")} +EOF + end + package["name"].tr("-", "_") end - def cargo_crate_name - spec.metadata.fetch("cargo_crate_name", spec.name).tr("-", "_") + def normalize_path(path) + return path unless File::ALT_SEPARATOR + + path.tr(File::ALT_SEPARATOR, File::SEPARATOR) end - def rustc_dynamic_linker_flags(dest_dir) + def rustc_dynamic_linker_flags(dest_dir, crate_name) split_flags("DLDFLAGS") - .map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir) } + .map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir, crate_name) } .compact .flat_map {|arg| ldflag_to_link_modifier(arg) } end @@ -204,7 +282,7 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder end # Interpolate substitution vars in the arg (i.e. $(DEFFILE)) - def maybe_resolve_ldflag_variable(input_arg, dest_dir) + def maybe_resolve_ldflag_variable(input_arg, dest_dir, crate_name) var_matches = input_arg.match(/\$\((\w+)\)/) return input_arg unless var_matches @@ -217,19 +295,19 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder # On windows, it is assumed that mkmf has setup an exports file for the # extension, so we have to to create one ourselves. when "DEFFILE" - write_deffile(dest_dir) + write_deffile(dest_dir, crate_name) else RbConfig::CONFIG[var_name] end end - def write_deffile(dest_dir) - deffile_path = File.join(dest_dir, "#{spec.name}-#{RbConfig::CONFIG["arch"]}.def") + def write_deffile(dest_dir, crate_name) + deffile_path = File.join(dest_dir, "#{crate_name}-#{RbConfig::CONFIG["arch"]}.def") export_prefix = makefile_config("EXPORT_PREFIX") || "" File.open(deffile_path, "w") do |f| f.puts "EXPORTS" - f.puts "#{export_prefix.strip}Init_#{spec.name}" + f.puts "#{export_prefix.strip}Init_#{crate_name}" end deffile_path @@ -264,44 +342,6 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder RbConfig.expand(val.dup) end - # Copied from ExtConfBuilder - def finalize_directory(dest_path, lib_dir, extension_dir) - require "fileutils" - require "tempfile" - - ext_path = final_extension_path(dest_path) - - begin - tmp_dest = Dir.mktmpdir(".gem.", extension_dir) - - # Some versions of `mktmpdir` return absolute paths, which will break make - # if the paths contain spaces. - # - # As such, we convert to a relative path. - tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir) - - full_tmp_dest = File.join(extension_dir, tmp_dest_relative) - - # TODO: remove in RubyGems 4 - if Gem.install_extension_in_lib && lib_dir - FileUtils.mkdir_p lib_dir - FileUtils.cp_r ext_path, lib_dir, remove_destination: true - end - - FileUtils::Entry_.new(full_tmp_dest).traverse do |ent| - destent = ent.class.new(dest_path, ent.rel) - destent.exist? || FileUtils.mv(ent.path, destent.path) - end - ensure - FileUtils.rm_rf tmp_dest if tmp_dest - end - end - - def get_relative_path(path, base) - path[0..base.length - 1] = "." if path.start_with?(base) - path - end - # Error raised when no cdylib artifact was created class DylibNotFoundError < StandardError def initialize(dir) diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index f01a6cd743..6f4d79cdcf 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -466,7 +466,7 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li def validate_rust_extensions(builder) # :nodoc: rust_extension = @specification.extensions.any? {|s| builder.builder_for(s).is_a? Gem::Ext::CargoBuilder } - missing_cargo_lock = !@specification.files.include?("Cargo.lock") + missing_cargo_lock = !@specification.files.any? {|f| f.end_with?("Cargo.lock") } error <<-ERROR if rust_extension && missing_cargo_lock You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec. diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index 61a911621f..72f4cc8e55 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -369,6 +369,7 @@ RSpec.describe "bundle binstubs <gem>" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" + gem "rails" G end @@ -396,6 +397,26 @@ RSpec.describe "bundle binstubs <gem>" do expect(bundled_app("bin/rackup.cmd")).to exist end end + + context "when the gem is bundler" do + it "warns without generating a standalone binstub" do + bundle "binstubs bundler --standalone" + expect(bundled_app("bin/bundle")).not_to exist + expect(bundled_app("bin/bundler")).not_to exist + expect(err).to include("Sorry, Bundler can only be run via RubyGems.") + end + end + + context "when specified --all option" do + it "generates standalone binstubs for all gems except bundler" do + bundle "binstubs --standalone --all" + expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/rails")).to exist + expect(bundled_app("bin/bundle")).not_to exist + expect(bundled_app("bin/bundler")).not_to exist + expect(err).not_to include("Sorry, Bundler can only be run via RubyGems.") + end + end end context "when the bin already exists" do diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index c6947afeae..5ca11dcba2 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -2,11 +2,10 @@ RSpec.describe "bundle exec" do let(:system_gems_to_install) { %w[rack-1.0.0 rack-0.9.1] } - before :each do - system_gems(system_gems_to_install, :path => default_bundle_path) - end it "works with --gemfile flag" do + system_gems(system_gems_to_install, :path => default_bundle_path) + create_file "CustomGemfile", <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "1.0.0" @@ -17,6 +16,8 @@ RSpec.describe "bundle exec" do end it "activates the correct gem" do + system_gems(system_gems_to_install, :path => default_bundle_path) + gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" @@ -27,6 +28,8 @@ RSpec.describe "bundle exec" do end it "works and prints no warnings when HOME is not writable" do + system_gems(system_gems_to_install, :path => default_bundle_path) + gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" @@ -209,8 +212,6 @@ RSpec.describe "bundle exec" do end context "with default gems" do - let(:system_gems_to_install) { [] } - let(:default_irb_version) { ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false } context "when not specified in Gemfile" do @@ -402,6 +403,8 @@ RSpec.describe "bundle exec" do end it "raises a helpful error when exec'ing to something outside of the bundle" do + system_gems(system_gems_to_install, :path => default_bundle_path) + bundle "config set clean false" # want to keep the rackup binstub install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -706,6 +709,8 @@ RSpec.describe "bundle exec" do RUBY before do + system_gems(system_gems_to_install, :path => default_bundle_path) + bundled_app(path).open("w") {|f| f << executable } bundled_app(path).chmod(0o755) diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index c886af0d89..2f82a9f9f3 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -821,6 +821,64 @@ RSpec.describe "bundle install with specific platforms" do bundle :install end + it "automatically fixes the lockfile if the specific platform is locked and we move to a newer ruby version for which a native package is not available" do + # + # Given an existing application using native gems (e.g., nokogiri) + # And a lockfile generated with a stable ruby version + # When want test the application against ruby-head and `bundle install` + # Then bundler should fall back to the generic ruby platform gem + # + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri", "1.14.0" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri (= 1.14.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri (= 1.14.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + private def setup_multiplatform_gem diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index e42cc67ec8..9567d2a3c3 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -230,6 +230,113 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end + it "doesn't reinstall already installed gems" do + system_gems "rack-1.0.0" + + script <<-RUBY + require '#{entrypoint}' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "activesupport" + gem "rack" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing rack") + expect(err).to be_empty + end + + it "installs gems in later gemfile calls" do + system_gems "rack-1.0.0" + + script <<-RUBY + require '#{entrypoint}' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "activesupport" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing rack") + expect(err).to be_empty + end + + it "doesn't reinstall already installed gems in later gemfile calls" do + system_gems "rack-1.0.0" + + script <<-RUBY + require '#{entrypoint}' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "activesupport" + end + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing rack") + expect(err).to be_empty + end + + it "installs gems with native extensions in later gemfile calls" do + system_gems "rack-1.0.0" + + build_git "foo" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("lib", __dir__) + FileUtils.mkdir_p(path) + File.open("\#{path}/foo.rb", "w") do |f| + f.puts "FOO = 'YES'" + end + end + RUBY + end + + script <<-RUBY + require '#{entrypoint}' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + end + + require 'foo' + puts FOO + puts $:.grep(/ext/) + RUBY + + expect(out).to include("YES") + expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s) + expect(err).to be_empty + end + it "installs inline gems when a Gemfile.lock is present" do gemfile <<-G source "https://notaserver.com" diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index f5b86433dc..9bfcbdaed8 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1519,4 +1519,29 @@ end expect(err).to be_empty end end + + it "does not undo the Kernel.require decorations", :rubygems => ">= 3.4.6" do + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + script = bundled_app("bin/script") + create_file(script, <<~RUBY) + module Kernel + module_function + + alias_method :require_before_extra_monkeypatches, :require + + def require(path) + puts "requiring \#{path} used the monkeypatch" + + require_before_extra_monkeypatches(path) + end + end + + require "bundler/setup" + + require "foo" + RUBY + + sys_exec "#{Gem.ruby} #{script}", :raise_on_error => false + expect(out).to include("requiring foo used the monkeypatch") + end end diff --git a/test/rubygems/bundler_test_gem.rb b/test/rubygems/bundler_test_gem.rb new file mode 100644 index 0000000000..16a02f343b --- /dev/null +++ b/test/rubygems/bundler_test_gem.rb @@ -0,0 +1,419 @@ +require_relative "helper" + +class TestBundlerGem < Gem::TestCase + PROJECT_DIR = File.expand_path("../..", __dir__).tap(&Gem::UNTAINT) + + def test_self_use_gemdeps + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("-") do + FileUtils.mkdir_p "detect/a/b" + FileUtils.mkdir_p "detect/a/Isolate" + + FileUtils.touch "detect/Isolate" + + begin + Dir.chdir "detect/a/b" + + Gem.use_gemdeps + + assert_equal add_bundler_full_name([]), loaded_spec_names + ensure + Dir.chdir @tempdir + end + end + end + end + + def test_self_find_files_with_gemfile + with_local_bundler_at(Gem.dir) do + cwd = File.expand_path("test/rubygems", PROJECT_DIR) + actual_load_path = $LOAD_PATH.unshift(cwd).dup + + discover_path = File.join "lib", "sff", "discover.rb" + + foo1, _ = %w[1 2].map do |version| + spec = quick_gem "sff", version do |s| + s.files << discover_path + end + + write_file(File.join "gems", spec.full_name, discover_path) do |fp| + fp.puts "# #{spec.full_name}" + end + + spec + end + Gem.refresh + + write_file(File.join Dir.pwd, "Gemfile") do |fp| + fp.puts "source 'https://rubygems.org'" + fp.puts "gem '#{foo1.name}', '#{foo1.version}'" + end + Gem.use_gemdeps(File.join Dir.pwd, "Gemfile") + + expected = [ + File.expand_path("test/rubygems/sff/discover.rb", PROJECT_DIR), + File.join(foo1.full_gem_path, discover_path), + ].sort + + assert_equal expected, Gem.find_files("sff/discover").sort + assert_equal expected, Gem.find_files("sff/**.rb").sort, "[ruby-core:31730]" + assert_equal cwd, actual_load_path.shift + end + end + + def test_auto_activation_of_specific_gemdeps_file + with_local_bundler_at(Gem.dir) do + a = util_spec "a", "1", nil, "lib/a.rb" + b = util_spec "b", "1", nil, "lib/b.rb" + c = util_spec "c", "1", nil, "lib/c.rb" + + install_specs a, b, c + + path = File.join @tempdir, "gem.deps.rb" + + File.open path, "w" do |f| + f.puts "gem 'a'" + f.puts "gem 'b'" + f.puts "gem 'c'" + end + + with_rubygems_gemdeps(path) do + Gem.use_gemdeps + + assert_equal add_bundler_full_name(%W[a-1 b-1 c-1]), loaded_spec_names + end + end + end + + def test_auto_activation_of_used_gemdeps_file + with_local_bundler_at(Gem.dir) do + a = util_spec "a", "1", nil, "lib/a.rb" + b = util_spec "b", "1", nil, "lib/b.rb" + c = util_spec "c", "1", nil, "lib/c.rb" + + install_specs a, b, c + + path = File.join @tempdir, "gem.deps.rb" + + File.open path, "w" do |f| + f.puts "gem 'a'" + f.puts "gem 'b'" + f.puts "gem 'c'" + end + + with_rubygems_gemdeps("-") do + expected_specs = [a, b, util_spec("bundler", Bundler::VERSION), c].compact.map(&:full_name) + + Gem.use_gemdeps + + assert_equal expected_specs, loaded_spec_names + end + end + end + + def test_looks_for_gemdeps_files_automatically_from_binstubs + path = File.join(@tempdir, "gd-tmp") + + with_local_bundler_at(path) do + a = util_spec "a", "1" do |s| + s.executables = %w[foo] + s.bindir = "exe" + end + + write_file File.join(@tempdir, "exe", "foo") do |fp| + fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort" + end + + b = util_spec "b", "1", nil, "lib/b.rb" + c = util_spec "c", "1", nil, "lib/c.rb" + + install_specs a, b, c + + install_gem a, :install_dir => path + install_gem b, :install_dir => path + install_gem c, :install_dir => path + + ENV["GEM_PATH"] = path + + with_rubygems_gemdeps("-") do + new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) + new_RUBYOPT = "-I#{rubygems_path} -I#{bundler_path}" + + path = File.join @tempdir, "gem.deps.rb" + + File.open path, "w" do |f| + f.puts "gem 'a'" + end + out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", &:read).split(/\n/) + end + + File.open path, "a" do |f| + f.puts "gem 'b'" + f.puts "gem 'c'" + end + out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", &:read).split(/\n/) + end + + assert_equal ["b-1", "c-1"], out - out0 + end + end + end + + def test_looks_for_gemdeps_files_automatically_from_binstubs_in_parent_dir + path = File.join(@tempdir, "gd-tmp") + + with_local_bundler_at(path) do + pend "IO.popen has issues on JRuby when passed :chdir" if Gem.java_platform? + + a = util_spec "a", "1" do |s| + s.executables = %w[foo] + s.bindir = "exe" + end + + write_file File.join(@tempdir, "exe", "foo") do |fp| + fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort" + end + + b = util_spec "b", "1", nil, "lib/b.rb" + c = util_spec "c", "1", nil, "lib/c.rb" + + install_specs a, b, c + + install_gem a, :install_dir => path + install_gem b, :install_dir => path + install_gem c, :install_dir => path + + ENV["GEM_PATH"] = path + + with_rubygems_gemdeps("-") do + Dir.mkdir "sub1" + + new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) + new_RUBYOPT = "-I#{rubygems_path} -I#{bundler_path}" + + path = File.join @tempdir, "gem.deps.rb" + + File.open path, "w" do |f| + f.puts "gem 'a'" + end + out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", :chdir => "sub1", &:read).split(/\n/) + end + + File.open path, "a" do |f| + f.puts "gem 'b'" + f.puts "gem 'c'" + end + out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do + IO.popen("foo", :chdir => "sub1", &:read).split(/\n/) + end + + Dir.rmdir "sub1" + + assert_equal ["b-1", "c-1"], out - out0 + end + end + end + + def test_use_gemdeps + with_local_bundler_at(Gem.dir) do + gem_deps_file = "gem.deps.rb".tap(&Gem::UNTAINT) + spec = util_spec "a", 1 + install_specs spec + + spec = Gem::Specification.find {|s| s == spec } + refute spec.activated? + + File.open gem_deps_file, "w" do |io| + io.write 'gem "a"' + end + + assert_nil Gem.gemdeps + + Gem.use_gemdeps gem_deps_file + + assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names + refute_nil Gem.gemdeps + end + end + + def test_use_gemdeps_ENV + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps(nil) do + spec = util_spec "a", 1 + + refute spec.activated? + + File.open "gem.deps.rb", "w" do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + refute spec.activated? + end + end + end + + def test_use_gemdeps_argument_missing + with_local_bundler_at(Gem.dir) do + e = assert_raise ArgumentError do + Gem.use_gemdeps "gem.deps.rb" + end + + assert_equal "Unable to find gem dependencies file at gem.deps.rb", + e.message + end + end + + def test_use_gemdeps_argument_missing_match_ENV + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("gem.deps.rb") do + e = assert_raise ArgumentError do + Gem.use_gemdeps "gem.deps.rb" + end + + assert_equal "Unable to find gem dependencies file at gem.deps.rb", + e.message + end + end + end + + def test_use_gemdeps_automatic + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("-") do + spec = util_spec "a", 1 + install_specs spec + spec = Gem::Specification.find {|s| s == spec } + + refute spec.activated? + + File.open "Gemfile", "w" do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names + end + end + end + + def test_use_gemdeps_automatic_missing + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("-") do + Gem.use_gemdeps + + assert true # count + end + end + end + + def test_use_gemdeps_disabled + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("") do + spec = util_spec "a", 1 + + refute spec.activated? + + File.open "gem.deps.rb", "w" do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + refute spec.activated? + end + end + end + + def test_use_gemdeps_missing_gem + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("x") do + File.open "x", "w" do |io| + io.write 'gem "a"' + end + + expected = <<-EXPECTED +Could not find gem 'a' in locally installed gems. +You may need to `bundle install` to install missing gems + + EXPECTED + + Gem::Deprecate.skip_during do + actual_stdout, actual_stderr = capture_output do + Gem.use_gemdeps + end + assert_empty actual_stdout + assert_equal(expected, actual_stderr) + end + end + end + end + + def test_use_gemdeps_specific + with_local_bundler_at(Gem.dir) do + with_rubygems_gemdeps("x") do + spec = util_spec "a", 1 + install_specs spec + + spec = Gem::Specification.find {|s| s == spec } + refute spec.activated? + + File.open "x", "w" do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names + end + end + end + + private + + def add_bundler_full_name(names) + names << "bundler-#{Bundler::VERSION}".freeze + names.sort! + names + end + + def with_path_and_rubyopt(path_value, rubyopt_value) + path, ENV["PATH"] = ENV["PATH"], path_value + rubyopt, ENV["RUBYOPT"] = ENV["RUBYOPT"], rubyopt_value + + yield + ensure + ENV["PATH"] = path + ENV["RUBYOPT"] = rubyopt + end + + def with_rubygems_gemdeps(value) + rubygems_gemdeps, ENV["RUBYGEMS_GEMDEPS"] = ENV["RUBYGEMS_GEMDEPS"], value + + yield + ensure + ENV["RUBYGEMS_GEMDEPS"] = rubygems_gemdeps + end + + def with_local_bundler_at(path) + require "bundler" + + # If bundler gemspec exists, pretend it's installed + bundler_gemspec = File.expand_path("../../bundler/bundler.gemspec", __dir__) + if File.exist?(bundler_gemspec) + target_gemspec_location = "#{path}/specifications/bundler-#{Bundler::VERSION}.gemspec" + + FileUtils.mkdir_p File.dirname(target_gemspec_location) + + File.write target_gemspec_location, Gem::Specification.load(bundler_gemspec).to_ruby_for_cache + end + + yield + ensure + Bundler.reset! + end +end diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index ea8aebb785..b572208e6b 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -616,27 +616,6 @@ class TestGem < Gem::TestCase assert_equal %w[https://rubygems.org/], Gem.default_sources end - def test_self_use_gemdeps - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("-") do - FileUtils.mkdir_p "detect/a/b" - FileUtils.mkdir_p "detect/a/Isolate" - - FileUtils.touch "detect/Isolate" - - begin - Dir.chdir "detect/a/b" - - Gem.use_gemdeps - - assert_equal add_bundler_full_name([]), loaded_spec_names - ensure - Dir.chdir @tempdir - end - end - end - end - def test_self_dir assert_equal @gemhome, Gem.dir end @@ -776,43 +755,6 @@ class TestGem < Gem::TestCase assert_equal cwd, $LOAD_PATH.shift end - def test_self_find_files_with_gemfile - with_local_bundler_at(Gem.dir) do - cwd = File.expand_path("test/rubygems", PROJECT_DIR) - actual_load_path = $LOAD_PATH.unshift(cwd).dup - - discover_path = File.join "lib", "sff", "discover.rb" - - foo1, _ = %w[1 2].map do |version| - spec = quick_gem "sff", version do |s| - s.files << discover_path - end - - write_file(File.join "gems", spec.full_name, discover_path) do |fp| - fp.puts "# #{spec.full_name}" - end - - spec - end - Gem.refresh - - write_file(File.join Dir.pwd, "Gemfile") do |fp| - fp.puts "source 'https://rubygems.org'" - fp.puts "gem '#{foo1.name}', '#{foo1.version}'" - end - Gem.use_gemdeps(File.join Dir.pwd, "Gemfile") - - expected = [ - File.expand_path("test/rubygems/sff/discover.rb", PROJECT_DIR), - File.join(foo1.full_gem_path, discover_path), - ].sort - - assert_equal expected, Gem.find_files("sff/discover").sort - assert_equal expected, Gem.find_files("sff/**.rb").sort, "[ruby-core:31730]" - assert_equal cwd, actual_load_path.shift - end - end - def test_self_find_latest_files cwd = File.expand_path("test/rubygems", PROJECT_DIR) $LOAD_PATH.unshift cwd @@ -1640,168 +1582,6 @@ class TestGem < Gem::TestCase "Wrong spec selected" end - def test_auto_activation_of_specific_gemdeps_file - with_local_bundler_at(Gem.dir) do - a = util_spec "a", "1", nil, "lib/a.rb" - b = util_spec "b", "1", nil, "lib/b.rb" - c = util_spec "c", "1", nil, "lib/c.rb" - - install_specs a, b, c - - path = File.join @tempdir, "gem.deps.rb" - - File.open path, "w" do |f| - f.puts "gem 'a'" - f.puts "gem 'b'" - f.puts "gem 'c'" - end - - with_rubygems_gemdeps(path) do - Gem.use_gemdeps - - assert_equal add_bundler_full_name(%W[a-1 b-1 c-1]), loaded_spec_names - end - end - end - - def test_auto_activation_of_used_gemdeps_file - with_local_bundler_at(Gem.dir) do - a = util_spec "a", "1", nil, "lib/a.rb" - b = util_spec "b", "1", nil, "lib/b.rb" - c = util_spec "c", "1", nil, "lib/c.rb" - - install_specs a, b, c - - path = File.join @tempdir, "gem.deps.rb" - - File.open path, "w" do |f| - f.puts "gem 'a'" - f.puts "gem 'b'" - f.puts "gem 'c'" - end - - with_rubygems_gemdeps("-") do - expected_specs = [a, b, util_spec("bundler", Bundler::VERSION), c].compact.map(&:full_name) - - Gem.use_gemdeps - - assert_equal expected_specs, loaded_spec_names - end - end - end - - def add_bundler_full_name(names) - names << "bundler-#{Bundler::VERSION}".freeze - names.sort! - names - end - - def test_looks_for_gemdeps_files_automatically_from_binstubs - path = File.join(@tempdir, "gd-tmp") - - with_local_bundler_at(path) do - a = util_spec "a", "1" do |s| - s.executables = %w[foo] - s.bindir = "exe" - end - - write_file File.join(@tempdir, "exe", "foo") do |fp| - fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort" - end - - b = util_spec "b", "1", nil, "lib/b.rb" - c = util_spec "c", "1", nil, "lib/c.rb" - - install_specs a, b, c - - install_gem a, :install_dir => path - install_gem b, :install_dir => path - install_gem c, :install_dir => path - - ENV["GEM_PATH"] = path - - with_rubygems_gemdeps("-") do - new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - new_RUBYOPT = "-I#{rubygems_path} -I#{bundler_path}" - - path = File.join @tempdir, "gem.deps.rb" - - File.open path, "w" do |f| - f.puts "gem 'a'" - end - out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do - IO.popen("foo", &:read).split(/\n/) - end - - File.open path, "a" do |f| - f.puts "gem 'b'" - f.puts "gem 'c'" - end - out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do - IO.popen("foo", &:read).split(/\n/) - end - - assert_equal ["b-1", "c-1"], out - out0 - end - end - end - - def test_looks_for_gemdeps_files_automatically_from_binstubs_in_parent_dir - path = File.join(@tempdir, "gd-tmp") - - with_local_bundler_at(path) do - pend "IO.popen has issues on JRuby when passed :chdir" if Gem.java_platform? - - a = util_spec "a", "1" do |s| - s.executables = %w[foo] - s.bindir = "exe" - end - - write_file File.join(@tempdir, "exe", "foo") do |fp| - fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort" - end - - b = util_spec "b", "1", nil, "lib/b.rb" - c = util_spec "c", "1", nil, "lib/c.rb" - - install_specs a, b, c - - install_gem a, :install_dir => path - install_gem b, :install_dir => path - install_gem c, :install_dir => path - - ENV["GEM_PATH"] = path - - with_rubygems_gemdeps("-") do - Dir.mkdir "sub1" - - new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - new_RUBYOPT = "-I#{rubygems_path} -I#{bundler_path}" - - path = File.join @tempdir, "gem.deps.rb" - - File.open path, "w" do |f| - f.puts "gem 'a'" - end - out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do - IO.popen("foo", :chdir => "sub1", &:read).split(/\n/) - end - - File.open path, "a" do |f| - f.puts "gem 'b'" - f.puts "gem 'c'" - end - out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do - IO.popen("foo", :chdir => "sub1", &:read).split(/\n/) - end - - Dir.rmdir "sub1" - - assert_equal ["b-1", "c-1"], out - out0 - end - end - end - def test_register_default_spec Gem.clear_default_specs @@ -1842,162 +1622,6 @@ class TestGem < Gem::TestCase assert_equal old_style, Gem.find_unresolved_default_spec("foo.rb") end - def test_use_gemdeps - with_local_bundler_at(Gem.dir) do - gem_deps_file = "gem.deps.rb".tap(&Gem::UNTAINT) - spec = util_spec "a", 1 - install_specs spec - - spec = Gem::Specification.find {|s| s == spec } - refute spec.activated? - - File.open gem_deps_file, "w" do |io| - io.write 'gem "a"' - end - - assert_nil Gem.gemdeps - - Gem.use_gemdeps gem_deps_file - - assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names - refute_nil Gem.gemdeps - end - end - - def test_use_gemdeps_ENV - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps(nil) do - spec = util_spec "a", 1 - - refute spec.activated? - - File.open "gem.deps.rb", "w" do |io| - io.write 'gem "a"' - end - - Gem.use_gemdeps - - refute spec.activated? - end - end - end - - def test_use_gemdeps_argument_missing - with_local_bundler_at(Gem.dir) do - e = assert_raise ArgumentError do - Gem.use_gemdeps "gem.deps.rb" - end - - assert_equal "Unable to find gem dependencies file at gem.deps.rb", - e.message - end - end - - def test_use_gemdeps_argument_missing_match_ENV - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("gem.deps.rb") do - e = assert_raise ArgumentError do - Gem.use_gemdeps "gem.deps.rb" - end - - assert_equal "Unable to find gem dependencies file at gem.deps.rb", - e.message - end - end - end - - def test_use_gemdeps_automatic - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("-") do - spec = util_spec "a", 1 - install_specs spec - spec = Gem::Specification.find {|s| s == spec } - - refute spec.activated? - - File.open "Gemfile", "w" do |io| - io.write 'gem "a"' - end - - Gem.use_gemdeps - - assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names - end - end - end - - def test_use_gemdeps_automatic_missing - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("-") do - Gem.use_gemdeps - - assert true # count - end - end - end - - def test_use_gemdeps_disabled - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("") do - spec = util_spec "a", 1 - - refute spec.activated? - - File.open "gem.deps.rb", "w" do |io| - io.write 'gem "a"' - end - - Gem.use_gemdeps - - refute spec.activated? - end - end - end - - def test_use_gemdeps_missing_gem - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("x") do - File.open "x", "w" do |io| - io.write 'gem "a"' - end - - expected = <<-EXPECTED -Could not find gem 'a' in locally installed gems. -You may need to `bundle install` to install missing gems - - EXPECTED - - Gem::Deprecate.skip_during do - actual_stdout, actual_stderr = capture_output do - Gem.use_gemdeps - end - assert_empty actual_stdout - assert_equal(expected, actual_stderr) - end - end - end - end - - def test_use_gemdeps_specific - with_local_bundler_at(Gem.dir) do - with_rubygems_gemdeps("x") do - spec = util_spec "a", 1 - install_specs spec - - spec = Gem::Specification.find {|s| s == spec } - refute spec.activated? - - File.open "x", "w" do |io| - io.write 'gem "a"' - end - - Gem.use_gemdeps - - assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names - end - end - end - def test_operating_system_defaults operating_system_defaults = Gem.operating_system_defaults @@ -2110,40 +1734,4 @@ You may need to `bundle install` to install missing gems def util_cache_dir File.join Gem.dir, "cache" end - - def with_path_and_rubyopt(path_value, rubyopt_value) - path, ENV["PATH"] = ENV["PATH"], path_value - rubyopt, ENV["RUBYOPT"] = ENV["RUBYOPT"], rubyopt_value - - yield - ensure - ENV["PATH"] = path - ENV["RUBYOPT"] = rubyopt - end - - def with_rubygems_gemdeps(value) - rubygems_gemdeps, ENV["RUBYGEMS_GEMDEPS"] = ENV["RUBYGEMS_GEMDEPS"], value - - yield - ensure - ENV["RUBYGEMS_GEMDEPS"] = rubygems_gemdeps - end - - def with_local_bundler_at(path) - require "bundler" - - # If bundler gemspec exists, pretend it's installed - bundler_gemspec = File.expand_path("../../bundler/bundler.gemspec", __dir__) - if File.exist?(bundler_gemspec) - target_gemspec_location = "#{path}/specifications/bundler-#{Bundler::VERSION}.gemspec" - - FileUtils.mkdir_p File.dirname(target_gemspec_location) - - File.write target_gemspec_location, Gem::Specification.load(bundler_gemspec).to_ruby_for_cache - end - - yield - ensure - Bundler.reset! - end end diff --git a/test/rubygems/test_gem_ext_cargo_builder.rb b/test/rubygems/test_gem_ext_cargo_builder.rb index 9c1ee2ae31..df50150d50 100644 --- a/test/rubygems/test_gem_ext_cargo_builder.rb +++ b/test/rubygems/test_gem_ext_cargo_builder.rb @@ -31,13 +31,12 @@ class TestGemExtCargoBuilder < Gem::TestCase Dir.chdir @ext do ENV.update(@rust_envs) - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) - builder.build nil, @dest_path, output + builder = Gem::Ext::CargoBuilder.new + builder.build "Cargo.toml", @dest_path, output end output = output.join "\n" - bundle = File.join(@dest_path, "release/rust_ruby_example.#{RbConfig::CONFIG['DLEXT']}") + bundle = File.join(@dest_path, "rust_ruby_example.#{RbConfig::CONFIG['DLEXT']}") assert_match(/Finished/, output) assert_match(/release/, output) @@ -58,13 +57,12 @@ class TestGemExtCargoBuilder < Gem::TestCase Dir.chdir @ext do ENV.update(@rust_envs) - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) - builder.build nil, @dest_path, output + builder = Gem::Ext::CargoBuilder.new + builder.build "Cargo.toml", @dest_path, output end output = output.join "\n" - bundle = File.join(@dest_path, "release/rust_ruby_example.#{RbConfig::CONFIG['DLEXT']}") + bundle = File.join(@dest_path, "rust_ruby_example.#{RbConfig::CONFIG['DLEXT']}") assert_ffi_handle bundle, "hello_from_rubygems" assert_ffi_handle bundle, "hello_from_rubygems_version" @@ -79,22 +77,17 @@ class TestGemExtCargoBuilder < Gem::TestCase skip_unsupported_platforms! setup_rust_gem "rust_ruby_example" - output = [] - FileUtils.rm(File.join(@ext, "src/lib.rs")) error = assert_raise(Gem::InstallError) do Dir.chdir @ext do ENV.update(@rust_envs) - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) - builder.build nil, @dest_path, output + builder = Gem::Ext::CargoBuilder.new + builder.build "Cargo.toml", @dest_path, [] end end - output = output.join "\n" - - assert_match "cargo failed", error.message + assert_match /cargo\s.*\sfailed/, error.message end def test_full_integration diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb b/test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb deleted file mode 100644 index 0e04f0de5e..0000000000 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb +++ /dev/null @@ -1,21 +0,0 @@ -if ENV["RUBYOPT"] || defined? Gem - ENV.delete "RUBYOPT" - - require "rbconfig" - cmd = [RbConfig.ruby, "--disable-gems", "build.rb", *ARGV] - - exec(*cmd) -end - -require "tmpdir" - -lp = File.expand_path("../../../../lib", __dir__) -gem = ["ruby", "-I#{lp}", File.expand_path("../../../../bin/gem", __dir__)] -gemspec = File.expand_path("custom_name.gemspec", __dir__) - -Dir.mktmpdir("custom_name") do |dir| - built_gem = File.expand_path(File.join(dir, "custom_name.gem")) - system(*gem, "build", gemspec, "--output", built_gem) - system(*gem, "install", "--verbose", "--local", built_gem, *ARGV) - system %q(ruby -rcustom_name -e "puts 'Result: ' + CustomName.say_hello") -end diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec b/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec index 1f8e270e96..43dddac243 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec @@ -2,9 +2,7 @@ Gem::Specification.new do |s| s.name = "custom_name" s.version = "0.1.0" s.summary = "A Rust extension for Ruby" - s.extensions = ["Cargo.toml"] + s.extensions = ["ext/custom_name_lib/Cargo.toml"] s.authors = ["Ian Ker-Seymer"] - s.files = ["Cargo.toml", "Cargo.lock", "src/lib.rs"] - - s.metadata["cargo_crate_name"] = "custom-name-ext" + s.files = ["lib/custom_name.rb", "ext/custom_name_lib/Cargo.toml", "ext/custom_name_lib/Cargo.lock", "ext/custom_name_lib/src/lib.rs"] end diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index e597d1981e..e597d1981e 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 53f0879012..53f0879012 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/src/lib.rs b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/src/lib.rs index 543ad4a70e..28ba3be564 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/src/lib.rs +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/src/lib.rs @@ -12,7 +12,7 @@ unsafe extern "C" fn say_hello(_klass: VALUE) -> VALUE { #[allow(non_snake_case)] #[no_mangle] -pub extern "C" fn Init_custom_name() { +pub extern "C" fn Init_custom_name_ext() { let name = CString::new("CustomName").unwrap(); let function_name = CString::new("say_hello").unwrap(); // bindgen does not properly detect the arity of the ruby callback function, so we have to transmute diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/lib/custom_name.rb b/test/rubygems/test_gem_ext_cargo_builder/custom_name/lib/custom_name.rb new file mode 100644 index 0000000000..5ae8b00a5f --- /dev/null +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/lib/custom_name.rb @@ -0,0 +1 @@ +require "custom_name_lib/custom_name_ext" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb deleted file mode 100644 index f404aa3468..0000000000 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb +++ /dev/null @@ -1,21 +0,0 @@ -if ENV["RUBYOPT"] || defined? Gem - ENV.delete "RUBYOPT" - - require "rbconfig" - cmd = [RbConfig.ruby, "--disable-gems", "build.rb", *ARGV] - - exec(*cmd) -end - -require "tmpdir" - -lp = File.expand_path("../../../../lib", __dir__) -gem = ["ruby", "-I#{lp}", File.expand_path("../../../../bin/gem", __dir__)] -gemspec = File.expand_path("rust_ruby_example.gemspec", __dir__) - -Dir.mktmpdir("rust_ruby_example") do |dir| - built_gem = File.expand_path(File.join(dir, "rust_ruby_example.gem")) - system(*gem, "build", gemspec, "--output", built_gem) - system(*gem, "install", "--verbose", "--local", built_gem, *ARGV) - system %q(ruby -rrust_ruby_example -e "puts 'Result: ' + RustRubyExample.reverse('hello world')") -end diff --git a/test/rubygems/test_gem_ext_cargo_builder_unit.rb b/test/rubygems/test_gem_ext_cargo_builder_unit.rb index dc9350bb3a..0ca8306842 100644 --- a/test/rubygems/test_gem_ext_cargo_builder_unit.rb +++ b/test/rubygems/test_gem_ext_cargo_builder_unit.rb @@ -6,8 +6,7 @@ require "rubygems/ext" class TestGemExtCargoBuilderUnit < Gem::TestCase def test_cargo_command_passes_args skip_unsupported_platforms! - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) + builder = Gem::Ext::CargoBuilder.new command = builder.cargo_command(Dir.pwd, @tempdir, ["--all-features"]) assert_includes command, "--all-features" @@ -15,8 +14,7 @@ class TestGemExtCargoBuilderUnit < Gem::TestCase def test_cargo_command_locks_in_release_profile skip_unsupported_platforms! - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) + builder = Gem::Ext::CargoBuilder.new builder.profile = :release command = builder.cargo_command(Dir.pwd, @tempdir) @@ -27,8 +25,7 @@ class TestGemExtCargoBuilderUnit < Gem::TestCase skip_unsupported_platforms! old_cargo = ENV["CARGO"] ENV["CARGO"] = "mycargo" - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) + builder = Gem::Ext::CargoBuilder.new command = builder.cargo_command(Dir.pwd, @tempdir) assert_includes command, "mycargo" @@ -38,8 +35,7 @@ class TestGemExtCargoBuilderUnit < Gem::TestCase def test_build_env_includes_rbconfig skip_unsupported_platforms! - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) + builder = Gem::Ext::CargoBuilder.new env = builder.build_env assert_equal env.fetch("RBCONFIG_RUBY_SO_NAME"), RbConfig::CONFIG["RUBY_SO_NAME"] @@ -49,8 +45,7 @@ class TestGemExtCargoBuilderUnit < Gem::TestCase skip_unsupported_platforms! old_cargo = ENV["CARGO_BUILD_TARGET"] ENV["CARGO_BUILD_TARGET"] = "x86_64-unknown-linux-gnu" - spec = Gem::Specification.new "rust_ruby_example", "0.1.0" - builder = Gem::Ext::CargoBuilder.new(spec) + builder = Gem::Ext::CargoBuilder.new command = builder.cargo_command(Dir.pwd, @tempdir, ["--locked"]) assert_includes command, "--target" diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index fc14020890..dd98ddc729 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -54,4 +54,4 @@ DEPENDENCIES webrick (~> 1.6) BUNDLED WITH - 2.4.5 + 2.4.6 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 4ead894c1f..fbb8d91956 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -70,4 +70,4 @@ DEPENDENCIES test-unit BUNDLED WITH - 2.4.5 + 2.4.6 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 350d0ef4ea..54307cd038 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -78,4 +78,4 @@ DEPENDENCIES test-unit BUNDLED WITH - 2.4.5 + 2.4.6 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index a99ba0be60..75880b6761 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -42,4 +42,4 @@ DEPENDENCIES webrick (= 1.7.0) BUNDLED WITH - 2.4.5 + 2.4.6 |