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