dependabot-conda 0.349.0 → 0.351.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/conda/conda_registry_client.rb +220 -0
- data/lib/dependabot/conda/file_fetcher.rb +108 -66
- data/lib/dependabot/conda/file_parser.rb +40 -38
- data/lib/dependabot/conda/file_updater.rb +40 -16
- data/lib/dependabot/conda/metadata_finder.rb +8 -15
- data/lib/dependabot/conda/update_checker/latest_version_finder.rb +111 -6
- data/lib/dependabot/conda/update_checker/requirements_updater.rb +428 -0
- data/lib/dependabot/conda/update_checker.rb +16 -63
- data/lib/dependabot/conda/version.rb +317 -6
- data/lib/dependabot/conda.rb +4 -3
- metadata +11 -10
- data/lib/dependabot/conda/python_package_classifier.rb +0 -88
|
@@ -1,21 +1,332 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
-
require "dependabot/
|
|
5
|
+
require "dependabot/version"
|
|
6
|
+
require "dependabot/utils"
|
|
6
7
|
|
|
7
8
|
module Dependabot
|
|
8
9
|
module Conda
|
|
9
|
-
# Conda version handling
|
|
10
|
-
|
|
10
|
+
# Conda version handling based on conda's version specification
|
|
11
|
+
# See: https://docs.conda.io/projects/conda/en/stable/user-guide/concepts/pkg-specs.html
|
|
12
|
+
#
|
|
13
|
+
# Version format: [epoch!]version[+local]
|
|
14
|
+
#
|
|
15
|
+
# Components:
|
|
16
|
+
# - Epoch (optional): Integer prefix with ! separator (e.g., "1!2.0")
|
|
17
|
+
# - Version: Main version identifier with segments separated by . or _
|
|
18
|
+
# - Local version (optional): Build metadata with + prefix (e.g., "1.0+abc.7")
|
|
19
|
+
#
|
|
20
|
+
# Special handling:
|
|
21
|
+
# - "dev" pre-releases sort before all other pre-releases
|
|
22
|
+
# - "post" releases sort after the main version
|
|
23
|
+
# - Underscores are normalized to dots
|
|
24
|
+
# - Case-insensitive string comparison
|
|
25
|
+
# - Fillvalue 0 insertion for missing segments
|
|
26
|
+
# - Integer < String in mixed-type segment comparison (numeric before pre-release)
|
|
27
|
+
class Version < Dependabot::Version
|
|
11
28
|
extend T::Sig
|
|
12
29
|
|
|
13
|
-
|
|
14
|
-
|
|
30
|
+
VERSION_PATTERN = /\A[a-z0-9_.!+\-]+\z/i
|
|
31
|
+
|
|
32
|
+
sig { override.params(version: VersionParameter).returns(T::Boolean) }
|
|
33
|
+
def self.correct?(version)
|
|
34
|
+
return false if version.nil? || version.to_s.empty?
|
|
35
|
+
|
|
36
|
+
version.to_s.match?(VERSION_PATTERN)
|
|
37
|
+
end
|
|
38
|
+
|
|
15
39
|
sig { override.params(version: VersionParameter).returns(Dependabot::Conda::Version) }
|
|
16
40
|
def self.new(version)
|
|
17
41
|
T.cast(super, Dependabot::Conda::Version)
|
|
18
42
|
end
|
|
43
|
+
|
|
44
|
+
sig { returns(Integer) }
|
|
45
|
+
attr_reader :epoch
|
|
46
|
+
|
|
47
|
+
sig { returns(T::Array[T.any(Integer, String)]) }
|
|
48
|
+
attr_reader :version_parts
|
|
49
|
+
|
|
50
|
+
sig { returns(T.nilable(T::Array[T.any(Integer, String)])) }
|
|
51
|
+
attr_reader :local_parts
|
|
52
|
+
|
|
53
|
+
VersionParts = T.let(Struct.new(:epoch, :main, :local, keyword_init: true), T.untyped)
|
|
54
|
+
private_constant :VersionParts
|
|
55
|
+
|
|
56
|
+
sig { override.params(version: VersionParameter).void }
|
|
57
|
+
def initialize(version)
|
|
58
|
+
@version_string = T.let(version.to_s, String)
|
|
59
|
+
|
|
60
|
+
raise ArgumentError, "Malformed version string #{version}" unless self.class.correct?(version)
|
|
61
|
+
|
|
62
|
+
# Validate no empty segments
|
|
63
|
+
validate_version!(@version_string)
|
|
64
|
+
|
|
65
|
+
# Parse epoch, main version, and local version
|
|
66
|
+
parts = parse_epoch_and_local(@version_string)
|
|
67
|
+
|
|
68
|
+
@epoch = T.let(parts.epoch.to_i, Integer)
|
|
69
|
+
@version_parts = T.let(parse_components(parts.main), T::Array[T.any(Integer, String)])
|
|
70
|
+
@local_parts = T.let(
|
|
71
|
+
parts.local ? parse_components(parts.local) : nil,
|
|
72
|
+
T.nilable(T::Array[T.any(Integer, String)])
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
super
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
|
|
79
|
+
def <=>(other)
|
|
80
|
+
return nil unless other.is_a?(Dependabot::Conda::Version)
|
|
81
|
+
|
|
82
|
+
# Step 1: Compare epochs (numerically)
|
|
83
|
+
epoch_comparison = @epoch <=> other.epoch
|
|
84
|
+
return epoch_comparison unless epoch_comparison.zero?
|
|
85
|
+
|
|
86
|
+
# Step 2: Compare version parts (segment by segment with fillvalue)
|
|
87
|
+
version_comparison = compare_parts(@version_parts, other.version_parts)
|
|
88
|
+
return version_comparison unless version_comparison.zero?
|
|
89
|
+
|
|
90
|
+
# Step 3: Compare local parts (if present)
|
|
91
|
+
compare_local_parts(@local_parts, other.local_parts)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# Validates version string for malformed segments
|
|
97
|
+
sig { params(version_str: String).void }
|
|
98
|
+
def validate_version!(version_str)
|
|
99
|
+
main_version = parse_epoch_and_local(version_str).main
|
|
100
|
+
|
|
101
|
+
# Check for empty segments (consecutive dots, leading/trailing dots)
|
|
102
|
+
return unless main_version.include?("..") || main_version.match?(/^\./) || main_version.match?(/\.$/)
|
|
103
|
+
|
|
104
|
+
raise ArgumentError, "Empty version segments not allowed in #{version_str}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Parses epoch and local version from version string
|
|
108
|
+
# Returns VersionParts struct with epoch, main, and local fields
|
|
109
|
+
sig { params(version_str: String).returns(T.untyped) }
|
|
110
|
+
def parse_epoch_and_local(version_str)
|
|
111
|
+
# Split on '!' for epoch
|
|
112
|
+
parts = version_str.split("!", 2)
|
|
113
|
+
if parts.length == 2
|
|
114
|
+
epoch_str = parts[0]
|
|
115
|
+
remainder = T.must(parts[1])
|
|
116
|
+
else
|
|
117
|
+
epoch_str = "0"
|
|
118
|
+
remainder = parts[0]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Split on '+' for local version
|
|
122
|
+
parts = T.must(remainder).split("+", 2)
|
|
123
|
+
main_version = parts[0]
|
|
124
|
+
local_version = parts[1]
|
|
125
|
+
|
|
126
|
+
VersionParts.new(epoch: epoch_str, main: T.must(main_version), local: local_version)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Parses version components into normalized segments
|
|
130
|
+
# Normalizes underscores to dots, handles special strings (dev, post)
|
|
131
|
+
# Splits alphanumeric segments like "a1" into ["a", 1]
|
|
132
|
+
sig { params(version_str: String).returns(T::Array[T.any(Integer, String)]) }
|
|
133
|
+
def parse_components(version_str)
|
|
134
|
+
# Normalize underscores to dots
|
|
135
|
+
normalized = version_str.tr("_", ".")
|
|
136
|
+
|
|
137
|
+
# Split on dots and dashes
|
|
138
|
+
raw_segments = normalized.split(/[.\-]/)
|
|
139
|
+
|
|
140
|
+
# Process each segment
|
|
141
|
+
segments = T.let([], T::Array[T.any(Integer, String)])
|
|
142
|
+
raw_segments.each do |segment|
|
|
143
|
+
next if segment.empty?
|
|
144
|
+
|
|
145
|
+
process_segment(segment, segments)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
segments
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Process a single version segment and add results to segments array
|
|
152
|
+
sig do
|
|
153
|
+
params(
|
|
154
|
+
segment: String,
|
|
155
|
+
segments: T::Array[T.any(Integer, String)]
|
|
156
|
+
).void
|
|
157
|
+
end
|
|
158
|
+
def process_segment(segment, segments)
|
|
159
|
+
# Key insight: Pre-release markers are only recognized when EMBEDDED in
|
|
160
|
+
# numeric segments (e.g., "0a1" in "1.0a1"), NOT when they appear as
|
|
161
|
+
# separate dot-delimited components (e.g., "rc1" in "2.rc1").
|
|
162
|
+
lower_seg = segment.downcase
|
|
163
|
+
has_embedded_prerelease = embedded_prerelease?(lower_seg)
|
|
164
|
+
|
|
165
|
+
subsegments = T.cast(segment.scan(/\d+|[a-z]+/i), T::Array[String])
|
|
166
|
+
|
|
167
|
+
if has_embedded_prerelease
|
|
168
|
+
process_prerelease_subsegments(subsegments, segments)
|
|
169
|
+
else
|
|
170
|
+
process_normal_subsegments(subsegments, segments)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Check if segment contains an embedded pre-release marker
|
|
175
|
+
sig { params(lower_seg: String).returns(T::Boolean) }
|
|
176
|
+
def embedded_prerelease?(lower_seg)
|
|
177
|
+
# Embedded: digits + a/b/rc + required digits (e.g., "0a1", "10b2", "2rc1")
|
|
178
|
+
lower_seg.match?(/^\d+(a|alpha|b|beta|rc|c)\d+$/) ||
|
|
179
|
+
# Embedded: digits + dev/post + optional digits (e.g., "0dev", "10post1")
|
|
180
|
+
lower_seg.match?(/^\d+(dev|post)\d*$/)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Process subsegments that contain pre-release markers
|
|
184
|
+
sig do
|
|
185
|
+
params(
|
|
186
|
+
subsegments: T::Array[String],
|
|
187
|
+
segments: T::Array[T.any(Integer, String)]
|
|
188
|
+
).void
|
|
189
|
+
end
|
|
190
|
+
def process_prerelease_subsegments(subsegments, segments)
|
|
191
|
+
subsegments.each do |subseg|
|
|
192
|
+
lower_subseg = subseg.downcase
|
|
193
|
+
segments << if lower_subseg.match?(/^(dev|a|alpha|b|beta|rc|c|post)$/)
|
|
194
|
+
normalize_prerelease_segment(subseg)
|
|
195
|
+
elsif subseg.match?(/^\d+$/)
|
|
196
|
+
subseg.to_i
|
|
197
|
+
else
|
|
198
|
+
subseg.downcase
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Process normal subsegments (no pre-release markers)
|
|
204
|
+
sig do
|
|
205
|
+
params(
|
|
206
|
+
subsegments: T::Array[String],
|
|
207
|
+
segments: T::Array[T.any(Integer, String)]
|
|
208
|
+
).void
|
|
209
|
+
end
|
|
210
|
+
def process_normal_subsegments(subsegments, segments)
|
|
211
|
+
subsegments.each do |subseg|
|
|
212
|
+
segments << if subseg.match?(/^\d+$/)
|
|
213
|
+
subseg.to_i
|
|
214
|
+
else
|
|
215
|
+
subseg.downcase
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Normalizes a pre-release or post-release segment
|
|
221
|
+
# Only called for confirmed pre-release/post-release patterns
|
|
222
|
+
sig { params(segment: String).returns(T.any(Integer, String)) }
|
|
223
|
+
def normalize_prerelease_segment(segment)
|
|
224
|
+
lower_segment = segment.downcase
|
|
225
|
+
|
|
226
|
+
# Pre-releases: use negative integers to sort before 0
|
|
227
|
+
return -4 if dev_prerelease?(lower_segment)
|
|
228
|
+
return -3 if alpha_prerelease?(lower_segment)
|
|
229
|
+
return -2 if beta_prerelease?(lower_segment)
|
|
230
|
+
return -1 if rc_prerelease?(lower_segment)
|
|
231
|
+
|
|
232
|
+
# Post-releases: use ~ prefix to sort after everything
|
|
233
|
+
return "~#{lower_segment}" if post_release?(lower_segment)
|
|
234
|
+
|
|
235
|
+
lower_segment
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Check if segment is dev pre-release
|
|
239
|
+
sig { params(lower_segment: String).returns(T::Boolean) }
|
|
240
|
+
def dev_prerelease?(lower_segment)
|
|
241
|
+
lower_segment == "dev" || lower_segment.match?(/^dev\d/)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Check if segment is alpha pre-release
|
|
245
|
+
sig { params(lower_segment: String).returns(T::Boolean) }
|
|
246
|
+
def alpha_prerelease?(lower_segment)
|
|
247
|
+
lower_segment == "a" || lower_segment == "alpha" || lower_segment.match?(/^(a|alpha)\d/)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Check if segment is beta pre-release
|
|
251
|
+
sig { params(lower_segment: String).returns(T::Boolean) }
|
|
252
|
+
def beta_prerelease?(lower_segment)
|
|
253
|
+
lower_segment == "b" || lower_segment == "beta" || lower_segment.match?(/^(b|beta)\d/)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Check if segment is rc pre-release
|
|
257
|
+
sig { params(lower_segment: String).returns(T::Boolean) }
|
|
258
|
+
def rc_prerelease?(lower_segment)
|
|
259
|
+
lower_segment == "rc" || lower_segment == "c" || lower_segment.match?(/^(rc|c)\d/)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Check if segment is post-release
|
|
263
|
+
sig { params(lower_segment: String).returns(T::Boolean) }
|
|
264
|
+
def post_release?(lower_segment)
|
|
265
|
+
lower_segment == "post" || lower_segment.start_with?("post")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Compares two arrays of version parts with fillvalue insertion
|
|
269
|
+
sig do
|
|
270
|
+
params(
|
|
271
|
+
parts1: T::Array[T.any(Integer, String)],
|
|
272
|
+
parts2: T::Array[T.any(Integer, String)]
|
|
273
|
+
).returns(Integer)
|
|
274
|
+
end
|
|
275
|
+
def compare_parts(parts1, parts2)
|
|
276
|
+
max_length = [parts1.length, parts2.length].max
|
|
277
|
+
|
|
278
|
+
max_length.times do |i|
|
|
279
|
+
# Insert fillvalue 0 for missing segments
|
|
280
|
+
seg1 = parts1[i] || 0
|
|
281
|
+
seg2 = parts2[i] || 0
|
|
282
|
+
|
|
283
|
+
result = compare_single_segment(seg1, seg2)
|
|
284
|
+
return result unless result.zero?
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
0 # All segments equal
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Compares two individual segments
|
|
291
|
+
# Rules:
|
|
292
|
+
# - Both integers: numeric comparison
|
|
293
|
+
# - Both strings: case-insensitive lexicographic comparison
|
|
294
|
+
# - Mixed types: Integer < String (numeric versions sort before pre-releases)
|
|
295
|
+
sig { params(seg1: T.any(Integer, String), seg2: T.any(Integer, String)).returns(Integer) }
|
|
296
|
+
def compare_single_segment(seg1, seg2)
|
|
297
|
+
# Both integers: numeric comparison
|
|
298
|
+
return seg1 <=> seg2 if seg1.is_a?(Integer) && seg2.is_a?(Integer)
|
|
299
|
+
|
|
300
|
+
# Both strings: case-insensitive lexicographic
|
|
301
|
+
# (already normalized to lowercase in parse_components)
|
|
302
|
+
return T.must(seg1 <=> seg2) if seg1.is_a?(String) && seg2.is_a?(String)
|
|
303
|
+
|
|
304
|
+
# Mixed types: Integer < String
|
|
305
|
+
# This means 1.0.0 (with fillvalue 0) < 1.0.0a (with "a")
|
|
306
|
+
seg1.is_a?(Integer) ? -1 : 1
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Compares local version parts
|
|
310
|
+
# - nil local version sorts before any local version
|
|
311
|
+
# - Both nil: equal
|
|
312
|
+
# - Otherwise compare using same rules as version parts
|
|
313
|
+
sig do
|
|
314
|
+
params(
|
|
315
|
+
local1: T.nilable(T::Array[T.any(Integer, String)]),
|
|
316
|
+
local2: T.nilable(T::Array[T.any(Integer, String)])
|
|
317
|
+
).returns(Integer)
|
|
318
|
+
end
|
|
319
|
+
def compare_local_parts(local1, local2)
|
|
320
|
+
# Both nil: equal
|
|
321
|
+
return 0 if local1.nil? && local2.nil?
|
|
322
|
+
|
|
323
|
+
# One nil: nil sorts before any local version
|
|
324
|
+
return -1 if local1.nil?
|
|
325
|
+
return 1 if local2.nil?
|
|
326
|
+
|
|
327
|
+
# Both present: compare using same rules as version parts
|
|
328
|
+
compare_parts(local1, local2)
|
|
329
|
+
end
|
|
19
330
|
end
|
|
20
331
|
end
|
|
21
332
|
end
|
data/lib/dependabot/conda.rb
CHANGED
|
@@ -17,15 +17,16 @@ Dependabot::PullRequestCreator::Labeler
|
|
|
17
17
|
.register_label_details("conda", name: "conda", colour: "44a047")
|
|
18
18
|
|
|
19
19
|
require "dependabot/dependency"
|
|
20
|
-
# Conda manages
|
|
20
|
+
# Conda manages packages from multiple ecosystems (Python, R, Julia, system tools)
|
|
21
|
+
# and can also contain pip dependencies for Python packages from PyPI
|
|
21
22
|
Dependabot::Dependency.register_production_check(
|
|
22
23
|
"conda",
|
|
23
24
|
lambda do |groups|
|
|
24
25
|
return true if groups.empty?
|
|
25
26
|
return true if groups.include?("default")
|
|
26
|
-
return true if groups.include?("dependencies")
|
|
27
|
+
return true if groups.include?("dependencies") # Conda packages
|
|
27
28
|
|
|
28
|
-
groups.include?("pip")
|
|
29
|
+
groups.include?("pip") # Pip packages (Python from PyPI)
|
|
29
30
|
end
|
|
30
31
|
)
|
|
31
32
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-conda
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.351.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.351.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.351.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: dependabot-python
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.351.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.351.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: debug
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -247,33 +247,34 @@ dependencies:
|
|
|
247
247
|
- - "~>"
|
|
248
248
|
- !ruby/object:Gem::Version
|
|
249
249
|
version: '1.9'
|
|
250
|
-
description: Dependabot-Conda provides support for
|
|
251
|
-
environment.yml files
|
|
252
|
-
|
|
250
|
+
description: Dependabot-Conda provides support for updating Conda packages defined
|
|
251
|
+
in Conda environment.yml files. Routes conda packages to Conda channel APIs and
|
|
252
|
+
pip packages to PyPI for accurate version information.
|
|
253
253
|
email: opensource@github.com
|
|
254
254
|
executables: []
|
|
255
255
|
extensions: []
|
|
256
256
|
extra_rdoc_files: []
|
|
257
257
|
files:
|
|
258
258
|
- lib/dependabot/conda.rb
|
|
259
|
+
- lib/dependabot/conda/conda_registry_client.rb
|
|
259
260
|
- lib/dependabot/conda/file_fetcher.rb
|
|
260
261
|
- lib/dependabot/conda/file_parser.rb
|
|
261
262
|
- lib/dependabot/conda/file_updater.rb
|
|
262
263
|
- lib/dependabot/conda/metadata_finder.rb
|
|
263
264
|
- lib/dependabot/conda/name_normaliser.rb
|
|
264
265
|
- lib/dependabot/conda/package_manager.rb
|
|
265
|
-
- lib/dependabot/conda/python_package_classifier.rb
|
|
266
266
|
- lib/dependabot/conda/requirement.rb
|
|
267
267
|
- lib/dependabot/conda/update_checker.rb
|
|
268
268
|
- lib/dependabot/conda/update_checker/latest_version_finder.rb
|
|
269
269
|
- lib/dependabot/conda/update_checker/requirement_translator.rb
|
|
270
|
+
- lib/dependabot/conda/update_checker/requirements_updater.rb
|
|
270
271
|
- lib/dependabot/conda/version.rb
|
|
271
272
|
homepage: https://github.com/dependabot/dependabot-core
|
|
272
273
|
licenses:
|
|
273
274
|
- MIT
|
|
274
275
|
metadata:
|
|
275
276
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
276
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
277
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.351.0
|
|
277
278
|
rdoc_options: []
|
|
278
279
|
require_paths:
|
|
279
280
|
- lib
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# typed: strong
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "sorbet-runtime"
|
|
5
|
-
|
|
6
|
-
module Dependabot
|
|
7
|
-
module Conda
|
|
8
|
-
class PythonPackageClassifier
|
|
9
|
-
extend T::Sig
|
|
10
|
-
|
|
11
|
-
# Known non-Python packages that should be ignored
|
|
12
|
-
NON_PYTHON_PATTERNS = T.let(
|
|
13
|
-
[
|
|
14
|
-
/^r-/i, # R packages (r-base, r-essentials, etc.)
|
|
15
|
-
/^r$/i, # R language itself
|
|
16
|
-
/^python$/i, # Python interpreter (conda-specific, not on PyPI)
|
|
17
|
-
/^git$/i, # Git version control
|
|
18
|
-
/^gcc$/i, # GCC compiler
|
|
19
|
-
/^cmake$/i, # CMake build system
|
|
20
|
-
/^make$/i, # Make build tool
|
|
21
|
-
/^curl$/i, # cURL utility
|
|
22
|
-
/^wget$/i, # Wget utility
|
|
23
|
-
/^vim$/i, # Vim editor
|
|
24
|
-
/^nano$/i, # Nano editor
|
|
25
|
-
/^nodejs$/i, # Node.js runtime
|
|
26
|
-
/^java$/i, # Java runtime
|
|
27
|
-
/^go$/i, # Go language
|
|
28
|
-
/^rust$/i, # Rust language
|
|
29
|
-
/^julia$/i, # Julia language
|
|
30
|
-
/^perl$/i, # Perl language
|
|
31
|
-
/^ruby$/i, # Ruby language
|
|
32
|
-
# System libraries
|
|
33
|
-
/^openssl$/i, # OpenSSL
|
|
34
|
-
/^zlib$/i, # zlib compression
|
|
35
|
-
/^libffi$/i, # Foreign Function Interface library
|
|
36
|
-
/^ncurses$/i, # Terminal control library
|
|
37
|
-
/^readline$/i, # Command line editing
|
|
38
|
-
# Compiler and build tools
|
|
39
|
-
/^_libgcc_mutex$/i,
|
|
40
|
-
/^_openmp_mutex$/i,
|
|
41
|
-
/^binutils$/i,
|
|
42
|
-
/^gxx_linux-64$/i,
|
|
43
|
-
# Multimedia libraries
|
|
44
|
-
/^ffmpeg$/i, # Video processing
|
|
45
|
-
/^opencv$/i, # Computer vision (note: opencv-python is different)
|
|
46
|
-
/^imageio$/i # Image I/O (note: imageio python package is different)
|
|
47
|
-
].freeze,
|
|
48
|
-
T::Array[Regexp]
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# Determine if a package name represents a Python package
|
|
52
|
-
sig { params(package_name: String).returns(T::Boolean) }
|
|
53
|
-
def self.python_package?(package_name)
|
|
54
|
-
return false if package_name.empty?
|
|
55
|
-
|
|
56
|
-
# Extract just the package name without version or channel information
|
|
57
|
-
normalized_name = extract_package_name(package_name).downcase.strip
|
|
58
|
-
return false if normalized_name.empty?
|
|
59
|
-
|
|
60
|
-
# Check if it's explicitly a non-Python package
|
|
61
|
-
return false if NON_PYTHON_PATTERNS.any? { |pattern| normalized_name.match?(pattern) }
|
|
62
|
-
|
|
63
|
-
# Block obvious binary/system files
|
|
64
|
-
return false if normalized_name.match?(/\.(exe|dll|so|dylib)$/i)
|
|
65
|
-
return false if normalized_name.match?(/^lib.+\.a$/i) # Static libraries
|
|
66
|
-
|
|
67
|
-
# Block system mutexes
|
|
68
|
-
return false if normalized_name.match?(/^_[a-z0-9]+_mutex$/i)
|
|
69
|
-
|
|
70
|
-
# Default: treat as Python package
|
|
71
|
-
# This aligns with the strategic decision to focus on Python packages
|
|
72
|
-
# Most packages in conda environments are Python packages
|
|
73
|
-
true
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Extract package name from conda specification (remove channel prefix if present)
|
|
77
|
-
sig { params(spec: String).returns(String) }
|
|
78
|
-
def self.extract_package_name(spec)
|
|
79
|
-
# Handle channel specifications like "conda-forge::numpy=1.21.0"
|
|
80
|
-
parts = spec.split("::")
|
|
81
|
-
package_spec = parts.last || spec
|
|
82
|
-
|
|
83
|
-
# Extract package name (before = or space or version operators)
|
|
84
|
-
package_spec.split(/[=<>!~\s]/).first&.strip || spec
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|