Class: Bundler::Definition

Inherits:
Object
  • Object
show all
Includes:
GemHelpers
Defined in:
lib/bundler/definition.rb

Constant Summary

Constants included from GemHelpers

GemHelpers::GENERICS, GemHelpers::GENERIC_CACHE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from GemHelpers

#generic

Constructor Details

#initialize(lockfile, dependencies, sources, unlock) ⇒ Definition

How does the new system work?

===
* Load information from Gemfile and Lockfile
* Invalidate stale locked specs
  * All specs from stale source are stale
  * All specs that are reachable only through a stale
    dependency are stale.
* If all fresh dependencies are satisfied by the locked
  specs, then we can try to resolve locally.


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

def initialize(lockfile, dependencies, sources, unlock)
  @dependencies, @sources, @unlock = dependencies, sources, unlock
  @remote            = false
  @specs             = nil
  @lockfile_contents = ""

  if lockfile && File.exists?(lockfile)
    @lockfile_contents = Bundler.read_file(lockfile)
    locked = LockfileParser.new(@lockfile_contents)
    @platforms      = locked.platforms

    if unlock != true
      @locked_deps    = locked.dependencies
      @locked_specs   = SpecSet.new(locked.specs)
      @locked_sources = locked.sources
    else
      @unlock         = {}
      @locked_deps    = []
      @locked_specs   = SpecSet.new([])
      @locked_sources = []
    end
  else
    @unlock         = {}
    @platforms      = []
    @locked_deps    = []
    @locked_specs   = SpecSet.new([])
    @locked_sources = []
  end

  @unlock[:gems] ||= []
  @unlock[:sources] ||= []

  current_platform = Gem.platforms.map { |p| generic(p) }.compact.last
  @new_platform = !@platforms.include?(current_platform)
  @platforms |= [current_platform]

  eager_unlock = expand_dependencies(@unlock[:gems])
  @unlock[:gems] = @locked_specs.for(eager_unlock).map { |s| s.name }

  converge_sources
  converge_dependencies
end

Instance Attribute Details

#dependenciesObject (readonly)

Returns the value of attribute dependencies.



7
8
9
# File 'lib/bundler/definition.rb', line 7

def dependencies
  @dependencies
end

#platformsObject (readonly)

Returns the value of attribute platforms.



7
8
9
# File 'lib/bundler/definition.rb', line 7

def platforms
  @platforms
end

#sourcesObject (readonly)

Returns the value of attribute sources.



7
8
9
# File 'lib/bundler/definition.rb', line 7

def sources
  @sources
end

Class Method Details

.build(gemfile, lockfile, unlock) ⇒ Object



9
10
11
12
13
14
15
16
17
18
# File 'lib/bundler/definition.rb', line 9

def self.build(gemfile, lockfile, unlock)
  unlock ||= {}
  gemfile = Pathname.new(gemfile).expand_path

  unless gemfile.file?
    raise GemfileNotFound, "#{gemfile} not found"
  end

  Dsl.evaluate(gemfile, lockfile, unlock)
end

Instance Method Details

#current_dependenciesObject



128
129
130
# File 'lib/bundler/definition.rb', line 128

def current_dependencies
  dependencies.reject { |d| !d.should_include? }
end

#ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) ⇒ Object

Raises:



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

def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
  changes = false

  msg = "You are trying to install in deployment mode after changing\n" \
        "your Gemfile. Run `bundle install` elsewhere and add the\n" \
        "updated Gemfile.lock to version control."

  unless explicit_flag
    msg += "\n\nIf this is a development machine, remove the Gemfile " \
           "freeze \nby running `bundle install --no-deployment`."
  end

  added =   []
  deleted = []
  changed = []

  if @locked_sources != @sources
    new_sources = @sources - @locked_sources
    deleted_sources = @locked_sources - @sources

    if new_sources.any?
      added.concat new_sources.map { |source| "* source: #{source}" }
    end

    if deleted_sources.any?
      deleted.concat deleted_sources.map { |source| "* source: #{source}" }
    end

    changes = true
  end

  both_sources = Hash.new { |h,k| h[k] = ["no specified source", "no specified source"] }
  @dependencies.each { |d| both_sources[d.name][0] = d.source if d.source }
  @locked_deps.each  { |d| both_sources[d.name][1] = d.source if d.source }
  both_sources.delete_if { |k,v| v[0] == v[1] }

  if @dependencies != @locked_deps
    new_deps = @dependencies - @locked_deps
    deleted_deps = @locked_deps - @dependencies

    if new_deps.any?
      added.concat new_deps.map { |d| "* #{pretty_dep(d)}" }
    end

    if deleted_deps.any?
      deleted.concat deleted_deps.map { |d| "* #{pretty_dep(d)}" }
    end

    both_sources.each do |name, sources|
      changed << "* #{name} from `#{sources[0]}` to `#{sources[1]}`"
    end

    changes = true
  end

  msg << "\n\nYou have added to the Gemfile:\n"     << added.join("\n") if added.any?
  msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
  msg << "\n\nYou have changed in the Gemfile:\n"   << changed.join("\n") if changed.any?
  msg << "\n"

  raise ProductionError, msg if added.any? || deleted.any? || changed.any?
