rubygems-update 2.6.7 → 2.6.8

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

Potentially problematic release.


This version of rubygems-update might be problematic. Click here for more details.

Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +11 -0
  3. data/Manifest.txt +10 -5
  4. data/bundler/CHANGELOG.md +108 -0
  5. data/bundler/DEVELOPMENT.md +6 -4
  6. data/bundler/ISSUES.md +17 -0
  7. data/bundler/README.md +2 -0
  8. data/bundler/exe/bundle +4 -6
  9. data/bundler/exe/bundle_ruby +2 -4
  10. data/bundler/exe/bundler +1 -19
  11. data/bundler/lib/bundler.rb +114 -44
  12. data/bundler/lib/bundler/cli.rb +90 -17
  13. data/bundler/lib/bundler/cli/binstubs.rb +4 -3
  14. data/bundler/lib/bundler/cli/cache.rb +1 -1
  15. data/bundler/lib/bundler/cli/check.rb +1 -1
  16. data/bundler/lib/bundler/cli/clean.rb +1 -1
  17. data/bundler/lib/bundler/cli/common.rb +13 -0
  18. data/bundler/lib/bundler/cli/console.rb +3 -0
  19. data/bundler/lib/bundler/cli/doctor.rb +93 -0
  20. data/bundler/lib/bundler/cli/exec.rb +18 -2
  21. data/bundler/lib/bundler/cli/gem.rb +3 -2
  22. data/bundler/lib/bundler/cli/inject.rb +25 -7
  23. data/bundler/lib/bundler/cli/install.rb +25 -7
  24. data/bundler/lib/bundler/cli/lock.rb +20 -7
  25. data/bundler/lib/bundler/cli/outdated.rb +97 -38
  26. data/bundler/lib/bundler/cli/platform.rb +1 -1
  27. data/bundler/lib/bundler/cli/show.rb +1 -1
  28. data/bundler/lib/bundler/cli/update.rb +9 -6
  29. data/bundler/lib/bundler/compact_index_client.rb +102 -0
  30. data/bundler/lib/bundler/compact_index_client/cache.rb +119 -0
  31. data/bundler/lib/bundler/compact_index_client/updater.rb +88 -0
  32. data/bundler/lib/bundler/current_ruby.rb +3 -3
  33. data/bundler/lib/bundler/definition.rb +210 -46
  34. data/bundler/lib/bundler/dependency.rb +1 -1
  35. data/bundler/lib/bundler/deployment.rb +6 -0
  36. data/bundler/lib/bundler/deprecate.rb +16 -0
  37. data/bundler/lib/bundler/dsl.rb +70 -24
  38. data/bundler/lib/bundler/endpoint_specification.rb +2 -0
  39. data/bundler/lib/bundler/env.rb +5 -1
  40. data/bundler/lib/bundler/environment_preserver.rb +1 -1
  41. data/bundler/lib/bundler/errors.rb +12 -1
  42. data/bundler/lib/bundler/feature_flag.rb +32 -0
  43. data/bundler/lib/bundler/fetcher.rb +3 -2
  44. data/bundler/lib/bundler/fetcher/base.rb +10 -0
  45. data/bundler/lib/bundler/fetcher/compact_index.rb +33 -12
  46. data/bundler/lib/bundler/fetcher/dependency.rb +2 -13
  47. data/bundler/lib/bundler/fetcher/downloader.rb +12 -1
  48. data/bundler/lib/bundler/friendly_errors.rb +9 -2
  49. data/bundler/lib/bundler/gem_helper.rb +3 -3
  50. data/bundler/lib/bundler/gem_helpers.rb +69 -1
  51. data/bundler/lib/bundler/gem_version_promoter.rb +175 -0
  52. data/bundler/lib/bundler/gemdeps.rb +28 -0
  53. data/bundler/lib/bundler/graph.rb +4 -25
  54. data/bundler/lib/bundler/index.rb +11 -2
  55. data/bundler/lib/bundler/injector.rb +12 -5
  56. data/bundler/lib/bundler/inline.rb +4 -4
  57. data/bundler/lib/bundler/installer.rb +25 -9
  58. data/bundler/lib/bundler/installer/gem_installer.rb +13 -15
  59. data/bundler/lib/bundler/installer/parallel_installer.rb +121 -99
  60. data/bundler/lib/bundler/lazy_specification.rb +28 -3
  61. data/bundler/lib/bundler/lockfile_parser.rb +27 -17
  62. data/bundler/lib/bundler/match_platform.rb +2 -1
  63. data/bundler/lib/bundler/mirror.rb +2 -2
  64. data/bundler/lib/bundler/plugin.rb +156 -32
  65. data/bundler/lib/bundler/plugin/api.rb +29 -5
  66. data/bundler/lib/bundler/plugin/api/source.rb +293 -0
  67. data/bundler/lib/bundler/plugin/dsl.rb +25 -1
  68. data/bundler/lib/bundler/plugin/index.rb +80 -13
  69. data/bundler/lib/bundler/plugin/installer.rb +6 -10
  70. data/bundler/lib/bundler/plugin/source_list.rb +4 -0
  71. data/bundler/lib/bundler/postit_trampoline.rb +56 -40
  72. data/bundler/lib/bundler/remote_specification.rb +5 -0
  73. data/bundler/lib/bundler/resolver.rb +64 -47
  74. data/bundler/lib/bundler/retry.rb +2 -1
  75. data/bundler/lib/bundler/ruby_version.rb +11 -4
  76. data/bundler/lib/bundler/rubygems_ext.rb +25 -3
  77. data/bundler/lib/bundler/rubygems_gem_installer.rb +54 -0
  78. data/bundler/lib/bundler/rubygems_integration.rb +148 -70
  79. data/bundler/lib/bundler/runtime.rb +27 -3
  80. data/bundler/lib/bundler/settings.rb +80 -17
  81. data/bundler/lib/bundler/setup.rb +7 -4
  82. data/bundler/lib/bundler/shared_helpers.rb +45 -8
  83. data/bundler/lib/bundler/source.rb +2 -1
  84. data/bundler/lib/bundler/source/gemspec.rb +4 -0
  85. data/bundler/lib/bundler/source/git.rb +9 -6
  86. data/bundler/lib/bundler/source/git/git_proxy.rb +37 -4
  87. data/bundler/lib/bundler/source/path.rb +10 -27
  88. data/bundler/lib/bundler/source/path/installer.rb +39 -11
  89. data/bundler/lib/bundler/source/rubygems.rb +3 -2
  90. data/bundler/lib/bundler/source_list.rb +28 -8
  91. data/bundler/lib/bundler/spec_set.rb +30 -15
  92. data/bundler/lib/bundler/templates/Executable.standalone +4 -2
  93. data/bundler/lib/bundler/templates/Gemfile +0 -1
  94. data/bundler/lib/bundler/templates/newgem/README.md.tt +1 -1
  95. data/bundler/lib/bundler/templates/newgem/bin/console.tt +1 -1
  96. data/bundler/lib/bundler/ui/shell.rb +25 -9
  97. data/bundler/lib/bundler/ui/silent.rb +10 -0
  98. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +1 -1
  99. data/bundler/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +8 -2
  100. data/bundler/lib/bundler/vendor/postit/lib/postit.rb +5 -5
  101. data/bundler/lib/bundler/vendor/postit/lib/postit/environment.rb +3 -3
  102. data/bundler/lib/bundler/vendor/postit/lib/postit/installer.rb +1 -1
  103. data/bundler/lib/bundler/vendor/postit/lib/postit/parser.rb +1 -1
  104. data/bundler/lib/bundler/vendor/postit/lib/postit/setup.rb +4 -4
  105. data/bundler/lib/bundler/vendor/postit/lib/postit/version.rb +2 -2
  106. data/bundler/lib/bundler/version.rb +1 -1
  107. data/bundler/lib/bundler/yaml_serializer.rb +34 -11
  108. data/bundler/man/bundle-binstubs.ronn +29 -0
  109. data/bundler/man/bundle-config.ronn +33 -1
  110. data/bundler/man/bundle-exec.ronn +9 -0
  111. data/bundler/man/bundle-install.ronn +6 -41
  112. data/bundler/man/bundle-package.ronn +1 -1
  113. data/bundler/man/bundle.ronn +9 -8
  114. data/bundler/man/gemfile.5.ronn +1 -1
  115. data/lib/rubygems.rb +1 -1
  116. data/lib/rubygems/dependency.rb +7 -4
  117. data/lib/rubygems/request.rb +46 -0
  118. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +7 -0
  119. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb +1 -1
  120. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +2 -2
  121. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +2 -2
  122. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +62 -0
  123. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -1
  124. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb +12 -1
  125. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb +2 -2
  126. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb +2 -2
  127. data/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb +1 -1
  128. data/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb +11 -3
  129. data/test/rubygems/test_gem_request.rb +132 -0
  130. data/test/rubygems/test_gem_specification.rb +7 -0
  131. metadata +34 -29
  132. data/bundler/lib/bundler/environment.rb +0 -42
  133. data/bundler/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +0 -79
  134. data/bundler/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb +0 -98
  135. data/bundler/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb +0 -80
  136. data/bundler/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb +0 -4
