bundler 1.0.21 → 1.1.rc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGELOG.md +153 -1
  3. data/README.md +3 -3
  4. data/Rakefile +15 -27
  5. data/bin/bundle +7 -0
  6. data/bundler.gemspec +1 -1
  7. data/lib/bundler/cli.rb +126 -45
  8. data/lib/bundler/definition.rb +22 -5
  9. data/lib/bundler/dep_proxy.rb +35 -0
  10. data/lib/bundler/dsl.rb +17 -34
  11. data/lib/bundler/endpoint_specification.rb +69 -0
  12. data/lib/bundler/fetcher.rb +221 -0
  13. data/lib/bundler/gem_helper.rb +0 -1
  14. data/lib/bundler/gem_helpers.rb +23 -0
  15. data/lib/bundler/index.rb +77 -38
  16. data/lib/bundler/installer.rb +43 -1
  17. data/lib/bundler/man/bundle-benchmark +19 -0
  18. data/lib/bundler/man/bundle-benchmark.txt +27 -0
  19. data/lib/bundler/man/bundle-config +1 -1
  20. data/lib/bundler/man/bundle-config.txt +3 -3
  21. data/lib/bundler/man/bundle-install +288 -0
  22. data/lib/bundler/man/bundle-install.txt +74 -79
  23. data/lib/bundler/man/bundle-package +1 -1
  24. data/lib/bundler/man/bundle-package.txt +1 -1
  25. data/lib/bundler/man/bundle-update +1 -1
  26. data/lib/bundler/man/bundle-update.txt +41 -41
  27. data/lib/bundler/man/gemfile.5 +6 -7
  28. data/lib/bundler/man/gemfile.5.txt +9 -9
  29. data/lib/bundler/match_platform.rb +13 -0
  30. data/lib/bundler/remote_specification.rb +6 -8
  31. data/lib/bundler/resolver.rb +32 -19
  32. data/lib/bundler/rubygems_ext.rb +2 -86
  33. data/lib/bundler/rubygems_integration.rb +35 -0
  34. data/lib/bundler/runtime.rb +84 -1
  35. data/lib/bundler/source.rb +85 -88
  36. data/lib/bundler/spec_set.rb +2 -0
  37. data/lib/bundler/templates/Executable +1 -1
  38. data/lib/bundler/templates/newgem/Gemfile.tt +1 -1
  39. data/lib/bundler/templates/newgem/Rakefile.tt +1 -0
  40. data/lib/bundler/templates/newgem/bin/newgem.tt +1 -1
  41. data/lib/bundler/templates/newgem/gitignore.tt +14 -1
  42. data/lib/bundler/templates/newgem/newgem.gemspec.tt +13 -20
  43. data/lib/bundler/ui.rb +32 -17
  44. data/lib/bundler/vendor/net/http/faster.rb +27 -0
  45. data/lib/bundler/vendor/net/http/persistent.rb +468 -0
  46. data/lib/bundler/version.rb +1 -1
  47. data/lib/bundler.rb +56 -23
  48. data/man/bundle-install.ronn +7 -0
  49. data/man/bundle.ronn +3 -0
  50. data/man/gemfile.5.ronn +6 -6
  51. data/spec/bundler/dsl_spec.rb +22 -0
  52. data/spec/bundler/source_spec.rb +25 -0
  53. data/spec/install/deprecated_spec.rb +2 -3
  54. data/spec/install/gems/dependency_api_spec.rb +358 -0
  55. data/spec/install/gems/flex_spec.rb +1 -1
  56. data/spec/install/gems/groups_spec.rb +17 -8
  57. data/spec/install/gems/platform_spec.rb +16 -0
  58. data/spec/install/gems/post_install_spec.rb +47 -0
  59. data/spec/install/gems/simple_case_spec.rb +61 -64
  60. data/spec/install/gems/standalone_spec.rb +238 -0
  61. data/spec/install/git_spec.rb +62 -0
  62. data/spec/other/check_spec.rb +30 -0
  63. data/spec/other/clean_spec.rb +397 -0
  64. data/spec/other/exec_spec.rb +0 -29
  65. data/spec/other/newgem_spec.rb +39 -0
  66. data/spec/other/outdated_spec.rb +93 -0
  67. data/spec/other/show_spec.rb +6 -0
  68. data/spec/quality_spec.rb +1 -1
  69. data/spec/realworld/edgecases_spec.rb +12 -0
  70. data/spec/runtime/executable_spec.rb +10 -0
  71. data/spec/runtime/require_spec.rb +8 -9
  72. data/spec/runtime/with_clean_env_spec.rb +60 -7
  73. data/spec/spec_helper.rb +8 -1
  74. data/spec/support/artifice/endopint_marshal_fail_basic_authentication.rb +13 -0
  75. data/spec/support/artifice/endpoint.rb +54 -0
  76. data/spec/support/artifice/endpoint_500.rb +37 -0
  77. data/spec/support/artifice/endpoint_api_missing.rb +16 -0
  78. data/spec/support/artifice/endpoint_basic_authentication.rb +13 -0
  79. data/spec/support/artifice/endpoint_extra.rb +27 -0
  80. data/spec/support/artifice/endpoint_extra_missing.rb +15 -0
  81. data/spec/support/artifice/endpoint_fallback.rb +18 -0
  82. data/spec/support/artifice/endpoint_marshal_fail.rb +11 -0
  83. data/spec/support/artifice/endpoint_redirect.rb +15 -0
  84. data/spec/support/builders.rb +7 -0
  85. data/spec/support/fakeweb/rack-1.0.0.marshal +2 -0
  86. data/spec/support/fakeweb/windows.rb +23 -0
  87. data/spec/support/helpers.rb +36 -3
  88. data/spec/support/path.rb +2 -0
  89. data/spec/support/rubygems_ext.rb +3 -3
  90. metadata +48 -74
