Class: Vagrant::Bundler

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant/bundler.rb

Overview

This class manages Vagrant's interaction with Bundler. Vagrant uses Bundler as a way to properly resolve all dependencies of Vagrant and all Vagrant-installed plugins.

Defined Under Namespace

Classes: BuiltinSet, PluginSet, VagrantSet

Constant Summary collapse

HASHICORP_GEMSTORE =

Location of HashiCorp gem repository

"https://gems.hashicorp.com/".freeze
DEFAULT_GEM_SOURCES =

Default gem repositories

[
  HASHICORP_GEMSTORE,
  "https://rubygems.org/".freeze
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBundler

Returns a new instance of Bundler.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/vagrant/bundler.rb', line 40

def initialize
  @plugin_gem_path = Vagrant.user_data_path.join("gems", RUBY_VERSION).freeze
  @logger = Log4r::Logger.new("vagrant::bundler")

  # TODO: Remove fix when https://github.com/rubygems/rubygems/pull/2735
  # gets merged and released
  #
  # Because of a rubygems bug, we need to set the gemrc file path
  # through this method rather than relying on the environment varible
  # GEMRC. On windows, that path gets split on `:`: and `;`, which means
  # the drive letter gets treated as its own path. If that path exists locally,
  # (like having a random folder called `c` where the library was invoked),
  # it fails thinking the folder `c` is a gemrc file.
  gemrc_val = ENV["GEMRC"]
  ENV["GEMRC"] = ""
  Gem.configuration = Gem::ConfigFile.new(["--config-file", gemrc_val])
  ENV["GEMRC"] = gemrc_val
end

Instance Attribute Details

#env_plugin_gem_pathPathname (readonly)

Returns Vagrant environment specific plugin path.

Returns:

  • (Pathname)

    Vagrant environment specific plugin path



38
39
40
# File 'lib/vagrant/bundler.rb', line 38

def env_plugin_gem_path
  @env_plugin_gem_path
end

#plugin_gem_pathPathname (readonly)

Returns Global plugin path.

Returns:

  • (Pathname)

    Global plugin path



36
37
38
# File 'lib/vagrant/bundler.rb', line 36

def plugin_gem_path
  @plugin_gem_path
end

Class Method Details

.instanceObject



31
32
33
# File 'lib/vagrant/bundler.rb', line 31

def self.instance
  @bundler ||= self.new
end

Instance Method Details

#clean(plugins, **opts) ⇒ Object

Clean removes any unused gems.



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
# File 'lib/vagrant/bundler.rb', line 176

def clean(plugins, **opts)
  @logger.debug("Cleaning Vagrant plugins of stale gems.")
  # Generate dependencies for all registered plugins
  plugin_deps = plugins.map do |name, info|
    gem_version = info['installed_gem_version']
    gem_version = info['gem_version'] if gem_version.to_s.empty?
    gem_version = "> 0" if gem_version.to_s.empty?
    Gem::Dependency.new(name, gem_version)
  end

  @logger.debug("Current plugin dependency list: #{plugin_deps}")

  # Load dependencies into a request set for resolution
  request_set = Gem::RequestSet.new(*plugin_deps)
  # Never allow dependencies to be remotely satisfied during cleaning
  request_set.remote = false

  # Sets that we can resolve our dependencies from. Note that we only
  # resolve from the current set as all required deps are activated during
  # init.
  current_set = generate_vagrant_set

  # Collect all plugin specifications
  plugin_specs = Dir.glob(plugin_gem_path.join('specifications/*.gemspec').to_s).map do |spec_path|
    Gem::Specification.load(spec_path)
  end

  # Include environment specific specification if enabled
  if env_plugin_gem_path
    plugin_specs += Dir.glob(env_plugin_gem_path.join('specifications/*.gemspec').to_s).map do |spec_path|
      Gem::Specification.load(spec_path)
    end
  end

  @logger.debug("Generating current plugin state solution set.")

  # Resolve the request set to ensure proper activation order
  solution = request_set.resolve(current_set)
  solution_specs = solution.map(&:full_spec)
  solution_full_names = solution_specs.map(&:full_name)

  # Find all specs installed to plugins directory that are not
  # found within the solution set.
  plugin_specs.delete_if do |spec|
    solution_full_names.include?(spec.full_name)
  end

  if env_plugin_gem_path
    # If we are cleaning locally, remove any global specs. If
    # not, remove any local specs
    if opts[:env_local]
      @logger.debug("Removing specifications that are not environment local")
      plugin_specs.delete_if do |spec|
        spec.full_gem_path.to_s.include?(plugin_gem_path.realpath.to_s)
      end
    else
      @logger.debug("Removing specifications that are environment local")
      plugin_specs.delete_if do |spec|
        spec.full_gem_path.to_s.include?(env_plugin_gem_path.realpath.to_s)
      end
    end
  end

  @logger.debug("Specifications to be removed - #{plugin_specs.map(&:full_name)}")

  # Now delete all unused specs
  plugin_specs.each do |spec|
    @logger.debug("Uninstalling gem - #{spec.full_name}")
    Gem::Uninstaller.new(spec.name,
      version: spec.version,
      install_dir: plugin_gem_path,
      all: true,
      executables: true,
      force: true,
      ignore: true,
    ).uninstall_gem(spec)
  end

  solution.find_all do |spec|
    plugins.keys.include?(spec.name)
  end
end

#deinitObject

Removes any temporary files created by init



133
134
135
# File 'lib/vagrant/bundler.rb', line 133

def deinit
  # no-op
end

#environment_path=(env_data_path) ⇒ Pathname

Enable Vagrant environment specific plugins at given data path

Parameters:

  • Path (Pathname)

    to Vagrant::Environment data directory

Returns:

  • (Pathname)

    Path to environment specific gem directory



63
64
65
# File 'lib/vagrant/bundler.rb', line 63

def environment_path=(env_data_path)
  @env_plugin_gem_path = env_data_path.join("plugins", "gems", RUBY_VERSION).freeze
end

#init!(plugins, repair = false) ⇒ Object

Initializes Bundler and the various gem paths so that we can begin loading gems.



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
# File 'lib/vagrant/bundler.rb', line 69

def init!(plugins, repair=false)
  if !@initial_specifications
    @initial_specifications = Gem::Specification.find_all{true}
  else
    Gem::Specification.all = @initial_specifications
    Gem::Specification.reset
  end

  # Add HashiCorp RubyGems source
  if !Gem.sources.include?(HASHICORP_GEMSTORE)
    sources = [HASHICORP_GEMSTORE] + Gem.sources.sources
    Gem.sources.replace(sources)
  end

  # Generate dependencies for all registered plugins
  plugin_deps = plugins.map do |name, info|
    Gem::Dependency.new(name, info['installed_gem_version'].to_s.empty? ? '> 0' : info['installed_gem_version'])
  end

  @logger.debug("Current generated plugin dependency list: #{plugin_deps}")

  # Load dependencies into a request set for resolution
  request_set = Gem::RequestSet.new(*plugin_deps)
  # Never allow dependencies to be remotely satisfied during init
  request_set.remote = false

  repair_result = nil
  begin
    # Compose set for resolution
    composed_set = generate_vagrant_set
    # Resolve the request set to ensure proper activation order
    solution = request_set.resolve(composed_set)
  rescue Gem::UnsatisfiableDependencyError => failure
    if repair
      raise failure if @init_retried
      @logger.debug("Resolution failed but attempting to repair. Failure: #{failure}")
      install(plugins)
      @init_retried = true
      retry
    else
      raise
    end
  end

  # Activate the gems
  activate_solution(solution)

  full_vagrant_spec_list = @initial_specifications +
    solution.map(&:full_spec)

  if(defined?(::Bundler))
    @logger.debug("Updating Bundler with full specification list")
    ::Bundler.rubygems.replace_entrypoints(full_vagrant_spec_list)
  end

  Gem.post_reset do
    Gem::Specification.all = full_vagrant_spec_list
  end

  Gem::Specification.reset
  nil
end

#install(plugins, env_local = false) ⇒ Array<Gem::Specification>

Installs the list of plugins.

Parameters:

  • plugins (Hash)
  • env_local (Boolean) (defaults to: false)

    Environment local plugin install

Returns:



142
143
144
# File 'lib/vagrant/bundler.rb', line 142

def install(plugins, env_local=false)
  internal_install(plugins, nil, env_local: env_local)
end

#install_local(path, opts = {}) ⇒ Gem::Specification

Installs a local '*.gem' file so that Bundler can find it.

Parameters:

  • path (String)

    Path to a local gem file.

Returns:



150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/vagrant/bundler.rb', line 150

def install_local(path, opts={})
  plugin_source = Gem::Source::SpecificFile.new(path)
  plugin_info = {
    plugin_source.spec.name => {
      "gem_version" => plugin_source.spec.version.to_s,
      "local_source" => plugin_source,
      "sources" => opts.fetch(:sources, [])
    }
  }
  @logger.debug("Installing local plugin - #{plugin_info}")
  internal_install(plugin_info, nil, env_local: opts[:env_local])
  plugin_source.spec
end

#update(plugins, specific, **opts) ⇒ Object

Update updates the given plugins, or every plugin if none is given.

Parameters:

  • plugins (Hash)
  • specific (Array<String>)

    Specific plugin names to update. If empty or nil, all plugins will be updated.



169
170
171
172
173
# File 'lib/vagrant/bundler.rb', line 169

def update(plugins, specific, **opts)
  specific ||= []
  update = opts.merge({gems: specific.empty? ? true : specific})
  internal_install(plugins, update)
end

#verboseObject

During the duration of the yielded block, Bundler loud output is enabled.



261
262
263
264
265
266
267
268
269
270
# File 'lib/vagrant/bundler.rb', line 261

def verbose
  if block_given?
    initial_state = @verbose
    @verbose = true
    yield
    @verbose = initial_state
  else
    @verbose = true
  end
end