@@ -0,0 +1,102 @@
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
+ @in_parallel = lambda do |inputs, &blk|
32
+ inputs.map(&blk)
33
+ end
34
+ end
35
+
36
+ def names
37
+ Bundler::CompactIndexClient.debug { "/names" }
38
+ update(@cache.names_path, "names")
39
+ @cache.names
40
+ end
41
+
42
+ def versions
43
+ Bundler::CompactIndexClient.debug { "/versions" }
44
+ update(@cache.versions_path, "versions")
45
+ versions, @info_checksums_by_name = @cache.versions
46
+ versions
47
+ end
48
+
49
+ def dependencies(names)
50
+ Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
51
+ in_parallel.call(names) do |name|
52
+ update_info(name)
53
+ @cache.dependencies(name).map {|d| d.unshift(name) }
54
+ end.flatten(1)
55
+ end
56
+
57
+ def spec(name, version, platform = nil)
58
+ Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" }
59
+ update_info(name)
60
+ @cache.specific_dependency(name, version, platform)
61
+ end
62
+
63
+ def update_and_parse_checksums!
64
+ Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
65
+ return @info_checksums_by_name if @parsed_checksums
66
+ update(@cache.versions_path, "versions")
67
+ @info_checksums_by_name = @cache.checksums
68
+ @parsed_checksums = true
69
+ end
70
+
71
+ private
72
+
73
+ def update(local_path, remote_path)
74
+ Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
75
+ unless @endpoints.add?(remote_path)
76
+ Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
77
+ return
78
+ end
79
+ @updater.update(local_path, url(remote_path))
80
+ end
81
+
82
+ def update_info(name)
83
+ Bundler::CompactIndexClient.debug { "update_info(#{name})" }
84
+ path = @cache.info_path(name)
85
+ checksum = @updater.checksum_for_file(path)
86
+ unless existing = @info_checksums_by_name[name]
87
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
88
+ return
89
+ end
90
+ if checksum == existing
91
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
92
+ return
93
+ end
94
+ Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
95
+ update(path, "info/#{name}")
96
+ end
97
+
98
+ def url(path)
99
+ path
100
+ end
101
+ end
102
+ 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
@@ -57,15 +57,15 @@ module Bundler
57
57
  end
