bundler 1.13.7 → 1.14.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop_todo.yml +100 -18
  4. data/.travis.yml +32 -18
  5. data/CHANGELOG.md +64 -2
  6. data/DEVELOPMENT.md +5 -3
  7. data/ISSUES.md +17 -0
  8. data/README.md +7 -0
  9. data/Rakefile +34 -23
  10. data/bin/rubocop +1 -1
  11. data/bundler.gemspec +2 -2
  12. data/exe/bundle +4 -6
  13. data/lib/bundler.rb +57 -5
  14. data/lib/bundler/cli.rb +51 -38
  15. data/lib/bundler/cli/binstubs.rb +1 -1
  16. data/lib/bundler/cli/cache.rb +1 -1
  17. data/lib/bundler/cli/check.rb +1 -1
  18. data/lib/bundler/cli/clean.rb +1 -1
  19. data/lib/bundler/cli/common.rb +30 -0
  20. data/lib/bundler/cli/doctor.rb +17 -19
  21. data/lib/bundler/cli/exec.rb +6 -0
  22. data/lib/bundler/cli/gem.rb +18 -4
  23. data/lib/bundler/cli/install.rb +9 -25
  24. data/lib/bundler/cli/lock.rb +8 -7
  25. data/lib/bundler/cli/outdated.rb +163 -56
  26. data/lib/bundler/cli/platform.rb +1 -1
  27. data/lib/bundler/cli/show.rb +1 -1
  28. data/lib/bundler/cli/update.rb +10 -23
  29. data/lib/bundler/compact_index_client.rb +108 -0
  30. data/lib/bundler/compact_index_client/cache.rb +119 -0
  31. data/lib/bundler/compact_index_client/updater.rb +88 -0
  32. data/lib/bundler/current_ruby.rb +4 -3
  33. data/lib/bundler/definition.rb +107 -17
  34. data/lib/bundler/dependency.rb +6 -0
  35. data/lib/bundler/dsl.rb +3 -2
  36. data/lib/bundler/env.rb +27 -18
  37. data/lib/bundler/errors.rb +22 -0
  38. data/lib/bundler/feature_flag.rb +32 -0
  39. data/lib/bundler/fetcher.rb +2 -2
  40. data/lib/bundler/fetcher/compact_index.rb +17 -5
  41. data/lib/bundler/fetcher/dependency.rb +1 -1
  42. data/lib/bundler/fetcher/downloader.rb +11 -0
  43. data/lib/bundler/friendly_errors.rb +28 -7
  44. data/lib/bundler/gem_helper.rb +1 -1
  45. data/lib/bundler/gem_helpers.rb +69 -1
  46. data/lib/bundler/gemdeps.rb +28 -0
  47. data/lib/bundler/index.rb +9 -4
  48. data/lib/bundler/inline.rb +3 -3
  49. data/lib/bundler/installer.rb +3 -2
  50. data/lib/bundler/installer/gem_installer.rb +2 -2
  51. data/lib/bundler/installer/parallel_installer.rb +40 -9
  52. data/lib/bundler/lazy_specification.rb +16 -1
  53. data/lib/bundler/lockfile_parser.rb +1 -2
  54. data/lib/bundler/match_platform.rb +12 -3
  55. data/lib/bundler/plugin.rb +4 -2
  56. data/lib/bundler/plugin/api.rb +2 -1
  57. data/lib/bundler/plugin/api/source.rb +1 -1
  58. data/lib/bundler/postit_trampoline.rb +12 -7
  59. data/lib/bundler/remote_specification.rb +5 -0
  60. data/lib/bundler/resolver.rb +59 -49
  61. data/lib/bundler/retry.rb +4 -1
  62. data/lib/bundler/ruby_version.rb +5 -0
  63. data/lib/bundler/rubygems_ext.rb +5 -0
  64. data/lib/bundler/rubygems_gem_installer.rb +60 -0
  65. data/lib/bundler/rubygems_integration.rb +28 -2
  66. data/lib/bundler/runtime.rb +2 -1
  67. data/lib/bundler/settings.rb +29 -5
  68. data/lib/bundler/setup.rb +1 -1
  69. data/lib/bundler/shared_helpers.rb +26 -15
  70. data/lib/bundler/source.rb +5 -0
  71. data/lib/bundler/source/git.rb +1 -1
  72. data/lib/bundler/source/git/git_proxy.rb +5 -0
  73. data/lib/bundler/source/path.rb +6 -1
  74. data/lib/bundler/source/rubygems.rb +11 -1
  75. data/lib/bundler/spec_set.rb +32 -13
  76. data/lib/bundler/templates/newgem/README.md.tt +1 -1
  77. data/lib/bundler/templates/newgem/bin/console.tt +1 -1
  78. data/lib/bundler/templates/newgem/gitignore.tt +5 -0
  79. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
  80. data/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +10 -1
  81. data/lib/bundler/ui/shell.rb +4 -0
  82. data/lib/bundler/ui/silent.rb +9 -0
  83. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +7 -0
  84. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +1 -1
  85. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +2 -2
  86. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +2 -2
  87. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +62 -0
  88. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -1
  89. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +12 -1
  90. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +2 -2
  91. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +2 -2
  92. data/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +1 -1
  93. data/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +22 -13
  94. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/faster.rb +1 -0
  95. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/persistent.rb +24 -23
  96. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/persistent/ssl_reuse.rb +2 -1
  97. data/lib/bundler/vendored_persistent.rb +9 -4
  98. data/lib/bundler/version.rb +1 -1
  99. data/lib/bundler/worker.rb +27 -5
  100. data/lib/bundler/yaml_serializer.rb +1 -1
  101. data/man/bundle-config.ronn +29 -2
  102. data/man/bundle-install.ronn +1 -1
  103. data/man/bundle-lock.ronn +47 -0
  104. data/man/bundle-outdated.ronn +107 -0
  105. data/man/bundle-update.ronn +152 -3
  106. data/man/bundle.ronn +27 -9
  107. data/man/gemfile.5.ronn +8 -0
  108. metadata +37 -31
  109. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +0 -79
  110. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb +0 -112
  111. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb +0 -80
  112. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb +0 -4
