dependabot-cargo 0.340.0 → 0.342.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 072a4c9070f558b9d46d699d40debaa675953e7d02c1975d31cdada8d53e30e8
4
- data.tar.gz: 6c52f00cc8b7a5fc95dbe6c5fcf0b9322c6bd5e23d1368d0e797fca360d62207
3
+ metadata.gz: 24474e4539bb3a8547a639f5a16737166f4d0e329c8b8d857245a2133719282e
4
+ data.tar.gz: fe51accf4820675950bbf0837704608f998a66cf67ccc5d0c4c405f400cea3b8
5
5
  SHA512:
6
- metadata.gz: 11fdbd06bcbe80bf10e11f44742024246e603cd8eafc82dd2687c88ee3e413329f0e298357e121ee1fa6707fc208a5b317aebf57999b9d1ceea97b663169f748
7
- data.tar.gz: 97efb3fef98287a70c1e3c7e42279ec9b8943a3b372feb8c883adfc7c5eaf73d53adeda79de2153d09a745ad94ff2db2e6a970af01fe6476518b5051cf0cd905
6
+ metadata.gz: 3b8448fe1beb3afd164187c8240c7dc67c7ae7ff07d67fbc7b112dd066b6ec6838af4d5c79024333261dc1240941b72ce05765b44581b189ac600c6c2034e3fa
7
+ data.tar.gz: d6a9626c6e705b73597dbe0cd94e5b95e3bbdaa532c98d7f2826f978095267e4ffadb7ae369b9ef01807f05ddf679638abde40ccbb337304e776af01ea7bc62d
@@ -57,6 +57,13 @@ module Dependabot
57
57
  fetched_files << T.must(rust_toolchain) if rust_toolchain
58
58
  fetched_files += fetch_path_dependency_and_workspace_files
59
59
 
60
+ # If the main Cargo.toml uses workspace dependencies, ensure we have the workspace root
61
+ parsed_manifest = parsed_file(cargo_toml)
62
+ if uses_workspace_dependencies?(parsed_manifest) || workspace_member?(parsed_manifest)
63
+ workspace_root = find_workspace_root(cargo_toml)
64
+ fetched_files << workspace_root if workspace_root && !fetched_files.include?(workspace_root)
65
+ end
66
+
60
67
  # Filter excluded files from final collection
61
68
  filtered_files = fetched_files.reject do |file|
62
69
  Dependabot::FileFiltering.should_exclude_path?(file.name, "file from final collection", @exclude_paths)
@@ -131,17 +138,17 @@ module Dependabot
131
138
 
132
139
  next if previously_fetched_files.map(&:name).include?(path)
133
140
  next if file.name == path
134
-
135
141
  next if Dependabot::FileFiltering.should_exclude_path?(path, "file from final collection", @exclude_paths)
136
142
 
137
143
  fetched_file = fetch_file_from_host(path, fetch_submodules: true)
138
144
  previously_fetched_files << fetched_file
139
- grandchild_requirement_files =
140
- fetch_workspace_files(
141
- file: fetched_file,
142
- previously_fetched_files: previously_fetched_files
143
- )
144
- [fetched_file, *grandchild_requirement_files]
145
+ grandchild_requirement_files = fetch_workspace_files(
146
+ file: fetched_file,
147
+ previously_fetched_files: previously_fetched_files
148
+ )
149
+
150
+ workspace_root = workspace_root_for_file(fetched_file)
151
+ [fetched_file, *grandchild_requirement_files, workspace_root]
145
152
  end.compact
146
153
 
147
154
  files.each { |f| f.support_file = file != cargo_toml }
@@ -168,24 +175,18 @@ module Dependabot
168
175
 
169
176
  next if previously_fetched_files.map(&:name).include?(path)
170
177
  next if file.name == path
171
-
172
178
  next if Dependabot::FileFiltering.should_exclude_path?(path, "file from final collection", @exclude_paths)
173
179
 
174
180
  fetched_file = fetch_file_from_host(path, fetch_submodules: true)
175
181
  .tap { |f| f.support_file = true }
176
182
  previously_fetched_files << fetched_file
