bundler 1.13.0.rc.1 → 1.13.0.rc.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.rubocop.yml +8 -0
  4. data/.rubocop_todo.yml +21 -21
  5. data/.travis.yml +5 -1
  6. data/CHANGELOG.md +33 -1
  7. data/DEVELOPMENT.md +1 -1
  8. data/Rakefile +21 -12
  9. data/bin/rake +1 -1
  10. data/bin/rspec +1 -1
  11. data/bin/rubocop +2 -2
  12. data/bundler.gemspec +2 -2
  13. data/exe/bundler +1 -19
  14. data/lib/bundler.rb +43 -34
  15. data/lib/bundler/cli.rb +54 -5
  16. data/lib/bundler/cli/binstubs.rb +3 -2
  17. data/lib/bundler/cli/console.rb +3 -0
  18. data/lib/bundler/cli/doctor.rb +95 -0
  19. data/lib/bundler/cli/exec.rb +18 -2
  20. data/lib/bundler/cli/gem.rb +1 -1
  21. data/lib/bundler/cli/inject.rb +25 -7
  22. data/lib/bundler/cli/install.rb +23 -2
  23. data/lib/bundler/cli/lock.rb +14 -2
  24. data/lib/bundler/cli/update.rb +9 -0
  25. data/lib/bundler/definition.rb +86 -17
  26. data/lib/bundler/deployment.rb +6 -0
  27. data/lib/bundler/dsl.rb +67 -22
  28. data/lib/bundler/env.rb +1 -1
  29. data/lib/bundler/environment_preserver.rb +1 -1
  30. data/lib/bundler/errors.rb +11 -1
  31. data/lib/bundler/fetcher.rb +3 -2
  32. data/lib/bundler/fetcher/base.rb +10 -0
  33. data/lib/bundler/fetcher/compact_index.rb +27 -9
  34. data/lib/bundler/fetcher/dependency.rb +1 -12
  35. data/lib/bundler/fetcher/downloader.rb +1 -1
  36. data/lib/bundler/friendly_errors.rb +4 -2
  37. data/lib/bundler/gem_helper.rb +2 -2
  38. data/lib/bundler/gem_version_promoter.rb +175 -0
  39. data/lib/bundler/graph.rb +4 -25
  40. data/lib/bundler/index.rb +9 -1
  41. data/lib/bundler/injector.rb +12 -5
  42. data/lib/bundler/inline.rb +2 -2
  43. data/lib/bundler/installer.rb +23 -8
  44. data/lib/bundler/installer/gem_installer.rb +13 -15
  45. data/lib/bundler/installer/parallel_installer.rb +121 -99
  46. data/lib/bundler/lazy_specification.rb +8 -2
  47. data/lib/bundler/lockfile_parser.rb +20 -12
  48. data/lib/bundler/mirror.rb +2 -2
  49. data/lib/bundler/plugin.rb +153 -31
  50. data/lib/bundler/plugin/api.rb +29 -5
  51. data/lib/bundler/plugin/api/source.rb +293 -0
  52. data/lib/bundler/plugin/dsl.rb +25 -1
  53. data/lib/bundler/plugin/index.rb +80 -13
  54. data/lib/bundler/plugin/installer.rb +6 -10
  55. data/lib/bundler/plugin/source_list.rb +4 -0
  56. data/lib/bundler/postit_trampoline.rb +57 -40
  57. data/lib/bundler/resolver.rb +24 -12
  58. data/lib/bundler/retry.rb +2 -1
  59. data/lib/bundler/ruby_version.rb +4 -2
  60. data/lib/bundler/rubygems_ext.rb +10 -3
  61. data/lib/bundler/rubygems_gem_installer.rb +6 -0
  62. data/lib/bundler/rubygems_integration.rb +101 -66
  63. data/lib/bundler/runtime.rb +25 -2
  64. data/lib/bundler/settings.rb +30 -11
  65. data/lib/bundler/setup.rb +6 -3
  66. data/lib/bundler/shared_helpers.rb +11 -5
  67. data/lib/bundler/source/gemspec.rb +4 -0
  68. data/lib/bundler/source/git.rb +9 -6
  69. data/lib/bundler/source/git/git_proxy.rb +27 -3
  70. data/lib/bundler/source/path.rb +4 -26
  71. data/lib/bundler/source/path/installer.rb +39 -11
  72. data/lib/bundler/source/rubygems.rb +1 -1
  73. data/lib/bundler/source_list.rb +28 -8
  74. data/lib/bundler/spec_set.rb +1 -1
  75. data/lib/bundler/templates/Executable.standalone +4 -2
  76. data/lib/bundler/templates/Gemfile +0 -1
  77. data/lib/bundler/ui/shell.rb +11 -3
  78. data/lib/bundler/ui/silent.rb +1 -3
  79. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +1 -2
  80. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb +16 -2
  81. data/lib/bundler/version.rb +1 -1
  82. data/lib/bundler/yaml_serializer.rb +34 -11
  83. data/man/bundle-binstubs.ronn +29 -0
  84. data/man/bundle-config.ronn +32 -0
  85. data/man/bundle-install.ronn +6 -41
  86. data/man/bundle-package.ronn +1 -1
  87. data/man/bundle.ronn +4 -3
  88. data/man/gemfile.5.ronn +1 -1
  89. metadata +13 -9
  90. data/lib/bundler/environment.rb +0 -42