end

#groupsObject



176
177
178
# File 'lib/bundler/definition.rb', line 176

def groups
  dependencies.map { |d| d.groups }.flatten.uniq
end

#indexObject



156
157
158
159
160
161
162
# File 'lib/bundler/definition.rb', line 156

def index
  @index ||= Index.build do |idx|
    @sources.each do |s|
      idx.use s.specs
    end
  end
end

#lock(file) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/bundler/definition.rb', line 180

def lock(file)
  contents = to_lock

  return if @lockfile_contents == contents

  if Bundler.settings[:frozen]
    # TODO: Warn here if we got here.
    return
  end

  # Convert to \r\n if the existing lock has them
  # i.e., Windows with `git config core.autocrlf=true`
  contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n")

  File.open(file, 'wb'){|f| f.puts(contents) }
end

#missing_specsObject



114
115
116
117
118
# File 'lib/bundler/definition.rb', line 114

def missing_specs
  missing = []
  resolve.materialize(requested_dependencies, missing)
  missing
end

#new_platform?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/bundler/definition.rb', line 110

def new_platform?
  @new_platform
end

#new_specsObject



102
103
104
# File 'lib/bundler/definition.rb', line 102

def new_specs
  specs - @locked_specs
end

#no_sources?Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/bundler/definition.rb', line 172

def no_sources?
  @sources.length == 1 && @sources.first.remotes.empty?
end

#removed_specsObject



106
107
108
# File 'lib/bundler/definition.rb', line 106

def removed_specs
  @locked_specs - specs
end

#requested_specsObject



120
121
122
123
124
125
126
# File 'lib/bundler/definition.rb', line 120

def requested_specs
  @requested_specs ||= begin
    groups = self.groups - Bundler.settings.without
    groups.map! { |g| g.to_sym }
    specs_for(groups)
  end
end

#resolveObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/bundler/definition.rb', line 138

def resolve
  @resolve ||= begin
    if Bundler.settings[:frozen]
      @locked_specs
    else
      last_resolve = converge_locked_specs
      source_requirements = {}
      dependencies.each do |dep|
        next unless dep.source
        source_requirements[dep.name] = dep.source.specs
      end

      # Run a resolve against the locally available gems
      last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve)
    end
  end
end

#resolve_remotely!Object



81
82
83
84
85
86
# File 'lib/bundler/definition.rb', line 81

def resolve_remotely!
  raise "Specs already loaded" if @specs
  @remote = true
  @sources.each { |s| s.remote! }
  specs
end

#resolve_with_cache!Object



75
76
77
78
79
# File 'lib/bundler/definition.rb', line 75

def resolve_with_cache!
  raise "Specs already loaded" if @specs
  @sources.each { |s| s.cached! }
  specs
end

#rubygems_indexObject



164
165
166
167
168
169
170
# File 'lib/bundler/definition.rb', line 164

def rubygems_index
  @rubygems_index ||= Index.build do |idx|
    @sources.find_all{|s| s.is_a?(Source::Rubygems) }.each do |s|
      idx.use s.specs
    end
  end
end

#specsObject



88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/bundler/definition.rb', line 88

def specs
  @specs ||= begin
    specs = resolve.materialize(requested_dependencies)

    unless specs["bundler"].any?
      local = Bundler.settings[:frozen] ? rubygems_index : index
      bundler = local.search(Gem::Dependency.new('bundler', VERSION)).last
      specs["bundler"] = bundler if bundler
    end

    specs
  end
end

#specs_for(groups) ⇒ Object



132
133
134
135
136
# File 'lib/bundler/definition.rb', line 132

def specs_for(groups)
  deps = dependencies.select { |d| (d.groups & groups).any? }
  deps.delete_if { |d| !d.should_include? }
  specs.for(expand_dependencies(deps))
end

#to_lockObject



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

def to_lock
  out = ""

  sorted_sources.each do |source|
    # Add the source header
    out << source.to_lock
    # Find all specs for this source
    resolve.
      select  { |s| s.source == source }.
      # This needs to be sorted by full name so that
      # gems with the same name, but different platform
      # are ordered consistantly
      sort_by { |s| s.full_name }.
      each do |spec|
        next if spec.name == 'bundler'
        out << spec.to_lock
    end
    out << "\n"
  end

  out << "PLATFORMS\n"

  platforms.map { |p| p.to_s }.sort.each do |p|
    out << "  #{p}\n"
  end

  out << "\n"
  out << "DEPENDENCIES\n"

  handled = []
  dependencies.
    sort_by { |d| d.to_s }.
    each do |dep|
      next if handled.include?(dep.name)
      out << dep.to_lock
      handled << dep.name
  end

  out
end