bundler 0.6.0 → 0.7.0

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.

@@ -175,11 +175,31 @@ will be compiled for the target platform without requiring that the
175
175
  Assuming a Rails app with Bundler's standard setup, add something like
176
176
  this to your top-level `.gitignore` to only keep the cache:
177
177
 
178
- vendor/gems/
178
+ vendor/gems/*
179
179
  !vendor/gems/cache/
180
180
 
181
181
  Make sure that you explicitly `git add vendor/gems/cache` before you commit.
182
182
 
183
+ ### Gems with compile-time options
184
+
185
+ Some gems require you to pass compile-time options to the gem install command.
186
+ For instance, to install mysql, you might do:
187
+
188
+ gem install mysql -- --with-mysql-config=/usr/local/lib/mysql
189
+
190
+ You can pass these options to the bundler by creating a YAML file containing
191
+ the options in question:
192
+
193
+ mysql:
194
+ mysql-config: /usr/local/lib/mysql
195
+
196
+ You can then point the bundler at the file:
197
+
198
+ gem bundle --build-options build_options.yml
199
+
200
+ In general, you will want to keep the build options YAML out of version control,
201
+ and provide the appropriate options for the system in question.
202
+
183
203
  ### Running your application
184
204
 
185
205
  The easiest way to run your application is to start it with an executable
@@ -196,7 +216,7 @@ You can use `gem exec bash` to enter a shell that will run all binaries in
196
216
  the current context.
197
217
 
198
218
  Yet another way is to manually require the environment file first. This is
199
- located in `[bundle_path]/environments/default.rb`. For example:
219
+ located in `[bundle_path]/gems/environment.rb`. For example:
200
220
 
201
221
  ruby -r vendor/gems/environment.rb my_ruby_script.rb
202
222
 
@@ -19,7 +19,7 @@ require "bundler/dependency"
19
19
  require "bundler/remote_specification"
20
20
 
21
21
  module Bundler
22
- VERSION = "0.6.0"
22
+ VERSION = "0.7.0"
23
23
 
24
24
  class << self
25
25
  attr_writer :logger
@@ -25,33 +25,40 @@ module Bundler
25
25
  rescue SourceNotCached => e
26
26
  Bundler.logger.error e.message
27
27
  exit 9
28
+ rescue ManifestFileNotFound => e
29
+ Bundler.logger.error e.message
30
+ exit 10
28
31
  end
29
32
 
30
33
  def initialize(options)
31
34
  @options = options
32
- @manifest = Bundler::Environment.load(@options[:manifest])
35
+ @environment = Bundler::Environment.load(@options[:manifest])
33
36
  end
34
37
 
35
38
  def bundle
36
- @manifest.install(@options)
39
+ @environment.install(@options)
37
40
  end
38
41
 
39
42
  def cache
40
- @manifest.cache(@options)
43
+ @environment.cache(@options)
41
44
  end
42
45
 
43
46
  def prune
44
- @manifest.prune(@options)
47
+ @environment.prune(@options)
45
48
  end
46
49
 
47
50
  def list
48
- @manifest.list(@options)
51
+ @environment.list(@options)
52
+ end
53
+
54
+ def list_outdated
55
+ @environment.list_outdated(@options)
49
56
  end
50
57
 
51
58
  def exec
52
- @manifest.setup_environment
59
+ @environment.setup_environment
53
60
  # w0t?
54
- super(*@options[:args])
61
+ super(*$command)
55
62
  end
56
63
 
57
64
  def run(command)
@@ -26,6 +26,21 @@ class Gem::Commands::BundleCommand < Gem::Command
26
26
  add_option('--list', "List all gems that are part of the active bundle") do
27
27
  options[:list] = true
28
28
  end
29
+
30
+ add_option('--list-outdated', "List all outdated gems that are part of the active bundle") do
31
+ options[:list_outdated] = true
32
+ end
33
+
34
+ add_option('-b', '--build-options OPTION_FILE', "Specify a path to a yml file with build options for binary gems") do |option_file, options|
35
+ if File.exist?(option_file)
36
+ options[:build_options] = YAML.load_file(option_file)
37
+ end
38
+ end
39
+
40
+ add_option('--only ENV', "Only expand the given environment. To specify multiple environments, use --only multiple times.") do |env, options|
41
+ options[:only] ||= []
42
+ options[:only] << env
43
+ end
29
44
  end
30
45
 
31
46
  def usage
@@ -47,6 +62,8 @@ Bundle stuff
47
62
  Bundler::CLI.run(:prune, options)
48
63
  elsif options[:list]
49
64
  Bundler::CLI.run(:list, options)
65
+ elsif options[:list_outdated]
66
+ Bundler::CLI.run(:list_outdated, options)
50
67
  else
51
68
  Bundler::CLI.run(:bundle, options)
52
69
  end
@@ -1,3 +1,8 @@
1
+ if exec = ARGV.index("exec")
2
+ $command = ARGV[(exec + 1)..-1]
3
+ ARGV.replace ARGV[0..exec]
4
+ end
5
+
1
6
  class Gem::Commands::ExecCommand < Gem::Command
2
7
 
3
8
  def initialize
@@ -2,7 +2,7 @@ module Bundler
2
2
  class InvalidEnvironmentName < StandardError; end
3
3
 
4
4
  class Dependency
5
- attr_reader :name, :version, :require_as, :only, :except
5
+ attr_reader :name, :version, :require_as, :only, :except, :bundle
6
6
 
7
7
  def initialize(name, options = {}, &block)
8
8
  options.each do |k, v|
@@ -11,9 +11,10 @@ module Bundler
11
11
 
12
12
  @name = name
13
13
  @version = options["version"] || ">= 0"
14
- @require_as = Array(options["require_as"] || name)
14
+ @require_as = options["require_as"]
15
15
  @only = options["only"]
16
16
  @except = options["except"]
17
+ @bundle = options.key?("bundle") ? options["bundle"] : true
17
18
  @block = block
18
19
 
19
20
  if (@only && @only.include?("rubygems")) || (@except && @except.include?("rubygems"))
@@ -36,8 +37,14 @@ module Bundler
36
37
  def require_env(environment)
37
38
  return unless in?(environment)
38
39
 
39
- @require_as.each do |file|
40
- require file
40
+ if @require_as
41
+ Array(@require_as).each { |file| require file }
42
+ else
43
+ begin
44
+ require name
45
+ rescue LoadError
46
+ # Do nothing
47
+ end
41
48
  end
42
49
 
43
50
  @block.call if @block
@@ -2,10 +2,16 @@ module Bundler
2
2
  class ManifestFileNotFound < StandardError; end
3
3
 
4
4
  class Dsl
5
+ def self.evaluate(environment, file)
6
+ builder = new(environment)
7
+ builder.instance_eval(File.read(file.to_s), file.to_s, 1)
8
+ end
9
+
5
10
  def initialize(environment)
6
11
  @environment = environment
7
- @sources = Hash.new { |h,k| h[k] = {} }
8
- @only, @except = nil, nil
12
+ @directory_sources = []
13
+ @git_sources = {}
14
+ @only, @except, @directory, @git = nil, nil, nil, nil
9
15
  end
10
16
 
11
17
  def bundle_path(path)
@@ -35,18 +41,36 @@ module Bundler
35
41
  end
36
42
  end
37
43
 
38
- def only(env)
39
- old, @only = @only, _combine_onlys(env)
44
+ def only(*env)
45
+ old, @only = @only, _combine_only(env)
40
46
  yield
41
47
  @only = old
42
48
  end
43
49
 
44
- def except(env)
45
- old, @except = @except, _combine_excepts(env)
50
+ def except(*env)
51
+ old, @except = @except, _combine_except(env)
46
52
  yield
47
53
  @except = old
48
54
  end
49
55
 
56
+ def directory(path, options = {})
57
+ raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
58
+ @directory = DirectorySource.new(options.merge(:location => path))
59
+ @directory_sources << @directory
60
+ @environment.add_priority_source(@directory)
61
+ yield if block_given?
62
+ @directory = nil
63
+ end
64
+
65
+ def git(uri, options = {})
66
+ raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
67
+ @git = GitSource.new(options.merge(:uri => uri))
68
+ @git_sources[uri] = @git
69
+ @environment.add_priority_source(@git)
70
+ yield if block_given?
71
+ @git = nil
72
+ end
73
+
50
74
  def clear_sources
51
75
  @environment.clear_sources
52
76
  end
@@ -55,37 +79,22 @@ module Bundler
55
79
  options = args.last.is_a?(Hash) ? args.pop : {}
56
80
  version = args.last
57
81
 
58
- options[:only] = _combine_onlys(options[:only] || options["only"])
59
- options[:except] = _combine_excepts(options[:except] || options["except"])
82
+ if path = options.delete(:vendored_at)
83
+ options[:path] = path
84
+ warn "The :vendored_at option is deprecated. Use :path instead.\nFrom #{caller[0]}"
85
+ end
86
+
87
+ options[:only] = _combine_only(options[:only] || options["only"])
88
+ options[:except] = _combine_except(options[:except] || options["except"])
60
89
 
61
90
  dep = Dependency.new(name, options.merge(:version => version))
62
91
 
63
- # OMG REFACTORZ. KTHX
64
- if vendored_at = options[:vendored_at]
65
- vendored_at = Pathname.new(vendored_at)
66
- vendored_at = @environment.filename.dirname.join(vendored_at) if vendored_at.relative?
67
-
68
- @sources[:directory][vendored_at.to_s] ||= begin
69
- source = DirectorySource.new(
70
- :name => name,
71
- :version => version,
72
- :location => vendored_at
73
- )
74
- @environment.add_priority_source(source)
75
- true
76
- end
77
- elsif git = options[:git]
78
- @sources[:git][git] ||= begin
79
- source = GitSource.new(
80
- :name => name,
81
- :version => version,
82
- :uri => git,
83
- :ref => options[:commit] || options[:tag],
84
- :branch => options[:branch]
85
- )
86
- @environment.add_priority_source(source)
87
- true
88
- end
92
+ if options.key?(:bundle) && !options[:bundle]
93
+ # We're using system gems for this one
94
+ elsif @git || options[:git]
95
+ _handle_git_option(name, version, options)
96
+ elsif @directory || options[:path]
97
+ _handle_vendored_option(name, version, options)
89
98
  end
90
99
 
91
100
  @environment.dependencies << dep
@@ -93,16 +102,66 @@ module Bundler
93
102
 
94
103
  private
95
104
 
96
- def _combine_onlys(only)
105
+ def _handle_vendored_option(name, version, options)
106
+ dir, path = _find_directory_source(options[:path])
107
+
108
+ if dir
109
+ dir.required_specs << name
110
+ dir.add_spec(path, name, version) if version
111
+ else
112
+ directory options[:path] do
113
+ _handle_vendored_option(name, version, {})
114
+ end
115
+ end
116
+ end
117
+
118
+ def _find_directory_source(path)
119
+ if @directory
120
+ return @directory, Pathname.new(path || '')
121
+ end
122
+
123
+ path = @environment.filename.dirname.join(path)
124
+
125
+ @directory_sources.each do |s|
126
+ if s.location.expand_path.to_s < path.expand_path.to_s
127
+ return s, path.relative_path_from(s.location)
128
+ end
129
+ end
130
+
131
+ nil
132
+ end
133
+
134
+ def _handle_git_option(name, version, options)
135
+ git = options[:git].to_s
136
+ ref = options[:commit] || options[:tag]
137
+ branch = options[:branch]
138
+
139
+ if source = @git || @git_sources[git]
140
+ if ref && source.ref != ref
141
+ raise GitSourceError, "'#{git}' already specified with ref: #{source.ref}"
142
+ elsif branch && source.branch != branch
143
+ raise GitSourceError, "'#{git}' already specified with branch: #{source.branch}"
144
+ end
145
+
146
+ source.required_specs << name
147
+ source.add_spec(Pathname.new(options[:path] || '.'), name, version) if version
148
+ else
149
+ git(git, :ref => ref, :branch => branch) do
150
+ _handle_git_option(name, version, options)
151
+ end
152
+ end
153
+ end
154
+
155
+ def _combine_only(only)
97
156
  return @only unless only
98
- only = [only].flatten.compact.uniq.map { |o| o.to_s }
157
+ only = Array(only).compact.uniq.map { |o| o.to_s }
99
158
  only &= @only if @only
100
159
  only
101
160
  end
102
161
 
103
- def _combine_excepts(except)
162
+ def _combine_except(except)
104
163
  return @except unless except
105
- except = [except].flatten.compact.uniq.map { |o| o.to_s }
164
+ except = Array(except).compact.uniq.map { |o| o.to_s }
106
165
  except |= @except if @except
107
166
  except
108
167
  end
@@ -10,11 +10,11 @@ module Bundler
10
10
  attr_accessor :rubygems, :system_gems
11
11
  attr_writer :gem_path, :bindir
12
12
 
13
- def self.load(gemfile = nil)
14
- gemfile = gemfile ? Pathname.new(gemfile).expand_path : default_manifest_file
13
+ def self.load(file = nil)
14
+ gemfile = Pathname.new(file || default_manifest_file).expand_path
15
15
 
16
16
  unless gemfile.file?
17
- raise ManifestFileNotFound, "#{filename.inspect} does not exist"
17
+ raise ManifestFileNotFound, "Manifest file not found: #{gemfile.to_s.inspect}"
18
18
  end
19
19
 
20
20
  new(gemfile)
@@ -32,9 +32,9 @@ module Bundler
32
32
  raise DefaultManifestNotFound
33
33
  end
34
34
 
35
- def initialize(filename) #, sources, dependencies, bindir, path, rubygems, system_gems)
35
+ def initialize(filename)
36
36
  @filename = filename
37
- @default_sources = [GemSource.new(:uri => "http://gems.rubyforge.org"), SystemGemSource.new({})]
37
+ @default_sources = default_sources
38
38
  @sources = []
39
39
  @priority_sources = []
40
40
  @dependencies = []
@@ -42,20 +42,27 @@ module Bundler
42
42
  @system_gems = true
43
43
 
44
44
  # Evaluate the Gemfile
45
- builder = Dsl.new(self)
46
- builder.instance_eval(File.read(filename))
45
+ Dsl.evaluate(self, filename)
47
46
  end
48
47
 
49
48
  def install(options = {})
49
+ if only_envs = options[:only]
50
+ dependencies.reject! { |d| !only_envs.any? {|env| d.in?(env) } }
51
+ end
52
+
53
+ no_bundle = dependencies.map { |dep| !dep.bundle && dep.name }.compact
54
+
50
55
  update = options[:update]
51
56
  cached = options[:cached]
52
57
 
53
58
  repository.install(gem_dependencies, sources,
54
- :rubygems => rubygems,
55
- :system_gems => system_gems,
56
- :manifest => filename,
57
- :update => update,
58
- :cached => cached
59
+ :rubygems => rubygems,
60
+ :system_gems => system_gems,
61
+ :manifest => filename,
62
+ :update => options[:update],
63
+ :cached => options[:cached],
64
+ :build_options => options[:build_options],
65
+ :no_bundle => no_bundle
59
66
  )
60
67
  Bundler.logger.info "Done."
61
68
  end
@@ -101,6 +108,18 @@ module Bundler
101
108
  end
102
109
  end
103
110
 
111
+ def list_outdated(options={})
112
+ outdated_gems = repository.outdated_gems
113
+ if outdated_gems.empty?
114
+ Bundler.logger.info "All gems are up to date."
115
+ else
116
+ Bundler.logger.info "Outdated gems:"
117
+ outdated_gems.each do |name|
118
+ Bundler.logger.info " * #{name}"
119
+ end
120
+ end
121
+ end
122
+
104
123
  def setup_environment
105
124
  unless system_gems
106
125
  ENV["GEM_HOME"] = gem_path
@@ -145,6 +164,10 @@ module Bundler
145
164
 
146
165
  private
147
166
 
167
+ def default_sources
168
+ [GemSource.new(:uri => "http://gems.rubyforge.org"), SystemGemSource.instance]
169
+ end
170
+
148
171
  def repository
149
172
  @repository ||= Repository.new(gem_path, bindir)
150
173
  end
@@ -20,22 +20,31 @@ module Bundler
20
20
  s.local = options[:cached]
21
21
  end
22
22
 
23
+ source_requirements = {}
24
+ options[:no_bundle].each do |name|
25
+ source_requirements[name] = SystemGemSource.instance
26
+ end
27
+
28
+ # Check to see whether the existing cache meets all the requirements
23
29
  begin
24
- valid = Resolver.resolve(dependencies, [source_index])
30
+ valid = Resolver.resolve(dependencies, [source_index], source_requirements)
25
31
  rescue Bundler::GemNotFound
26
32
  end
27
33
 
28
- if options[:cached]
29
- sources = sources.select { |s| s.can_be_local? }
30
- end
34
+ sources = only_local(sources) if options[:cached]
31
35
 
36
+ # Check the remote sources if the existing cache does not meet the requirements
37
+ # or the user passed --update
32
38
  if options[:update] || !valid
33
39
  Bundler.logger.info "Calculating dependencies..."
34
- bundle = Resolver.resolve(dependencies, [@cache] + sources)
40
+ bundle = Resolver.resolve(dependencies, [@cache] + sources, source_requirements)
41
+ download(bundle, options)
35
42
  do_install(bundle, options)
36
43
  valid = bundle
37
44
  end
38
- cleanup(valid)
45
+
46
+ generate_bins(valid, options)
47
+ cleanup(valid, options)
39
48
  configure(valid, options)
40
49
  end
41
50
 
@@ -53,12 +62,14 @@ module Bundler
53
62
  s.local = true
54
63
  end
55
64
 
56
- sources = sources.select { |s| s.can_be_local? }
65
+ sources = only_local(sources)
57
66
  bundle = Resolver.resolve(dependencies, [@cache] + sources)
58
- @cache.gems.each do |name, spec|
59
- unless bundle.any? { |s| s.name == spec.name && s.version == spec.version }
60
- Bundler.logger.info "Pruning #{spec.name} (#{spec.version}) from the cache"
61
- FileUtils.rm @path.join("cache", "#{spec.full_name}.gem")
67
+ @cache.gems.each do |name, specs|
68
+ specs.each do |spec|
69
+ unless bundle.any? { |s| s.name == spec.name && s.version == spec.version }
70
+ Bundler.logger.info "Pruning #{spec.name} (#{spec.version}) from the cache"
71
+ FileUtils.rm @path.join("cache", "#{spec.full_name}.gem")
72
+ end
62
73
  end
63
74
  end
64
75
  end
@@ -67,6 +78,10 @@ module Bundler
67
78
  source_index.gems.values
68
79
  end
69
80
 
81
+ def outdated_gems
82
+ source_index.outdated.sort
83
+ end
84
+
70
85
  def source_index
71
86
  index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
72
87
  index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
@@ -79,10 +94,20 @@ module Bundler
79
94
 
80
95
  private
81
96
 
82
- def do_install(bundle, options)
83
- bundle.download
97
+ def only_local(sources)
98
+ sources.select { |s| s.can_be_local? }
99
+ end
100
+
101
+ def download(bundle, options)
102
+ bundle.sort_by {|s| s.full_name.downcase }.each do |spec|
103
+ next if options[:no_bundle].include?(spec.name)
104
+ spec.source.download(spec)
105
+ end
106
+ end
84
107
 
108
+ def do_install(bundle, options)
85
109
  bundle.each do |spec|
110
+ next if options[:no_bundle].include?(spec.name)
86
111
  spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec")
87
112
  # Do nothing if the gem is already expanded
88
113
  next if @path.join("gems", spec.full_name).directory?
@@ -96,11 +121,35 @@ module Bundler
96
121
  end
97
122
  end
98
123
 
124
+ def generate_bins(bundle, options)
125
+ bundle.each do |spec|
126
+ next if options[:no_bundle].include?(spec.name)
127
+ # HAX -- Generate the bin
128
+ bin_dir = @bindir
129
+ path = @path
130
+ installer = Gem::Installer.allocate
131
+ installer.instance_eval do
132
+ @spec = spec
133
+ @bin_dir = bin_dir
134
+ @gem_dir = path.join("gems", "#{spec.full_name}")
135
+ @gem_home = path
136
+ @wrappers = true
137
+ @format_executable = false
138
+ @env_shebang = false
139
+ end
140
+ installer.generate_bin
141
+ end
142
+ end
143
+
99
144
  def expand_gemfile(spec, options)
100
145
  Bundler.logger.info "Installing #{spec.name} (#{spec.version})"
101
146
 
102
147
  gemfile = @path.join("cache", "#{spec.full_name}.gem").to_s
103
148
 
149
+ if build_args = options[:build_options] && options[:build_options][spec.name]
150
+ Gem::Command.build_args = build_args.map {|k,v| "--with-#{k}=#{v}"}
151
+ end
152
+
104
153
  installer = Gem::Installer.new(gemfile, options.merge(
105
154
  :install_dir => @path,
106
155
  :ignore_dependencies => true,
@@ -109,25 +158,14 @@ module Bundler
109
158
  :bin_dir => @bindir
110
159
  ))
111
160
  installer.install
161
+ ensure
162
+ Gem::Command.build_args = []
112
163
  end
113
164
 
114
165
  def expand_vendored_gem(spec, options)
115
166
  add_spec(spec)
116
167
  FileUtils.mkdir_p(@path.join("gems"))
117
168
  File.symlink(spec.location, @path.join("gems", spec.full_name))
118
-
119
- # HAX -- Generate the bin
120
- bin_dir = @bindir
121
- path = @path
122
- installer = Gem::Installer.allocate
123
- installer.instance_eval do
124
- @spec = spec
125
- @bin_dir = bin_dir
126
- @gem_dir = path.join("gems", "#{spec.full_name}")
127
- @gem_home = path
128
- @wrappers = true
129
- end
130
- installer.generate_bin
131
169
  end
132
170
 
133
171
  def add_spec(spec)
@@ -139,7 +177,7 @@ module Bundler
139
177
  end
140
178
  end
141
179
 
142
- def cleanup(valid)
180
+ def cleanup(valid, options)
143
181
  to_delete = gems
144
182
  to_delete.delete_if do |spec|
145
183
  valid.any? { |other| spec.name == other.name && spec.version == other.version }
@@ -173,13 +211,9 @@ module Bundler
173
211
  def generate_environment(specs, options)
174
212
  FileUtils.mkdir_p(path)
175
213
 
176
- load_paths = load_paths_for_specs(specs)
214
+ load_paths = load_paths_for_specs(specs, options)
177
215
  bindir = @bindir.relative_path_from(path).to_s
178
216
  filename = options[:manifest].relative_path_from(path).to_s
179
- spec_files = specs.inject({}) do |hash, spec|
180
- relative = spec.loaded_from.relative_path_from(@path).to_s
181
- hash.merge!(spec.name => relative)
182
- end
183
217
 
184
218
  File.open(path.join("environment.rb"), "w") do |file|
185
219
  template = File.read(File.join(File.dirname(__FILE__), "templates", "environment.erb"))
@@ -188,20 +222,27 @@ module Bundler
188
222
  end
189
223
  end
190
224
 
191
- def load_paths_for_specs(specs)
225
+ def load_paths_for_specs(specs, options)
192
226
  load_paths = []
193
227
  specs.each do |spec|
228
+ next if options[:no_bundle].include?(spec.name)
194
229
  gem_path = Pathname.new(spec.full_gem_path)
195
- if spec.bindir
196
- load_paths << gem_path.join(spec.bindir).relative_path_from(@path).to_s
197
- end
230
+ load_paths << load_path_for(gem_path, spec.bindir) if spec.bindir
198
231
  spec.require_paths.each do |path|
199
- load_paths << gem_path.join(path).relative_path_from(@path).to_s
232
+ load_paths << load_path_for(gem_path, path)
200
233
  end
201
234
  end
202
235
  load_paths
203
236
  end
204
237
 
238
+ def load_path_for(gem_path, path)
239
+ gem_path.join(path).relative_path_from(@path).to_s
240
+ end
241
+
242
+ def spec_file_for(spec)
243
+ spec.loaded_from.relative_path_from(@path).to_s
244
+ end
245
+
205
246
  def require_code(file, dep)
206
247
  constraint = case
207
248
  when dep.only then %{ if #{dep.only.inspect}.include?(env)}
@@ -36,8 +36,8 @@ module Bundler
36
36
  # ==== Returns
37
37
  # <GemBundle>,nil:: If the list of dependencies can be resolved, a
38
38
  # collection of gemspecs is returned. Otherwise, nil is returned.
39
- def self.resolve(requirements, sources)
40
- resolver = new(sources)
39
+ def self.resolve(requirements, sources, source_requirements = {})
40
+ resolver = new(sources, source_requirements)
41
41
  result = catch(:success) do
42
42
  resolver.resolve(requirements, {})
43
43
  output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
@@ -49,22 +49,38 @@ module Bundler
49
49
  raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
50
50
  nil
51
51
  end
52
- result && GemBundle.new(result.values)
52
+ if result
53
+ # Order gems in order of dependencies. Every gem's dependency is at
54
+ # a smaller index in the array.
55
+ ordered = []
56
+ result.values.each do |spec1|
57
+ index = nil
58
+ place = ordered.detect do |spec2|
59
+ spec1.dependencies.any? { |d| d.name == spec2.name }
60
+ end
61
+ place ?
62
+ ordered.insert(ordered.index(place), spec1) :
63
+ ordered << spec1
64
+ end
65
+ ordered.reverse
66
+ end
53
67
  end
54
68
 
55
- def initialize(sources)
69
+ def initialize(sources, source_requirements)
56
70
  @errors = {}
57
71
  @stack = []
58
- @specs = Hash.new { |h,k| h[k] = {} }
72
+ @specs = Hash.new { |h,k| h[k] = [] }
73
+ @by_gem = source_requirements
59
74
  @cache = {}
60
75
  @index = {}
61
76
 
62
- sources.reverse_each do |source|
63
- source.gems.values.each do |spec|
64
- # TMP HAX FOR OPTZ
65
- spec.source = source
66
- next unless Gem::Platform.match(spec.platform)
67
- @specs[spec.name][spec.version] = spec
77
+ sources.each do |source|
78
+ source.gems.each do |name, specs|
79
+ # Hack to work with a regular Gem::SourceIndex
80
+ [specs].flatten.compact.each do |spec|
81
+ next if @specs[spec.name].any? { |s| s.version == spec.version }
82
+ @specs[spec.name] << spec
83
+ end
68
84
  end
69
85
  end
70
86
  end
@@ -74,11 +90,15 @@ module Bundler
74
90
  # gem dependencies have been resolved.
75
91
  throw :success, activated if reqs.empty?
76
92
 
77
- # Sort requirements so that the ones that are easiest to resolve are first.
78
- # Easiest to resolve is defined by: Is this gem already activated? Otherwise,
79
- # check the number of child dependencies this requirement has.
80
- reqs = reqs.sort_by do |req|
81
- activated[req.name] ? 0 : search(req).size
93
+ # Sort dependencies so that the ones that are easiest to resolve are first.
94
+ # Easiest to resolve is defined by:
95
+ # 1) Is this gem already activated?
96
+ # 2) Do the version requirements include prereleased gems?
97
+ # 3) Sort by number of gems available in the source.
98
+ reqs = reqs.sort_by do |a|
99
+ [ activated[a.name] ? 0 : 1,
100
+ a.version_requirements.prerelease? ? 0 : 1,
101
+ activated[a.name] ? 0 : search(a).size ]
82
102
  end
83
103
 
84
104
  activated = activated.dup
@@ -178,7 +198,9 @@ module Bundler
178
198
 
179
199
  def search(dependency)
180
200
  @cache[dependency.hash] ||= begin
181
- @specs[dependency.name].values.select do |spec|
201
+ collection = @by_gem[dependency.name].gems if @by_gem[dependency.name]
202
+ collection ||= @specs
203
+ collection[dependency.name].select do |spec|
182
204
  match = dependency =~ spec
183
205
  match &= dependency.version_requirements.prerelease? if spec.version.prerelease?
184
206
  match
@@ -1,8 +1,23 @@
1
1
  module Bundler
2
+ class DirectorySourceError < StandardError; end
3
+ class GitSourceError < StandardError ; end
2
4
  # Represents a source of rubygems. Initially, this is only gem repositories, but
3
5
  # eventually, this will be git, svn, HTTP
4
6
  class Source
5
7
  attr_accessor :repository, :local
8
+
9
+ def initialize(options) ; end
10
+
11
+ private
12
+
13
+ def process_source_gems(gems)
14
+ new_gems = Hash.new { |h,k| h[k] = [] }
15
+ gems.values.each do |spec|
16
+ spec.source = self
17
+ new_gems[spec.name] << spec
18
+ end
19
+ new_gems
20
+ end
6
21
  end
7
22
 
8
23
  class GemSource < Source
@@ -38,7 +53,7 @@ module Bundler
38
53
  destination = repository.path
39
54
 
40
55
  unless destination.writable?
41
- raise RubygemsRetardation
56
+ raise RubygemsRetardation, "destination: #{destination} is not writable"
42
57
  end
43
58
 
44
59
  # Download the gem
@@ -65,10 +80,11 @@ module Bundler
65
80
  index = Marshal.load(main_index)
66
81
  end
67
82
 
68
- gems = {}
83
+ gems = Hash.new { |h,k| h[k] = [] }
69
84
  index.each do |name, version, platform|
70
85
  spec = RemoteSpecification.new(name, version, platform, @uri)
71
- gems[spec.full_name] = spec
86
+ spec.source = self
87
+ gems[spec.name] << spec if Gem::Platform.match(spec.platform)
72
88
  end
73
89
  gems
74
90
  rescue Gem::RemoteFetcher::FetchError => e
@@ -77,8 +93,13 @@ module Bundler
77
93
  end
78
94
 
79
95
  class SystemGemSource < Source
96
+
97
+ def self.instance
98
+ @instance ||= new({})
99
+ end
100
+
80
101
  def initialize(options)
81
- # Nothing to do
102
+ @source = Gem::SourceIndex.from_installed_gems
82
103
  end
83
104
 
84
105
  def can_be_local?
@@ -86,7 +107,7 @@ module Bundler
86
107
  end
87
108
 
88
109
  def gems
89
- @specs ||= Gem::SourceIndex.from_installed_gems.gems
110
+ @gems ||= process_source_gems(@source.gems)
90
111
  end
91
112
 
92
113
  def ==(other)
@@ -98,9 +119,6 @@ module Bundler
98
119
  end
99
120
 
100
121
  def download(spec)
101
- # gemfile = Pathname.new(local.loaded_from)
102
- # gemfile = gemfile.dirname.join('..', 'cache', "#{local.full_name}.gem").expand_path
103
- # repository.cache(File.join(Gem.dir, "cache", "#{local.full_name}.gem"))
104
122
  gemfile = Pathname.new(spec.loaded_from)
105
123
  gemfile = gemfile.dirname.join('..', 'cache', "#{spec.full_name}.gem")
106
124
  repository.cache(gemfile)
@@ -108,10 +126,6 @@ module Bundler
108
126
 
109
127
  private
110
128
 
111
- def fetch_specs
112
-
113
- end
114
-
115
129
  end
116
130
 
117
131
  class GemDirectorySource < Source
@@ -144,11 +158,12 @@ module Bundler
144
158
  private
145
159
 
146
160
  def fetch_specs
147
- specs = {}
161
+ specs = Hash.new { |h,k| h[k] = [] }
148
162
 
149
163
  Dir["#{@location}/*.gem"].each do |gemfile|
150
164
  spec = Gem::Format.from_file_by_path(gemfile).spec
151
- specs[spec.full_name] = spec
165
+ spec.source = self
166
+ specs[spec.name] << spec
152
167
  end
153
168
 
154
169
  specs
@@ -156,13 +171,23 @@ module Bundler
156
171
  end
157
172
 
158
173
  class DirectorySource < Source
159
- attr_reader :location
174
+ attr_reader :location, :specs, :required_specs
160
175
 
161
176
  def initialize(options)
162
- @name = options[:name]
163
- @version = options[:version]
164
- @location = options[:location]
165
- @require_paths = options[:require_paths] || %w(lib)
177
+ if options[:location]
178
+ @location = Pathname.new(options[:location]).expand_path
179
+ end
180
+ @glob = options[:glob] || "**/*.gemspec"
181
+ @specs = {}
182
+ @required_specs = []
183
+ end
184
+
185
+ def add_spec(path, name, version, require_paths = %w(lib))
186
+ raise DirectorySourceError, "already have a gem defined for '#{path}'" if @specs[path.to_s]
187
+ @specs[path.to_s] = Gem::Specification.new do |s|
188
+ s.name = name
189
+ s.version = Gem::Version.new(version)
190
+ end
166
191
  end