@@ -29,7 +29,7 @@ module Bundler
29
29
  output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}"
30
30
 
31
31
  begin
32
- Bundler.definition.validate_ruby!
32
+ Bundler.definition.validate_runtime!
33
33
  output << "Your current platform satisfies the Ruby version requirement."
34
34
  rescue RubyVersionMismatch => e
35
35
  output << e.message
@@ -13,7 +13,7 @@ module Bundler
13
13
 
14
14
  def run
15
15
  Bundler.ui.silence do
16
- Bundler.definition.validate_ruby!
16
+ Bundler.definition.validate_runtime!
17
17
  Bundler.load.lock
18
18
  end
19
19
 
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "bundler/cli/common"
3
+
2
4
  module Bundler
3
5
  class CLI::Update
4
6
  attr_reader :options, :gems
@@ -10,7 +12,7 @@ module Bundler
10
12
  def run
11
13
  Bundler.ui.level = "error" if options[:quiet]
12
14
 
13
- Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
15
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
14
16
 
15
17
  sources = Array(options[:source])
16
18
  groups = Array(options[:group]).map(&:to_sym)
@@ -27,7 +29,6 @@ module Bundler
27
29
  names = Bundler.locked_gems.specs.map(&:name)
28
30
  gems.each do |g|
29
31
  next if names.include?(g)
30
- require "bundler/cli/common"
31
32
  raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(g, names)
32
33
  end
33
34
 
@@ -36,15 +37,11 @@ module Bundler
36
37
  gems.concat(specs.map(&:name))
37
38
  end
38
39
 
39
- Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby])
40
+ Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby],
41
+ :lock_shared_dependencies => options[:conservative])
40
42
  end
41
43
 
42
- patch_level = [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
43
- raise ProductionError, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1
44
- Bundler.definition.gem_version_promoter.tap do |gvp|
45
- gvp.level = patch_level.first || :major
46
- gvp.strict = options[:strict]
47
- end
44
+ Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options)
48
45
 
49
46
  Bundler::Fetcher.disable_endpoint = options["full-index"]
50
47
 
@@ -54,11 +51,8 @@ module Bundler
54
51
 