@@ -43,7 +43,7 @@ module Bundler
43
43
  private
44
44
 
45
45
  def fetch_valid_mirror_for(uri)
46
- mirror = (@mirrors[URI(uri.to_s.downcase)] || Mirror.new(uri)).validate!(@prober)
46
+ mirror = (@mirrors[URI(uri.to_s.downcase)] || @mirrors[URI(uri.to_s).host] || Mirror.new(uri)).validate!(@prober)
47
47
  mirror = Mirror.new(uri) unless mirror.valid?
48
48
  mirror
49
49
  end
@@ -121,7 +121,7 @@ module Bundler
121
121
  if uri == "all"
122
122
  @all = true
123
123
  else
124
- @uri = Settings.normalize_uri(uri)
124
+ @uri = URI(uri).absolute? ? Settings.normalize_uri(uri) : uri
125
125
  end
126
126
  @value = value
127
127
  end
@@ -10,45 +10,60 @@ module Bundler
10
10
 
11
11
  class MalformattedPlugin < PluginError; end
12
12
  class UndefinedCommandError < PluginError; end
13
+ class UnknownSourceError < PluginError; end
13
14
 
14
15
  PLUGIN_FILE_NAME = "plugins.rb".freeze
15
16
 
16
17
  module_function
17
18
 
18
- @commands = {}
19
+ def reset!
20
+ instance_variables.each {|i| remove_instance_variable(i) }
21
+
22
+ @sources = {}
23
+ @commands = {}
24
+ @hooks_by_event = Hash.new {|h, k| h[k] = [] }
25
+ @loaded_plugin_names = []
26
+ end
27
+
28
+ reset!
19
29
 
20
30
  # Installs a new plugin by the given name
21
31
  #
22
32
  # @param [Array<String>] names the name of plugin to be installed
23
- # @param [Hash] options various parameters as described in description
24
- # @option options [String] :source rubygems source to fetch the plugin gem from
25
- # @option options [String] :version (optional) the version of the plugin to install
33
+ # @param [Hash] options various parameters as described in description.
34
+ # Refer to cli/plugin for available options
26
35
  def install(names, options)
27
- paths = Installer.new.install(names, options)
36
+ specs = Installer.new.install(names, options)
28
37
 
29
- save_plugins paths
38
+ save_plugins names, specs
30
39
  rescue PluginError => e
31
- paths.values.map {|path| Bundler.rm_rf(path) } if paths
32
- Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}"
40
+ specs.values.map {|spec| Bundler.rm_rf(spec.full_gem_path) } if specs
41
+ Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}"
33
42
  end
34
43
 
35
44
  # Evaluates the Gemfile with a limited DSL and installs the plugins
36
45
  # specified by plugin method
37
46
  #
38
47
  # @param [Pathname] gemfile path
48
+ # @param [Proc] block that can be evaluated for (inline) Gemfile
39
49
  def gemfile_install(gemfile = nil, &inline)
50
+ builder = DSL.new
40
51
  if block_given?
41
- builder = DSL.new
42
52
  builder.instance_eval(&inline)
43
- definition = builder.to_definition(nil, true)
44
53
  else
45
- definition = DSL.evaluate(gemfile, nil, {})
54
+ builder.eval_gemfile(gemfile)
46
55
  end
47
- return unless definition.dependencies.any?
56
+ definition = builder.to_definition(nil, true)
57
+
58
+ return if definition.dependencies.empty?
48
59
 
