dependabot-python 0.385.0 → 0.386.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6743a3b5b5ae514de1fc09550af84d3285ae7fed69a8c74b8d95291b7cb2c1a
|
|
4
|
+
data.tar.gz: 1bde8ed1421e31bf67c7ca1fab04f7f0c2edd3fbd4afcb2eb38df5c0bba007d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e484276f80b4690e9f41f471f18adb8f4e0adf1481c73c1912ead78274785d908cea245a50265a3f2e567ae643d5d4759fc1694fbc66ec91903f3c65136f95f7
|
|
7
|
+
data.tar.gz: f0b4c4092d96440d9e98acb64d750b380712cb456da886ecf4c8a6872ef5cb4b2f3d98551fb0e940878de37fe600dcc5981494e6d9eee978351f8ade1e584f99
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "sorbet-runtime"
|
|
6
|
+
|
|
7
|
+
require "dependabot/dependency_file"
|
|
8
|
+
require "dependabot/dependency_graphers/base"
|
|
9
|
+
require "dependabot/python/shared_file_fetcher"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Python
|
|
13
|
+
class DependencyGrapher < Dependabot::DependencyGraphers::Base
|
|
14
|
+
# Splits a directory of requirements files (pip / pip-compile) into one manifest group per "layer".
|
|
15
|
+
#
|
|
16
|
+
# Python is the reported case of "layered requirements": a single directory can hold multiple
|
|
17
|
+
# independent requirements files (e.g. base-requirements.txt, test-requirements.txt) cross-linked
|
|
18
|
+
# with `-r`/`-c`.
|
|
19
|
+
#
|
|
20
|
+
# Each 'layer' is its own manifest and must get its own snapshot, rather than the whole
|
|
21
|
+
# directory collapsing onto the first `.txt` alphabetically.
|
|
22
|
+
class RequirementsLayers
|
|
23
|
+
extend T::Sig
|
|
24
|
+
|
|
25
|
+
# Regex patterns for detecting Python requirements / dependencies .txt manifest variants.
|
|
26
|
+
# Used to filter out unrelated .txt files (e.g. README-style notes, tool output, etc.) from being
|
|
27
|
+
# treated as pip manifests.
|
|
28
|
+
|
|
29
|
+
# Matches "requirements" preceded by a hyphen, period, underscore, start-of-string, or slash,
|
|
30
|
+
# followed by non-whitespace chars and ".txt".
|
|
31
|
+
# Examples: requirements.txt, requirements.prod.txt, requirements/production.txt
|
|
32
|
+
REQUIREMENTS_TXT_REGEX = T.let(%r{(?:[-._]|^|/)requirements[^\s]*\.txt$}i, Regexp)
|
|
33
|
+
|
|
34
|
+
# More lenient: matches "require" at the start of the filename or after a hyphen/period/underscore/
|
|
35
|
+
# slash delimiter, with an optional hyphen/underscore/slash suffix. The leading delimiter prevents
|
|
36
|
+
# matching "require" as a substring of another word (e.g. "prequire.txt", "acquire.txt").
|
|
37
|
+
# Examples: require.txt, require-test.txt, py3-require.txt, pyenv_require_e2e.txt
|
|
38
|
+
REQUIRE_TXT_REGEX = T.let(%r{(?:[-._]|^|/)require(?:[-_/][^\s.]*)?\.txt$}i, Regexp)
|
|
39
|
+
|
|
40
|
+
# Matches "dependencies" / "dependency" preceded by a hyphen, period, underscore,
|
|
41
|
+
# start-of-string, or slash, followed by non-whitespace chars and ".txt".
|
|
42
|
+
# Examples: dependencies.txt, my-dependencies.txt, dependencies/python/ansible-lint.txt
|
|
43
|
+
DEPENDENCIES_TXT_REGEX = T.let(%r{(?:[-._]|^|/)dependenc(?:y|ies)[^\s]*\.txt$}i, Regexp)
|
|
44
|
+
|
|
45
|
+
# More lenient: matches "depend" / "depends" at the start of the filename or after a hyphen/period/
|
|
46
|
+
# underscore/slash delimiter, with an optional hyphen/underscore/slash suffix. The leading delimiter
|
|
47
|
+
# prevents matching "depend" as a substring of another word (e.g. "independ.txt").
|
|
48
|
+
# Examples: depend.txt, depends.txt, depend-test.txt, py3-depends.txt
|
|
49
|
+
DEPEND_TXT_REGEX = T.let(%r{(?:[-._]|^|/)depend(?:s)?(?:[-_/][^\s.]*)?\.txt$}i, Regexp)
|
|
50
|
+
|
|
51
|
+
# Whether a .txt filename looks like a real pip manifest rather than an unrelated .txt file.
|
|
52
|
+
sig { params(path: String).returns(T::Boolean) }
|
|
53
|
+
def self.manifest_txt_filename?(path)
|
|
54
|
+
path.match?(REQUIREMENTS_TXT_REGEX) ||
|
|
55
|
+
path.match?(REQUIRE_TXT_REGEX) ||
|
|
56
|
+
path.match?(DEPENDENCIES_TXT_REGEX) ||
|
|
57
|
+
path.match?(DEPEND_TXT_REGEX)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Resolves the sibling paths a requirements file references via `-r`/`-c`, returning repo-relative
|
|
61
|
+
# cleanpaths (matching fetched DependencyFile names). Single home for reference resolution, shared by the
|
|
62
|
+
# grapher's bystander filter and the layering group builder so reference-syntax handling never drifts.
|
|
63
|
+
#
|
|
64
|
+
# Reuses SharedFileFetcher's reference regexes so callers keep exactly the children the file fetcher pulled in.
|
|
65
|
+
sig { params(file: Dependabot::DependencyFile).returns(T::Array[String]) }
|
|
66
|
+
def self.referenced_paths(file)
|
|
67
|
+
content = file.content
|
|
68
|
+
return [] if content.nil?
|
|
69
|
+
|
|
70
|
+
current_dir = File.dirname(file.name)
|
|
71
|
+
referenced =
|
|
72
|
+
content.scan(Dependabot::Python::SharedFileFetcher::CHILD_REQUIREMENT_REGEX).flatten +
|
|
73
|
+
content.scan(Dependabot::Python::SharedFileFetcher::CONSTRAINT_REGEX).flatten
|
|
74
|
+
|
|
75
|
+
referenced.map do |path|
|
|
76
|
+
resolved = current_dir == "." ? path : File.join(current_dir, path)
|
|
77
|
+
Pathname.new(resolved).cleanpath.to_path
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
|
|
82
|
+
def initialize(dependency_files:)
|
|
83
|
+
@dependency_files = dependency_files
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Builds one manifest group per requirements layer. A layer's primary is its compiled `.txt` (the
|
|
87
|
+
# most specific file, akin to a lockfile) when present, otherwise its `.in`. Each group also includes
|
|
88
|
+
# the paired `.in`/`.txt`, any constraints files, and any sibling files referenced via `-r`/`-c` so
|
|
89
|
+
# the parser can resolve the layer in isolation.
|
|
90
|
+
sig { returns(T::Array[Dependabot::DependencyGraphers::ManifestGroup]) }
|
|
91
|
+
def groups
|
|
92
|
+
layer_primaries.map do |primary|
|
|
93
|
+
Dependabot::DependencyGraphers::ManifestGroup.new(
|
|
94
|
+
primary: primary,
|
|
95
|
+
files: files_for_layer(primary)
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
103
|
+
attr_reader :dependency_files
|
|
104
|
+
|
|
105
|
+
# The set of files that act as the owning manifest for a layer: every requirements `.txt` that looks
|
|
106
|
+
# like a real manifest, plus any `.in` file that has no compiled `.txt` counterpart.
|
|
107
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
108
|
+
def layer_primaries
|
|
109
|
+
txt_primaries = requirement_family_files.select do |f|
|
|
110
|
+
f.name.end_with?(".txt") && self.class.manifest_txt_filename?(f.name)
|
|
111
|
+
end
|
|
112
|
+
compiled_stems = txt_primaries.map { |f| requirements_stem(f.name) }
|
|
113
|
+
|
|
114
|
+
in_primaries = requirement_family_files.select do |f|
|
|
115
|
+
f.name.end_with?(".in") && !compiled_stems.include?(requirements_stem(f.name))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
txt_primaries + in_primaries
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# All the files a single layer needs to parse: the primary, its paired `.in`/`.txt`, constraints
|
|
122
|
+
# files and any `-r`/`-c` referenced siblings (added as support files so they never win attribution).
|
|
123
|
+
sig { params(primary: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
|
|
124
|
+
def files_for_layer(primary)
|
|
125
|
+
stem = requirements_stem(primary.name)
|
|
126
|
+
|
|
127
|
+
paired = requirement_family_files.select do |f|
|
|
128
|
+
f != primary && requirements_stem(f.name) == stem
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
referenced = referenced_requirement_files(primary)
|
|
132
|
+
|
|
133
|
+
support = (paired + referenced + constraints_files).uniq.map do |f|
|
|
134
|
+
as_support_file(f)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
[primary] + support
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
141
|
+
def requirement_family_files
|
|
142
|
+
@requirement_family_files ||= T.let(
|
|
143
|
+
dependency_files.select { |f| f.name.end_with?(".txt", ".in") },
|
|
144
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
149
|
+
def constraints_files
|
|
150
|
+
requirement_family_files.select { |f| File.basename(f.name).include?("constraint") }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Finds sibling files referenced by a requirements file via `-r`/`-c`, following the chain transitively.
|
|
154
|
+
#
|
|
155
|
+
# A referenced file can itself reference further files (e.g. `develop.in -r test.in`, `test.in -r base.in`),
|
|
156
|
+
# so we walk the whole closure to gather every sibling the layer needs on disk to parse in isolation.
|
|
157
|
+
sig { params(file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
|
|
158
|
+
def referenced_requirement_files(file)
|
|
159
|
+
by_name = requirement_family_files.to_h { |f| [f.name, f] }
|
|
160
|
+
seen = T.let(Set.new, T::Set[String])
|
|
161
|
+
queue = [file]
|
|
162
|
+
collected = T.let([], T::Array[Dependabot::DependencyFile])
|
|
163
|
+
|
|
164
|
+
until queue.empty?
|
|
165
|
+
current = T.must(queue.shift)
|
|
166
|
+
self.class.referenced_paths(current).each do |path|
|
|
167
|
+
next if seen.include?(path)
|
|
168
|
+
|
|
169
|
+
seen << path
|
|
170
|
+
referenced = by_name[path]
|
|
171
|
+
next if referenced.nil?
|
|
172
|
+
|
|
173
|
+
collected << referenced
|
|
174
|
+
queue << referenced
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
collected
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Returns a support-file copy of the given file so it can be parsed for cross-reference resolution
|
|
182
|
+
# without becoming an attribution target. Never mutates the shared DependencyFile.
|
|
183
|
+
sig { params(file: Dependabot::DependencyFile).returns(Dependabot::DependencyFile) }
|
|
184
|
+
def as_support_file(file)
|
|
185
|
+
return file if file.support_file?
|
|
186
|
+
|
|
187
|
+
Dependabot::DependencyFile.new(
|
|
188
|
+
name: file.name,
|
|
189
|
+
content: file.content,
|
|
190
|
+
directory: file.directory,
|
|
191
|
+
type: file.type,
|
|
192
|
+
support_file: true,
|
|
193
|
+
vendored_file: file.vendored_file,
|
|
194
|
+
symlink_target: file.symlink_target,
|
|
195
|
+
content_encoding: file.content_encoding,
|
|
196
|
+
deleted: file.deleted?,
|
|
197
|
+
operation: file.operation,
|
|
198
|
+
mode: file.mode
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# The stem shared by a pip-compile `.in` and its compiled `.txt` (e.g. "base-requirements").
|
|
203
|
+
sig { params(name: String).returns(String) }
|
|
204
|
+
def requirements_stem(name)
|
|
205
|
+
name.sub(/\.(txt|in)\z/, "")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "sorbet-runtime"
|
|
6
|
+
require "pathname"
|
|
6
7
|
|
|
7
8
|
require "dependabot/dependency_graphers"
|
|
8
9
|
require "dependabot/dependency_graphers/base"
|
|
@@ -11,36 +12,14 @@ require "dependabot/python/language_version_manager"
|
|
|
11
12
|
require "dependabot/python/name_normaliser"
|
|
12
13
|
require "dependabot/python/pip_compile_file_matcher"
|
|
13
14
|
require "dependabot/python/pipenv_runner"
|
|
15
|
+
require "dependabot/python/shared_file_fetcher"
|
|
14
16
|
require "toml-rb"
|
|
15
17
|
|
|
16
18
|
module Dependabot
|
|
17
19
|
module Python
|
|
18
20
|
class DependencyGrapher < Dependabot::DependencyGraphers::Base
|
|
19
21
|
require_relative "dependency_grapher/lockfile_generator"
|
|
20
|
-
|
|
21
|
-
# Regex patterns for detecting Python requirements / dependencies .txt manifest variants.
|
|
22
|
-
# Used by the dependency grapher to filter out unrelated .txt files (e.g. README-style notes,
|
|
23
|
-
# tool output, etc.) from being treated as pip manifests.
|
|
24
|
-
|
|
25
|
-
# Matches "requirements" preceded by a hyphen, period, underscore, start-of-string, or slash,
|
|
26
|
-
# followed by non-whitespace chars and ".txt".
|
|
27
|
-
# Examples: requirements.txt, requirements.prod.txt, requirements/production.txt
|
|
28
|
-
REQUIREMENTS_TXT_REGEX = T.let(%r{(?:[-._]|^|/)requirements[^\s]*\.txt$}i, Regexp)
|
|
29
|
-
|
|
30
|
-
# More lenient: matches "require" with optional prefix (no dots/whitespace)
|
|
31
|
-
# and optional hyphen/underscore/slash suffix. Does not match "require" as a substring.
|
|
32
|
-
# Examples: require.txt, require-test.txt, py3-require.txt, pyenv_require_e2e.txt
|
|
33
|
-
REQUIRE_TXT_REGEX = T.let(%r{[^\s|.]*require(?:[-_/][^\s|.]*)?\.txt$}i, Regexp)
|
|
34
|
-
|
|
35
|
-
# Matches "dependencies" / "dependency" preceded by a hyphen, period, underscore,
|
|
36
|
-
# start-of-string, or slash, followed by non-whitespace chars and ".txt".
|
|
37
|
-
# Examples: dependencies.txt, my-dependencies.txt, dependencies/python/ansible-lint.txt
|
|
38
|
-
DEPENDENCIES_TXT_REGEX = T.let(%r{(?:[-._]|^|/)dependenc(?:y|ies)[^\s]*\.txt$}i, Regexp)
|
|
39
|
-
|
|
40
|
-
# More lenient: matches "depend" / "depends" with optional prefix (no dots/whitespace)
|
|
41
|
-
# and optional hyphen/underscore/slash suffix. Does not match "depend" as a substring.
|
|
42
|
-
# Examples: depend.txt, depends.txt, depend-test.txt, py3-depends.txt
|
|
43
|
-
DEPEND_TXT_REGEX = T.let(%r{[^\s|.]*depend(?:s)?(?:[-_/][^\s|.]*)?\.txt$}i, Regexp)
|
|
22
|
+
require_relative "dependency_grapher/requirements_layers"
|
|
44
23
|
|
|
45
24
|
sig { override.returns(Dependabot::DependencyFile) }
|
|
46
25
|
def relevant_dependency_file
|
|
@@ -59,11 +38,22 @@ module Dependabot
|
|
|
59
38
|
relevant_file = candidates.compact.first
|
|
60
39
|
return relevant_file if relevant_file
|
|
61
40
|
|
|
41
|
+
# If we do not have any dependencies to report, the absence of a relevant manifest file is tolerable, it
|
|
42
|
+
# means the file fetcher retrieved bystander `txt` files we have filtered out and the correct outcome
|
|
43
|
+
# is a blank snapshot for this path.
|
|
44
|
+
return empty_manifest_file if resolved_dependencies.empty?
|
|
45
|
+
|
|
46
|
+
# If dependencies resolved but no owning manifest could be identified, we are in an inconsistent state
|
|
47
|
+
# that we cannot represent.
|
|
62
48
|
raise DependabotError, "No supported dependency file present."
|
|
63
49
|
end
|
|
64
50
|
|
|
65
51
|
sig { override.void }
|
|
66
52
|
def prepare!
|
|
53
|
+
# Exclude .txt files that don't look like pip manifests before we parse anything to avoid risk of bystander
|
|
54
|
+
# files in non-pip projects.
|
|
55
|
+
filter_non_manifest_txt_files!
|
|
56
|
+
|
|
67
57
|
if poetry_project_without_lockfile?
|
|
68
58
|
# Generating an ephemeral lockfile requires executing `poetry lock`. Strictly speaking, that violates our
|
|
69
59
|
# policy of refusing to run Python tooling when external code execution is disallowed, so fail fast.
|
|
@@ -76,8 +66,126 @@ module Dependabot
|
|
|
76
66
|
super
|
|
77
67
|
end
|
|
78
68
|
|
|
69
|
+
# Layering is specific to pip / pip-compile for Python.
|
|
70
|
+
sig { override.returns(T::Array[Dependabot::DependencyGraphers::ManifestGroup]) }
|
|
71
|
+
def manifest_groups
|
|
72
|
+
return super unless supports_layering?
|
|
73
|
+
|
|
74
|
+
groups = RequirementsLayers.new(dependency_files: dependency_files).groups
|
|
75
|
+
# If we try to apply grouping, but find there is only one group, we prefer
|
|
76
|
+
# to fallback to the base method (the whole directory parsed as one manifest,
|
|
77
|
+
# which naturally includes any setup.py/setup.cfg/pyproject.toml present).
|
|
78
|
+
return super if groups.length < 2
|
|
79
|
+
|
|
80
|
+
groups + non_requirements_manifest_groups
|
|
81
|
+
end
|
|
82
|
+
|
|
79
83
|
private
|
|
80
84
|
|
|
85
|
+
# When a pip/pip-compile directory is split into requirements layers, any non-requirements manifest
|
|
86
|
+
# sharing that directory (setup.py, setup.cfg, pyproject.toml) would otherwise fall outside every layer
|
|
87
|
+
# group and have its dependencies dropped.
|
|
88
|
+
#
|
|
89
|
+
# We instead emit each as its own self-attributed group so its dependencies are preserved and attributed
|
|
90
|
+
# to the file itself. This matches existing static analysis behaviour.
|
|
91
|
+
#
|
|
92
|
+
# NOTE:
|
|
93
|
+
# This logic is only applied on the pip/pip-compile path, so poetry.lock/Pipfile.lock are absent as they would
|
|
94
|
+
# select the poetry/pipenv path which does not support layering.
|
|
95
|
+
#
|
|
96
|
+
# If a pyproject.toml is present, it is treated as a pip-context manifest.
|
|
97
|
+
sig { returns(T::Array[Dependabot::DependencyGraphers::ManifestGroup]) }
|
|
98
|
+
def non_requirements_manifest_groups
|
|
99
|
+
[setup_file, setup_cfg_file, pyproject_toml].compact.map do |file|
|
|
100
|
+
Dependabot::DependencyGraphers::ManifestGroup.new(primary: file, files: [file])
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# An empty, nameless dependency file used to represent a directory that has no supported manifest and
|
|
105
|
+
# resolves to no dependencies. The submission layer treats a nameless manifest as "nothing to report",
|
|
106
|
+
# producing a valid, empty snapshot for the directory instead of a failure.
|
|
107
|
+
sig { returns(Dependabot::DependencyFile) }
|
|
108
|
+
def empty_manifest_file
|
|
109
|
+
Dependabot::DependencyFile.new(
|
|
110
|
+
name: "",
|
|
111
|
+
content: "",
|
|
112
|
+
directory: file_parser.source&.directory || "/"
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Non-.txt files (pyproject.toml, setup.py, Pipfile, lockfiles, .in files, etc.) are always retained.
|
|
117
|
+
# A .txt file is kept when its name looks like a pip requirements/dependencies manifest, the pip-compile
|
|
118
|
+
# matcher recognises it as a compiled lockfile, or it is referenced (transitively) via `-r`/`-c` from a
|
|
119
|
+
# retained requirements file. The last case preserves constraint/child files (e.g. `constraints.txt`) that
|
|
120
|
+
# the parser needs on disk to resolve a real manifest, while still dropping bystander `.txt` files.
|
|
121
|
+
sig { void }
|
|
122
|
+
def filter_non_manifest_txt_files!
|
|
123
|
+
keep = txt_files_to_keep
|
|
124
|
+
|
|
125
|
+
file_parser.dependency_files.reject! do |file|
|
|
126
|
+
next false unless file.name.end_with?(".txt")
|
|
127
|
+
|
|
128
|
+
!keep.include?(file.name)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Names of the `.txt` files that must be retained: those that look like manifests or pip-compile lockfiles,
|
|
133
|
+
# plus any `.txt` reachable via `-r`/`-c` references from a retained requirements file (`.in` files are
|
|
134
|
+
# never dropped, so references originating from them are followed too).
|
|
135
|
+
sig { returns(T::Set[String]) }
|
|
136
|
+
def txt_files_to_keep
|
|
137
|
+
files_by_name = dependency_files.to_h { |file| [file.name, file] }
|
|
138
|
+
|
|
139
|
+
seeds = dependency_files.select do |file|
|
|
140
|
+
file.name.end_with?(".txt") &&
|
|
141
|
+
(RequirementsLayers.manifest_txt_filename?(file.name) ||
|
|
142
|
+
pip_compile_file_matcher.lockfile_for_pip_compile_file?(file))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# `.in` files are always retained, so a `.txt` they reference must be kept too.
|
|
146
|
+
roots = seeds + dependency_files.select { |file| file.name.end_with?(".in") }
|
|
147
|
+
|
|
148
|
+
reachable_txt_names(roots, files_by_name, Set.new(seeds.map(&:name)))
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Breadth-first closure over `-r`/`-c` references starting from `roots`, adding every referenced `.txt`
|
|
152
|
+
# file that exists in `files_by_name` to `keep`.
|
|
153
|
+
sig do
|
|
154
|
+
params(
|
|
155
|
+
roots: T::Array[Dependabot::DependencyFile],
|
|
156
|
+
files_by_name: T::Hash[String, Dependabot::DependencyFile],
|
|
157
|
+
keep: T::Set[String]
|
|
158
|
+
).returns(T::Set[String])
|
|
159
|
+
end
|
|
160
|
+
def reachable_txt_names(roots, files_by_name, keep)
|
|
161
|
+
queue = roots.dup
|
|
162
|
+
until queue.empty?
|
|
163
|
+
file = T.must(queue.shift)
|
|
164
|
+
referenced_txt_names(file).each do |name|
|
|
165
|
+
referenced_file = files_by_name[name]
|
|
166
|
+
next if referenced_file.nil? || keep.include?(name)
|
|
167
|
+
|
|
168
|
+
keep << name
|
|
169
|
+
queue << referenced_file
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
keep
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Resolves the `.txt` files referenced from a requirements file via `-r`/`-c`, returning their names
|
|
177
|
+
# relative to the repo (matching the fetched DependencyFile names) so we can retain them. Delegates to
|
|
178
|
+
# RequirementsLayers.referenced_paths so grouping and the bystander filter resolve references identically.
|
|
179
|
+
sig { params(file: Dependabot::DependencyFile).returns(T::Array[String]) }
|
|
180
|
+
def referenced_txt_names(file)
|
|
181
|
+
RequirementsLayers.referenced_paths(file).select { |path| path.end_with?(".txt") }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
sig { returns(T::Boolean) }
|
|
185
|
+
def supports_layering?
|
|
186
|
+
[PipPackageManager::NAME, PipCompilePackageManager::NAME].include?(python_package_manager)
|
|
187
|
+
end
|
|
188
|
+
|
|
81
189
|
# Returns the poetry.lock only if it was committed to the repo,
|
|
82
190
|
# not if it was generated ephemerally. This ensures that
|
|
83
191
|
# relevant_dependency_file reports the real manifest (pyproject.toml).
|
|
@@ -380,19 +488,11 @@ module Dependabot
|
|
|
380
488
|
|
|
381
489
|
@pip_requirements_file = T.let(
|
|
382
490
|
dependency_files.find { |f| f.name == "requirements.txt" } ||
|
|
383
|
-
dependency_files.find { |f| f.name.end_with?(".txt") &&
|
|
491
|
+
dependency_files.find { |f| f.name.end_with?(".txt") && RequirementsLayers.manifest_txt_filename?(f.name) },
|
|
384
492
|
T.nilable(Dependabot::DependencyFile)
|
|
385
493
|
)
|
|
386
494
|
end
|
|
387
495
|
|
|
388
|
-
sig { params(path: String).returns(T::Boolean) }
|
|
389
|
-
def python_manifest_txt_filename?(path)
|
|
390
|
-
path.match?(REQUIREMENTS_TXT_REGEX) ||
|
|
391
|
-
path.match?(REQUIRE_TXT_REGEX) ||
|
|
392
|
-
path.match?(DEPENDENCIES_TXT_REGEX) ||
|
|
393
|
-
path.match?(DEPEND_TXT_REGEX)
|
|
394
|
-
end
|
|
395
|
-
|
|
396
496
|
sig { params(filename: String).returns(T.nilable(Dependabot::DependencyFile)) }
|
|
397
497
|
def dependency_file(filename)
|
|
398
498
|
dependency_files.find { |file| file.name == filename }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-python
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.386.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.386.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.386.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -277,6 +277,7 @@ files:
|
|
|
277
277
|
- lib/dependabot/python/authed_url_builder.rb
|
|
278
278
|
- lib/dependabot/python/dependency_grapher.rb
|
|
279
279
|
- lib/dependabot/python/dependency_grapher/lockfile_generator.rb
|
|
280
|
+
- lib/dependabot/python/dependency_grapher/requirements_layers.rb
|
|
280
281
|
- lib/dependabot/python/file_fetcher.rb
|
|
281
282
|
- lib/dependabot/python/file_parser.rb
|
|
282
283
|
- lib/dependabot/python/file_parser/pipfile_files_parser.rb
|
|
@@ -322,7 +323,7 @@ licenses:
|
|
|
322
323
|
- MIT
|
|
323
324
|
metadata:
|
|
324
325
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
325
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
326
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.386.0
|
|
326
327
|
rdoc_options: []
|
|
327
328
|
require_paths:
|
|
328
329
|
- lib
|