data/lib/bundler/dsl.rb CHANGED
@@ -20,6 +20,8 @@ module Bundler
20
20
  @env = nil
21
21
  end
22
22
 
23
+ attr_accessor :dependencies
24
+
23
25
  def gemspec(opts = nil)
24
26
  path = opts && opts[:path] || '.'
25
27
  name = opts && opts[:name] || '{,*}'
@@ -90,18 +92,18 @@ module Bundler
90
92
  def source(source, options = {})
91
93
  case source
92
94
  when :gemcutter, :rubygems, :rubyforge then
93
- rubygems_source "http://rubygems.org"
95
+ @rubygems_source.add_remote "http://rubygems.org"
94
96
  return
95
97
  when String
96
- rubygems_source source
98
+ @rubygems_source.add_remote source
97
99
  return
98
- end
99
-
100
- @source = source
101
- options[:prepend] ? @sources.unshift(@source) : @sources << @source
100
+ else
101
+ @source = source
102
+ options[:prepend] ? @sources.unshift(@source) : @sources << @source
102
103
 
103
- yield if block_given?
104
- @source
104
+ yield if block_given?
105
+ return @source
106
+ end
105
107
  ensure
106
108
  @source = nil
107
109
  end
@@ -126,8 +128,7 @@ module Bundler
126
128
  end
127
129
 
128
130
  def to_definition(lockfile, unlock)
129
- @sources << @rubygems_source
130
- @sources.uniq!
131
+ @sources << @rubygems_source unless @sources.include?(@rubygems_source)
131
132
  Definition.new(lockfile, @dependencies, @sources, unlock)
132
133
  end
133
134
 
@@ -168,21 +169,8 @@ module Bundler
168
169
  end
169
170
  end
170
171
 
171
- deprecate :only, :group
172
- deprecate :except
173
- deprecate :disable_system_gems
174
- deprecate :disable_rubygems
175
- deprecate :clear_sources
176
- deprecate :bundle_path
177
- deprecate :bin_path
178
-
179
172
  private
180
173
 
