bundler 1.2.5 → 1.3.0.pre

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.

Files changed (124) hide show
  1. data/.gitignore +10 -7
  2. data/.travis.yml +12 -3
  3. data/CHANGELOG.md +26 -19
  4. data/CONTRIBUTE.md +97 -0
  5. data/README.md +4 -2
  6. data/Rakefile +17 -59
  7. data/bundler.gemspec +2 -1
  8. data/lib/bundler.rb +23 -20
  9. data/lib/bundler/cli.rb +68 -22
  10. data/lib/bundler/definition.rb +3 -2
  11. data/lib/bundler/deprecate.rb +15 -0
  12. data/lib/bundler/dsl.rb +14 -16
  13. data/lib/bundler/environment.rb +0 -5
  14. data/lib/bundler/fetcher.rb +23 -78
  15. data/lib/bundler/friendly_errors.rb +4 -5
  16. data/lib/bundler/gem_helper.rb +14 -16
  17. data/lib/bundler/injector.rb +64 -0
  18. data/lib/bundler/installer.rb +1 -7
  19. data/lib/bundler/lazy_specification.rb +6 -3
  20. data/lib/bundler/lockfile_parser.rb +25 -13
  21. data/lib/bundler/resolver.rb +0 -1
  22. data/lib/bundler/rubygems_integration.rb +83 -17
  23. data/lib/bundler/settings.rb +4 -2
  24. data/lib/bundler/similarity_detector.rb +63 -0
  25. data/lib/bundler/source.rb +3 -886
  26. data/lib/bundler/source/git.rb +267 -0
  27. data/lib/bundler/source/git/git_proxy.rb +142 -0
  28. data/lib/bundler/source/path.rb +209 -0
  29. data/lib/bundler/source/path/installer.rb +33 -0
  30. data/lib/bundler/source/rubygems.rb +261 -0
  31. data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -0
  32. data/lib/bundler/templates/newgem/rspec.tt +2 -0
  33. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +9 -0
  34. data/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +2 -0
  35. data/lib/bundler/templates/newgem/test/minitest_helper.rb.tt +4 -0
  36. data/lib/bundler/templates/newgem/test/test_newgem.rb.tt +11 -0
  37. data/lib/bundler/ui.rb +20 -5
  38. data/lib/bundler/vendor/.document +0 -0
  39. data/lib/bundler/vendor/thor.rb +74 -5
  40. data/lib/bundler/vendor/thor/actions.rb +5 -5
  41. data/lib/bundler/vendor/thor/actions/directory.rb +1 -0
  42. data/lib/bundler/vendor/thor/actions/file_manipulation.rb +7 -1
  43. data/lib/bundler/vendor/thor/base.rb +44 -11
  44. data/lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb +5 -0
  45. data/lib/bundler/vendor/thor/parser/argument.rb +14 -7
  46. data/lib/bundler/vendor/thor/parser/arguments.rb +7 -1
  47. data/lib/bundler/vendor/thor/parser/option.rb +8 -8
  48. data/lib/bundler/vendor/thor/parser/options.rb +62 -24
  49. data/lib/bundler/vendor/thor/runner.rb +1 -1
  50. data/lib/bundler/vendor/thor/shell/basic.rb +2 -2
  51. data/lib/bundler/vendor/thor/task.rb +2 -2
  52. data/lib/bundler/vendor/thor/version.rb +1 -1
  53. data/lib/bundler/vendored_persistent.rb +3 -15
  54. data/lib/bundler/version.rb +1 -1
  55. data/man/bundle-exec.ronn +1 -1
  56. data/man/bundle-update.ronn +1 -1
  57. data/man/bundle.ronn +4 -1
  58. data/spec/bundler/bundler_spec.rb +2 -28
  59. data/spec/bundler/cli_rspec.rb +9 -0
  60. data/spec/bundler/definition_spec.rb +1 -1
  61. data/spec/bundler/dsl_spec.rb +15 -8
  62. data/spec/bundler/gem_helper_spec.rb +38 -21
  63. data/spec/bundler/psyched_yaml_spec.rb +1 -0
  64. data/spec/bundler/source_spec.rb +3 -3
  65. data/spec/cache/gems_spec.rb +24 -24
  66. data/spec/cache/git_spec.rb +21 -23
  67. data/spec/cache/path_spec.rb +11 -11
  68. data/spec/cache/platform_spec.rb +6 -6
  69. data/spec/install/deploy_spec.rb +38 -38
  70. data/spec/install/gems/c_ext_spec.rb +2 -2
  71. data/spec/install/gems/dependency_api_spec.rb +23 -116
  72. data/spec/install/gems/env_spec.rb +1 -1
  73. data/spec/install/gems/flex_spec.rb +7 -8
  74. data/spec/install/gems/groups_spec.rb +10 -10
  75. data/spec/install/gems/packed_spec.rb +4 -4
  76. data/spec/install/gems/platform_spec.rb +3 -3
  77. data/spec/install/gems/post_install_spec.rb +9 -9
  78. data/spec/install/gems/resolving_spec.rb +2 -2
  79. data/spec/install/gems/simple_case_spec.rb +50 -53
  80. data/spec/install/gems/standalone_spec.rb +19 -19
  81. data/spec/install/gems/sudo_spec.rb +31 -16
  82. data/spec/install/gems/win32_spec.rb +1 -1
  83. data/spec/install/gemspec_spec.rb +6 -6
  84. data/spec/install/git_spec.rb +34 -34
  85. data/spec/install/invalid_spec.rb +3 -3
  86. data/spec/install/path_spec.rb +71 -8
  87. data/spec/install/upgrade_spec.rb +2 -2
  88. data/spec/integration/inject.rb +78 -0
  89. data/spec/lock/git_spec.rb +2 -2
  90. data/spec/lock/lockfile_spec.rb +14 -14
  91. data/spec/other/check_spec.rb +29 -29
  92. data/spec/other/clean_spec.rb +47 -48
  93. data/spec/other/config_spec.rb +20 -20
  94. data/spec/other/console_spec.rb +5 -5
  95. data/spec/other/exec_spec.rb +48 -28
  96. data/spec/other/ext_spec.rb +3 -3
  97. data/spec/other/help_spec.rb +6 -6
  98. data/spec/other/init_spec.rb +8 -8
  99. data/spec/other/newgem_spec.rb +95 -15
  100. data/spec/other/open_spec.rb +10 -5
  101. data/spec/other/outdated_spec.rb +8 -8
  102. data/spec/other/platform_spec.rb +45 -45
  103. data/spec/other/show_spec.rb +10 -10
  104. data/spec/quality_spec.rb +2 -2
  105. data/spec/realworld/dependency_api_spec.rb +61 -0
  106. data/spec/realworld/edgecases_spec.rb +8 -8
  107. data/spec/runtime/executable_spec.rb +13 -13
  108. data/spec/runtime/load_spec.rb +12 -12
  109. data/spec/runtime/platform_spec.rb +1 -1
  110. data/spec/runtime/require_spec.rb +24 -24
  111. data/spec/runtime/setup_spec.rb +113 -56
  112. data/spec/runtime/with_clean_env_spec.rb +11 -13
  113. data/spec/spec_helper.rb +6 -0
  114. data/spec/support/artifice/endpoint.rb +28 -13
  115. data/spec/support/artifice/endpoint_extra.rb +4 -0
  116. data/spec/support/builders.rb +1 -1
  117. data/spec/support/helpers.rb +2 -7
  118. data/spec/support/indexes.rb +3 -3
  119. data/spec/support/matchers.rb +6 -6
  120. data/spec/update/gems_spec.rb +19 -8
  121. data/spec/update/git_spec.rb +10 -10
  122. data/spec/update/source_spec.rb +1 -1
  123. metadata +86 -55
  124. data/.rspec +0 -2