58
58
 
59
59
  def mswin64?
60
- Bundler::WINDOWS && Gem::Platform.local.os == "mswin64" && Gem::Platform.local.cpu == "x64"
60
+ Bundler::WINDOWS && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64"
61
61
  end
62
62
 
63
63
  def mingw?
64
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu != "x64"
64
+ Bundler::WINDOWS && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64"
65
65
  end
66
66
 
67
67
  def x64_mingw?
68
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu == "x64"
68
+ Bundler::WINDOWS && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64"
69
69
  end
70
70
 
71
71
  (KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version|
@@ -7,7 +7,15 @@ module Bundler
7
7
  class Definition
8
8
  include GemHelpers
9
9
 
10
- attr_reader :dependencies, :platforms, :ruby_version, :locked_deps
10
+ attr_reader(
11
+ :dependencies,
12
+ :gem_version_promoter,
13
+ :locked_deps,
14
+ :locked_gems,
15
+ :platforms,
16
+ :requires,
17
+ :ruby_version
18
+ )
11
19
 
12
20
  # Given a gemfile and lockfile creates a Bundler definition
13
21
  #
@@ -54,21 +62,23 @@ module Bundler
54
62
  @specs = nil
55
63
  @ruby_version = ruby_version
56
64
 
65
+ @lockfile = lockfile
57
66
  @lockfile_contents = String.new
58
67
  @locked_bundler_version = nil
59
68
  @locked_ruby_version = nil
60
69
 
61
70
  if lockfile && File.exist?(lockfile)
62
71
  @lockfile_contents = Bundler.read_file(lockfile)
63
- locked = LockfileParser.new(@lockfile_contents)
64
- @platforms = locked.platforms
65
- @locked_bundler_version = locked.bundler_version
66
- @locked_ruby_version = locked.ruby_version
72
+ @locked_gems = LockfileParser.new(@lockfile_contents)
73
+ @locked_platforms = @locked_gems.platforms
74
+ @platforms = @locked_platforms.dup
75
+ @locked_bundler_version = @locked_gems.bundler_version
76
+ @locked_ruby_version = @locked_gems.ruby_version
67
77
 
68
78
  if unlock != true
69
- @locked_deps = locked.dependencies
70
- @locked_specs = SpecSet.new(locked.specs)
71
- @locked_sources = locked.sources
79
+ @locked_deps = @locked_gems.dependencies
80
+ @locked_specs = SpecSet.new(@locked_gems.specs)
81
+ @locked_sources = @locked_gems.sources
72
82
  else
73
83
  @unlock = {}
74
84
  @locked_deps = []
@@ -78,33 +88,37 @@ module Bundler
78
88
  else
79
89
  @unlock = {}
80
90
  @platforms = []
91
+ @locked_gems = nil
81
92
  @locked_deps = []
82
93
  @locked_specs = SpecSet.new([])
83
94
  @locked_sources = []
95
+ @locked_platforms = []
84
96
  end
85
97
 
86
98
  @unlock[:gems] ||= []
87
99
  @unlock[:sources] ||= []
88
- @unlock[:ruby] ||= if @ruby_version && @locked_ruby_version
89
- unless locked_ruby_version_object = RubyVersion.from_string(@locked_ruby_version)
90
- raise LockfileError, "Failed to create a `RubyVersion` object from " \
91
- "`#{@locked_ruby_version}` found in #{lockfile} -- try running `bundle update --ruby`."
92
- end
100
+ @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
93
101
  @ruby_version.diff(locked_ruby_version_object)
94
102
  end
95
- @unlocking ||= @unlock[:ruby] || (!@locked_ruby_version ^ !@ruby_version)
103
+ @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
96
104
 
97
- current_platform = Bundler.rubygems.platforms.map {|p| generic(p) }.compact.last
98
- add_platform(current_platform)
105
+ add_current_platform unless Bundler.settings[:frozen]
99
106
 
100
107
  @path_changes = converge_paths
101
- eager_unlock = expand_dependencies(@unlock[:gems])
102
- @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
113
+
114
+ @gem_version_promoter = create_gem_version_promoter
103
115
 
104
116
  @source_changes = converge_sources
105
117
  @dependency_changes = converge_dependencies
106
118
  @local_changes = converge_locals
107
119
 
120
+ @requires = compute_requires
121
+
108
122
  fixup_dependency_types!
109
123
  end
110
124
 
@@ -123,6 +137,19 @@ module Bundler
123
137
  end
124
138
  end
125
139
 
140
+ def create_gem_version_promoter
141
+ locked_specs =
142
+ if @unlocking && @locked_specs.empty? && !@lockfile_contents.empty?
143
+ # Definition uses an empty set of locked_specs to indicate all gems
144
+ # are unlocked, but GemVersionPromoter needs the locked_specs
145
+ # for conservative comparison.
146
+ Bundler::SpecSet.new(@locked_gems.specs)
147
+ else
148
+ @locked_specs
149
+ end
150
+ GemVersionPromoter.new(locked_specs, @unlock[:gems])
151
+ end
152
+
126
153
  def resolve_with_cache!
127
154
  raise "Specs already loaded" if @specs
128
155
  sources.cached!
@@ -149,7 +176,7 @@ module Bundler
149
176
  rescue GemNotFound => e # Handle yanked gem
150
177
  gem_name, gem_version = extract_gem_info(e)
151
178
  locked_gem = @locked_specs[gem_name].last
152
- 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
153
180
  raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \
154
181
  "be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \
155
182
  "that means the author of #{locked_gem} has removed it. You'll need to update your bundle " \
@@ -198,7 +225,7 @@ module Bundler
198
225
  end
199
226
 
200
227
  def current_dependencies
201
- dependencies.reject {|d| !d.should_include? }
228
+ dependencies.select(&:should_include?)
202
229
  end
203
230
 
204
231
  def specs_for(groups)
@@ -220,8 +247,8 @@ module Bundler
220
247
  last_resolve
221
248
  else
222
249
  # Run a resolve against the locally available gems
223
- Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies")
224
- last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, ruby_version)
250
+ Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
251
+ last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve)
225
252
  end