55
52
  Bundler.settings[:jobs] = opts["jobs"] if opts["jobs"]
56
53
 
57
- # rubygems plugins sometimes hook into the gem install process
58
- Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins)
59
-
60
- Bundler.definition.validate_ruby!
61
- Installer.install Bundler.root, Bundler.definition, opts
54
+ Bundler.definition.validate_runtime!
55
+ installer = Installer.install Bundler.root, Bundler.definition, opts
62
56
  Bundler.load.cache if Bundler.app_cache.exist?
63
57
 
64
58
  if Bundler.settings[:clean] && Bundler.settings[:path]
@@ -67,15 +61,8 @@ module Bundler
67
61
  end
68
62
 
69
63
  Bundler.ui.confirm "Bundle updated!"
70
- without_groups_messages
71
- end
72
-
73
- private
74
-
75
- def without_groups_messages
76
- return unless Bundler.settings.without.any?
77
- require "bundler/cli/common"
78
- Bundler.ui.confirm Bundler::CLI::Common.without_groups_message
64
+ Bundler::CLI::Common.output_without_groups_message
65
+ Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
79
66
  end
80
67
  end
81
68
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+ require "pathname"
3
+ require "set"
4
+
5
+ module Bundler
6
+ class CompactIndexClient
7
+ DEBUG_MUTEX = Mutex.new
8
+ def self.debug
9
+ return unless ENV["DEBUG_COMPACT_INDEX"]
10
+ DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
11
+ end
12
+
13
+ class Error < StandardError; end
14
+
15
+ require "bundler/compact_index_client/cache"
16
+ require "bundler/compact_index_client/updater"
17
+
18
+ attr_reader :directory
19
+
20
+ # @return [Lambda] A lambda that takes an array of inputs and a block, and
21
+ # maps the inputs with the block in parallel.
22
+ #
23
+ attr_accessor :in_parallel
24
+
25
+ def initialize(directory, fetcher)
26
+ @directory = Pathname.new(directory)
27
+ @updater = Updater.new(fetcher)
28
+ @cache = Cache.new(@directory)
29
+ @endpoints = Set.new
30
+ @info_checksums_by_name = {}
31
+ @parsed_checksums = false
32
+ @mutex = Mutex.new
33
+ @in_parallel = lambda do |inputs, &blk|
34
+ inputs.map(&blk)
35
+ end
36
+ end
37
+
38
+ def names
39
+ Bundler::CompactIndexClient.debug { "/names" }
40
+ update(@cache.names_path, "names")
41
+ @cache.names
42
+ end
43
+
44
+ def versions
45
+ Bundler::CompactIndexClient.debug { "/versions" }
46
+ update(@cache.versions_path, "versions")
47
+ versions, @info_checksums_by_name = @cache.versions
48
+ versions
49
+ end
50
+
51
+ def dependencies(names)
52
+ Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
53
+ in_parallel.call(names) do |name|
54
+ update_info(name)
55
+ @cache.dependencies(name).map {|d| d.unshift(name) }
56
+ end.flatten(1)
57
+ end
58
+
59
+ def spec(name, version, platform = nil)
60
+ Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" }
61
+ update_info(name)
62
+ @cache.specific_dependency(name, version, platform)
63
+ end
64
+
65
+ def update_and_parse_checksums!
66
+ Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
67
+ return @info_checksums_by_name if @parsed_checksums
68
+ update(@cache.versions_path, "versions")
69
+ @info_checksums_by_name = @cache.checksums
70
+ @parsed_checksums = true
71
+ end
72
+
73
+ private
74
+
75
+ def update(local_path, remote_path)
76
+ Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
77
+ unless synchronize { @endpoints.add?(remote_path) }
78
+ Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
79
+ return
80
+ end
81
+ @updater.update(local_path, url(remote_path))
82
+ end
83
+
84
+ def update_info(name)
85
+ Bundler::CompactIndexClient.debug { "update_info(#{name})" }
86
+ path = @cache.info_path(name)
87
+ checksum = @updater.checksum_for_file(path)
88
+ unless existing = @info_checksums_by_name[name]
89
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
90
+ return
91
+ end
92
+ if checksum == existing
93
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
94
+ return
95
+ end
96
+ Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
97
+ update(path, "info/#{name}")
98
+ end
99
+
100
+ def url(path)
101
+ path
102
+ end
103
+
104
+ def synchronize
105
+ @mutex.synchronize { yield }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+ require "digest/md5"
3
+
4
+ module Bundler
5
+ class CompactIndexClient
6
+ class Cache
7
+ attr_reader :directory
8
+
9
+ def initialize(directory)
10
+ @directory = Pathname.new(directory).expand_path
11
+ info_roots.each do |dir|
12
+ SharedHelpers.filesystem_access(dir) do
13
+ FileUtils.mkdir_p(dir)
14
+ end
15
+ end
16
+ end
17
+
18
+ def names
19
+ lines(names_path)
20
+ end
21
+
22
+ def names_path
23
+ directory.join("names")
24
+ end
25
+
26
+ def versions
27
+ versions_by_name = Hash.new {|hash, key| hash[key] = [] }
28
+ info_checksums_by_name = {}
29
+
30
+ lines(versions_path).each do |line|
31
+ name, versions_string, info_checksum = line.split(" ", 3)
32
+ info_checksums_by_name[name] = info_checksum || ""
33
+ versions_string.split(",").each do |version|
34
+ if version.start_with?("-")
35
+ version = version[1..-1].split("-", 2).unshift(name)
36
+ versions_by_name[name].delete(version)
37
+ else
38
+ version = version.split("-", 2).unshift(name)
39
+ versions_by_name[name] << version
40
+ end
41
+ end
42
+ end
43
+
44
+ [versions_by_name, info_checksums_by_name]
45
+ end
46
+
47
+ def versions_path
48
+ directory.join("versions")
49
+ end
50
+
51
+ def checksums
52
+ checksums = {}
53
+
54
+ lines(versions_path).each do |line|
55
+ name, _, checksum = line.split(" ", 3)
56
+ checksums[name] = checksum
57
+ end
58
+
59
+ checksums
60
+ end
61
+
62
+ def dependencies(name)
63
+ lines(info_path(name)).map do |line|
64
+ parse_gem(line)
65
+ end
66
+ end
67
+
68
+ def info_path(name)
69
+ name = name.to_s
70
+ if name =~ /[^a-z0-9_-]/
71
+ name += "-#{Digest::MD5.hexdigest(name).downcase}"
72
+ info_roots.last.join(name)
73
+ else
74
+ info_roots.first.join(name)
75
+ end
76
+ end
77
+
78
+ def specific_dependency(name, version, platform)
79
+ pattern = [version, platform].compact.join("-")
80
+ return nil if pattern.empty?
81
+
82
+ gem_lines = info_path(name).read
83
+ gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0]
84
+ gem_line ? parse_gem(gem_line) : nil
85
+ end
86
+
87
+ private
88
+
89
+ def lines(path)
90
+ return [] unless path.file?
91
+ lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n")
92
+ header = lines.index("---")
93
+ header ? lines[header + 1..-1] : lines
94
+ end
95
+
96
+ def parse_gem(string)
97
+ version_and_platform, rest = string.split(" ", 2)
98
+ version, platform = version_and_platform.split("-", 2)
99
+ dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
100
+ dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
101
+ requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
102
+ [version, platform, dependencies, requirements]
103
+ end
104
+
105
+ def parse_dependency(string)
106
+ dependency = string.split(":")
107
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
108
+ dependency
109
+ end
110
+
111
+ def info_roots
112
+ [
113
+ directory.join("info"),
114
+ directory.join("info-special-characters"),
115
+ ]
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require "fileutils"
3
+ require "stringio"
4
+ require "tmpdir"
5
+ require "zlib"
6
+
7
+ module Bundler
8
+ class CompactIndexClient
9
+ class Updater
10
+ class MisMatchedChecksumError < Error
11
+ def initialize(path, server_checksum, local_checksum)
12
+ @path = path
13
+ @server_checksum = server_checksum
14
+ @local_checksum = local_checksum
15
+ end
16
+
17
+ def message
18
+ "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \
19
+ "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})."
20
+ end
21
+ end
22
+
23
+ def initialize(fetcher)
24
+ @fetcher = fetcher
25
+ end
26
+
27
+ def update(local_path, remote_path, retrying = nil)
28
+ headers = {}
29
+
30
+ Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir|
31
+ local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename)
32
+
33
+ # first try to fetch any new bytes on the existing file
34
+ if retrying.nil? && local_path.file?
35
+ FileUtils.cp local_path, local_temp_path
36
+ headers["If-None-Match"] = etag_for(local_temp_path)
37
+ headers["Range"] = "bytes=#{local_temp_path.size}-"
38
+ else
39
+ # Fastly ignores Range when Accept-Encoding: gzip is set
40
+ headers["Accept-Encoding"] = "gzip"
41
+ end
42
+
43
+ response = @fetcher.call(remote_path, headers)
44
+ return nil if response.is_a?(Net::HTTPNotModified)
45
+
46
+ content = response.body
47
+ if response["Content-Encoding"] == "gzip"
48
+ content = Zlib::GzipReader.new(StringIO.new(content)).read
49
+ end
50
+
51
+ mode = response.is_a?(Net::HTTPPartialContent) ? "a" : "w"
52
+ SharedHelpers.filesystem_access(local_temp_path) do
53
+ local_temp_path.open(mode) {|f| f << content }
54
+ end
55
+
56
+ response_etag = response["ETag"].gsub(%r{\AW/}, "")
57
+ if etag_for(local_temp_path) == response_etag
58
+ SharedHelpers.filesystem_access(local_path) do
59
+ FileUtils.mv(local_temp_path, local_path)
60
+ end
61
+ return nil
62
+ end
63
+
64
+ if retrying
65
+ raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path))
66
+ end
67
+
68
+ update(local_path, remote_path, :retrying)
69
+ end
70
+ end
71
+
72
+ def etag_for(path)
73
+ sum = checksum_for_file(path)
74
+ sum ? %("#{sum}") : nil
75
+ end
76
+
77
+ def checksum_for_file(path)
78
+ return nil unless path.file?
79
+ # This must use IO.read instead of Digest.file().hexdigest
80
+ # because we need to preserve \n line endings on windows when calculating
81
+ # the checksum
82
+ SharedHelpers.filesystem_access(path, :read) do
83
+ Digest::MD5.hexdigest(IO.read(path))
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -16,6 +16,7 @@ module Bundler
16
16
  2.2
