From 234516b2a06457a37309af3b27425d3b5fc2b420 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 11 Jul 2020 13:38:01 +0200 Subject: [PATCH 1/3] Group attr_accessor in Gem::Platform --- lib/rubygems/platform.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 34306fcf83f9..8c4c8902f402 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -9,11 +9,7 @@ class Gem::Platform @local = nil - attr_accessor :cpu - - attr_accessor :os - - attr_accessor :version + attr_accessor :cpu, :os, :version def self.local arch = RbConfig::CONFIG['arch'] From e030491a095918559cf28f47498e7ed2aacc4b40 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 11 Jul 2020 13:35:41 +0200 Subject: [PATCH 2/3] Pass more information when comparing platforms * Passing the gem specification enables more flexibility when checking whether a gem is compatible with the various aspects of the "current platform" including OS, architecture, RUBY_ENGINE, etc. * Relates to https://github.com/rubygems/rubygems/issues/2945 * Based on https://github.com/oracle/truffleruby/commit/ae83667c220f52cbea4e83c44d2b9f7027688a3a --- bundler/lib/bundler/index.rb | 2 +- lib/rubygems/available_set.rb | 2 +- lib/rubygems/dependency.rb | 2 +- lib/rubygems/name_tuple.rb | 2 +- lib/rubygems/platform.rb | 24 ++++++- .../request_set/gem_dependency_api.rb | 6 +- lib/rubygems/resolver/api_specification.rb | 2 +- lib/rubygems/resolver/specification.rb | 2 +- lib/rubygems/spec_fetcher.rb | 2 +- lib/rubygems/specification.rb | 4 +- test/rubygems/test_gem_platform.rb | 67 +++++++++++++++++-- 11 files changed, 97 insertions(+), 18 deletions(-) diff --git a/bundler/lib/bundler/index.rb b/bundler/lib/bundler/index.rb index 9166a927388f..e1407b4894dd 100644 --- a/bundler/lib/bundler/index.rb +++ b/bundler/lib/bundler/index.rb @@ -195,7 +195,7 @@ def search_by_dependency(dependency, base = nil) if base # allow all platforms when searching from a lockfile dependency.matches_spec?(spec) else - dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform) + dependency.matches_spec?(spec) && Gem::Platform.match_spec?(spec) end end diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb index 80ef29df640a..499483d9e94c 100644 --- a/lib/rubygems/available_set.rb +++ b/lib/rubygems/available_set.rb @@ -73,7 +73,7 @@ def all_specs end def match_platform! - @set.reject! {|t| !Gem::Platform.match(t.spec.platform) } + @set.reject! {|t| !Gem::Platform.match_spec?(t.spec) } @sorted = nil self end diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 8634d71a726e..68f3e3d9915f 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -281,7 +281,7 @@ def matching_specs(platform_only = false) if platform_only matches.reject! do |spec| - spec.nil? || !Gem::Platform.match(spec.platform) + spec.nil? || !Gem::Platform.match_spec?(spec) end end diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb index cb5604e8dd5d..3d0afa30947a 100644 --- a/lib/rubygems/name_tuple.rb +++ b/lib/rubygems/name_tuple.rb @@ -59,7 +59,7 @@ def full_name # Indicate if this NameTuple matches the current platform. def match_platform? - Gem::Platform.match @platform + Gem::Platform.match_gem? @platform, @name end ## diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 8c4c8902f402..8dad2a0d3b7d 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -18,18 +18,38 @@ def self.local end def self.match(platform) - Gem.platforms.any? do |local_platform| + match_platforms?(platform, Gem.platforms) + end + + class << self + extend Gem::Deprecate + rubygems_deprecate :match, "Gem::Platform.match_spec?" + end + + def self.match_platforms?(platform, platforms) + platforms.any? do |local_platform| platform.nil? or local_platform == platform or (local_platform != Gem::Platform::RUBY and local_platform =~ platform) end end + private_class_method :match_platforms? + + def self.match_spec?(spec) + match_gem?(spec.platform, spec.name) + end + + def self.match_gem?(platform, gem_name) + # Note: this method might be redefined by Ruby implementations to + # customize behavior per RUBY_ENGINE, gem_name or other criteria. + match_platforms?(platform, Gem.platforms) + end def self.installable?(spec) if spec.respond_to? :installable_platform? spec.installable_platform? else - match spec.platform + match_spec? spec end end diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index 9fbe3a1e44d6..fa152d3d9f6c 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -379,7 +379,7 @@ def gem(name, *requirements) Gem::Requirement.create requirements end - return unless gem_platforms options + return unless gem_platforms name, options groups = gem_group name, options @@ -532,7 +532,7 @@ def gem_source(name, options) # :nodoc: # Handles the platforms: option from +options+. Returns true if the # platform matches the current platform. - def gem_platforms(options) # :nodoc: + def gem_platforms(name, options) # :nodoc: platform_names = Array(options.delete :platform) platform_names.concat Array(options.delete :platforms) platform_names.concat @current_platforms if @current_platforms @@ -543,7 +543,7 @@ def gem_platforms(options) # :nodoc: raise ArgumentError, "unknown platform #{platform_name.inspect}" unless platform = PLATFORM_MAP[platform_name] - next false unless Gem::Platform.match platform + next false unless Gem::Platform.match_gem? platform, name if engines = ENGINE_MAP[platform_name] next false unless engines.include? Gem.ruby_engine diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb index a47d91033130..13ee0b15b956 100644 --- a/lib/rubygems/resolver/api_specification.rb +++ b/lib/rubygems/resolver/api_specification.rb @@ -42,7 +42,7 @@ def fetch_development_dependencies # :nodoc: end def installable_platform? # :nodoc: - Gem::Platform.match @platform + Gem::Platform.match_gem? @platform, @name end def pretty_print(q) # :nodoc: diff --git a/lib/rubygems/resolver/specification.rb b/lib/rubygems/resolver/specification.rb index 7fe2afd3bdf1..5ae5f1581363 100644 --- a/lib/rubygems/resolver/specification.rb +++ b/lib/rubygems/resolver/specification.rb @@ -104,7 +104,7 @@ def download(options) # Returns true if this specification is installable on this platform. def installable_platform? - Gem::Platform.match spec.platform + Gem::Platform.match_spec? spec end def local? # :nodoc: diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index f748b090ccd4..b2bcadc49cd9 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -98,7 +98,7 @@ def search_for_dependency(dependency, matching_platform=true) found[source] = specs.select do |tup| if dependency.match?(tup) - if matching_platform and !Gem::Platform.match(tup.platform) + if matching_platform and !Gem::Platform.match_gem?(tup.platform, tup.name) pm = ( rejected_specs[dependency] ||= \ Gem::PlatformMismatch.new(tup.name, tup.version)) diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 883cad35f9fd..254316787e58 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -804,7 +804,7 @@ def self.stubs stubs = stubs.uniq {|stub| stub.full_name } _resort!(stubs) - @@stubs_by_name = stubs.select {|s| Gem::Platform.match s.platform }.group_by(&:name) + @@stubs_by_name = stubs.select {|s| Gem::Platform.match_spec? s }.group_by(&:name) stubs end end @@ -831,7 +831,7 @@ def self.stubs_for(name) @@stubs_by_name[name] else pattern = "#{name}-*.gemspec" - stubs = installed_stubs(dirs, pattern).select {|s| Gem::Platform.match s.platform } + default_stubs(pattern) + stubs = installed_stubs(dirs, pattern).select {|s| Gem::Platform.match_spec? s } + default_stubs(pattern) stubs = stubs.uniq {|stub| stub.full_name }.group_by(&:name) stubs.each_value {|v| _resort!(v) } diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb index 030033d2af7b..83a6f24de431 100644 --- a/test/rubygems/test_gem_platform.rb +++ b/test/rubygems/test_gem_platform.rb @@ -11,10 +11,69 @@ def test_self_local end def test_self_match - assert Gem::Platform.match(nil), 'nil == ruby' - assert Gem::Platform.match(Gem::Platform.local), 'exact match' - assert Gem::Platform.match(Gem::Platform.local.to_s), '=~ match' - assert Gem::Platform.match(Gem::Platform::RUBY), 'ruby' + Gem::Deprecate.skip_during do + assert Gem::Platform.match(nil), 'nil == ruby' + assert Gem::Platform.match(Gem::Platform.local), 'exact match' + assert Gem::Platform.match(Gem::Platform.local.to_s), '=~ match' + assert Gem::Platform.match(Gem::Platform::RUBY), 'ruby' + end + end + + def test_self_match_gem? + assert Gem::Platform.match_gem?(nil, 'json'), 'nil == ruby' + assert Gem::Platform.match_gem?(Gem::Platform.local, 'json'), 'exact match' + assert Gem::Platform.match_gem?(Gem::Platform.local.to_s, 'json'), '=~ match' + assert Gem::Platform.match_gem?(Gem::Platform::RUBY, 'json'), 'ruby' + end + + def test_self_match_spec? + make_spec = -> platform do + util_spec 'mygem-for-platform-match_spec', '1' do |s| + s.platform = platform + end + end + + assert Gem::Platform.match_spec?(make_spec.call(nil)), 'nil == ruby' + assert Gem::Platform.match_spec?(make_spec.call(Gem::Platform.local)), 'exact match' + assert Gem::Platform.match_spec?(make_spec.call(Gem::Platform.local.to_s)), '=~ match' + assert Gem::Platform.match_spec?(make_spec.call(Gem::Platform::RUBY)), 'ruby' + end + + def test_self_match_spec_with_match_gem_override + make_spec = -> name, platform do + util_spec name, '1' do |s| + s.platform = platform + end + end + + class << Gem::Platform + alias_method :original_match_gem?, :match_gem? + def match_gem?(platform, gem_name) + # e.g., sassc and libv8 are such gems, their native extensions do not use the Ruby C API + if gem_name == 'gem-with-ruby-impl-independent-precompiled-ext' + match_platforms?(platform, [Gem::Platform::RUBY, Gem::Platform.local]) + else + match_platforms?(platform, Gem.platforms) + end + end + end + + platforms = Gem.platforms + Gem.platforms = [Gem::Platform::RUBY] + begin + assert_equal true, Gem::Platform.match_spec?(make_spec.call('mygem', Gem::Platform::RUBY)) + assert_equal false, Gem::Platform.match_spec?(make_spec.call('mygem', Gem::Platform.local)) + + name = 'gem-with-ruby-impl-independent-precompiled-ext' + assert_equal true, Gem::Platform.match_spec?(make_spec.call(name, Gem::Platform.local)) + ensure + Gem.platforms = platforms + class << Gem::Platform + remove_method :match_gem? + alias_method :match_gem?, :original_match_gem? # rubocop:disable Lint/DuplicateMethods + remove_method :original_match_gem? + end + end end def test_self_new From b9b0758b1f818c0001aa5a58f7741f0750a5f55b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 11 Jul 2020 14:43:47 +0200 Subject: [PATCH 3/3] Only use Gem::Platform.match_spec? if available in Bundler * So it also works with older versions of RubyGems. --- bundler/lib/bundler/index.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bundler/lib/bundler/index.rb b/bundler/lib/bundler/index.rb index e1407b4894dd..3444ae15c4f6 100644 --- a/bundler/lib/bundler/index.rb +++ b/bundler/lib/bundler/index.rb @@ -195,7 +195,11 @@ def search_by_dependency(dependency, base = nil) if base # allow all platforms when searching from a lockfile dependency.matches_spec?(spec) else - dependency.matches_spec?(spec) && Gem::Platform.match_spec?(spec) + if Gem::Platform.respond_to? :match_spec? + dependency.matches_spec?(spec) && Gem::Platform.match_spec?(spec) + else + dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform) + end end end