181
- def rubygems_source(source)
182
- @rubygems_source.add_remote source
183
- @sources << @rubygems_source
184
- end
185
-
186
174
  def _normalize_hash(opts)
187
175
  # Cannot modify a hash during an iteration in 1.9
188
176
  opts.keys.each do |k|
@@ -197,7 +185,7 @@ module Bundler
197
185
  def _normalize_options(name, version, opts)
198
186
  _normalize_hash(opts)
199
187
 
200
- invalid_keys = opts.keys - %w(group groups git path name branch ref tag require submodules platform platforms type)
188
+ invalid_keys = opts.keys - %w(group groups git github path name branch ref tag require submodules platform platforms type)
201
189
  if invalid_keys.any?
202
190
  plural = invalid_keys.size > 1
203
191
  message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "
@@ -223,7 +211,11 @@ module Bundler
223
211
  raise DslError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
224
212
  end
225
213
 
226
- # Normalize git and path options
214
+ if github = opts.delete("github")
215
+ github = "#{github}/#{github}" unless github.include?("/")
216
+ opts["git"] = "git://github.com/#{github}.git"
217
+ end
218
+
227
219
  ["git", "path"].each do |type|
228
220
  if param = opts[type]
229
221
  if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
@@ -243,15 +235,6 @@ module Bundler
243
235
  end
244
236
 
245
237
  def _deprecated_options(options)
246
- if options.include?(:require_as)
247
- raise DeprecatedError, "Please replace :require_as with :require"
248
- elsif options.include?(:vendored_at)
249
- raise DeprecatedError, "Please replace :vendored_at with :path"
250
- elsif options.include?(:only)
251
- raise DeprecatedError, "Please replace :only with :group"
252
- elsif options.include?(:except)
253
- raise DeprecatedError, "The :except option is no longer supported"
254
- end
255
238
  end
256
239
  end
257
240
  end