17
17
  2.3
18
18
  2.4
19
+ 2.5
19
20
  ).freeze
20
21
 
21
22
  KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze
@@ -57,15 +58,15 @@ module Bundler
57
58
  end
58
59
 
59
60
  def mswin64?
60
- Bundler::WINDOWS && Gem::Platform.local.os == "mswin64" && Gem::Platform.local.cpu == "x64"
61
+ Bundler::WINDOWS && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64"
61
62
  end
62
63
 
63
64
  def mingw?
64
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu != "x64"
65
+ Bundler::WINDOWS && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64"
65
66
  end
66
67
 
67
68
  def x64_mingw?
68
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu == "x64"
69
+ Bundler::WINDOWS && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64"
69
70
  end
70
71
 
71
72
  (KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version|
@@ -62,6 +62,7 @@ module Bundler
62
62
  @specs = nil
63
63
  @ruby_version = ruby_version
64
64
 
65
+ @lockfile = lockfile
65
66
  @lockfile_contents = String.new
66
67
  @locked_bundler_version = nil
67
68
  @locked_ruby_version = nil
@@ -69,7 +70,8 @@ module Bundler
69
70
  if lockfile && File.exist?(lockfile)
70
71
  @lockfile_contents = Bundler.read_file(lockfile)
71
72
  @locked_gems = LockfileParser.new(@lockfile_contents)
72
- @platforms = @locked_gems.platforms
73
+ @locked_platforms = @locked_gems.platforms
74
+ @platforms = @locked_platforms.dup
73
75
  @locked_bundler_version = @locked_gems.bundler_version
74
76
  @locked_ruby_version = @locked_gems.ruby_version
75
77
 
@@ -90,25 +92,24 @@ module Bundler
90
92
  @locked_deps = []
91
93
  @locked_specs = SpecSet.new([])
92
94
  @locked_sources = []
95
+ @locked_platforms = []
93
96
  end
94
97
 
95
98
  @unlock[:gems] ||= []
96
99
  @unlock[:sources] ||= []
97
- @unlock[:ruby] ||= if @ruby_version && @locked_ruby_version
98
- unless locked_ruby_version_object = RubyVersion.from_string(@locked_ruby_version)
99
- raise LockfileError, "Failed to create a `RubyVersion` object from " \
100
- "`#{@locked_ruby_version}` found in #{lockfile} -- try running `bundle update --ruby`."
101
- end
100
+ @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
102
101
  @ruby_version.diff(locked_ruby_version_object)
103
102
  end
104
103
  @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
105
104
 
106
- current_platform = Bundler.rubygems.platforms.map {|p| generic(p) }.compact.last
107
- add_platform(current_platform)
105
+ add_current_platform unless Bundler.settings[:frozen]
108
106
 
109
107
  @path_changes = converge_paths
110
- eager_unlock = expand_dependencies(@unlock[:gems])
111
- @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name)
108
+
109
+ unless @unlock[:lock_shared_dependencies]
110
+ eager_unlock = expand_dependencies(@unlock[:gems])
111
+ @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name)
112
+ end
112
113
 