49
- plugins = Installer.new.install_definition(definition)
60
+ plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p }
61
+ installed_specs = Installer.new.install_definition(definition)
50
62
 
51
- save_plugins plugins
63
+ save_plugins plugins, installed_specs, builder.inferred_plugins
64
+ rescue => e
65
+ Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
66
+ raise
52
67
  end
53
68
 
54
69
  # The index object used to store the details about the plugin
@@ -56,9 +71,25 @@ module Bundler
56
71
  @index ||= Index.new
57
72
  end
58
73
 
59
- # The directory root to all plugin related data
74
+ # The directory root for all plugin related data
75
+ #
76
+ # Points to root in app_config_path if ran in an app else points to the one
77
+ # in user_bundle_path
60
78
  def root
61
- @root ||= Bundler.user_bundle_path.join("plugin")
79
+ @root ||= if SharedHelpers.in_bundle?
80
+ local_root
81
+ else
82
+ global_root
83
+ end
84
+ end
85
+
86
+ def local_root
87
+ Bundler.app_config_path.join("plugin")
88
+ end
89
+
90
+ # The global directory root for all plugin related data
91
+ def global_root
92
+ Bundler.user_bundle_path.join("plugin")
62
93
  end
63
94
 
64
95
  # The cache directory for plugin stuffs
@@ -71,7 +102,7 @@ module Bundler
71
102
  @commands[command] = cls
72
103
  end
73
104
 
74
- # Checks if any plugins handles the command
105
+ # Checks if any plugin handles the command
75
106
  def command?(command)
76
107
  !index.command_plugin(command).nil?
77
108
  end
@@ -79,13 +110,64 @@ module Bundler
79
110
  # To be called from Cli class to pass the command and argument to
80
111
  # approriate plugin class
81
112
  def exec_command(command, args)
82
- raise UndefinedCommandError, "Command #{command} not found" unless command? command
113
+ raise UndefinedCommandError, "Command `#{command}` not found" unless command? command
83
114
 
84
115
  load_plugin index.command_plugin(command) unless @commands.key? command
85
116
 
86
117
  @commands[command].new.exec(command, args)
87
118
  end
88
119
 
120
+ # To be called via the API to register to handle a source plugin
121
+ def add_source(source, cls)
122
+ @sources[source] = cls
123
+ end
124
+
125
+ # Checks if any plugin declares the source
126
+ def source?(name)
127
+ !index.source_plugin(name.to_s).nil?
128
+ end
129
+
130
+ # @return [Class] that handles the source. The calss includes API::Source
131
+ def source(name)
132
+ raise UnknownSourceError, "Source #{name} not found" unless source? name
133
+
134
+ load_plugin(index.source_plugin(name)) unless @sources.key? name
135
+
136
+ @sources[name]
137
+ end
138
+
139
+ # @param [Hash] The options that are present in the lock file
140
+ # @return [API::Source] the instance of the class that handles the source
141
+ # type passed in locked_opts
142
+ def source_from_lock(locked_opts)
143
+ src = source(locked_opts["type"])
144
+
145
+ src.new(locked_opts.merge("uri" => locked_opts["remote"]))
146
+ end
147
+
148
+ # To be called via the API to register a hooks and corresponding block that
149
+ # will be called to handle the hook
150
+ def add_hook(event, &block)
151
+ @hooks_by_event[event.to_s] << block
152
+ end
153
+
154
+ # Runs all the hooks that are registered for the passed event
155
+ #
156
+ # It passes the passed arguments and block to the block registered with
157
+ # the api.
158
+ #
159
+ # @param [String] event
160
+ def hook(event, *args, &arg_blk)
161
+ return unless Bundler.settings[:plugins]
162
+
163
+ plugins = index.hook_plugins(event)
164
+ return unless plugins.any?
165
+
166
+ (plugins - @loaded_plugin_names).each {|name| load_plugin(name) }
167
+
168
+ @hooks_by_event[event].each {|blk| blk.call(*args, &arg_blk) }
169
+ end
170
+
89
171
  # currently only intended for specs
90
172
  #
91
173
  # @return [String, nil] installed path
@@ -95,13 +177,16 @@ module Bundler
95
177
 
96
178
  # Post installation processing and registering with index
97
179
  #