177
- grandchild_requirement_files =
178
- fetch_path_dependency_files(
179
- file: fetched_file,
180
- previously_fetched_files: previously_fetched_files
181
- )
182
-
183
- # If this path dependency file is a workspace member that inherits from
184
- # its root workspace, we search for the root to include it so Cargo can
185
- # resolve the path dependency file manifest properly.
186
- root = find_workspace_root(fetched_file) if workspace_member?(parsed_file(fetched_file))
187
-
188
- [fetched_file, *grandchild_requirement_files, root]
183
+ grandchild_requirement_files = fetch_path_dependency_files(
184
+ file: fetched_file,
185
+ previously_fetched_files: previously_fetched_files
186
+ )
187
+
188
+ workspace_root = workspace_root_for_file(fetched_file)
189
+ [fetched_file, *grandchild_requirement_files, workspace_root]
189
190
  rescue Dependabot::DependencyFileNotFound
190
191
  next unless required_path?(file, path)
191
192
 
@@ -198,15 +199,21 @@ module Dependabot
198
199
  unfetchable_required_path_deps
199
200
  end
200
201
 
202
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) }
203
+ def workspace_root_for_file(file)
204
+ parsed_manifest = parsed_file(file)
205
+ return unless workspace_member?(parsed_manifest) || uses_workspace_dependencies?(parsed_manifest)
206
+
207
+ find_workspace_root(file)
208
+ end
209
+
201
210
  sig { params(dependencies: T::Hash[T.untyped, T.untyped]).returns(T::Array[String]) }
202
211
  def collect_path_dependencies_paths(dependencies)
203
- paths = []
204
- dependencies.each do |_, details|
212
+ dependencies.filter_map do |_, details|
205
213
  next unless details.is_a?(Hash) && details["path"]
206
214
 
207
- paths << File.join(details["path"], "Cargo.toml").delete_prefix("/")
215
+ File.join(details["path"], "Cargo.toml").delete_prefix("/")
208
216
  end
209
- paths
210
217
  end
211
218
 
212
219
  # rubocop:enable Metrics/PerceivedComplexity
@@ -229,8 +236,7 @@ module Dependabot
229
236
  end
230
237
  end
231
238
 
232
- paths += replacement_path_dependency_paths_from_file(file)
233
- paths
239
+ paths + replacement_path_dependency_paths_from_file(file)
234
240
  end
235
241
 
236
242
  sig { params(file: Dependabot::DependencyFile).returns(T::Array[String]) }
@@ -239,8 +245,7 @@ module Dependabot
239
245
 
240
246
  # Paths specified as replacements
241
247
  parsed_file(file).fetch("replace", {}).each do |_, details|
242
- next unless details.is_a?(Hash)
243
- next unless details["path"]
248
+ next unless details.is_a?(Hash) && details["path"]
244
249
 
245
250
  paths << File.join(details["path"], "Cargo.toml")
246
251
  end
@@ -250,8 +255,7 @@ module Dependabot
250
255
  next unless details.is_a?(Hash)
251
256
 
252
257
  details.each do |_, dep_details|
253
- next unless dep_details.is_a?(Hash)
254
- next unless dep_details["path"]
258
+ next unless dep_details.is_a?(Hash) && dep_details["path"]
255
259
 
256
260
  paths << File.join(dep_details["path"], "Cargo.toml")
257
261
  end
@@ -260,6 +264,35 @@ module Dependabot
260
264
  paths
261
265
  end
262
266
 
267
+ # Check if this Cargo manifest uses workspace dependencies
268
+ # (e.g. dependency = { workspace = true }).
269
+ sig { params(parsed_manifest: T::Hash[T.untyped, T.untyped]).returns(T::Boolean) }
270
+ def uses_workspace_dependencies?(parsed_manifest)
271
+ # Check regular dependencies
272
+ workspace_deps = Cargo::FileParser::DEPENDENCY_TYPES.any? do |type|
273
+ deps = parsed_manifest.fetch(type, {})
274
+ deps.any? do |_, details|
275
+ next false unless details.is_a?(Hash)
276
+
277
+ details["workspace"] == true
278
+ end
279
+ end
280
+
281
+ return true if workspace_deps
282
+
283
+ # Check target-specific dependencies
284
+ parsed_manifest.fetch("target", {}).any? do |_, target_details|
285
+ Cargo::FileParser::DEPENDENCY_TYPES.any? do |type|
286
+ deps = target_details.fetch(type, {})
287
+ deps.any? do |_, details|
288
+ next false unless details.is_a?(Hash)
289
+
290
+ details["workspace"] == true
291
+ end
292
+ end
293
+ end
294
+ end
295
+
263
296
  # See if this Cargo manifest inherits any property from a workspace