113
114
  @gem_version_promoter = create_gem_version_promoter
114
115
 
@@ -175,7 +176,7 @@ module Bundler
175
176
  rescue GemNotFound => e # Handle yanked gem
176
177
  gem_name, gem_version = extract_gem_info(e)
177
178
  locked_gem = @locked_specs[gem_name].last
178
- raise if locked_gem.nil? || locked_gem.version.to_s != gem_version
179
+ raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote
179
180
  raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \
180
181
  "be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \
181
182
  "that means the author of #{locked_gem} has removed it. You'll need to update your bundle " \
@@ -247,7 +248,7 @@ module Bundler
247
248
  else
248
249
  # Run a resolve against the locally available gems
249
250
  Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
250
- last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, ruby_version, gem_version_promoter, additional_base_requirements_for_resolve)
251
+ last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve)
251
252
  end
252
253
  end
253
254
  end
@@ -262,6 +263,8 @@ module Bundler
262
263
  dependency_names -= pinned_spec_names(source.specs)
263
264
  dependency_names.concat(source.unmet_deps).uniq!
264
265
  end
266
+ idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel)
267
+ idx << Gem::Specification.new("rubygems\0", Gem::VERSION)
265
268
  end
266
269
  end
267
270
 
@@ -338,6 +341,18 @@ module Bundler
338
341
  end