98
- # @param [Hash] plugins mapped to their installtion path
99
- def save_plugins(plugins)
100
- plugins.each do |name, path|
101
- path = Pathname.new path
102
- validate_plugin! path
103
- register_plugin name, path
104
- Bundler.ui.info "Installed plugin #{name}"
180
+ # @param [Array<String>] plugins list to be installed
181
+ # @param [Hash] specs of plugins mapped to installation path (currently they
182
+ # contain all the installed specs, including plugins)
183
+ # @param [Array<String>] names of inferred source plugins that can be ignored
184
+ def save_plugins(plugins, specs, optional_plugins = [])
185
+ plugins.each do |name|
186
+ spec = specs[name]
187
+ validate_plugin! Pathname.new(spec.full_gem_path)
188
+ installed = register_plugin(name, spec, optional_plugins.include?(name))
189
+ Bundler.ui.info "Installed plugin #{name}" if installed
105
190
  end
106
191
  end
107
192
 
@@ -110,21 +195,33 @@ module Bundler
110
195
  # At present it only checks whether it contains plugins.rb file
111
196
  #
112
197
  # @param [Pathname] plugin_path the path plugin is installed at
113
- # @raise [Error] if plugins.rb file is not found
198
+ # @raise [MalformattedPlugin] if plugins.rb file is not found
114
199
  def validate_plugin!(plugin_path)
115
200
  plugin_file = plugin_path.join(PLUGIN_FILE_NAME)
116
- raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin!" unless plugin_file.file?
201
+ raise MalformattedPlugin, "#{PLUGIN_FILE_NAME} was not found in the plugin." unless plugin_file.file?
117
202
  end
118
203
 
119
204
  # Runs the plugins.rb file in an isolated namespace, records the plugin
120
205
  # actions it registers for and then passes the data to index to be stored.
121
206
  #
122
207
  # @param [String] name the name of the plugin
123
- # @param [Pathname] path the path where the plugin is installed at
124
- def register_plugin(name, path)
208
+ # @param [Specification] spec of installed plugin
209
+ # @param [Boolean] optional_plugin, removed if there is conflict with any
210
+ # other plugin (used for default source plugins)
211
+ #
212
+ # @raise [MalformattedPlugin] if plugins.rb raises any error
213
+ def register_plugin(name, spec, optional_plugin = false)
125
214
  commands = @commands
215
+ sources = @sources
216
+ hooks = @hooks_by_event
126
217
 
127
218
  @commands = {}
219
+ @sources = {}
220
+ @hooks_by_event = Hash.new {|h, k| h[k] = [] }
221
+
222
+ load_paths = spec.load_paths
223
+ add_to_load_path(load_paths)
224
+ path = Pathname.new spec.full_gem_path
128
225
 
129
226
  begin
130
227
  load path.join(PLUGIN_FILE_NAME), true
@@ -132,9 +229,18 @@ module Bundler
132
229
  raise MalformattedPlugin, "#{e.class}: #{e.message}"
133
230
  end
134
231
 
135
- index.register_plugin name, path.to_s, @commands.keys
232
+ if optional_plugin && @sources.keys.any? {|s| source? s }
233
+ Bundler.rm_rf(path)
234
+ false
235
+ else
236
+ index.register_plugin(name, path.to_s, load_paths, @commands.keys,
237
+ @sources.keys, @hooks_by_event.keys)
238
+ true
239
+ end
136
240
  ensure
137
241
  @commands = commands
242
+ @sources = sources
243
+ @hooks_by_event = hooks
138
244
  end
139
245
 
140
246
  # Executes the plugins.rb file
@@ -146,11 +252,27 @@ module Bundler
146
252
  # done to avoid conflicts
147
253
  path = index.plugin_path(name)
148
254
 
255
+ add_to_load_path(index.load_paths(name))
256
+
149
257
  load path.join(PLUGIN_FILE_NAME)
258
+
259
+ @loaded_plugin_names << name
260
+ rescue => e
261
+ Bundler.ui.error "Failed loading plugin #{name}: #{e.message}"
262
+ raise
263
+ end
264
+
265
+ def add_to_load_path(load_paths)
266
+ if insert_index = Bundler.rubygems.load_path_insert_index
267
+ $LOAD_PATH.insert(insert_index, *load_paths)
268
+ else
269
+ $LOAD_PATH.unshift(*load_paths)
270
+ end
150
271
  end
151
272
 
152
273
  class << self
