summaryrefslogtreecommitdiff
path: root/lib/bundler/plugin/installer.rb
blob: 6771f3f153769887d7d18cdc61165916c993ea94 (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
# frozen_string_literal: true

module Bundler
  # Handles the installation of plugin in appropriate directories.
  #
  # This class is supposed to be wrapper over the existing gem installation infra
  # but currently it itself handles everything as the Source's subclasses (e.g. Source::RubyGems)
  # are heavily dependent on the Gemfile.
  module Plugin
    class Installer
      autoload :Rubygems, File.expand_path("installer/rubygems", __dir__)
      autoload :Git,      File.expand_path("installer/git", __dir__)
      autoload :Path, File.expand_path("installer/path", __dir__)

      def install(names, options)
        check_sources_consistency!(options)

        version = options[:version] || [">= 0"]

        if options[:git]
          install_git(names, version, options)
        elsif options[:path]
          install_path(names, version, options[:path])
        else
          sources = options[:source] || Gem.sources
          install_rubygems(names, version, sources)
        end
      end

      # Installs the plugin from Definition object created by limited parsing of
      # Gemfile searching for plugins to be installed
      #
      # @param [Definition] definition object
      # @return [Hash] map of names to their specs they are installed with
      def install_definition(definition)
        def definition.lock(*); end
        definition.resolve_remotely!
        specs = definition.specs

        install_from_specs specs
      end

      private

      def check_sources_consistency!(options)
        if options.key?(:git) && options.key?(:local_git)
          raise InvalidOption, "Remote and local plugin git sources can't be both specified"
        end

        # back-compat; local_git is an alias for git
        if options.key?(:local_git)
          Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git")
          options[:git] = options.delete(:local_git)
        end

        if (options.keys & [:source, :git, :path]).length > 1
          raise InvalidOption, "Only one of --source, --git, or --path may be specified"
        end

        if (options.key?(:branch) || options.key?(:ref)) && !options.key?(:git)
          raise InvalidOption, "--#{options.key?(:branch) ? "branch" : "ref"} can only be used with git sources"
        end

        if options.key?(:branch) && options.key?(:ref)
          raise InvalidOption, "--branch and --ref can't be both specified"
        end
      end

      def install_git(names, version, options)
        source_list = SourceList.new
        source = source_list.add_git_source({ "uri" => options[:git],
                                              "branch" => options[:branch],
                                              "ref" => options[:ref] })

        install_all_sources(names, version, source_list, source)
      end

      def install_path(names, version, path)
        source_list = SourceList.new
        source = source_list.add_path_source({ "path" => path })

        install_all_sources(names, version, source_list, source)
      end

      # Installs the plugin from rubygems source and returns the path where the
      # plugin was installed
      #
      # @param [String] name of the plugin gem to search in the source
      # @param [Array] version of the gem to install
      # @param [String, Array<String>] source(s) to resolve the gem
      #
      # @return [Hash] map of names to the specs of plugins installed
      def install_rubygems(names, version, sources)
        source_list = SourceList.new

        Array(sources).each {|remote| source_list.add_global_rubygems_remote(remote) }

        install_all_sources(names, version, source_list)
      end

      def install_all_sources(names, version, source_list, source = nil)
        deps = names.map {|name| Dependency.new(name, version, { "source" => source }) }

        Bundler.configure_gem_home_and_path(Plugin.root)

        Bundler.settings.temporary(deployment: false, frozen: false) do
          definition = Definition.new(nil, deps, source_list, true)

          install_definition(definition)
        end
      end

      # Installs the plugins and deps from the provided specs and returns map of
      # gems to their paths
      #
      # @param specs to install
      #
      # @return [Hash] map of names to the specs
      def install_from_specs(specs)
        paths = {}

        specs.each do |spec|
          spec.source.install spec

          paths[spec.name] = spec
        end

        paths
      end
    end
  end
end