diff --git a/Manifest.txt b/Manifest.txt index ead5a06ffa3c..aee8e7db68bc 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -448,6 +448,7 @@ lib/rubygems/package/tar_writer.rb lib/rubygems/package_task.rb lib/rubygems/path_support.rb lib/rubygems/platform.rb +lib/rubygems/platform/string_parser.rb lib/rubygems/psych_tree.rb lib/rubygems/query_utils.rb lib/rubygems/rdoc.rb diff --git a/bundler/lib/bundler/installer/standalone.rb b/bundler/lib/bundler/installer/standalone.rb index 2cdada44d68c..753c43d1fbdc 100644 --- a/bundler/lib/bundler/installer/standalone.rb +++ b/bundler/lib/bundler/installer/standalone.rb @@ -15,6 +15,7 @@ def generate file.puts prevent_gem_activation file.puts define_path_helpers file.puts reverse_rubygems_kernel_mixin + file.puts platform_parser paths.each do |path| if Pathname.new(path).absolute? file.puts %($:.unshift "#{path}") @@ -33,7 +34,7 @@ def paths Array(spec.require_paths).map do |path| gem_path(path, spec). sub(version_dir, '#{RUBY_ENGINE}/#{Gem.ruby_api_version}'). - sub(extensions_dir, 'extensions/\k/#{Gem.extension_api_version}') + sub(extensions_dir, new_extensions_dir) # This is a static string intentionally. It's interpolated at a later time. end end.flatten.compact @@ -112,5 +113,39 @@ def reverse_rubygems_kernel_mixin end END end + + def platform_parser_path + "#{RbConfig::CONFIG["rubylibdir"]}/rubygems/platform/string_parser.rb" + end + + def support_multiplatform_setup_script? + File.exist?(platform_parser_path) + end + + def new_extensions_dir + if support_multiplatform_setup_script? + 'extensions/#{local_platform}/#{Gem.extension_api_version}' + else + 'extensions/\k/#{Gem.extension_api_version}' + end + end + + def platform_parser + if support_multiplatform_setup_script? + parser_code = File.read(platform_parser_path).gsub("# frozen_string_literal: true\n\n", "") + <<~END + unless defined?(Gem::Platform::StringParser) + #{parser_code.chomp} + end + def local_platform + force_mswin_version = true + Gem::Platform::StringParser + .run(RbConfig::CONFIG["arch"], force_mswin_version) + .compact + .join "-" + end + END + end + end end end diff --git a/bundler/spec/install/gems/standalone_spec.rb b/bundler/spec/install/gems/standalone_spec.rb index 4929c4533796..f63cfc50d385 100644 --- a/bundler/spec/install/gems/standalone_spec.rb +++ b/bundler/spec/install/gems/standalone_spec.rb @@ -253,15 +253,56 @@ G end - it "generates a bundle/bundler/setup.rb with the proper paths" do - expected_path = bundled_app("bundle/bundler/setup.rb") - script_content = File.read(expected_path) - expect(script_content).to include("def self.ruby_api_version") - expect(script_content).to include("def self.extension_api_version") - extension_line = script_content.each_line.find {|line| line.include? "/extensions/" }.strip - platform = Gem::Platform.local - expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/' - expect(extension_line).to end_with platform.to_s + '/#{Gem.extension_api_version}/very_simple_binary-1.0")' + context "when multiplatform setup.rb isn't supported" do + before do + if File.exist?("#{RbConfig::CONFIG["rubylibdir"]}/rubygems/platform/string_parser.rb") + skip "multiplatform setup.rb is supported" + end + end + + it "generates a bundle/bundler/setup.rb with the necessary path helpers" do + expected_path = bundled_app("bundle/bundler/setup.rb") + script_content = File.read(expected_path) + + expect(script_content).to include("def self.ruby_api_version") + expect(script_content).to include("def self.extension_api_version") + expect(script_content).to_not include("def local_platform") + end + + it "generates a bundle/bundler/setup.rb with the proper paths" do + expected_path = bundled_app("bundle/bundler/setup.rb") + script_content = File.read(expected_path) + extension_line = script_content.each_line.find {|line| line.include? "/extensions/" }.strip + platform = Gem::Platform.local + + expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/' + expect(extension_line).to end_with platform.to_s + '/#{Gem.extension_api_version}/very_simple_binary-1.0")' + end + end + + context "when multiplatform setup.rb is supported" do + before do + unless File.exist?("#{RbConfig::CONFIG["rubylibdir"]}/rubygems/platform/string_parser.rb") + skip "multiplatform setup.rb isn't supported" + end + end + + it "generates a bundle/bundler/setup.rb with the local_platform helper" do + expected_path = bundled_app("bundle/bundler/setup.rb") + script_content = File.read(expected_path) + expect(script_content).to include("def self.ruby_api_version") + expect(script_content).to include("def self.extension_api_version") + expect(script_content).to include("def local_platform") + end + + it "generates a bundle/bundler/setup.rb that is multiplatform" do + expected_path = bundled_app("bundle/bundler/setup.rb") + script_content = File.read(expected_path) + extension_line = script_content.each_line.find {|line| line.include? "/extensions/" }.strip + + expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/' + expect(extension_line).to end_with '#{local_platform}/#{Gem.extension_api_version}/very_simple_binary-1.0")' + end end end diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 5c5abdc2e2bd..a3676520a9e9 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -14,9 +14,8 @@ class Gem::Platform def self.local @local ||= begin - arch = RbConfig::CONFIG["arch"] - arch = "#{arch}_60" if /mswin(?:32|64)$/.match?(arch) - new(arch) + force_mswin_version = true + new(StringParser.run(RbConfig::CONFIG["arch"], force_mswin_version)) end end @@ -77,57 +76,8 @@ def initialize(arch) when Array then @cpu, @os, @version = arch when String then - arch = arch.split "-" - - if arch.length > 2 && arch.last !~ /\d+(\.\d+)?$/ # reassemble x86-linux-{libc} - extra = arch.pop - arch.last << "-#{extra}" - end - - cpu = arch.shift - - @cpu = case cpu - when /i\d86/ then "x86" - else cpu - end - - if arch.length == 2 && arch.last =~ /^\d+(\.\d+)?$/ # for command-line - @os, @version = arch - return - end - - os, = arch - if os.nil? - @cpu = nil - os = cpu - end # legacy jruby - - @os, @version = case os - when /aix(\d+)?/ then ["aix", $1] - when /cygwin/ then ["cygwin", nil] - when /darwin(\d+)?/ then ["darwin", $1] - when /^macruby$/ then ["macruby", nil] - when /freebsd(\d+)?/ then ["freebsd", $1] - when /^java$/, /^jruby$/ then ["java", nil] - when /^java([\d.]*)/ then ["java", $1] - when /^dalvik(\d+)?$/ then ["dalvik", $1] - when /^dotnet$/ then ["dotnet", nil] - when /^dotnet([\d.]*)/ then ["dotnet", $1] - when /linux-?(\w+)?/ then ["linux", $1] - when /mingw32/ then ["mingw32", nil] - when /mingw-?(\w+)?/ then ["mingw", $1] - when /(mswin\d+)(\_(\d+))?/ then - os = $1 - version = $3 - @cpu = "x86" if @cpu.nil? && os =~ /32$/ - [os, version] - when /netbsdelf/ then ["netbsdelf", nil] - when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] - when /solaris(\d+\.\d+)?/ then ["solaris", $1] - # test - when /^(\w+_platform)(\d+)?/ then [$1, $2] - else ["unknown", nil] - end + # The string parser has its own file because we need to reuse it in Bundler::Standalone. + @cpu, @os, @version = StringParser.run(arch) when Gem::Platform then @cpu = arch.cpu @os = arch.os @@ -255,3 +205,5 @@ def =~(other) CURRENT = "current" end + +require_relative "platform/string_parser" diff --git a/lib/rubygems/platform/string_parser.rb b/lib/rubygems/platform/string_parser.rb new file mode 100644 index 000000000000..05475b30cf46 --- /dev/null +++ b/lib/rubygems/platform/string_parser.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Gem::Platform::StringParser + def self.run(arch, force_mswin_version = false) + arch = "#{arch}_60" if /mswin(?:32|64)$/.match?(arch) && force_mswin_version + arch = arch.split "-" + + if arch.length > 2 && arch.last !~ /\d+(\.\d+)?$/ # reassemble x86-linux-{libc} + extra = arch.pop + arch.last << "-#{extra}" + end + + cpu = arch.shift + + parsed_cpu = case cpu + when /i\d86/ then "x86" + else cpu + end + + if arch.length == 2 && arch.last =~ /^\d+(\.\d+)?$/ # for command-line + parsed_os, parsed_version = arch + return [parsed_cpu, parsed_os, parsed_version] + end + + os, = arch + if os.nil? + parsed_cpu = nil + os = cpu + end # legacy jruby + + parsed_os, parsed_version = case os + when /aix(\d+)?/ then ["aix", $1] + when /cygwin/ then ["cygwin", nil] + when /darwin(\d+)?/ then ["darwin", $1] + when /^macruby$/ then ["macruby", nil] + when /freebsd(\d+)?/ then ["freebsd", $1] + when /^java$/, /^jruby$/ then ["java", nil] + when /^java([\d.]*)/ then ["java", $1] + when /^dalvik(\d+)?$/ then ["dalvik", $1] + when /^dotnet$/ then ["dotnet", nil] + when /^dotnet([\d.]*)/ then ["dotnet", $1] + when /linux-?(\w+)?/ then ["linux", $1] + when /mingw32/ then ["mingw32", nil] + when /mingw-?(\w+)?/ then ["mingw", $1] + when /(mswin\d+)(\_(\d+))?/ then + os = $1 + version = $3 + parsed_cpu = "x86" if parsed_cpu.nil? && os =~ /32$/ + [os, version] + when /netbsdelf/ then ["netbsdelf", nil] + when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] + when /solaris(\d+\.\d+)?/ then ["solaris", $1] + # test + when /^(\w+_platform)(\d+)?/ then [$1, $2] + else ["unknown", nil] + end + + [parsed_cpu, parsed_os, parsed_version] + end +end