dependabot-uv 0.332.0 → 0.334.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 +4 -4
- data/lib/dependabot/uv/authed_url_builder.rb +8 -3
- data/lib/dependabot/uv/file_fetcher.rb +16 -2
- data/lib/dependabot/uv/file_parser/pyproject_files_parser.rb +1 -0
- data/lib/dependabot/uv/file_parser/python_requirement_parser.rb +39 -16
- data/lib/dependabot/uv/file_parser/setup_file_parser.rb +1 -0
- data/lib/dependabot/uv/file_updater/compile_file_updater.rb +149 -70
- data/lib/dependabot/uv/file_updater/lock_file_updater.rb +3 -2
- data/lib/dependabot/uv/file_updater/requirement_file_updater.rb +8 -8
- data/lib/dependabot/uv/file_updater/requirement_replacer.rb +61 -24
- data/lib/dependabot/uv/file_updater.rb +2 -2
- data/lib/dependabot/uv/language.rb +1 -0
- data/lib/dependabot/uv/metadata_finder.rb +41 -10
- data/lib/dependabot/uv/package/package_registry_finder.rb +116 -61
- data/lib/dependabot/uv/requirement.rb +28 -19
- data/lib/dependabot/uv/update_checker/lock_file_resolver.rb +26 -2
- data/lib/dependabot/uv/update_checker/pip_compile_version_resolver.rb +133 -54
- data/lib/dependabot/uv/update_checker/pip_version_resolver.rb +58 -22
- data/lib/dependabot/uv/update_checker/requirements_updater.rb +79 -31
- data/lib/dependabot/uv/update_checker.rb +120 -36
- data/lib/dependabot/uv/version.rb +22 -14
- metadata +6 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
@@ -12,22 +12,23 @@ module Dependabot
|
|
|
12
12
|
class Requirement < Dependabot::Requirement
|
|
13
13
|
extend T::Sig
|
|
14
14
|
|
|
15
|
-
OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s
|
|
15
|
+
OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9)*])\s*\|+/, Regexp)
|
|
16
16
|
|
|
17
17
|
# Add equality and arbitrary-equality matchers
|
|
18
|
-
OPS = OPS.merge(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
OPS = T.let(OPS.merge(
|
|
19
|
+
"==" => ->(v, r) { v == r },
|
|
20
|
+
"===" => ->(v, r) { v.to_s == r.to_s }
|
|
21
|
+
), T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)])
|
|
22
22
|
|
|
23
23
|
quoted = OPS.keys.sort_by(&:length).reverse
|
|
24
24
|
.map { |k| Regexp.quote(k) }.join("|")
|
|
25
25
|
version_pattern = Uv::Version::VERSION_PATTERN
|
|
26
26
|
|
|
27
|
-
PATTERN_RAW = "\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze
|
|
28
|
-
PATTERN = /\A#{PATTERN_RAW}\z
|
|
29
|
-
PARENS_PATTERN = /\A\(([^)]+)\)\z
|
|
27
|
+
PATTERN_RAW = T.let("\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze, String)
|
|
28
|
+
PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
|
|
29
|
+
PARENS_PATTERN = T.let(/\A\(([^)]+)\)\z/, Regexp)
|
|
30
30
|
|
|
31
|
+
sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
|
|
31
32
|
def self.parse(obj)
|
|
32
33
|
return ["=", Uv::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
|
33
34
|
|
|
@@ -63,6 +64,7 @@ module Dependabot
|
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
|
|
67
|
+
sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
|
|
66
68
|
def initialize(*requirements)
|
|
67
69
|
requirements = requirements.flatten.flat_map do |req_string|
|
|
68
70
|
next if req_string.nil?
|
|
@@ -78,20 +80,23 @@ module Dependabot
|
|
|
78
80
|
super(requirements)
|
|
79
81
|
end
|
|
80
82
|
|
|
83
|
+
sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
|
|
81
84
|
def satisfied_by?(version)
|
|
82
85
|
version = Uv::Version.new(version.to_s)
|
|
83
86
|
|
|
84
|
-
requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) }
|
|
87
|
+
requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
|
|
85
88
|
end
|
|
86
89
|
|
|
90
|
+
sig { returns(T::Boolean) }
|
|
87
91
|
def exact?
|
|
88
|
-
return false unless
|
|
92
|
+
return false unless requirements.size == 1
|
|
89
93
|
|
|
90
|
-
%w(= == ===).include?(
|
|
94
|
+
%w(= == ===).include?(requirements[0][0])
|
|
91
95
|
end
|
|
92
96
|
|
|
93
97
|
private
|
|
94
98
|
|
|
99
|
+
sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
|
|
95
100
|
def convert_python_constraint_to_ruby_constraint(req_string)
|
|
96
101
|
return nil if req_string.nil? || req_string.strip.empty?
|
|
97
102
|
return nil if req_string == "*"
|
|
@@ -111,6 +116,7 @@ module Dependabot
|
|
|
111
116
|
|
|
112
117
|
# Poetry uses ~ requirements.
|
|
113
118
|
# https://github.com/sdispater/poetry#tilde-requirements
|
|
119
|
+
sig { params(req_string: String).returns(String) }
|
|
114
120
|
def convert_tilde_req(req_string)
|
|
115
121
|
version = req_string.gsub(/^~\>?/, "")
|
|
116
122
|
parts = version.split(".")
|
|
@@ -120,44 +126,47 @@ module Dependabot
|
|
|
120
126
|
|
|
121
127
|
# Poetry uses ^ requirements
|
|
122
128
|
# https://github.com/sdispater/poetry#caret-requirement
|
|
129
|
+
sig { params(req_string: String).returns(T::Array[String]) }
|
|
123
130
|
def convert_caret_req(req_string)
|
|
124
131
|
version = req_string.gsub(/^\^/, "")
|
|
125
132
|
parts = version.split(".")
|
|
126
|
-
parts.fill(0, parts.length...3)
|
|
133
|
+
parts.fill("0", parts.length...3)
|
|
127
134
|
first_non_zero = parts.find { |d| d != "0" }
|
|
128
135
|
first_non_zero_index =
|
|
129
136
|
first_non_zero ? parts.index(first_non_zero) : parts.count - 1
|
|
130
137
|
upper_bound = parts.map.with_index do |part, i|
|
|
131
|
-
if i < first_non_zero_index then part
|
|
138
|
+
if i < T.must(first_non_zero_index) then part
|
|
132
139
|
elsif i == first_non_zero_index then (part.to_i + 1).to_s
|
|
133
140
|
# .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
|
|
134
|
-
elsif i > first_non_zero_index && i == 2 then "0.dev"
|
|
141
|
+
elsif i > T.must(first_non_zero_index) && i == 2 then "0.dev"
|
|
135
142
|
else
|
|
136
|
-
0
|
|
143
|
+
"0"
|
|
137
144
|
end
|
|
138
145
|
end.join(".")
|
|
139
146
|
|
|
140
147
|
[">= #{version}", "< #{upper_bound}"]
|
|
141
148
|
end
|
|
142
149
|
|
|
150
|
+
sig { params(req_string: String).returns(String) }
|
|
143
151
|
def convert_wildcard(req_string)
|
|
144
152
|
# NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
|
|
145
153
|
# "!= 1.0.0". There's no way to model this correctly in Ruby :'(
|
|
146
154
|
quoted_ops = OPS.keys.sort_by(&:length).reverse
|
|
147
155
|
.map { |k| Regexp.quote(k) }.join("|")
|
|
148
|
-
|
|
149
|
-
|
|
156
|
+
op_match = req_string.match(/\A\s*(#{quoted_ops})?/)
|
|
157
|
+
op = op_match&.captures&.first.to_s.strip
|
|
150
158
|
exact_op = ["", "=", "==", "==="].include?(op)
|
|
151
159
|
|
|
152
160
|
req_string.strip
|
|
153
161
|
.split(".")
|
|
154
|
-
.first(req_string.split(".").index { |s| s.include?("*") } + 1)
|
|
162
|
+
.first(T.must(req_string.split(".").index { |s| s.include?("*") }) + 1)
|
|
155
163
|
.join(".")
|
|
156
164
|
.gsub(/\*(?!$)/, "0")
|
|
157
165
|
.gsub(/\*$/, "0.dev")
|
|
158
166
|
.tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
|
|
159
167
|
end
|
|
160
168
|
|
|
169
|
+
sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
|
|
161
170
|
def convert_exact(req_string)
|
|
162
171
|
arbitrary_equality = req_string.start_with?("===")
|
|
163
172
|
cleaned_version = req_string.gsub(/^=+/, "").strip
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
4
6
|
require "dependabot/uv/version"
|
|
5
7
|
require "dependabot/uv/requirement"
|
|
6
8
|
require "dependabot/uv/update_checker"
|
|
@@ -9,6 +11,16 @@ module Dependabot
|
|
|
9
11
|
module Uv
|
|
10
12
|
class UpdateChecker
|
|
11
13
|
class LockFileResolver
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
sig do
|
|
17
|
+
params(
|
|
18
|
+
dependency: Dependabot::Dependency,
|
|
19
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
20
|
+
credentials: T::Array[Dependabot::Credential],
|
|
21
|
+
repo_contents_path: T.nilable(String)
|
|
22
|
+
).void
|
|
23
|
+
end
|
|
12
24
|
def initialize(dependency:, dependency_files:, credentials:, repo_contents_path: nil)
|
|
13
25
|
@dependency = dependency
|
|
14
26
|
@dependency_files = dependency_files
|
|
@@ -16,6 +28,7 @@ module Dependabot
|
|
|
16
28
|
@repo_contents_path = repo_contents_path
|
|
17
29
|
end
|
|
18
30
|
|
|
31
|
+
sig { params(requirement: T.nilable(String)).returns(T.nilable(Dependabot::Uv::Version)) }
|
|
19
32
|
def latest_resolvable_version(requirement:)
|
|
20
33
|
return nil unless requirement
|
|
21
34
|
|
|
@@ -28,19 +41,30 @@ module Dependabot
|
|
|
28
41
|
nil
|
|
29
42
|
end
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
sig { params(_version: T.untyped).returns(T::Boolean) }
|
|
45
|
+
def resolvable?(_version)
|
|
46
|
+
# Always return true since we don't actually attempt resolution
|
|
47
|
+
# This is just a placeholder implementation
|
|
32
48
|
true
|
|
33
49
|
end
|
|
34
50
|
|
|
51
|
+
sig { returns(T.nilable(Dependabot::Uv::Version)) }
|
|
35
52
|
def lowest_resolvable_security_fix_version
|
|
36
53
|
nil
|
|
37
54
|
end
|
|
38
55
|
|
|
39
56
|
private
|
|
40
57
|
|
|
58
|
+
sig { returns(Dependabot::Dependency) }
|
|
41
59
|
attr_reader :dependency
|
|
60
|
+
|
|
61
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
42
62
|
attr_reader :dependency_files
|
|
63
|
+
|
|
64
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
43
65
|
attr_reader :credentials
|
|
66
|
+
|
|
67
|
+
sig { returns(T.nilable(String)) }
|
|
44
68
|
attr_reader :repo_contents_path
|
|
45
69
|
end
|
|
46
70
|
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "open3"
|
|
5
|
+
require "sorbet-runtime"
|
|
6
|
+
|
|
5
7
|
require "dependabot/dependency"
|
|
6
8
|
require "dependabot/uv/requirement_parser"
|
|
7
9
|
require "dependabot/uv/file_fetcher"
|
|
@@ -22,34 +24,60 @@ module Dependabot
|
|
|
22
24
|
# This class does version resolution for pip-compile. Its approach is:
|
|
23
25
|
# - Unlock the dependency we're checking in the requirements.in file
|
|
24
26
|
# - Run `pip-compile` and see what the result is
|
|
27
|
+
# rubocop:disable Metrics/ClassLength
|
|
25
28
|
class PipCompileVersionResolver
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
extend T::Sig
|
|
30
|
+
|
|
31
|
+
GIT_DEPENDENCY_UNREACHABLE_REGEX = T.let(/git clone --filter=blob:none --quiet (?<url>[^\s]+).* /, Regexp)
|
|
32
|
+
GIT_REFERENCE_NOT_FOUND_REGEX = T.let(/Did not find branch or tag '(?<tag>[^\n"]+)'/m, Regexp)
|
|
33
|
+
NATIVE_COMPILATION_ERROR = T.let(
|
|
34
|
+
"pip._internal.exceptions.InstallationSubprocessError: Getting requirements to build wheel exited with 1",
|
|
35
|
+
String
|
|
36
|
+
)
|
|
30
37
|
# See https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata
|
|
31
|
-
PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]
|
|
32
|
-
RESOLUTION_IMPOSSIBLE_ERROR = "ResolutionImpossible"
|
|
33
|
-
ERROR_REGEX = /(?<=ERROR\:\W)
|
|
34
|
-
UV_UNRESOLVABLE_REGEX = / × No solution found when resolving dependencies:[\s\S]
|
|
38
|
+
PYTHON_PACKAGE_NAME_REGEX = T.let(/[A-Za-z0-9_\-]+/, Regexp)
|
|
39
|
+
RESOLUTION_IMPOSSIBLE_ERROR = T.let("ResolutionImpossible", String)
|
|
40
|
+
ERROR_REGEX = T.let(/(?<=ERROR\:\W).*$/, Regexp)
|
|
41
|
+
UV_UNRESOLVABLE_REGEX = T.let(/ × No solution found when resolving dependencies:[\s\S]*$/, Regexp)
|
|
35
42
|
|
|
43
|
+
sig { returns(Dependabot::Dependency) }
|
|
36
44
|
attr_reader :dependency
|
|
45
|
+
|
|
46
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
37
47
|
attr_reader :dependency_files
|
|
48
|
+
|
|
49
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
38
50
|
attr_reader :credentials
|
|
51
|
+
|
|
52
|
+
sig { returns(T.nilable(String)) }
|
|
39
53
|
attr_reader :repo_contents_path
|
|
54
|
+
|
|
55
|
+
sig { returns(PipCompileErrorHandler) }
|
|
40
56
|
attr_reader :error_handler
|
|
41
57
|
|
|
58
|
+
sig do
|
|
59
|
+
params(
|
|
60
|
+
dependency: Dependabot::Dependency,
|
|
61
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
62
|
+
credentials: T::Array[Dependabot::Credential],
|
|
63
|
+
repo_contents_path: T.nilable(String)
|
|
64
|
+
).void
|
|
65
|
+
end
|
|
42
66
|
def initialize(dependency:, dependency_files:, credentials:, repo_contents_path:)
|
|
43
|
-
@dependency = dependency
|
|
44
|
-
@dependency_files = dependency_files
|
|
45
|
-
@credentials = credentials
|
|
46
|
-
@repo_contents_path = repo_contents_path
|
|
47
|
-
@build_isolation = true
|
|
48
|
-
@error_handler = PipCompileErrorHandler.new
|
|
67
|
+
@dependency = T.let(dependency, Dependabot::Dependency)
|
|
68
|
+
@dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
|
|
69
|
+
@credentials = T.let(credentials, T::Array[Dependabot::Credential])
|
|
70
|
+
@repo_contents_path = T.let(repo_contents_path, T.nilable(String))
|
|
71
|
+
@build_isolation = T.let(true, T::Boolean)
|
|
72
|
+
@error_handler = T.let(PipCompileErrorHandler.new, PipCompileErrorHandler)
|
|
49
73
|
end
|
|
50
74
|
|
|
75
|
+
sig { params(requirement: T.nilable(String)).returns(T.nilable(Dependabot::Uv::Version)) }
|
|
51
76
|
def latest_resolvable_version(requirement: nil)
|
|
52
|
-
@latest_resolvable_version_string ||=
|
|
77
|
+
@latest_resolvable_version_string ||= T.let(
|
|
78
|
+
{},
|
|
79
|
+
T.nilable(T::Hash[T.nilable(String), T.nilable(Dependabot::Uv::Version)])
|
|
80
|
+
)
|
|
53
81
|
return @latest_resolvable_version_string[requirement] if @latest_resolvable_version_string.key?(requirement)
|
|
54
82
|
|
|
55
83
|
version_string =
|
|
@@ -59,9 +87,10 @@ module Dependabot
|
|
|
59
87
|
version_string.nil? ? nil : Uv::Version.new(version_string)
|
|
60
88
|
end
|
|
61
89
|
|
|
90
|
+
sig { params(version: Gem::Version).returns(T::Boolean) }
|
|
62
91
|
def resolvable?(version:)
|
|
63
|
-
@resolvable ||= {}
|
|
64
|
-
return @resolvable[version] if @resolvable.key?(version)
|
|
92
|
+
@resolvable ||= T.let({}, T.nilable(T::Hash[Gem::Version, T::Boolean]))
|
|
93
|
+
return T.must(@resolvable[version]) if @resolvable.key?(version)
|
|
65
94
|
|
|
66
95
|
@resolvable[version] = if latest_resolvable_version(requirement: "==#{version}")
|
|
67
96
|
true
|
|
@@ -72,6 +101,7 @@ module Dependabot
|
|
|
72
101
|
|
|
73
102
|
private
|
|
74
103
|
|
|
104
|
+
sig { params(requirement: T.nilable(String)).returns(T.nilable(String)) }
|
|
75
105
|
def fetch_latest_resolvable_version_string(requirement:)
|
|
76
106
|
SharedHelpers.in_a_temporary_directory do
|
|
77
107
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
|
@@ -90,6 +120,7 @@ module Dependabot
|
|
|
90
120
|
end
|
|
91
121
|
end
|
|
92
122
|
|
|
123
|
+
sig { params(filename: String).returns(T::Boolean) }
|
|
93
124
|
def compile_file(filename)
|
|
94
125
|
# Shell out to pip-compile.
|
|
95
126
|
# This is slow, as pip-compile needs to do installs.
|
|
@@ -126,17 +157,24 @@ module Dependabot
|
|
|
126
157
|
end
|
|
127
158
|
|
|
128
159
|
handle_pip_compile_errors(e.message)
|
|
160
|
+
false
|
|
129
161
|
end
|
|
130
162
|
|
|
163
|
+
sig { params(error: Dependabot::SharedHelpers::HelperSubprocessFailed).returns(T::Boolean) }
|
|
131
164
|
def compilation_error?(error)
|
|
132
165
|
error.message.include?(NATIVE_COMPILATION_ERROR)
|
|
133
166
|
end
|
|
134
167
|
|
|
135
168
|
# rubocop:disable Metrics/AbcSize
|
|
136
169
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
170
|
+
sig { params(message: String).returns(T.nilable(String)) }
|
|
137
171
|
def handle_pip_compile_errors(message)
|
|
138
172
|
if message.include?("No solution found when resolving dependencies")
|
|
139
|
-
|
|
173
|
+
match_result = message.scan(UV_UNRESOLVABLE_REGEX).last
|
|
174
|
+
if match_result
|
|
175
|
+
error_message = match_result.is_a?(Array) ? match_result.join : match_result
|
|
176
|
+
raise DependencyFileNotResolvable, error_message
|
|
177
|
+
end
|
|
140
178
|
end
|
|
141
179
|
|
|
142
180
|
check_original_requirements_resolvable if message.include?(RESOLUTION_IMPOSSIBLE_ERROR)
|
|
@@ -153,21 +191,23 @@ module Dependabot
|
|
|
153
191
|
end
|
|
154
192
|
|
|
155
193
|
if message.match?(GIT_REFERENCE_NOT_FOUND_REGEX)
|
|
156
|
-
tag = message.match(GIT_REFERENCE_NOT_FOUND_REGEX).named_captures.fetch("tag")
|
|
157
|
-
constraints_section = message.split("Finding the best candidates:").first
|
|
194
|
+
tag = T.must(T.must(message.match(GIT_REFERENCE_NOT_FOUND_REGEX)).named_captures.fetch("tag"))
|
|
195
|
+
constraints_section = T.must(message.split("Finding the best candidates:").first)
|
|
158
196
|
egg_regex = /#{Regexp.escape(tag)}#egg=(#{PYTHON_PACKAGE_NAME_REGEX})/
|
|
159
197
|
name_match = constraints_section.scan(egg_regex)
|
|
160
198
|
|
|
161
199
|
# We can determine the name of the package from another part of the logger output if it has a unique tag
|
|
162
|
-
|
|
200
|
+
if name_match.length == 1 && name_match.first.is_a?(Array)
|
|
201
|
+
raise GitDependencyReferenceNotFound, T.must(T.cast(T.must(name_match.first), T::Array[String]).first)
|
|
202
|
+
end
|
|
163
203
|
|
|
164
204
|
raise GitDependencyReferenceNotFound, "(unknown package at #{tag})"
|
|
165
205
|
end
|
|
166
206
|
|
|
167
207
|
if message.match?(GIT_DEPENDENCY_UNREACHABLE_REGEX)
|
|
168
|
-
url = message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX)
|
|
169
|
-
|
|
170
|
-
raise GitDependenciesNotReachable, url
|
|
208
|
+
url = T.must(message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX))
|
|
209
|
+
.named_captures.fetch("url")
|
|
210
|
+
raise GitDependenciesNotReachable, T.must(url)
|
|
171
211
|
end
|
|
172
212
|
|
|
173
213
|
raise Dependabot::OutOfDisk if message.end_with?("[Errno 28] No space left on device")
|
|
@@ -185,6 +225,7 @@ module Dependabot
|
|
|
185
225
|
# Note: We raise errors from this method, rather than returning a
|
|
186
226
|
# boolean, so that all deps for this repo will raise identical
|
|
187
227
|
# errors when failing to update
|
|
228
|
+
sig { returns(T::Boolean) }
|
|
188
229
|
def check_original_requirements_resolvable
|
|
189
230
|
SharedHelpers.in_a_temporary_directory do
|
|
190
231
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
|
@@ -216,12 +257,14 @@ module Dependabot
|
|
|
216
257
|
end
|
|
217
258
|
end
|
|
218
259
|
|
|
219
|
-
|
|
260
|
+
sig { params(command: String, fingerprint: String, env: T::Hash[String, String]).void }
|
|
261
|
+
def run_command(command, fingerprint:, env: python_env)
|
|
220
262
|
SharedHelpers.run_shell_command(command, env: env, fingerprint: fingerprint, stderr_to_stdout: true)
|
|
221
263
|
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
222
264
|
handle_pip_compile_errors(e.message)
|
|
223
265
|
end
|
|
224
266
|
|
|
267
|
+
sig { params(options: String).returns(String) }
|
|
225
268
|
def pip_compile_options_fingerprint(options)
|
|
226
269
|
options.sub(
|
|
227
270
|
/--output-file=\S+/, "--output-file=<output_file>"
|
|
@@ -232,6 +275,7 @@ module Dependabot
|
|
|
232
275
|
)
|
|
233
276
|
end
|
|
234
277
|
|
|
278
|
+
sig { params(filename: String).returns(String) }
|
|
235
279
|
def pip_compile_options(filename)
|
|
236
280
|
options = @build_isolation ? ["--build-isolation"] : ["--no-build-isolation"]
|
|
237
281
|
options += pip_compile_index_options
|
|
@@ -247,6 +291,7 @@ module Dependabot
|
|
|
247
291
|
options.join(" ")
|
|
248
292
|
end
|
|
249
293
|
|
|
294
|
+
sig { returns(T::Array[String]) }
|
|
250
295
|
def pip_compile_index_options
|
|
251
296
|
credentials
|
|
252
297
|
.select { |cred| cred["type"] == "python_index" }
|
|
@@ -261,6 +306,7 @@ module Dependabot
|
|
|
261
306
|
end
|
|
262
307
|
end
|
|
263
308
|
|
|
309
|
+
sig { params(command: String, fingerprint: String).void }
|
|
264
310
|
def run_pip_compile_command(command, fingerprint:)
|
|
265
311
|
run_command(
|
|
266
312
|
"pyenv local #{language_version_manager.python_major_minor}",
|
|
@@ -270,38 +316,43 @@ module Dependabot
|
|
|
270
316
|
run_command(command, fingerprint: fingerprint)
|
|
271
317
|
end
|
|
272
318
|
|
|
319
|
+
# rubocop:disable Metrics/AbcSize
|
|
320
|
+
sig { params(requirements_file: Dependabot::DependencyFile).returns(T::Array[String]) }
|
|
273
321
|
def uv_pip_compile_options_from_compiled_file(requirements_file)
|
|
274
322
|
options = []
|
|
275
323
|
|
|
276
|
-
options << "--no-emit-index-url" unless requirements_file.content.include?("index-url http")
|
|
324
|
+
options << "--no-emit-index-url" unless T.must(requirements_file.content).include?("index-url http")
|
|
277
325
|
|
|
278
|
-
options << "--generate-hashes" if requirements_file.content.include?("--hash=sha")
|
|
326
|
+
options << "--generate-hashes" if T.must(requirements_file.content).include?("--hash=sha")
|
|
279
327
|
|
|
280
|
-
options << "--no-annotate" unless requirements_file.content.include?("# via ")
|
|
328
|
+
options << "--no-annotate" unless T.must(requirements_file.content).include?("# via ")
|
|
281
329
|
|
|
282
|
-
options << "--pre" if requirements_file.content.include?("--pre")
|
|
330
|
+
options << "--pre" if T.must(requirements_file.content).include?("--pre")
|
|
283
331
|
|
|
284
|
-
options << "--no-strip-extras" if requirements_file.content.include?("--no-strip-extras")
|
|
332
|
+
options << "--no-strip-extras" if T.must(requirements_file.content).include?("--no-strip-extras")
|
|
285
333
|
|
|
286
|
-
if requirements_file.content.include?("--no-binary") ||
|
|
334
|
+
if T.must(requirements_file.content).include?("--no-binary") ||
|
|
335
|
+
T.must(requirements_file.content).include?("--only-binary")
|
|
287
336
|
options << "--emit-build-options"
|
|
288
337
|
end
|
|
289
338
|
|
|
290
|
-
if (resolver = FileUpdater::CompileFileUpdater::RESOLVER_REGEX.match(requirements_file.content))
|
|
339
|
+
if (resolver = FileUpdater::CompileFileUpdater::RESOLVER_REGEX.match(T.must(requirements_file.content)))
|
|
291
340
|
options << "--resolver=#{resolver}"
|
|
292
341
|
end
|
|
293
342
|
|
|
294
|
-
options << "--universal" if requirements_file.content.include?("--universal")
|
|
343
|
+
options << "--universal" if T.must(requirements_file.content).include?("--universal")
|
|
295
344
|
|
|
296
345
|
options
|
|
297
346
|
end
|
|
347
|
+
# rubocop:enable Metrics/AbcSize
|
|
298
348
|
|
|
349
|
+
sig { returns(T::Hash[String, String]) }
|
|
299
350
|
def python_env
|
|
300
351
|
env = {}
|
|
301
352
|
|
|
302
353
|
# Handle Apache Airflow 1.10.x installs
|
|
303
|
-
if dependency_files.any? { |f| f.content.include?("apache-airflow") }
|
|
304
|
-
if dependency_files.any? { |f| f.content.include?("unidecode") }
|
|
354
|
+
if dependency_files.any? { |f| T.must(f.content).include?("apache-airflow") }
|
|
355
|
+
if dependency_files.any? { |f| T.must(f.content).include?("unidecode") }
|
|
305
356
|
env["AIRFLOW_GPL_UNIDECODE"] = "yes"
|
|
306
357
|
else
|
|
307
358
|
env["SLUGIFY_USES_TEXT_UNIDECODE"] = "yes"
|
|
@@ -311,6 +362,9 @@ module Dependabot
|
|
|
311
362
|
env
|
|
312
363
|
end
|
|
313
364
|
|
|
365
|
+
sig do
|
|
366
|
+
params(updated_req: T.nilable(String), update_requirement: T::Boolean).void
|
|
367
|
+
end
|
|
314
368
|
def write_temporary_dependency_files(updated_req: nil,
|
|
315
369
|
update_requirement: true)
|
|
316
370
|
dependency_files.each do |file|
|
|
@@ -328,6 +382,7 @@ module Dependabot
|
|
|
328
382
|
File.write(".python-version", language_version_manager.python_major_minor)
|
|
329
383
|
end
|
|
330
384
|
|
|
385
|
+
sig { void }
|
|
331
386
|
def write_original_manifest_files
|
|
332
387
|
pip_compile_files.each do |file|
|
|
333
388
|
FileUtils.mkdir_p(Pathname.new(file.name).dirname)
|
|
@@ -335,29 +390,33 @@ module Dependabot
|
|
|
335
390
|
end
|
|
336
391
|
end
|
|
337
392
|
|
|
393
|
+
sig { params(file: Dependabot::DependencyFile, updated_req: T.nilable(String)).returns(String) }
|
|
338
394
|
def update_req_file(file, updated_req)
|
|
339
|
-
return file.content unless file.name.end_with?(".in")
|
|
395
|
+
return T.must(file.content) unless file.name.end_with?(".in")
|
|
340
396
|
|
|
341
397
|
req = dependency.requirements.find { |r| r[:file] == file.name }
|
|
342
398
|
|
|
343
|
-
return file.content + "\n#{dependency.name} #{updated_req}" unless req&.fetch(:requirement)
|
|
399
|
+
return T.must(file.content) + "\n#{dependency.name} #{updated_req}" unless req&.fetch(:requirement)
|
|
344
400
|
|
|
345
401
|
Uv::FileUpdater::RequirementReplacer.new(
|
|
346
|
-
content: file.content,
|
|
402
|
+
content: T.must(file.content),
|
|
347
403
|
dependency_name: dependency.name,
|
|
348
404
|
old_requirement: req[:requirement],
|
|
349
405
|
new_requirement: updated_req
|
|
350
406
|
).updated_content
|
|
351
407
|
end
|
|
352
408
|
|
|
409
|
+
sig { params(name: String).returns(String) }
|
|
353
410
|
def normalise(name)
|
|
354
411
|
NameNormaliser.normalise(name)
|
|
355
412
|
end
|
|
356
413
|
|
|
414
|
+
sig { params(message: String).returns(String) }
|
|
357
415
|
def clean_error_message(message)
|
|
358
|
-
message.scan(ERROR_REGEX).last
|
|
416
|
+
T.must(T.cast(message.scan(ERROR_REGEX), T::Array[String]).last)
|
|
359
417
|
end
|
|
360
418
|
|
|
419
|
+
sig { returns(T::Array[String]) }
|
|
361
420
|
def filenames_to_compile
|
|
362
421
|
files_from_reqs =
|
|
363
422
|
dependency.requirements
|
|
@@ -375,10 +434,11 @@ module Dependabot
|
|
|
375
434
|
order_filenames_for_compilation(filenames)
|
|
376
435
|
end
|
|
377
436
|
|
|
437
|
+
sig { params(filename: String).returns(T.nilable(Dependabot::DependencyFile)) }
|
|
378
438
|
def compiled_file_for_filename(filename)
|
|
379
439
|
compiled_file =
|
|
380
440
|
compiled_files
|
|
381
|
-
.find { |f| f.content.match?(output_file_regex(filename)) }
|
|
441
|
+
.find { |f| T.must(f.content).match?(output_file_regex(filename)) }
|
|
382
442
|
|
|
383
443
|
compiled_file ||=
|
|
384
444
|
compiled_files
|
|
@@ -387,22 +447,25 @@ module Dependabot
|
|
|
387
447
|
compiled_file
|
|
388
448
|
end
|
|
389
449
|
|
|
450
|
+
sig { params(filename: String).returns(String) }
|
|
390
451
|
def output_file_regex(filename)
|
|
391
452
|
"--output-file[=\s]+.*\s#{Regexp.escape(filename)}\s*$"
|
|
392
453
|
end
|
|
393
454
|
|
|
455
|
+
sig { params(compiled_file: T.nilable(Dependabot::DependencyFile)).returns(T::Boolean) }
|
|
394
456
|
def compiled_file_includes_dependency?(compiled_file)
|
|
395
457
|
return false unless compiled_file
|
|
396
458
|
|
|
397
459
|
regex = RequirementParser::INSTALL_REQ_WITH_REQUIREMENT
|
|
398
460
|
|
|
399
461
|
matches = []
|
|
400
|
-
compiled_file.content.scan(regex) { matches << Regexp.last_match }
|
|
462
|
+
T.must(compiled_file.content).scan(regex) { matches << Regexp.last_match }
|
|
401
463
|
matches.any? { |m| normalise(m[:name]) == dependency.name }
|
|
402
464
|
end
|
|
403
465
|
|
|
404
466
|
# If the files we need to update require one another then we need to
|
|
405
467
|
# update them in the right order
|
|
468
|
+
sig { params(filenames: T::Array[String]).returns(T::Array[String]) }
|
|
406
469
|
def order_filenames_for_compilation(filenames)
|
|
407
470
|
ordered_filenames = T.let([], T::Array[String])
|
|
408
471
|
|
|
@@ -410,7 +473,7 @@ module Dependabot
|
|
|
410
473
|
ordered_filenames +=
|
|
411
474
|
remaining_filenames
|
|
412
475
|
.reject do |fn|
|
|
413
|
-
unupdated_reqs = requirement_map[fn] - ordered_filenames
|
|
476
|
+
unupdated_reqs = T.must(requirement_map[fn]) - ordered_filenames
|
|
414
477
|
unupdated_reqs.intersect?(filenames)
|
|
415
478
|
end
|
|
416
479
|
end
|
|
@@ -418,11 +481,12 @@ module Dependabot
|
|
|
418
481
|
ordered_filenames
|
|
419
482
|
end
|
|
420
483
|
|
|
484
|
+
sig { returns(T::Hash[String, T::Array[String]]) }
|
|
421
485
|
def requirement_map
|
|
422
486
|
child_req_regex = Uv::FileFetcher::CHILD_REQUIREMENT_REGEX
|
|
423
|
-
@requirement_map ||=
|
|
487
|
+
@requirement_map ||= T.let(
|
|
424
488
|
pip_compile_files.each_with_object({}) do |file, req_map|
|
|
425
|
-
paths = file.content.scan(child_req_regex).flatten
|
|
489
|
+
paths = T.must(file.content).scan(child_req_regex).flatten
|
|
426
490
|
current_dir = File.dirname(file.name)
|
|
427
491
|
|
|
428
492
|
req_map[file.name] =
|
|
@@ -434,9 +498,12 @@ module Dependabot
|
|
|
434
498
|
|
|
435
499
|
path
|
|
436
500
|
end.uniq.compact
|
|
437
|
-
end
|
|
501
|
+
end,
|
|
502
|
+
T.nilable(T::Hash[String, T::Array[String]])
|
|
503
|
+
)
|
|
438
504
|
end
|
|
439
505
|
|
|
506
|
+
sig { returns(T.nilable(String)) }
|
|
440
507
|
def parse_updated_files
|
|
441
508
|
updated_files =
|
|
442
509
|
dependency_files.map do |file|
|
|
@@ -454,47 +521,59 @@ module Dependabot
|
|
|
454
521
|
).parse.find { |d| d.name == dependency.name }&.version
|
|
455
522
|
end
|
|
456
523
|
|
|
524
|
+
sig { returns(Dependabot::Uv::FileParser::PythonRequirementParser) }
|
|
457
525
|
def python_requirement_parser
|
|
458
|
-
@python_requirement_parser ||=
|
|
526
|
+
@python_requirement_parser ||= T.let(
|
|
459
527
|
FileParser::PythonRequirementParser.new(
|
|
460
528
|
dependency_files: dependency_files
|
|
461
|
-
)
|
|
529
|
+
), T.nilable(FileParser::PythonRequirementParser)
|
|
530
|
+
)
|
|
462
531
|
end
|
|
463
532
|
|
|
533
|
+
sig { returns(Dependabot::Uv::LanguageVersionManager) }
|
|
464
534
|
def language_version_manager
|
|
465
|
-
@language_version_manager ||=
|
|
535
|
+
@language_version_manager ||= T.let(
|
|
466
536
|
LanguageVersionManager.new(
|
|
467
537
|
python_requirement_parser: python_requirement_parser
|
|
468
|
-
)
|
|
538
|
+
), T.nilable(LanguageVersionManager)
|
|
539
|
+
)
|
|
469
540
|
end
|
|
470
541
|
|
|
542
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
471
543
|
def setup_files
|
|
472
544
|
dependency_files.select { |f| f.name.end_with?("setup.py") }
|
|
473
545
|
end
|
|
474
546
|
|
|
547
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
475
548
|
def pip_compile_files
|
|
476
549
|
dependency_files.select { |f| f.name.end_with?(".in") }
|
|
477
550
|
end
|
|
478
551
|
|
|
552
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
479
553
|
def compiled_files
|
|
480
554
|
dependency_files.select { |f| f.name.end_with?(".txt") }
|
|
481
555
|
end
|
|
482
556
|
|
|
557
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
483
558
|
def setup_cfg_files
|
|
484
559
|
dependency_files.select { |f| f.name.end_with?("setup.cfg") }
|
|
485
560
|
end
|
|
486
561
|
end
|
|
562
|
+
# rubocop:enable Metrics/ClassLength
|
|
487
563
|
end
|
|
488
564
|
|
|
489
565
|
class PipCompileErrorHandler
|
|
490
|
-
|
|
566
|
+
extend T::Sig
|
|
567
|
+
|
|
568
|
+
SUBPROCESS_ERROR = T.let(/subprocess-exited-with-error/, Regexp)
|
|
491
569
|
|
|
492
|
-
INSTALLATION_ERROR = /InstallationError
|
|
570
|
+
INSTALLATION_ERROR = T.let(/InstallationError/, Regexp)
|
|
493
571
|
|
|
494
|
-
INSTALLATION_SUBPROCESS_ERROR = /InstallationSubprocessError
|
|
572
|
+
INSTALLATION_SUBPROCESS_ERROR = T.let(/InstallationSubprocessError/, Regexp)
|
|
495
573
|
|
|
496
|
-
HASH_MISMATCH = /HashMismatch
|
|
574
|
+
HASH_MISMATCH = T.let(/HashMismatch/, Regexp)
|
|
497
575
|
|
|
576
|
+
sig { params(error: String).void }
|
|
498
577
|
def handle_pipcompile_error(error)
|
|
499
578
|
return unless error.match?(SUBPROCESS_ERROR) || error.match?(INSTALLATION_ERROR) ||
|
|
500
579
|
error.match?(INSTALLATION_SUBPROCESS_ERROR) || error.match?(HASH_MISMATCH)
|