bundler 0.9.26 → 1.0.0.beta.1

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.

@@ -2,74 +2,243 @@ require "digest/sha1"
2
2
 
3
3
  module Bundler
4
4
  class Definition
5
- def self.from_gemfile(gemfile)
5
+ attr_reader :dependencies, :platforms
6
+
7
+ def self.build(gemfile, lockfile, unlock)
8
+ unlock ||= {}
6
9
  gemfile = Pathname.new(gemfile).expand_path
7
10
 
8
11
  unless gemfile.file?
9
12
  raise GemfileNotFound, "#{gemfile} not found"
10
13
  end
11
14
 
12
- Dsl.evaluate(gemfile)
15
+ # TODO: move this back into DSL
16
+ builder = Dsl.new
17
+ builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1)
18
+ builder.to_definition(lockfile, unlock)
13
19
  end
14
20
 
15
- def self.from_lock(lockfile)
16
- begin
17
- locked_definition = Locked.new(YAML.load_file(lockfile))
18
- rescue ArgumentError
19
- raise GemfileError, "Your Gemfile.lock was generated by Bundler 0.10.\n" +
20
- "You must delete it if you wish to use Bundler 0.9."
21
+ =begin
22
+ How does the new system work?
23
+ ===
24
+ * Load information from Gemfile and Lockfile
25
+ * Invalidate stale locked specs
26
+ * All specs from stale source are stale
27
+ * All specs that are reachable only through a stale
28
+ dependency are stale.
29
+ * If all fresh dependencies are satisfied by the locked
30
+ specs, then we can try to resolve locally.
31
+ =end
32
+
33
+ def initialize(lockfile, dependencies, sources, unlock)
34
+ @dependencies, @sources, @unlock = dependencies, sources, unlock
35
+ @specs = nil
36
+ @unlock[:gems] ||= []
37
+ @unlock[:sources] ||= []
38
+
39
+ if lockfile && File.exists?(lockfile)
40
+ locked = LockfileParser.new(File.read(lockfile))
41
+ @platforms = locked.platforms
42
+ @locked_deps = locked.dependencies
43
+ @last_resolve = SpecSet.new(locked.specs)
44
+ @locked_sources = locked.sources
45
+ else
46
+ @platforms = []
47
+ @locked_deps = []
48
+ @last_resolve = SpecSet.new([])
49
+ @locked_sources = []
21
50
  end
22
51
 
23
- hash = Digest::SHA1.hexdigest(File.read("#{Bundler.root}/Gemfile"))
24
- unless locked_definition.hash == hash
25
- raise GemfileChanged, "You changed your Gemfile after locking. Please relock using `bundle lock`"
52
+ current_platform = Gem.platforms.map { |p| p.to_generic }.compact.last
53
+ @platforms |= [current_platform]
54
+
55
+ converge
56
+ end
57
+
58
+ def resolve_remotely!
59
+ raise "Specs already loaded" if @specs
60
+ @sources.each { |s| s.remote! }
61
+ specs
62
+ end
63
+
64
+ def specs
65
+ @specs ||= resolve.materialize(requested_dependencies)
66
+ end
67
+
68
+ def missing_specs
69
+ missing = []
70
+ resolve.materialize(requested_dependencies, missing)
71
+ missing
72
+ end
73
+
74
+ def requested_specs
75
+ @requested_specs ||= begin
76
+ groups = self.groups - Bundler.settings.without
77
+ groups.map! { |g| g.to_sym }
78
+ specs_for(groups)
26
79
  end
80
+ end
81
+
82
+ def current_dependencies
83
+ dependencies.reject { |d| !d.should_include? }
84
+ end
27
85
 
28
- locked_definition
86
+ def specs_for(groups)
87
+ deps = dependencies.select { |d| (d.groups & groups).any? }
88
+ deps.delete_if { |d| !d.should_include? }
89
+ specs.for(expand_dependencies(deps))
29
90
  end
30
91
 
