dependabot-bundler 0.138.2 → 0.138.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2eb2653cea8a396b9d65f20ea24e8680bdcb0909c2e28ad045beca904f2a984b
4
- data.tar.gz: d7ffc19ecc9db88a04e5222f132e18f94f353eb806ef454d0e958c531531dbd5
3
+ metadata.gz: b879a336288560d831472f3d6163c9fc391159001388ce2b1454154af3b7e026
4
+ data.tar.gz: cd9f4ca835e065ba322f6edf43830954714396e2e33a292cbdb0ca8defe776bb
5
5
  SHA512:
6
- metadata.gz: 28ea0b95452a1c7bc2cf7fe5d3c211c678a89d5b062f7184dc23fd06dd5f8df8a61ccd9e66abb3a807fc10958f74623245ef4809830600a44ce6472095ce369a
7
- data.tar.gz: 46c21b5dbc8ddaac57a40c1a6e48c16427562c829d6b29879a5b1b6d77b9d3c16ebdef515ef32c6f71d4c57c4feeed57ff22ee7f78a6839991e3444cefd44181
6
+ metadata.gz: e688088af198726160d18a81d649ced5627ac3311b80449698d1854ca656d9bf411f792553a99ad9cc45b012af97df82c5cac0753bed375c5a4009867759df3e
7
+ data.tar.gz: aa6730e552463a32bef72bc0ceb7209ead39f327994b547fb3345a167cbbf7f7cc0c10a8e5f756b2d2ec41e7227e11fffef60e716bb158e7e40025c77fa1a39a
data/helpers/v1/build CHANGED
@@ -21,4 +21,5 @@ cd "$install_dir"
21
21
 
22
22
  # NOTE: Sets `BUNDLED WITH` to match the installed v1 version in Gemfile.lock
23
23
  # forcing native helpers to run with the same version
24
- BUNDLER_VERSION=1 bundle install --without test
24
+ BUNDLER_VERSION=1 bundle config set --local without "test"
25
+ BUNDLER_VERSION=1 bundle install
data/helpers/v1/run.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler"
2
4
  require "json"
3
5
 
@@ -11,11 +13,25 @@ require "git_source_patch"
11
13
 
12
14
  require "functions"
13
15
 
16
+ MAX_BUNDLER_VERSION="2.0.0"
17
+
18
+ def validate_bundler_version!
19
+ return true if correct_bundler_version?
20
+
21
+ raise StandardError, "Called with Bundler '#{Bundler::VERSION}', expected < '#{MAX_BUNDLER_VERSION}'"
22
+ end
23
+
24
+ def correct_bundler_version?
25
+ Gem::Version.new(Bundler::VERSION) < Gem::Version.new(MAX_BUNDLER_VERSION)
26
+ end
27
+
14
28
  def output(obj)
15
29
  print JSON.dump(obj)
16
30
  end
17
31
 
18
32
  begin
33
+ validate_bundler_version!
34
+
19
35
  request = JSON.parse($stdin.read)
20
36
 
21
37
  function = request["function"]
@@ -27,6 +27,9 @@ LOCKFILE_ENDING = /(?<ending>\s*(?:RUBY VERSION|BUNDLED WITH).*)/m.freeze
27
27
 
28
28
  def project_dependency_files(project)
29
29
  project_path = File.expand_path(File.join("../../spec/fixtures/projects/bundler1", project))
30
+
31
+ raise "Fixture does not exist for project: '#{project}'" unless Dir.exist?(project_path)
32
+
30
33
  Dir.chdir(project_path) do
31
34
  # NOTE: Include dotfiles (e.g. .npmrc)
32
35
  files = Dir.glob("**/*", File::FNM_DOTMATCH)
data/helpers/v2/build CHANGED
@@ -11,6 +11,7 @@ fi
11
11
  helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
12
12
  cp -r \
13
13
  "$helpers_dir/lib" \
14
+ "$helpers_dir/monkey_patches" \
14
15
  "$helpers_dir/run.rb" \
15
16
  "$helpers_dir/Gemfile" \
16
17
  "$install_dir"
@@ -20,4 +21,5 @@ cd "$install_dir"
20
21
  # NOTE: Sets `BUNDLED WITH` to match the installed v1 version in Gemfile.lock
21
22
  # forcing specs and native helpers to run with the same version
22
23
  BUNDLER_VERSION=2 bundle config set --local path ".bundle"
23
- BUNDLER_VERSION=2 bundle install --without test
24
+ BUNDLER_VERSION=2 bundle config set --local without "test"
25
+ BUNDLER_VERSION=2 bundle install
@@ -1,66 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "functions/conflicting_dependency_resolver"
4
+ require "functions/dependency_source"
1
5
  require "functions/file_parser"