264
297
  # (e.g. edition = { workspace = true }).
265
298
  sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T::Boolean) }
@@ -99,12 +99,44 @@ module Dependabot
99
99
 
100
100
  sig { params(error: StandardError).returns(T.noreturn) }
101
101
  def handle_cargo_error(error)
102
- raise unless error.message.include?("failed to select a version") ||
103
- error.message.include?("no matching version") ||
104
- error.message.include?("unexpected end of input while parsing major version number")
102
+ raise unless resolvable_cargo_error?(error.message)
105
103
  raise if error.message.include?("`#{dependency.name} ")
106
104
 
107
- raise Dependabot::DependencyFileNotResolvable, error.message
105
+ extract_binary_path_error(error.message)
106
+ end
107
+
108
+ sig { params(message: String).returns(T::Boolean) }
109
+ def resolvable_cargo_error?(message)
110
+ message.include?("failed to select a version") ||
111
+ message.include?("no matching version") ||
112
+ message.include?("unexpected end of input while parsing major version number") ||
113
+ message.match?(/couldn't find `[^`]+\.rs`/) ||
114
+ message.match?(/failed to find `[^`]+\.rs`/) ||
115
+ message.match?(/could not find `[^`]+\.rs`/) ||
116
+ message.match?(/cannot find binary `[^`]+`/) ||
117
+ message.include?("Please specify bin.path if you want to use a non-default path") ||
118
+ message.include?("binary target")
119
+ end
120
+
121
+ sig { params(message: String).returns(T.noreturn) }
122
+ def extract_binary_path_error(message)
123
+ if (match = message.match(/can't find `([^`]+)` bin at `([^`]+)`/))
124
+ binary_name = match[1]
125
+ expected_path = match[2]
126
+ raise Dependabot::DependencyFileNotResolvable,
127
+ "Binary '#{binary_name}' not found at expected path '#{expected_path}'. " \
128
+ "Please check the bin.path configuration in Cargo.toml."
129
+ elsif (match = message.match(/(couldn't find|failed to find|could not find) `([^`]+\.rs)`/))
130
+ file_path = match[2]
131
+ raise Dependabot::DependencyFileNotResolvable,
132
+ "Source file '#{file_path}' not found. Please check the bin.path configuration in Cargo.toml."
133
+ elsif (match = message.match(/cannot find binary `([^`]+)`/))
134
+ binary_name = match[1]
135
+ raise Dependabot::DependencyFileNotResolvable,
136
+ "Binary target '#{binary_name}' not found. Please check the [[bin]] configuration in Cargo.toml."
137
+ end
138
+
139
+ raise Dependabot::DependencyFileNotResolvable, message
108
140
  end
109
141
 
110
142
  # rubocop:disable Metrics/PerceivedComplexity
@@ -186,28 +218,24 @@ module Dependabot
186
218
  start = Time.now
187
219
  command = SharedHelpers.escape_command(command)
188
220
  Helpers.setup_credentials_in_environment(credentials)
189
- # Pass through any registry tokens supplied via CARGO_REGISTRIES_...
190
- # environment variables.
191
221
  env = ENV.select { |key, _value| key.match(/^CARGO_REGISTRIES_/) }
192
222
  stdout, process = Open3.capture2e(env, command)
193
223
  time_taken = Time.now - start
194
224
 
195
- # Raise an error with the output from the shell session if Cargo
196
- # returns a non-zero status
197
225
  return if process.success?
198
226
 
227
+ handle_cargo_command_error(stdout, command, fingerprint, time_taken)
228
+ end
229
+
230
+ sig { params(stdout: String, command: String, fingerprint: String, time_taken: Float).returns(T.noreturn) }
231
+ def handle_cargo_command_error(stdout, command, fingerprint, time_taken)
199
232
  if using_old_toolchain?(stdout)
200
233
  raise Dependabot::DependencyFileNotEvaluatable, "Dependabot only supports toolchain 1.68 and up."
201
234
  end
202
235
 
203
- # package doesn't exist in the index
204
- if (match = stdout.match(/no matching package named `([^`]+)` found/))
205
- raise Dependabot::DependencyFileNotResolvable, match[1]
206
- end
207
-
208
- if (match = /error: no matching package found\nsearched package name: `([^`]+)`/m.match(stdout))
209
- raise Dependabot::DependencyFileNotResolvable, match[1]
210
- end
236
+ check_ambiguous_package_error(stdout)
237
+ check_missing_package_error(stdout)
238
+ check_binary_path_error(stdout)
211
239
 
212
240
  raise SharedHelpers::HelperSubprocessFailed.new(
213
241
  message: stdout,
@@ -215,11 +243,48 @@ module Dependabot
215
243
  command: command,
216
244
  fingerprint: fingerprint,
217
245
  time_taken: time_taken,
218
- process_exit_value: process.to_s
246
+ process_exit_value: "non-zero"
219
247
  }
220
248
  )
221
249
  end
222
250
 
251
+ sig { params(stdout: String).void }
252
+ def check_ambiguous_package_error(stdout)
253
+ ambiguous_match = stdout.match(/There are multiple `([^`]+)` packages.*specification `([^`]+)` is ambiguous/)
254
+ return unless ambiguous_match
255
+
256
+ raise Dependabot::DependencyFileNotEvaluatable, "Ambiguous package specification: #{ambiguous_match[2]}"
257
+ end
258
+
259
+ sig { params(stdout: String).void }
260
+ def check_missing_package_error(stdout)
261
+ if (match = stdout.match(/no matching package named `([^`]+)` found/))
262
+ raise Dependabot::DependencyFileNotResolvable, match[1]
263
+ end
264
+
265
+ if (match = /error: no matching package found\nsearched package name: `([^`]+)`/m.match(stdout))
266
+ raise Dependabot::DependencyFileNotResolvable, match[1]
267
+ end
268
+ end
269
+
270
+ sig { params(stdout: String).void }
271
+ def check_binary_path_error(stdout)
272
+ return unless binary_path_error?(stdout)
273
+
274
+ extract_binary_path_error(stdout)
275
+ end
276
+
277
+ sig { params(stdout: String).returns(T::Boolean) }
278
+ def binary_path_error?(stdout)
279
+ stdout.match?(/couldn't find `[^`]+\.rs`/) ||
280
+ stdout.match?(/failed to find `[^`]+\.rs`/) ||
281
+ stdout.match?(/could not find `[^`]+\.rs`/) ||
282
+ stdout.match?(/cannot find binary `[^`]+`/) ||
283
+ stdout.match?(/binary target `[^`]+` not found/) ||
284
+ stdout.include?("Please specify bin.path if you want to use a non-default path") ||
285
+ (stdout.include?("binary target") && stdout.include?("not found"))
286
+ end
287
+
223
288
  sig { params(message: String).returns(T::Boolean) }
