bundler 1.9.10 → 1.10.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +21 -13
- data/Rakefile +2 -2
- data/bin/bundle_ruby +2 -0
- data/bin/bundler +1 -1
- data/lib/bundler.rb +6 -10
- data/lib/bundler/cli.rb +23 -1
- data/lib/bundler/cli/gem.rb +5 -2
- data/lib/bundler/cli/install.rb +37 -5
- data/lib/bundler/cli/lock.rb +36 -0
- data/lib/bundler/cli/outdated.rb +9 -2
- data/lib/bundler/definition.rb +22 -7
- data/lib/bundler/dependency.rb +7 -6
- data/lib/bundler/deployment.rb +3 -0
- data/lib/bundler/dsl.rb +172 -39
- data/lib/bundler/endpoint_specification.rb +1 -1
- data/lib/bundler/fetcher.rb +90 -252
- data/lib/bundler/fetcher/base.rb +27 -0
- data/lib/bundler/fetcher/dependency.rb +88 -0
- data/lib/bundler/fetcher/downloader.rb +61 -0
- data/lib/bundler/fetcher/index.rb +31 -0
- data/lib/bundler/friendly_errors.rb +3 -0
- data/lib/bundler/inline.rb +50 -0
- data/lib/bundler/installer.rb +15 -60
- data/lib/bundler/installer/parallel_installer.rb +117 -0
- data/lib/bundler/lazy_specification.rb +1 -1
- data/lib/bundler/lockfile_parser.rb +26 -10
- data/lib/bundler/remote_specification.rb +21 -1
- data/lib/bundler/resolver.rb +2 -1
- data/lib/bundler/retry.rb +11 -10
- data/lib/bundler/rubygems_ext.rb +1 -1
- data/lib/bundler/rubygems_integration.rb +33 -6
- data/lib/bundler/settings.rb +58 -14
- data/lib/bundler/shared_helpers.rb +6 -3
- data/lib/bundler/source.rb +0 -10
- data/lib/bundler/source/git.rb +2 -2
- data/lib/bundler/source/path.rb +1 -1
- data/lib/bundler/source/path/installer.rb +8 -11
- data/lib/bundler/source/rubygems.rb +46 -16
- data/lib/bundler/source/rubygems/remote.rb +39 -0
- data/lib/bundler/templates/newgem/.travis.yml.tt +1 -0
- data/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +2 -2
- data/lib/bundler/templates/newgem/Rakefile.tt +2 -0
- data/lib/bundler/templates/newgem/test/{test_newgem.rb.tt → newgem_test.rb.tt} +2 -2
- data/lib/bundler/templates/newgem/test/{minitest_helper.rb.tt → test_helper.rb.tt} +0 -0
- data/lib/bundler/version.rb +1 -1
- data/man/bundle-config.ronn +7 -0
- data/man/bundle-install.ronn +9 -0
- data/man/bundle.ronn +3 -3
- data/man/gemfile.5.ronn +9 -5
- metadata +13 -8
- data/UPGRADING.md +0 -103
- data/lib/bundler/anonymizable_uri.rb +0 -32
data/lib/bundler/deployment.rb
CHANGED
@@ -33,6 +33,7 @@ module Bundler
|
|
33
33
|
set :bundle_dir, File.join(fetch(:shared_path), 'bundle')
|
34
34
|
set :bundle_flags, "--deployment --quiet"
|
35
35
|
set :bundle_without, [:development, :test]
|
36
|
+
set :bundle_with, [:mysql]
|
36
37
|
set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle"
|
37
38
|
set :bundle_roles, #{role_default} # e.g. [:app, :batch]
|
38
39
|
DESC
|
@@ -42,6 +43,7 @@ module Bundler
|
|
42
43
|
bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), 'bundle'))
|
43
44
|
bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile")
|
44
45
|
bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact
|
46
|
+
bundle_with = [*context.fetch(:bundle_with, [])].compact
|
45
47
|
app_path = context.fetch(:latest_release)
|
46
48
|
if app_path.to_s.empty?
|
47
49
|
raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.")
|
@@ -50,6 +52,7 @@ module Bundler
|
|
50
52
|
args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty?
|
51
53
|
args << bundle_flags.to_s
|
52
54
|
args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty?
|
55
|
+
args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty?
|
53
56
|
|
54
57
|
run "cd #{app_path} && #{bundle_cmd} install #{args.join(' ')}"
|
55
58
|
end
|
data/lib/bundler/dsl.rb
CHANGED
@@ -16,32 +16,30 @@ module Bundler
|
|
16
16
|
attr_accessor :dependencies
|
17
17
|
|
18
18
|
def initialize
|
19
|
-
@source
|
20
|
-
@sources
|
21
|
-
@git_sources
|
22
|
-
@dependencies
|
23
|
-
@groups
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@
|
19
|
+
@source = nil
|
20
|
+
@sources = SourceList.new
|
21
|
+
@git_sources = {}
|
22
|
+
@dependencies = []
|
23
|
+
@groups = []
|
24
|
+
@install_conditionals = []
|
25
|
+
@optional_groups = []
|
26
|
+
@platforms = []
|
27
|
+
@env = nil
|
28
|
+
@ruby_version = nil
|
27
29
|
add_git_sources
|
28
30
|
end
|
29
31
|
|
30
32
|
def eval_gemfile(gemfile, contents = nil)
|
31
33
|
contents ||= Bundler.read_file(gemfile.to_s)
|
32
34
|
instance_eval(contents, gemfile.to_s, 1)
|
33
|
-
rescue
|
34
|
-
|
35
|
-
raise
|
36
|
-
rescue ScriptError, RegexpError, NameError, ArgumentError, RuntimeError => e
|
37
|
-
e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})"
|
38
|
-
Bundler.ui.warn e.backtrace.join("\n ")
|
39
|
-
raise GemfileError, "There was an error in your Gemfile," \
|
40
|
-
" and Bundler cannot continue."
|
35
|
+
rescue Exception => e
|
36
|
+
message = "There was an error parsing `#{File.basename gemfile.to_s}`: #{e.message}"
|
37
|
+
raise DSLError.new(message, gemfile, e.backtrace, contents)
|
41
38
|
end
|
42
39
|
|
43
40
|
def gemspec(opts = nil)
|
44
41
|
path = opts && opts[:path] || '.'
|
42
|
+
glob = opts && opts[:glob]
|
45
43
|
name = opts && opts[:name] || '{,*}'
|
46
44
|
development_group = opts && opts[:development_group] || :development
|
47
45
|
expanded_path = File.expand_path(path, Bundler.default_gemfile.dirname)
|
@@ -52,7 +50,7 @@ module Bundler
|
|
52
50
|
when 1
|
53
51
|
spec = Bundler.load_gemspec(gemspecs.first)
|
54
52
|
raise InvalidOption, "There was an error loading the gemspec at #{gemspecs.first}." unless spec
|
55
|
-
gem spec.name, :path => path
|
53
|
+
gem spec.name, :path => path, :glob => glob
|
56
54
|
group(development_group) do
|
57
55
|
spec.development_dependencies.each do |dep|
|
58
56
|
gem dep.name, *(dep.requirement.as_list + [:type => :development])
|
@@ -158,16 +156,32 @@ module Bundler
|
|
158
156
|
end
|
159
157
|
|
160
158
|
def to_definition(lockfile, unlock)
|
161
|
-
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version)
|
159
|
+
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups)
|
162
160
|
end
|
163
161
|
|
164
162
|
def group(*args, &blk)
|
163
|
+
opts = Hash === args.last ? args.pop.dup : {}
|
164
|
+
normalize_group_options(opts, args)
|
165
|
+
|
165
166
|
@groups.concat args
|
167
|
+
|
168
|
+
if opts["optional"]
|
169
|
+
optional_groups = args - @optional_groups
|
170
|
+
@optional_groups.concat optional_groups
|
171
|
+
end
|
172
|
+
|
166
173
|
yield
|
167
174
|
ensure
|
168
175
|
args.each { @groups.pop }
|
169
176
|
end
|
170
177
|
|
178
|
+
def install_if(*args, &blk)
|
179
|
+
@install_conditionals.concat args
|
180
|
+
blk.call
|
181
|
+
ensure
|
182
|
+
args.each { @groups.pop }
|
183
|
+
end
|
184
|
+
|
171
185
|
def platforms(*platforms)
|
172
186
|
@platforms.concat platforms
|
173
187
|
yield
|
@@ -184,9 +198,7 @@ module Bundler
|
|
184
198
|
end
|
185
199
|
|
186
200
|
def method_missing(name, *args)
|
187
|
-
|
188
|
-
raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile\n" \
|
189
|
-
" from #{location}"
|
201
|
+
raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
|
190
202
|
end
|
191
203
|
|
192
204
|
private
|
@@ -224,7 +236,7 @@ module Bundler
|
|
224
236
|
end
|
225
237
|
|
226
238
|
def valid_keys
|
227
|
-
@valid_keys ||= %w(group groups git path name branch ref tag require submodules platform platforms type source)
|
239
|
+
@valid_keys ||= %w(group groups git path glob name branch ref tag require submodules platform platforms type source install_if)
|
228
240
|
end
|
229
241
|
|
230
242
|
def normalize_options(name, version, opts)
|
@@ -238,25 +250,19 @@ module Bundler
|
|
238
250
|
normalize_hash(opts)
|
239
251
|
|
240
252
|
git_names = @git_sources.keys.map(&:to_s)
|
241
|
-
|
242
|
-
invalid_keys = opts.keys - (valid_keys + git_names)
|
243
|
-
if invalid_keys.any?
|
244
|
-
message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "
|
245
|
-
message << if invalid_keys.size > 1
|
246
|
-
"as options for gem '#{name}', but they are invalid."
|
247
|
-
else
|
248
|
-
"as an option for gem '#{name}', but it is invalid."
|
249
|
-
end
|
250
|
-
|
251
|
-
message << " Valid options are: #{valid_keys.join(", ")}"
|
252
|
-
raise InvalidOption, message
|
253
|
-
end
|
253
|
+
validate_keys("gem '#{name}'", opts, valid_keys + git_names)
|
254
254
|
|
255
255
|
groups = @groups.dup
|
256
256
|
opts["group"] = opts.delete("groups") || opts["group"]
|
257
257
|
groups.concat Array(opts.delete("group"))
|
258
258
|
groups = [:default] if groups.empty?
|
259
259
|
|
260
|
+
install_if = @install_conditionals.dup
|
261
|
+
install_if.concat Array(opts.delete("install_if"))
|
262
|
+
install_if = install_if.reduce(true) do |memo, val|
|
263
|
+
memo && (val.respond_to?(:call) ? val.call : val)
|
264
|
+
end
|
265
|
+
|
260
266
|
platforms = @platforms.dup
|
261
267
|
opts["platforms"] = opts["platform"] || opts["platforms"]
|
262
268
|
platforms.concat Array(opts.delete("platforms"))
|
@@ -289,10 +295,35 @@ module Bundler
|
|
289
295
|
end
|
290
296
|
end
|
291
297
|
|
292
|
-
opts["source"]
|
293
|
-
opts["env"]
|
294
|
-
opts["platforms"]
|
295
|
-
opts["group"]
|
298
|
+
opts["source"] ||= @source
|
299
|
+
opts["env"] ||= @env
|
300
|
+
opts["platforms"] = platforms.dup
|
301
|
+
opts["group"] = groups
|
302
|
+
opts["should_include"] = install_if
|
303
|
+
end
|
304
|
+
|
305
|
+
def normalize_group_options(opts, groups)
|
306
|
+
normalize_hash(opts)
|
307
|
+
|
308
|
+
groups = groups.map {|group| ":#{group}" }.join(", ")
|
309
|
+
validate_keys("group #{groups}", opts, %w(optional))
|
310
|
+
|
311
|
+
opts["optional"] ||= false
|
312
|
+
end
|
313
|
+
|
314
|
+
def validate_keys(command, opts, valid_keys)
|
315
|
+
invalid_keys = opts.keys - valid_keys
|
316
|
+
if invalid_keys.any?
|
317
|
+
message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "
|
318
|
+
message << if invalid_keys.size > 1
|
319
|
+
"as options for #{command}, but they are invalid."
|
320
|
+
else
|
321
|
+
"as an option for #{command}, but it is invalid."
|
322
|
+
end
|
323
|
+
|
324
|
+
message << " Valid options are: #{valid_keys.join(", ")}"
|
325
|
+
raise InvalidOption, message
|
326
|
+
end
|
296
327
|
end
|
297
328
|
|
298
329
|
def normalize_source(source)
|
@@ -327,5 +358,107 @@ module Bundler
|
|
327
358
|
end
|
328
359
|
end
|
329
360
|
|
361
|
+
class DSLError < GemfileError
|
362
|
+
# @return [String] the description that should be presented to the user.
|
363
|
+
#
|
364
|
+
attr_reader :description
|
365
|
+
|
366
|
+
# @return [String] the path of the dsl file that raised the exception.
|
367
|
+
#
|
368
|
+
attr_reader :dsl_path
|
369
|
+
|
370
|
+
# @return [Exception] the backtrace of the exception raised by the
|
371
|
+
# evaluation of the dsl file.
|
372
|
+
#
|
373
|
+
attr_reader :backtrace
|
374
|
+
|
375
|
+
# @param [Exception] backtrace @see backtrace
|
376
|
+
# @param [String] dsl_path @see dsl_path
|
377
|
+
#
|
378
|
+
def initialize(description, dsl_path, backtrace, contents = nil)
|
379
|
+
@status_code = $!.respond_to?(:status_code) && $!.status_code
|
380
|
+
|
381
|
+
@description = description
|
382
|
+
@dsl_path = dsl_path
|
383
|
+
@backtrace = backtrace
|
384
|
+
@contents = contents
|
385
|
+
end
|
386
|
+
|
387
|
+
def status_code
|
388
|
+
@status_code || super
|
389
|
+
end
|
390
|
+
|
391
|
+
# @return [String] the contents of the DSL that cause the exception to
|
392
|
+
# be raised.
|
393
|
+
#
|
394
|
+
def contents
|
395
|
+
@contents ||= begin
|
396
|
+
dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# The message of the exception reports the content of podspec for the
|
401
|
+
# line that generated the original exception.
|
402
|
+
#
|
403
|
+
# @example Output
|
404
|
+
#
|
405
|
+
# Invalid podspec at `RestKit.podspec` - undefined method
|
406
|
+
# `exclude_header_search_paths=' for #<Pod::Specification for
|
407
|
+
# `RestKit/Network (0.9.3)`>
|
408
|
+
#
|
409
|
+
# from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
|
410
|
+
# -------------------------------------------
|
411
|
+
# # because it would break: #import <CoreData/CoreData.h>
|
412
|
+
# > ns.exclude_header_search_paths = 'Code/RestKit.h'
|
413
|
+
# end
|
414
|
+
# -------------------------------------------
|
415
|
+
#
|
416
|
+
# @return [String] the message of the exception.
|
417
|
+
#
|
418
|
+
def message
|
419
|
+
@message ||= begin
|
420
|
+
trace_line, description = parse_line_number_from_description
|
421
|
+
|
422
|
+
m = "\n[!] "
|
423
|
+
m << description
|
424
|
+
m << ". Bundler cannot continue.\n"
|
425
|
+
|
426
|
+
return m unless backtrace && dsl_path && contents
|
427
|
+
|
428
|
+
trace_line = backtrace.find { |l| l.include?(dsl_path.to_s) } || trace_line
|
429
|
+
return m unless trace_line
|
430
|
+
line_numer = trace_line.split(':')[1].to_i - 1
|
431
|
+
return m unless line_numer
|
432
|
+
|
433
|
+
lines = contents.lines.to_a
|
434
|
+
indent = ' # '
|
435
|
+
indicator = indent.gsub('#', '>')
|
436
|
+
first_line = (line_numer.zero?)
|
437
|
+
last_line = (line_numer == (lines.count - 1))
|
438
|
+
|
439
|
+
m << "\n"
|
440
|
+
m << "#{indent}from #{trace_line.gsub(/:in.*$/, '')}\n"
|
441
|
+
m << "#{indent}-------------------------------------------\n"
|
442
|
+
m << "#{indent}#{ lines[line_numer - 1] }" unless first_line
|
443
|
+
m << "#{indicator}#{ lines[line_numer] }"
|
444
|
+
m << "#{indent}#{ lines[line_numer + 1] }" unless last_line
|
445
|
+
m << "\n" unless m.end_with?("\n")
|
446
|
+
m << "#{indent}-------------------------------------------\n"
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
private
|
451
|
+
|
452
|
+
def parse_line_number_from_description
|
453
|
+
description = self.description
|
454
|
+
if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
|
455
|
+
trace_line = Regexp.last_match[1]
|
456
|
+
description = description.sub(/#{Regexp.quote trace_line}:\s*/, '').sub("\n", ' - ')
|
457
|
+
end
|
458
|
+
[trace_line, description]
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
330
462
|
end
|
463
|
+
|
331
464
|
end
|
data/lib/bundler/fetcher.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
require 'bundler/vendored_persistent'
|
2
|
-
require 'securerandom'
|
3
2
|
require 'cgi'
|
3
|
+
require 'securerandom'
|
4
4
|
|
5
5
|
module Bundler
|
6
6
|
|
7
7
|
# Handles all the fetching with the rubygems server
|
8
8
|
class Fetcher
|
9
|
+
autoload :Downloader, 'bundler/fetcher/downloader'
|
10
|
+
autoload :Dependency, 'bundler/fetcher/dependency'
|
11
|
+
autoload :Index, 'bundler/fetcher/index'
|
12
|
+
|
9
13
|
# This error is raised when it looks like the network is down
|
10
14
|
class NetworkDownError < HTTPError; end
|
11
15
|
# This error is raised if the API returns a 413 (only printed in verbose)
|
@@ -52,97 +56,21 @@ module Bundler
|
|
52
56
|
|
53
57
|
class << self
|
54
58
|
attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
|
55
|
-
|
56
|
-
def download_gem_from_uri(spec, uri)
|
57
|
-
spec.fetch_platform
|
58
|
-
|
59
|
-
download_path = Bundler.requires_sudo? ? Bundler.tmp(spec.full_name) : Bundler.rubygems.gem_dir
|
60
|
-
gem_path = "#{Bundler.rubygems.gem_dir}/cache/#{spec.full_name}.gem"
|
61
|
-
|
62
|
-
FileUtils.mkdir_p("#{download_path}/cache")
|
63
|
-
Bundler.rubygems.download_gem(spec, uri, download_path)
|
64
|
-
|
65
|
-
if Bundler.requires_sudo?
|
66
|
-
Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/cache"
|
67
|
-
Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
|
68
|
-
end
|
69
|
-
|
70
|
-
gem_path
|
71
|
-
ensure
|
72
|
-
Bundler.rm_rf(download_path) if Bundler.requires_sudo?
|
73
|
-
end
|
74
|
-
|
75
|
-
def user_agent
|
76
|
-
@user_agent ||= begin
|
77
|
-
ruby = Bundler.ruby_version
|
78
|
-
|
79
|
-
agent = "bundler/#{Bundler::VERSION}"
|
80
|
-
agent << " rubygems/#{Gem::VERSION}"
|
81
|
-
agent << " ruby/#{ruby.version}"
|
82
|
-
agent << " (#{ruby.host})"
|
83
|
-
agent << " command/#{ARGV.first}"
|
84
|
-
|
85
|
-
if ruby.engine != "ruby"
|
86
|
-
# engine_version raises on unknown engines
|
87
|
-
engine_version = ruby.engine_version rescue "???"
|
88
|
-
agent << " #{ruby.engine}/#{engine_version}"
|
89
|
-
end
|
90
|
-
|
91
|
-
agent << " options/#{Bundler.settings.all.join(",")}"
|
92
|
-
|
93
|
-
# add a random ID so we can consolidate runs server-side
|
94
|
-
agent << " " << SecureRandom.hex(8)
|
95
|
-
|
96
|
-
# add any user agent strings set in the config
|
97
|
-
extra_ua = Bundler.settings[:user_agent]
|
98
|
-
agent << " " << extra_ua if extra_ua
|
99
|
-
|
100
|
-
agent
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
59
|
end
|
105
60
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
@max_retries = 3 # How many retries for the API call
|
61
|
+
self.redirect_limit = Bundler.settings[:redirect] # How many redirects to allow in one request
|
62
|
+
self.api_timeout = Bundler.settings[:timeout] # How long to wait for each API call
|
63
|
+
self.max_retries = Bundler.settings[:retry] # How many retries for the API call
|
110
64
|
|
111
|
-
|
65
|
+
def initialize(remote)
|
66
|
+
@remote = remote
|
112
67
|
|
113
68
|
Socket.do_not_reverse_lookup = true
|
114
69
|
connection # create persistent connection
|
115
70
|
end
|
116
71
|
|
117
|
-
def connection
|
118
|
-
@connection ||= begin
|
119
|
-
needs_ssl = remote_uri.scheme == "https" ||
|
120
|
-
Bundler.settings[:ssl_verify_mode] ||
|
121
|
-
Bundler.settings[:ssl_client_cert]
|
122
|
-
raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
|
123
|
-
|
124
|
-
con = Net::HTTP::Persistent.new 'bundler', :ENV
|
125
|
-
|
126
|
-
if remote_uri.scheme == "https"
|
127
|
-
con.verify_mode = (Bundler.settings[:ssl_verify_mode] ||
|
128
|
-
OpenSSL::SSL::VERIFY_PEER)
|
129
|
-
con.cert_store = bundler_cert_store
|
130
|
-
end
|
131
|
-
|
132
|
-
if Bundler.settings[:ssl_client_cert]
|
133
|
-
pem = File.read(Bundler.settings[:ssl_client_cert])
|
134
|
-
con.cert = OpenSSL::X509::Certificate.new(pem)
|
135
|
-
con.key = OpenSSL::PKey::RSA.new(pem)
|
136
|
-
end
|
137
|
-
|
138
|
-
con.read_timeout = @api_timeout
|
139
|
-
con.override_headers["User-Agent"] = self.class.user_agent
|
140
|
-
con
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
72
|
def uri
|
145
|
-
@
|
73
|
+
@remote.anonymized_uri
|
146
74
|
end
|
147
75
|
|
148
76
|
# fetch a gem specification
|
@@ -156,37 +84,26 @@ module Bundler
|
|
156
84
|
elsif cached_spec_path = gemspec_cached_path(spec_file_name)
|
157
85
|
Bundler.load_gemspec(cached_spec_path)
|
158
86
|
else
|
159
|
-
Bundler.load_marshal Gem.inflate(fetch
|
87
|
+
Bundler.load_marshal Gem.inflate(downloader.fetch uri)
|
160
88
|
end
|
161
89
|
rescue MarshalError
|
162
90
|
raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
|
163
91
|
"Your network or your gem server is probably having issues right now."
|
164
92
|
end
|
165
93
|
|
166
|
-
# cached gem specification path, if one exists
|
167
|
-
def gemspec_cached_path spec_file_name
|
168
|
-
paths = Bundler.rubygems.spec_cache_dirs.map { |dir| File.join(dir, spec_file_name) }
|
169
|
-
paths = paths.select {|path| File.file? path }
|
170
|
-
paths.first
|
171
|
-
end
|
172
|
-
|
173
94
|
# return the specs in the bundler format as an index
|
174
95
|
def specs(gem_names, source)
|
175
96
|
old = Bundler.rubygems.sources
|
176
|
-
index = Index.new
|
177
|
-
|
178
|
-
if gem_names && use_api
|
179
|
-
specs = fetch_remote_specs(gem_names)
|
180
|
-
end
|
181
|
-
|
182
|
-
if specs.nil?
|
183
|
-
# API errors mean we should treat this as a non-API source
|
184
|
-
@use_api = false
|
97
|
+
index = Bundler::Index.new
|
185
98
|
|
186
|
-
|
187
|
-
|
99
|
+
specs = {}
|
100
|
+
fetchers.dup.each do |f|
|
101
|
+
unless f.api_fetcher? && !gem_names
|
102
|
+
break if specs = f.specs(gem_names)
|
188
103
|
end
|
104
|
+
fetchers.delete(f)
|
189
105
|
end
|
106
|
+
@use_api = false if fetchers.none?(&:api_fetcher?)
|
190
107
|
|
191
108
|
specs[remote_uri].each do |name, version, platform, dependencies|
|
192
109
|
next if name == 'bundler'
|
@@ -197,189 +114,111 @@ module Bundler
|
|
197
114
|
spec = RemoteSpecification.new(name, version, platform, self)
|
198
115
|
end
|
199
116
|
spec.source = source
|
200
|
-
spec.
|
117
|
+
spec.remote = @remote
|
201
118
|
index << spec
|
202
119
|
end
|
203
120
|
|
204
121
|
index
|
205
|
-
rescue CertificateFailureError
|
122
|
+
rescue CertificateFailureError
|
206
123
|
Bundler.ui.info "" if gem_names && use_api # newline after dots
|
207
|
-
raise
|
124
|
+
raise
|
208
125
|
ensure
|
209
126
|
Bundler.rubygems.sources = old
|
210
127
|
end
|
211
128
|
|
212
|
-
# fetch index
|
213
|
-
def fetch_remote_specs(gem_names, full_dependency_list = [], last_spec_list = [])
|
214
|
-
query_list = gem_names - full_dependency_list
|
215
|
-
|
216
|
-
# only display the message on the first run
|
217
|
-
if Bundler.ui.debug?
|
218
|
-
Bundler.ui.debug "Query List: #{query_list.inspect}"
|
219
|
-
else
|
220
|
-
Bundler.ui.info ".", false
|
221
|
-
end
|
222
|
-
|
223
|
-
return {remote_uri => last_spec_list} if query_list.empty?
|
224
|
-
|
225
|
-
remote_specs = Bundler::Retry.new("dependency api", AUTH_ERRORS).attempts do
|
226
|
-
fetch_dependency_remote_specs(query_list)
|
227
|
-
end
|
228
|
-
|
229
|
-
spec_list, deps_list = remote_specs
|
230
|
-
returned_gems = spec_list.map {|spec| spec.first }.uniq
|
231
|
-
fetch_remote_specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list)
|
232
|
-
rescue HTTPError, MarshalError, GemspecError
|
233
|
-
Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over
|
234
|
-
Bundler.ui.debug "could not fetch from the dependency API, trying the full index"
|
235
|
-
@use_api = false
|
236
|
-
return nil
|
237
|
-
end
|
238
|
-
|
239
129
|
def use_api
|
240
130
|
return @use_api if defined?(@use_api)
|
241
131
|
|
242
132
|
if remote_uri.scheme == "file" || Bundler::Fetcher.disable_endpoint
|
243
133
|
@use_api = false
|
244
|
-
|
245
|
-
|
134
|
+
else
|
135
|
+
fetchers.reject! { |f| f.api_fetcher? && !f.api_available? }
|
136
|
+
@use_api = fetchers.any?(&:api_fetcher?)
|
246
137
|
end
|
247
|
-
rescue NetworkDownError => e
|
248
|
-
raise HTTPError, e.message
|
249
|
-
rescue AuthenticationRequiredError
|
250
|
-
# We got a 401 from the server. Don't fall back to the full index, just fail.
|
251
|
-
raise
|
252
|
-
rescue HTTPError
|
253
|
-
@use_api = false
|
254
138
|
end
|
255
139
|
|
256
|
-
def
|
257
|
-
|
258
|
-
|
140
|
+
def user_agent
|
141
|
+
@user_agent ||= begin
|
142
|
+
ruby = Bundler.ruby_version
|
259
143
|
|
260
|
-
|
144
|
+
agent = "bundler/#{Bundler::VERSION}"
|
145
|
+
agent << " rubygems/#{Gem::VERSION}"
|
146
|
+
agent << " ruby/#{ruby.version}"
|
147
|
+
agent << " (#{ruby.host})"
|
148
|
+
agent << " command/#{ARGV.first}"
|
261
149
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
]
|
150
|
+
if ruby.engine != "ruby"
|
151
|
+
# engine_version raises on unknown engines
|
152
|
+
engine_version = ruby.engine_version rescue "???"
|
153
|
+
agent << " #{ruby.engine}/#{engine_version}"
|
154
|
+
end
|
268
155
|
|
269
|
-
|
270
|
-
raise HTTPError, "Too many redirects" if counter >= @redirect_limit
|
156
|
+
agent << " options/#{Bundler.settings.all.join(",")}"
|
271
157
|
|
272
|
-
|
273
|
-
|
158
|
+
# add a random ID so we can consolidate runs server-side
|
159
|
+
agent << " " << SecureRandom.hex(8)
|
274
160
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
new_uri.password = uri.password
|
281
|
-
end
|
282
|
-
fetch(new_uri, counter + 1)
|
283
|
-
when Net::HTTPSuccess
|
284
|
-
response.body
|
285
|
-
when Net::HTTPRequestEntityTooLarge
|
286
|
-
raise FallbackError, response.body
|
287
|
-
when Net::HTTPUnauthorized
|
288
|
-
raise AuthenticationRequiredError, remote_uri.host
|
289
|
-
else
|
290
|
-
raise HTTPError, "#{response.class}: #{response.body}"
|
161
|
+
# add any user agent strings set in the config
|
162
|
+
extra_ua = Bundler.settings[:user_agent]
|
163
|
+
agent << " " << extra_ua if extra_ua
|
164
|
+
|
165
|
+
agent
|
291
166
|
end
|
292
167
|
end
|
293
168
|
|
294
|
-
def
|
295
|
-
|
296
|
-
req = Net::HTTP::Get.new uri.request_uri
|
297
|
-
if uri.user
|
298
|
-
user = CGI.unescape(uri.user)
|
299
|
-
password = uri.password ? CGI.unescape(uri.password) : nil
|
300
|
-
req.basic_auth(user, password)
|
301
|
-
end
|
302
|
-
connection.request(uri, req)
|
303
|
-
rescue OpenSSL::SSL::SSLError
|
304
|
-
raise CertificateFailureError.new(uri)
|
305
|
-
rescue *HTTP_ERRORS => e
|
306
|
-
Bundler.ui.trace e
|
307
|
-
case e.message
|
308
|
-
when /host down:/, /getaddrinfo: nodename nor servname provided/
|
309
|
-
raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \
|
310
|
-
"connection and try again."
|
311
|
-
else
|
312
|
-
raise HTTPError, "Network error while fetching #{uri}"
|
313
|
-
end
|
169
|
+
def fetchers
|
170
|
+
@fetchers ||= FETCHERS.map { |f| f.new(downloader, remote_uri, fetch_uri, uri) }
|
314
171
|
end
|
315
172
|
|
316
|
-
def
|
317
|
-
uri
|
318
|
-
uri.query = "gems=#{URI.encode(gem_names.join(","))}" if gem_names.any?
|
319
|
-
uri
|
173
|
+
def inspect
|
174
|
+
"#<#{self.class}:0x#{object_id} uri=#{uri}>"
|
320
175
|
end
|
321
176
|
|
322
|
-
|
323
|
-
def fetch_dependency_remote_specs(gem_names)
|
324
|
-
Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(',')}"
|
325
|
-
gem_list = []
|
326
|
-
deps_list = []
|
177
|
+
private
|
327
178
|
|
328
|
-
|
329
|
-
marshalled_deps = fetch dependency_api_uri(names)
|
330
|
-
gem_list += Bundler.load_marshal(marshalled_deps)
|
331
|
-
end
|
179
|
+
FETCHERS = [Dependency, Index]
|
332
180
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
181
|
+
def connection
|
182
|
+
@connection ||= begin
|
183
|
+
needs_ssl = remote_uri.scheme == "https" ||
|
184
|
+
Bundler.settings[:ssl_verify_mode] ||
|
185
|
+
Bundler.settings[:ssl_client_cert]
|
186
|
+
raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
|
339
187
|
|
340
|
-
|
341
|
-
end
|
188
|
+
con = Net::HTTP::Persistent.new 'bundler', :ENV
|
342
189
|
|
343
|
-
|
344
|
-
|
190
|
+
if remote_uri.scheme == "https"
|
191
|
+
con.verify_mode = (Bundler.settings[:ssl_verify_mode] ||
|
192
|
+
OpenSSL::SSL::VERIFY_PEER)
|
193
|
+
con.cert_store = bundler_cert_store
|
194
|
+
end
|
345
195
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
Bundler.rubygems.fetch_all_remote_specs
|
351
|
-
rescue Gem::RemoteFetcher::FetchError, OpenSSL::SSL::SSLError => e
|
352
|
-
case e.message
|
353
|
-
when /certificate verify failed/
|
354
|
-
raise CertificateFailureError.new(uri)
|
355
|
-
when /401/
|
356
|
-
raise AuthenticationRequiredError, remote_uri
|
357
|
-
when /403/
|
358
|
-
if remote_uri.userinfo
|
359
|
-
raise BadAuthenticationError, remote_uri
|
360
|
-
else
|
361
|
-
raise AuthenticationRequiredError, remote_uri
|
196
|
+
if Bundler.settings[:ssl_client_cert]
|
197
|
+
pem = File.read(Bundler.settings[:ssl_client_cert])
|
198
|
+
con.cert = OpenSSL::X509::Certificate.new(pem)
|
199
|
+
con.key = OpenSSL::PKey::RSA.new(pem)
|
362
200
|
end
|
363
|
-
|
364
|
-
|
365
|
-
|
201
|
+
|
202
|
+
con.read_timeout = Fetcher.api_timeout
|
203
|
+
con.override_headers["User-Agent"] = user_agent
|
204
|
+
con
|
366
205
|
end
|
367
|
-
ensure
|
368
|
-
Bundler.rubygems.sources = old_sources
|
369
206
|
end
|
370
207
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
puts # we shouldn't print the error message on the "fetching info" status line
|
377
|
-
raise GemspecError,
|
378
|
-
"Unfortunately, the gem #{s[:name]} (#{s[:number]}) has an invalid " \
|
379
|
-
"gemspec. \nPlease ask the gem author to yank the bad version to fix " \
|
380
|
-
"this issue. For more information, see http://bit.ly/syck-defaultkey."
|
208
|
+
# cached gem specification path, if one exists
|
209
|
+
def gemspec_cached_path spec_file_name
|
210
|
+
paths = Bundler.rubygems.spec_cache_dirs.map { |dir| File.join(dir, spec_file_name) }
|
211
|
+
paths = paths.select {|path| File.file? path }
|
212
|
+
paths.first
|
381
213
|
end
|
382
214
|
|
215
|
+
HTTP_ERRORS = [
|
216
|
+
Timeout::Error, EOFError, SocketError, Errno::ENETDOWN,
|
217
|
+
Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
|
218
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
219
|
+
Net::HTTP::Persistent::Error
|
220
|
+
]
|
221
|
+
|
383
222
|
def bundler_cert_store
|
384
223
|
store = OpenSSL::X509::Store.new
|
385
224
|
if Bundler.settings[:ssl_ca_cert]
|
@@ -398,12 +237,6 @@ module Bundler
|
|
398
237
|
|
399
238
|
private
|
400
239
|
|
401
|
-
def configured_uri_for(uri)
|
402
|
-
uri = Bundler::Source.mirror_for(uri)
|
403
|
-
config_auth = Bundler.settings[uri.to_s] || Bundler.settings[uri.host]
|
404
|
-
AnonymizableURI.new(uri, config_auth)
|
405
|
-
end
|
406
|
-
|
407
240
|
def fetch_uri
|
408
241
|
@fetch_uri ||= begin
|
409
242
|
if remote_uri.host == "rubygems.org"
|
@@ -417,7 +250,12 @@ module Bundler
|
|
417
250
|
end
|
418
251
|
|
419
252
|
def remote_uri
|
420
|
-
@
|
253
|
+
@remote.uri
|
254
|
+
end
|
255
|
+
|
256
|
+
def downloader
|
257
|
+
@downloader ||= Downloader.new(connection, self.class.redirect_limit)
|
421
258
|
end
|
259
|
+
|
422
260
|
end
|
423
261
|
end
|