@@ -0,0 +1,69 @@
1
+ require 'uri'
2
+
3
+ module Bundler
4
+ # used for Creating Specifications from the Gemcutter Endpoint
5
+ class EndpointSpecification < Gem::Specification
6
+ include MatchPlatform
7
+
8
+ attr_reader :name, :version, :platform, :dependencies
9
+ attr_accessor :source
10
+
11
+ def initialize(name, version, platform, dependencies)
12
+ @name = name
13
+ @version = version
14
+ @platform = platform
15
+ @dependencies = dependencies
16
+ end
17
+
18
+ def fetch_platform
19
+ @plaftorm
20
+ end
21
+
22
+ # needed for standalone, load required_paths from local gemspec
23
+ # after the gem in installed
24
+ def require_paths
25
+ if @remote_specification
26
+ @remote_specification.require_paths
27
+ elsif _local_specification
28
+ _local_specification.require_paths
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ # needed for binstubs
35
+ def executables
36
+ if @remote_specification
37
+ @remote_specification.executables
38
+ elsif _local_specification
39
+ _local_specification.executables
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ # needed for bundle clean
46
+ def bindir
47
+ if @remote_specification
48
+ @remote_specification.bindir
49
+ elsif _local_specification
50
+ _local_specification.bindir
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def _local_specification
57
+ eval(File.read(local_specification_path)) if @loaded_from && File.exists?(local_specification_path)
58
+ end
59
+
60
+ def __swap__(spec)
61
+ @remote_specification = spec
62
+ end
63
+
64
+ private
65
+ def local_specification_path
66
+ "#{base_dir}/specifications/#{full_name}.gemspec"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,221 @@
1
+ require 'uri'
2
+ require 'net/http/persistent'
3
+
4
+ module Bundler
5
+ # Handles all the fetching with the rubygems server
6
+ class Fetcher
7
+ REDIRECT_LIMIT = 5
8
+
9
+ attr_reader :has_api
10
+
11
+ class << self
12
+ attr_accessor :disable_endpoint
13
+
14
+ @@spec_fetch_map ||= {}
15
+
16
+ def fetch(spec)
17
+ spec, uri = @@spec_fetch_map[spec.full_name]
18
+ if spec
19
+ path = download_gem_from_uri(spec, uri)
20
+ s = Bundler.rubygems.spec_from_gem(path)
21
+ spec.__swap__(s)
22
+ end
23
+ end
24
+
25
+ def download_gem_from_uri(spec, uri)
26
+ spec.fetch_platform
27
+
28
+ download_path = Bundler.requires_sudo? ? Bundler.tmp : Bundler.rubygems.gem_dir
29
+ gem_path = "#{Bundler.rubygems.gem_dir}/cache/#{spec.full_name}.gem"
30
+
31
+ FileUtils.mkdir_p("#{download_path}/cache")
32
+ Bundler.rubygems.download_gem(spec, uri, download_path)
33
+
34
+ if Bundler.requires_sudo?
35
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/cache"
36
+ Bundler.sudo "mv #{Bundler.tmp}/cache/#{spec.full_name}.gem #{gem_path}"
37
+ end
38
+
39
+ gem_path
40
+ end
41
+ end
42
+
43
+ def initialize(remote_uri)
44
+ @remote_uri = remote_uri
45
+ @has_api = true # will be set to false if the rubygems index is ever fetched
46
+ @@connection ||= Net::HTTP::Persistent.new nil, :ENV
47
+ end
48
+
49
+ # fetch a gem specification
50
+ def fetch_spec(spec)
51
+ spec = spec - [nil, 'ruby', '']
52
+ spec_file_name = "#{spec.join '-'}.gemspec.rz"
53
+
54
+ uri = URI.parse("#{@remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}")
55
+
56
+ spec_rz = (uri.scheme == "file") ? Gem.read_binary(uri.path) : fetch(uri)
57
+ Marshal.load Gem.inflate(spec_rz)
58
+ end
59
+
60
+ # return the specs in the bundler format as an index
61
+ def specs(gem_names, source)
62
+ index = Index.new
63
+
64
+ if !gem_names || @remote_uri.scheme == "file" || Bundler::Fetcher.disable_endpoint
65
+ Bundler.ui.info "Fetching source index from #{strip_user_pass_from_uri(@remote_uri)}"
66
+ specs = fetch_all_remote_specs
67
+ else
68
+ Bundler.ui.info "Fetching gem metadata from #{strip_user_pass_from_uri(@remote_uri)}", Bundler.ui.debug?
69
+ begin
70
+ specs = fetch_remote_specs(gem_names)
71
+ # fall back to the legacy index in the following cases
72
+ # 1. Gemcutter Endpoint doesn't return a 200
73
+ # 2. Marshal blob doesn't load properly
74
+ # 3. One of the YAML gemspecs has the Syck::DefaultKey problem
75
+ rescue HTTPError, TypeError => e
76
+ # new line now that the dots are over
77
+ Bundler.ui.info "" unless Bundler.ui.debug?
78
+
79
+ if @remote_uri.to_s.include?("rubygems.org")
80
+ Bundler.ui.info "Error #{e.class} during request to dependency API"
81
+ end
82
+ Bundler.ui.debug e.message
83
+ Bundler.ui.debug e.backtrace
84
+
85
+ Bundler.ui.info "Fetching full source index from #{strip_user_pass_from_uri(@remote_uri)}"
86
+ specs = fetch_all_remote_specs
87
+ else
88
+ # new line now that the dots are over
89
+ Bundler.ui.info "" unless Bundler.ui.debug?
90
+ end
91
+ end
92
+
93
+ specs[@remote_uri].each do |name, version, platform, dependencies|
94
+ next if name == 'bundler'
95
+ spec = nil
96
+ if dependencies
97
+ spec = EndpointSpecification.new(name, version, platform, dependencies)
98
+ else
99
+ spec = RemoteSpecification.new(name, version, platform, self)
100
+ end
101
+ spec.source = source
102
+ @@spec_fetch_map[spec.full_name] = [spec, @remote_uri]
103
+ index << spec
104
+ end
105
+
106
+ index
107
+ end
108
+
109
+ # fetch index
110
+ def fetch_remote_specs(gem_names, full_dependency_list = [], last_spec_list = [])
111
+ query_list = gem_names - full_dependency_list
112
+
113
+ # only display the message on the first run
114
+ if Bundler.ui.debug?
115
+ Bundler.ui.debug "Query List: #{query_list.inspect}"
116
+ else
117
+ Bundler.ui.info ".", false
118
+ end
119
+
120
+ return {@remote_uri => last_spec_list} if query_list.empty?
121
+
122
+ spec_list, deps_list = fetch_dependency_remote_specs(query_list)
123
+ returned_gems = spec_list.map {|spec| spec.first }.uniq
124
+
125
+ fetch_remote_specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list)
126
+ end
127
+
128
+ private
129
+
130
+ def fetch(uri, counter = 0)
131
+ raise HTTPError, "Too many redirects" if counter >= REDIRECT_LIMIT
132
+
133
+ begin
134
+ Bundler.ui.debug "Fetching from: #{uri}"
135
+ response = @@connection.request(uri)
136
+ rescue SocketError, Timeout, Net::HTTP::Persistent::Error
137
+ raise Bundler::HTTPError, "Network error while fetching #{uri}"
138
+ end
139
+
140
+ case response
141
+ when Net::HTTPRedirection
142
+ Bundler.ui.debug("HTTP Redirection")
143
+ new_uri = URI.parse(response["location"])
144
+ new_uri.user = uri.user
145
+ new_uri.password = uri.password
146
+ fetch(new_uri, counter + 1)
147
+ when Net::HTTPSuccess
148
+ Bundler.ui.debug("HTTP Success")
149
+ response.body
150
+ else
151
+ Bundler.ui.debug("HTTP Error")
152
+ raise HTTPError
153
+ end
154
+ end
155
+
156
+ # fetch from Gemcutter Dependency Endpoint API
157
+ def fetch_dependency_remote_specs(gem_names)
158
+ Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(' ')}"
159
+ uri = URI.parse("#{@remote_uri}api/v1/dependencies?gems=#{gem_names.join(",")}")
160
+ marshalled_deps = fetch(uri)
161
+ gem_list = Marshal.load(marshalled_deps)
162
+ deps_list = []
163
+
164
+ spec_list = gem_list.map do |s|
165
+ dependencies = s[:dependencies].map do |d|
166
+ begin
167
+ name, requirement = d
168
+ dep = Gem::Dependency.new(name, requirement.split(", "))
169
+ rescue ArgumentError => e
170
+ if e.message.include?('Illformed requirement ["#<YAML::Syck::DefaultKey')
171
+ puts # we shouldn't print the error message on the "fetching info" status line
172
+ raise GemspecError, %{Unfortunately, the gem #{s[:name]} (#{s[:number]}) } +
173
+ %{has an invalid gemspec. As a result, Bundler cannot install this Gemfile. } +
174
+ %{Please ask the gem author to yank the bad version to fix this issue. For } +
175
+ %{more information, see http://bit.ly/illformed-requirement.}
176
+ else
177
+ raise e
178
+ end
179
+ end
180
+
181
+ deps_list << dep.name
182
+
183
+ dep
184
+ end
185
+
186
+ [s[:name], Gem::Version.new(s[:number]), s[:platform], dependencies]
187
+ end
188
+
189
+ [spec_list, deps_list.uniq]
190
+ end
191
+
192
+ # fetch from modern index: specs.4.8.gz
193
+ def fetch_all_remote_specs
194
+ @has_api = false
195
+ Gem.sources = ["#{@remote_uri}"]
196
+ spec_list = Hash.new { |h,k| h[k] = [] }
197
+ begin
198
+ # Fetch all specs, minus prerelease specs
199
+ spec_list = Gem::SpecFetcher.new.list(true, false)
200
+ # Then fetch the prerelease specs
201
+ begin
202
+ Gem::SpecFetcher.new.list(false, true).each {|k, v| spec_list[k] += v }
203
+ rescue Gem::RemoteFetcher::FetchError
204
+ Bundler.ui.debug "Could not fetch prerelease specs from #{strip_user_pass_from_uri(@remote_uri)}"
205
+ end
206
+ rescue Gem::RemoteFetcher::FetchError
207
+ raise Bundler::HTTPError, "Could not reach #{strip_user_pass_from_uri(@remote_uri)}"
208
+ end
209
+
210
+ return spec_list
211
+ end
212
+
213
+ def strip_user_pass_from_uri(uri)
214
+ uri_dup = uri.dup
215
+ uri_dup.user = "****" if uri_dup.user
216
+ uri_dup.password = "****" if uri_dup.password
217
+
218
+ uri_dup
219
+ end
220
+ end
221
+ end
@@ -42,7 +42,6 @@ module Bundler
42
42
  def build_gem
43
43
  file_name = nil
44
44
  sh("gem build -V '#{spec_path}'") { |out, code|
45
- raise out unless out[/Successfully/]
46
45
  file_name = File.basename(built_gem_path)
47
46
  FileUtils.mkdir_p(File.join(base, 'pkg'))
48
47
  FileUtils.mv(built_gem_path, 'pkg')
@@ -0,0 +1,23 @@
1
+ module Bundler
2
+ module GemHelpers
3
+
4
+ GENERIC_CACHE = {}
5
+ GENERICS = [
6
+ Gem::Platform.new('java'),
7
+ Gem::Platform.new('mswin32'),
8
+ Gem::Platform.new('x86-mingw32'),
9
+ Gem::Platform::RUBY
10
+ ]
11
+
12
+ def generic(p)
13
+ return p if p == Gem::Platform::RUBY
14
+
15
+ GENERIC_CACHE[p] ||= begin
16
+ found = GENERICS.find do |p2|
17
+ p2.is_a?(Gem::Platform) && p.os == p2.os
18
+ end
19
+ found || Gem::Platform::RUBY
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/bundler/index.rb CHANGED
@@ -8,16 +8,18 @@ module Bundler
8
8
  i
9
9
  end
10
10
 
11
- attr_reader :specs
11
+ attr_reader :specs, :sources
12
12
  protected :specs
13
13
 
14
14
  def initialize
15
+ @sources = []
15
16
  @cache = {}
16
17
  @specs = Hash.new { |h,k| h[k] = [] }
17
18
  end
18
19
 
19
20
  def initialize_copy(o)
20
21
  super
22
+ @sources = @sources.dup
21
23
  @cache = {}
22
24
  @specs = Hash.new { |h,k| h[k] = [] }
23
25
 
@@ -26,43 +28,43 @@ module Bundler
26
28
  end
27
29
  end
28
30
 
31
+ def inspect
32
+ "<Index sources=#{sources.map{|s| s.inspect}} specs.size=#{specs.size}>"
33
+ end
34
+
29
35
  def empty?
30
36
  each { return false }
31
37
  true
32
38
  end
33
39
 
34
- def search(query)
35
- case query
36
- when Gem::Specification, RemoteSpecification, LazySpecification then search_by_spec(query)
37
- when String then @specs[query]
38
- else search_by_dependency(query)
40
+ # Search this index's specs, and any source indexes that this index knows
41
+ # about, returning all of the results.
42
+ def search(query, base = nil)
43
+ results = local_search(query, base)
44
+ @sources.each do |source|
45
+ results += source.search(query, base)
39
46
  end
47
+ results
40
48
  end
41
49
 
42
- def search_for_all_platforms(dependency, base = [])
43
- specs = @specs[dependency.name] + base
44
-
45
- wants_prerelease = dependency.requirement.prerelease?
46
- only_prerelease = specs.all? {|spec| spec.version.prerelease? }
47
- found = specs.select { |spec| dependency.matches_spec?(spec) }
48
-
49
- unless wants_prerelease || only_prerelease
50
- found.reject! { |spec| spec.version.prerelease? }
50
+ def local_search(query, base = nil)
51
+ case query
52
+ when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
53
+ when String then specs_by_name(query)
54
+ when Gem::Dependency then search_by_dependency(query, base)
55
+ else
56
+ raise "You can't search for a #{query.inspect}."
51
57
  end
52
-
53
- found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
54
58
  end
55
59
 
56
- def sources
57
- @specs.values.map do |specs|
58
- specs.map{|s| s.source.class }
59
- end.flatten.uniq
60
+ def source_types
61
+ sources.map{|s| s.class }.uniq
60
62
  end
61
63
 
62
64
  alias [] search
63
65
 
64
66
  def <<(spec)
65
- arr = @specs[spec.name]
67
+ arr = specs_by_name(spec.name)
66
68
 
67
69
  arr.delete_if do |s|
68
70
  same_version?(s.version, spec.version) && s.platform == spec.platform
@@ -73,11 +75,21 @@ module Bundler
73
75
  end
74
76
 
75
77
  def each(&blk)
76
- @specs.values.each do |specs|
78
+ specs.values.each do |specs|
77
79
  specs.each(&blk)
78
80
  end
79
81
  end
80
82
 
83
+ # returns a list of the dependencies
84
+ def unmet_dependency_names
85
+ dependency_names = specs.values.map do |array_of_s|
86
+ array_of_s.map do |s|
87
+ s.dependencies.map{|d| d.name }
88
+ end
89
+ end.flatten.uniq
90
+ dependency_names.select{|name| specs_by_name(name).empty? }
91
+ end
92
+
81
93
  def use(other, override_dupes = false)
82
94
  return unless other
83
95
  other.each do |s|
@@ -90,16 +102,57 @@ module Bundler
90
102
  self
91
103
  end
92
104
 
105
+ def size
106
+ @sources.inject(@specs.size) do |size, source|
107
+ size += source.size
108
+ end
109
+ end
110
+
93
111
  def ==(o)
94
112
  all? do |s|
95
113
  s2 = o[s].first and (s.dependencies & s2.dependencies).empty?
96
114
  end
97
115
  end
98
116
 
117
+ def add_source(index)
118
+ if index.is_a?(Index)
119
+ @sources << index
120
+ @sources.uniq! # need to use uniq! here instead of checking for the item before adding
121
+ else
122
+ raise ArgumentError, "Source must be an index, not #{index.class}"
123
+ end
124
+ end
125
+
99
126
  private
100
127
 
128
+ def specs_by_name(name)
129
+ @specs[name]
130
+ end
131
+
132
+ def search_by_dependency(dependency, base = nil)
133
+ @cache[dependency.hash^base.hash] ||= begin
134
+ specs = specs_by_name(dependency.name) + (base || [])
135
+ found = specs.select do |spec|
136
+ if base # allow all platforms when searching from a lockfile
137
+ dependency.matches_spec?(spec)
138
+ else
139
+ dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform)
140
+ end
141
+ end
142
+
143
+ wants_prerelease = dependency.requirement.prerelease?
144
+ only_prerelease = specs.all? {|spec| spec.version.prerelease? }
145
+
146
+ unless wants_prerelease || only_prerelease
147
+ found.reject! { |spec| spec.version.prerelease? }
148
+ end
149
+
150
+ found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
151
+ end
152
+ end
153
+
101
154
  def search_by_spec(spec)
102
- @specs[spec.name].select do |s|
155
+ specs_by_name(spec.name).select do |s|
103
156
  same_version?(s.version, spec.version) && Gem::Platform.new(s.platform) == Gem::Platform.new(spec.platform)
104
157
  end
105
158
  end
@@ -120,19 +173,5 @@ module Bundler
120
173
  dep.requirement.satisfied_by?(spec.version)
121
174
  end
122
175
 
123
- def search_by_dependency(dependency)
124
- @cache[dependency.hash] ||= begin
125
- specs = @specs[dependency.name]
126
- found = specs.select { |spec| dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform) }
127
-
128
- wants_prerelease = dependency.requirement.prerelease?
129
- only_prerelease = specs.all? {|spec| spec.version.prerelease? }
130
- unless wants_prerelease || only_prerelease
131
- found.reject! { |spec| spec.version.prerelease? }
132
- end
133
-
134
- found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
135
- end
136
- end
137
176
  end
138
177
  end
@@ -3,6 +3,10 @@ require 'rubygems/dependency_installer'
3
3
 
4
4
  module Bundler
5
5
  class Installer < Environment
6
+ class << self
7
+ attr_accessor :post_install_messages
8
+ end
9
+
6
10
  def self.install(root, definition, options = {})
7
11
  installer = new(root, definition)
8
12
  installer.run(options)
@@ -24,6 +28,7 @@ module Bundler
24
28
 
25
29
  if dependencies.empty?
26
30
  Bundler.ui.warn "The Gemfile specifies no dependencies"
31
+ lock
27
32
  return
28
33
  end
29
34
 
@@ -46,11 +51,13 @@ module Bundler
46
51
  # Must install gems in the order that the resolver provides
47
52
  # as dependencies might actually affect the installation of
48
53
  # the gem.
54
+ Installer.post_install_messages = {}
49
55
  specs.each do |spec|
50
56
  install_gem_from_spec(spec)
51
57
  end
52
58
 
53
59
  lock
60
+ generate_standalone(options[:standalone]) if options[:standalone]
54
61
  end
55
62
 
56
63
  private
@@ -58,7 +65,7 @@ module Bundler
58
65
  def install_gem_from_spec(spec)
59
66
  # Download the gem to get the spec, because some specs that are returned
60
67
  # by rubygems.org are broken and wrong.
61
- spec.source.fetch(spec) if spec.source.respond_to?(:fetch)
68
+ Bundler::Fetcher.fetch(spec) if spec.source.is_a?(Bundler::Source::Rubygems)
62
69
 
63
70
  # Fetch the build settings, if there are any
64
71
  settings = Bundler.settings["build.#{spec.name}"]
@@ -72,6 +79,10 @@ module Bundler
72
79
  generate_bundler_executable_stubs(spec) if Bundler.settings[:bin]
73
80
  FileUtils.rm_rf(Bundler.tmp)
74
81
  rescue Exception => e
82
+ # install hook failed
83
+ raise e if e.is_a?(Gem::InstallError)
84
+
85
+ # other failure, likely a native extension build failure
75
86
  Bundler.ui.info ""
76
87
  Bundler.ui.warn "#{e.class}: #{e.message}"
77
88
  msg = "An error occured while installing #{spec.name} (#{spec.version}),"
@@ -93,5 +104,36 @@ module Bundler
93
104
  end
94
105
  end
95
106
  end
107
+
108
+ def generate_standalone(groups)
109
+ standalone_path = Bundler.settings[:path]
110
+ bundler_path = File.join(standalone_path, "bundler")
111
+ FileUtils.mkdir_p(bundler_path)
112
+
113
+ paths = []
114
+
115
+ if groups.empty?
116
+ specs = Bundler.definition.requested_specs
117
+ else
118
+ specs = Bundler.definition.specs_for groups.map { |g| g.to_sym }
119
+ end
120
+
121
+ specs.each do |spec|
122
+ next if spec.name == "bundler"
123
+
124
+ spec.require_paths.each do |path|
125
+ full_path = File.join(spec.full_gem_path, path)
126
+ paths << Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path))
127
+ end
128
+ end
129
+
130
+
131
+ File.open File.join(bundler_path, "setup.rb"), "w" do |file|
132
+ file.puts "path = File.expand_path('..', __FILE__)"
133
+ paths.each do |path|
134
+ file.puts %{$:.unshift File.expand_path("\#{path}/#{path}")}
135
+ end
136
+ end
137
+ end
96
138
  end
97
139
  end