dependabot-conda 0.325.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/dependabot/conda/file_fetcher.rb +153 -0
- data/lib/dependabot/conda/file_parser.rb +285 -0
- data/lib/dependabot/conda/file_updater.rb +225 -0
- data/lib/dependabot/conda/metadata_finder.rb +71 -0
- data/lib/dependabot/conda/name_normaliser.rb +19 -0
- data/lib/dependabot/conda/package_manager.rb +45 -0
- data/lib/dependabot/conda/python_package_classifier.rb +85 -0
- data/lib/dependabot/conda/requirement.rb +133 -0
- data/lib/dependabot/conda/update_checker/latest_version_finder.rb +91 -0
- data/lib/dependabot/conda/update_checker/requirement_translator.rb +57 -0
- data/lib/dependabot/conda/update_checker.rb +196 -0
- data/lib/dependabot/conda/version.rb +23 -0
- data/lib/dependabot/conda.rb +35 -0
- metadata +294 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/metadata_finders"
|
|
6
|
+
require "dependabot/metadata_finders/base"
|
|
7
|
+
require "dependabot/conda/python_package_classifier"
|
|
8
|
+
require "dependabot/python/metadata_finder"
|
|
9
|
+
|
|
10
|
+
module Dependabot
|
|
11
|
+
module Conda
|
|
12
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
|
13
|
+
extend T::Sig
|
|
14
|
+
|
|
15
|
+
sig { override.returns(T.nilable(String)) }
|
|
16
|
+
def homepage_url
|
|
17
|
+
return super unless python_package?(dependency.name)
|
|
18
|
+
|
|
19
|
+
# Delegate to Python metadata finder for enhanced PyPI-based homepage URLs
|
|
20
|
+
python_metadata_finder.homepage_url
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
sig { override.returns(T.nilable(Dependabot::Source)) }
|
|
26
|
+
def look_up_source
|
|
27
|
+
return nil unless python_package?(dependency.name)
|
|
28
|
+
|
|
29
|
+
# Delegate to Python metadata finder for Python packages
|
|
30
|
+
python_metadata_finder.send(:look_up_source)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { params(package_name: String).returns(T::Boolean) }
|
|
34
|
+
def python_package?(package_name)
|
|
35
|
+
PythonPackageClassifier.python_package?(package_name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sig { returns(Dependabot::Python::MetadataFinder) }
|
|
39
|
+
def python_metadata_finder
|
|
40
|
+
# Cache the Python metadata finder instance for reuse across method calls
|
|
41
|
+
# Credentials are passed through as-is since conda manifests don't specify pip-index credentials
|
|
42
|
+
# TODO: If we decide to support non python packages for Conda we will have to review this
|
|
43
|
+
@python_metadata_finder ||= T.let(
|
|
44
|
+
Dependabot::Python::MetadataFinder.new(
|
|
45
|
+
dependency: python_dependency,
|
|
46
|
+
credentials: credentials
|
|
47
|
+
),
|
|
48
|
+
T.nilable(Dependabot::Python::MetadataFinder)
|
|
49
|
+
)
|
|
50
|
+
@python_metadata_finder
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
sig { returns(Dependabot::Dependency) }
|
|
54
|
+
def python_dependency
|
|
55
|
+
Dependabot::Dependency.new(
|
|
56
|
+
name: dependency.name,
|
|
57
|
+
version: dependency.version,
|
|
58
|
+
requirements: dependency.requirements.map do |req|
|
|
59
|
+
req.merge(
|
|
60
|
+
file: req[:file]&.gsub(/environment\.ya?ml/, "requirements.txt"),
|
|
61
|
+
source: nil # No private pip-index in conda manifests
|
|
62
|
+
)
|
|
63
|
+
end,
|
|
64
|
+
package_manager: "pip"
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Dependabot::MetadataFinders.register("conda", Dependabot::Conda::MetadataFinder)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module Conda
|
|
8
|
+
class NameNormaliser
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { params(name: String).returns(String) }
|
|
12
|
+
def self.normalise(name)
|
|
13
|
+
# Conda package names follow similar rules to Python packages
|
|
14
|
+
# Convert to lowercase and replace underscores/dots with hyphens
|
|
15
|
+
name.downcase.tr("_.", "-")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/ecosystem"
|
|
6
|
+
require "dependabot/conda/version"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module Conda
|
|
10
|
+
ECOSYSTEM = "conda"
|
|
11
|
+
|
|
12
|
+
SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
|
|
13
|
+
DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
|
|
14
|
+
|
|
15
|
+
class CondaPackageManager < Dependabot::Ecosystem::VersionManager
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
NAME = "conda"
|
|
19
|
+
VERSION = "latest"
|
|
20
|
+
|
|
21
|
+
SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
|
|
22
|
+
DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
|
|
23
|
+
|
|
24
|
+
sig { void }
|
|
25
|
+
def initialize
|
|
26
|
+
super(
|
|
27
|
+
name: NAME,
|
|
28
|
+
version: Dependabot::Conda::Version.new(VERSION),
|
|
29
|
+
deprecated_versions: DEPRECATED_VERSIONS,
|
|
30
|
+
supported_versions: SUPPORTED_VERSIONS
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
sig { override.returns(T::Boolean) }
|
|
35
|
+
def deprecated?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
sig { override.returns(T::Boolean) }
|
|
40
|
+
def unsupported?
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
/^r-/i, # R packages (r-base, r-essentials, etc.)
|
|
14
|
+
/^r$/i, # R language itself
|
|
15
|
+
/^python$/i, # Python interpreter (conda-specific, not on PyPI)
|
|
16
|
+
/^git$/i, # Git version control
|
|
17
|
+
/^gcc$/i, # GCC compiler
|
|
18
|
+
/^cmake$/i, # CMake build system
|
|
19
|
+
/^make$/i, # Make build tool
|
|
20
|
+
/^curl$/i, # cURL utility
|
|
21
|
+
/^wget$/i, # Wget utility
|
|
22
|
+
/^vim$/i, # Vim editor
|
|
23
|
+
/^nano$/i, # Nano editor
|
|
24
|
+
/^nodejs$/i, # Node.js runtime
|
|
25
|
+
/^java$/i, # Java runtime
|
|
26
|
+
/^go$/i, # Go language
|
|
27
|
+
/^rust$/i, # Rust language
|
|
28
|
+
/^julia$/i, # Julia language
|
|
29
|
+
/^perl$/i, # Perl language
|
|
30
|
+
/^ruby$/i, # Ruby language
|
|
31
|
+
# System libraries
|
|
32
|
+
/^openssl$/i, # OpenSSL
|
|
33
|
+
/^zlib$/i, # zlib compression
|
|
34
|
+
/^libffi$/i, # Foreign Function Interface library
|
|
35
|
+
/^ncurses$/i, # Terminal control library
|
|
36
|
+
/^readline$/i, # Command line editing
|
|
37
|
+
# Compiler and build tools
|
|
38
|
+
/^_libgcc_mutex$/i,
|
|
39
|
+
/^_openmp_mutex$/i,
|
|
40
|
+
/^binutils$/i,
|
|
41
|
+
/^gxx_linux-64$/i,
|
|
42
|
+
# Multimedia libraries
|
|
43
|
+
/^ffmpeg$/i, # Video processing
|
|
44
|
+
/^opencv$/i, # Computer vision (note: opencv-python is different)
|
|
45
|
+
/^imageio$/i # Image I/O (note: imageio python package is different)
|
|
46
|
+
].freeze, T::Array[Regexp])
|
|
47
|
+
|
|
48
|
+
# Determine if a package name represents a Python package
|
|
49
|
+
sig { params(package_name: String).returns(T::Boolean) }
|
|
50
|
+
def self.python_package?(package_name)
|
|
51
|
+
return false if package_name.empty?
|
|
52
|
+
|
|
53
|
+
# Extract just the package name without version or channel information
|
|
54
|
+
normalized_name = extract_package_name(package_name).downcase.strip
|
|
55
|
+
return false if normalized_name.empty?
|
|
56
|
+
|
|
57
|
+
# Check if it's explicitly a non-Python package
|
|
58
|
+
return false if NON_PYTHON_PATTERNS.any? { |pattern| normalized_name.match?(pattern) }
|
|
59
|
+
|
|
60
|
+
# Block obvious binary/system files
|
|
61
|
+
return false if normalized_name.match?(/\.(exe|dll|so|dylib)$/i)
|
|
62
|
+
return false if normalized_name.match?(/^lib.+\.a$/i) # Static libraries
|
|
63
|
+
|
|
64
|
+
# Block system mutexes
|
|
65
|
+
return false if normalized_name.match?(/^_[a-z0-9]+_mutex$/i)
|
|
66
|
+
|
|
67
|
+
# Default: treat as Python package
|
|
68
|
+
# This aligns with the strategic decision to focus on Python packages
|
|
69
|
+
# Most packages in conda environments are Python packages
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Extract package name from conda specification (remove channel prefix if present)
|
|
74
|
+
sig { params(spec: String).returns(String) }
|
|
75
|
+
def self.extract_package_name(spec)
|
|
76
|
+
# Handle channel specifications like "conda-forge::numpy=1.21.0"
|
|
77
|
+
parts = spec.split("::")
|
|
78
|
+
package_spec = parts.last || spec
|
|
79
|
+
|
|
80
|
+
# Extract package name (before = or space or version operators)
|
|
81
|
+
package_spec.split(/[=<>!~\s]/).first&.strip || spec
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/requirement"
|
|
6
|
+
require "dependabot/python/version"
|
|
7
|
+
require "dependabot/conda/version"
|
|
8
|
+
|
|
9
|
+
module Dependabot
|
|
10
|
+
module Conda
|
|
11
|
+
class Requirement < Dependabot::Requirement
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
# Conda uses different operators than pip:
|
|
15
|
+
# conda: =, >=, >, <, <=, !=, ~
|
|
16
|
+
# pip: ==, >=, >, <, <=, !=, ~=
|
|
17
|
+
|
|
18
|
+
# Support both conda and pip operators
|
|
19
|
+
OPS = T.let(OPS.merge(
|
|
20
|
+
"=" => ->(v, r) { v == r }, # conda equality
|
|
21
|
+
"==" => ->(v, r) { v == r }, # pip equality
|
|
22
|
+
"~=" => ->(v, r) { v >= r && v.release < r.bump } # pip compatible release
|
|
23
|
+
), T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)])
|
|
24
|
+
|
|
25
|
+
quoted = OPS.keys.sort_by(&:length).reverse
|
|
26
|
+
.map { |k| Regexp.quote(k) }.join("|")
|
|
27
|
+
# Use Python version pattern since conda version inherits from it
|
|
28
|
+
version_pattern = Dependabot::Python::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
|
+
|
|
33
|
+
sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
|
|
34
|
+
def self.parse(obj)
|
|
35
|
+
return ["=", obj] if obj.is_a?(Gem::Version)
|
|
36
|
+
|
|
37
|
+
unless (matches = PATTERN.match(obj.to_s))
|
|
38
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
|
39
|
+
raise BadRequirementError, msg
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return DefaultRequirement if matches[:op] == ">=" && matches[:version] == "0"
|
|
43
|
+
|
|
44
|
+
[matches[:op] || "=", Dependabot::Conda::Version.new(T.must(matches[:version]))]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
|
|
48
|
+
def self.requirements_array(requirement_string)
|
|
49
|
+
return [new(nil)] if requirement_string.nil?
|
|
50
|
+
|
|
51
|
+
# Handle complex requirements like ">=1.0.0,<2.0.0"
|
|
52
|
+
requirement_string.strip.split(",").map do |req_string|
|
|
53
|
+
new(req_string.strip)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
|
|
58
|
+
def initialize(*requirements)
|
|
59
|
+
@original_string = T.let(requirements.first&.to_s, T.nilable(String))
|
|
60
|
+
|
|
61
|
+
requirements = requirements.flatten.flat_map do |req_string|
|
|
62
|
+
next if req_string.nil?
|
|
63
|
+
|
|
64
|
+
# Handle complex requirements and convert to Ruby-compatible format
|
|
65
|
+
req_string.split(",").map(&:strip).map do |r|
|
|
66
|
+
convert_conda_constraint_to_ruby_constraint(r)
|
|
67
|
+
end
|
|
68
|
+
end.compact
|
|
69
|
+
|
|
70
|
+
super(requirements)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
|
|
74
|
+
def satisfied_by?(version)
|
|
75
|
+
version = Dependabot::Conda::Version.new(version.to_s)
|
|
76
|
+
|
|
77
|
+
requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sig { returns(T::Boolean) }
|
|
81
|
+
def exact?
|
|
82
|
+
return false unless requirements.size == 1
|
|
83
|
+
|
|
84
|
+
%w(= == ===).include?(requirements[0][0])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
sig { returns(String) }
|
|
88
|
+
def to_s
|
|
89
|
+
@original_string || super
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
sig { params(req_string: String).returns(T.nilable(T.any(String, T::Array[String]))) }
|
|
95
|
+
def convert_conda_constraint_to_ruby_constraint(req_string)
|
|
96
|
+
return nil if req_string.strip.empty?
|
|
97
|
+
return nil if req_string == "*"
|
|
98
|
+
|
|
99
|
+
# Handle conda wildcard patterns like "=1.2.*"
|
|
100
|
+
return convert_wildcard_requirement(req_string) if req_string.match?(/=\s*\d+(\.\d+)*\.\*/)
|
|
101
|
+
|
|
102
|
+
# Handle pip-style compatible release operator
|
|
103
|
+
req_string = req_string.gsub("~=", "~>") if req_string.include?("~=")
|
|
104
|
+
|
|
105
|
+
# Convert conda single = to Ruby = for internal processing
|
|
106
|
+
req_string
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
|
|
110
|
+
def convert_wildcard_requirement(req_string)
|
|
111
|
+
# Convert "=1.2.*" to appropriate range constraints
|
|
112
|
+
version_part = req_string.gsub(/^=\s*/, "").gsub(/\.\*$/, "")
|
|
113
|
+
parts = version_part.split(".")
|
|
114
|
+
|
|
115
|
+
if parts.length == 1
|
|
116
|
+
# "=1.*" becomes ">= 1.0.0, < 2.0.0"
|
|
117
|
+
major = parts[0].to_i
|
|
118
|
+
[">= #{major}.0.0", "< #{major + 1}.0.0"]
|
|
119
|
+
elsif parts.length == 2
|
|
120
|
+
# "=1.2.*" becomes ">= 1.2.0, < 1.3.0"
|
|
121
|
+
major = parts[0].to_i
|
|
122
|
+
minor = parts[1].to_i
|
|
123
|
+
[">= #{major}.#{minor}.0", "< #{major}.#{minor + 1}.0"]
|
|
124
|
+
else
|
|
125
|
+
# Fallback to exact match without wildcard
|
|
126
|
+
"= #{version_part}.0"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
Dependabot::Utils.register_requirement_class("conda", Dependabot::Conda::Requirement)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/package/package_latest_version_finder"
|
|
6
|
+
require "dependabot/python/update_checker/latest_version_finder"
|
|
7
|
+
require "dependabot/dependency"
|
|
8
|
+
require_relative "requirement_translator"
|
|
9
|
+
|
|
10
|
+
module Dependabot
|
|
11
|
+
module Conda
|
|
12
|
+
class UpdateChecker
|
|
13
|
+
class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
|
|
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
|
+
ignored_versions: T::Array[String],
|
|
22
|
+
raise_on_ignored: T::Boolean,
|
|
23
|
+
security_advisories: T::Array[Dependabot::SecurityAdvisory],
|
|
24
|
+
cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
|
|
25
|
+
).void
|
|
26
|
+
end
|
|
27
|
+
def initialize(dependency:, dependency_files:, credentials:,
|
|
28
|
+
ignored_versions:, raise_on_ignored:, security_advisories:,
|
|
29
|
+
cooldown_options:)
|
|
30
|
+
@raise_on_ignored = T.let(raise_on_ignored, T::Boolean)
|
|
31
|
+
@cooldown_options = T.let(cooldown_options, T.nilable(Dependabot::Package::ReleaseCooldownOptions))
|
|
32
|
+
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { override.returns(T.nilable(Dependabot::Package::PackageDetails)) }
|
|
37
|
+
def package_details
|
|
38
|
+
@package_details ||= python_latest_version_finder.package_details
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
sig { override.returns(T::Boolean) }
|
|
42
|
+
def cooldown_enabled?
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
sig { returns(Dependabot::Python::UpdateChecker::LatestVersionFinder) }
|
|
49
|
+
def python_latest_version_finder
|
|
50
|
+
@python_latest_version_finder ||= T.let(
|
|
51
|
+
Dependabot::Python::UpdateChecker::LatestVersionFinder.new(
|
|
52
|
+
dependency: python_compatible_dependency,
|
|
53
|
+
dependency_files: dependency_files,
|
|
54
|
+
credentials: credentials,
|
|
55
|
+
ignored_versions: ignored_versions,
|
|
56
|
+
raise_on_ignored: @raise_on_ignored,
|
|
57
|
+
security_advisories: security_advisories,
|
|
58
|
+
cooldown_options: @cooldown_options
|
|
59
|
+
),
|
|
60
|
+
T.nilable(Dependabot::Python::UpdateChecker::LatestVersionFinder)
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
sig { returns(Dependabot::Dependency) }
|
|
65
|
+
def python_compatible_dependency
|
|
66
|
+
# Convert conda dependency to python-compatible dependency
|
|
67
|
+
Dependabot::Dependency.new(
|
|
68
|
+
name: dependency.name,
|
|
69
|
+
version: dependency.version,
|
|
70
|
+
requirements: python_compatible_requirements,
|
|
71
|
+
package_manager: "pip" # Use pip for PyPI compatibility
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
76
|
+
def python_compatible_requirements
|
|
77
|
+
dependency.requirements.map do |req|
|
|
78
|
+
req.merge(
|
|
79
|
+
requirement: convert_conda_requirement_to_pip(req[:requirement])
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
sig { params(conda_requirement: T.nilable(String)).returns(T.nilable(String)) }
|
|
85
|
+
def convert_conda_requirement_to_pip(conda_requirement)
|
|
86
|
+
RequirementTranslator.conda_to_pip(conda_requirement)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module Conda
|
|
8
|
+
class UpdateChecker
|
|
9
|
+
class RequirementTranslator
|
|
10
|
+
extend T::Sig
|
|
11
|
+
|
|
12
|
+
# Convert conda-style requirements to pip-compatible requirements
|
|
13
|
+
sig { params(conda_requirement: T.nilable(String)).returns(T.nilable(String)) }
|
|
14
|
+
def self.conda_to_pip(conda_requirement)
|
|
15
|
+
return nil unless conda_requirement
|
|
16
|
+
|
|
17
|
+
# Handle different conda requirement patterns
|
|
18
|
+
case conda_requirement
|
|
19
|
+
when /^=([0-9]+(?:\.[0-9]+)*)\.\*$/
|
|
20
|
+
# Handle wildcards: =1.21.* -> >=1.21.0,<1.22.0
|
|
21
|
+
convert_wildcard_requirement(conda_requirement)
|
|
22
|
+
when /^=([0-9]+(?:\.[0-9]+)*)$/
|
|
23
|
+
# Handle exact equality: =1.2.3 -> ==1.2.3
|
|
24
|
+
conda_requirement.gsub(/^=/, "==")
|
|
25
|
+
when /^(>=|>|<=|<|!=)(.+)$/
|
|
26
|
+
# Handle comparison operators: >=1.2.0 -> >=1.2.0 (no change)
|
|
27
|
+
conda_requirement
|
|
28
|
+
when /^([0-9]+(?:\.[0-9]+)*)$/
|
|
29
|
+
# Handle bare version: 1.2.3 -> ==1.2.3
|
|
30
|
+
"==#{conda_requirement}"
|
|
31
|
+
else
|
|
32
|
+
# Handle complex constraints: >=3.8,<3.11 -> >=3.8,<3.11 (no change)
|
|
33
|
+
conda_requirement
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { params(wildcard_requirement: String).returns(String) }
|
|
38
|
+
def self.convert_wildcard_requirement(wildcard_requirement)
|
|
39
|
+
# Extract version pattern: =1.21.* -> 1.21
|
|
40
|
+
version_match = wildcard_requirement.match(/^=([0-9]+(?:\.[0-9]+)*)\.\*$/)
|
|
41
|
+
return wildcard_requirement unless version_match
|
|
42
|
+
|
|
43
|
+
base_version = version_match[1]
|
|
44
|
+
version_parts = T.must(base_version).split(".")
|
|
45
|
+
|
|
46
|
+
# Calculate next version for upper bound
|
|
47
|
+
next_version_parts = version_parts.dup
|
|
48
|
+
next_version_parts[-1] = (next_version_parts[-1].to_i + 1).to_s
|
|
49
|
+
next_version = next_version_parts.join(".")
|
|
50
|
+
|
|
51
|
+
# Return range: >=1.21.0,<1.22.0
|
|
52
|
+
">=#{base_version}.0,<#{next_version}.0"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|