Module: Bundler::Plugin::API::Source

Defined in:
lib/bundler/plugin/api/source.rb

Overview

This class provides the base to build source plugins All the method here are required to build a source plugin (except ‘uri_hash`, `gem_install_dir`; they are helpers).

Defaults for methods, where ever possible are provided which is expected to work. But, all source plugins have to override ‘fetch_gemspec_files` and `install`. Defaults are also not provided for `remote!`, `cache!` and `unlock!`.

The defaults shall work for most situations but nevertheless they can be (preferably should be) overridden as per the plugins’ needs safely (as long as they behave as expected). On overriding ‘initialize` you should call super first.

If required plugin should override ‘hash`, `==` and `eql?` methods to be able to match objects representing same sources, but may be created in different situation (like form gemfile and lockfile). The default ones checks only for class and uri, but elaborate source plugins may need more comparisons (e.g. git checking on branch or tag).

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#dependency_namesArray<String>

Returns Names of dependencies that the source should try to resolve. It is not necessary to use this list internally. This is present to be compatible with ‘Definition` and is used by rubygems source.

Returns:

  • (Array<String>)

    Names of dependencies that the source should try to resolve. It is not necessary to use this list internally. This is present to be compatible with ‘Definition` and is used by rubygems source.



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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/bundler/plugin/api/source.rb', line 41

module Source
  attr_reader :uri, :options, :name
  attr_accessor :dependency_names

  def initialize(opts)
    @options = opts
    @dependency_names = []
    @uri = opts["uri"]
    @type = opts["type"]
    @name = opts["name"] || "#{@type} at #{@uri}"
  end

  # This is used by the default `spec` method to constructs the
  # Specification objects for the gems and versions that can be installed
  # by this source plugin.
  #
  # Note: If the spec method is overridden, this function is not necessary
  #
  # @return [Array<String>] paths of the gemspec files for gems that can
  #                         be installed
  def fetch_gemspec_files
    []
  end

  # Options to be saved in the lockfile so that the source plugin is able
  # to check out same version of gem later.
  #
  # There options are passed when the source plugin is created from the
  # lock file.
  #
  # @return [Hash]
  def options_to_lock
    {}
  end

  # Install the gem specified by the spec at appropriate path.
  # `install_path` provides a sufficient default, if the source can only
  # satisfy one gem,  but is not binding.
  #
  # @return [String] post installation message (if any)
  def install(spec, opts)
    raise MalformattedPlugin, "Source plugins need to override the install method."
  end

  # It builds extensions, generates bins and installs them for the spec
  # provided.
  #
  # It depends on `spec.loaded_from` to get full_gem_path. The source
  # plugins should set that.
  #
  # It should be called in `install` after the plugin is done placing the
  # gem at correct install location.
  #
  # It also runs Gem hooks `pre_install`, `post_build` and `post_install`
  #
  # Note: Do not override if you don't know what you are doing.
  def post_install(spec, disable_exts = false)
    opts = { :env_shebang => false, :disable_extensions => disable_exts }
    installer = Bundler::Source::Path::Installer.new(spec, opts)
    installer.post_install
  end

  # A default installation path to install a single gem. If the source
  # servers multiple gems, it's not of much use and the source should one
  # of its own.
  def install_path
    @install_path ||=
      begin
        base_name = File.basename(Bundler::URI.parse(uri).normalize.path)

        gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
      end
  end

  # Parses the gemspec files to find the specs for the gems that can be
  # satisfied by the source.
  #
  # Few important points to keep in mind:
  #   - If the gems are not installed then it shall return specs for all
  #   the gems it can satisfy
  #   - If gem is installed (that is to be detected by the plugin itself)
  #   then it shall return at least the specs that are installed.
  #   - The `loaded_from` for each of the specs shall be correct (it is
  #   used to find the load path)
  #
  # @return [Bundler::Index] index containing the specs
  def specs
    files = fetch_gemspec_files

    Bundler::Index.build do |index|
      files.each do |file|
        next unless spec = Bundler.load_gemspec(file)
        Bundler.rubygems.set_installed_by_version(spec)

        spec.source = self
        Bundler.rubygems.validate(spec)

        index << spec
      end
    end
  end

  # Set internal representation to fetch the gems/specs locally.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the local system.
  def local!
  end

  # Set internal representation to fetch the gems/specs from remote.
  #
  # When this is called, the source should try to fetch the specs and
  # install from remote path.
  def remote!
  end

  # Set internal representation to fetch the gems/specs from app cache.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the path provided by `app_cache_path`.
  def cached!
  end

  # This is called to update the spec and installation.
  #
  # If the source plugin is loaded from lockfile or otherwise, it shall
  # refresh the cache/specs (e.g. git sources can make a fresh clone).
  def unlock!
  end

  # Name of directory where plugin the is expected to cache the gems when
  # #cache is called.
  #
  # Also this name is matched against the directories in cache for pruning
  #
  # This is used by `app_cache_path`
  def app_cache_dirname
    base_name = File.basename(Bundler::URI.parse(uri).normalize.path)
    "#{base_name}-#{uri_hash}"
  end

  # This method is called while caching to save copy of the gems that the
  # source can resolve to path provided by `app_cache_app`so that they can
  # be reinstalled from the cache without querying the remote (i.e. an
  # alternative to remote)
  #
  # This is stored with the app and source plugins should try to provide
  # specs and install only from this cache when `cached!` is called.
  #
  # This cache is different from the internal caching that can be done
  # at sub paths of `cache_path` (from API). This can be though as caching
  # by bundler.
  def cache(spec, custom_path = nil)
    new_cache_path = app_cache_path(custom_path)

    FileUtils.rm_rf(new_cache_path)
    FileUtils.cp_r(install_path, new_cache_path)
    FileUtils.touch(app_cache_path.join(".bundlecache"))
  end

  # This shall check if two source object represent the same source.
  #
  # The comparison shall take place only on the attribute that can be
  # inferred from the options passed from Gemfile and not on attributes
  # that are used to pin down the gem to specific version (e.g. Git
  # sources should compare on branch and tag but not on commit hash)
  #
  # The sources objects are constructed from Gemfile as well as from
  # lockfile. To converge the sources, it is necessary that they match.
  #
  # The same applies for `eql?` and `hash`
  def ==(other)
    other.is_a?(self.class) && uri == other.uri
  end

  # When overriding `eql?` please preserve the behaviour as mentioned in
  # docstring for `==` method.
  alias_method :eql?, :==

  # When overriding `hash` please preserve the behaviour as mentioned in
  # docstring for `==` method, i.e. two methods equal by above comparison
  # should have same hash.
  def hash
    [self.class, uri].hash
  end

  # A helper method, not necessary if not used internally.
  def installed?
    File.directory?(install_path)
  end

  # The full path where the plugin should cache the gem so that it can be
  # installed latter.
  #
  # Note: Do not override if you don't know what you are doing.
  def app_cache_path(custom_path = nil)
    @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def unmet_deps
    specs.unmet_dependency_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def spec_names
    specs.spec_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def add_dependency_names(names)
    @dependencies |= Array(names)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def can_lock?(spec)
    spec.source == self
  end

  # Generates the content to be entered into the lockfile.
  # Saves type and remote and also calls to `options_to_lock`.
  #
  # Plugin should use `options_to_lock` to save information in lockfile
  # and not override this.
  #
  # Note: Do not override if you don't know what you are doing.
  def to_lock
    out = String.new("#{LockfileParser::PLUGIN}\n")
    out << "  remote: #{@uri}\n"
    out << "  type: #{@type}\n"
    options_to_lock.each do |opt, value|
      out << "  #{opt}: #{value}\n"
    end
    out << "  specs:\n"
  end

  def to_s
    "plugin source for #{@type} with uri #{@uri}"
  end
  alias_method :identifier, :to_s

  # NOTE: Do not override if you don't know what you are doing.
  def include?(other)
    other == self
  end

  def uri_hash
    SharedHelpers.digest(:SHA1).hexdigest(uri)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def gem_install_dir
    Bundler.install_path
  end

  # It is used to obtain the full_gem_path.
  #
  # spec's loaded_from path is expanded against this to get full_gem_path
  #
  # Note: Do not override if you don't know what you are doing.
  def root
    Bundler.root
  end

  # @private
  # This API on source might not be stable, and for now we expect plugins
  # to download all specs in `#specs`, so we implement the method for
  # compatibility purposes and leave it undocumented (and don't support)
  # overriding it)
  def double_check_for(*); end
end

#nameString (readonly)

Returns name that can be used to uniquely identify a source.

Returns:

  • (String)

    name that can be used to uniquely identify a source



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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/bundler/plugin/api/source.rb', line 41

module Source
  attr_reader :uri, :options, :name
  attr_accessor :dependency_names

  def initialize(opts)
    @options = opts
    @dependency_names = []
    @uri = opts["uri"]
    @type = opts["type"]
    @name = opts["name"] || "#{@type} at #{@uri}"
  end

  # This is used by the default `spec` method to constructs the
  # Specification objects for the gems and versions that can be installed
  # by this source plugin.
  #
  # Note: If the spec method is overridden, this function is not necessary
  #
  # @return [Array<String>] paths of the gemspec files for gems that can
  #                         be installed
  def fetch_gemspec_files
    []
  end

  # Options to be saved in the lockfile so that the source plugin is able
  # to check out same version of gem later.
  #
  # There options are passed when the source plugin is created from the
  # lock file.
  #
  # @return [Hash]
  def options_to_lock
    {}
  end

  # Install the gem specified by the spec at appropriate path.
  # `install_path` provides a sufficient default, if the source can only
  # satisfy one gem,  but is not binding.
  #
  # @return [String] post installation message (if any)
  def install(spec, opts)
    raise MalformattedPlugin, "Source plugins need to override the install method."
  end

  # It builds extensions, generates bins and installs them for the spec
  # provided.
  #
  # It depends on `spec.loaded_from` to get full_gem_path. The source
  # plugins should set that.
  #
  # It should be called in `install` after the plugin is done placing the
  # gem at correct install location.
  #
  # It also runs Gem hooks `pre_install`, `post_build` and `post_install`
  #
  # Note: Do not override if you don't know what you are doing.
  def post_install(spec, disable_exts = false)
    opts = { :env_shebang => false, :disable_extensions => disable_exts }
    installer = Bundler::Source::Path::Installer.new(spec, opts)
    installer.post_install
  end

  # A default installation path to install a single gem. If the source
  # servers multiple gems, it's not of much use and the source should one
  # of its own.
  def install_path
    @install_path ||=
      begin
        base_name = File.basename(Bundler::URI.parse(uri).normalize.path)

        gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
      end
  end

  # Parses the gemspec files to find the specs for the gems that can be
  # satisfied by the source.
  #
  # Few important points to keep in mind:
  #   - If the gems are not installed then it shall return specs for all
  #   the gems it can satisfy
  #   - If gem is installed (that is to be detected by the plugin itself)
  #   then it shall return at least the specs that are installed.
  #   - The `loaded_from` for each of the specs shall be correct (it is
  #   used to find the load path)
  #
  # @return [Bundler::Index] index containing the specs
  def specs
    files = fetch_gemspec_files

    Bundler::Index.build do |index|
      files.each do |file|
        next unless spec = Bundler.load_gemspec(file)
        Bundler.rubygems.set_installed_by_version(spec)

        spec.source = self
        Bundler.rubygems.validate(spec)

        index << spec
      end
    end
  end

  # Set internal representation to fetch the gems/specs locally.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the local system.
  def local!
  end

  # Set internal representation to fetch the gems/specs from remote.
  #
  # When this is called, the source should try to fetch the specs and
  # install from remote path.
  def remote!
  end

  # Set internal representation to fetch the gems/specs from app cache.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the path provided by `app_cache_path`.
  def cached!
  end

  # This is called to update the spec and installation.
  #
  # If the source plugin is loaded from lockfile or otherwise, it shall
  # refresh the cache/specs (e.g. git sources can make a fresh clone).
  def unlock!
  end

  # Name of directory where plugin the is expected to cache the gems when
  # #cache is called.
  #
  # Also this name is matched against the directories in cache for pruning
  #
  # This is used by `app_cache_path`
  def app_cache_dirname
    base_name = File.basename(Bundler::URI.parse(uri).normalize.path)
    "#{base_name}-#{uri_hash}"
  end

  # This method is called while caching to save copy of the gems that the
  # source can resolve to path provided by `app_cache_app`so that they can
  # be reinstalled from the cache without querying the remote (i.e. an
  # alternative to remote)
  #
  # This is stored with the app and source plugins should try to provide
  # specs and install only from this cache when `cached!` is called.
  #
  # This cache is different from the internal caching that can be done
  # at sub paths of `cache_path` (from API). This can be though as caching
  # by bundler.
  def cache(spec, custom_path = nil)
    new_cache_path = app_cache_path(custom_path)

    FileUtils.rm_rf(new_cache_path)
    FileUtils.cp_r(install_path, new_cache_path)
    FileUtils.touch(app_cache_path.join(".bundlecache"))
  end

  # This shall check if two source object represent the same source.
  #
  # The comparison shall take place only on the attribute that can be
  # inferred from the options passed from Gemfile and not on attributes
  # that are used to pin down the gem to specific version (e.g. Git
  # sources should compare on branch and tag but not on commit hash)
  #
  # The sources objects are constructed from Gemfile as well as from
  # lockfile. To converge the sources, it is necessary that they match.
  #
  # The same applies for `eql?` and `hash`
  def ==(other)
    other.is_a?(self.class) && uri == other.uri
  end

  # When overriding `eql?` please preserve the behaviour as mentioned in
  # docstring for `==` method.
  alias_method :eql?, :==

  # When overriding `hash` please preserve the behaviour as mentioned in
  # docstring for `==` method, i.e. two methods equal by above comparison
  # should have same hash.
  def hash
    [self.class, uri].hash
  end

  # A helper method, not necessary if not used internally.
  def installed?
    File.directory?(install_path)
  end

  # The full path where the plugin should cache the gem so that it can be
  # installed latter.
  #
  # Note: Do not override if you don't know what you are doing.
  def app_cache_path(custom_path = nil)
    @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def unmet_deps
    specs.unmet_dependency_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def spec_names
    specs.spec_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def add_dependency_names(names)
    @dependencies |= Array(names)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def can_lock?(spec)
    spec.source == self
  end

  # Generates the content to be entered into the lockfile.
  # Saves type and remote and also calls to `options_to_lock`.
  #
  # Plugin should use `options_to_lock` to save information in lockfile
  # and not override this.
  #
  # Note: Do not override if you don't know what you are doing.
  def to_lock
    out = String.new("#{LockfileParser::PLUGIN}\n")
    out << "  remote: #{@uri}\n"
    out << "  type: #{@type}\n"
    options_to_lock.each do |opt, value|
      out << "  #{opt}: #{value}\n"
    end
    out << "  specs:\n"
  end

  def to_s
    "plugin source for #{@type} with uri #{@uri}"
  end
  alias_method :identifier, :to_s

  # NOTE: Do not override if you don't know what you are doing.
  def include?(other)
    other == self
  end

  def uri_hash
    SharedHelpers.digest(:SHA1).hexdigest(uri)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def gem_install_dir
    Bundler.install_path
  end

  # It is used to obtain the full_gem_path.
  #
  # spec's loaded_from path is expanded against this to get full_gem_path
  #
  # Note: Do not override if you don't know what you are doing.
  def root
    Bundler.root
  end

  # @private
  # This API on source might not be stable, and for now we expect plugins
  # to download all specs in `#specs`, so we implement the method for
  # compatibility purposes and leave it undocumented (and don't support)
  # overriding it)
  def double_check_for(*); end
end

#optionsString (readonly)

Returns options passed during initialization (either from lockfile or Gemfile).

Returns:

  • (String)

    options passed during initialization (either from lockfile or Gemfile)



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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/bundler/plugin/api/source.rb', line 41

module Source
  attr_reader :uri, :options, :name
  attr_accessor :dependency_names

  def initialize(opts)
    @options = opts
    @dependency_names = []
    @uri = opts["uri"]
    @type = opts["type"]
    @name = opts["name"] || "#{@type} at #{@uri}"
  end

  # This is used by the default `spec` method to constructs the
  # Specification objects for the gems and versions that can be installed
  # by this source plugin.
  #
  # Note: If the spec method is overridden, this function is not necessary
  #
  # @return [Array<String>] paths of the gemspec files for gems that can
  #                         be installed
  def fetch_gemspec_files
    []
  end

  # Options to be saved in the lockfile so that the source plugin is able
  # to check out same version of gem later.
  #
  # There options are passed when the source plugin is created from the
  # lock file.
  #
  # @return [Hash]
  def options_to_lock
    {}
  end

  # Install the gem specified by the spec at appropriate path.
  # `install_path` provides a sufficient default, if the source can only
  # satisfy one gem,  but is not binding.
  #
  # @return [String] post installation message (if any)
  def install(spec, opts)
    raise MalformattedPlugin, "Source plugins need to override the install method."
  end

  # It builds extensions, generates bins and installs them for the spec
  # provided.
  #
  # It depends on `spec.loaded_from` to get full_gem_path. The source
  # plugins should set that.
  #
  # It should be called in `install` after the plugin is done placing the
  # gem at correct install location.
  #
  # It also runs Gem hooks `pre_install`, `post_build` and `post_install`
  #
  # Note: Do not override if you don't know what you are doing.
  def post_install(spec, disable_exts = false)
    opts = { :env_shebang => false, :disable_extensions => disable_exts }
    installer = Bundler::Source::Path::Installer.new(spec, opts)
    installer.post_install
  end

  # A default installation path to install a single gem. If the source
  # servers multiple gems, it's not of much use and the source should one
  # of its own.
  def install_path
    @install_path ||=
      begin
        base_name = File.basename(Bundler::URI.parse(uri).normalize.path)

        gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
      end
  end

  # Parses the gemspec files to find the specs for the gems that can be
  # satisfied by the source.
  #
  # Few important points to keep in mind:
  #   - If the gems are not installed then it shall return specs for all
  #   the gems it can satisfy
  #   - If gem is installed (that is to be detected by the plugin itself)
  #   then it shall return at least the specs that are installed.
  #   - The `loaded_from` for each of the specs shall be correct (it is
  #   used to find the load path)
  #
  # @return [Bundler::Index] index containing the specs
  def specs
    files = fetch_gemspec_files

    Bundler::Index.build do |index|
      files.each do |file|
        next unless spec = Bundler.load_gemspec(file)
        Bundler.rubygems.set_installed_by_version(spec)

        spec.source = self
        Bundler.rubygems.validate(spec)

        index << spec
      end
    end
  end

  # Set internal representation to fetch the gems/specs locally.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the local system.
  def local!
  end

  # Set internal representation to fetch the gems/specs from remote.
  #
  # When this is called, the source should try to fetch the specs and
  # install from remote path.
  def remote!
  end

  # Set internal representation to fetch the gems/specs from app cache.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the path provided by `app_cache_path`.
  def cached!
  end

  # This is called to update the spec and installation.
  #
  # If the source plugin is loaded from lockfile or otherwise, it shall
  # refresh the cache/specs (e.g. git sources can make a fresh clone).
  def unlock!
  end

  # Name of directory where plugin the is expected to cache the gems when
  # #cache is called.
  #
  # Also this name is matched against the directories in cache for pruning
  #
  # This is used by `app_cache_path`
  def app_cache_dirname
    base_name = File.basename(Bundler::URI.parse(uri).normalize.path)
    "#{base_name}-#{uri_hash}"
  end

  # This method is called while caching to save copy of the gems that the
  # source can resolve to path provided by `app_cache_app`so that they can
  # be reinstalled from the cache without querying the remote (i.e. an
  # alternative to remote)
  #
  # This is stored with the app and source plugins should try to provide
  # specs and install only from this cache when `cached!` is called.
  #
  # This cache is different from the internal caching that can be done
  # at sub paths of `cache_path` (from API). This can be though as caching
  # by bundler.
  def cache(spec, custom_path = nil)
    new_cache_path = app_cache_path(custom_path)

    FileUtils.rm_rf(new_cache_path)
    FileUtils.cp_r(install_path, new_cache_path)
    FileUtils.touch(app_cache_path.join(".bundlecache"))
  end

  # This shall check if two source object represent the same source.
  #
  # The comparison shall take place only on the attribute that can be
  # inferred from the options passed from Gemfile and not on attributes
  # that are used to pin down the gem to specific version (e.g. Git
  # sources should compare on branch and tag but not on commit hash)
  #
  # The sources objects are constructed from Gemfile as well as from
  # lockfile. To converge the sources, it is necessary that they match.
  #
  # The same applies for `eql?` and `hash`
  def ==(other)
    other.is_a?(self.class) && uri == other.uri
  end

  # When overriding `eql?` please preserve the behaviour as mentioned in
  # docstring for `==` method.
  alias_method :eql?, :==

  # When overriding `hash` please preserve the behaviour as mentioned in
  # docstring for `==` method, i.e. two methods equal by above comparison
  # should have same hash.
  def hash
    [self.class, uri].hash
  end

  # A helper method, not necessary if not used internally.
  def installed?
    File.directory?(install_path)
  end

  # The full path where the plugin should cache the gem so that it can be
  # installed latter.
  #
  # Note: Do not override if you don't know what you are doing.
  def app_cache_path(custom_path = nil)
    @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def unmet_deps
    specs.unmet_dependency_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def spec_names
    specs.spec_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def add_dependency_names(names)
    @dependencies |= Array(names)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def can_lock?(spec)
    spec.source == self
  end

  # Generates the content to be entered into the lockfile.
  # Saves type and remote and also calls to `options_to_lock`.
  #
  # Plugin should use `options_to_lock` to save information in lockfile
  # and not override this.
  #
  # Note: Do not override if you don't know what you are doing.
  def to_lock
    out = String.new("#{LockfileParser::PLUGIN}\n")
    out << "  remote: #{@uri}\n"
    out << "  type: #{@type}\n"
    options_to_lock.each do |opt, value|
      out << "  #{opt}: #{value}\n"
    end
    out << "  specs:\n"
  end

  def to_s
    "plugin source for #{@type} with uri #{@uri}"
  end
  alias_method :identifier, :to_s

  # NOTE: Do not override if you don't know what you are doing.
  def include?(other)
    other == self
  end

  def uri_hash
    SharedHelpers.digest(:SHA1).hexdigest(uri)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def gem_install_dir
    Bundler.install_path
  end

  # It is used to obtain the full_gem_path.
  #
  # spec's loaded_from path is expanded against this to get full_gem_path
  #
  # Note: Do not override if you don't know what you are doing.
  def root
    Bundler.root
  end

  # @private
  # This API on source might not be stable, and for now we expect plugins
  # to download all specs in `#specs`, so we implement the method for
  # compatibility purposes and leave it undocumented (and don't support)
  # overriding it)
  def double_check_for(*); end
end

#uriString (readonly)

Returns the remote specified with ‘source` block in Gemfile.

Returns:

  • (String)

    the remote specified with ‘source` block in Gemfile



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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/bundler/plugin/api/source.rb', line 41

module Source
  attr_reader :uri, :options, :name
  attr_accessor :dependency_names

  def initialize(opts)
    @options = opts
    @dependency_names = []
    @uri = opts["uri"]
    @type = opts["type"]
    @name = opts["name"] || "#{@type} at #{@uri}"
  end

  # This is used by the default `spec` method to constructs the
  # Specification objects for the gems and versions that can be installed
  # by this source plugin.
  #
  # Note: If the spec method is overridden, this function is not necessary
  #
  # @return [Array<String>] paths of the gemspec files for gems that can
  #                         be installed
  def fetch_gemspec_files
    []
  end

  # Options to be saved in the lockfile so that the source plugin is able
  # to check out same version of gem later.
  #
  # There options are passed when the source plugin is created from the
  # lock file.
  #
  # @return [Hash]
  def options_to_lock
    {}
  end

  # Install the gem specified by the spec at appropriate path.
  # `install_path` provides a sufficient default, if the source can only
  # satisfy one gem,  but is not binding.
  #
  # @return [String] post installation message (if any)
  def install(spec, opts)
    raise MalformattedPlugin, "Source plugins need to override the install method."
  end

  # It builds extensions, generates bins and installs them for the spec
  # provided.
  #
  # It depends on `spec.loaded_from` to get full_gem_path. The source
  # plugins should set that.
  #
  # It should be called in `install` after the plugin is done placing the
  # gem at correct install location.
  #
  # It also runs Gem hooks `pre_install`, `post_build` and `post_install`
  #
  # Note: Do not override if you don't know what you are doing.
  def post_install(spec, disable_exts = false)
    opts = { :env_shebang => false, :disable_extensions => disable_exts }
    installer = Bundler::Source::Path::Installer.new(spec, opts)
    installer.post_install
  end

  # A default installation path to install a single gem. If the source
  # servers multiple gems, it's not of much use and the source should one
  # of its own.
  def install_path
    @install_path ||=
      begin
        base_name = File.basename(Bundler::URI.parse(uri).normalize.path)

        gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
      end
  end

  # Parses the gemspec files to find the specs for the gems that can be
  # satisfied by the source.
  #
  # Few important points to keep in mind:
  #   - If the gems are not installed then it shall return specs for all
  #   the gems it can satisfy
  #   - If gem is installed (that is to be detected by the plugin itself)
  #   then it shall return at least the specs that are installed.
  #   - The `loaded_from` for each of the specs shall be correct (it is
  #   used to find the load path)
  #
  # @return [Bundler::Index] index containing the specs
  def specs
    files = fetch_gemspec_files

    Bundler::Index.build do |index|
      files.each do |file|
        next unless spec = Bundler.load_gemspec(file)
        Bundler.rubygems.set_installed_by_version(spec)

        spec.source = self
        Bundler.rubygems.validate(spec)

        index << spec
      end
    end
  end

  # Set internal representation to fetch the gems/specs locally.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the local system.
  def local!
  end

  # Set internal representation to fetch the gems/specs from remote.
  #
  # When this is called, the source should try to fetch the specs and
  # install from remote path.
  def remote!
  end

  # Set internal representation to fetch the gems/specs from app cache.
  #
  # When this is called, the source should try to fetch the specs and
  # install from the path provided by `app_cache_path`.
  def cached!
  end

  # This is called to update the spec and installation.
  #
  # If the source plugin is loaded from lockfile or otherwise, it shall
  # refresh the cache/specs (e.g. git sources can make a fresh clone).
  def unlock!
  end

  # Name of directory where plugin the is expected to cache the gems when
  # #cache is called.
  #
  # Also this name is matched against the directories in cache for pruning
  #
  # This is used by `app_cache_path`
  def app_cache_dirname
    base_name = File.basename(Bundler::URI.parse(uri).normalize.path)
    "#{base_name}-#{uri_hash}"
  end

  # This method is called while caching to save copy of the gems that the
  # source can resolve to path provided by `app_cache_app`so that they can
  # be reinstalled from the cache without querying the remote (i.e. an
  # alternative to remote)
  #
  # This is stored with the app and source plugins should try to provide
  # specs and install only from this cache when `cached!` is called.
  #
  # This cache is different from the internal caching that can be done
  # at sub paths of `cache_path` (from API). This can be though as caching
  # by bundler.
  def cache(spec, custom_path = nil)
    new_cache_path = app_cache_path(custom_path)

    FileUtils.rm_rf(new_cache_path)
    FileUtils.cp_r(install_path, new_cache_path)
    FileUtils.touch(app_cache_path.join(".bundlecache"))
  end

  # This shall check if two source object represent the same source.
  #
  # The comparison shall take place only on the attribute that can be
  # inferred from the options passed from Gemfile and not on attributes
  # that are used to pin down the gem to specific version (e.g. Git
  # sources should compare on branch and tag but not on commit hash)
  #
  # The sources objects are constructed from Gemfile as well as from
  # lockfile. To converge the sources, it is necessary that they match.
  #
  # The same applies for `eql?` and `hash`
  def ==(other)
    other.is_a?(self.class) && uri == other.uri
  end

  # When overriding `eql?` please preserve the behaviour as mentioned in
  # docstring for `==` method.
  alias_method :eql?, :==

  # When overriding `hash` please preserve the behaviour as mentioned in
  # docstring for `==` method, i.e. two methods equal by above comparison
  # should have same hash.
  def hash
    [self.class, uri].hash
  end

  # A helper method, not necessary if not used internally.
  def installed?
    File.directory?(install_path)
  end

  # The full path where the plugin should cache the gem so that it can be
  # installed latter.
  #
  # Note: Do not override if you don't know what you are doing.
  def app_cache_path(custom_path = nil)
    @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def unmet_deps
    specs.unmet_dependency_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def spec_names
    specs.spec_names
  end

  # Used by definition.
  #
  # Note: Do not override if you don't know what you are doing.
  def add_dependency_names(names)
    @dependencies |= Array(names)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def can_lock?(spec)
    spec.source == self
  end

  # Generates the content to be entered into the lockfile.
  # Saves type and remote and also calls to `options_to_lock`.
  #
  # Plugin should use `options_to_lock` to save information in lockfile
  # and not override this.
  #
  # Note: Do not override if you don't know what you are doing.
  def to_lock
    out = String.new("#{LockfileParser::PLUGIN}\n")
    out << "  remote: #{@uri}\n"
    out << "  type: #{@type}\n"
    options_to_lock.each do |opt, value|
      out << "  #{opt}: #{value}\n"
    end
    out << "  specs:\n"
  end

  def to_s
    "plugin source for #{@type} with uri #{@uri}"
  end
  alias_method :identifier, :to_s

  # NOTE: Do not override if you don't know what you are doing.
  def include?(other)
    other == self
  end

  def uri_hash
    SharedHelpers.digest(:SHA1).hexdigest(uri)
  end

  # NOTE: Do not override if you don't know what you are doing.
  def gem_install_dir
    Bundler.install_path
  end

  # It is used to obtain the full_gem_path.
  #
  # spec's loaded_from path is expanded against this to get full_gem_path
  #
  # Note: Do not override if you don't know what you are doing.
  def root
    Bundler.root
  end

  # @private
  # This API on source might not be stable, and for now we expect plugins
  # to download all specs in `#specs`, so we implement the method for
  # compatibility purposes and leave it undocumented (and don't support)
  # overriding it)
  def double_check_for(*); end
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?

This shall check if two source object represent the same source.

The comparison shall take place only on the attribute that can be inferred from the options passed from Gemfile and not on attributes that are used to pin down the gem to specific version (e.g. Git sources should compare on branch and tag but not on commit hash)

The sources objects are constructed from Gemfile as well as from lockfile. To converge the sources, it is necessary that they match.

The same applies for ‘eql?` and `hash`



212
213
214
# File 'lib/bundler/plugin/api/source.rb', line 212

def ==(other)
  other.is_a?(self.class) && uri == other.uri
end

#add_dependency_names(names) ⇒ Object

Used by definition.

Note: Do not override if you don’t know what you are doing.



257
258
259
# File 'lib/bundler/plugin/api/source.rb', line 257

def add_dependency_names(names)
  @dependencies |= Array(names)
end

#app_cache_dirnameObject

Name of directory where plugin the is expected to cache the gems when #cache is called.

Also this name is matched against the directories in cache for pruning

This is used by ‘app_cache_path`



177
178
179
180
# File 'lib/bundler/plugin/api/source.rb', line 177

def app_cache_dirname
  base_name = File.basename(Bundler::URI.parse(uri).normalize.path)
  "#{base_name}-#{uri_hash}"
end

#app_cache_path(custom_path = nil) ⇒ Object

The full path where the plugin should cache the gem so that it can be installed latter.

Note: Do not override if you don’t know what you are doing.



236
237
238
# File 'lib/bundler/plugin/api/source.rb', line 236

def app_cache_path(custom_path = nil)
  @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
end

#cache(spec, custom_path = nil) ⇒ Object

This method is called while caching to save copy of the gems that the source can resolve to path provided by ‘app_cache_app`so that they can be reinstalled from the cache without querying the remote (i.e. an alternative to remote)

This is stored with the app and source plugins should try to provide specs and install only from this cache when ‘cached!` is called.

This cache is different from the internal caching that can be done at sub paths of ‘cache_path` (from API). This can be though as caching by bundler.



193
194
195
196
197
198
199
# File 'lib/bundler/plugin/api/source.rb', line 193

def cache(spec, custom_path = nil)
  new_cache_path = app_cache_path(custom_path)

  FileUtils.rm_rf(new_cache_path)
  FileUtils.cp_r(install_path, new_cache_path)
  FileUtils.touch(app_cache_path.join(".bundlecache"))
end

#cached!Object

Set internal representation to fetch the gems/specs from app cache.

When this is called, the source should try to fetch the specs and install from the path provided by ‘app_cache_path`.



161
162
# File 'lib/bundler/plugin/api/source.rb', line 161

def cached!
end

#can_lock?(spec) ⇒ Boolean

NOTE: Do not override if you don’t know what you are doing.

Returns:

  • (Boolean)


262
263
264
# File 'lib/bundler/plugin/api/source.rb', line 262

def can_lock?(spec)
  spec.source == self
end

#double_check_forObject

This API on source might not be stable, and for now we expect plugins to download all specs in ‘#specs`, so we implement the method for compatibility purposes and leave it undocumented (and don’t support) overriding it)



316
# File 'lib/bundler/plugin/api/source.rb', line 316

def double_check_for(*); end

#fetch_gemspec_filesArray<String>

This is used by the default ‘spec` method to constructs the Specification objects for the gems and versions that can be installed by this source plugin.

Note: If the spec method is overridden, this function is not necessary

Returns:

  • (Array<String>)

    paths of the gemspec files for gems that can be installed



61
62
63
# File 'lib/bundler/plugin/api/source.rb', line 61

def fetch_gemspec_files
  []
end

#gem_install_dirObject

NOTE: Do not override if you don’t know what you are doing.



298
299
300
# File 'lib/bundler/plugin/api/source.rb', line 298

def gem_install_dir
  Bundler.install_path
end

#hashObject

When overriding ‘hash` please preserve the behaviour as mentioned in docstring for `==` method, i.e. two methods equal by above comparison should have same hash.



223
224
225
# File 'lib/bundler/plugin/api/source.rb', line 223

def hash
  [self.class, uri].hash
end

#include?(other) ⇒ Boolean

NOTE: Do not override if you don’t know what you are doing.

Returns:

  • (Boolean)


289
290
291
# File 'lib/bundler/plugin/api/source.rb', line 289

def include?(other)
  other == self
end

#initialize(opts) ⇒ Object



45
46
47
48
49
50
51
# File 'lib/bundler/plugin/api/source.rb', line 45

def initialize(opts)
  @options = opts
  @dependency_names = []
  @uri = opts["uri"]
  @type = opts["type"]
  @name = opts["name"] || "#{@type} at #{@uri}"
end

#install(spec, opts) ⇒ String

Install the gem specified by the spec at appropriate path. ‘install_path` provides a sufficient default, if the source can only satisfy one gem, but is not binding.

Returns:

  • (String)

    post installation message (if any)

Raises:



81
82
83
# File 'lib/bundler/plugin/api/source.rb', line 81

def install(spec, opts)
  raise MalformattedPlugin, "Source plugins need to override the install method."
end

#install_pathObject

A default installation path to install a single gem. If the source servers multiple gems, it’s not of much use and the source should one of its own.



106
107
108
109
110
111
112
113
# File 'lib/bundler/plugin/api/source.rb', line 106

def install_path
  @install_path ||=
    begin
      base_name = File.basename(Bundler::URI.parse(uri).normalize.path)

      gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}")
    end
end

#installed?Boolean

A helper method, not necessary if not used internally.

Returns:

  • (Boolean)


228
229
230
# File 'lib/bundler/plugin/api/source.rb', line 228

def installed?
  File.directory?(install_path)
end

#local!Object

Set internal representation to fetch the gems/specs locally.

When this is called, the source should try to fetch the specs and install from the local system.



147
148
# File 'lib/bundler/plugin/api/source.rb', line 147

def local!
end

#options_to_lockHash

Options to be saved in the lockfile so that the source plugin is able to check out same version of gem later.

There options are passed when the source plugin is created from the lock file.

Returns:

  • (Hash)


72
73
74
# File 'lib/bundler/plugin/api/source.rb', line 72

def options_to_lock
  {}
end

#post_install(spec, disable_exts = false) ⇒ Object

It builds extensions, generates bins and installs them for the spec provided.

It depends on ‘spec.loaded_from` to get full_gem_path. The source plugins should set that.

It should be called in ‘install` after the plugin is done placing the gem at correct install location.

It also runs Gem hooks ‘pre_install`, `post_build` and `post_install`

Note: Do not override if you don’t know what you are doing.



97
98
99
100
101
# File 'lib/bundler/plugin/api/source.rb', line 97

def post_install(spec, disable_exts = false)
  opts = { :env_shebang => false, :disable_extensions => disable_exts }
  installer = Bundler::Source::Path::Installer.new(spec, opts)
  installer.post_install
end

#remote!Object

Set internal representation to fetch the gems/specs from remote.

When this is called, the source should try to fetch the specs and install from remote path.



154
155
# File 'lib/bundler/plugin/api/source.rb', line 154

def remote!
end

#rootObject

It is used to obtain the full_gem_path.

spec’s loaded_from path is expanded against this to get full_gem_path

Note: Do not override if you don’t know what you are doing.



307
308
309
# File 'lib/bundler/plugin/api/source.rb', line 307

def root
  Bundler.root
end

#spec_namesObject

Used by definition.

Note: Do not override if you don’t know what you are doing.



250
251
252
# File 'lib/bundler/plugin/api/source.rb', line 250

def spec_names
  specs.spec_names
end

#specsBundler::Index

Parses the gemspec files to find the specs for the gems that can be satisfied by the source.

Few important points to keep in mind:

- If the gems are not installed then it shall return specs for all
the gems it can satisfy
- If gem is installed (that is to be detected by the plugin itself)
then it shall return at least the specs that are installed.
- The `loaded_from` for each of the specs shall be correct (it is
used to find the load path)

Returns:



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/bundler/plugin/api/source.rb', line 127

def specs
  files = fetch_gemspec_files

  Bundler::Index.build do |index|
    files.each do |file|
      next unless spec = Bundler.load_gemspec(file)
      Bundler.rubygems.set_installed_by_version(spec)

      spec.source = self
      Bundler.rubygems.validate(spec)

      index << spec
    end
  end
end

#to_lockObject

Generates the content to be entered into the lockfile. Saves type and remote and also calls to ‘options_to_lock`.

Plugin should use ‘options_to_lock` to save information in lockfile and not override this.

Note: Do not override if you don’t know what you are doing.



273
274
275
276
277
278
279
280
281
# File 'lib/bundler/plugin/api/source.rb', line 273

def to_lock
  out = String.new("#{LockfileParser::PLUGIN}\n")
  out << "  remote: #{@uri}\n"
  out << "  type: #{@type}\n"
  options_to_lock.each do |opt, value|
    out << "  #{opt}: #{value}\n"
  end
  out << "  specs:\n"
end

#to_sObject Also known as: identifier



283
284
285
# File 'lib/bundler/plugin/api/source.rb', line 283

def to_s
  "plugin source for #{@type} with uri #{@uri}"
end

#unlock!Object

This is called to update the spec and installation.

If the source plugin is loaded from lockfile or otherwise, it shall refresh the cache/specs (e.g. git sources can make a fresh clone).



168
169
# File 'lib/bundler/plugin/api/source.rb', line 168

def unlock!
end

#unmet_depsObject

Used by definition.

Note: Do not override if you don’t know what you are doing.



243
244
245
# File 'lib/bundler/plugin/api/source.rb', line 243

def unmet_deps
  specs.unmet_dependency_names
end

#uri_hashObject



293
294
295
# File 'lib/bundler/plugin/api/source.rb', line 293

def uri_hash
  SharedHelpers.digest(:SHA1).hexdigest(uri)
end