339
342
  end
340
343
 
344
+ def locked_ruby_version_object
345
+ return unless @locked_ruby_version
346
+ @locked_ruby_version_object ||= begin
347
+ unless version = RubyVersion.from_string(@locked_ruby_version)
348
+ raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \
349
+ "#{@lockfile} could not be parsed. " \
350
+ "Try running bundle update --ruby to resolve this."
351
+ end
352
+ version
353
+ end
354
+ end
355
+
341
356
  def to_lock
342
357
  out = String.new
343
358
 
@@ -401,6 +416,11 @@ module Bundler
401
416
  deleted = []
402
417
  changed = []
403
418
 
419
+ new_platforms = @platforms - @locked_platforms
420
+ deleted_platforms = @locked_platforms - @platforms
421
+ added.concat new_platforms.map {|p| "* platform: #{p}" }
422
+ deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }
423
+
404
424
  gemfile_sources = sources.lock_sources
405
425
 
406
426
  new_sources = gemfile_sources - @locked_sources
@@ -449,6 +469,11 @@ module Bundler
449
469
  raise ProductionError, msg if added.any? || deleted.any? || changed.any?
450
470
  end
451
471
 
472
+ def validate_runtime!
473
+ validate_ruby!
474
+ validate_platforms!
475
+ end
476
+
452
477
  def validate_ruby!
453
478
  return unless ruby_version
454
479
 
@@ -474,6 +499,18 @@ module Bundler
474
499
  end
475
500
  end
476
501
 