6
+ require "functions/force_updater"
7
+ require "functions/lockfile_updater"
8
+ require "functions/version_resolver"
2
9
 
3
10
  module Functions
4
11
  class NotImplementedError < StandardError; end
5
12
 
6
13
  def self.parsed_gemfile(lockfile_name:, gemfile_name:, dir:)
7
14
  set_bundler_flags_and_credentials(dir: dir, credentials: [],
8
- using_bundler2: false)
15
+ using_bundler2: false)
9
16
  FileParser.new(lockfile_name: lockfile_name).
10
17
  parsed_gemfile(gemfile_name: gemfile_name)
11
18
  end
12
19
 
13
20
  def self.parsed_gemspec(lockfile_name:, gemspec_name:, dir:)
14
21
  set_bundler_flags_and_credentials(dir: dir, credentials: [],
15
- using_bundler2: false)
22
+ using_bundler2: false)
16
23
  FileParser.new(lockfile_name: lockfile_name).
17
24
  parsed_gemspec(gemspec_name: gemspec_name)
18
25
  end
19
26
 
20
27
  def self.vendor_cache_dir(dir:)
21
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
28
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
29
+ using_bundler2: false)
30
+ Bundler.app_cache
22
31
  end
23
32
 
24
33
  def self.update_lockfile(dir:, gemfile_name:, lockfile_name:, using_bundler2:,
25
34
  credentials:, dependencies:)
26
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
35
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
36
+ using_bundler2: using_bundler2)
37
+ LockfileUpdater.new(
38
+ gemfile_name: gemfile_name,
39
+ lockfile_name: lockfile_name,
40
+ dependencies: dependencies
41
+ ).run
27
42
  end
28
43
 
29
44
  def self.force_update(dir:, dependency_name:, target_version:, gemfile_name:,
30
45
  lockfile_name:, using_bundler2:, credentials:,
31
46
  update_multiple_dependencies:)
32
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
47
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
48
+ using_bundler2: using_bundler2)
49
+ ForceUpdater.new(
50
+ dependency_name: dependency_name,
51
+ target_version: target_version,
52
+ gemfile_name: gemfile_name,
53
+ lockfile_name: lockfile_name,
54
+ update_multiple_dependencies: update_multiple_dependencies
55
+ ).run
33
56
  end
34
57
 
35
58
  def self.dependency_source_type(gemfile_name:, dependency_name:, dir:,
36
59
  credentials:)
37
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
60
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
61
+ using_bundler2: false)
62
+
63
+ DependencySource.new(
64
+ gemfile_name: gemfile_name,
65
+ dependency_name: dependency_name
66
+ ).type
38
67
  end
39
68
 
40
69
  def self.depencency_source_latest_git_version(gemfile_name:, dependency_name:,
41
70
  dir:, credentials:,
42
71
  dependency_source_url:,
43
72
  dependency_source_branch:)
44
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
73
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
74
+ using_bundler2: false)
75
+ DependencySource.new(
76
+ gemfile_name: gemfile_name,
77
+ dependency_name: dependency_name
78
+ ).latest_git_version(
79
+ dependency_source_url: dependency_source_url,
80
+ dependency_source_branch: dependency_source_branch
81
+ )
45
82
  end
46
83
 
47
84
  def self.private_registry_versions(gemfile_name:, dependency_name:, dir:,
48
85
  credentials:)
49
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
86
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
87
+ using_bundler2: false)
88
+
89
+ DependencySource.new(
90
+ gemfile_name: gemfile_name,
91
+ dependency_name: dependency_name
92
+ ).private_registry_versions
50
93
  end
51
94
 
52
95
  def self.resolve_version(dependency_name:, dependency_requirements:,
53
96
  gemfile_name:, lockfile_name:, using_bundler2:,
54
97
  dir:, credentials:)
55
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
98
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials, using_bundler2: using_bundler2)
99
+ VersionResolver.new(
100
+ dependency_name: dependency_name,
101
+ dependency_requirements: dependency_requirements,
102
+ gemfile_name: gemfile_name,
103
+ lockfile_name: lockfile_name
104
+ ).version_details
56
105
  end
57
106
 
58
107
  def self.jfrog_source(dir:, gemfile_name:, credentials:, using_bundler2:)
59
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
108
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials, using_bundler2: using_bundler2)
109
+
110
+ Bundler::Definition.build(gemfile_name, nil, {}).
111
+ send(:sources).
112
+ rubygems_remotes.
113
+ find { |uri| uri.host.include?("jfrog") }&.
114
+ host
60
115
  end