224
289
  def using_old_toolchain?(message)
225
290
  return true if message.include?("usage of sparse registries requires `-Z sparse-registry`")
@@ -18,14 +18,6 @@ module Dependabot
18
18
  require_relative "file_updater/lockfile_updater"
19
19
  require_relative "file_updater/workspace_manifest_updater"
20
20
 
21
- sig { override.returns(T::Array[Regexp]) }
22
- def self.updated_files_regex
23
- [
24
- /Cargo\.toml$/, # Matches Cargo.toml in the root directory or any subdirectory
25
- /Cargo\.lock$/ # Matches Cargo.lock in the root directory or any subdirectory
26
- ]
27
- end
28
-
29
21
  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
30
22
  def updated_dependency_files
31
23
  # Returns an array of updated files. Only files that have been updated
@@ -118,6 +118,7 @@ module Dependabot
118
118
  end
119
119
 
120
120
  replace_req_on_target_specific_deps!(parsed_manifest, filename)
121
+ replace_req_on_workspace_deps!(parsed_manifest, filename)
121
122
 
122
123
  TomlRB.dump(parsed_manifest)
123
124
  end
@@ -148,6 +149,24 @@ module Dependabot
148
149
  end
149
150
  end
150
151
 
152
+ sig { params(parsed_manifest: T::Hash[String, T.untyped], filename: String).void }
153
+ def replace_req_on_workspace_deps!(parsed_manifest, filename)
154
+ workspace = parsed_manifest.fetch("workspace", {})
155
+ workspace_deps = workspace.fetch("dependencies", {})
156
+
157
+ workspace_deps.each do |name, req|
158
+ next unless dependency.name == name_from_declaration(name, req)
159
+
160
+ updated_req = temporary_requirement_for_resolution(filename)
161
+
162
+ if req.is_a?(Hash)
163
+ parsed_manifest["workspace"]["dependencies"][name]["version"] = updated_req
164
+ else
165
+ parsed_manifest["workspace"]["dependencies"][name] = updated_req
166
+ end
167
+ end
168
+ end
169
+
151
170
  sig { params(content: String).returns(String) }
