summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/rubygems.rb24
-rw-r--r--lib/rubygems/ext/builder.rb10
-rw-r--r--lib/rubygems/ext/cargo_builder.rb7
-rw-r--r--lib/rubygems/ext/cmake_builder.rb9
-rw-r--r--lib/rubygems/ext/configure_builder.rb9
-rw-r--r--lib/rubygems/ext/ext_conf_builder.rb8
-rw-r--r--lib/rubygems/ext/rake_builder.rb7
-rw-r--r--lib/rubygems/install_update_options.rb5
-rw-r--r--lib/rubygems/installer.rb4
-rw-r--r--lib/rubygems/platform.rb7
-rw-r--r--lib/rubygems/target_rbconfig.rb50
-rw-r--r--test/rubygems/test_gem_ext_builder.rb59
12 files changed, 179 insertions, 20 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index ac225ca70a..ba7cf273b9 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -18,6 +18,7 @@ require_relative "rubygems/compatibility"
require_relative "rubygems/defaults"
require_relative "rubygems/deprecate"
require_relative "rubygems/errors"
+require_relative "rubygems/target_rbconfig"
##
# RubyGems is the Ruby standard for publishing and managing third party
@@ -179,6 +180,8 @@ module Gem
@discover_gems_on_require = true
+ @target_rbconfig = nil
+
##
# Try to activate a gem containing +path+. Returns true if
# activation succeeded or wasn't needed because it was already
@@ -397,6 +400,23 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
end
##
+ # The RbConfig object for the deployment target platform.
+ #
+ # This is usually the same as the running platform, but may be
+ # different if you are cross-compiling.
+
+ def self.target_rbconfig
+ @target_rbconfig || Gem::TargetRbConfig.for_running_ruby
+ end
+
+ def self.set_target_rbconfig(rbconfig_path)
+ @target_rbconfig = Gem::TargetRbConfig.from_path(rbconfig_path)
+ Gem::Platform.local(refresh: true)
+ Gem.platforms << Gem::Platform.local unless Gem.platforms.include? Gem::Platform.local
+ @target_rbconfig
+ end
+
+ ##
# Quietly ensure the Gem directory +dir+ contains all the proper
# subdirectories. If we can't create a directory due to a permission
# problem, then we will silently continue.
@@ -450,7 +470,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# distinction as extensions cannot be shared between the two.
def self.extension_api_version # :nodoc:
- if RbConfig::CONFIG["ENABLE_SHARED"] == "no"
+ if target_rbconfig["ENABLE_SHARED"] == "no"
"#{ruby_api_version}-static"
else
ruby_api_version
@@ -810,7 +830,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# Returns a String containing the API compatibility version of Ruby
def self.ruby_api_version
- @ruby_api_version ||= RbConfig::CONFIG["ruby_version"].dup
+ @ruby_api_version ||= target_rbconfig["ruby_version"].dup
end
def self.env_requirement(gem_name)
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index be1ba3031c..12eb62ef16 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -19,13 +19,14 @@ class Gem::Ext::Builder
$1.downcase
end
- def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"])
+ def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"],
+ target_rbconfig: Gem.target_rbconfig)
unless File.exist? File.join(make_dir, "Makefile")
raise Gem::InstallError, "Makefile not found"
end
# try to find make program from Ruby configure arguments first
- RbConfig::CONFIG["configure_args"] =~ /with-make-prog\=(\w+)/
+ target_rbconfig["configure_args"] =~ /with-make-prog\=(\w+)/
make_program_name = ENV["MAKE"] || ENV["make"] || $1
make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make"
make_program = Shellwords.split(make_program_name)
@@ -131,10 +132,11 @@ class Gem::Ext::Builder
# have build arguments, saved, set +build_args+ which is an ARGV-style
# array.
- def initialize(spec, build_args = spec.build_args)
+ def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig)
@spec = spec
@build_args = build_args
@gem_dir = spec.full_gem_path
+ @target_rbconfig = target_rbconfig
@ran_rake = false
end
@@ -191,7 +193,7 @@ EOF
FileUtils.mkdir_p dest_path
results = builder.build(extension, dest_path,
- results, @build_args, lib_dir, extension_dir)
+ results, @build_args, lib_dir, extension_dir, @target_rbconfig)
verbose { results.join("\n") }
diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb
index 09ad1407c2..81b28c3c77 100644
--- a/lib/rubygems/ext/cargo_builder.rb
+++ b/lib/rubygems/ext/cargo_builder.rb
@@ -16,10 +16,15 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder
@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,
+ target_rbconfig=Gem.target_rbconfig)
require "tempfile"
require "fileutils"
+ if target_rbconfig.path
+ warn "--target-rbconfig is not yet supported for Rust extensions. Ignoring"
+ 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
diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb
index b162664784..34564f668d 100644
--- a/lib/rubygems/ext/cmake_builder.rb
+++ b/lib/rubygems/ext/cmake_builder.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd)
+ def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd,
+ target_rbconfig=Gem.target_rbconfig)
+ if target_rbconfig.path
+ warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring"
+ end
+
unless File.exist?(File.join(cmake_dir, "Makefile"))
require_relative "../command"
cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args]
@@ -9,7 +14,7 @@ class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
run cmd, results, class_name, cmake_dir
end
- make dest_path, results, cmake_dir
+ make dest_path, results, cmake_dir, target_rbconfig: target_rbconfig
results
end
diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb
index 6b8590ba2e..d91b1ec5e8 100644
--- a/lib/rubygems/ext/configure_builder.rb
+++ b/lib/rubygems/ext/configure_builder.rb
@@ -7,14 +7,19 @@
#++
class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd)
+ def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd,
+ target_rbconfig=Gem.target_rbconfig)
+ if target_rbconfig.path
+ warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring"
+ end
+
unless File.exist?(File.join(configure_dir, "Makefile"))
cmd = ["sh", "./configure", "--prefix=#{dest_path}", *args]
run cmd, results, class_name, configure_dir
end
- make dest_path, results, configure_dir
+ make dest_path, results, configure_dir, target_rbconfig: target_rbconfig
results
end
diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb
index fb68a7a8cc..6645c56773 100644
--- a/lib/rubygems/ext/ext_conf_builder.rb
+++ b/lib/rubygems/ext/ext_conf_builder.rb
@@ -7,7 +7,8 @@
#++
class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd)
+ def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd,
+ target_rbconfig=Gem.target_rbconfig)
require "fileutils"
require "tempfile"
@@ -23,6 +24,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
begin
cmd = ruby << File.basename(extension)
+ cmd << "--target-rbconfig=#{target_rbconfig.path}" if target_rbconfig.path
cmd.push(*args)
run(cmd, results, class_name, extension_dir) do |s, r|
@@ -39,7 +41,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
ENV["DESTDIR"] = nil
- make dest_path, results, extension_dir, tmp_dest_relative
+ make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig
full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
@@ -55,7 +57,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
destent.exist? || FileUtils.mv(ent.path, destent.path)
end
- make dest_path, results, extension_dir, tmp_dest_relative, ["clean"]
+ make dest_path, results, extension_dir, tmp_dest_relative, ["clean"], target_rbconfig: target_rbconfig
ensure
ENV["DESTDIR"] = destdir
end
diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb
index 0171807b39..42393a4a06 100644
--- a/lib/rubygems/ext/rake_builder.rb
+++ b/lib/rubygems/ext/rake_builder.rb
@@ -9,7 +9,12 @@ require_relative "../shellwords"
#++
class Gem::Ext::RakeBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd)
+ def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd,
+ target_rbconfig=Gem.target_rbconfig)
+ if target_rbconfig.path
+ warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring"
+ end
+
if /mkrf_conf/i.match?(File.basename(extension))
run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir)
end
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index aad207a718..0d0f0dc211 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -179,6 +179,11 @@ module Gem::InstallUpdateOptions
"Suggest alternates when gems are not found") do |v,_o|
options[:suggest_alternate] = v
end
+
+ add_option(:"Install/Update", "--target-rbconfig [FILE]",
+ "rbconfig.rb for the deployment target platform") do |v, _o|
+ Gem.set_target_rbconfig(v)
+ end
end
##
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 844f292ba2..dd346cda4e 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -847,7 +847,7 @@ TEXT
# configure scripts and rakefiles or mkrf_conf files.
def build_extensions
- builder = Gem::Ext::Builder.new spec, build_args
+ builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig
builder.build_extensions
end
@@ -993,7 +993,7 @@ TEXT
end
def rb_config
- RbConfig::CONFIG
+ Gem.target_rbconfig
end
def ruby_install_name
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index d54ad12880..9ef61ba218 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -12,9 +12,10 @@ class Gem::Platform
attr_accessor :cpu, :os, :version
- def self.local
- @local ||= begin
- arch = RbConfig::CONFIG["arch"]
+ def self.local(refresh: false)
+ return @local if @local && !refresh
+ @local = begin
+ arch = Gem.target_rbconfig["arch"]
arch = "#{arch}_60" if /mswin(?:32|64)$/.match?(arch)
new(arch)
end
diff --git a/lib/rubygems/target_rbconfig.rb b/lib/rubygems/target_rbconfig.rb
new file mode 100644
index 0000000000..21d90ee9db
--- /dev/null
+++ b/lib/rubygems/target_rbconfig.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+##
+# A TargetConfig is a wrapper around an RbConfig object that provides a
+# consistent interface for querying configuration for *deployment target
+# platform*, where the gem being installed is intended to run on.
+#
+# The TargetConfig is typically created from the RbConfig of the running Ruby
+# process, but can also be created from an RbConfig file on disk for cross-
+# compiling gems.
+
+class Gem::TargetRbConfig
+ attr_reader :path
+
+ def initialize(rbconfig, path)
+ @rbconfig = rbconfig
+ @path = path
+ end
+
+ ##
+ # Creates a TargetRbConfig for the platform that RubyGems is running on.
+
+ def self.for_running_ruby
+ new(::RbConfig, nil)
+ end
+
+ ##
+ # Creates a TargetRbConfig from the RbConfig file at the given path.
+ # Typically used for cross-compiling gems.
+
+ def self.from_path(rbconfig_path)
+ namespace = Module.new do |m|
+ # Load the rbconfig.rb file within a new anonymous module to avoid
+ # conflicts with the rbconfig for the running platform.
+ Kernel.load rbconfig_path, m
+ end
+ rbconfig = namespace.const_get(:RbConfig)
+
+ new(rbconfig, rbconfig_path)
+ end
+
+ ##
+ # Queries the configuration for the given key.
+
+ def [](key)
+ @rbconfig::CONFIG[key]
+ end
+end
diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb
index d5812da2aa..9907ec5f21 100644
--- a/test/rubygems/test_gem_ext_builder.rb
+++ b/test/rubygems/test_gem_ext_builder.rb
@@ -310,6 +310,65 @@ install:
assert_path_exist @spec.extension_dir
end
+ def test_build_extensions_with_target_rbconfig
+ fake_rbconfig = File.join @tempdir, "fake_rbconfig.rb"
+ File.open fake_rbconfig, "w" do |f|
+ f.write <<~RUBY
+ module RbConfig
+ CONFIG = {}
+ MAKEFILE_CONFIG = {}
+
+ def self.fire_update!(key, value); end
+ def self.expand(val, config = CONFIG); val; end
+ end
+ RUBY
+ RbConfig::CONFIG.each do |k, v|
+ f.puts %(RbConfig::CONFIG["#{k}"] = "#{v}")
+ end
+ RbConfig::MAKEFILE_CONFIG.each do |k, v|
+ f.puts %(RbConfig::MAKEFILE_CONFIG["#{k}"] = "#{v}")
+ end
+ f.puts "RbConfig::CONFIG['host_os'] = 'fake_os'"
+ f.puts "RbConfig::CONFIG['arch'] = 'fake_arch'"
+ end
+
+ system(Gem.ruby, "-rmkmf", "-e", "exit MakeMakefile::RbConfig::CONFIG['host_os'] == 'fake_os'",
+ "--", "--target-rbconfig=#{fake_rbconfig}")
+ pend "This version of mkmf does not support --target-rbconfig" unless $?.success?
+
+ @spec.extensions << "extconf.rb"
+ @builder = Gem::Ext::Builder.new @spec, "", Gem::TargetRbConfig.from_path(fake_rbconfig)
+
+ FileUtils.mkdir_p @spec.gem_dir
+
+ File.open File.join(@spec.gem_dir, "extconf.rb"), "w" do |f|
+ f.write <<-'RUBY'
+ require 'mkmf'
+
+ extconf_args = File.join __dir__, 'rbconfig_dump'
+ File.open extconf_args, 'w' do |f|
+ ["host_os", "arch"].each do |k|
+ f.puts "#{k}=#{MakeMakefile::RbConfig::CONFIG[k]}"
+ end
+ end
+
+ create_makefile 'a'
+ RUBY
+ end
+
+ use_ui @ui do
+ @builder.build_extensions
+ end
+
+ path = File.join @spec.gem_dir, "rbconfig_dump"
+
+ assert_equal <<~DUMP, File.read(path)
+ host_os=fake_os
+ arch=fake_arch
+ DUMP
+ assert_path_exist @spec.extension_dir
+ end
+
def test_initialize
build_info_dir = File.join @gemhome, "build_info"