61
116
 
62
117
  def self.git_specs(dir:, gemfile_name:, credentials:, using_bundler2:)
63
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
118
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
119
+ using_bundler2: using_bundler2)
120
+
121
+ git_specs = Bundler::Definition.build(gemfile_name, nil, {}).dependencies.
122
+ select do |spec|
123
+ spec.source.is_a?(Bundler::Source::Git)
124
+ end
125
+ git_specs.map do |spec|
126
+ # Piggy-back off some private Bundler methods to configure the
127
+ # URI with auth details in the same way Bundler does.
128
+ git_proxy = spec.source.send(:git_proxy)
129
+ auth_uri = spec.source.uri.gsub("git://", "https://")
130
+ auth_uri = git_proxy.send(:configured_uri_for, auth_uri)
131
+ auth_uri += ".git" unless auth_uri.end_with?(".git")
132
+ auth_uri += "/info/refs?service=git-upload-pack"
133
+ {
134
+ uri: spec.source.uri,
135
+ auth_uri: auth_uri
136
+ }
137
+ end
64
138
  end
65
139
 
66
140
  def self.set_bundler_flags_and_credentials(dir:, credentials:,
@@ -110,6 +184,12 @@ module Functions
110
184
 
111
185
  def self.conflicting_dependencies(dir:, dependency_name:, target_version:,
112
186
  lockfile_name:, using_bundler2:, credentials:)
113
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
187
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
188
+ using_bundler2: using_bundler2)
189
+ ConflictingDependencyResolver.new(
190
+ dependency_name: dependency_name,
191
+ target_version: target_version,
192
+ lockfile_name: lockfile_name
193
+ ).conflicting_dependencies
114
194
  end