@@ -111,6 +111,7 @@ module Bundler
111
111
  hash[key] = value
112
112
  hash.delete(key) if value.nil?
113
113
  FileUtils.mkdir_p(file.dirname)
114
+ require 'bundler/psyched_yaml'
114
115
  File.open(file, "w") { |f| f.puts hash.to_yaml }
115
116
  end
116
117
  value
@@ -127,9 +128,10 @@ module Bundler
127
128
 
128
129
  def load_config(config_file)
129
130
  if config_file.exist? && !config_file.size.zero?
130
- yaml = YAML.load_file(config_file)
131
+ Hash[config_file.read.scan(/^(BUNDLE_.+): '?(.+?)'?$/)]
132
+ else
133
+ {}
131
134
  end
132
- yaml || {}
133
135
  end
134
136
 
135
137
  end
@@ -0,0 +1,63 @@
1
+ module Bundler
2
+ class SimilarityDetector
3
+ SimilarityScore = Struct.new(:string, :distance)
4
+
5
+ # initialize with an array of words to be matched against
6
+ def initialize(corpus)
7
+ @corpus = corpus
8
+ end
9
+
10
+ # return an array of words similar to 'word' from the corpus
11
+ def similar_words(word, limit=3)
12
+ words_by_similarity = @corpus.map{|w| SimilarityScore.new(w, levenshtein_distance(word, w))}
13
+ words_by_similarity.select{|s| s.distance<=limit}.sort_by(&:distance).map(&:string)
14
+ end
15
+
16
+ # return the result of 'similar_words', concatenated into a list
17
+ # (eg "a, b, or c")
18
+ def similar_word_list(word, limit=3)
19
+ words = similar_words(word,limit)
20
+ if words.length==1
21
+ words[0]
22
+ elsif words.length>1
23
+ [words[0..-2].join(', '), words[-1]].join(' or ')
24
+ end
25
+ end
26
+
27
+
28
+ protected
29
+ # http://www.informit.com/articles/article.aspx?p=683059&seqNum=36
30
+ def levenshtein_distance(this, that, ins=2, del=2, sub=1)
31
+ # ins, del, sub are weighted costs
32
+ return nil if this.nil?
33
+ return nil if that.nil?
34
+ dm = [] # distance matrix
35
+
36
+ # Initialize first row values
37
+ dm[0] = (0..this.length).collect { |i| i * ins }
38
+ fill = [0] * (this.length - 1)
39
+
40
+ # Initialize first column values
41
+ for i in 1..that.length
42
+ dm[i] = [i * del, fill.flatten]
43
+ end
44
+
45
+ # populate matrix
46
+ for i in 1..that.length
47
+ for j in 1..this.length
48
+ # critical comparison
49
+ dm[i][j] = [
50
+ dm[i-1][j-1] +
51
+ (this[j-1] == that[i-1] ? 0 : sub),
52
+ dm[i][j-1] + ins,
53
+ dm[i-1][j] + del
54
+ ].min
55
+ end
56
+ end
57
+
58
+ # The last value in matrix is the Levenshtein distance between the strings
59
+ dm[that.length][this.length]
60
+ end
61
+
62
+ end
63
+ end
@@ -1,890 +1,7 @@
1
- require "uri"
2
- require 'rubygems/user_interaction'
3
- require "rubygems/installer"
4
- require "rubygems/spec_fetcher"
5
- require "rubygems/format"
6
- require "digest/sha1"
7
- require "fileutils"
8
-
9
1
  module Bundler