153
- private :load_plugin, :register_plugin, :save_plugins, :validate_plugin!
274
+ private :load_plugin, :register_plugin, :save_plugins, :validate_plugin!,
275
+ :add_to_load_path
154
276
  end
155
277
  end
156
278
  end
@@ -23,19 +23,35 @@ module Bundler
23
23
  # and hooks).
24
24
  module Plugin
25
25
  class API
26
+ autoload :Source, "bundler/plugin/api/source"
26
27
  # The plugins should declare that they handle a command through this helper.
27
28
  #
28
29
  # @param [String] command being handled by them
29
- # @param [Class] (optional) class that shall handle the command. If not
30
+ # @param [Class] (optional) class that handles the command. If not
30
31
  # provided, the `self` class will be used.
31
32
  def self.command(command, cls = self)
32
33
  Plugin.add_command command, cls
33
34
  end
34
35
 
35
- # The cache dir to be used by the plugins for persistance storage
36
+ # The plugins should declare that they provide a installation source
37
+ # through this helper.
38
+ #
39
+ # @param [String] the source type they provide
40
+ # @param [Class] (optional) class that handles the source. If not
41
+ # provided, the `self` class will be used.
42
+ def self.source(source, cls = self)
43
+ cls.send :include, Bundler::Plugin::API::Source
44
+ Plugin.add_source source, cls
45
+ end
46
+
47
+ def self.hook(event, &block)
48
+ Plugin.add_hook(event, &block)
49
+ end
50
+
51
+ # The cache dir to be used by the plugins for storage
36
52
  #
37
53
  # @return [Pathname] path of the cache dir
38
- def cache
54
+ def cache_dir
39
55
  Plugin.cache.join("plugins")
40
56
  end
41
57
 
@@ -48,8 +64,16 @@ module Bundler
48
64
  end
49
65
 
50
66
  def method_missing(name, *args, &blk)
51
- super unless Bundler.respond_to?(name)
52
- Bundler.send(name, *args, &blk)
67
+ return Bundler.send(name, *args, &blk) if Bundler.respond_to?(name)
68
+
69
+ return SharedHelpers.send(name, *args, &blk) if SharedHelpers.respond_to?(name)
70
+
71
+ super
72
+ end
73
+
74
+ def respond_to_missing?(name, include_private = false)
75
+ SharedHelpers.respond_to?(name, include_private) ||
76
+ Bundler.respond_to?(name, include_private) || super
53
77
  end
54
78
  end
55
79
  end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+ require "uri"
