dependabot-core 0.80.1 → 0.81.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,202 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
- require "dependabot/dependency_file"
5
- require "dependabot/file_parsers/rust/cargo"
6
- require "dependabot/update_checkers/rust/cargo"
7
-
8
- module Dependabot
9
- module UpdateCheckers
10
- module Rust
11
- class Cargo
12
- # This class takes a set of dependency files and sanitizes them for use
13
- # in UpdateCheckers::Rust::Cargo.
14
- class FilePreparer
15
- def initialize(dependency_files:, dependency:,
16
- unlock_requirement: true,
17
- replacement_git_pin: nil,
18
- latest_allowable_version: nil)
19
- @dependency_files = dependency_files
20
- @dependency = dependency
21
- @unlock_requirement = unlock_requirement
22
- @replacement_git_pin = replacement_git_pin
23
- @latest_allowable_version = latest_allowable_version
24
- end
25
-
26
- def prepared_dependency_files
27
- files = []
28
- files += manifest_files.map do |file|
29
- DependencyFile.new(
30
- name: file.name,
31
- content: manifest_content_for_update_check(file),
32
- directory: file.directory
33
- )
34
- end
35
- files << lockfile if lockfile
36
- files << toolchain if toolchain
37
- files
38
- end
39
-
40
- private
41
-
42
- attr_reader :dependency_files, :dependency, :replacement_git_pin,
43
- :latest_allowable_version
44
-
45
- def unlock_requirement?
46
- @unlock_requirement
47
- end
48
-
49
- def replace_git_pin?
50
- !replacement_git_pin.nil?
51
- end
52
-
53
- def manifest_content_for_update_check(file)
54
- content = file.content
55
-
56
- unless file.support_file?
57
- content = replace_version_constraint(content, file.name)
58
- content = replace_git_pin(content) if replace_git_pin?
59
- end
60
-
61
- content = replace_ssh_urls(content)
62
-
63
- content
64
- end
65
-
66
- # Note: We don't need to care about formatting in this method, since
67
- # we're only using the manifest to find the latest resolvable version
68
- def replace_version_constraint(content, filename)
69
- parsed_manifest = TomlRB.parse(content)
70
-
71
- FileParsers::Rust::Cargo::DEPENDENCY_TYPES.each do |type|
72
- next unless (req = parsed_manifest.dig(type, dependency.name))
73
-
74
- updated_req = temporary_requirement_for_resolution(filename)
75
-
76
- if req.is_a?(Hash)
77
- parsed_manifest[type][dependency.name]["version"] = updated_req
78
- else
79
- parsed_manifest[type][dependency.name] = updated_req
80
- end
81
- end
82
-
83
- TomlRB.dump(parsed_manifest)
84
- end
85
-
86
- def replace_git_pin(content)
87
- parsed_manifest = TomlRB.parse(content)
88
-
89
- FileParsers::Rust::Cargo::DEPENDENCY_TYPES.each do |type|
90
- next unless (req = parsed_manifest.dig(type, dependency.name))
91
- next unless req.is_a?(Hash)
92
- next unless [req["tag"], req["rev"]].compact.uniq.count == 1
93
-
94
- if req["tag"]
95
- parsed_manifest[type][dependency.name]["tag"] =
96
- replacement_git_pin
97
- end
98
-
99
- if req["rev"]
100
- parsed_manifest[type][dependency.name]["rev"] =
101
- replacement_git_pin
102
- end
103
- end
104
-
105
- TomlRB.dump(parsed_manifest)
106
- end
107
-
108
- def replace_ssh_urls(content)
109
- parsed_manifest = TomlRB.parse(content)
110
-
111
- FileParsers::Rust::Cargo::DEPENDENCY_TYPES.each do |type|
112
- (parsed_manifest[type] || {}).each do |_, details|
113
- next unless details.is_a?(Hash)
114
- next unless details["git"]
115
-
116
- details["git"] = details["git"].
117
- gsub(%r{ssh://git@(.*?)/}, 'https://\1/')
118
- end
119
- end
120
-
121
- TomlRB.dump(parsed_manifest)
122
- end
123
-
124
- def temporary_requirement_for_resolution(filename)
125
- original_req = dependency.requirements.
126
- find { |r| r.fetch(:file) == filename }&.
127
- fetch(:requirement)
128
-
129
- lower_bound_req =
130
- if original_req && !unlock_requirement?
131
- original_req
132
- else
133
- ">= #{lower_bound_version}"
134
- end
135
-
136
- unless Utils::Rust::Version.correct?(latest_allowable_version) &&
137
- Utils::Rust::Version.new(latest_allowable_version) >=
138
- Utils::Rust::Version.new(lower_bound_version)
139
- return lower_bound_req
140
- end
141
-
142
- lower_bound_req + ", <= #{latest_allowable_version}"
143
- end
144
-
145
- def lower_bound_version
146
- @lower_bound_version ||=
147
- if git_dependency? && git_dependency_version
148
- git_dependency_version
149
- elsif !git_dependency? && dependency.version
150
- dependency.version
151
- else
152
- version_from_requirement =
153
- dependency.requirements.map { |r| r.fetch(:requirement) }.
154
- compact.
155
- flat_map { |req_str| Utils::Rust::Requirement.new(req_str) }.
156
- flat_map(&:requirements).
157
- reject { |req_array| req_array.first.start_with?("<") }.
158
- map(&:last).
159
- max&.to_s
160
-
161
- version_from_requirement || 0
162
- end
163
- end
164
-
165
- def git_dependency_version
166
- return unless lockfile
167
-
168
- TomlRB.parse(lockfile.content).
169
- fetch("package", []).
170
- select { |p| p["name"] == dependency.name }.
171
- find { |p| p["source"].end_with?(dependency.version) }.
172
- fetch("version")
173
- end
174
-
175
- def manifest_files
176
- @manifest_files ||=
177
- dependency_files.select { |f| f.name.end_with?("Cargo.toml") }
178
-
179
- raise "No Cargo.toml!" if @manifest_files.none?
180
-
181
- @manifest_files
182
- end
183
-
184
- def lockfile
185
- @lockfile ||= dependency_files.find { |f| f.name == "Cargo.lock" }
186
- end
187
-
188
- def toolchain
189
- @toolchain ||=
190
- dependency_files.find { |f| f.name == "rust-toolchain" }
191
- end
192
-
193
- def git_dependency?
194
- GitCommitChecker.
195
- new(dependency: dependency, credentials: []).
196
- git_dependency?
197
- end
198
- end
199
- end
200
- end
201
- end
202
- end
@@ -1,175 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ################################################################################
4
- # For more details on rust version constraints, see: #
5
- # - https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html #
6
- # - https://steveklabnik.github.io/semver/semver/index.html #
7
- ################################################################################
8
-
9
- require "dependabot/update_checkers/rust/cargo"
10
- require "dependabot/utils/rust/requirement"
11
- require "dependabot/utils/rust/version"
12
-
13
- module Dependabot
14
- module UpdateCheckers
15
- module Rust
16
- class Cargo
17
- class RequirementsUpdater
18
- class UnfixableRequirement < StandardError; end
19
-
20
- VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-*]+)*/.freeze
21
- ALLOWED_UPDATE_STRATEGIES =
22
- %i(bump_versions bump_versions_if_necessary).freeze
23
-
24
- def initialize(requirements:, updated_source:, update_strategy:,
25
- library:, latest_version:, latest_resolvable_version:)
26
- @requirements = requirements
27
- @updated_source = updated_source
28
- @update_strategy = update_strategy
29
- @library = library
30
-
31
- check_update_strategy
32
-
33
- if latest_version && version_class.correct?(latest_version)
34
- @latest_version = version_class.new(latest_version)
35
- end
36
-
37
- return unless latest_resolvable_version
38
- return unless version_class.correct?(latest_resolvable_version)
39
-
40
- @latest_resolvable_version =
41
- version_class.new(latest_resolvable_version)
42
- end
43
-
44
- def updated_requirements
45
- # Note: Order is important here. The FileUpdater needs the updated
46
- # requirement at index `i` to correspond to the previous requirement
47
- # at the same index.
48
- requirements.map do |req|
49
- req = req.merge(source: updated_source)
50
- next req unless latest_resolvable_version
51
- next req if req[:requirement].nil?
52
-
53
- # TODO: Add a widen_ranges options
54
- if update_strategy == :bump_versions_if_necessary
55
- update_version_requirement_if_needed(req)
56
- else
57
- update_version_requirement(req)
58
- end
59
- end
60
- end
61
-
62
- private
63
-
64
- attr_reader :requirements, :updated_source, :update_strategy,
65
- :latest_version, :latest_resolvable_version
66
-
67
- def library?
68
- @library
69
- end
70
-
71
- def check_update_strategy
72
- return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy)
73
-
74
- raise "Unknown update strategy: #{update_strategy}"
75
- end
76
-
77
- def target_version
78
- library? ? latest_version : latest_resolvable_version
79
- end
80
-
81
- def update_version_requirement(req)
82
- string_reqs = req[:requirement].split(",").map(&:strip)
83
-
84
- new_requirement =
85
- if (exact_req = exact_req(string_reqs))
86
- # If there's an exact version, just return that
87
- # (it will dominate any other requirements)
88
- update_version_string(exact_req)
89
- elsif (req_to_update = non_range_req(string_reqs)) &&
90
- update_version_string(req_to_update) != req_to_update
91
- # If a ~, ^, or * range needs to be updated, just return that
92
- # (it will dominate any other requirements)
93
- update_version_string(req_to_update)
94
- else
95
- # Otherwise, we must have a range requirement that needs
96
- # updating. Update it, but keep other requirements too
97
- update_range_requirements(string_reqs)
98
- end
99
-
100
- req.merge(requirement: new_requirement)
101
- end
102
-
103
- def update_version_requirement_if_needed(req)
104
- string_reqs = req[:requirement].split(",").map(&:strip)
105
- ruby_reqs = string_reqs.map { |r| Utils::Rust::Requirement.new(r) }
106
-
107
- return req if ruby_reqs.all? { |r| r.satisfied_by?(target_version) }
108
-
109
- update_version_requirement(req)
110
- end
111
-
112
- def update_version_string(req_string)
113
- req_string.sub(VERSION_REGEX) do |old_version|
114
- # For pre-release versions, just use the full version string
115
- next target_version.to_s if old_version.match?(/\d-/)
116
-
117
- old_parts = old_version.split(".")
118
- new_parts = target_version.to_s.split(".").
119
- first(old_parts.count)
120
- new_parts.map.with_index do |part, i|
121
- old_parts[i] == "*" ? "*" : part
122
- end.join(".")
123
- end
124
- end
125
-
126
- def non_range_req(string_reqs)
127
- string_reqs.find { |r| r.include?("*") || r.match?(/^[\d~^]/) }
128
- end
129
-
130
- def exact_req(string_reqs)
131
- string_reqs.find { |r| Utils::Rust::Requirement.new(r).exact? }
132
- end
133
-
134
- def update_range_requirements(string_reqs)
135
- string_reqs.map do |req|
136
- next req unless req.match?(/[<>]/)
137
-
138
- ruby_req = Utils::Rust::Requirement.new(req)
139
- next req if ruby_req.satisfied_by?(target_version)
140
-
141
- raise UnfixableRequirement if req.start_with?(">")
142
-
143
- req.sub(VERSION_REGEX) do |old_version|
144
- update_greatest_version(old_version, target_version)
145
- end
146
- end.join(", ")
147
- rescue UnfixableRequirement
148
- :unfixable
149
- end
150
-
151
- def update_greatest_version(old_version, version_to_be_permitted)
152
- version = version_class.new(old_version)
153
- version = version.release if version.prerelease?
154
-
155
- index_to_update =
156
- version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max
157
-
158
- version.segments.map.with_index do |_, index|
159
- if index < index_to_update
160
- version_to_be_permitted.segments[index]
161
- elsif index == index_to_update
162
- version_to_be_permitted.segments[index] + 1
163
- else 0
164
- end
165
- end.join(".")
166
- end
167
-
168
- def version_class
169
- Utils::Rust::Version
170
- end
171
- end
172
- end
173
- end
174
- end
175
- end
@@ -1,242 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
- require "dependabot/shared_helpers"
5
- require "dependabot/file_parsers/rust/cargo"
6
- require "dependabot/update_checkers/rust/cargo"
7
- require "dependabot/utils/rust/version"
8
- require "dependabot/errors"
9
-
10
- module Dependabot
11
- module UpdateCheckers
12
- module Rust
13
- class Cargo
14
- class VersionResolver
15
- BRANCH_NOT_FOUND_REGEX =
16
- /failed to find branch `(?<branch>[^`]+)`/.freeze
17
-
18
- def initialize(dependency:, credentials:,
19
- original_dependency_files:, prepared_dependency_files:)
20
- @dependency = dependency
21
- @prepared_dependency_files = prepared_dependency_files
22
- @original_dependency_files = original_dependency_files
23
- @credentials = credentials
24
- end
25
-
26
- def latest_resolvable_version
27
- @latest_resolvable_version ||= fetch_latest_resolvable_version
28
- end
29
-
30
- private
31
-
32
- attr_reader :dependency, :credentials,
33
- :prepared_dependency_files, :original_dependency_files
34
-
35
- def fetch_latest_resolvable_version
36
- base_directory = prepared_dependency_files.first.directory
37
- SharedHelpers.in_a_temporary_directory(base_directory) do
38
- write_temporary_dependency_files
39
-
40
- SharedHelpers.with_git_configured(credentials: credentials) do
41
- # Shell out to Cargo, which handles everything for us, and does
42
- # so without doing an install (so it's fast).
43
- command = "cargo update -p #{dependency_spec} --verbose"
44
- run_cargo_command(command)
45
- end
46
-
47
- new_lockfile_content = File.read("Cargo.lock")
48
- updated_version = get_version_from_lockfile(new_lockfile_content)
49
-
50
- return if updated_version.nil?
51
- return updated_version if git_dependency?
52
-
53
- version_class.new(updated_version)
54
- end
55
- rescue SharedHelpers::HelperSubprocessFailed => error
56
- handle_cargo_errors(error)
57
- end
58
-
59
- def get_version_from_lockfile(lockfile_content)
60
- versions = TomlRB.parse(lockfile_content).fetch("package").
61
- select { |p| p["name"] == dependency.name }
62
-
63
- updated_version =
64
- if dependency.top_level?
65
- versions.max_by { |p| version_class.new(p.fetch("version")) }
66
- else
67
- versions.min_by { |p| version_class.new(p.fetch("version")) }
68
- end
69
-
70
- if git_dependency?
71
- updated_version.fetch("source").split("#").last
72
- else
73
- updated_version.fetch("version")
74
- end
75
- end
76
-
77
- def dependency_spec
78
- spec = dependency.name
79
-
80
- if git_dependency?
81
- spec += ":#{git_dependency_version}" if git_dependency_version
82
- elsif dependency.version
83
- spec += ":#{dependency.version}"
84
- end
85
-
86
- spec
87
- end
88
-
89
- def run_cargo_command(command)
90
- raw_response = nil
91
- IO.popen(command, err: %i(child out)) do |process|
92
- raw_response = process.read
93
- end
94
-
95
- # Raise an error with the output from the shell session if Cargo
96
- # returns a non-zero status
97
- return if $CHILD_STATUS.success?
98
-
99
- raise SharedHelpers::HelperSubprocessFailed.new(
100
- raw_response,
101
- command
102
- )
103
- end
104
-
105
- def write_temporary_dependency_files(prepared: true)
106
- write_manifest_files(prepared: prepared)
107
-
108
- File.write(lockfile.name, lockfile.content) if lockfile
109
- File.write(toolchain.name, toolchain.content) if toolchain
110
- end
111
-
112
- def handle_cargo_errors(error)
113
- if error.message.include?("does not have these features")
114
- # TODO: Ideally we should update the declaration not to ask
115
- # for the specified features
116
- return nil
117
- end
118
-
119
- if error.message.match?(BRANCH_NOT_FOUND_REGEX)
120
- branch = error.message.match(BRANCH_NOT_FOUND_REGEX).
121
- named_captures.fetch("branch")
122
- raise Dependabot::BranchNotFound, branch
123
- end
124
-
125
- if resolvability_error?(error.message)
126
- raise Dependabot::DependencyFileNotResolvable, error.message
127
- end
128
-
129
- raise error
130
- end
131
-
132
- def resolvability_error?(message)
133
- return true if message.include?("failed to parse lock")
134
- return true if message.include?("believes it's in a workspace")
135
- return true if message.include?("wasn't a root")
136
- return true if message.include?("requires a nightly version")
137
- return true if message.match?(/feature `[^\`]+` is required/)
138
-
139
- !original_requirements_resolvable?
140
- end
141
-
142
- def original_requirements_resolvable?
143
- base_directory = original_dependency_files.first.directory
144
- SharedHelpers.in_a_temporary_directory(base_directory) do
145
- write_temporary_dependency_files(prepared: false)
146
-
147
- SharedHelpers.with_git_configured(credentials: credentials) do
148
- command = "cargo update -p #{dependency_spec} --verbose"
149
- run_cargo_command(command)
150
- end
151
- end
152
-
153
- true
154
- rescue SharedHelpers::HelperSubprocessFailed => error
155
- raise unless error.message.include?("no matching version") ||
156
- error.message.include?("failed to select a version")
157
-
158
- false
159
- end
160
-
161
- def write_manifest_files(prepared: true)
162
- manifest_files = if prepared then prepared_manifest_files
163
- else original_manifest_files
164
- end
165
-
166
- manifest_files.each do |file|
167
- path = file.name
168
- dir = Pathname.new(path).dirname
169
- FileUtils.mkdir_p(dir)
170
- File.write(file.name, sanitized_manifest_content(file.content))
171
-
172
- FileUtils.mkdir_p(File.join(dir, "src"))
173
- File.write(File.join(dir, "src/lib.rs"), dummy_app_content)
174
- File.write(File.join(dir, "src/main.rs"), dummy_app_content)
175
- end
176
- end
177
-
178
- def git_dependency_version
179
- return unless lockfile
180
-
181
- TomlRB.parse(lockfile.content).
182
- fetch("package", []).
183
- select { |p| p["name"] == dependency.name }.
184
- find { |p| p["source"].end_with?(dependency.version) }.
185
- fetch("version")
186
- end
187
-
188
- def dummy_app_content
189
- %{fn main() {\nprintln!("Hello, world!");\n}}
190
- end
191
-
192
- def sanitized_manifest_content(content)
193
- object = TomlRB.parse(content)
194
-
195
- package_name = object.dig("package", "name")
196
- return content unless package_name&.match?(/[\{\}]/)
197
-
198
- if lockfile
199
- raise "Sanitizing name for pkg with lockfile. Investigate!"
200
- end
201
-
202
- object["package"]["name"] = "sanitized"
203
- TomlRB.dump(object)
204
- end
205
-
206
- def prepared_manifest_files
207
- @prepared_manifest_files ||=
208
- prepared_dependency_files.
209
- select { |f| f.name.end_with?("Cargo.toml") }
210
- end
211
-
212
- def original_manifest_files
213
- @original_manifest_files ||=
214
- original_dependency_files.
215
- select { |f| f.name.end_with?("Cargo.toml") }
216
- end
217
-
218
- def lockfile
219
- @lockfile ||= prepared_dependency_files.
220
- find { |f| f.name == "Cargo.lock" }
221
- end
222
-
223
- def toolchain
224
- @toolchain ||= prepared_dependency_files.
225
- find { |f| f.name == "rust-toolchain" }
226
- end
227
-
228
- def git_dependency?
229
- GitCommitChecker.new(
230
- dependency: dependency,
231
- credentials: credentials
232
- ).git_dependency?
233
- end
234
-
235
- def version_class
236
- Utils::Rust::Version
237
- end
238
- end
239
- end
240
- end
241
- end
242
- end