226
253
  end
227
254
  end
@@ -236,6 +263,8 @@ module Bundler
236
263
  dependency_names -= pinned_spec_names(source.specs)
237
264
  dependency_names.concat(source.unmet_deps).uniq!
238
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)
239
268
  end
240
269
  end
241
270
 
@@ -312,6 +341,18 @@ module Bundler
312
341
  end
313
342
  end
314
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
+
315
356
  def to_lock
316
357
  out = String.new
317
358
 
@@ -375,6 +416,11 @@ module Bundler
375
416
  deleted = []
376
417
  changed = []
377
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
+
378
424
  gemfile_sources = sources.lock_sources
379
425
 
380
426
  new_sources = gemfile_sources - @locked_sources
@@ -423,6 +469,11 @@ module Bundler
423
469
  raise ProductionError, msg if added.any? || deleted.any? || changed.any?
424
470
  end
425
471
 
472
+ def validate_runtime!
473
+ validate_ruby!
474
+ validate_platforms!
475
+ end
476
+
426
477
  def validate_ruby!
427
478
  return unless ruby_version
428
479
 
@@ -448,11 +499,38 @@ module Bundler
448
499
  end
449
500
  end
450
501
 
502
+ # TODO: refactor this so that `match_platform` can be called with two platforms
503
+ DummyPlatform = Struct.new(:platform)
504
+ class DummyPlatform; include MatchPlatform; end
505
+ def validate_platforms!
506
+ return if @platforms.any? do |bundle_platform|
507
+ bundle_platform = DummyPlatform.new(bundle_platform)
508
+ Bundler.rubygems.platforms.any? do |local_platform|
509
+ bundle_platform.match_platform(local_platform)
510
+ end
511
+ end
512
+
513
+ raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
514
+ "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
515
+ "there's no compatible match between those two lists."
516
+ end
517
+
451
518
  def add_platform(platform)