3
+ require "digest/sha1"
4
+
5
+ module Bundler
6
+ module Plugin
7
+ class API
8
+ # This class provides the base to build source plugins
9
+ # All the method here are require to build a source plugin (except
10
+ # `uri_hash`, `gem_install_dir`; they are helpers).
11
+ #
12
+ # Defaults for methods, where ever possible are provided which is
13
+ # expected to work. But, all source plugins have to override
14
+ # `fetch_gemspec_files` and `install`. Defaults are also not provided for
15
+ # `remote!`, `cache!` and `unlock!`.
16
+ #
17
+ # The defaults shall work for most situations but nevertheless they can
18
+ # be (preferably should be) overridden as per the plugins' needs safely
19
+ # (as long as they behave as expected).
20
+ # On overriding `initialize` you should call super first.
21
+ #
22
+ # If required plugin should override `hash`, `==` and `eql?` methods to be
23
+ # able to match objects representing same sources, but may be created in
24
+ # different situation (like form gemfile and lockfile). The default ones
25
+ # checks only for class and uri, but elaborate source plugins may need
26
+ # more comparisons (e.g. git checking on branch or tag).
27
+ #
28
+ # @!attribute [r] uri
29
+ # @return [String] the remote specified with `source` block in Gemfile
30
+ #
31
+ # @!attribute [r] options
32
+ # @return [String] options passed during initialization (either from
33
+ # lockfile or Gemfile)
34
+ #
35
+ # @!attribute [r] name
36
+ # @return [String] name that can be used to uniquely identify a source
37
+ #
38
+ # @!attribute [rw] dependency_names
39
+ # @return [Array<String>] Names of dependencies that the source should
40
+ # try to resolve. It is not necessary to use this list intenally. This
41
+ # is present to be compatible with `Definition` and is used by
42
+ # rubygems source.
43
+ module Source
44
+ attr_reader :uri, :options, :name
45
+ attr_accessor :dependency_names
46
+
47
+ def initialize(opts)
48
+ @options = opts
49
+ @dependency_names = []
50
+ @uri = opts["uri"]
51
+ @type = opts["type"]
52
+ @name = opts["name"] || "#{@type} at #{@uri}"
53
+ end
54
+
55
+ # This is used by the default `spec` method to constructs the
56
+ # Specification objects for the gems and versions that can be installed
57
+ # by this source plugin.
58
+ #
59
+ # Note: If the spec method is overridden, this function is not necessary
60
+ #
61
+ # @return [Array<String>] paths of the gemspec files for gems that can
62
+ # be installed
63
+ def fetch_gemspec_files
64
+ []
65
+ end
66
+
67
+ # Options to be saved in the lockfile so that the source plugin is able
68
+ # to check out same version of gem later.
69
+ #
70
+ # There options are passed when the source plugin is created from the
71
+ # lock file.
72
+ #
73
+ # @return [Hash]
74
+ def options_to_lock
75
+ {}
76
+ end
77
+
78
+ # Install the gem specified by the spec at appropriate path.
79
+ # `install_path` provides a sufficient default, if the source can only
80
+ # satisfy one gem, but is not binding.
81
+ #
82
+ # @return [String] post installation message (if any)
83
+ def install(spec, opts)
84
+ raise MalformattedPlugin, "Source plugins need to override the install method."
85
+ end
86
+
87
+ # It builds extensions, generates bins and installs them for the spec
88
+ # provided.
89
+ #
90
+ # It depends on `spec.loaded_from` to get full_gem_path. The source
91
+ # plugins should set that.
92
+ #
93
+ # It should be called in `install` after the plugin is done placing the
94
+ # gem at correct install location.
95
+ #
96
+ # It also runs Gem hooks `post_install`, `post_build` and `post_install`
97
+ #
98
+ # Note: Do not override if you don't know what you are doing.
99
+ def post_install(spec, disable_exts = false)
100
+ opts = { :env_shebang => false, :disable_extensions => disable_exts }
101
+ installer = Bundler::Source::Path::Installer.new(spec, opts)
102
+ installer.post_install
103
+ end
104
+
105
+ # A default installation path to install a single gem. If the source
106
+ # servers multiple gems, it's not of much use and the source should one
107
+ # of its own.
108
+ def install_path
109
+ @install_path ||=
110
+ begin
111
+ base_name = File.basename(URI.parse(uri).normalize.path)
112
+
113
+ gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
114
+ end
115
+ end
116
+
117
+ # Parses the gemspec files to find the specs for the gems that can be
118
+ # satisfied by the source.
119
+ #
120
+ # Few important points to keep in mind:
121
+ # - If the gems are not installed then it shall return specs for all
122
+ # the gems it can satisfy
123
+ # - If gem is installed (that is to be detected by the plugin itself)
124
+ # then it shall return at least the specs that are installed.
125
+ # - The `loaded_from` for each of the specs shall be correct (it is
126
+ # used to find the load path)
127
+ #
128
+ # @return [Bundler::Index] index containing the specs
129
+ def specs
130
+ files = fetch_gemspec_files
131
+
132
+ Bundler::Index.build do |index|
133
+ files.each do |file|
134
+ next unless spec = Bundler.load_gemspec(file)
135
+ Bundler.rubygems.set_installed_by_version(spec)
136
+
137
+ spec.source = self
138
+ Bundler.rubygems.validate(spec)
139
+
140
+ index << spec
141
+ end
142
+ end
143
+ end
144
+
145
+ # Set internal representation to fetch the gems/specs from remote.
146
+ #
147
+ # When this is called, the source should try to fetch the specs and
148
+ # install from remote path.
149
+ def remote!
150
+ end
151
+
152
+ # Set internal representation to fetch the gems/specs from app cache.
153
+ #
154
+ # When this is called, the source should try to fetch the specs and
155
+ # install from the path provided by `app_cache_path`.
156
+ def cached!
157
+ end
158
+
159
+ # This is called to update the spec and installation.
160
+ #
161
+ # If the source plugin is loaded from lockfile or otherwise, it shall
162
+ # refresh the cache/specs (e.g. git sources can make a fresh clone).
163
+ def unlock!
164
+ end
165
+
166
+ # Name of directory where plugin the is expected to cache the gems when
167
+ # #cache is called.
168
+ #
169
+ # Also this name is matched against the directories in cache for pruning
170
+ #
171
+ # This is used by `app_cache_path`
172
+ def app_cache_dirname
173
+ base_name = File.basename(URI.parse(uri).normalize.path)
174
+ "#{base_name}-#{uri_hash}"
175
+ end
176
+
177
+ # This method is called while caching to save copy of the gems that the
178
+ # source can resolve to path provided by `app_cache_app`so that they can
179
+ # be reinstalled from the cache without querying the remote (i.e. an
180
+ # alternative to remote)
181
+ #
182
+ # This is stored with the app and source plugins should try to provide
183
+ # specs and install only from this cache when `cached!` is called.
184
+ #
185
+ # This cache is different from the internal caching that can be done
186
+ # at sub paths of `cache_path` (from API). This can be though as caching
187
+ # by bundler.
188
+ def cache(spec, custom_path = nil)
189
+ new_cache_path = app_cache_path(custom_path)
190
+
191
+ FileUtils.rm_rf(new_cache_path)
192
+ FileUtils.cp_r(install_path, new_cache_path)
193
+ FileUtils.touch(app_cache_path.join(".bundlecache"))
194
+ end
195
+
196
+ # This shall check if two source object represent the same source.
197
+ #
198
+ # The comparison shall take place only on the attribute that can be
199
+ # inferred from the options passed from Gemfile and not on attibutes
200
+ # that are used to pin down the gem to specific version (e.g. Git
201
+ # sources should compare on branch and tag but not on commit hash)
202
+ #
203
+ # The sources objects are constructed from Gemfile as well as from
204
+ # lockfile. To converge the sources, it is necessary that they match.
205
+ #
206
+ # The same applies for `eql?` and `hash`
207
+ def ==(other)
208
+ other.is_a?(self.class) && uri == other.uri
209
+ end
210
+
211
+ # When overriding `eql?` please preserve the behaviour as mentioned in
212
+ # docstring for `==` method.
213
+ alias_method :eql?, :==
214
+
215
+ # When overriding `hash` please preserve the behaviour as mentioned in
216
+ # docstring for `==` method, i.e. two methods equal by above comparison
217
+ # should have same hash.
218
+ def hash
219
+ [self.class, uri].hash
220
+ end
221
+
222
+ # A helper method, not necessary if not used internally.
223
+ def installed?
224
+ File.directory?(install_path)
225
+ end
226
+
227
+ # The full path where the plugin should cache the gem so that it can be
228
+ # installed latter.
229
+ #
230
+ # Note: Do not override if you don't know what you are doing.
231
+ def app_cache_path(custom_path = nil)
232
+ @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
233
+ end
234
+
235
+ # Used by definition.
236
+ #
237
+ # Note: Do not override if you don't know what you are doing.
238
+ def unmet_deps
239
+ specs.unmet_dependency_names
240
+ end
241
+
242
+ # Note: Do not override if you don't know what you are doing.
243
+ def can_lock?(spec)
244
+ spec.source == self
245
+ end
246
+
247
+ # Generates the content to be entered into the lockfile.
248
+ # Saves type and remote and also calls to `options_to_lock`.
249
+ #
250
+ # Plugin should use `options_to_lock` to save information in lockfile
251
+ # and not override this.
252
+ #
253
+ # Note: Do not override if you don't know what you are doing.
254
+ def to_lock
255
+ out = String.new("#{LockfileParser::PLUGIN}\n")
256
+ out << " remote: #{@uri}\n"
257
+ out << " type: #{@type}\n"
258
+ options_to_lock.each do |opt, value|
259
+ out << " #{opt}: #{value}\n"
260
+ end
261
+ out << " specs:\n"
262
+ end
263
+
264
+ def to_s
265
+ "plugin source for #{options[:type]} with uri #{uri}"
266
+ end
267
+
268
+ # Note: Do not override if you don't know what you are doing.
269
+ def include?(other)
270
+ other == self
271
+ end
272
+
273
+ def uri_hash
274
+ Digest::SHA1.hexdigest(uri)
275
+ end
276
+
277
+ # Note: Do not override if you don't know what you are doing.
278
+ def gem_install_dir
279
+ Bundler.install_path
280
+ end
281
+
282
+ # It is used to obtain the full_gem_path.
283
+ #
284
+ # spec's loaded_from path is expanded against this to get full_gem_path
285
+ #
286
+ # Note: Do not override if you don't know what you are doing.
287
+ def root
288
+ Bundler.root
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end