152
171
  def replace_git_pin(content)
153
172
  parsed_manifest = TomlRB.parse(content)
@@ -238,75 +238,100 @@ module Dependabot
238
238
  raise Dependabot::DependencyFileNotResolvable, msg
239
239
  end
240
240
 
241
- # rubocop:disable Metrics/AbcSize
242
- # rubocop:disable Metrics/PerceivedComplexity
243
241
  sig { params(error: StandardError).void }
244
242
  def handle_cargo_errors(error)
245
- if error.message.include?("does not have these features")
243
+ return if missing_feature_error?(error)
244
+ return if recoverable_workspace_error?(error)
245
+
246
+ handle_git_authentication_errors(error)
247
+ handle_git_reference_errors(error)
248
+ handle_toolchain_errors(error)
249
+ handle_resolvability_errors(error)
250
+
251
+ raise
252
+ end
253
+
254
+ sig { params(error: StandardError).returns(T::Boolean) }
255
+ def missing_feature_error?(error)
256
+ if error.message.include?("does not have these features") ||
257
+ error.message.include?("does not have that feature")
246
258
  # TODO: Ideally we should update the declaration not to ask
247
259
  # for the specified features
248
- return
260
+ return true
249
261
  end
250
262
 
251
- if error.message.include?("authenticate when downloading repo") ||
252
- error.message.include?("fatal: Authentication failed for")
253
- # Check all dependencies for reachability (so that we raise a
254
- # consistent error)
255
- urls = unreachable_git_urls
256
-
257
- if T.must(urls).none?
258
- url = T.must(
259
- T.must(error.message.match(UNABLE_TO_UPDATE))
260
- .named_captures.fetch("url")
261
- ).split(/[#?]/).first
262
- raise if T.must(reachable_git_urls).include?(url)
263
-
264
- # Fix: Wrap url in T.must since split().first can return nil
265
- T.must(urls) << T.must(url)
266
- end
263
+ false
264
+ end
265
+
266
+ sig { params(error: StandardError).void }
267
+ def handle_git_authentication_errors(error)
268
+ return unless error.message.include?("authenticate when downloading repo") ||
269
+ error.message.include?("fatal: Authentication failed for")
270
+
271
+ urls = unreachable_git_urls
272
+
273
+ if T.must(urls).none?
274
+ url = T.must(
275
+ T.must(error.message.match(UNABLE_TO_UPDATE))
276
+ .named_captures.fetch("url")
277
+ ).split(/[#?]/).first
278
+ raise if T.must(reachable_git_urls).include?(url)
267
279
 
268
- raise Dependabot::GitDependenciesNotReachable, T.must(urls)
280
+ T.must(urls) << T.must(url)
269
281
  end
270
282
 
283
+ raise Dependabot::GitDependenciesNotReachable, T.must(urls)
284
+ end
285
+
286
+ sig { params(error: StandardError).void }
287
+ def handle_git_reference_errors(error)
271
288
  [BRANCH_NOT_FOUND_REGEX, REF_NOT_FOUND_REGEX, GIT_REF_NOT_FOUND_REGEX, NOT_OUR_REF_REGEX].each do |regex|
272
289
  next unless error.message.match?(regex)
273
290
 
274
291
  dependency_url = T.must(T.must(error.message.match(regex)).named_captures.fetch("url")).split(/[#?]/).first
275
- # Fix: Wrap dependency_url in T.must since split().first can return nil
276
292
  raise Dependabot::GitDependencyReferenceNotFound, T.must(dependency_url)
277
293
  end
294
+ end
278
295
 
296
+ sig { params(error: StandardError).returns(T::Boolean) }
297
+ def recoverable_workspace_error?(error)
279
298
  if workspace_native_library_update_error?(error.message)
280
299
  # This happens when we're updating one part of a workspace which
281
300
  # triggers an update of a subdependency that uses a native library,
282
301
  # whilst leaving another part of the workspace using an older
283
302
  # version. Ideally we would prevent the subdependency update.
284
- return nil
303
+ return true
285
304
  end
286
305
 
287
306
  if git_dependency? && error.message.include?("no matching package")
288
307
  # This happens when updating a git dependency whose version has
289
308
  # changed from a release to a pre-release version
290
- return nil
309
+ return true
291
310
  end
292
311
 
293
312
  if error.message.include?("all possible versions conflict")
294
313
  # This happens when a top-level requirement locks us to an old
295
314
  # patch release of a dependency that is a sub-dep of what we're
296
315
  # updating. It's (probably) a Cargo bug.
297
- return nil
316
+ return true
298
317
  end
299
318
 
300
- if using_old_toolchain?(error.message)
301
- raise Dependabot::DependencyFileNotEvaluatable, "Dependabot only supports toolchain 1.68 and up."
302
- end
319
+ false
320
+ end
303
321
 
304
- raise Dependabot::DependencyFileNotResolvable, error.message if resolvability_error?(error.message)
322
+ sig { params(error: StandardError).void }
323
+ def handle_toolchain_errors(error)
324
+ return unless using_old_toolchain?(error.message)
305
325
 
306
- raise
326
+ raise Dependabot::DependencyFileNotEvaluatable, "Dependabot only supports toolchain 1.68 and up."
327
+ end
328
+
329
+ sig { params(error: StandardError).void }
330
+ def handle_resolvability_errors(error)
331
+ return unless resolvability_error?(error.message)
332
+
333
+ raise Dependabot::DependencyFileNotResolvable, error.message
307
334
  end
308
- # rubocop:enable Metrics/AbcSize
309
- # rubocop:enable Metrics/PerceivedComplexity
310
335
 
311
336
  sig { params(message: T.nilable(String)).returns(T.any(Dependabot::Version, T::Boolean)) }
312
337
  def using_old_toolchain?(message)
@@ -360,20 +385,37 @@ module Dependabot
360
385
 
361
386
  sig { params(message: String).returns(T::Boolean) }
362
387
  def resolvability_error?(message)
363
- return true if message.include?("failed to parse lock")
364
- return true if message.include?("believes it's in a workspace")
365
- return true if message.include?("wasn't a root")
366
- return true if message.include?("requires a nightly version")
367
- return true if message.match?(/feature `[^\`]+` is required/)
368
- return true if message.include?("unexpected end of input while parsing major version number")
388
+ return true if common_resolvability_error?(message)
389
+ return true if binary_path_error?(message)
369
390
 
370
391
  original_requirements_resolvable = original_requirements_resolvable?
371
-
372
392
  return false if original_requirements_resolvable == :unknown
373
393
 
374
394
  !original_requirements_resolvable
375
395
  end
376
396
 
397
+ sig { params(message: String).returns(T::Boolean) }
398
+ def common_resolvability_error?(message)
399
+ message.include?("failed to parse lock") ||
400
+ message.include?("believes it's in a workspace") ||
401
+ message.include?("wasn't a root") ||
402
+ message.include?("requires a nightly version") ||
403
+ message.match?(/feature `[^\`]+` is required/) ||
404
+ message.include?("unexpected end of input while parsing major version number")
405
+ end
406
+
407
+ sig { params(message: String).returns(T::Boolean) }
408
+ def binary_path_error?(message)
409
+ message.match?(/couldn't find `[^`]+\.rs`/) ||
410
+ message.match?(/failed to find `[^`]+\.rs`/) ||
411
+ message.match?(/could not find `[^`]+\.rs`/) ||
412
+ message.match?(/cannot find binary `[^`]+`/) ||
413
+ message.match?(/binary target `[^`]+` not found/) ||
414
+ message.include?("Please specify bin.path if you want to use a non-default path") ||
415
+ message.include?("binary target") ||
416
+ message.include?("target not found")
417
+ end
418
+
377
419
  sig { returns(T.any(TrueClass, FalseClass, Symbol)) }
378
420
  def original_requirements_resolvable?
379
421
  base_directory = T.must(original_dependency_files.first).directory
@@ -421,9 +463,11 @@ module Dependabot
421
463
 
422
464
  T.must(manifest_files).each do |file|
423
465
  path = file.name
424
- dir = Pathname.new(path).dirname
425
- FileUtils.mkdir_p(dir)
426
- File.write(file.name, sanitized_manifest_content(T.must(file.content)))
466
+ # Convert absolute paths to relative paths to avoid permission errors
467
+ relative_path = path.start_with?("/") ? path[1..-1] || "." : path
468
+ dir = Pathname.new(relative_path).dirname
469
+ FileUtils.mkdir_p(dir) unless dir.to_s == "."
470
+ File.write(relative_path, sanitized_manifest_content(T.must(file.content)))
427
471
 
428
472
  next if virtual_manifest?(file)
429
473
 
@@ -261,12 +261,25 @@ module Dependabot
261
261
  latest_allowable_version: latest_version
262
262
  ).prepared_dependency_files
263
263
 
264
- VersionResolver.new(
264
+ result = VersionResolver.new(
265
265
  dependency: dependency,
266
266
  prepared_dependency_files: prepared_files,
267
267
  original_dependency_files: dependency_files,
268
268
  credentials: credentials
269
269
  ).latest_resolvable_version
270
+
271
+ # If the resolver returns a version higher than latest_version, cap it at latest_version
272
+ # This handles cases where the resolver might pick prereleases or incorrect versions
273
+ # Only apply this logic for semantic versions, not git SHAs
274
+ if result && latest_version &&
275
+ !git_dependency? &&
276
+ version_class.correct?(result.to_s) &&
277
+ version_class.correct?(latest_version.to_s) &&
278
+ version_class.new(result.to_s) > version_class.new(latest_version.to_s)
279
+ latest_version
280
+ else
281
+ result
282
+ end
270
283
  end
271
284
 
272
285
  sig { returns(T.nilable(T.any(String, Gem::Version))) }
@@ -43,6 +43,122 @@ module Dependabot
43
43
 
44
44
  version.to_s.match?(ANCHORED_VERSION_PATTERN)
45
45
  end
46
+
47
+ # Cargo uses a different semantic versioning approach for pre-1.0 versions:
48
+ # - For 0.y.z versions: changes in y are considered major/breaking
49
+ # - For 0.0.z versions: changes in z are considered major/breaking
50
+ # - Only the leftmost non-zero component is considered for compatibility
51
+
52
+ sig { override.returns(T::Array[String]) }
53
+ def ignored_patch_versions
54
+ parts = to_s.split(".")
55
+ major = parts[0].to_i
56
+ minor = parts[1].to_i
57
+
58
+ # For 0.0.z versions, patch changes are breaking, so treat as major
59
+ return ignored_major_versions if major.zero? && minor.zero?
60
+
61
+ # For 0.y.z versions, patch changes are compatible, use standard logic
62
+ return super if major.zero?
63
+
64
+ # For 1.y.z+ versions, use standard semantic versioning
65
+ super
66
+ end
67
+
68
+ sig { override.returns(T::Array[String]) }
69
+ def ignored_minor_versions
70
+ parts = to_s.split(".")
71
+ major = parts[0].to_i
72
+
73
+ # For 0.y.z versions, minor changes are breaking, so treat as major
74
+ return ignored_major_versions if major.zero?
75
+
76
+ # For 1.y.z+ versions, use standard semantic versioning
77
+ super
78
+ end
79
+
80
+ # Determines the correct update type for a version change according to Cargo's semantic versioning rules
81
+ # For pre-1.0 versions, Cargo treats changes in the leftmost non-zero component as breaking
82
+ sig { params(from_version: T.any(String, Dependabot::Cargo::Version), to_version: T.any(String, Dependabot::Cargo::Version)).returns(String) }
83
+ def self.update_type(from_version, to_version)
84
+ from_v, to_v = normalize_versions(from_version, to_version)
85
+ from_major, from_minor, from_patch, to_major, to_minor, to_patch = extract_version_parts(from_v, to_v)
86
+
87
+ # Standard semver for 1.0.0+ versions
88
+ return standard_semver_type(from_major, from_minor, from_patch, to_major, to_minor, to_patch) if from_major >= 1
89
+
90
+ # Cargo pre-1.0 semver rules
91
+ cargo_pre_1_0_type(from_major, from_minor, from_patch, to_major, to_minor, to_patch)
92
+ rescue StandardError => e
93
+ # Log the error but return a safe default
94
+ Dependabot.logger.warn("Error in Cargo::Version.update_type: #{e.message}")
95
+ "major" # Default to major for safety
96
+ end
97
+
98
+ sig do
99
+ params(
100
+ from_version: T.any(String, Dependabot::Cargo::Version),
101
+ to_version: T.any(String, Dependabot::Cargo::Version)
102
+ ).returns([Dependabot::Cargo::Version, Dependabot::Cargo::Version])
103
+ end
104
+ def self.normalize_versions(from_version, to_version)
105
+ from_v = from_version.is_a?(String) ? T.cast(new(from_version), Dependabot::Cargo::Version) : from_version
106
+ to_v = to_version.is_a?(String) ? T.cast(new(to_version), Dependabot::Cargo::Version) : to_version
107
+ [from_v, to_v]
108
+ end
109
+
110
+ sig { params(from_v: Dependabot::Cargo::Version, to_v: Dependabot::Cargo::Version).returns([Integer, Integer, Integer, Integer, Integer, Integer]) }
111
+ def self.extract_version_parts(from_v, to_v)
112
+ from_parts = from_v.to_s.split(".").map(&:to_i)
113
+ to_parts = to_v.to_s.split(".").map(&:to_i)
114
+
115
+ [from_parts[0] || 0, from_parts[1] || 0, from_parts[2] || 0,
116
+ to_parts[0] || 0, to_parts[1] || 0, to_parts[2] || 0]
117
+ end
118
+
119
+ # rubocop:disable Metrics/ParameterLists
120
+ sig do
121
+ params(
122
+ from_major: Integer,
123
+ from_minor: Integer,
124
+ from_patch: Integer,
125
+ to_major: Integer,
126
+ to_minor: Integer,
127
+ to_patch: Integer
128
+ ).returns(String)
129
+ end
130
+ def self.standard_semver_type(from_major, from_minor, from_patch, to_major, to_minor, to_patch)
131
+ return "major" if to_major > from_major
132
+ return "minor" if to_minor > from_minor
133
+ return "patch" if to_patch > from_patch
134
+
135
+ "patch"
136
+ end
137
+
138
+ sig do
139
+ params(
140
+ from_major: Integer,
141
+ from_minor: Integer,
142
+ from_patch: Integer,
143
+ to_major: Integer,
144
+ to_minor: Integer,
145
+ to_patch: Integer
146
+ ).returns(String)
147
+ end
148
+ def self.cargo_pre_1_0_type(from_major, from_minor, from_patch, to_major, to_minor, to_patch)
149
+ # Any major version increase is always major
150
+ return "major" if to_major > from_major
151
+
152
+ # For 0.0.z versions, any change is breaking
153
+ return "major" if from_minor.zero? && (to_minor > from_minor || to_patch > from_patch)
154
+
155
+ # For 0.y.z versions, minor changes are breaking
156
+ return "major" if to_minor > from_minor
157
+
158
+ # Only patch changes remain
159
+ "patch"
160
+ end
161
+ # rubocop:enable Metrics/ParameterLists
46
162
  end
47
163
  end
48
164
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-cargo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.340.0
4
+ version: 0.342.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.340.0
18
+ version: 0.342.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.340.0
25
+ version: 0.342.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -266,7 +266,7 @@ licenses:
266
266
  - MIT
267
267
  metadata:
268
268
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
269
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.340.0
269
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.342.1
270
270
  rdoc_options: []
271
271
  require_paths:
272
272
  - lib