dependabot-uv 0.349.0 → 0.350.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/helpers/requirements.txt +1 -1
- data/lib/dependabot/uv/authed_url_builder.rb +3 -27
- data/lib/dependabot/uv/file_updater/lock_file_updater.rb +31 -26
- data/lib/dependabot/uv/metadata_finder.rb +6 -210
- data/lib/dependabot/uv/name_normaliser.rb +3 -17
- data/lib/dependabot/uv/requirement.rb +4 -196
- data/lib/dependabot/uv/requirement_parser.rb +5 -53
- data/lib/dependabot/uv/version.rb +4 -321
- metadata +18 -6
- data/lib/dependabot/uv/file_parser/setup_file_parser.rb +0 -194
- data/lib/dependabot/uv/pipenv_runner.rb +0 -110
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b23c5beac181aafb8e92f670984121a88254da3a1804dbd7891e9e6bc274b84c
|
|
4
|
+
data.tar.gz: c5007c5452b38eac6210eac2df08635c69e3ed7fb7410e8173e9964678902d9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f646e21b2dd5f869a1fc56a48ab0f04051d202b4efc6f98a4d3dd2efca28526ffae66068ee3d59fd2ad5e03ad243db060ddcc703df018cefb4c1f06369043887
|
|
7
|
+
data.tar.gz: e5044b7cd9b6bb4f3a8273309e1d20a0ffb784aa5c5b92401f28e0a885cef9ac239ee2e4a51fdff6978425644529e705a835b8d58932bd45aae92e47d9dfc3e5
|
data/helpers/requirements.txt
CHANGED
|
@@ -2,35 +2,11 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/python/authed_url_builder"
|
|
5
6
|
|
|
6
7
|
module Dependabot
|
|
7
8
|
module Uv
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sig { params(credential: Credential).returns(String) }
|
|
12
|
-
def self.authed_url(credential:)
|
|
13
|
-
token = T.let(credential.fetch("token", nil), T.nilable(String))
|
|
14
|
-
url = T.let(credential.fetch("index-url", nil), T.nilable(String))
|
|
15
|
-
return "" unless url
|
|
16
|
-
return url unless token
|
|
17
|
-
|
|
18
|
-
basic_auth_details =
|
|
19
|
-
if token.ascii_only? && token.include?(":") then token
|
|
20
|
-
elsif Base64.decode64(token).ascii_only? &&
|
|
21
|
-
Base64.decode64(token).include?(":")
|
|
22
|
-
Base64.decode64(token)
|
|
23
|
-
else
|
|
24
|
-
token
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
if basic_auth_details.include?(":")
|
|
28
|
-
username, _, password = basic_auth_details.partition(":")
|
|
29
|
-
basic_auth_details = "#{CGI.escape(username)}:#{CGI.escape(password)}"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
url.sub("://", "://#{basic_auth_details}@")
|
|
33
|
-
end
|
|
34
|
-
end
|
|
9
|
+
# UV uses the same authenticated URL building logic as Python
|
|
10
|
+
AuthedUrlBuilder = Dependabot::Python::AuthedUrlBuilder
|
|
35
11
|
end
|
|
36
12
|
end
|
|
@@ -127,16 +127,39 @@ module Dependabot
|
|
|
127
127
|
def replace_dep(dep, content, new_r, old_r)
|
|
128
128
|
new_req = new_r[:requirement]
|
|
129
129
|
old_req = old_r[:requirement]
|
|
130
|
+
escaped_name = Regexp.escape(dep.name)
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
regex = /(["']#{escaped_name})([^"']+)(["'])/x
|
|
133
|
+
|
|
134
|
+
replaced = T.let(false, T::Boolean)
|
|
135
|
+
|
|
136
|
+
updated_content = content.gsub(regex) do
|
|
137
|
+
captured_requirement = Regexp.last_match(2)
|
|
138
|
+
|
|
139
|
+
if requirements_match?(T.must(captured_requirement), old_req)
|
|
140
|
+
replaced = true
|
|
141
|
+
"#{Regexp.last_match(1)}#{new_req}#{Regexp.last_match(3)}"
|
|
142
|
+
else
|
|
143
|
+
Regexp.last_match(0)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
unless replaced
|
|
148
|
+
updated_content = content.sub(regex) do
|
|
149
|
+
"#{Regexp.last_match(1)}#{new_req}#{Regexp.last_match(3)}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
updated_content
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
sig { params(req1: String, req2: String).returns(T::Boolean) }
|
|
157
|
+
def requirements_match?(req1, req2)
|
|
158
|
+
normalize = lambda do |req|
|
|
159
|
+
req.split(",").map(&:strip).sort.join(",")
|
|
139
160
|
end
|
|
161
|
+
|
|
162
|
+
normalize.call(req1) == normalize.call(req2)
|
|
140
163
|
end
|
|
141
164
|
|
|
142
165
|
sig { returns(String) }
|
|
@@ -313,24 +336,6 @@ module Dependabot
|
|
|
313
336
|
url.gsub(%r{^https?://}, "").gsub(/[^a-zA-Z0-9]/, "_").upcase
|
|
314
337
|
end
|
|
315
338
|
|
|
316
|
-
sig { params(dep: T.untyped, old_req: T.untyped).returns(Regexp) }
|
|
317
|
-
def declaration_regex(dep, old_req)
|
|
318
|
-
escaped_name = Regexp.escape(dep.name)
|
|
319
|
-
# Extract the requirement operator and version
|
|
320
|
-
operator = old_req.fetch(:requirement).match(/^(.+?)[0-9]/)&.captures&.first
|
|
321
|
-
# Escape special regex characters in the operator
|
|
322
|
-
escaped_operator = Regexp.escape(operator) if operator
|
|
323
|
-
|
|
324
|
-
# Match various formats of dependency declarations:
|
|
325
|
-
# 1. "dependency==1.0.0" (with quotes around the entire string)
|
|
326
|
-
# 2. dependency==1.0.0 (without quotes)
|
|
327
|
-
# The declaration should only include the package name, operator, and version
|
|
328
|
-
# without the enclosing quotes
|
|
329
|
-
/
|
|
330
|
-
["']?(?<declaration>#{escaped_name}\s*#{escaped_operator}[\d\.\*]+)["']?
|
|
331
|
-
/x
|
|
332
|
-
end
|
|
333
|
-
|
|
334
339
|
sig { returns(String) }
|
|
335
340
|
def lock_options
|
|
336
341
|
options = lock_index_options
|
|
@@ -1,220 +1,16 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "excon"
|
|
5
4
|
require "sorbet-runtime"
|
|
6
|
-
require "
|
|
7
|
-
|
|
5
|
+
require "dependabot/python/metadata_finder"
|
|
8
6
|
require "dependabot/metadata_finders"
|
|
9
|
-
require "dependabot/metadata_finders/base"
|
|
10
|
-
require "dependabot/registry_client"
|
|
11
|
-
require "dependabot/uv/authed_url_builder"
|
|
12
|
-
require "dependabot/uv/name_normaliser"
|
|
13
7
|
|
|
14
8
|
module Dependabot
|
|
15
9
|
module Uv
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
MAIN_PYPI_URL = "https://pypi.org/pypi"
|
|
20
|
-
|
|
21
|
-
sig do
|
|
22
|
-
params(
|
|
23
|
-
dependency: Dependabot::Dependency,
|
|
24
|
-
credentials: T::Array[Dependabot::Credential]
|
|
25
|
-
)
|
|
26
|
-
.void
|
|
27
|
-
end
|
|
28
|
-
def initialize(dependency:, credentials:)
|
|
29
|
-
super
|
|
30
|
-
@pypi_listing = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
|
|
31
|
-
@source_from_description = T.let(nil, T.nilable(String))
|
|
32
|
-
@source_from_homepage = T.let(nil, T.nilable(String))
|
|
33
|
-
@homepage_response = T.let(nil, T.nilable(Excon::Response))
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
sig { returns(T.nilable(String)) }
|
|
37
|
-
def homepage_url
|
|
38
|
-
pypi_listing.dig("info", "home_page") ||
|
|
39
|
-
pypi_listing.dig("info", "project_urls", "Homepage") ||
|
|
40
|
-
pypi_listing.dig("info", "project_urls", "homepage") ||
|
|
41
|
-
super
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
sig { override.returns(T.nilable(Dependabot::Source)) }
|
|
47
|
-
def look_up_source
|
|
48
|
-
potential_source_urls = [
|
|
49
|
-
pypi_listing.dig("info", "project_urls", "Source"),
|
|
50
|
-
pypi_listing.dig("info", "project_urls", "Repository"),
|
|
51
|
-
pypi_listing.dig("info", "home_page"),
|
|
52
|
-
pypi_listing.dig("info", "download_url"),
|
|
53
|
-
pypi_listing.dig("info", "docs_url")
|
|
54
|
-
].compact
|
|
55
|
-
|
|
56
|
-
potential_source_urls +=
|
|
57
|
-
(pypi_listing.dig("info", "project_urls") || {}).values
|
|
58
|
-
|
|
59
|
-
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
|
60
|
-
source_url ||= source_from_description
|
|
61
|
-
source_url ||= source_from_homepage
|
|
62
|
-
|
|
63
|
-
Source.from_url(source_url)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
67
|
-
sig { returns(T.nilable(String)) }
|
|
68
|
-
def source_from_description
|
|
69
|
-
potential_source_urls = []
|
|
70
|
-
desc = pypi_listing.dig("info", "description")
|
|
71
|
-
return unless desc
|
|
72
|
-
|
|
73
|
-
desc.scan(Source::SOURCE_REGEX) do
|
|
74
|
-
potential_source_urls << Regexp.last_match.to_s
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Looking for a source where the repo name exactly matches the
|
|
78
|
-
# dependency name
|
|
79
|
-
match_url = potential_source_urls.find do |url|
|
|
80
|
-
repo = Source.from_url(url)&.repo
|
|
81
|
-
repo&.downcase&.end_with?(normalised_dependency_name)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
return match_url if match_url
|
|
85
|
-
|
|
86
|
-
# Failing that, look for a source where the full dependency name is
|
|
87
|
-
# mentioned when the link is followed
|
|
88
|
-
@source_from_description ||= T.let(
|
|
89
|
-
potential_source_urls.find do |url|
|
|
90
|
-
full_url = Source.from_url(url)&.url
|
|
91
|
-
next unless full_url
|
|
92
|
-
|
|
93
|
-
response = Dependabot::RegistryClient.get(url: full_url)
|
|
94
|
-
next unless response.status == 200
|
|
95
|
-
|
|
96
|
-
response.body.include?(normalised_dependency_name)
|
|
97
|
-
end,
|
|
98
|
-
T.nilable(String)
|
|
99
|
-
)
|
|
100
|
-
end
|
|
101
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
102
|
-
|
|
103
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
104
|
-
sig { returns(T.nilable(String)) }
|
|
105
|
-
def source_from_homepage
|
|
106
|
-
homepage_body_local = homepage_body
|
|
107
|
-
return unless homepage_body_local
|
|
108
|
-
|
|
109
|
-
potential_source_urls = []
|
|
110
|
-
homepage_body_local.scan(Source::SOURCE_REGEX) do
|
|
111
|
-
potential_source_urls << Regexp.last_match.to_s
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
match_url = potential_source_urls.find do |url|
|
|
115
|
-
repo = Source.from_url(url)&.repo
|
|
116
|
-
repo&.downcase&.end_with?(normalised_dependency_name)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
return match_url if match_url
|
|
120
|
-
|
|
121
|
-
@source_from_homepage ||= T.let(
|
|
122
|
-
potential_source_urls.find do |url|
|
|
123
|
-
full_url = Source.from_url(url)&.url
|
|
124
|
-
next unless full_url
|
|
125
|
-
|
|
126
|
-
response = Dependabot::RegistryClient.get(url: full_url)
|
|
127
|
-
next unless response.status == 200
|
|
128
|
-
|
|
129
|
-
response.body.include?(normalised_dependency_name)
|
|
130
|
-
end,
|
|
131
|
-
T.nilable(String)
|
|
132
|
-
)
|
|
133
|
-
end
|
|
134
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
135
|
-
|
|
136
|
-
sig { returns(T.nilable(String)) }
|
|
137
|
-
def homepage_body
|
|
138
|
-
homepage_url = pypi_listing.dig("info", "home_page")
|
|
139
|
-
|
|
140
|
-
return unless homepage_url
|
|
141
|
-
return if [
|
|
142
|
-
"pypi.org",
|
|
143
|
-
"pypi.python.org"
|
|
144
|
-
].include?(URI(homepage_url).host)
|
|
145
|
-
|
|
146
|
-
@homepage_response ||= T.let(
|
|
147
|
-
begin
|
|
148
|
-
Dependabot::RegistryClient.get(url: homepage_url)
|
|
149
|
-
rescue Excon::Error::Timeout, Excon::Error::Socket,
|
|
150
|
-
Excon::Error::TooManyRedirects, ArgumentError
|
|
151
|
-
nil
|
|
152
|
-
end,
|
|
153
|
-
T.nilable(Excon::Response)
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return unless @homepage_response&.status == 200
|
|
157
|
-
|
|
158
|
-
@homepage_response&.body
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
|
162
|
-
def pypi_listing
|
|
163
|
-
return @pypi_listing unless @pypi_listing.nil?
|
|
164
|
-
return @pypi_listing = {} if dependency.version&.include?("+")
|
|
165
|
-
|
|
166
|
-
possible_listing_urls.each do |url|
|
|
167
|
-
response = fetch_authed_url(url)
|
|
168
|
-
next unless response.status == 200
|
|
169
|
-
|
|
170
|
-
@pypi_listing = JSON.parse(response.body)
|
|
171
|
-
return @pypi_listing
|
|
172
|
-
rescue JSON::ParserError
|
|
173
|
-
next
|
|
174
|
-
rescue Excon::Error::Timeout
|
|
175
|
-
next
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
@pypi_listing = {} # No listing found
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
sig { params(url: String).returns(Excon::Response) }
|
|
182
|
-
def fetch_authed_url(url)
|
|
183
|
-
if url.match(%r{(.*)://(.*?):(.*)@([^@]+)$}) &&
|
|
184
|
-
Regexp.last_match&.captures&.[](1)&.include?("@")
|
|
185
|
-
protocol, user, pass, url = T.must(Regexp.last_match).captures
|
|
186
|
-
|
|
187
|
-
Dependabot::RegistryClient.get(
|
|
188
|
-
url: "#{protocol}://#{url}",
|
|
189
|
-
options: {
|
|
190
|
-
user: user,
|
|
191
|
-
password: pass
|
|
192
|
-
}
|
|
193
|
-
)
|
|
194
|
-
else
|
|
195
|
-
Dependabot::RegistryClient.get(url: url)
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
sig { returns(T::Array[String]) }
|
|
200
|
-
def possible_listing_urls
|
|
201
|
-
credential_urls =
|
|
202
|
-
credentials
|
|
203
|
-
.select { |cred| cred["type"] == "python_index" }
|
|
204
|
-
.map { |c| AuthedUrlBuilder.authed_url(credential: c) }
|
|
205
|
-
|
|
206
|
-
(credential_urls + [MAIN_PYPI_URL]).map do |base_url|
|
|
207
|
-
base_url.gsub(%r{/$}, "") + "/#{normalised_dependency_name}/json"
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Strip [extras] from name (dependency_name[extra_dep,other_extra])
|
|
212
|
-
sig { returns(String) }
|
|
213
|
-
def normalised_dependency_name
|
|
214
|
-
NameNormaliser.normalise(dependency.name)
|
|
215
|
-
end
|
|
216
|
-
end
|
|
10
|
+
# UV uses Python's PyPI metadata lookup, so we delegate to Python::MetadataFinder
|
|
11
|
+
MetadataFinder = Dependabot::Python::MetadataFinder
|
|
217
12
|
end
|
|
218
13
|
end
|
|
219
14
|
|
|
220
|
-
Dependabot::MetadataFinders
|
|
15
|
+
Dependabot::MetadataFinders
|
|
16
|
+
.register("uv", Dependabot::Uv::MetadataFinder)
|
|
@@ -2,25 +2,11 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/python/name_normaliser"
|
|
5
6
|
|
|
6
7
|
module Dependabot
|
|
7
8
|
module Uv
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sig { params(name: String).returns(String) }
|
|
12
|
-
def self.normalise(name)
|
|
13
|
-
extras_regex = /\[.+\]/
|
|
14
|
-
name.downcase.gsub(/[-_.]+/, "-").gsub(extras_regex, "")
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
sig { params(name: String, extras: T::Array[String]).returns(String) }
|
|
18
|
-
def self.normalise_including_extras(name, extras)
|
|
19
|
-
normalised_name = normalise(name)
|
|
20
|
-
return normalised_name if extras.empty?
|
|
21
|
-
|
|
22
|
-
normalised_name + "[" + extras.join(",") + "]"
|
|
23
|
-
end
|
|
24
|
-
end
|
|
9
|
+
# UV uses the same Python package name normalization (PEP 503)
|
|
10
|
+
NameNormaliser = Dependabot::Python::NameNormaliser
|
|
25
11
|
end
|
|
26
12
|
end
|
|
@@ -1,206 +1,14 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
-
|
|
6
|
-
require "dependabot/requirement"
|
|
5
|
+
require "dependabot/python/requirement"
|
|
7
6
|
require "dependabot/utils"
|
|
8
|
-
require "dependabot/uv/version"
|
|
9
7
|
|
|
10
8
|
module Dependabot
|
|
11
9
|
module Uv
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9)*])\s*\|+/, Regexp)
|
|
16
|
-
|
|
17
|
-
# Add equality and arbitrary-equality matchers
|
|
18
|
-
OPS = T.let(
|
|
19
|
-
OPS.merge(
|
|
20
|
-
"==" => ->(v, r) { v == r },
|
|
21
|
-
"===" => ->(v, r) { v.to_s == r.to_s }
|
|
22
|
-
),
|
|
23
|
-
T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)]
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
quoted = OPS.keys.sort_by(&:length).reverse
|
|
27
|
-
.map { |k| Regexp.quote(k) }.join("|")
|
|
28
|
-
version_pattern = Uv::Version::VERSION_PATTERN
|
|
29
|
-
|
|
30
|
-
PATTERN_RAW = T.let("\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze, String)
|
|
31
|
-
PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
|
|
32
|
-
PARENS_PATTERN = T.let(/\A\(([^)]+)\)\z/, Regexp)
|
|
33
|
-
|
|
34
|
-
sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
|
|
35
|
-
def self.parse(obj)
|
|
36
|
-
return ["=", Uv::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
|
37
|
-
|
|
38
|
-
line = obj.to_s
|
|
39
|
-
if (matches = PARENS_PATTERN.match(line))
|
|
40
|
-
line = matches[1]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
unless (matches = PATTERN.match(line))
|
|
44
|
-
msg = "Illformed requirement [#{obj.inspect}]"
|
|
45
|
-
raise BadRequirementError, msg
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
return DefaultRequirement if matches[:op] == ">=" && matches[:version] == "0"
|
|
49
|
-
|
|
50
|
-
[matches[:op] || "=", Uv::Version.new(T.must(matches[:version]))]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Returns an array of requirements. At least one requirement from the
|
|
54
|
-
# returned array must be satisfied for a version to be valid.
|
|
55
|
-
#
|
|
56
|
-
# NOTE: Or requirements are only valid for Poetry.
|
|
57
|
-
sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
|
|
58
|
-
def self.requirements_array(requirement_string)
|
|
59
|
-
return [new(nil)] if requirement_string.nil?
|
|
60
|
-
|
|
61
|
-
if (matches = PARENS_PATTERN.match(requirement_string))
|
|
62
|
-
requirement_string = matches[1]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
T.must(requirement_string).strip.split(OR_SEPARATOR).map do |req_string|
|
|
66
|
-
new(req_string.strip)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
|
|
71
|
-
def initialize(*requirements)
|
|
72
|
-
requirements = requirements.flatten.flat_map do |req_string|
|
|
73
|
-
next if req_string.nil?
|
|
74
|
-
|
|
75
|
-
# Standard python doesn't support whitespace in requirements, but Poetry does.
|
|
76
|
-
req_string = req_string.gsub(/(\d +)([<=>])/, '\1,\2')
|
|
77
|
-
|
|
78
|
-
req_string.split(",").map(&:strip).map do |r|
|
|
79
|
-
convert_python_constraint_to_ruby_constraint(r)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
super(requirements)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
|
|
87
|
-
def satisfied_by?(version)
|
|
88
|
-
version = Uv::Version.new(version.to_s)
|
|
89
|
-
|
|
90
|
-
requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
sig { returns(T::Boolean) }
|
|
94
|
-
def exact?
|
|
95
|
-
return false unless requirements.size == 1
|
|
96
|
-
|
|
97
|
-
%w(= == ===).include?(requirements[0][0])
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
|
|
103
|
-
def convert_python_constraint_to_ruby_constraint(req_string)
|
|
104
|
-
return nil if req_string.nil? || req_string.strip.empty?
|
|
105
|
-
return nil if req_string == "*"
|
|
106
|
-
|
|
107
|
-
req_string = req_string.gsub("~=", "~>")
|
|
108
|
-
req_string = req_string.gsub(/(?<=\d)[<=>].*\Z/, "")
|
|
109
|
-
|
|
110
|
-
if req_string.match?(/~[^>]/) then convert_tilde_req(req_string)
|
|
111
|
-
elsif req_string.start_with?("^") then convert_caret_req(req_string)
|
|
112
|
-
elsif req_string.match?(/^=?={0,2}\s*\d+\.\d+(\.\d+)?(-[a-z0-9.-]+)?(\.\*)?$/i)
|
|
113
|
-
convert_exact(req_string)
|
|
114
|
-
elsif req_string.include?(".*") then convert_wildcard(req_string)
|
|
115
|
-
else
|
|
116
|
-
req_string
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Poetry uses ~ requirements.
|
|
121
|
-
# https://github.com/sdispater/poetry#tilde-requirements
|
|
122
|
-
sig { params(req_string: String).returns(String) }
|
|
123
|
-
def convert_tilde_req(req_string)
|
|
124
|
-
version = req_string.gsub(/^~\>?/, "")
|
|
125
|
-
parts = version.split(".")
|
|
126
|
-
parts << "0" if parts.count < 3
|
|
127
|
-
"~> #{parts.join('.')}"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Poetry uses ^ requirements
|
|
131
|
-
# https://github.com/sdispater/poetry#caret-requirement
|
|
132
|
-
sig { params(req_string: String).returns(T::Array[String]) }
|
|
133
|
-
def convert_caret_req(req_string)
|
|
134
|
-
version = req_string.gsub(/^\^/, "")
|
|
135
|
-
parts = version.split(".")
|
|
136
|
-
parts.fill("0", parts.length...3)
|
|
137
|
-
first_non_zero = parts.find { |d| d != "0" }
|
|
138
|
-
first_non_zero_index =
|
|
139
|
-
first_non_zero ? parts.index(first_non_zero) : parts.count - 1
|
|
140
|
-
upper_bound = parts.map.with_index do |part, i|
|
|
141
|
-
if i < T.must(first_non_zero_index) then part
|
|
142
|
-
elsif i == first_non_zero_index then (part.to_i + 1).to_s
|
|
143
|
-
# .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
|
|
144
|
-
elsif i > T.must(first_non_zero_index) && i == 2 then "0.dev"
|
|
145
|
-
else
|
|
146
|
-
"0"
|
|
147
|
-
end
|
|
148
|
-
end.join(".")
|
|
149
|
-
|
|
150
|
-
[">= #{version}", "< #{upper_bound}"]
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
sig { params(req_string: String).returns(String) }
|
|
154
|
-
def convert_wildcard(req_string)
|
|
155
|
-
# NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
|
|
156
|
-
# "!= 1.0.0". There's no way to model this correctly in Ruby :'(
|
|
157
|
-
quoted_ops = OPS.keys.sort_by(&:length).reverse
|
|
158
|
-
.map { |k| Regexp.quote(k) }.join("|")
|
|
159
|
-
op_match = req_string.match(/\A\s*(#{quoted_ops})?/)
|
|
160
|
-
op = op_match&.captures&.first.to_s.strip
|
|
161
|
-
exact_op = ["", "=", "==", "==="].include?(op)
|
|
162
|
-
|
|
163
|
-
req_string.strip
|
|
164
|
-
.split(".")
|
|
165
|
-
.first(T.must(req_string.split(".").index { |s| s.include?("*") }) + 1)
|
|
166
|
-
.join(".")
|
|
167
|
-
.gsub(/\*(?!$)/, "0")
|
|
168
|
-
.gsub(/\*$/, "0.dev")
|
|
169
|
-
.tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
|
|
173
|
-
def convert_exact(req_string)
|
|
174
|
-
arbitrary_equality = req_string.start_with?("===")
|
|
175
|
-
cleaned_version = req_string.gsub(/^=+/, "").strip
|
|
176
|
-
|
|
177
|
-
return ["=== #{cleaned_version}"] if arbitrary_equality
|
|
178
|
-
|
|
179
|
-
# Handle versions wildcarded with .*, e.g. 1.0.*
|
|
180
|
-
if cleaned_version.include?(".*")
|
|
181
|
-
# Remove all characters after the first .*, and the .*
|
|
182
|
-
cleaned_version = cleaned_version.split(".*").first
|
|
183
|
-
version = Version.new(cleaned_version)
|
|
184
|
-
# Get the release segment parts [major, minor, patch]
|
|
185
|
-
version_parts = version.release_segment
|
|
186
|
-
|
|
187
|
-
if version_parts.length == 1
|
|
188
|
-
major = T.must(version_parts[0])
|
|
189
|
-
[">= #{major}.0.0.dev", "< #{major + 1}.0.0"]
|
|
190
|
-
elsif version_parts.length == 2
|
|
191
|
-
major, minor = version_parts
|
|
192
|
-
"~> #{major}.#{minor}.0.dev"
|
|
193
|
-
elsif version_parts.length == 3
|
|
194
|
-
major, minor, patch = version_parts
|
|
195
|
-
"~> #{major}.#{minor}.#{patch}.dev"
|
|
196
|
-
else
|
|
197
|
-
"= #{cleaned_version}"
|
|
198
|
-
end
|
|
199
|
-
else
|
|
200
|
-
"= #{cleaned_version}"
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
10
|
+
# UV uses Python's requirement scheme, so we delegate to Python::Requirement
|
|
11
|
+
Requirement = Dependabot::Python::Requirement
|
|
204
12
|
end
|
|
205
13
|
end
|
|
206
14
|
|
|
@@ -1,60 +1,12 @@
|
|
|
1
1
|
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/python/requirement_parser"
|
|
6
|
+
|
|
4
7
|
module Dependabot
|
|
5
8
|
module Uv
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
EXTRA = /[a-zA-Z0-9\-_\.]+/
|
|
9
|
-
COMPARISON = /===|==|>=|<=|<|>|~=|!=/
|
|
10
|
-
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
|
|
11
|
-
|
|
12
|
-
REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*v?(?<version>#{VERSION})/
|
|
13
|
-
HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|\\|$)/
|
|
14
|
-
REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
|
|
15
|
-
HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
|
|
16
|
-
MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
|
|
17
|
-
PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
|
|
18
|
-
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
|
|
19
|
-
ENV_VAR =
|
|
20
|
-
/python_version|python_full_version|os_name|sys_platform|
|
|
21
|
-
platform_release|platform_system|platform_version|platform_machine|
|
|
22
|
-
platform_python_implementation|implementation_name|
|
|
23
|
-
implementation_version/
|
|
24
|
-
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
|
|
25
|
-
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
|
|
26
|
-
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
|
|
27
|
-
|
|
28
|
-
INSTALL_REQ_WITH_REQUIREMENT =
|
|
29
|
-
/\s*\\?\s*(?<name>#{NAME})
|
|
30
|
-
\s*\\?\s*(\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
31
|
-
\s*\\?\s*\(?(?<requirements>#{REQUIREMENTS})\)?
|
|
32
|
-
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
|
33
|
-
\s*\\?\s*(?<hashes>#{HASHES})?
|
|
34
|
-
\s*#*\s*(?<comment>.+)?
|
|
35
|
-
/x
|
|
36
|
-
|
|
37
|
-
INSTALL_REQ_WITHOUT_REQUIREMENT =
|
|
38
|
-
/^\s*\\?\s*(?<name>#{NAME})
|
|
39
|
-
\s*\\?\s*(\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
40
|
-
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
|
41
|
-
\s*\\?\s*(?<hashes>#{HASHES})?
|
|
42
|
-
\s*#*\s*(?<comment>.+)?$
|
|
43
|
-
/x
|
|
44
|
-
|
|
45
|
-
VALID_REQ_TXT_REQUIREMENT =
|
|
46
|
-
/^\s*\\?\s*(?<name>#{NAME})
|
|
47
|
-
\s*\\?\s*(\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
48
|
-
\s*\\?\s*\(?(?<requirements>#{REQUIREMENTS})?\)?
|
|
49
|
-
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
|
50
|
-
\s*\\?\s*(?<hashes>#{HASHES})?
|
|
51
|
-
\s*(\#+\s*(?<comment>.*))?$
|
|
52
|
-
/x
|
|
53
|
-
|
|
54
|
-
NAME_WITH_EXTRAS =
|
|
55
|
-
/\s*\\?\s*(?<name>#{NAME})
|
|
56
|
-
(\s*\\?\s*\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
57
|
-
/x
|
|
58
|
-
end
|
|
9
|
+
# UV uses the same Python requirement parsing regex patterns (PEP 508)
|
|
10
|
+
RequirementParser = Dependabot::Python::RequirementParser
|
|
59
11
|
end
|
|
60
12
|
end
|
|
@@ -1,331 +1,14 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
-
|
|
6
|
-
require "dependabot/version"
|
|
5
|
+
require "dependabot/python/version"
|
|
7
6
|
require "dependabot/utils"
|
|
8
7
|
|
|
9
|
-
# See https://packaging.python.org/en/latest/specifications/version-specifiers for spec details.
|
|
10
|
-
|
|
11
8
|
module Dependabot
|
|
12
9
|
module Uv
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
sig { returns(Integer) }
|
|
17
|
-
attr_reader :epoch
|
|
18
|
-
|
|
19
|
-
sig { returns(T::Array[Integer]) }
|
|
20
|
-
attr_reader :release_segment
|
|
21
|
-
|
|
22
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
23
|
-
attr_reader :dev
|
|
24
|
-
|
|
25
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
26
|
-
attr_reader :pre
|
|
27
|
-
|
|
28
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
29
|
-
attr_reader :post
|
|
30
|
-
|
|
31
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
32
|
-
attr_reader :local
|
|
33
|
-
|
|
34
|
-
INFINITY = T.let(1000, Integer)
|
|
35
|
-
NEGATIVE_INFINITY = T.let(-INFINITY, Integer)
|
|
36
|
-
|
|
37
|
-
# See https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
|
38
|
-
VERSION_PATTERN = /
|
|
39
|
-
v?
|
|
40
|
-
(?:
|
|
41
|
-
(?:(?<epoch>[0-9]+)!)? # epoch
|
|
42
|
-
(?<release>[0-9]+(?:\.[0-9]+)*) # release
|
|
43
|
-
(?<pre> # prerelease
|
|
44
|
-
[-_\.]?
|
|
45
|
-
(?<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
|
46
|
-
[-_\.]?
|
|
47
|
-
(?<pre_n>[0-9]+)?
|
|
48
|
-
)?
|
|
49
|
-
(?<post> # post release
|
|
50
|
-
(?:-(?<post_n1>[0-9]+))
|
|
51
|
-
|
|
|
52
|
-
(?:
|
|
53
|
-
[-_\.]?
|
|
54
|
-
(?<post_l>post|rev|r)
|
|
55
|
-
[-_\.]?
|
|
56
|
-
(?<post_n2>[0-9]+)?
|
|
57
|
-
)
|
|
58
|
-
)?
|
|
59
|
-
(?<dev> # dev release
|
|
60
|
-
[-_\.]?
|
|
61
|
-
(?<dev_l>dev)
|
|
62
|
-
[-_\.]?
|
|
63
|
-
(?<dev_n>[0-9]+)?
|
|
64
|
-
)?
|
|
65
|
-
)
|
|
66
|
-
(?:\+(?<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
|
67
|
-
/ix
|
|
68
|
-
|
|
69
|
-
ANCHORED_VERSION_PATTERN = /\A\s*#{VERSION_PATTERN}\s*\z/
|
|
70
|
-
|
|
71
|
-
sig { override.params(version: VersionParameter).returns(T::Boolean) }
|
|
72
|
-
def self.correct?(version)
|
|
73
|
-
return false if version.nil?
|
|
74
|
-
|
|
75
|
-
version.to_s.match?(ANCHORED_VERSION_PATTERN)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
sig { override.params(version: VersionParameter).void }
|
|
79
|
-
def initialize(version) # rubocop:disable Metrics/AbcSize
|
|
80
|
-
raise Dependabot::BadRequirementError, "Malformed version string - string is nil" if version.nil?
|
|
81
|
-
|
|
82
|
-
@version_string = T.let(version.to_s, String)
|
|
83
|
-
|
|
84
|
-
raise Dependabot::BadRequirementError, "Malformed version string - string is empty" if @version_string.empty?
|
|
85
|
-
|
|
86
|
-
matches = ANCHORED_VERSION_PATTERN.match(@version_string.downcase)
|
|
87
|
-
|
|
88
|
-
unless matches
|
|
89
|
-
raise Dependabot::BadRequirementError,
|
|
90
|
-
"Malformed version string - #{@version_string} does not match regex"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
@epoch = T.let(matches["epoch"].to_i, Integer)
|
|
94
|
-
@release_segment = T.let(matches["release"]&.split(".")&.map(&:to_i) || [], T::Array[Integer])
|
|
95
|
-
@pre = T.let(
|
|
96
|
-
parse_letter_version(matches["pre_l"], matches["pre_n"]),
|
|
97
|
-
T.nilable(T::Array[T.any(String, Integer)])
|
|
98
|
-
)
|
|
99
|
-
@post = T.let(
|
|
100
|
-
parse_letter_version(matches["post_l"], matches["post_n1"] || matches["post_n2"]),
|
|
101
|
-
T.nilable(T::Array[T.any(String, Integer)])
|
|
102
|
-
)
|
|
103
|
-
@dev = T.let(
|
|
104
|
-
parse_letter_version(matches["dev_l"], matches["dev_n"]),
|
|
105
|
-
T.nilable(T::Array[T.any(String, Integer)])
|
|
106
|
-
)
|
|
107
|
-
@local = T.let(parse_local_version(matches["local"]), T.nilable(T::Array[T.any(String, Integer)]))
|
|
108
|
-
super(matches["release"] || "")
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
sig { override.params(version: VersionParameter).returns(Dependabot::Uv::Version) }
|
|
112
|
-
def self.new(version)
|
|
113
|
-
T.cast(super, Dependabot::Uv::Version)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
sig { returns(String) }
|
|
117
|
-
def to_s
|
|
118
|
-
@version_string
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
sig { returns(String) }
|
|
122
|
-
def inspect # :nodoc:
|
|
123
|
-
"#<#{self.class} #{@version_string}>"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
sig { returns(T::Boolean) }
|
|
127
|
-
def prerelease?
|
|
128
|
-
!!(pre || dev)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
sig { returns(Dependabot::Uv::Version) }
|
|
132
|
-
def release
|
|
133
|
-
Dependabot::Uv::Version.new(release_segment.join("."))
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
sig { params(other: VersionParameter).returns(Integer) }
|
|
137
|
-
def <=>(other)
|
|
138
|
-
other = Dependabot::Uv::Version.new(other.to_s) unless other.is_a?(Dependabot::Uv::Version)
|
|
139
|
-
|
|
140
|
-
epoch_comparison = epoch <=> other.epoch
|
|
141
|
-
return epoch_comparison unless epoch_comparison.zero?
|
|
142
|
-
|
|
143
|
-
release_comparison = release_version_comparison(other)
|
|
144
|
-
return release_comparison unless release_comparison.zero?
|
|
145
|
-
|
|
146
|
-
pre_comparison = compare_keys(pre_cmp_key, other.pre_cmp_key)
|
|
147
|
-
return pre_comparison unless pre_comparison.zero?
|
|
148
|
-
|
|
149
|
-
post_comparison = compare_keys(post_cmp_key, other.post_cmp_key)
|
|
150
|
-
return post_comparison unless post_comparison.zero?
|
|
151
|
-
|
|
152
|
-
dev_comparison = compare_keys(dev_cmp_key, other.dev_cmp_key)
|
|
153
|
-
return dev_comparison unless dev_comparison.zero?
|
|
154
|
-
|
|
155
|
-
compare_keys(local_cmp_key, other.local_cmp_key)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
sig do
|
|
159
|
-
params(
|
|
160
|
-
key: T.any(Integer, T::Array[T.any(String, Integer)]),
|
|
161
|
-
other_key: T.any(Integer, T::Array[T.any(String, Integer)])
|
|
162
|
-
).returns(Integer)
|
|
163
|
-
end
|
|
164
|
-
def compare_keys(key, other_key)
|
|
165
|
-
if key.is_a?(Integer) && other_key.is_a?(Integer)
|
|
166
|
-
key <=> other_key
|
|
167
|
-
elsif key.is_a?(Array) && other_key.is_a?(Array)
|
|
168
|
-
key <=> other_key
|
|
169
|
-
elsif key.is_a?(Integer)
|
|
170
|
-
key == NEGATIVE_INFINITY ? -1 : 1
|
|
171
|
-
elsif other_key.is_a?(Integer)
|
|
172
|
-
other_key == NEGATIVE_INFINITY ? 1 : -1
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
177
|
-
def pre_cmp_key
|
|
178
|
-
if pre.nil? && post.nil? && dev # sort 1.0.dev0 before 1.0a0
|
|
179
|
-
NEGATIVE_INFINITY
|
|
180
|
-
elsif pre.nil?
|
|
181
|
-
INFINITY # versions without a pre-release should sort after those with one.
|
|
182
|
-
else
|
|
183
|
-
T.must(pre)
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
188
|
-
def local_cmp_key
|
|
189
|
-
if local.nil?
|
|
190
|
-
# Versions without a local segment should sort before those with one.
|
|
191
|
-
NEGATIVE_INFINITY
|
|
192
|
-
else
|
|
193
|
-
# According to PEP440.
|
|
194
|
-
# - Alphanumeric segments sort before numeric segments
|
|
195
|
-
# - Alphanumeric segments sort lexicographically
|
|
196
|
-
# - Numeric segments sort numerically
|
|
197
|
-
# - Shorter versions sort before longer versions when the prefixes match exactly
|
|
198
|
-
T.must(local).map do |token|
|
|
199
|
-
if token.is_a?(Integer)
|
|
200
|
-
[token, ""]
|
|
201
|
-
else
|
|
202
|
-
[NEGATIVE_INFINITY, token]
|
|
203
|
-
end
|
|
204
|
-
end.flatten
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
209
|
-
def post_cmp_key
|
|
210
|
-
# Versions without a post segment should sort before those with one.
|
|
211
|
-
return NEGATIVE_INFINITY if post.nil?
|
|
212
|
-
|
|
213
|
-
T.must(post)
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
217
|
-
def dev_cmp_key
|
|
218
|
-
# Versions without a dev segment should sort after those with one.
|
|
219
|
-
return INFINITY if dev.nil?
|
|
220
|
-
|
|
221
|
-
T.must(dev)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
sig { returns(String) }
|
|
225
|
-
def lowest_prerelease_suffix
|
|
226
|
-
"dev0"
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
sig { override.returns(T::Array[String]) }
|
|
230
|
-
def ignored_patch_versions
|
|
231
|
-
parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3
|
|
232
|
-
version_parts = parts.fill(0, parts.length...2)
|
|
233
|
-
upper_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + [lowest_prerelease_suffix]
|
|
234
|
-
lower_bound = "> #{self}"
|
|
235
|
-
upper_bound = "< #{upper_parts.join('.')}"
|
|
236
|
-
|
|
237
|
-
["#{lower_bound}, #{upper_bound}"]
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
sig { override.returns(T::Array[String]) }
|
|
241
|
-
def ignored_minor_versions
|
|
242
|
-
parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3
|
|
243
|
-
version_parts = parts.fill(0, parts.length...2)
|
|
244
|
-
lower_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + [lowest_prerelease_suffix]
|
|
245
|
-
upper_parts = version_parts.first(0) + [version_parts[0].to_i + 1] + [lowest_prerelease_suffix]
|
|
246
|
-
lower_bound = ">= #{lower_parts.join('.')}"
|
|
247
|
-
upper_bound = "< #{upper_parts.join('.')}"
|
|
248
|
-
|
|
249
|
-
["#{lower_bound}, #{upper_bound}"]
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
sig { override.returns(T::Array[String]) }
|
|
253
|
-
def ignored_major_versions
|
|
254
|
-
version_parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3
|
|
255
|
-
lower_parts = [version_parts[0].to_i + 1] + [lowest_prerelease_suffix] # earliest next major version prerelease
|
|
256
|
-
lower_bound = ">= #{lower_parts.join('.')}"
|
|
257
|
-
|
|
258
|
-
[lower_bound]
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
private
|
|
262
|
-
|
|
263
|
-
sig { params(other: Dependabot::Uv::Version).returns(Integer) }
|
|
264
|
-
def release_version_comparison(other)
|
|
265
|
-
tokens, other_tokens = pad_for_comparison(release_segment, other.release_segment)
|
|
266
|
-
tokens <=> other_tokens
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
sig do
|
|
270
|
-
params(
|
|
271
|
-
tokens: T::Array[Integer],
|
|
272
|
-
other_tokens: T::Array[Integer]
|
|
273
|
-
).returns(T::Array[T::Array[Integer]])
|
|
274
|
-
end
|
|
275
|
-
def pad_for_comparison(tokens, other_tokens)
|
|
276
|
-
tokens = tokens.dup
|
|
277
|
-
other_tokens = other_tokens.dup
|
|
278
|
-
|
|
279
|
-
longer = [tokens, other_tokens].max_by(&:count)
|
|
280
|
-
shorter = [tokens, other_tokens].min_by(&:count)
|
|
281
|
-
|
|
282
|
-
difference = T.must(longer).length - T.must(shorter).length
|
|
283
|
-
|
|
284
|
-
difference.times { T.must(shorter) << 0 }
|
|
285
|
-
|
|
286
|
-
[tokens, other_tokens]
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
sig { params(local: T.nilable(String)).returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
290
|
-
def parse_local_version(local)
|
|
291
|
-
return if local.nil?
|
|
292
|
-
|
|
293
|
-
# Takes a string like abc.1.twelve and turns it into ["abc", 1, "twelve"]
|
|
294
|
-
local.split(/[\._-]/).map { |s| /^\d+$/.match?(s) ? s.to_i : s }
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
sig do
|
|
298
|
-
params(
|
|
299
|
-
letter: T.nilable(String), number: T.nilable(String)
|
|
300
|
-
).returns(T.nilable(T::Array[T.any(String, Integer)]))
|
|
301
|
-
end
|
|
302
|
-
def parse_letter_version(letter = nil, number = nil)
|
|
303
|
-
return if letter.nil? && number.nil?
|
|
304
|
-
|
|
305
|
-
if letter
|
|
306
|
-
# Implicit 0 for cases where prerelease has no numeral
|
|
307
|
-
number ||= 0
|
|
308
|
-
|
|
309
|
-
# Normalize alternate spellings
|
|
310
|
-
if letter == "alpha"
|
|
311
|
-
letter = "a"
|
|
312
|
-
elsif letter == "beta"
|
|
313
|
-
letter = "b"
|
|
314
|
-
elsif %w(c pre preview).include? letter
|
|
315
|
-
letter = "rc"
|
|
316
|
-
elsif %w(rev r).include? letter
|
|
317
|
-
letter = "post"
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
return letter, number.to_i
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Number but no letter i.e. implicit post release syntax (e.g. 1.0-1)
|
|
324
|
-
letter = "post"
|
|
325
|
-
|
|
326
|
-
[letter, number.to_i]
|
|
327
|
-
end
|
|
328
|
-
end
|
|
10
|
+
# UV uses Python's version scheme, so we delegate to Python::Version
|
|
11
|
+
Version = Dependabot::Python::Version
|
|
329
12
|
end
|
|
330
13
|
end
|
|
331
14
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-uv
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.350.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.350.0
|
|
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.
|
|
25
|
+
version: 0.350.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: dependabot-python
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - '='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.350.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - '='
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.350.0
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: debug
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -253,7 +267,6 @@ files:
|
|
|
253
267
|
- lib/dependabot/uv/file_parser.rb
|
|
254
268
|
- lib/dependabot/uv/file_parser/pyproject_files_parser.rb
|
|
255
269
|
- lib/dependabot/uv/file_parser/python_requirement_parser.rb
|
|
256
|
-
- lib/dependabot/uv/file_parser/setup_file_parser.rb
|
|
257
270
|
- lib/dependabot/uv/file_updater.rb
|
|
258
271
|
- lib/dependabot/uv/file_updater/compile_file_updater.rb
|
|
259
272
|
- lib/dependabot/uv/file_updater/lock_file_updater.rb
|
|
@@ -268,7 +281,6 @@ files:
|
|
|
268
281
|
- lib/dependabot/uv/package/package_details_fetcher.rb
|
|
269
282
|
- lib/dependabot/uv/package/package_registry_finder.rb
|
|
270
283
|
- lib/dependabot/uv/package_manager.rb
|
|
271
|
-
- lib/dependabot/uv/pipenv_runner.rb
|
|
272
284
|
- lib/dependabot/uv/requirement.rb
|
|
273
285
|
- lib/dependabot/uv/requirement_parser.rb
|
|
274
286
|
- lib/dependabot/uv/requirements_file_matcher.rb
|
|
@@ -284,7 +296,7 @@ licenses:
|
|
|
284
296
|
- MIT
|
|
285
297
|
metadata:
|
|
286
298
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
287
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
299
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.350.0
|
|
288
300
|
rdoc_options: []
|
|
289
301
|
require_paths:
|
|
290
302
|
- lib
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "dependabot/dependency"
|
|
5
|
-
require "dependabot/errors"
|
|
6
|
-
require "dependabot/file_parsers/base/dependency_set"
|
|
7
|
-
require "dependabot/shared_helpers"
|
|
8
|
-
require "dependabot/uv/file_parser"
|
|
9
|
-
require "dependabot/uv/native_helpers"
|
|
10
|
-
require "dependabot/uv/name_normaliser"
|
|
11
|
-
require "sorbet-runtime"
|
|
12
|
-
|
|
13
|
-
module Dependabot
|
|
14
|
-
module Uv
|
|
15
|
-
class FileParser
|
|
16
|
-
class SetupFileParser
|
|
17
|
-
extend T::Sig
|
|
18
|
-
|
|
19
|
-
INSTALL_REQUIRES_REGEX = /install_requires\s*=\s*\[/m
|
|
20
|
-
SETUP_REQUIRES_REGEX = /setup_requires\s*=\s*\[/m
|
|
21
|
-
TESTS_REQUIRE_REGEX = /tests_require\s*=\s*\[/m
|
|
22
|
-
EXTRAS_REQUIRE_REGEX = /extras_require\s*=\s*\{/m
|
|
23
|
-
|
|
24
|
-
CLOSING_BRACKET = T.let({ "[" => "]", "{" => "}" }.freeze, T.any(T.untyped, T.untyped))
|
|
25
|
-
|
|
26
|
-
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
|
|
27
|
-
def initialize(dependency_files:)
|
|
28
|
-
@dependency_files = dependency_files
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
|
32
|
-
def dependency_set
|
|
33
|
-
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
|
34
|
-
|
|
35
|
-
parsed_setup_file.each do |dep|
|
|
36
|
-
# If a requirement has a `<` or `<=` marker then updating it is
|
|
37
|
-
# probably blocked. Ignore it.
|
|
38
|
-
next if dep["markers"].include?("<")
|
|
39
|
-
|
|
40
|
-
# If the requirement is our inserted version, ignore it
|
|
41
|
-
# (we wouldn't be able to update it)
|
|
42
|
-
next if dep["version"] == "0.0.1+dependabot"
|
|
43
|
-
|
|
44
|
-
dependencies <<
|
|
45
|
-
Dependency.new(
|
|
46
|
-
name: normalised_name(dep["name"], dep["extras"]),
|
|
47
|
-
version: dep["version"]&.include?("*") ? nil : dep["version"],
|
|
48
|
-
requirements: [{
|
|
49
|
-
requirement: dep["requirement"],
|
|
50
|
-
file: Pathname.new(dep["file"]).cleanpath.to_path,
|
|
51
|
-
source: nil,
|
|
52
|
-
groups: [dep["requirement_type"]]
|
|
53
|
-
}],
|
|
54
|
-
package_manager: "uv"
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
dependencies
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
63
|
-
attr_reader :dependency_files
|
|
64
|
-
|
|
65
|
-
sig { returns(T.untyped) }
|
|
66
|
-
def parsed_setup_file
|
|
67
|
-
SharedHelpers.in_a_temporary_directory do
|
|
68
|
-
write_temporary_dependency_files
|
|
69
|
-
|
|
70
|
-
requirements = SharedHelpers.run_helper_subprocess(
|
|
71
|
-
command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
|
|
72
|
-
function: "parse_setup",
|
|
73
|
-
args: [Dir.pwd]
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
check_requirements(requirements)
|
|
77
|
-
requirements
|
|
78
|
-
end
|
|
79
|
-
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
80
|
-
raise Dependabot::DependencyFileNotEvaluatable, e.message if e.message.start_with?("InstallationError")
|
|
81
|
-
|
|
82
|
-
return [] unless setup_file
|
|
83
|
-
|
|
84
|
-
parsed_sanitized_setup_file
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
sig { returns(T.nilable(T.any(T::Hash[String, T.untyped], String, T::Array[T::Hash[String, T.untyped]]))) }
|
|
88
|
-
def parsed_sanitized_setup_file
|
|
89
|
-
SharedHelpers.in_a_temporary_directory do
|
|
90
|
-
write_sanitized_setup_file
|
|
91
|
-
|
|
92
|
-
requirements = SharedHelpers.run_helper_subprocess(
|
|
93
|
-
command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
|
|
94
|
-
function: "parse_setup",
|
|
95
|
-
args: [Dir.pwd]
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
check_requirements(requirements)
|
|
99
|
-
requirements
|
|
100
|
-
end
|
|
101
|
-
rescue SharedHelpers::HelperSubprocessFailed
|
|
102
|
-
# Assume there are no dependencies in setup.py files that fail to
|
|
103
|
-
# parse. This isn't ideal, and we should continue to improve
|
|
104
|
-
# parsing, but there are a *lot* of things that can go wrong at
|
|
105
|
-
# the moment!
|
|
106
|
-
[]
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
sig { params(requirements: T.untyped).returns(T.untyped) }
|
|
110
|
-
def check_requirements(requirements)
|
|
111
|
-
requirements&.each do |dep|
|
|
112
|
-
next unless dep["requirement"]
|
|
113
|
-
|
|
114
|
-
Uv::Requirement.new(dep["requirement"].split(","))
|
|
115
|
-
rescue Gem::Requirement::BadRequirementError => e
|
|
116
|
-
raise Dependabot::DependencyFileNotEvaluatable, e.message
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
sig { void }
|
|
121
|
-
def write_temporary_dependency_files
|
|
122
|
-
dependency_files
|
|
123
|
-
.reject { |f| f.name == ".python-version" }
|
|
124
|
-
.each do |file|
|
|
125
|
-
path = file.name
|
|
126
|
-
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
|
127
|
-
File.write(path, file.content)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Write a setup.py with only entries for the requires fields.
|
|
132
|
-
#
|
|
133
|
-
# This sanitization is far from perfect (it will fail if any of the
|
|
134
|
-
# entries are dynamic), but it is an alternative approach to the one
|
|
135
|
-
# used in parser.py which sometimes succeeds when that has failed.
|
|
136
|
-
sig { void }
|
|
137
|
-
def write_sanitized_setup_file
|
|
138
|
-
install_requires = get_regexed_req_array(INSTALL_REQUIRES_REGEX)
|
|
139
|
-
setup_requires = get_regexed_req_array(SETUP_REQUIRES_REGEX)
|
|
140
|
-
tests_require = get_regexed_req_array(TESTS_REQUIRE_REGEX)
|
|
141
|
-
extras_require = get_regexed_req_dict(EXTRAS_REQUIRE_REGEX)
|
|
142
|
-
|
|
143
|
-
tmp = "from setuptools import setup\n\n" \
|
|
144
|
-
"setup(name=\"sanitized-package\",version=\"0.0.1\","
|
|
145
|
-
|
|
146
|
-
tmp += "install_requires=#{install_requires}," if install_requires
|
|
147
|
-
tmp += "setup_requires=#{setup_requires}," if setup_requires
|
|
148
|
-
tmp += "tests_require=#{tests_require}," if tests_require
|
|
149
|
-
tmp += "extras_require=#{extras_require}," if extras_require
|
|
150
|
-
tmp += ")"
|
|
151
|
-
|
|
152
|
-
File.write("setup.py", tmp)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
sig { params(regex: Regexp).returns(T.nilable(String)) }
|
|
156
|
-
def get_regexed_req_array(regex)
|
|
157
|
-
return unless (mch = setup_file.content.match(regex))
|
|
158
|
-
|
|
159
|
-
"[#{mch.post_match[0..closing_bracket_index(mch.post_match, '[')]}"
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
sig { params(regex: Regexp).returns(T.nilable(String)) }
|
|
163
|
-
def get_regexed_req_dict(regex)
|
|
164
|
-
return unless (mch = setup_file.content.match(regex))
|
|
165
|
-
|
|
166
|
-
"{#{mch.post_match[0..closing_bracket_index(mch.post_match, '{')]}"
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
sig { params(string: String, bracket: String).returns(Integer) }
|
|
170
|
-
def closing_bracket_index(string, bracket)
|
|
171
|
-
closes_required = 1
|
|
172
|
-
|
|
173
|
-
string.chars.each_with_index do |char, index|
|
|
174
|
-
closes_required += 1 if char == bracket
|
|
175
|
-
closes_required -= 1 if char == CLOSING_BRACKET.fetch(bracket)
|
|
176
|
-
return index if closes_required.zero?
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
0
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
sig { params(name: String, extras: T::Array[String]).returns(String) }
|
|
183
|
-
def normalised_name(name, extras)
|
|
184
|
-
NameNormaliser.normalise_including_extras(name, extras)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
sig { returns(T.untyped) }
|
|
188
|
-
def setup_file
|
|
189
|
-
dependency_files.find { |f| f.name == "setup.py" }
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "dependabot/shared_helpers"
|
|
5
|
-
require "dependabot/uv/file_parser"
|
|
6
|
-
require "json"
|
|
7
|
-
require "sorbet-runtime"
|
|
8
|
-
|
|
9
|
-
module Dependabot
|
|
10
|
-
module Uv
|
|
11
|
-
class PipenvRunner
|
|
12
|
-
extend T::Sig
|
|
13
|
-
|
|
14
|
-
sig do
|
|
15
|
-
params(
|
|
16
|
-
dependency: Dependabot::Dependency,
|
|
17
|
-
lockfile: T.nilable(Dependabot::DependencyFile),
|
|
18
|
-
language_version_manager: LanguageVersionManager
|
|
19
|
-
)
|
|
20
|
-
.void
|
|
21
|
-
end
|
|
22
|
-
def initialize(dependency:, lockfile:, language_version_manager:)
|
|
23
|
-
@dependency = dependency
|
|
24
|
-
@lockfile = lockfile
|
|
25
|
-
@language_version_manager = language_version_manager
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
sig { params(constraint: String).returns(String) }
|
|
29
|
-
def run_upgrade(constraint)
|
|
30
|
-
constraint = "" if constraint == "*"
|
|
31
|
-
command = "pyenv exec pipenv upgrade --verbose #{dependency_name}#{constraint}"
|
|
32
|
-
command << " --dev" if lockfile_section == "develop"
|
|
33
|
-
|
|
34
|
-
run(command, fingerprint: "pyenv exec pipenv upgrade --verbose <dependency_name><constraint>")
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
sig { params(constraint: String).returns(T.nilable(String)) }
|
|
38
|
-
def run_upgrade_and_fetch_version(constraint)
|
|
39
|
-
run_upgrade(constraint)
|
|
40
|
-
|
|
41
|
-
updated_lockfile = JSON.parse(File.read("Pipfile.lock"))
|
|
42
|
-
|
|
43
|
-
fetch_version_from_parsed_lockfile(updated_lockfile)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
|
|
47
|
-
def run(command, fingerprint: nil)
|
|
48
|
-
run_command(
|
|
49
|
-
"pyenv local #{language_version_manager.python_major_minor}",
|
|
50
|
-
fingerprint: "pyenv local <python_major_minor>"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
run_command(command, fingerprint: fingerprint)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
sig { returns(Dependabot::Dependency) }
|
|
59
|
-
attr_reader :dependency
|
|
60
|
-
|
|
61
|
-
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
62
|
-
attr_reader :lockfile
|
|
63
|
-
|
|
64
|
-
sig { returns(LanguageVersionManager) }
|
|
65
|
-
attr_reader :language_version_manager
|
|
66
|
-
|
|
67
|
-
sig { params(updated_lockfile: T::Hash[String, T.untyped]).returns(T.nilable(String)) }
|
|
68
|
-
def fetch_version_from_parsed_lockfile(updated_lockfile)
|
|
69
|
-
deps = updated_lockfile[lockfile_section] || {}
|
|
70
|
-
|
|
71
|
-
deps.dig(dependency_name, "version")
|
|
72
|
-
&.gsub(/^==/, "")
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
|
|
76
|
-
def run_command(command, fingerprint: nil)
|
|
77
|
-
SharedHelpers.run_shell_command(command, env: pipenv_env_variables, fingerprint: fingerprint)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
sig { returns(String) }
|
|
81
|
-
def lockfile_section
|
|
82
|
-
if dependency.requirements.any?
|
|
83
|
-
T.must(dependency.requirements.first)[:groups].first
|
|
84
|
-
else
|
|
85
|
-
Uv::FileParser::DEPENDENCY_GROUP_KEYS.each do |keys|
|
|
86
|
-
section = keys.fetch(:lockfile)
|
|
87
|
-
return section if JSON.parse(T.must(T.must(lockfile).content))[section].keys.any?(dependency_name)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
sig { returns(String) }
|
|
93
|
-
def dependency_name
|
|
94
|
-
dependency.metadata[:original_name] || dependency.name
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
sig { returns(T::Hash[String, String]) }
|
|
98
|
-
def pipenv_env_variables
|
|
99
|
-
{
|
|
100
|
-
"PIPENV_YES" => "true", # Install new Python ver if needed
|
|
101
|
-
"PIPENV_MAX_RETRIES" => "3", # Retry timeouts
|
|
102
|
-
"PIPENV_NOSPIN" => "1", # Don't pollute logs with spinner
|
|
103
|
-
"PIPENV_TIMEOUT" => "600", # Set install timeout to 10 minutes
|
|
104
|
-
"PIP_DEFAULT_TIMEOUT" => "60", # Set pip timeout to 1 minute
|
|
105
|
-
"COLUMNS" => "250" # Avoid line wrapping
|
|
106
|
-
}
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|