452
519
  @new_platform ||= !@platforms.include?(platform)
453
520
  @platforms |= [platform]
454
521
  end
455
522
 
523
+ def remove_platform(platform)
524
+ return if @platforms.delete(Gem::Platform.new(platform))
525
+ raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
526
+ end
527
+
528
+ def add_current_platform
529
+ current_platform = Bundler.local_platform
530
+ add_platform(current_platform) if Bundler.settings[:specific_platform]
531
+ add_platform(generic(current_platform))
532
+ end
533
+
456
534
  attr_reader :sources
457
535
  private :sources
458
536
 
@@ -462,6 +540,27 @@ module Bundler
462
540
  !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes
463
541
  end
464
542
 
543
+ def change_reason
544
+ if @unlocking
545
+ unlock_reason = @unlock.reject {|_k, v| Array(v).empty? }.map do |k, v|
546
+ if v == true
547
+ k.to_s
548
+ else
549
+ v = Array(v)
550
+ "#{k}: (#{v.join(", ")})"
551
+ end
552
+ end.join(", ")
553
+ return "bundler is unlocking #{unlock_reason}"
554
+ end
555
+ [
556
+ [@source_changes, "the list of sources changed"],
557
+ [@dependency_changes, "the dependencies in your gemfile changed"],
558
+ [@new_platform, "you added a new platform to your gemfile"],
559
+ [@path_changes, "the gemspecs for path gems changed"],
560
+ [@local_changes, "the gemspecs for git local gems changed"],
561
+ ].select(&:first).map(&:last).join(", ")
562
+ end
563
+
465
564
  def pretty_dep(dep, source = false)
