diff options
-rw-r--r-- | lib/rubygems.rb | 24 | ||||
-rw-r--r-- | lib/rubygems/ext/builder.rb | 10 | ||||
-rw-r--r-- | lib/rubygems/ext/cargo_builder.rb | 7 | ||||
-rw-r--r-- | lib/rubygems/ext/cmake_builder.rb | 9 | ||||
-rw-r--r-- | lib/rubygems/ext/configure_builder.rb | 9 | ||||
-rw-r--r-- | lib/rubygems/ext/ext_conf_builder.rb | 8 | ||||
-rw-r--r-- | lib/rubygems/ext/rake_builder.rb | 7 | ||||
-rw-r--r-- | lib/rubygems/install_update_options.rb | 5 | ||||
-rw-r--r-- | lib/rubygems/installer.rb | 4 | ||||
-rw-r--r-- | lib/rubygems/platform.rb | 7 | ||||
-rw-r--r-- | lib/rubygems/target_rbconfig.rb | 50 | ||||
-rw-r--r-- | test/rubygems/test_gem_ext_builder.rb | 59 |
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" |