summaryrefslogtreecommitdiff
path: root/lib/bundler/gem_helpers.rb
blob: ad12bf89a4c54725934c7d3d5632efccbfef1eb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# frozen_string_literal: true

module Bundler
  module GemHelpers
    GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant
    GENERICS = [
      Gem::Platform::JAVA,
      *Gem::Platform::WINDOWS,
    ].freeze

    def generic(p)
      GENERIC_CACHE[p] ||= begin
        found = GENERICS.find do |match|
          p === match
        end
        found || Gem::Platform::RUBY
      end
    end
    module_function :generic

    def generic_local_platform
      generic(local_platform)
    end
    module_function :generic_local_platform

    def local_platform
      Bundler.local_platform
    end
    module_function :local_platform

    def generic_local_platform_is_ruby?
      generic_local_platform == Gem::Platform::RUBY
    end
    module_function :generic_local_platform_is_ruby?

    def platform_specificity_match(spec_platform, user_platform)
      spec_platform = Gem::Platform.new(spec_platform)

      PlatformMatch.specificity_score(spec_platform, user_platform)
    end
    module_function :platform_specificity_match

    def select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
      matching = if force_ruby
        specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! }
      else
        specs.select {|spec| spec.match_platform(platform) }
      end

      if prefer_locked
        locked_originally = matching.select {|spec| spec.is_a?(LazySpecification) }
        return locked_originally if locked_originally.any?
      end

      matching
    end
    module_function :select_all_platform_match

    def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
      matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked)

      sort_and_filter_best_platform_match(matching, platform)
    end
    module_function :select_best_platform_match

    def select_best_local_platform_match(specs, force_ruby: false)
      matching = select_all_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation)

      sort_best_platform_match(matching, local_platform)
    end
    module_function :select_best_local_platform_match

    def sort_and_filter_best_platform_match(matching, platform)
      return matching if matching.one?

      exact = matching.select {|spec| spec.platform == platform }
      return exact if exact.any?

      sorted_matching = sort_best_platform_match(matching, platform)
      exemplary_spec = sorted_matching.first

      sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) }
    end
    module_function :sort_and_filter_best_platform_match

    def sort_best_platform_match(matching, platform)
      matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) }
    end
    module_function :sort_best_platform_match

    class PlatformMatch
      def self.specificity_score(spec_platform, user_platform)
        return -1 if spec_platform == user_platform
        return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY

        os_match(spec_platform, user_platform) +
          cpu_match(spec_platform, user_platform) * 10 +
          platform_version_match(spec_platform, user_platform) * 100
      end

      def self.os_match(spec_platform, user_platform)
        if spec_platform.os == user_platform.os
          0
        else
          1
        end
      end

      def self.cpu_match(spec_platform, user_platform)
        if spec_platform.cpu == user_platform.cpu
          0
        elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
          0
        elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
          1
        else
          2
        end
      end

      def self.platform_version_match(spec_platform, user_platform)
        if spec_platform.version == user_platform.version
          0
        elsif spec_platform.version.nil?
          1
        else
          2
        end
      end
    end

    def same_specificity(platform, spec, exemplary_spec)
      platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
    end
    module_function :same_specificity

    def same_deps(spec, exemplary_spec)
      same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort
      same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version
      same_runtime_deps && same_metadata_deps
    end
    module_function :same_deps
  end
end