466
565
  msg = String.new(dep.name)
467
566
  msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default
@@ -470,24 +569,16 @@ module Bundler
470
569
  end
471
570
 
472
571
  # Check if the specs of the given source changed
473
- # according to the locked source. A block should be
474
- # in order to specify how the locked version of
475
- # the source should be found.
476
- def specs_changed?(source, &block)
477
- locked = @locked_sources.find(&block)
478
-
479
- if locked
480
- unlocking = @locked_specs.any? do |locked_spec|
481
- locked_spec.source.class == locked.class && locked_spec.source != locked
482
- end
483
- end
572
+ # according to the locked source.
573
+ def specs_changed?(source)
574
+ locked = @locked_sources.find {|s| s == source }
484
575
 
485
- !locked || unlocking || dependencies_for_source_changed?(source) || specs_for_source_changed?(source)
576
+ !locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source)
486
577
  end
487
578
 
488
- def dependencies_for_source_changed?(source)
579
+ def dependencies_for_source_changed?(source, locked_source = source)
489
580
  deps_for_source = @dependencies.select {|s| s.source == source }
490
- locked_deps_for_source = @locked_deps.select {|s| s.source == source }
581
+ locked_deps_for_source = @locked_deps.select {|s| s.source == locked_source }
491
582
 
492
583
  Set.new(deps_for_source) != Set.new(locked_deps_for_source)
493
584
  end
@@ -514,22 +605,34 @@ module Bundler
514
605
  end
515
606
  end
516
607
 
517
- locals.any? do |source, changed|
518
- changed || specs_changed?(source) {|o| source.class == o.class && source.uri == o.uri }
519
- end
608
+ sources_with_changes = locals.select do |source, changed|
609
+ changed || specs_changed?(source)
610
+ end.map(&:first)
611
+ !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty?
520
612
  end
521
613
 
522
614
  def converge_paths
523
615
  sources.path_sources.any? do |source|
524
- specs_changed?(source) do |ls|
525
- ls.class == source.class && ls.path == source.path
526
- end
616
+ specs_changed?(source)
527
617
  end
528
618
  end
529
619
 
620
+ def converge_path_source_to_gemspec_source(source)
621
+ return source unless source.instance_of?(Source::Path)
622
+ gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source }
623
+ gemspec_source || source
624
+ end
625
+
530
626
  def converge_sources
531
627
  changes = false
532
628
 
629
+ @locked_sources.map! do |source|
630
+ converge_path_source_to_gemspec_source(source)
631
+ end
632
+ @locked_specs.each do |spec|
633
+ spec.source &&= converge_path_source_to_gemspec_source(spec.source)
634
+ end
635
+
533
636
  # Get the Rubygems sources from the Gemfile.lock
534
637
  locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
535
638
  # Get the Rubygems remotes from the Gemfile
@@ -574,6 +677,9 @@ module Bundler
574
677
  elsif dep.source
575
678
  dep.source = sources.get(dep.source)
576
679
  end
680
+ if dep.source.is_a?(Source::Gemspec)
681
+ dep.platforms.concat(@platforms.map {|p| Dependency::REVERSE_PLATFORM_MAP[p] }.flatten(1)).uniq!
682
+ end
577
683
  end