31
- attr_reader :dependencies, :sources
92
+ def resolve
93
+ @resolve ||= begin
94
+ if @last_resolve.valid_for?(expanded_dependencies)
95
+ @last_resolve
96
+ else
97
+ source_requirements = {}
98
+ dependencies.each do |dep|
99
+ next unless dep.source
100
+ source_requirements[dep.name] = dep.source.specs
101
+ end
32
102
 
33
- alias actual_dependencies dependencies
103
+ # Run a resolve against the locally available gems
104
+ Resolver.resolve(expanded_dependencies, index, source_requirements, @last_resolve)
105
+ end
106
+ end
107
+ end
108
+
109
+ def index
110
+ @index ||= Index.build do |idx|
111
+ @sources.each do |s|
112
+ idx.use s.specs
113
+ end
114
+ end
115
+ end
34
116
 
35
- def initialize(dependencies, sources)
36
- @dependencies = dependencies
37
- @sources = sources
117
+ def no_sources?
118
+ @sources.length == 1 && @sources.first.remotes.empty?
38
119
  end
39
120
 
40
121
  def groups
41
122
  dependencies.map { |d| d.groups }.flatten.uniq
42
123
  end
43
124
 
44
- class Locked < Definition
45
- def initialize(details)
46
- @details = details
125
+ def to_lock
126
+ out = ""
127
+
128
+ sorted_sources.each do |source|
129
+ # Add the source header
130
+ out << source.to_lock
131
+ # Find all specs for this source
132
+ resolve.
133
+ select { |s| s.source == source }.
134
+ sort_by { |s| [s.name, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }.
135
+ each do |spec|
136
+ out << spec.to_lock
137
+ end
138
+ out << "\n"
139
+ end
140
+
141
+ out << "PLATFORMS\n"
142
+
143
+ platforms.map { |p| p.to_s }.sort.each do |p|
144
+ out << " #{p}\n"
145
+ end
146
+
147
+ out << "\n"
148
+ out << "DEPENDENCIES\n"
149
+
150
+ dependencies.
151
+ sort_by { |d| d.name }.
152
+ each do |dep|
153
+ out << dep.to_lock
47
154
  end
48
155
 
49
- def hash
50
- @details["hash"]
156
+ out
157
+ end
158
+
159
+ private
160
+
161
+ def converge
162
+ converge_sources
163
+ converge_dependencies
164
+ converge_locked_specs
165
+ end
166
+
167
+ def converge_sources
168
+ @sources = (@locked_sources & @sources) | @sources
169
+ @sources.each do |source|
170
+ source.unlock! if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
51
171
  end
172
+ end
52
173
 
53
- def sources
54
- @sources ||= @details["sources"].map do |args|
55
- name, options = args.to_a.flatten
56
- Bundler::Source.const_get(name).new(options)
174
+ def converge_dependencies
175
+ (@dependencies + @locked_deps).each do |dep|
176
+ if dep.source
177
+ source = @sources.find { |s| dep.source == s }
178
+ raise "Something went wrong, there is no matching source" unless source
179
+ dep.source = source
57
180
  end
58
181
  end
182
+ end
59
183
 
60
- def actual_dependencies
61
- @actual_dependencies ||= @details["specs"].map do |args|
62
- name, details = args.to_a.flatten
63
- details["source"] = sources[details["source"]] if details.include?("source")
64
- Bundler::Dependency.new(name, details.delete("version"), details)
184
+ def converge_locked_specs
185
+ deps = []
186
+
187
+ @dependencies.each do |dep|
188
+ if in_locked_deps?(dep) || satisfies_locked_spec?(dep)
189
+ deps << dep
65
190
  end
66
191
  end
67
192
 
68
- def dependencies
69
- @dependencies ||= @details["dependencies"].map do |dep, opts|
70
- Bundler::Dependency.new(dep, opts.delete("version"), opts)
193
+ converged = []
194
+ @last_resolve.each do |s|
195
+ s.source = @sources.find { |src| s.source == src }
196
+
197
+ next if s.source.nil? || @unlock[:sources].include?(s.name)
198
+
199
+ converged << s
200
+ end
201
+
202
+ resolve = SpecSet.new(converged)
203
+ resolve = resolve.for(expand_dependencies(deps), @unlock[:gems])
204
+ @last_resolve.select!(resolve.names)
205
+ end
206
+
207
+ def in_locked_deps?(dep)
208
+ @locked_deps.any? do |d|
209
+ dep == d && dep.source == d.source
210
+ end
211
+ end
212
+
213
+ def satisfies_locked_spec?(dep)
214
+ @last_resolve.any? { |s| s.satisfies?(dep) }
215
+ end
216
+
217
+ def expanded_dependencies
218
+ @expanded_dependencies ||= expand_dependencies(dependencies)
219
+ end
220
+
221
+ def expand_dependencies(dependencies)
222
+ deps = []
223
+ dependencies.each do |dep|
224
+ dep.gem_platforms(@platforms).each do |p|
225
+ deps << DepProxy.new(dep, p)
71
226
  end
72
227
  end
228
+ deps
229
+ end
230
+
231
+ def sorted_sources
232
+ @sources.sort_by do |s|
233
+ # Place GEM at the top
234
+ [ s.is_a?(Source::Rubygems) ? 1 : 0, s.to_s ]
235
+ end
236
+ end
237
+
238
+ def requested_dependencies
239
+ groups = self.groups - Bundler.settings.without
240
+ groups.map! { |g| g.to_sym }
241
+ dependencies.reject { |d| !d.should_include? || (d.groups & groups).empty? }
73
242
  end
74
243
  end
75
244
  end
@@ -5,6 +5,15 @@ module Bundler
5
5
  class Dependency < Gem::Dependency
6
6
  attr_reader :autorequire
7
7
  attr_reader :groups
8
+ attr_reader :platforms
9
+
10
+ PLATFORM_MAP = {
11
+ :ruby => Gem::Platform::RUBY,
12
+ :ruby_18 => Gem::Platform::RUBY,
13
+ :ruby_19 => Gem::Platform::RUBY,
14
+ :jruby => Gem::Platform::JAVA,
15
+ :mswin => Gem::Platform::MSWIN
16
+ }
8
17
 
9
18
  def initialize(name, version, options = {}, &blk)
10
19
  super(name, version)
@@ -12,10 +21,78 @@ module Bundler
12
21
  @autorequire = nil
13
22
  @groups = Array(options["group"] || :default).map { |g| g.to_sym }
14
23
  @source = options["source"]
24
+ @platforms = Array(options["platforms"])
25
+ @env = options["env"]
15
26
 
16
27
  if options.key?('require')
17
28
  @autorequire = Array(options['require'] || [])
18
29
  end
19
30
  end
31
+
32
+ def gem_platforms(valid_platforms)
33
+ return valid_platforms if @platforms.empty?
34
+
35
+ platforms = []
36
+ @platforms.each do |p|
37
+ platform = PLATFORM_MAP[p]
38
+ next unless valid_platforms.include?(platform)
39
+ platforms |= [platform]
40
+ end
41
+ platforms
42
+ end
43
+
44
+ def should_include?
45
+ current_env? && current_platform?
46
+ end
47
+
48
+ def current_env?
49
+ return true unless @env
50
+ if Hash === @env
51
+ @env.all? do |key, val|
52
+ ENV[key.to_s] && (String === val ? ENV[key.to_s] == val : ENV[key.to_s] =~ val)
53
+ end
54
+ else
55
+ ENV[@env.to_s]
56
+ end
57
+ end
58
+
59
+ def current_platform?
60
+ return true if @platforms.empty?
61
+ @platforms.any? { |p| send("#{p}?") }
62
+ end
63
+
64
+ def to_lock
65
+ out = " #{name}"
66
+
67
+ unless requirement == Gem::Requirement.default
68
+ out << " (#{requirement.to_s})"
69
+ end
70
+
71
+ out << '!' if source
72
+
73
+ out << "\n"
74
+ end
75
+
76
+ private
77
+
78
+ def ruby?
79
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx")
80
+ end
81
+
82
+ def ruby_18?
83
+ ruby? && RUBY_VERSION < "1.9"
84
+ end
85
+
86
+ def ruby_19?
87
+ ruby? && RUBY_VERSION >= "1.9"
88
+ end
89
+
90
+ def jruby?
91
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
92
+ end
93
+
94
+ def mswin?
95
+ # w0t?
96
+ end
20
97
  end
21
98
  end
@@ -8,11 +8,16 @@ module Bundler
8
8
  builder.to_definition
9
9
  end
10
10
 
11
+ VALID_PLATFORMS = [:ruby_18, :ruby_19, :ruby, :jruby, :mswin]
12
+
11
13
  def initialize
12
- @source = nil
13
- @sources = []
14
- @dependencies = []
15
- @group = [:default]
14
+ @rubygems_source = Source::Rubygems.new
15
+ @source = nil
16
+ @sources = []
17
+ @dependencies = []
18
+ @groups = []
19
+ @platforms = []
20
+ @env = nil
16
21
  end
17
22
 
18
23
  def gem(name, *args)
@@ -29,12 +34,16 @@ module Bundler
29
34
  end
30
35
 
31
36
  def source(source, options = {})
32
- @source = case source
33
- when :gemcutter, :rubygems, :rubyforge then Source::Rubygems.new("uri" => "http://gemcutter.org")
34
- when String then Source::Rubygems.new("uri" => source)
35
- else source
37
+ case source
38
+ when :gemcutter, :rubygems, :rubyforge then
39
+ rubygems_source "http://rubygems.org"
40
+ return
41
+ when String
42
+ rubygems_source source
43
+ return
36
44
  end
37
45
 
46
+ @source = source
38
47
  options[:prepend] ? @sources.unshift(@source) : @sources << @source
39
48
 
40
49
  yield if block_given?
@@ -51,15 +60,31 @@ module Bundler
51
60
  source Source::Git.new(_normalize_hash(options).merge("uri" => uri)), source_options, &blk
52
61
  end
53
62
 
54
- def to_definition
55
- Definition.new(@dependencies, @sources)
63
+ def to_definition(lockfile, unlock)
64
+ @sources << @rubygems_source
65
+ @sources.uniq!
66
+ Definition.new(lockfile, @dependencies, @sources, unlock)
56
67
  end
57
68
 
58
69
  def group(*args, &blk)
59
- old, @group = @group, args
70
+ @groups.concat args
71
+ yield
72
+ ensure
73
+ args.each { @groups.pop }
74
+ end
75
+
76
+ def platforms(*platforms)
77
+ @platforms.concat platforms
78
+ yield
79
+ ensure
80
+ platforms.each { @platforms.pop }
81
+ end
82
+
83
+ def env(name)
84
+ @env, old = name, @env
60
85
  yield
61
86
  ensure
62
- @group = old
87
+ @env = old
63
88
  end
64
89
 
65
90
  # Deprecated methods
@@ -87,6 +112,11 @@ module Bundler
87
112
 
88
113
  private
89
114
 
115
+ def rubygems_source(source)
116
+ @rubygems_source.add_remote source
117
+ @sources << @rubygems_source
118
+ end
119
+
90
120
  def _version?(version)
91
121
  version && Gem::Version.new(version) rescue false
92
122
  end
@@ -117,7 +147,17 @@ module Bundler
117
147
  raise InvalidOption, message
118
148
  end
119
149
 
120
- group = opts.delete("group") || @group
150
+ groups = @groups.dup
151
+ groups.concat Array(opts.delete("group"))
152
+ groups = [:default] if groups.empty?
153
+
154
+ platforms = @platforms.dup
155
+ platforms.concat Array(opts.delete("platforms"))
156
+ platforms.map! { |p| p.to_sym }
157
+ platforms.each do |p|
158
+ next if VALID_PLATFORMS.include?(p)
159
+ raise DslError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
160
+ end
121
161
 
122
162
  # Normalize git and path options
123
163
  ["git", "path"].each do |type|
@@ -128,9 +168,10 @@ module Bundler
128
168
  end
129
169
  end
130
170
 
131
- opts["source"] ||= @source
132
-
133
- opts["group"] = group
171
+ opts["source"] ||= @source
172
+ opts["env"] ||= @env
173
+ opts["platforms"] = @platforms.dup
174
+ opts["group"] = groups
134
175
  end
135
176
 
136
177
  def _deprecated_options(options)