115
195
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Functions
4
+ class ConflictingDependencyResolver
5
+ def initialize(dependency_name:, target_version:, lockfile_name:)
6
+ @dependency_name = dependency_name
7
+ @target_version = target_version
8
+ @lockfile_name = lockfile_name
9
+ end
10
+
11
+ # Finds any dependencies in the lockfile that have a subdependency on the
12
+ # given dependency that does not satisfly the target_version.
13
+ # @return [Array<Hash{String => String}]
14
+ # * explanation [String] a sentence explaining the conflict
15
+ # * name [String] the blocking dependencies name
16
+ # * version [String] the version of the blocking dependency
17
+ # * requirement [String] the requirement on the target_dependency
18
+ def conflicting_dependencies
19
+ Bundler.settings.set_command_option("only_update_to_newer_versions", true)
20
+
21
+ parent_specs.flat_map do |parent_spec|
22
+ top_level_specs_for(parent_spec).map do |top_level|
23
+ dependency = parent_spec.dependencies.find { |bd| bd.name == dependency_name }
24
+ {
25
+ "explanation" => explanation(parent_spec, dependency, top_level),
26
+ "name" => parent_spec.name,
27
+ "version" => parent_spec.version.to_s,
28
+ "requirement" => dependency.requirement.to_s
29
+ }
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :dependency_name, :target_version, :lockfile_name
37
+
38
+ def parent_specs
39
+ version = Gem::Version.new(target_version)
40
+ parsed_lockfile.specs.filter do |spec|
41
+ spec.dependencies.any? do |dep|
42
+ dep.name == dependency_name &&
43
+ !dep.requirement.satisfied_by?(version)
44
+ end
45
+ end
46
+ end
47
+
48
+ def top_level_specs_for(parent_spec)
49
+ return [parent_spec] if top_level?(parent_spec)
50
+
51
+ parsed_lockfile.specs.filter do |spec|
52
+ spec.dependencies.any? do |dep|
53
+ dep.name == parent_spec.name && top_level?(spec)
54
+ end
55
+ end
56
+ end
57
+
58
+ def top_level?(spec)
59
+ parsed_lockfile.dependencies.key?(spec.name)
60
+ end
61
+
62
+ def explanation(spec, dependency, top_level)
63
+ if spec.name == top_level.name
64
+ "#{spec.name} (#{spec.version}) requires #{dependency_name} (#{dependency.requirement})"
65
+ else
66
+ "#{top_level.name} (#{top_level.version}) requires #{dependency_name} "\
67
+ "(#{dependency.requirement}) via #{spec.name} (#{spec.version})"
68
+ end
69
+ end
70
+
71
+ def parsed_lockfile
72
+ @parsed_lockfile ||= Bundler::LockfileParser.new(lockfile)
73
+ end
74
+
75
+ def lockfile
76
+ return @lockfile if defined?(@lockfile)
77
+
78
+ @lockfile =
79
+ begin
80
+ return unless lockfile_name && File.exist?(lockfile_name)
81
+
82
+ File.read(lockfile_name)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,86 @@
1
+ module Functions
2
+ class DependencySource
3
+ attr_reader :gemfile_name, :dependency_name
4
+
5
+ RUBYGEMS = "rubygems"
6
+ PRIVATE_REGISTRY = "private"
7
+ GIT = "git"
8
+ OTHER = "other"
9
+
10
+ def initialize(gemfile_name:, dependency_name:)
11
+ @gemfile_name = gemfile_name
12
+ @dependency_name = dependency_name
13
+ end
14
+
15
+ def type
16
+ bundler_source = specified_source || default_source
17
+ type_of(bundler_source)
18
+ end
19
+
20
+ def latest_git_version(dependency_source_url:, dependency_source_branch:)
21
+ source = Bundler::Source::Git.new(
22
+ "uri" => dependency_source_url,
23
+ "branch" => dependency_source_branch,
24
+ "name" => dependency_name,
25
+ "submodules" => true
26
+ )
27
+
28
+ # Tell Bundler we're fine with fetching the source remotely
29
+ source.instance_variable_set(:@allow_remote, true)
30
+
31
+ spec = source.specs.first
32
+ { version: spec.version, commit_sha: spec.source.revision }
33
+ end
34
+
35
+ def private_registry_versions
36
+ bundler_source = specified_source || default_source
37
+
38
+ bundler_source.
39
+ fetchers.flat_map do |fetcher|
40
+ fetcher.
41
+ specs_with_retry([dependency_name], bundler_source).
42
+ search_all(dependency_name)
43
+ end.
44
+ map(&:version)
45
+ end
46
+
47
+ private
48
+
49
+ def type_of(bundler_source)
50
+ case bundler_source
51
+ when Bundler::Source::Rubygems
52
+ remote = bundler_source.remotes.first
53
+ if remote.nil? || remote.to_s == "https://rubygems.org/"
54
+ RUBYGEMS
55
+ else
56
+ PRIVATE_REGISTRY
57
+ end
58
+ when Bundler::Source::Git
59
+ GIT
60
+ else
61
+ OTHER
62
+ end
63
+ end
64
+
65
+ def specified_source
66
+ return @specified_source if defined? @specified_source
67
+
68
+ @specified_source = definition.dependencies.
69
+ find { |dep| dep.name == dependency_name }&.source
70
+ end
71
+
72
+ def default_source
73
+ definition.send(:sources).default_source
74
+ end
75
+
76
+ def definition
77
+ @definition ||= Bundler::Definition.build(gemfile_name, nil, {})
78
+ end
79
+
80
+ def serialize_bundler_source(source)
81
+ {
82
+ type: source.class.to_s
83
+ }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,167 @@
1
+ module Functions
2
+ class ForceUpdater
3
+ class TransitiveDependencyError < StandardError; end
4
+
5
+ def initialize(dependency_name:, target_version:, gemfile_name:,
6
+ lockfile_name:, update_multiple_dependencies:)
7
+ @dependency_name = dependency_name
8
+ @target_version = target_version
9
+ @gemfile_name = gemfile_name
10
+ @lockfile_name = lockfile_name
11
+ @update_multiple_dependencies = update_multiple_dependencies
12
+ end
13
+
14
+ def run
15
+ # Only allow upgrades. Otherwise it's unlikely that this
16
+ # resolution will be found by the FileUpdater
17
+ Bundler.settings.set_command_option(
18
+ "only_update_to_newer_versions",
19
+ true
20
+ )
21
+
22
+ dependencies_to_unlock = []
23
+
24
+ begin
25
+ definition = build_definition(dependencies_to_unlock: dependencies_to_unlock)
26
+ definition.resolve_remotely!
27
+ specs = definition.resolve
28
+ updates = [{ name: dependency_name }] +
29
+ dependencies_to_unlock.map { |dep| { name: dep.name } }
30
+ specs = specs.map do |dep|
31
+ {
32
+ name: dep.name,
33
+ version: dep.version
34
+ }
35
+ end
36
+ [updates, specs]
37
+ rescue Bundler::VersionConflict => e
38
+ raise unless update_multiple_dependencies?
39
+
40
+ # TODO: Not sure this won't unlock way too many things...
41
+ new_dependencies_to_unlock =
42
+ new_dependencies_to_unlock_from(
43
+ error: e,
44
+ already_unlocked: dependencies_to_unlock
45
+ )
46
+
47
+ raise if new_dependencies_to_unlock.none?
48
+
49
+ dependencies_to_unlock += new_dependencies_to_unlock
50
+ retry
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :dependency_name, :target_version, :gemfile_name,
57
+ :lockfile_name, :credentials,
58
+ :update_multiple_dependencies
59
+ alias update_multiple_dependencies? update_multiple_dependencies
60
+
61
+ def new_dependencies_to_unlock_from(error:, already_unlocked:)
62
+ potentials_deps =
63
+ relevant_conflicts(error, already_unlocked).
64
+ flat_map(&:requirement_trees).
65
+ reject do |tree|
66
+ # If the final requirement wasn't specific, it can't be binding
67
+ next true if tree.last.requirement == Gem::Requirement.new(">= 0")
68
+
69
+ # If the conflict wasn't for the dependency we're updating then
70
+ # we don't have enough info to reject it
71
+ next false unless tree.last.name == dependency_name
72
+
73
+ # If the final requirement *was* for the dependency we're updating
74
+ # then we can ignore the tree if it permits the target version
75
+ tree.last.requirement.satisfied_by?(
76
+ Gem::Version.new(target_version)
77
+ )
78
+ end.map(&:first)
79
+
80
+ potentials_deps.
81
+ reject { |dep| already_unlocked.map(&:name).include?(dep.name) }.
82
+ reject { |dep| [dependency_name, "ruby\0"].include?(dep.name) }.
83
+ uniq
84
+ end
85
+
86
+ def relevant_conflicts(error, dependencies_being_unlocked)
87
+ names = [*dependencies_being_unlocked.map(&:name), dependency_name]
88
+
89
+ # For a conflict to be relevant to the updates we're making it must be
90
+ # 1) caused by a new requirement introduced by our unlocking, or
91
+ # 2) caused by an old requirement that prohibits the update.
92
+ # Hence, we look at the beginning and end of the requirement trees
93
+ error.cause.conflicts.values.
94
+ select do |conflict|
95
+ conflict.requirement_trees.any? do |t|
96
+ names.include?(t.last.name) || names.include?(t.first.name)
97
+ end
98
+ end
99
+ end
100
+
101
+ def build_definition(dependencies_to_unlock:)
102
+ gems_to_unlock = dependencies_to_unlock.map(&:name) + [dependency_name]
103
+ definition = Bundler::Definition.build(
104
+ gemfile_name,
105
+ lockfile_name,
106
+ gems: gems_to_unlock + subdependencies,
107
+ lock_shared_dependencies: true
108
+ )
109
+
110
+ # Remove the Gemfile / gemspec requirements on the gems we're
111
+ # unlocking (i.e., completely unlock them)
112
+ gems_to_unlock.each do |gem_name|
113
+ unlock_gem(definition: definition, gem_name: gem_name)
114
+ end
115
+
116
+ dep = definition.dependencies.
117
+ find { |d| d.name == dependency_name }
118
+
119
+ # If the dependency is not found in the Gemfile it means this is a
120
+ # transitive dependency that we can't force update.
121
+ raise TransitiveDependencyError unless dep
122
+
123
+ # Set the requirement for the gem we're forcing an update of
124
+ new_req = Gem::Requirement.create("= #{target_version}")
125
+ dep.instance_variable_set(:@requirement, new_req)
126
+ dep.source = nil if dep.source.is_a?(Bundler::Source::Git)
127
+
128
+ definition
129
+ end
130
+
131
+ def lockfile
132
+ return @lockfile if defined?(@lockfile)
133
+
134
+ @lockfile =
135
+ begin
136
+ return unless lockfile_name && File.exist?(lockfile_name)
137
+
138
+ File.read(lockfile_name)
139
+ end
140
+ end
141
+
142
+ def subdependencies
143
+ # If there's no lockfile we don't need to worry about
144
+ # subdependencies
145
+ return [] unless lockfile
146
+
147
+ all_deps = Bundler::LockfileParser.new(lockfile).
148
+ specs.map(&:name).map(&:to_s)
149
+ top_level = Bundler::Definition.
150
+ build(gemfile_name, lockfile_name, {}).
151
+ dependencies.map(&:name).map(&:to_s)
152
+
153
+ all_deps - top_level
154
+ end
155
+
156
+ def unlock_gem(definition:, gem_name:)
157
+ dep = definition.dependencies.find { |d| d.name == gem_name }
158
+ version = definition.locked_gems.specs.
159
+ find { |d| d.name == gem_name }.version
160
+
161
+ dep&.instance_variable_set(
162
+ :@requirement,
163
+ Gem::Requirement.create(">= #{version}")
164
+ )
165
+ end
166
+ end
167
+ end