578
684
  Set.new(@dependencies) != Set.new(@locked_deps)
579
685
  end
@@ -625,7 +731,7 @@ module Bundler
625
731
  # then we unlock it.
626
732
 
627
733
  # Path sources have special logic
628
- if s.source.instance_of?(Source::Path)
734
+ if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec)
629
735
  other = s.source.specs[s].first
630
736
 
631
737
  # If the spec is no longer in the path source, unlock it. This
@@ -667,16 +773,54 @@ module Bundler
667
773
  @locked_specs.any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
668
774
  end
669
775
 
776
+ # This list of dependencies is only used in #resolve, so it's OK to add
777
+ # the metadata dependencies here
670
778
  def expanded_dependencies
671
- @expanded_dependencies ||= expand_dependencies(dependencies, @remote)
779
+ @expanded_dependencies ||= begin
780
+ ruby_versions = concat_ruby_version_requirements(@ruby_version)
781
+ if ruby_versions.empty? || !@ruby_version.exact?
782
+ concat_ruby_version_requirements(RubyVersion.system)
783
+ concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
784
+ end
785
+
786
+ metadata_dependencies = [
787
+ Dependency.new("ruby\0", ruby_versions),
788
+ Dependency.new("rubygems\0", Gem::VERSION),
789
+ ]
790
+ expand_dependencies(dependencies + metadata_dependencies, @remote)
791
+ end
792
+ end
793
+
794
+ def concat_ruby_version_requirements(ruby_version, ruby_versions = [])
795
+ return ruby_versions unless ruby_version
796
+ if ruby_version.patchlevel
797
+ ruby_versions << ruby_version.to_gem_version_with_patchlevel
798
+ else
799
+ ruby_versions.concat(ruby_version.versions.map do |version|
800
+ requirement = Gem::Requirement.new(version)
801
+ if requirement.exact?
802
+ "~> #{version}.0"
803
+ else
804
+ requirement
805
+ end
806
+ end)
807
+ end
672
808
  end
673
809
 
674
810
  def expand_dependencies(dependencies, remote = false)
675
811
  deps = []
676
812
  dependencies.each do |dep|
677
813
  dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
678
- next unless remote || dep.current_platform?
679
- dep.gem_platforms(@platforms).each do |p|
814
+ next if !remote && !dep.current_platform?
815
+ platforms = dep.gem_platforms(@platforms)
816
+ if platforms.empty?
817
+ Bundler.ui.warn \
818
+ "The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \
819
+ "Bundler is installing for #{@platforms.join ", "} but the dependency " \
820
+ "is only for #{dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] }.join ", "}. " \
821
+ "To add those platforms to the bundle, run `bundle lock --add-platform #{dep.platforms.join ", "}`."
822
+ end
823
+ platforms.each do |p|
680
824
  deps << DepProxy.new(dep, p) if remote || p == generic_local_platform
681
825
  end
682
826
  end
@@ -740,5 +884,25 @@ module Bundler
740
884
  # to an array. The first element will be the gem name (e.g. foo), the second will be the version number.
741
885
  error.message.scan(/Could not find (\w+)-(\d+(?:\.\d+)+)/).flatten
742
886
  end
887
+
888
+ def compute_requires
889
+ dependencies.reduce({}) do |requires, dep|
890
+ next requires unless dep.should_include?
891
+ requires[dep.name] = Array(dep.autorequire || dep.name).map do |file|
892
+ # Allow `require: true` as an alias for `require: <name>`
893
+ file == true ? dep.name : file
894
+ end
895
+ requires
896
+ end
897
+ end
898
+
899
+ def additional_base_requirements_for_resolve
900
+ return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
901
+ @locked_gems.specs.reduce({}) do |requirements, locked_spec|
902
+ dep = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
903
+ requirements[locked_spec.name] = DepProxy.new(dep, locked_spec.platform)
904
+ requirements
905
+ end.values
906
+ end
743
907
  end
744
908
  end