summaryrefslogtreecommitdiff
path: root/lib/bundler/installer/standalone.rb
blob: 5331df2e95bd63ef67eb54ab7ebca7364697b0cc (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
# frozen_string_literal: true

module Bundler
  class Standalone
    def initialize(groups, definition)
      @specs = definition.specs_for(groups)
    end

    def generate
      SharedHelpers.filesystem_access(bundler_path) do |p|
        FileUtils.mkdir_p(p)
      end
      File.open File.join(bundler_path, "setup.rb"), "w" do |file|
        file.puts "require 'rbconfig'"
        file.puts prevent_gem_activation
        file.puts define_path_helpers
        file.puts reverse_rubygems_kernel_mixin
        paths.each do |path|
          if Pathname.new(path).absolute?
            file.puts %($:.unshift "#{path}")
          else
            file.puts %($:.unshift File.expand_path("\#{__dir__}/#{path}"))
          end
        end
      end
    end

    private

    def paths
      @specs.map do |spec|
        next if spec.name == "bundler"
        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<platform>/#{Gem.extension_api_version}')
          # This is a static string intentionally. It's interpolated at a later time.
        end
      end.flatten.compact
    end

    def version_dir
      "#{RUBY_ENGINE}/#{Gem.ruby_api_version}"
    end

    def extensions_dir
      %r{extensions/(?<platform>[^/]+)/#{Regexp.escape(Gem.extension_api_version)}}
    end

    def bundler_path
      Bundler.root.join(Bundler.settings[:path].to_s, "bundler")
    end

    def gem_path(path, spec)
      full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path)
      if spec.source.instance_of?(Source::Path) && spec.source.path.absolute?
        full_path
      else
        SharedHelpers.relative_path_to(full_path, from: Bundler.root.join(bundler_path))
      end
    rescue TypeError
      error_message = "#{spec.name} #{spec.version} has an invalid gemspec"
      raise Gem::InvalidSpecificationException.new(error_message)
    end

    def prevent_gem_activation
      <<~'END'
        module Kernel
          remove_method(:gem) if private_method_defined?(:gem)

          def gem(*)
          end

          private :gem
        end
      END
    end

    def define_path_helpers
      <<~'END'
        unless defined?(Gem)
          module Gem
            def self.ruby_api_version
              RbConfig::CONFIG["ruby_version"]
            end

            def self.extension_api_version
              if 'no' == RbConfig::CONFIG['ENABLE_SHARED']
                "#{ruby_api_version}-static"
              else
                ruby_api_version
              end
            end
          end
        end
      END
    end

    def reverse_rubygems_kernel_mixin
      <<~END
      if Gem.respond_to?(:discover_gems_on_require=)
        Gem.discover_gems_on_require = false
      else
        [::Kernel.singleton_class, ::Kernel].each do |k|
          if k.private_method_defined?(:gem_original_require)
            private_require = k.private_method_defined?(:require)
            k.send(:remove_method, :require)
            k.send(:define_method, :require, k.instance_method(:gem_original_require))
            k.send(:private, :require) if private_require
          end
        end
      end
      END
    end
  end
end