dependabot-uv 0.363.0 → 0.364.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dependabot/uv/dependency_grapher.rb +196 -0
- data/lib/dependabot/uv/file_parser.rb +16 -2
- data/lib/dependabot/uv/file_updater/lock_file_updater.rb +15 -15
- data/lib/dependabot/uv.rb +1 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66f81d0ac637cdaa3193618598079972af284af23add69f280b5dc5eb26e988d
|
|
4
|
+
data.tar.gz: 0a10dc4db04f2e311feef435dc31c35dabf6f15ad8f02b6ba5d64daef384a615
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 186c1e97d18b0166ffd052abafbaffcfe18b880daf3c54dc6e065d5ae6f525c7c58d5813205339daa3821ca9e3aae619c311c402ce888009f6b7eece1d88fb90
|
|
7
|
+
data.tar.gz: e6c02a262d85ac3c593603cdf2e2ed519c3174470e1fe36827708297db5ec191da41c2cb96d63d22ecab5d46a8f03712d2ac2430bcab6a784b29a964ee40da82
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require "dependabot/dependency_graphers"
|
|
7
|
+
require "dependabot/dependency_graphers/base"
|
|
8
|
+
require "dependabot/uv/file_parser"
|
|
9
|
+
require "toml-rb"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Uv
|
|
13
|
+
class DependencyGrapher < Dependabot::DependencyGraphers::Base
|
|
14
|
+
UV_LOCK_COMMAND = T.let("pyenv exec uv lock --color never --no-progress && cat uv.lock", String)
|
|
15
|
+
UV_TREE_COMMAND = T.let("pyenv exec uv tree -q --color never --no-progress --frozen", String)
|
|
16
|
+
|
|
17
|
+
# Used to capture package lines from `uv tree` output.
|
|
18
|
+
#
|
|
19
|
+
# Example output:
|
|
20
|
+
# ├── flask v3.1.3
|
|
21
|
+
# │ ├── click v8.3.1
|
|
22
|
+
# │ └── jinja2 v3.1.6
|
|
23
|
+
# │ └── markupsafe v3.0.3
|
|
24
|
+
#
|
|
25
|
+
# The `prefix` contains tree-depth segments (`│ ` or ` `) and
|
|
26
|
+
# `package` is the dependency name token before the `v<version>` marker.
|
|
27
|
+
UV_TREE_LINE_REGEX = T.let(
|
|
28
|
+
/^(?<prefix>(?:(?:│ )|(?: ))*)(?:├──|└──)\s(?<package>.+?)\sv[^\s]+(?:\s+\(.*\))?$/,
|
|
29
|
+
Regexp
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
sig { override.returns(Dependabot::DependencyFile) }
|
|
33
|
+
def relevant_dependency_file
|
|
34
|
+
# This cannot realistically happen as the parser will throw a runtime error
|
|
35
|
+
# on init without a pyproject.toml file,
|
|
36
|
+
# but this will avoid surprises if anything changes.
|
|
37
|
+
raise DependabotError, "No pyproject.toml present in dependency files." unless pyproject_toml
|
|
38
|
+
|
|
39
|
+
T.must(pyproject_toml)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
sig { override.params(dependency: Dependabot::Dependency).returns(T::Array[String]) }
|
|
45
|
+
def fetch_subdependencies(dependency)
|
|
46
|
+
dependency_names = @dependencies.map(&:name)
|
|
47
|
+
package_relationships.fetch(dependency.name, []).select { |child| dependency_names.include?(child) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sig { returns(T::Hash[String, T::Array[String]]) }
|
|
51
|
+
def package_relationships
|
|
52
|
+
@package_relationships ||= T.let(
|
|
53
|
+
fetch_package_relationships,
|
|
54
|
+
T.nilable(T::Hash[String, T::Array[String]])
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# See UV tree docs https://docs.astral.sh/uv/reference/cli/#uv-tree
|
|
59
|
+
# First try extracting relationships from uv.lock directly. If there is no
|
|
60
|
+
# lockfile, generate one in a temporary parsed context and parse that.
|
|
61
|
+
# If lockfile parsing fails for any reason, fall back to uv tree output.
|
|
62
|
+
sig { returns(T::Hash[String, T::Array[String]]) }
|
|
63
|
+
def fetch_package_relationships
|
|
64
|
+
return package_relationships_from_lockfile(T.must(T.must(uv_lock).content)) if uv_lock
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
Dependabot.logger.info("No uv.lock present, generating ephemeral lockfile for dependency graphing")
|
|
68
|
+
generated_lockfile = uv_parser.run_in_parsed_context(UV_LOCK_COMMAND)
|
|
69
|
+
return package_relationships_from_lockfile(generated_lockfile)
|
|
70
|
+
rescue StandardError => e
|
|
71
|
+
Dependabot.logger.warn("Failed to build dependency graph from uv.lock: #{e.message}")
|
|
72
|
+
Dependabot.logger.info("Falling back to parsing uv tree output")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
package_relationships_from_tree
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
sig { params(lockfile_content: String).returns(T::Hash[String, T::Array[String]]) }
|
|
79
|
+
def package_relationships_from_lockfile(lockfile_content)
|
|
80
|
+
lockfile_packages(lockfile_content).each_with_object({}) do |package_data, rels|
|
|
81
|
+
parent = lockfile_parent_name(package_data)
|
|
82
|
+
next unless parent
|
|
83
|
+
|
|
84
|
+
rels[parent] ||= []
|
|
85
|
+
rels[parent].concat(lockfile_child_names(package_data))
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
Dependabot.logger.warn("Failed to parse uv.lock relationships: #{e.message}")
|
|
89
|
+
{}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
sig { params(lockfile_content: String).returns(T::Array[T.untyped]) }
|
|
93
|
+
def lockfile_packages(lockfile_content)
|
|
94
|
+
parsed = TomlRB.parse(lockfile_content)
|
|
95
|
+
T.cast(parsed.fetch("package", []), T::Array[T.untyped])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
sig { params(package_data: T.untyped).returns(T.nilable(String)) }
|
|
99
|
+
def lockfile_parent_name(package_data)
|
|
100
|
+
return unless package_data.is_a?(Hash)
|
|
101
|
+
|
|
102
|
+
package_name = package_data["name"]
|
|
103
|
+
return unless package_name.is_a?(String)
|
|
104
|
+
|
|
105
|
+
normalised_dependency_name(package_name)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
sig { params(package_data: T.untyped).returns(T::Array[String]) }
|
|
109
|
+
def lockfile_child_names(package_data)
|
|
110
|
+
dependencies =
|
|
111
|
+
if package_data.is_a?(Hash)
|
|
112
|
+
T.cast(package_data["dependencies"], T.nilable(T::Array[T.untyped])) || []
|
|
113
|
+
else
|
|
114
|
+
[]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
dependencies.filter_map do |dependency|
|
|
118
|
+
dependency_name = lockfile_dependency_name(dependency)
|
|
119
|
+
normalised_dependency_name(dependency_name) if dependency_name
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
sig { params(dependency_data: T.untyped).returns(T.nilable(String)) }
|
|
124
|
+
def lockfile_dependency_name(dependency_data)
|
|
125
|
+
if dependency_data.is_a?(Hash)
|
|
126
|
+
name = dependency_data["name"]
|
|
127
|
+
return name if name.is_a?(String)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
return dependency_data if dependency_data.is_a?(String)
|
|
131
|
+
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
sig { returns(T::Hash[String, T::Array[String]]) }
|
|
136
|
+
def package_relationships_from_tree
|
|
137
|
+
relationship_stack = T.let([], T::Array[String])
|
|
138
|
+
|
|
139
|
+
uv_parser.run_in_parsed_context(UV_TREE_COMMAND).lines.each_with_object({}) do |line, rels|
|
|
140
|
+
match = line.match(UV_TREE_LINE_REGEX)
|
|
141
|
+
next unless match
|
|
142
|
+
|
|
143
|
+
package = normalised_dependency_name(T.must(match[:package]))
|
|
144
|
+
depth = T.must(match[:prefix]).scan(/(?:│ | )/).length
|
|
145
|
+
|
|
146
|
+
relationship_stack[depth] = package
|
|
147
|
+
relationship_stack.slice!(depth + 1, relationship_stack.length)
|
|
148
|
+
|
|
149
|
+
parent = depth.zero? ? nil : relationship_stack[depth - 1]
|
|
150
|
+
rels[package] ||= []
|
|
151
|
+
next unless parent
|
|
152
|
+
|
|
153
|
+
rels[parent] ||= []
|
|
154
|
+
rels[parent] << package
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
sig { returns(Dependabot::Uv::FileParser) }
|
|
159
|
+
def uv_parser
|
|
160
|
+
T.cast(file_parser, Dependabot::Uv::FileParser)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
sig { params(name: String).returns(String) }
|
|
164
|
+
def normalised_dependency_name(name)
|
|
165
|
+
Dependabot::Uv::FileParser.normalize_dependency_name(name)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
sig { override.params(_dependency: Dependabot::Dependency).returns(String) }
|
|
169
|
+
def purl_pkg_for(_dependency)
|
|
170
|
+
"pypi"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
174
|
+
def pyproject_toml
|
|
175
|
+
return @pyproject_toml if defined?(@pyproject_toml)
|
|
176
|
+
|
|
177
|
+
@pyproject_toml = T.let(
|
|
178
|
+
dependency_files.find { |f| f.name == "pyproject.toml" },
|
|
179
|
+
T.nilable(Dependabot::DependencyFile)
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
184
|
+
def uv_lock
|
|
185
|
+
return @uv_lock if defined?(@uv_lock)
|
|
186
|
+
|
|
187
|
+
@uv_lock = T.let(
|
|
188
|
+
dependency_files.find { |f| f.name == "uv.lock" },
|
|
189
|
+
T.nilable(Dependabot::DependencyFile)
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
Dependabot::DependencyGraphers.register("uv", Dependabot::Uv::DependencyGrapher)
|
|
@@ -71,6 +71,21 @@ module Dependabot
|
|
|
71
71
|
)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
sig { override.params(command: String).returns(String) }
|
|
75
|
+
def run_in_parsed_context(command)
|
|
76
|
+
SharedHelpers.in_a_temporary_directory do
|
|
77
|
+
dependency_files.each do |file|
|
|
78
|
+
path = file.name
|
|
79
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
|
80
|
+
File.write(path, file.content)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
setup_python_environment
|
|
84
|
+
|
|
85
|
+
SharedHelpers.run_shell_command(command)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
74
89
|
# Normalize dependency names to match the PyPI index normalization
|
|
75
90
|
sig { params(name: String, extras: T::Array[String]).returns(String) }
|
|
76
91
|
def self.normalize_dependency_name(name, extras = [])
|
|
@@ -94,8 +109,7 @@ module Dependabot
|
|
|
94
109
|
def python_requirement_parser
|
|
95
110
|
@python_requirement_parser ||= T.let(
|
|
96
111
|
PythonRequirementParser.new(
|
|
97
|
-
dependency_files:
|
|
98
|
-
dependency_files
|
|
112
|
+
dependency_files: dependency_files
|
|
99
113
|
),
|
|
100
114
|
T.nilable(PythonRequirementParser)
|
|
101
115
|
)
|
|
@@ -359,13 +359,13 @@ module Dependabot
|
|
|
359
359
|
.select { |cred| cred["type"] == "python_index" }
|
|
360
360
|
.reject { |cred| explicit_index?(cred) }
|
|
361
361
|
.map do |cred|
|
|
362
|
-
|
|
362
|
+
authed_url = AuthedUrlBuilder.authed_url(credential: cred)
|
|
363
363
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
364
|
+
if cred.replaces_base?
|
|
365
|
+
"--default-index #{authed_url}"
|
|
366
|
+
else
|
|
367
|
+
"--index #{authed_url}"
|
|
368
|
+
end
|
|
369
369
|
end
|
|
370
370
|
end
|
|
371
371
|
|
|
@@ -421,18 +421,18 @@ module Dependabot
|
|
|
421
421
|
.select { |cred| cred["type"] == "python_index" }
|
|
422
422
|
.select { |cred| explicit_index?(cred) }
|
|
423
423
|
.each do |cred|
|
|
424
|
-
|
|
425
|
-
|
|
424
|
+
index_name = find_index_name_for_credential(cred)
|
|
425
|
+
next unless index_name
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
env_name = index_name.upcase.gsub(/[^A-Z0-9]/, "_")
|
|
428
428
|
|
|
429
|
-
|
|
429
|
+
env_vars["UV_INDEX_#{env_name}_USERNAME"] = cred["username"] if cred["username"]
|
|
430
430
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
431
|
+
if cred["password"]
|
|
432
|
+
env_vars["UV_INDEX_#{env_name}_PASSWORD"] = cred["password"]
|
|
433
|
+
elsif cred["token"]
|
|
434
|
+
env_vars["UV_INDEX_#{env_name}_PASSWORD"] = cred["token"]
|
|
435
|
+
end
|
|
436
436
|
end
|
|
437
437
|
|
|
438
438
|
env_vars
|
data/lib/dependabot/uv.rb
CHANGED
|
@@ -16,6 +16,7 @@ end
|
|
|
16
16
|
|
|
17
17
|
# These all need to be required so the various classes can be registered in a
|
|
18
18
|
# lookup table of package manager names to concrete classes.
|
|
19
|
+
require "dependabot/uv/dependency_grapher"
|
|
19
20
|
require "dependabot/uv/file_fetcher"
|
|
20
21
|
require "dependabot/uv/file_parser"
|
|
21
22
|
require "dependabot/uv/update_checker"
|
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.364.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.364.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.364.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.364.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.364.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: debug
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -263,6 +263,7 @@ files:
|
|
|
263
263
|
- helpers/run.py
|
|
264
264
|
- lib/dependabot/uv.rb
|
|
265
265
|
- lib/dependabot/uv/authed_url_builder.rb
|
|
266
|
+
- lib/dependabot/uv/dependency_grapher.rb
|
|
266
267
|
- lib/dependabot/uv/file_fetcher.rb
|
|
267
268
|
- lib/dependabot/uv/file_fetcher/workspace_fetcher.rb
|
|
268
269
|
- lib/dependabot/uv/file_parser.rb
|
|
@@ -299,7 +300,7 @@ licenses:
|
|
|
299
300
|
- MIT
|
|
300
301
|
metadata:
|
|
301
302
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
302
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
303
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.364.0
|
|
303
304
|
rdoc_options: []
|
|
304
305
|
require_paths:
|
|
305
306
|
- lib
|