dependabot-core 0.78.0 → 0.79.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/CHANGELOG.md +5 -0
- data/helpers/npm/lib/updater.js +11 -5
- data/helpers/npm/package.json +2 -2
- data/helpers/npm/yarn.lock +26 -28
- data/helpers/yarn/lib/replace-lockfile-declaration.js +15 -3
- data/helpers/yarn/lib/updater.js +17 -5
- data/helpers/yarn/package.json +2 -2
- data/helpers/yarn/yarn.lock +24 -31
- data/lib/dependabot/file_fetchers.rb +0 -2
- data/lib/dependabot/file_parsers.rb +0 -2
- data/lib/dependabot/file_updaters.rb +0 -2
- data/lib/dependabot/metadata_finders.rb +0 -2
- data/lib/dependabot/update_checkers.rb +0 -2
- data/lib/dependabot/utils.rb +0 -4
- data/lib/dependabot/version.rb +1 -1
- metadata +3 -34
- data/helpers/python/lib/__init__.py +0 -0
- data/helpers/python/lib/hasher.py +0 -23
- data/helpers/python/lib/parser.py +0 -130
- data/helpers/python/requirements.txt +0 -9
- data/helpers/python/run.py +0 -18
- data/lib/dependabot/file_fetchers/python/pip.rb +0 -305
- data/lib/dependabot/file_parsers/python/pip.rb +0 -223
- data/lib/dependabot/file_parsers/python/pip/pipfile_files_parser.rb +0 -154
- data/lib/dependabot/file_parsers/python/pip/poetry_files_parser.rb +0 -141
- data/lib/dependabot/file_parsers/python/pip/setup_file_parser.rb +0 -164
- data/lib/dependabot/file_updaters/python/pip.rb +0 -147
- data/lib/dependabot/file_updaters/python/pip/pip_compile_file_updater.rb +0 -363
- data/lib/dependabot/file_updaters/python/pip/pipfile_file_updater.rb +0 -397
- data/lib/dependabot/file_updaters/python/pip/pipfile_preparer.rb +0 -125
- data/lib/dependabot/file_updaters/python/pip/poetry_file_updater.rb +0 -289
- data/lib/dependabot/file_updaters/python/pip/pyproject_preparer.rb +0 -105
- data/lib/dependabot/file_updaters/python/pip/requirement_file_updater.rb +0 -166
- data/lib/dependabot/file_updaters/python/pip/requirement_replacer.rb +0 -95
- data/lib/dependabot/file_updaters/python/pip/setup_file_sanitizer.rb +0 -91
- data/lib/dependabot/file_updaters/ruby/.DS_Store +0 -0
- data/lib/dependabot/metadata_finders/python/pip.rb +0 -120
- data/lib/dependabot/update_checkers/python/pip.rb +0 -227
- data/lib/dependabot/update_checkers/python/pip/latest_version_finder.rb +0 -252
- data/lib/dependabot/update_checkers/python/pip/pip_compile_version_resolver.rb +0 -380
- data/lib/dependabot/update_checkers/python/pip/pipfile_version_resolver.rb +0 -559
- data/lib/dependabot/update_checkers/python/pip/poetry_version_resolver.rb +0 -300
- data/lib/dependabot/update_checkers/python/pip/requirements_updater.rb +0 -367
- data/lib/dependabot/utils/python/requirement.rb +0 -130
- data/lib/dependabot/utils/python/version.rb +0 -88
- data/lib/python_requirement_parser.rb +0 -33
- data/lib/python_versions.rb +0 -21
@@ -1,252 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "excon"
|
4
|
-
|
5
|
-
require "dependabot/update_checkers/python/pip"
|
6
|
-
require "dependabot/shared_helpers"
|
7
|
-
|
8
|
-
module Dependabot
|
9
|
-
module UpdateCheckers
|
10
|
-
module Python
|
11
|
-
class Pip
|
12
|
-
class LatestVersionFinder
|
13
|
-
def initialize(dependency:, dependency_files:, credentials:,
|
14
|
-
ignored_versions:)
|
15
|
-
@dependency = dependency
|
16
|
-
@dependency_files = dependency_files
|
17
|
-
@credentials = credentials
|
18
|
-
@ignored_versions = ignored_versions
|
19
|
-
end
|
20
|
-
|
21
|
-
def latest_version
|
22
|
-
@latest_version ||= fetch_latest_version
|
23
|
-
end
|
24
|
-
|
25
|
-
def latest_version_with_no_unlock
|
26
|
-
@latest_version_with_no_unlock ||=
|
27
|
-
fetch_latest_version_with_no_unlock
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
attr_reader :dependency, :dependency_files, :credentials,
|
33
|
-
:ignored_versions
|
34
|
-
|
35
|
-
def fetch_latest_version
|
36
|
-
versions = available_versions
|
37
|
-
versions.reject! { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }
|
38
|
-
versions.reject!(&:prerelease?) unless wants_prerelease?
|
39
|
-
versions.max
|
40
|
-
end
|
41
|
-
|
42
|
-
def fetch_latest_version_with_no_unlock
|
43
|
-
versions = available_versions
|
44
|
-
reqs = dependency.requirements.map do |r|
|
45
|
-
reqs = (r.fetch(:requirement) || "").split(",").map(&:strip)
|
46
|
-
requirement_class.new(reqs)
|
47
|
-
end
|
48
|
-
versions.reject!(&:prerelease?) unless wants_prerelease?
|
49
|
-
versions.sort.reverse.
|
50
|
-
reject { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }.
|
51
|
-
find { |v| reqs.all? { |r| r.satisfied_by?(v) } }
|
52
|
-
end
|
53
|
-
|
54
|
-
def wants_prerelease?
|
55
|
-
if dependency.version
|
56
|
-
version = version_class.new(dependency.version.tr("+", "."))
|
57
|
-
return version.prerelease?
|
58
|
-
end
|
59
|
-
|
60
|
-
dependency.requirements.any? do |req|
|
61
|
-
reqs = (req.fetch(:requirement) || "").split(",").map(&:strip)
|
62
|
-
reqs.any? { |r| r.match?(/[A-Za-z]/) }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# See https://www.python.org/dev/peps/pep-0503/ for details of the
|
67
|
-
# Simple Repository API we use here.
|
68
|
-
def available_versions
|
69
|
-
index_urls.flat_map do |index_url|
|
70
|
-
sanitized_url = index_url.gsub(%r{(?<=//).*(?=@)}, "redacted")
|
71
|
-
index_response = registry_response_for_dependency(index_url)
|
72
|
-
|
73
|
-
if [401, 403].include?(index_response.status) &&
|
74
|
-
[401, 403].include?(registry_index_response(index_url).status)
|
75
|
-
raise PrivateSourceAuthenticationFailure, sanitized_url
|
76
|
-
end
|
77
|
-
|
78
|
-
index_response.body.
|
79
|
-
scan(%r{<a\s.*?>(.*?)</a>}m).flatten.
|
80
|
-
select { |n| n.match?(name_regex) }.
|
81
|
-
map do |filename|
|
82
|
-
version =
|
83
|
-
filename.
|
84
|
-
gsub(/#{name_regex}-/i, "").
|
85
|
-
split(/-|(\.tar\.)/).
|
86
|
-
first
|
87
|
-
next unless version_class.correct?(version)
|
88
|
-
|
89
|
-
version_class.new(version)
|
90
|
-
end.compact
|
91
|
-
rescue Excon::Error::Timeout, Excon::Error::Socket
|
92
|
-
next if MAIN_PYPI_INDEXES.include?(index_url)
|
93
|
-
|
94
|
-
raise PrivateSourceAuthenticationFailure, sanitized_url
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def index_urls
|
99
|
-
main_index_url =
|
100
|
-
config_variable_index_urls[:main] ||
|
101
|
-
pipfile_index_urls[:main] ||
|
102
|
-
requirement_file_index_urls[:main] ||
|
103
|
-
pip_conf_index_urls[:main] ||
|
104
|
-
"https://pypi.python.org/simple/"
|
105
|
-
|
106
|
-
if main_index_url
|
107
|
-
main_index_url = main_index_url.strip.gsub(%r{/*$}, "") + "/"
|
108
|
-
end
|
109
|
-
|
110
|
-
extra_index_urls =
|
111
|
-
config_variable_index_urls[:extra] +
|
112
|
-
pipfile_index_urls[:extra] +
|
113
|
-
requirement_file_index_urls[:extra] +
|
114
|
-
pip_conf_index_urls[:extra]
|
115
|
-
|
116
|
-
extra_index_urls =
|
117
|
-
extra_index_urls.map { |url| url.strip.gsub(%r{/*$}, "") + "/" }
|
118
|
-
|
119
|
-
[main_index_url, *extra_index_urls].uniq
|
120
|
-
end
|
121
|
-
|
122
|
-
def registry_response_for_dependency(index_url)
|
123
|
-
Excon.get(
|
124
|
-
index_url + normalised_name + "/",
|
125
|
-
idempotent: true,
|
126
|
-
**SharedHelpers.excon_defaults
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
def registry_index_response(index_url)
|
131
|
-
Excon.get(
|
132
|
-
index_url,
|
133
|
-
idempotent: true,
|
134
|
-
**SharedHelpers.excon_defaults
|
135
|
-
)
|
136
|
-
end
|
137
|
-
|
138
|
-
def requirement_file_index_urls
|
139
|
-
urls = { main: nil, extra: [] }
|
140
|
-
|
141
|
-
requirements_files.each do |file|
|
142
|
-
if file.content.match?(/^--index-url\s(.+)/)
|
143
|
-
urls[:main] =
|
144
|
-
file.content.match(/^--index-url\s(.+)/).captures.first
|
145
|
-
end
|
146
|
-
urls[:extra] += file.content.scan(/^--extra-index-url\s(.+)/).
|
147
|
-
flatten
|
148
|
-
end
|
149
|
-
|
150
|
-
urls
|
151
|
-
end
|
152
|
-
|
153
|
-
def pip_conf_index_urls
|
154
|
-
urls = { main: nil, extra: [] }
|
155
|
-
|
156
|
-
return urls unless pip_conf
|
157
|
-
|
158
|
-
content = pip_conf.content
|
159
|
-
|
160
|
-
if content.match?(/^index-url\s*=/x)
|
161
|
-
urls[:main] = content.match(/^index-url\s*=\s*(.+)/).
|
162
|
-
captures.first
|
163
|
-
end
|
164
|
-
urls[:extra] += content.scan(/^extra-index-url\s*=(.+)/).flatten
|
165
|
-
|
166
|
-
urls
|
167
|
-
end
|
168
|
-
|
169
|
-
def pipfile_index_urls
|
170
|
-
urls = { main: nil, extra: [] }
|
171
|
-
|
172
|
-
return urls unless pipfile
|
173
|
-
|
174
|
-
pipfile_object = TomlRB.parse(pipfile.content)
|
175
|
-
|
176
|
-
urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
|
177
|
-
|
178
|
-
pipfile_object["source"]&.each do |source|
|
179
|
-
urls[:extra] << source.fetch("url") if source["url"]
|
180
|
-
end
|
181
|
-
urls[:extra] = urls[:extra].uniq
|
182
|
-
|
183
|
-
urls
|
184
|
-
rescue TomlRB::ParseError
|
185
|
-
urls
|
186
|
-
end
|
187
|
-
|
188
|
-
def config_variable_index_urls
|
189
|
-
urls = { main: nil, extra: [] }
|
190
|
-
|
191
|
-
index_url_creds = credentials.
|
192
|
-
select { |cred| cred["type"] == "python_index" }
|
193
|
-
urls[:main] =
|
194
|
-
index_url_creds.
|
195
|
-
find { |cred| cred["replaces-base"] }&.
|
196
|
-
fetch("index-url")
|
197
|
-
urls[:extra] =
|
198
|
-
index_url_creds.
|
199
|
-
reject { |cred| cred["replaces-base"] }.
|
200
|
-
map { |cred| cred["index-url"] }
|
201
|
-
|
202
|
-
urls
|
203
|
-
end
|
204
|
-
|
205
|
-
def ignore_reqs
|
206
|
-
ignored_versions.map { |req| requirement_class.new(req.split(",")) }
|
207
|
-
end
|
208
|
-
|
209
|
-
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
210
|
-
def normalised_name
|
211
|
-
dependency.name.downcase.gsub(/[-_.]+/, "-")
|
212
|
-
end
|
213
|
-
|
214
|
-
def name_regex
|
215
|
-
parts = dependency.name.split(/[\s_.-]/).map { |n| Regexp.quote(n) }
|
216
|
-
/#{parts.join("[\s_.-]")}/i
|
217
|
-
end
|
218
|
-
|
219
|
-
def pip_conf
|
220
|
-
dependency_files.find { |f| f.name == "pip.conf" }
|
221
|
-
end
|
222
|
-
|
223
|
-
def pipfile
|
224
|
-
dependency_files.find { |f| f.name == "Pipfile" }
|
225
|
-
end
|
226
|
-
|
227
|
-
def pyproject
|
228
|
-
dependency_files.find { |f| f.name == "pyproject.toml" }
|
229
|
-
end
|
230
|
-
|
231
|
-
def requirements_files
|
232
|
-
dependency_files.select { |f| f.name.match?(/requirements/x) }
|
233
|
-
end
|
234
|
-
|
235
|
-
def pip_compile_files
|
236
|
-
dependency_files.select { |f| f.name.end_with?(".in") }
|
237
|
-
end
|
238
|
-
|
239
|
-
def version_class
|
240
|
-
Utils.version_class_for_package_manager(dependency.package_manager)
|
241
|
-
end
|
242
|
-
|
243
|
-
def requirement_class
|
244
|
-
Utils.requirement_class_for_package_manager(
|
245
|
-
dependency.package_manager
|
246
|
-
)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
@@ -1,380 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "python_requirement_parser"
|
4
|
-
require "dependabot/file_fetchers/python/pip"
|
5
|
-
require "dependabot/file_parsers/python/pip"
|
6
|
-
require "dependabot/update_checkers/python/pip"
|
7
|
-
require "dependabot/file_updaters/python/pip/requirement_replacer"
|
8
|
-
require "dependabot/file_updaters/python/pip/setup_file_sanitizer"
|
9
|
-
require "dependabot/utils/python/version"
|
10
|
-
require "dependabot/shared_helpers"
|
11
|
-
|
12
|
-
# rubocop:disable Metrics/ClassLength
|
13
|
-
module Dependabot
|
14
|
-
module UpdateCheckers
|
15
|
-
module Python
|
16
|
-
class Pip
|
17
|
-
# This class does version resolution for pip-compile. Its approach is:
|
18
|
-
# - Unlock the dependency we're checking in the requirements.in file
|
19
|
-
# - Run `pip-compile` and see what the result is
|
20
|
-
class PipCompileVersionResolver
|
21
|
-
VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/.freeze
|
22
|
-
|
23
|
-
attr_reader :dependency, :dependency_files, :credentials
|
24
|
-
|
25
|
-
def initialize(dependency:, dependency_files:, credentials:,
|
26
|
-
unlock_requirement:, latest_allowable_version:)
|
27
|
-
@dependency = dependency
|
28
|
-
@dependency_files = dependency_files
|
29
|
-
@credentials = credentials
|
30
|
-
@latest_allowable_version = latest_allowable_version
|
31
|
-
@unlock_requirement = unlock_requirement
|
32
|
-
end
|
33
|
-
|
34
|
-
def latest_resolvable_version
|
35
|
-
return @latest_resolvable_version if @resolution_already_attempted
|
36
|
-
|
37
|
-
@resolution_already_attempted = true
|
38
|
-
@latest_resolvable_version ||= fetch_latest_resolvable_version
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
attr_reader :latest_allowable_version
|
44
|
-
|
45
|
-
def unlock_requirement?
|
46
|
-
@unlock_requirement
|
47
|
-
end
|
48
|
-
|
49
|
-
def fetch_latest_resolvable_version
|
50
|
-
@latest_resolvable_version_string ||=
|
51
|
-
SharedHelpers.in_a_temporary_directory do
|
52
|
-
SharedHelpers.with_git_configured(credentials: credentials) do
|
53
|
-
write_temporary_dependency_files
|
54
|
-
|
55
|
-
filenames_to_compile.each do |filename|
|
56
|
-
# Shell out to pip-compile.
|
57
|
-
# This is slow, as pip-compile needs to do installs.
|
58
|
-
cmd = "pyenv exec pip-compile --allow-unsafe "\
|
59
|
-
"-P #{dependency.name} #{filename}"
|
60
|
-
run_command(cmd)
|
61
|
-
end
|
62
|
-
|
63
|
-
# Remove any .python-version file before parsing the reqs
|
64
|
-
FileUtils.remove_entry(".python-version", true)
|
65
|
-
|
66
|
-
parse_updated_files
|
67
|
-
end
|
68
|
-
rescue SharedHelpers::HelperSubprocessFailed => error
|
69
|
-
handle_pip_compile_errors(error)
|
70
|
-
end
|
71
|
-
return unless @latest_resolvable_version_string
|
72
|
-
|
73
|
-
Utils::Python::Version.new(@latest_resolvable_version_string)
|
74
|
-
end
|
75
|
-
|
76
|
-
def parse_requirements_from_cwd_files
|
77
|
-
SharedHelpers.run_helper_subprocess(
|
78
|
-
command: "pyenv exec python #{python_helper_path}",
|
79
|
-
function: "parse_requirements",
|
80
|
-
args: [Dir.pwd]
|
81
|
-
)
|
82
|
-
end
|
83
|
-
|
84
|
-
def handle_pip_compile_errors(error)
|
85
|
-
if error.message.include?("Could not find a version")
|
86
|
-
check_original_requirements_resolvable
|
87
|
-
# If the original requirements are resolvable but we get an
|
88
|
-
# incompatibility update after unlocking then it's likely to be
|
89
|
-
# due to problems with pip-compile's cascading resolution
|
90
|
-
return nil
|
91
|
-
end
|
92
|
-
|
93
|
-
if error.message.include?('Command "python setup.py egg_info') &&
|
94
|
-
error.message.include?(dependency.name)
|
95
|
-
# The latest version of the dependency we're updating is borked
|
96
|
-
# (because it has an unevaluatable setup.py). Skip the update.
|
97
|
-
return nil
|
98
|
-
end
|
99
|
-
|
100
|
-
if error.message.include?("Could not find a version ") &&
|
101
|
-
!error.message.include?(dependency.name)
|
102
|
-
# Sometimes pip-tools gets confused and can't work around
|
103
|
-
# sub-dependency incompatibilities. Ignore those cases.
|
104
|
-
return nil
|
105
|
-
end
|
106
|
-
|
107
|
-
raise
|
108
|
-
end
|
109
|
-
|
110
|
-
# Needed because pip-compile's resolver isn't perfect.
|
111
|
-
# Note: We raise errors from this method, rather than returning a
|
112
|
-
# boolean, so that all deps for this repo will raise identical
|
113
|
-
# errors when failing to update
|
114
|
-
def check_original_requirements_resolvable
|
115
|
-
SharedHelpers.in_a_temporary_directory do
|
116
|
-
SharedHelpers.with_git_configured(credentials: credentials) do
|
117
|
-
write_temporary_dependency_files(unlock_requirement: false)
|
118
|
-
|
119
|
-
filenames_to_compile.each do |filename|
|
120
|
-
cmd = "pyenv exec pip-compile --allow-unsafe #{filename}"
|
121
|
-
run_command(cmd)
|
122
|
-
end
|
123
|
-
|
124
|
-
true
|
125
|
-
rescue SharedHelpers::HelperSubprocessFailed => error
|
126
|
-
raise unless error.message.include?("Could not find a version")
|
127
|
-
|
128
|
-
msg = clean_error_message(error.message)
|
129
|
-
raise if msg.empty?
|
130
|
-
|
131
|
-
raise DependencyFileNotResolvable, msg
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def run_command(command)
|
137
|
-
command = command.dup
|
138
|
-
raw_response = nil
|
139
|
-
IO.popen(command, err: %i(child out)) do |process|
|
140
|
-
raw_response = process.read
|
141
|
-
end
|
142
|
-
|
143
|
-
# Raise an error with the output from the shell session if
|
144
|
-
# pip-compile returns a non-zero status
|
145
|
-
return if $CHILD_STATUS.success?
|
146
|
-
|
147
|
-
raise SharedHelpers::HelperSubprocessFailed.new(
|
148
|
-
raw_response,
|
149
|
-
command
|
150
|
-
)
|
151
|
-
rescue SharedHelpers::HelperSubprocessFailed => error
|
152
|
-
original_error ||= error
|
153
|
-
msg = error.message
|
154
|
-
|
155
|
-
relevant_error =
|
156
|
-
if error_suggests_bad_python_version?(msg) then original_error
|
157
|
-
else error
|
158
|
-
end
|
159
|
-
|
160
|
-
raise relevant_error unless error_suggests_bad_python_version?(msg)
|
161
|
-
raise relevant_error if File.exist?(".python-version")
|
162
|
-
|
163
|
-
command = "pyenv local 2.7.15 && " + command
|
164
|
-
retry
|
165
|
-
ensure
|
166
|
-
FileUtils.remove_entry(".python-version", true)
|
167
|
-
end
|
168
|
-
|
169
|
-
def error_suggests_bad_python_version?(message)
|
170
|
-
return true if message.include?("not find a version that satisfies")
|
171
|
-
|
172
|
-
message.include?('Command "python setup.py egg_info" failed')
|
173
|
-
end
|
174
|
-
|
175
|
-
def write_temporary_dependency_files(unlock_requirement: true)
|
176
|
-
dependency_files.each do |file|
|
177
|
-
next if file.name == ".python-version"
|
178
|
-
|
179
|
-
path = file.name
|
180
|
-
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
181
|
-
File.write(
|
182
|
-
path,
|
183
|
-
unlock_requirement ? unlock_dependency(file) : file.content
|
184
|
-
)
|
185
|
-
end
|
186
|
-
|
187
|
-
setup_files.each do |file|
|
188
|
-
path = file.name
|
189
|
-
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
190
|
-
File.write(path, sanitized_setup_file_content(file))
|
191
|
-
end
|
192
|
-
|
193
|
-
setup_cfg_files.each do |file|
|
194
|
-
path = file.name
|
195
|
-
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
196
|
-
File.write(path, "[metadata]\nname = sanitized-package\n")
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def sanitized_setup_file_content(file)
|
201
|
-
@sanitized_setup_file_content ||= {}
|
202
|
-
if @sanitized_setup_file_content[file.name]
|
203
|
-
return @sanitized_setup_file_content[file.name]
|
204
|
-
end
|
205
|
-
|
206
|
-
@sanitized_setup_file_content[file.name] =
|
207
|
-
FileUpdaters::Python::Pip::SetupFileSanitizer.
|
208
|
-
new(setup_file: file, setup_cfg: setup_cfg(file)).
|
209
|
-
sanitized_content
|
210
|
-
end
|
211
|
-
|
212
|
-
def setup_cfg(file)
|
213
|
-
dependency_files.find do |f|
|
214
|
-
f.name == file.name.sub(/\.py$/, ".cfg")
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def unlock_dependency(file)
|
219
|
-
return file.content unless file.name.end_with?(".in")
|
220
|
-
return file.content unless dependency.version
|
221
|
-
return file.content unless unlock_requirement?
|
222
|
-
|
223
|
-
req = dependency.requirements.find { |r| r[:file] == file.name }
|
224
|
-
return file.content unless req&.fetch(:requirement)
|
225
|
-
|
226
|
-
FileUpdaters::Python::Pip::RequirementReplacer.new(
|
227
|
-
content: file.content,
|
228
|
-
dependency_name: dependency.name,
|
229
|
-
old_requirement: req[:requirement],
|
230
|
-
new_requirement: updated_version_requirement_string
|
231
|
-
).updated_content
|
232
|
-
end
|
233
|
-
|
234
|
-
def updated_version_requirement_string
|
235
|
-
lower_bound_req = updated_version_req_lower_bound
|
236
|
-
|
237
|
-
# Add the latest_allowable_version as an upper bound. This means
|
238
|
-
# ignore conditions are considered when checking for the latest
|
239
|
-
# resolvable version.
|
240
|
-
#
|
241
|
-
# NOTE: This isn't perfect. If v2.x is ignored and v3 is out but
|
242
|
-
# unresolvable then the `latest_allowable_version` will be v3, and
|
243
|
-
# we won't be ignoring v2.x releases like we should be.
|
244
|
-
return lower_bound_req if latest_allowable_version.nil?
|
245
|
-
unless Utils::Python::Version.correct?(latest_allowable_version)
|
246
|
-
return lower_bound_req
|
247
|
-
end
|
248
|
-
|
249
|
-
lower_bound_req + ", <= #{latest_allowable_version}"
|
250
|
-
end
|
251
|
-
|
252
|
-
def updated_version_req_lower_bound
|
253
|
-
if dependency.version
|
254
|
-
">= #{dependency.version}"
|
255
|
-
else
|
256
|
-
version_for_requirement =
|
257
|
-
dependency.requirements.map { |r| r[:requirement] }.compact.
|
258
|
-
reject { |req_string| req_string.start_with?("<") }.
|
259
|
-
select { |req_string| req_string.match?(VERSION_REGEX) }.
|
260
|
-
map { |req_string| req_string.match(VERSION_REGEX) }.
|
261
|
-
select { |version| Gem::Version.correct?(version) }.
|
262
|
-
max_by { |version| Gem::Version.new(version) }
|
263
|
-
|
264
|
-
">= #{version_for_requirement || 0}"
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def python_helper_path
|
269
|
-
project_root = File.join(File.dirname(__FILE__), "../../../../..")
|
270
|
-
File.join(project_root, "helpers/python/run.py")
|
271
|
-
end
|
272
|
-
|
273
|
-
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
274
|
-
def normalise(name)
|
275
|
-
name.downcase.gsub(/[-_.]+/, "-")
|
276
|
-
end
|
277
|
-
|
278
|
-
def clean_error_message(message)
|
279
|
-
# Redact any URLs, as they may include credentials
|
280
|
-
message.gsub(/http.*?(?=\s)/, "<redacted>")
|
281
|
-
end
|
282
|
-
|
283
|
-
def filenames_to_compile
|
284
|
-
files_from_reqs =
|
285
|
-
dependency.requirements.
|
286
|
-
map { |r| r[:file] }.
|
287
|
-
select { |fn| fn.end_with?(".in") }
|
288
|
-
|
289
|
-
files_from_compiled_files =
|
290
|
-
pip_compile_files.map(&:name).select do |fn|
|
291
|
-
compiled_file = dependency_files.
|
292
|
-
find { |f| f.name == fn.gsub(/\.in$/, ".txt") }
|
293
|
-
compiled_file_includes_dependency?(compiled_file)
|
294
|
-
end
|
295
|
-
|
296
|
-
filenames = [*files_from_reqs, *files_from_compiled_files].uniq
|
297
|
-
|
298
|
-
order_filenames_for_compilation(filenames)
|
299
|
-
end
|
300
|
-
|
301
|
-
def compiled_file_includes_dependency?(compiled_file)
|
302
|
-
return false unless compiled_file
|
303
|
-
|
304
|
-
regex = PythonRequirementParser::INSTALL_REQ_WITH_REQUIREMENT
|
305
|
-
|
306
|
-
matches = []
|
307
|
-
compiled_file.content.scan(regex) { matches << Regexp.last_match }
|
308
|
-
matches.any? { |m| normalise(m[:name]) == dependency.name }
|
309
|
-
end
|
310
|
-
|
311
|
-
# If the files we need to update require one another then we need to
|
312
|
-
# update them in the right order
|
313
|
-
def order_filenames_for_compilation(filenames)
|
314
|
-
ordered_filenames = []
|
315
|
-
|
316
|
-
while (remaining_filenames = filenames - ordered_filenames).any?
|
317
|
-
ordered_filenames +=
|
318
|
-
remaining_filenames.
|
319
|
-
select do |fn|
|
320
|
-
unupdated_reqs = requirement_map[fn] - ordered_filenames
|
321
|
-
(unupdated_reqs & filenames).empty?
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
ordered_filenames
|
326
|
-
end
|
327
|
-
|
328
|
-
def requirement_map
|
329
|
-
child_req_regex = FileFetchers::Python::Pip::CHILD_REQUIREMENT_REGEX
|
330
|
-
@requirement_map ||=
|
331
|
-
pip_compile_files.each_with_object({}) do |file, req_map|
|
332
|
-
paths = file.content.scan(child_req_regex).flatten
|
333
|
-
current_dir = File.dirname(file.name)
|
334
|
-
|
335
|
-
req_map[file.name] =
|
336
|
-
paths.map do |path|
|
337
|
-
path = File.join(current_dir, path) if current_dir != "."
|
338
|
-
path = Pathname.new(path).cleanpath.to_path
|
339
|
-
path = path.gsub(/\.txt$/, ".in")
|
340
|
-
next if path == file.name
|
341
|
-
|
342
|
-
path
|
343
|
-
end.uniq.compact
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
def parse_updated_files
|
348
|
-
updated_files =
|
349
|
-
dependency_files.map do |file|
|
350
|
-
next file if file.name == ".python-version"
|
351
|
-
|
352
|
-
updated_file = file.dup
|
353
|
-
updated_file.content = File.read(file.name)
|
354
|
-
updated_file
|
355
|
-
end
|
356
|
-
|
357
|
-
FileParsers::Python::Pip.new(
|
358
|
-
dependency_files: updated_files,
|
359
|
-
source: nil,
|
360
|
-
credentials: credentials
|
361
|
-
).parse.find { |d| d.name == dependency.name }&.version
|
362
|
-
end
|
363
|
-
|
364
|
-
def setup_files
|
365
|
-
dependency_files.select { |f| f.name.end_with?("setup.py") }
|
366
|
-
end
|
367
|
-
|
368
|
-
def pip_compile_files
|
369
|
-
dependency_files.select { |f| f.name.end_with?(".in") }
|
370
|
-
end
|
371
|
-
|
372
|
-
def setup_cfg_files
|
373
|
-
dependency_files.select { |f| f.name.end_with?("setup.cfg") }
|
374
|
-
end
|
375
|
-
end
|
376
|
-
end
|
377
|
-
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
# rubocop:enable Metrics/ClassLength
|