502
+ def validate_platforms!
503
+ return if @platforms.any? do |bundle_platform|
504
+ Bundler.rubygems.platforms.any? do |local_platform|
505
+ MatchPlatform.platforms_match?(bundle_platform, local_platform)
506
+ end
507
+ end
508
+
509
+ raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
510
+ "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
511
+ "there's no compatible match between those two lists."
512
+ end
513
+
477
514
  def add_platform(platform)
478
515
  @new_platform ||= !@platforms.include?(platform)
479
516
  @platforms |= [platform]
@@ -484,6 +521,20 @@ module Bundler
484
521
  raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
485
522
  end
486
523
 
524
+ def add_current_platform
525
+ current_platform = Bundler.local_platform
526
+ add_platform(current_platform) if Bundler.settings[:specific_platform]
527
+ add_platform(generic(current_platform))
528
+ end
529
+
530
+ def find_resolved_spec(current_spec)
531
+ specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
532
+ end
533
+
534
+ def find_indexed_specs(current_spec)
535
+ index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
536
+ end
537
+
487
538
  attr_reader :sources
488
539
  private :sources
489
540
 
@@ -726,16 +777,54 @@ module Bundler
726
777
  @locked_specs.any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
727
778
  end
728
779
 
780
+ # This list of dependencies is only used in #resolve, so it's OK to add
781
+ # the metadata dependencies here
729
782
  def expanded_dependencies
730
- @expanded_dependencies ||= expand_dependencies(dependencies, @remote)
783
+ @expanded_dependencies ||= begin
784
+ ruby_versions = concat_ruby_version_requirements(@ruby_version)
785
+ if ruby_versions.empty? || !@ruby_version.exact?
786
+ concat_ruby_version_requirements(RubyVersion.system)
787
+ concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
788
+ end
789
+
790
+ metadata_dependencies = [
791
+ Dependency.new("ruby\0", ruby_versions),
792
+ Dependency.new("rubygems\0", Gem::VERSION),
793
+ ]
794
+ expand_dependencies(dependencies + metadata_dependencies, @remote)
795
+ end
796
+ end
797
+
798
+ def concat_ruby_version_requirements(ruby_version, ruby_versions = [])
799
+ return ruby_versions unless ruby_version
800
+ if ruby_version.patchlevel
801
+ ruby_versions << ruby_version.to_gem_version_with_patchlevel
802
+ else
803
+ ruby_versions.concat(ruby_version.versions.map do |version|
804
+ requirement = Gem::Requirement.new(version)
805
+ if requirement.exact?
806
+ "~> #{version}.0"
807
+ else
808
+ requirement
809
+ end
810
+ end)
811
+ end
731
812
  end
732
813
 
733
814
  def expand_dependencies(dependencies, remote = false)
734
815
  deps = []
735
816
  dependencies.each do |dep|
736
817
  dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
737
- next unless remote || dep.current_platform?
738
- dep.gem_platforms(@platforms).each do |p|
818
+ next if !remote && !dep.current_platform?
819
+ platforms = dep.gem_platforms(@platforms)
820
+ if platforms.empty?
821
+ Bundler.ui.warn \
822
+ "The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \
823
+ "Bundler is installing for #{@platforms.join ", "} but the dependency " \
824
+ "is only for #{dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] }.join ", "}. " \
825
+ "To add those platforms to the bundle, run `bundle lock --add-platform #{dep.platforms.join ", "}`."
826
+ end
827
+ platforms.each do |p|
739
828
  deps << DepProxy.new(dep, p) if remote || p == generic_local_platform
740
829
  end
741
830
  end
@@ -812,9 +901,10 @@ module Bundler
812
901
  end
813
902
 
814
903
  def additional_base_requirements_for_resolve
815
- return [] unless @locked_gems && Bundler.settings[:only_update_to_newer_versions]
904
+ return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
816
905
  @locked_gems.specs.reduce({}) do |requirements, locked_spec|
817
- requirements[locked_spec.name] = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
906
+ dep = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
907
+ requirements[locked_spec.name] = DepProxy.new(dep, locked_spec.platform)
818
908
  requirements
819
909
  end.values
820
910
  end