10
2
  module Source
11
- # TODO: Refactor this class
12
- class Rubygems
13
- FORCE_MODERN_INDEX_LIMIT = 100 # threshold for switching back to the modern index instead of fetching every spec
14
-
15
- attr_reader :remotes, :caches
16
- attr_accessor :dependency_names
17
-
18
- def initialize(options = {})
19
- @options = options
20
- @remotes = (options["remotes"] || []).map { |r| normalize_uri(r) }
21
- @fetchers = {}
22
- @allow_remote = false
23
- @allow_cached = false
24
-
25
- @caches = [ Bundler.app_cache ] +
26
- Bundler.rubygems.gem_path.map{|p| File.expand_path("#{p}/cache") }
27
- end
28
-
29
- def remote!
30
- @allow_remote = true
31
- end
32
-
33
- def cached!
34
- @allow_cached = true
35
- end
36
-
37
- def hash
38
- Rubygems.hash
39
- end
40
-
41
- def eql?(o)
42
- Rubygems === o
43
- end
44
-
45
- alias == eql?
46
-
47
- def options
48
- { "remotes" => @remotes.map { |r| r.to_s } }
49
- end
50
-
51
- def self.from_lock(options)
52
- s = new(options)
53
- Array(options["remote"]).each { |r| s.add_remote(r) }
54
- s
55
- end
56
-
57
- def to_lock
58
- out = "GEM\n"
59
- out << remotes.map {|r| " remote: #{r}\n" }.join
60
- out << " specs:\n"
61
- end
62
-
63
- def to_s
64
- remote_names = self.remotes.map { |r| r.to_s }.join(', ')
65
- "rubygems repository #{remote_names}"
66
- end
67
- alias_method :name, :to_s
68
-
69
- def specs
70
- @specs ||= fetch_specs
71
- end
72
-
73
- def install(spec)
74
- if installed_specs[spec].any?
75
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) "
76
- return
77
- end
78
-
79
- Bundler.ui.info "Installing #{spec.name} (#{spec.version}) "
80
- path = cached_gem(spec)
81
- if Bundler.requires_sudo?
82
- install_path = Bundler.tmp
83
- bin_path = install_path.join("bin")
84
- else
85
- install_path = Bundler.rubygems.gem_dir
86
- bin_path = Bundler.system_bindir
87
- end
88
-
89
- Bundler.rubygems.preserve_paths do
90
- Bundler::GemInstaller.new(path,
91
- :install_dir => install_path.to_s,
92
- :bin_dir => bin_path.to_s,
93
- :ignore_dependencies => true,
94
- :wrappers => true,
95
- :env_shebang => true
96
- ).install
97
- end
98
-
99
- if spec.post_install_message
100
- Installer.post_install_messages[spec.name] = spec.post_install_message
101
- end
102
-
103
- # SUDO HAX
104
- if Bundler.requires_sudo?
105
- Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/gems"
106
- Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/specifications"
107
- Bundler.sudo "cp -R #{Bundler.tmp}/gems/#{spec.full_name} #{Bundler.rubygems.gem_dir}/gems/"
108
- Bundler.sudo "cp -R #{Bundler.tmp}/specifications/#{spec.full_name}.gemspec #{Bundler.rubygems.gem_dir}/specifications/"
109
- Bundler.mkdir_p Bundler.system_bindir
110
- spec.executables.each do |exe|
111
- Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.system_bindir}"
112
- end
113
- end
114
-
115
- spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
116
- end
117
-
118
- def cache(spec)
119
- cached_path = cached_gem(spec)
120
- raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
121
- return if File.dirname(cached_path) == Bundler.app_cache.to_s
122
- Bundler.ui.info " * #{File.basename(cached_path)}"
123
- FileUtils.cp(cached_path, Bundler.app_cache)
124
- end
125
-
126
- def add_remote(source)
127
- @remotes << normalize_uri(source)
128
- end
129
-
130
- def replace_remotes(source)
131
- return false if source.remotes == @remotes
132
-
133
- @remotes = []
134
- source.remotes.each do |r|
135
- add_remote r.to_s
136
- end
137
-
138
- true
139
- end
140
-
141
- private
142
-
143
- def cached_gem(spec)
144
- possibilities = @caches.map { |p| "#{p}/#{spec.file_name}" }
145
- cached_gem = possibilities.find { |p| File.exist?(p) }
146
- unless cached_gem
147
- raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation"
148
- end
149
- cached_gem
150
- end
151
-
152
- def normalize_uri(uri)
153
- uri = uri.to_s
154
- uri = "#{uri}/" unless uri =~ %r'/$'
155
- uri = URI(uri)
156
- raise ArgumentError, "The source must be an absolute URI" unless uri.absolute?
157
- uri
158
- end
159
-
160
- def fetch_specs
161
- # remote_specs usually generates a way larger Index than the other
162
- # sources, and large_idx.use small_idx is way faster than
163
- # small_idx.use large_idx.
164
- if @allow_remote
165
- idx = remote_specs.dup
166
- else
167
- idx = Index.new
168
- end
169
- idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
170
- idx.use(installed_specs, :override_dupes)
171
- idx
172
- end
173
-
174
- def installed_specs
175
- @installed_specs ||= begin
176
- idx = Index.new
177
- have_bundler = false
178
- Bundler.rubygems.all_specs.reverse.each do |spec|
179
- next if spec.name == 'bundler' && spec.version.to_s != VERSION
180
- have_bundler = true if spec.name == 'bundler'
181
- spec.source = self
182
- idx << spec
183
- end
184
-
185
- # Always have bundler locally
186
- unless have_bundler
187
- # We're running bundler directly from the source
188
- # so, let's create a fake gemspec for it (it's a path)
189
- # gemspec
190
- bundler = Gem::Specification.new do |s|
191
- s.name = 'bundler'
192
- s.version = VERSION
193
- s.platform = Gem::Platform::RUBY
194
- s.source = self
195
- s.authors = ["bundler team"]
196
- s.loaded_from = File.expand_path("..", __FILE__)
197
- end
198
- idx << bundler
199
- end
200
- idx
201
- end
202
- end
203
-
204
- def cached_specs
205
- @cached_specs ||= begin
206
- idx = installed_specs.dup
207
-
208
- path = Bundler.app_cache
209
- Dir["#{path}/*.gem"].each do |gemfile|
210
- next if gemfile =~ /^bundler\-[\d\.]+?\.gem/
211
-
212
- begin
213
- s ||= Bundler.rubygems.spec_from_gem(gemfile)
214
- rescue Gem::Package::FormatError
215
- raise GemspecError, "Could not read gem at #{gemfile}. It may be corrupted."
216
- end
217
-
218
- s.source = self
219
- idx << s
220
- end
221
- end
222
-
223
- idx
224
- end
225
-
226
- def remote_specs
227
- @remote_specs ||= begin
228
- idx = Index.new
229
- old = Bundler.rubygems.sources
230
-
231
- sources = {}
232
- remotes.each do |uri|
233
- fetcher = Bundler::Fetcher.new(uri)
234
- specs = fetcher.specs(dependency_names, self)
235
- sources[fetcher] = specs.size
236
-
237
- idx.use specs
238
- end
239
-
240
- # don't need to fetch all specifications for every gem/version on
241
- # the rubygems repo if there's no api endpoints to search over
242
- # or it has too many specs to fetch
243
- fetchers = sources.keys
244
- api_fetchers = fetchers.select {|fetcher| fetcher.has_api }
245
- modern_index_fetchers = fetchers - api_fetchers
246
- if api_fetchers.any? && modern_index_fetchers.all? {|fetcher| sources[fetcher] < FORCE_MODERN_INDEX_LIMIT }
247
- # this will fetch all the specifications on the rubygems repo
248
- unmet_dependency_names = idx.unmet_dependency_names
249
- unmet_dependency_names -= ['bundler'] # bundler will always be unmet
250
-
251
- Bundler.ui.debug "Unmet Dependencies: #{unmet_dependency_names}"
252
- if unmet_dependency_names.any?
253
- api_fetchers.each do |fetcher|
254
- idx.use fetcher.specs(unmet_dependency_names, self)
255
- end
256
- end
257
- else
258
- Bundler::Fetcher.disable_endpoint = true
259
- api_fetchers.each {|fetcher| idx.use fetcher.specs([], self) }
260
- end
261
-
262
- idx
263
- ensure
264
- Bundler.rubygems.sources = old
265
- end
266
- end
267
- end
268
-
269
-
270
- class Path
271
- class Installer < Bundler::GemInstaller
272
- def initialize(spec, options = {})
273
- @spec = spec
274
- @bin_dir = Bundler.requires_sudo? ? "#{Bundler.tmp}/bin" : "#{Bundler.rubygems.gem_dir}/bin"
275
- @gem_dir = Bundler.rubygems.path(spec.full_gem_path)
276
- @wrappers = options[:wrappers] || true
277
- @env_shebang = options[:env_shebang] || true
278
- @format_executable = options[:format_executable] || false
279
- end
280
-
281
- def generate_bin
282
- return if spec.executables.nil? || spec.executables.empty?
283
-
284
- if Bundler.requires_sudo?
285
- FileUtils.mkdir_p("#{Bundler.tmp}/bin") unless File.exist?("#{Bundler.tmp}/bin")
286
- end
287
- super
288
- if Bundler.requires_sudo?
289
- Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/bin"
290
- spec.executables.each do |exe|
291
- Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.rubygems.gem_dir}/bin/"
292
- end
293
- end
294
- end
295
- end
296
-
297
- attr_reader :path, :options
298
- attr_writer :name
299
- attr_accessor :version
300
-
301
- DEFAULT_GLOB = "{,*,*/*}.gemspec"
302
-
303
- def initialize(options)
304
- @options = options
305
- @glob = options["glob"] || DEFAULT_GLOB
306
-
307
- @allow_cached = false
308
- @allow_remote = false
309
-
310
- if options["path"]
311
- @path = Pathname.new(options["path"])
312
- @path = @path.expand_path(Bundler.root) unless @path.relative?
313
- end
314
-
315
- @name = options["name"]
316
- @version = options["version"]
317
-
318
- # Stores the original path. If at any point we move to the
319
- # cached directory, we still have the original path to copy from.
320
- @original_path = @path
321
- end
322
-
323
- def remote!
324
- @allow_remote = true
325
- end
326
-
327
- def cached!
328
- @allow_cached = true
329
- end
330
-
331
- def self.from_lock(options)
332
- new(options.merge("path" => options.delete("remote")))
333
- end
334
-
335
- def to_lock
336
- out = "PATH\n"
337
- out << " remote: #{relative_path}\n"
338
- out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
339
- out << " specs:\n"
340
- end
341
-
342
- def to_s
343
- "source at #{@path}"
344
- end
345
-
346
- def hash
347
- self.class.hash
348
- end
349
-
350
- def eql?(o)
351
- o.instance_of?(Path) &&
352
- path.expand_path(Bundler.root) == o.path.expand_path(Bundler.root) &&
353
- version == o.version
354
- end
355
-
356
- alias == eql?
357
-
358
- def name
359
- File.basename(path.expand_path(Bundler.root).to_s)
360
- end
361
-
362
- def install(spec)
363
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
364
- # Let's be honest, when we're working from a path, we can't
365
- # really expect native extensions to work because the whole point
366
- # is to just be able to modify what's in that path and go. So, let's
367
- # not put ourselves through the pain of actually trying to generate
368
- # the full gem.
369
- Installer.new(spec).generate_bin
370
- end
371
-
372
- def cache(spec)
373
- return unless Bundler.settings[:cache_all]
374
- return if @original_path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0
375
- FileUtils.rm_rf(app_cache_path)
376
- FileUtils.cp_r("#{@original_path}/.", app_cache_path)
377
- FileUtils.touch(app_cache_path.join(".bundlecache"))
378
- end
379
-
380
- def local_specs(*)
381
- @local_specs ||= load_spec_files
382
- end
383
-
384
- def specs
385
- if has_app_cache?
386
- @path = app_cache_path
387
- end
388
- local_specs
389
- end
390
-
391
- def app_cache_dirname
392
- name
393
- end
394
-
395
- private
396
-
397
- def app_cache_path
398
- @app_cache_path ||= Bundler.app_cache.join(app_cache_dirname)
399
- end
400
-
401
- def has_app_cache?
402
- SharedHelpers.in_bundle? && app_cache_path.exist?
403
- end
404
-
405
- def load_spec_files
406
- index = Index.new
407
- expanded_path = path.expand_path(Bundler.root)
408
-
409
- if File.directory?(expanded_path)
410
- Dir["#{expanded_path}/#{@glob}"].each do |file|
411
- spec = Bundler.load_gemspec(file)
412
- if spec
413
- spec.loaded_from = file.to_s
414
- spec.source = self
415
- index << spec
416
- end
417
- end
418
-
419
- if index.empty? && @name && @version
420
- index << Gem::Specification.new do |s|
421
- s.name = @name
422
- s.source = self
423
- s.version = Gem::Version.new(@version)
424
- s.platform = Gem::Platform::RUBY
425
- s.summary = "Fake gemspec for #{@name}"
426
- s.relative_loaded_from = "#{@name}.gemspec"
427
- s.authors = ["no one"]
428
- if expanded_path.join("bin").exist?
429
- executables = expanded_path.join("bin").children
430
- executables.reject!{|p| File.directory?(p) }
431
- s.executables = executables.map{|c| c.basename.to_s }
432
- end
433
- end
434
- end
435
- else
436
- raise PathError, "The path `#{expanded_path}` does not exist."
437
- end
438
-
439
- index
440
- end
441
-
442
- def relative_path
443
- if path.to_s.match(%r{^#{Regexp.escape Bundler.root.to_s}})
444
- return path.relative_path_from(Bundler.root)
445
- end
446
- path
447
- end
448
-
449
- def generate_bin(spec)
450
- gem_dir = Pathname.new(spec.full_gem_path)
451
-
452
- # Some gem authors put absolute paths in their gemspec
453
- # and we have to save them from themselves
454
- spec.files = spec.files.map do |p|
455
- next if File.directory?(p)
456
- begin
457
- Pathname.new(p).relative_path_from(gem_dir).to_s
458
- rescue ArgumentError
459
- p
460
- end
461
- end.compact
462
-
463
- gem_file = Dir.chdir(gem_dir){ Gem::Builder.new(spec).build }
464
-
465
- installer = Path::Installer.new(spec, :env_shebang => false)
466
- run_hooks(:pre_install, installer)
467
- installer.build_extensions
468
- run_hooks(:post_build, installer)
469
- installer.generate_bin
470
- run_hooks(:post_install, installer)
471
- rescue Gem::InvalidSpecificationException => e
472
- Bundler.ui.warn "\n#{spec.name} at #{spec.full_gem_path} did not have a valid gemspec.\n" \
473
- "This prevents bundler from installing bins or native extensions, but " \
474
- "that may not affect its functionality."
475
-
476
- if !spec.extensions.empty? && !spec.email.empty?
477
- Bundler.ui.warn "If you need to use this package without installing it from a gem " \
478
- "repository, please contact #{spec.email} and ask them " \
479
- "to modify their .gemspec so it can work with `gem build`."
480
- end
481
-
482
- Bundler.ui.warn "The validation message from Rubygems was:\n #{e.message}"
483
- ensure
484
- Dir.chdir(gem_dir){ FileUtils.rm_rf(gem_file) if gem_file && File.exist?(gem_file) }
485
- end
486
-
487
- def run_hooks(type, installer)
488
- hooks_meth = "#{type}_hooks"
489
- return unless Gem.respond_to?(hooks_meth)
490
- Gem.send(hooks_meth).each do |hook|
491
- result = hook.call(installer)
492
- if result == false
493
- location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
494
- message = "#{type} hook#{location} failed for #{installer.spec.full_name}"
495
- raise InstallHookError, message
496
- end
497
- end
498
- end
499
- end
500
-
501
- class Git < Path
502
- # The GitProxy is responsible to iteract with git repositories.
503
- # All actions required by the Git source is encapsualted in this
504
- # object.
505
- class GitProxy
506
- attr_accessor :path, :uri, :ref, :revision
507
-
508
- def initialize(path, uri, ref, revision=nil, &allow)
509
- @path = path
510
- @uri = uri
511
- @ref = ref
512
- @revision = revision
513
- @allow = allow || Proc.new { true }
514
- end
515
-
516
- remove_method :revision if method_defined? :revision
517
- def revision
518
- @revision ||= allowed_in_path { git("rev-parse #{ref}").strip }
519
- end
520
-
521
- def branch
522
- @branch ||= allowed_in_path do
523
- git("branch") =~ /^\* (.*)$/ && $1.strip
524
- end
525
- end
526
-
527
- def contains?(commit)
528
- allowed_in_path do
529
- result = git_null("branch --contains #{commit}")
530
- $? == 0 && result =~ /^\* (.*)$/
531
- end
532
- end
533
-
534
- def checkout
535
- if path.exist?
536
- return if has_revision_cached?
537
- Bundler.ui.info "Updating #{uri}"
538
- in_path do
539
- git %|fetch --force --quiet --tags #{uri_escaped} "refs/heads/*:refs/heads/*"|
540
- end
541
- else
542
- Bundler.ui.info "Fetching #{uri}"
543
- FileUtils.mkdir_p(path.dirname)
544
- git %|clone #{uri_escaped} "#{path}" --bare --no-hardlinks|
545
- end
546
- end
547
-
548
- def copy_to(destination, submodules=false)
549
- unless File.exist?(destination.join(".git"))
550
- FileUtils.mkdir_p(destination.dirname)
551
- FileUtils.rm_rf(destination)
552
- git %|clone --no-checkout "#{path}" "#{destination}"|
553
- File.chmod((0777 & ~File.umask), destination)
554
- end
555
-
556
- Dir.chdir(destination) do
557
- git %|fetch --force --quiet --tags "#{path}"|
558
- git "reset --hard #{@revision}"
559
-
560
- if submodules
561
- git "submodule update --init --recursive"
562
- end
563
- end
564
- end
565
-
566
- private
567
-
568
- # TODO: Do not rely on /dev/null.
569
- # Given that open3 is not cross platform until Ruby 1.9.3,
570
- # the best solution is to pipe to /dev/null if it exists.
571
- # If it doesn't, everything will work fine, but the user
572
- # will get the $stderr messages as well.
573
- def git_null(command)
574
- if !Bundler::WINDOWS && File.exist?("/dev/null")
575
- git("#{command} 2>/dev/null", false)
576
- else
577
- git(command, false)
578
- end
579
- end
580
-
581
- def git(command, check_errors=true)
582
- if allow?
583
- out = %x{git #{command}}
584
-
585
- if check_errors && $?.exitstatus != 0
586
- msg = "Git error: command `git #{command}` in directory #{Dir.pwd} has failed."
587
- msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path.exist?
588
- raise GitError, msg
589
- end
590
- out
591
- else
592
- raise GitError, "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, " \
593
- "this error message could probably be more useful. Please submit a ticket at http://github.com/carlhuda/bundler/issues " \
594
- "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
595
- end
596
- end
597
-
598
- def has_revision_cached?
599
- return unless @revision
600
- in_path { git("cat-file -e #{@revision}") }
601
- true
602
- rescue GitError
603
- false
604
- end
605
-
606
- # Escape the URI for git commands
607
- def uri_escaped
608
- if Bundler::WINDOWS
609
- # Windows quoting requires double quotes only, with double quotes
610
- # inside the string escaped by being doubled.
611
- '"' + uri.gsub('"') {|s| '""'} + '"'
612
- else
613
- # Bash requires single quoted strings, with the single quotes escaped
614
- # by ending the string, escaping the quote, and restarting the string.
615
- "'" + uri.gsub("'") {|s| "'\\''"} + "'"
616
- end
617
- end
618
-
619
- def allow?
620
- @allow.call
621
- end
622
-
623
- def in_path(&blk)
624
- checkout unless path.exist?
625
- Dir.chdir(path, &blk)
626
- end
627
-
628
- def allowed_in_path
629
- if allow?
630
- in_path { yield }
631
- else
632
- raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application"
633
- end
634
- end
635
- end
636
-
637
- attr_reader :uri, :ref, :branch, :options, :submodules
638
-
639
- def initialize(options)
640
- @options = options
641
- @glob = options["glob"] || DEFAULT_GLOB
642
-
643
- @allow_cached = false
644
- @allow_remote = false
645
-
646
- # Stringify options that could be set as symbols
647
- %w(ref branch tag revision).each{|k| options[k] = options[k].to_s if options[k] }
648
-
649
- @uri = options["uri"]
650
- @branch = options["branch"]
651
- @ref = options["ref"] || options["branch"] || options["tag"] || 'master'
652
- @submodules = options["submodules"]
653
- @name = options["name"]
654
- @version = options["version"]
655
-
656
- @update = false
657
- @installed = nil
658
- @local = false
659
- end
660
-
661
- def self.from_lock(options)
662
- new(options.merge("uri" => options.delete("remote")))
663
- end
664
-
665
- def to_lock
666
- out = "GIT\n"
667
- out << " remote: #{@uri}\n"
668
- out << " revision: #{revision}\n"
669
- %w(ref branch tag submodules).each do |opt|
670
- out << " #{opt}: #{options[opt]}\n" if options[opt]
671
- end
672
- out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
673
- out << " specs:\n"
674
- end
675
-
676
- def eql?(o)
677
- Git === o &&
678
- uri == o.uri &&
679
- ref == o.ref &&
680
- branch == o.branch &&
681
- name == o.name &&
682
- version == o.version &&
683
- submodules == o.submodules
684
- end
685
-
686
- alias == eql?
687
-
688
- def to_s
689
- at = if local?
690
- path
691
- elsif options["ref"]
692
- shortref_for_display(options["ref"])
693
- else
694
- ref
695
- end
696
- "#{uri} (at #{at})"
697
- end
698
-
699
- def name
700
- File.basename(@uri, '.git')
701
- end
702
-
703
- # This is the path which is going to contain a specific
704
- # checkout of the git repository. When using local git
705
- # repos, this is set to the local repo.
706
- def install_path
707
- @install_path ||= begin
708
- git_scope = "#{base_name}-#{shortref_for_path(revision)}"
709
-
710
- if Bundler.requires_sudo?
711
- Bundler.user_bundle_path.join(Bundler.ruby_scope).join(git_scope)
712
- else
713
- Bundler.install_path.join(git_scope)
714
- end
715
- end
716
- end
717
-
718
- alias :path :install_path
719
-
720
- def unlock!
721
- git_proxy.revision = nil
722
- end
723
-
724
- def local_override!(path)
725
- return false if local?
726
-
727
- path = Pathname.new(path)
728
- path = path.expand_path(Bundler.root) unless path.relative?
729
-
730
- unless options["branch"] || Bundler.settings[:disable_local_branch_check]
731
- raise GitError, "Cannot use local override for #{name} at #{path} because " \
732
- ":branch is not specified in Gemfile. Specify a branch or use " \
733
- "`bundle config --delete` to remove the local override"
734
- end
735
-
736
- unless path.exist?
737
- raise GitError, "Cannot use local override for #{name} because #{path} " \
738
- "does not exist. Check `bundle config --delete` to remove the local override"
739
- end
740
-
741
- set_local!(path)
742
-
743
- # Create a new git proxy without the cached revision
744
- # so the Gemfile.lock always picks up the new revision.
745
- @git_proxy = GitProxy.new(path, uri, ref)
746
-
747
- if git_proxy.branch != options["branch"] && !Bundler.settings[:disable_local_branch_check]
748
- raise GitError, "Local override for #{name} at #{path} is using branch " \
749
- "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}"
750
- end
751
-
752
- changed = cached_revision && cached_revision != git_proxy.revision
753
-
754
- if changed && !git_proxy.contains?(cached_revision)
755
- raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \
756
- "but the current branch in your local override for #{name} does not contain such commit. " \
757
- "Please make sure your branch is up to date."
758
- end
759
-
760
- changed
761
- end
762
-
763
- # TODO: actually cache git specs
764
- def specs(*)
765
- if has_app_cache? && !local?
766
- set_local!(app_cache_path)
767
- end
768
-
769
- if requires_checkout? && !@update
770
- git_proxy.checkout
771
- git_proxy.copy_to(install_path, submodules)
772
- @update = true
773
- end
774
-
775
- local_specs
776
- end
777
-
778
- def install(spec)
779
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
780
- if requires_checkout? && !@installed
781
- Bundler.ui.debug " * Checking out revision: #{ref}"
782
- git_proxy.copy_to(install_path, submodules)
783
- @installed = true
784
- end
785
- generate_bin(spec)
786
- end
787
-
788
- def cache(spec)
789
- return unless Bundler.settings[:cache_all]
790
- return if path == app_cache_path
791
- cached!
792
- FileUtils.rm_rf(app_cache_path)
793
- git_proxy.checkout if requires_checkout?
794
- git_proxy.copy_to(app_cache_path, @submodules)
795
- FileUtils.rm_rf(app_cache_path.join(".git"))
796
- FileUtils.touch(app_cache_path.join(".bundlecache"))
797
- end
798
-
799
- def load_spec_files
800
- super
801
- rescue PathError, GitError
802
- raise GitError, "#{to_s} is not checked out. Please run `bundle install`"
803
- end
804
-
805
- # This is the path which is going to contain a cache
806
- # of the git repository. When using the same git repository
807
- # across different projects, this cache will be shared.
808
- # When using local git repos, this is set to the local repo.
809
- def cache_path
810
- @cache_path ||= begin
811
- git_scope = "#{base_name}-#{uri_hash}"
812
-
813
- if Bundler.requires_sudo?
814
- Bundler.user_bundle_path.join("cache/git", git_scope)
815
- else
816
- Bundler.cache.join("git", git_scope)
817
- end
818
- end
819
- end
820
-
821
- def app_cache_dirname
822
- "#{base_name}-#{shortref_for_path(cached_revision || revision)}"
823
- end
824
-
825
- private
826
-
827
- def set_local!(path)
828
- @local = true
829
- @local_specs = @git_proxy = nil
830
- @cache_path = @install_path = path
831
- end
832
-
833
- def has_app_cache?
834
- cached_revision && super
835
- end
836
-
837
- def local?
838
- @local
839
- end
840
-
841
- def requires_checkout?
842
- allow_git_ops? && !local?
843
- end
844
-
845
- def base_name
846
- File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*},''),".git")
847
- end
848
-
849
- def shortref_for_display(ref)
850
- ref[0..6]
851
- end
852
-
853
- def shortref_for_path(ref)
854
- ref[0..11]
855
- end
856
-
857
- def uri_hash
858
- if uri =~ %r{^\w+://(\w+@)?}
859
- # Downcase the domain component of the URI
860
- # and strip off a trailing slash, if one is present
861
- input = URI.parse(uri).normalize.to_s.sub(%r{/$},'')
862
- else
863
- # If there is no URI scheme, assume it is an ssh/git URI
864
- input = uri
865
- end
866
- Digest::SHA1.hexdigest(input)
867
- end
868
-
869
- def allow_git_ops?
870
- @allow_remote || @allow_cached
871
- end
872
-
873
- def cached_revision
874
- options["revision"]
875
- end
876
-
877
- def revision
878
- git_proxy.revision
879
- end
880
-
881
- def cached?
882
- cache_path.exist?
883
- end
884
-
885
- def git_proxy
886
- @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision){ allow_git_ops? }
887
- end
888
- end
3
+ autoload :Rubygems, 'bundler/source/rubygems'
4
+ autoload :Path, 'bundler/source/path'
5
+ autoload :Git, 'bundler/source/git'
889
6
  end
890
7
  end