167
192
 
168
193
  def can_be_local?
@@ -171,67 +196,70 @@ module Bundler
171
196
 
172
197
  def gems
173
198
  @gems ||= begin
174
- specs = {}
175
-
176
- # Find any gemspec files in the directory and load those specs
177
- Dir["#{location}/**/*.gemspec"].each do |file|
178
- file = Pathname.new(file)
179
- if spec = eval(File.read(file)) and validate_gemspec(file, spec)
180
- spec.location = file.dirname.expand_path
181
- specs[spec.full_name] = spec
199
+ # Locate all gemspecs from the directory
200
+ specs = locate_gemspecs
201
+ specs = merge_defined_specs(specs)
202
+
203
+ required_specs.each do |required|
204
+ unless specs.any? {|k,v| v.name == required }
205
+ raise DirectorySourceError, "No gemspec for '#{required}' was found in" \
206
+ " '#{location}'. Please explicitly specify a version."
182
207
  end
183
208
  end
184
209
 
185
- # If a gemspec for the dependency was not found, add it to the list
186
- if specs.keys.grep(/^#{Regexp.escape(@name)}/).empty?
187
- case
188
- when @version.nil?
189
- raise ArgumentError, "If you use :at, you must specify the gem " \
190
- "and version you wish to stand in for"
191
- when !Gem::Version.correct?(@version)
192
- raise ArgumentError, "If you use :at, you must specify a gem and " \
193
- "version. You specified #{@version} for the version"
194
- end
210
+ process_source_gems(specs)
211
+ end
212
+ end
195
213
 
196
- default = Gem::Specification.new do |s|
197
- s.name = @name
198
- s.version = Gem::Version.new(@version) if @version
199
- s.location = location
200
- end
201
- specs[default.full_name] = default
214
+ def locate_gemspecs
215
+ Dir["#{location}/#{@glob}"].inject({}) do |specs, file|
216
+ file = Pathname.new(file)
217
+ if spec = eval(File.read(file)) and validate_gemspec(file.dirname, spec)
218
+ spec.location = file.dirname.expand_path
219
+ specs[spec.full_name] = spec
202
220
  end
203
-
204
221
  specs
205
222
  end
206
223
  end
207
224
 
208
- # Too aggressive apparently.
209
- # ===
210
- # def validate_gemspec(file, spec)
211
- # file = Pathname.new(file)
212
- # Dir.chdir(file.dirname) do
213
- # spec.validate
214
- # end
215
- # rescue Gem::InvalidSpecificationException => e
216
- # file = file.relative_path_from(repository.path)
217
- # Bundler.logger.warn e.message
218
- # Bundler.logger.warn "Gemspec #{spec.name} (#{spec.version}) found at '#{file}' is not valid"
219
- # false
220
- # end
221
- def validate_gemspec(file, spec)
222
- base = file.dirname
225
+ def merge_defined_specs(specs)
226
+ @specs.each do |path, spec|
227
+ # Set the spec location
228
+ spec.location = "#{location}/#{path}"
229
+
230
+ if existing = specs.values.find { |s| s.name == spec.name }
231
+ if existing.version != spec.version
232
+ raise DirectorySourceError, "The version you specified for #{spec.name}" \
233
+ " is #{spec.version}. The gemspec is #{existing.version}."
234
+ # Not sure if this is needed
235
+ # ====
236
+ # elsif File.expand_path(existing.location) != File.expand_path(spec.location)
237
+ # raise DirectorySourceError, "The location you specified for #{spec.name}" \
238
+ # " is '#{spec.location}'. The gemspec was found at '#{existing.location}'."
239
+ end
240
+ elsif !validate_gemspec(spec.location, spec)
241
+ raise "Your gem definition is not valid: #{spec}"
242
+ else
243
+ specs[spec.full_name] = spec
244
+ end
245
+ end
246
+ specs
247
+ end
248
+
249
+ def validate_gemspec(path, spec)
250
+ path = Pathname.new(path)
223
251
  msg = "Gemspec for #{spec.name} (#{spec.version}) is invalid:"
224
252
  # Check the require_paths
225
- (spec.require_paths || []).each do |path|
226
- unless base.join(path).directory?
227
- Bundler.logger.warn "#{msg} Missing require path: '#{path}'"
253
+ (spec.require_paths || []).each do |require_path|
254
+ unless path.join(require_path).directory?
255
+ Bundler.logger.warn "#{msg} Missing require path: '#{require_path}'"
228
256
  return false
229
257
  end
230
258
  end
231
259
 
232
260
  # Check the executables
233
261
  (spec.executables || []).each do |exec|
234
- unless base.join(spec.bindir, exec).file?
262
+ unless path.join(spec.bindir, exec).file?
235
263
  Bundler.logger.warn "#{msg} Missing executable: '#{File.join(spec.bindir, exec)}'"
236
264
  return false
237
265
  end
@@ -255,6 +283,8 @@ module Bundler
255
283
  end
256
284
 
257
285
  class GitSource < DirectorySource
286
+ attr_reader :ref, :uri, :branch
287
+
258
288
  def initialize(options)
259
289
  super
260
290
  @uri = options[:uri]
@@ -281,9 +311,9 @@ module Bundler
281
311
  `git clone #{@uri} #{location} --no-hardlinks`
282
312
 
283
313
  if @ref
284
- Dir.chdir(location) { `git checkout #{@ref}` }
314
+ Dir.chdir(location) { `git checkout --quiet #{@ref}` }
285
315
  elsif @branch && @branch != "master"
286
- Dir.chdir(location) { `git checkout origin/#{@branch}` }
316
+ Dir.chdir(location) { `git checkout --quiet origin/#{@branch}` }
287
317
  end
288
318
  end
289
319
  super
@@ -20,9 +20,14 @@ module Bundler
20
20
  require "rubygems"
21
21
 
22
22
  @bundled_specs = {}
23
- <% spec_files.each do |name, path| -%>
24
- @bundled_specs["<%= name %>"] = eval(File.read("#{dir}/<%= path %>"))
25
- @bundled_specs["<%= name %>"].loaded_from = "#{dir}/<%= path %>"
23
+ <% specs.each do |spec| -%>
24
+ <% if options[:no_bundle].include?(spec.name) -%>
25
+ gem "<%= spec.name %>", "<%= spec.version %>"
26
+ <% else -%>
27
+ <% path = spec_file_for(spec) -%>
28
+ @bundled_specs["<%= spec.name %>"] = eval(File.read("#{dir}/<%= path %>"))
29
+ @bundled_specs["<%= spec.name %>"].loaded_from = "#{dir}/<%= path %>"
30
+ <% end -%>
26
31
  <% end -%>
27
32
 
28
33
  def self.add_specs_to_loaded_specs
@@ -42,51 +47,62 @@ module Bundler
42
47
  def self.require_env(env = nil)
43
48
  context = Class.new do
44
49
  def initialize(env) @env = env && env.to_s ; end
45
- def method_missing(*) ; end
46
- def only(env)
47
- old, @only = @only, _combine_onlys(env)
50
+ def method_missing(*) ; yield if block_given? ; end
51
+ def only(*env)
52
+ old, @only = @only, _combine_only(env.flatten)
48
53
  yield
49
54
  @only = old
50
55
  end
51
- def except(env)
52
- old, @except = @except, _combine_excepts(env)
56
+ def except(*env)
57
+ old, @except = @except, _combine_except(env.flatten)
53
58
  yield
54
59
  @except = old
55
60
  end
56
61
  def gem(name, *args)
57
- opt = args.last || {}
58
- only = _combine_onlys(opt[:only] || opt["only"])
59
- except = _combine_excepts(opt[:except] || opt["except"])
62
+ opt = args.last.is_a?(Hash) ? args.pop : {}
63
+ only = _combine_only(opt[:only] || opt["only"])
64
+ except = _combine_except(opt[:except] || opt["except"])
60
65
  files = opt[:require_as] || opt["require_as"] || name
61
66
  files = [files] unless files.respond_to?(:each)
62
67
 
63
68
  return unless !only || only.any? {|e| e == @env }
64
69
  return if except && except.any? {|e| e == @env }
65
70
 
66
- files.each { |f| require f }
71
+ if files = opt[:require_as] || opt["require_as"]
72
+ files = Array(files)
73
+ files.each { |f| require f }
74
+ else
75
+ begin
76
+ require name
77
+ rescue LoadError
78
+ # Do nothing
79
+ end
80
+ end
67
81
  yield if block_given?
68
82
  true
69
83
  end
70
84
  private
71
- def _combine_onlys(only)
85
+ def _combine_only(only)
72
86
  return @only unless only
73
87
  only = [only].flatten.compact.uniq.map { |o| o.to_s }
74
88
  only &= @only if @only
75
89
  only
76
90
  end
77
- def _combine_excepts(except)
91
+ def _combine_except(except)
78
92
  return @except unless except
79
93
  except = [except].flatten.compact.uniq.map { |o| o.to_s }
80
94
  except |= @except if @except
81
95
  except
82
96
  end
83
97
  end
84
- context.new(env && env.to_s).instance_eval(File.read(@gemfile))
98
+ context.new(env && env.to_s).instance_eval(File.read(@gemfile), @gemfile, 1)
85
99
  end
86
100
  end
87
101
 
88
102
  <% if options[:rubygems] -%>
89
103
  module Gem
104
+ @loaded_stacks = Hash.new { |h,k| h[k] = [] }
105
+
90
106
  def source_index.refresh!
91
107
  super
92
108
  Bundler.add_specs_to_index
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-09-23 00:00:00 -07:00
13
+ date: 2009-11-05 00:00:00 -08:00
14
14
  default_executable:
15
15
  dependencies: []
16
16