summaryrefslogtreecommitdiff
path: root/spec/bundler/support/rubygems_version_manager.rb
blob: aa4cfc460b1a1a27cdc2576f365d9e077c1c250f (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
# frozen_string_literal: true

require "pathname"
require_relative "helpers"
require_relative "path"

class RubygemsVersionManager
  include Spec::Helpers
  include Spec::Path

  def initialize(source)
    @source = source
  end

  def switch
    return if use_system?

    assert_system_features_not_loaded!

    switch_local_copy_if_needed

    reexec_if_needed
  end

  def assert_system_features_not_loaded!
    at_exit do
      rubylibdir = RbConfig::CONFIG["rubylibdir"]

      rubygems_path = rubylibdir + "/rubygems"
      rubygems_default_path = rubygems_path + "/defaults"

      bundler_path = rubylibdir + "/bundler"
      bundler_exemptions = Gem.rubygems_version < Gem::Version.new("3.2.0") ? [bundler_path + "/errors.rb"] : []

      bad_loaded_features = $LOADED_FEATURES.select do |loaded_feature|
        (loaded_feature.start_with?(rubygems_path) && !loaded_feature.start_with?(rubygems_default_path)) ||
          (loaded_feature.start_with?(bundler_path) && !bundler_exemptions.any? {|bundler_exemption| loaded_feature.start_with?(bundler_exemption) })
      end

      if bad_loaded_features.any?
        raise "the following features were incorrectly loaded:\n#{bad_loaded_features.join("\n")}"
      end
    end
  end

private

  def use_system?
    @source.nil?
  end

  def reexec_if_needed
    return unless rubygems_unrequire_needed?

    require "rbconfig"

    ruby = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"])
    ruby << RbConfig::CONFIG["EXEEXT"]

    cmd = [ruby, $0, *ARGV].compact

    ENV["RUBYOPT"] = opt_add("-I#{local_copy_path.join("lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"]))

    exec(ENV, *cmd)
  end

  def switch_local_copy_if_needed
    return unless local_copy_switch_needed?

    sys_exec!("git remote update", :dir => local_copy_path)
    sys_exec!("git checkout #{target_tag} --quiet", :dir => local_copy_path)

    ENV["RGV"] = local_copy_path.to_s
  end

  def rubygems_unrequire_needed?
    !$LOADED_FEATURES.include?(local_copy_path.join("lib/rubygems.rb").to_s)
  end

  def local_copy_switch_needed?
    !source_is_path? && target_tag != local_copy_tag
  end

  def target_tag
    @target_tag ||= resolve_target_tag
  end

  def local_copy_tag
    sys_exec!("git rev-parse --abbrev-ref HEAD", :dir => local_copy_path)
  end

  def local_copy_path
    @local_copy_path ||= resolve_local_copy_path
  end

  def resolve_local_copy_path
    return expanded_source if source_is_path?

    rubygems_path = root.join("tmp/rubygems")

    unless rubygems_path.directory?
      rubygems_path.parent.mkpath
      sys_exec!("git clone https://github.com/rubygems/rubygems.git #{rubygems_path}")
    end

    rubygems_path
  end

  def source_is_path?
    expanded_source.directory?
  end

  def expanded_source
    @expanded_source ||= Pathname.new(@source).expand_path(root)
  end

  def resolve_target_tag
    return "v#{@source}" if @source.match(/^\d/)

    @source
  end
end