dependabot-core 0.78.0 → 0.79.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/helpers/npm/lib/updater.js +11 -5
  4. data/helpers/npm/package.json +2 -2
  5. data/helpers/npm/yarn.lock +26 -28
  6. data/helpers/yarn/lib/replace-lockfile-declaration.js +15 -3
  7. data/helpers/yarn/lib/updater.js +17 -5
  8. data/helpers/yarn/package.json +2 -2
  9. data/helpers/yarn/yarn.lock +24 -31
  10. data/lib/dependabot/file_fetchers.rb +0 -2
  11. data/lib/dependabot/file_parsers.rb +0 -2
  12. data/lib/dependabot/file_updaters.rb +0 -2
  13. data/lib/dependabot/metadata_finders.rb +0 -2
  14. data/lib/dependabot/update_checkers.rb +0 -2
  15. data/lib/dependabot/utils.rb +0 -4
  16. data/lib/dependabot/version.rb +1 -1
  17. metadata +3 -34
  18. data/helpers/python/lib/__init__.py +0 -0
  19. data/helpers/python/lib/hasher.py +0 -23
  20. data/helpers/python/lib/parser.py +0 -130
  21. data/helpers/python/requirements.txt +0 -9
  22. data/helpers/python/run.py +0 -18
  23. data/lib/dependabot/file_fetchers/python/pip.rb +0 -305
  24. data/lib/dependabot/file_parsers/python/pip.rb +0 -223
  25. data/lib/dependabot/file_parsers/python/pip/pipfile_files_parser.rb +0 -154
  26. data/lib/dependabot/file_parsers/python/pip/poetry_files_parser.rb +0 -141
  27. data/lib/dependabot/file_parsers/python/pip/setup_file_parser.rb +0 -164
  28. data/lib/dependabot/file_updaters/python/pip.rb +0 -147
  29. data/lib/dependabot/file_updaters/python/pip/pip_compile_file_updater.rb +0 -363
  30. data/lib/dependabot/file_updaters/python/pip/pipfile_file_updater.rb +0 -397
  31. data/lib/dependabot/file_updaters/python/pip/pipfile_preparer.rb +0 -125
  32. data/lib/dependabot/file_updaters/python/pip/poetry_file_updater.rb +0 -289
  33. data/lib/dependabot/file_updaters/python/pip/pyproject_preparer.rb +0 -105
  34. data/lib/dependabot/file_updaters/python/pip/requirement_file_updater.rb +0 -166
  35. data/lib/dependabot/file_updaters/python/pip/requirement_replacer.rb +0 -95
  36. data/lib/dependabot/file_updaters/python/pip/setup_file_sanitizer.rb +0 -91
  37. data/lib/dependabot/file_updaters/ruby/.DS_Store +0 -0
  38. data/lib/dependabot/metadata_finders/python/pip.rb +0 -120
  39. data/lib/dependabot/update_checkers/python/pip.rb +0 -227
  40. data/lib/dependabot/update_checkers/python/pip/latest_version_finder.rb +0 -252
  41. data/lib/dependabot/update_checkers/python/pip/pip_compile_version_resolver.rb +0 -380
  42. data/lib/dependabot/update_checkers/python/pip/pipfile_version_resolver.rb +0 -559
  43. data/lib/dependabot/update_checkers/python/pip/poetry_version_resolver.rb +0 -300
  44. data/lib/dependabot/update_checkers/python/pip/requirements_updater.rb +0 -367
  45. data/lib/dependabot/utils/python/requirement.rb +0 -130
  46. data/lib/dependabot/utils/python/version.rb +0 -88
  47. data/lib/python_requirement_parser.rb +0 -33
  48. data/lib/python_versions.rb +0 -21
@@ -1,397 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
-
5
- require "python_requirement_parser"
6
- require "dependabot/file_updaters/python/pip"
7
- require "dependabot/shared_helpers"
8
-
9
- # rubocop:disable Metrics/ClassLength
10
- module Dependabot
11
- module FileUpdaters
12
- module Python
13
- class Pip
14
- class PipfileFileUpdater
15
- require_relative "pipfile_preparer"
16
- require_relative "setup_file_sanitizer"
17
-
18
- attr_reader :dependencies, :dependency_files, :credentials
19
-
20
- def initialize(dependencies:, dependency_files:, credentials:)
21
- @dependencies = dependencies
22
- @dependency_files = dependency_files
23
- @credentials = credentials
24
- end
25
-
26
- def updated_dependency_files
27
- return @updated_dependency_files if @update_already_attempted
28
-
29
- @update_already_attempted = true
30
- @updated_dependency_files ||= fetch_updated_dependency_files
31
- end
32
-
33
- private
34
-
35
- def dependency
36
- # For now, we'll only ever be updating a single dependency
37
- dependencies.first
38
- end
39
-
40
- def fetch_updated_dependency_files
41
- updated_files = []
42
-
43
- if file_changed?(pipfile)
44
- updated_files <<
45
- updated_file(file: pipfile, content: updated_pipfile_content)
46
- end
47
-
48
- if lockfile
49
- if lockfile.content == updated_lockfile_content
50
- raise "Expected Pipfile.lock to change!"
51
- end
52
-
53
- updated_files <<
54
- updated_file(file: lockfile, content: updated_lockfile_content)
55
- end
56
-
57
- updated_files += updated_generated_requirements_files
58
- updated_files
59
- end
60
-
61
- def updated_pipfile_content
62
- dependencies.
63
- select { |dep| requirement_changed?(pipfile, dep) }.
64
- reduce(pipfile.content.dup) do |content, dep|
65
- updated_requirement =
66
- dep.requirements.find { |r| r[:file] == pipfile.name }.
67
- fetch(:requirement)
68
-
69
- old_req =
70
- dep.previous_requirements.
71
- find { |r| r[:file] == pipfile.name }.
72
- fetch(:requirement)
73
-
74
- updated_content =
75
- content.gsub(declaration_regex(dep)) do |line|
76
- line.gsub(old_req, updated_requirement)
77
- end
78
-
79
- raise "Content did not change!" if content == updated_content
80
-
81
- updated_content
82
- end
83
- end
84
-
85
- def updated_lockfile_content
86
- @updated_lockfile_content ||=
87
- updated_generated_files.fetch(:lockfile)
88
- end
89
-
90
- def generate_updated_requirements_files?
91
- return true if generated_requirements_files("default").any?
92
-
93
- generated_requirements_files("develop").any?
94
- end
95
-
96
- def generated_requirements_files(type)
97
- return [] unless lockfile
98
-
99
- pipfile_lock_deps = parsed_lockfile[type]&.keys&.sort || []
100
- pipfile_lock_deps = pipfile_lock_deps.map { |n| normalise(n) }
101
-
102
- regex = PythonRequirementParser::INSTALL_REQ_WITH_REQUIREMENT
103
-
104
- # Find any requirement files that list the same dependencies as
105
- # the (old) Pipfile.lock. Any such files were almost certainly
106
- # generated using `pipenv lock -r`
107
- requirements_files.select do |req_file|
108
- deps = []
109
- req_file.content.scan(regex) { deps << Regexp.last_match }
110
- deps = deps.map { |m| normalise(m[:name]) }
111
- deps.sort == pipfile_lock_deps
112
- end
113
- end
114
-
115
- def updated_generated_requirements_files
116
- updated_files = []
117
-
118
- generated_requirements_files("default").each do |file|
119
- next if file.content == updated_req_content
120
-
121
- updated_files <<
122
- updated_file(file: file, content: updated_req_content)
123
- end
124
-
125
- generated_requirements_files("develop").each do |file|
126
- next if file.content == updated_dev_req_content
127
-
128
- updated_files <<
129
- updated_file(file: file, content: updated_dev_req_content)
130
- end
131
-
132
- updated_files
133
- end
134
-
135
- def updated_req_content
136
- updated_generated_files.fetch(:requirements_txt)
137
- end
138
-
139
- def updated_dev_req_content
140
- updated_generated_files.fetch(:dev_requirements_txt)
141
- end
142
-
143
- def prepared_pipfile_content
144
- content = updated_pipfile_content
145
- content = freeze_other_dependencies(content)
146
- content = freeze_dependencies_being_updated(content)
147
- content = add_private_sources(content)
148
- content
149
- end
150
-
151
- def freeze_other_dependencies(pipfile_content)
152
- PipfilePreparer.
153
- new(pipfile_content: pipfile_content).
154
- freeze_top_level_dependencies_except(dependencies, lockfile)
155
- end
156
-
157
- def freeze_dependencies_being_updated(pipfile_content)
158
- pipfile_object = TomlRB.parse(pipfile_content)
159
-
160
- dependencies.each do |dep|
161
- %w(packages dev-packages).each do |type|
162
- names = pipfile_object[type]&.keys || []
163
- pkg_name = names.find { |nm| normalise(nm) == dep.name }
164
- next unless pkg_name
165
-
166
- if pipfile_object[type][pkg_name].is_a?(Hash)
167
- pipfile_object[type][pkg_name]["version"] =
168
- "==#{dep.version}"
169
- else
170
- pipfile_object[type][pkg_name] = "==#{dep.version}"
171
- end
172
- end
173
- end
174
-
175
- TomlRB.dump(pipfile_object)
176
- end
177
-
178
- def add_private_sources(pipfile_content)
179
- PipfilePreparer.
180
- new(pipfile_content: pipfile_content).
181
- replace_sources(credentials)
182
- end
183
-
184
- def updated_generated_files
185
- @updated_generated_files ||=
186
- SharedHelpers.in_a_temporary_directory do
187
- SharedHelpers.with_git_configured(credentials: credentials) do
188
- write_temporary_dependency_files(prepared_pipfile_content)
189
-
190
- # Initialize a git repo to appease pip-tools
191
- IO.popen("git init", err: %i(child out)) if setup_files.any?
192
-
193
- run_pipenv_command(
194
- pipenv_environment_variables + "pyenv exec pipenv lock"
195
- )
196
-
197
- result = { lockfile: File.read("Pipfile.lock") }
198
- result[:lockfile] = post_process_lockfile(result[:lockfile])
199
-
200
- # Generate updated requirement.txt entries, if needed.
201
- if generate_updated_requirements_files?
202
- generate_updated_requirements_files
203
-
204
- result[:requirements_txt] = File.read("req.txt")
205
- result[:dev_requirements_txt] = File.read("dev-req.txt")
206
- end
207
-
208
- result
209
- end
210
- end
211
- end
212
-
213
- def post_process_lockfile(updated_lockfile_content)
214
- pipfile_hash = pipfile_hash_for(updated_pipfile_content)
215
- original_reqs = parsed_lockfile["_meta"]["requires"]
216
- original_source = parsed_lockfile["_meta"]["sources"]
217
-
218
- new_lockfile = updated_lockfile_content.dup
219
- new_lockfile_json = JSON.parse(new_lockfile)
220
- new_lockfile_json["_meta"]["hash"]["sha256"] = pipfile_hash
221
- new_lockfile_json["_meta"]["requires"] = original_reqs
222
- new_lockfile_json["_meta"]["sources"] = original_source
223
-
224
- JSON.pretty_generate(new_lockfile_json, indent: " ").
225
- gsub(/\{\n\s*\}/, "{}").
226
- gsub(/\}\z/, "}\n")
227
- end
228
-
229
- def generate_updated_requirements_files
230
- run_pipenv_command(
231
- pipenv_environment_variables +
232
- "pyenv exec pipenv lock -r > req.txt"
233
- )
234
- run_pipenv_command(
235
- pipenv_environment_variables +
236
- "pyenv exec pipenv lock -r -d > dev-req.txt"
237
- )
238
- end
239
-
240
- def run_pipenv_command(cmd)
241
- raw_response = nil
242
- IO.popen(cmd, err: %i(child out)) { |p| raw_response = p.read }
243
-
244
- # Raise an error with the output from the shell session if Pipenv
245
- # returns a non-zero status
246
- return if $CHILD_STATUS.success?
247
-
248
- raise SharedHelpers::HelperSubprocessFailed.new(raw_response, cmd)
249
- rescue SharedHelpers::HelperSubprocessFailed => error
250
- original_error ||= error
251
- msg = error.message
252
-
253
- relevant_error =
254
- if error_suggests_bad_python_version?(msg) then original_error
255
- else error
256
- end
257
-
258
- raise relevant_error unless error_suggests_bad_python_version?(msg)
259
- raise relevant_error if cmd.include?("--two")
260
-
261
- cmd = cmd.gsub("pipenv ", "pipenv --two ")
262
- retry
263
- end
264
-
265
- def error_suggests_bad_python_version?(message)
266
- return true if message.include?("UnsupportedPythonVersion")
267
-
268
- message.include?('Command "python setup.py egg_info" failed')
269
- end
270
-
271
- def write_temporary_dependency_files(pipfile_content)
272
- dependency_files.each do |file|
273
- next if file.name == ".python-version"
274
-
275
- path = file.name
276
- FileUtils.mkdir_p(Pathname.new(path).dirname)
277
- File.write(path, file.content)
278
- end
279
-
280
- setup_files.each do |file|
281
- path = file.name
282
- FileUtils.mkdir_p(Pathname.new(path).dirname)
283
- File.write(path, sanitized_setup_file_content(file))
284
- end
285
-
286
- setup_cfg_files.each do |file|
287
- path = file.name
288
- FileUtils.mkdir_p(Pathname.new(path).dirname)
289
- File.write(path, "[metadata]\nname = sanitized-package\n")
290
- end
291
-
292
- # Overwrite the pipfile with updated content
293
- File.write("Pipfile", pipfile_content)
294
- end
295
-
296
- def sanitized_setup_file_content(file)
297
- @sanitized_setup_file_content ||= {}
298
- if @sanitized_setup_file_content[file.name]
299
- return @sanitized_setup_file_content[file.name]
300
- end
301
-
302
- @sanitized_setup_file_content[file.name] =
303
- SetupFileSanitizer.
304
- new(setup_file: file, setup_cfg: setup_cfg(file)).
305
- sanitized_content
306
- end
307
-
308
- def setup_cfg(file)
309
- dependency_files.find do |f|
310
- f.name == file.name.sub(/\.py$/, ".cfg")
311
- end
312
- end
313
-
314
- def pipfile_hash_for(pipfile_content)
315
- SharedHelpers.in_a_temporary_directory do |dir|
316
- File.write(File.join(dir, "Pipfile"), pipfile_content)
317
- SharedHelpers.run_helper_subprocess(
318
- command: "pyenv exec python #{python_helper_path}",
319
- function: "get_pipfile_hash",
320
- args: [dir]
321
- )
322
- end
323
- end
324
-
325
- def declaration_regex(dep)
326
- escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
327
- /(?:^|["'])#{escaped_name}["']?\s*=.*$/i
328
- end
329
-
330
- def file_changed?(file)
331
- dependencies.any? { |dep| requirement_changed?(file, dep) }
332
- end
333
-
334
- def requirement_changed?(file, dependency)
335
- changed_requirements =
336
- dependency.requirements - dependency.previous_requirements
337
-
338
- changed_requirements.any? { |f| f[:file] == file.name }
339
- end
340
-
341
- def updated_file(file:, content:)
342
- updated_file = file.dup
343
- updated_file.content = content
344
- updated_file
345
- end
346
-
347
- def python_helper_path
348
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
349
- File.join(project_root, "helpers/python/run.py")
350
- end
351
-
352
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
353
- def normalise(name)
354
- name.downcase.gsub(/[-_.]+/, "-")
355
- end
356
-
357
- def parsed_lockfile
358
- @parsed_lockfile ||= JSON.parse(lockfile.content)
359
- end
360
-
361
- def pipfile
362
- @pipfile ||= dependency_files.find { |f| f.name == "Pipfile" }
363
- end
364
-
365
- def lockfile
366
- @lockfile ||= dependency_files.find { |f| f.name == "Pipfile.lock" }
367
- end
368
-
369
- def setup_files
370
- dependency_files.select { |f| f.name.end_with?("setup.py") }
371
- end
372
-
373
- def setup_cfg_files
374
- dependency_files.select { |f| f.name.end_with?("setup.cfg") }
375
- end
376
-
377
- def requirements_files
378
- dependency_files.select { |f| f.name.end_with?(".txt") }
379
- end
380
-
381
- def pipenv_environment_variables
382
- environment_variables = [
383
- "PIPENV_YES=true", # Install new Python versions if needed
384
- "PIPENV_MAX_RETRIES=3", # Retry timeouts
385
- "PIPENV_NOSPIN=1", # Don't pollute logs with spinner
386
- "PIPENV_TIMEOUT=600", # Set install timeout to 10 minutes
387
- "PIP_DEFAULT_TIMEOUT=60" # Set pip timeout to 1 minute
388
- ]
389
-
390
- environment_variables.join(" ") + " "
391
- end
392
- end
393
- end
394
- end
395
- end
396
- end
397
- # rubocop:enable Metrics/ClassLength
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
-
5
- require "dependabot/file_parsers/python/pip"
6
- require "dependabot/file_updaters/python/pip"
7
-
8
- module Dependabot
9
- module FileUpdaters
10
- module Python
11
- class Pip
12
- class PipfilePreparer
13
- def initialize(pipfile_content:)
14
- @pipfile_content = pipfile_content
15
- end
16
-
17
- def replace_sources(credentials)
18
- pipfile_object = TomlRB.parse(pipfile_content)
19
-
20
- pipfile_object["source"] =
21
- pipfile_sources.reject { |h| h["url"].include?("${") } +
22
- config_variable_sources(credentials)
23
-
24
- TomlRB.dump(pipfile_object)
25
- end
26
-
27
- def freeze_top_level_dependencies_except(dependencies, lockfile)
28
- return pipfile_content unless lockfile
29
-
30
- pipfile_object = TomlRB.parse(pipfile_content)
31
- excluded_names = dependencies.map(&:name)
32
-
33
- FileParsers::Python::Pip::DEPENDENCY_GROUP_KEYS.each do |keys|
34
- next unless pipfile_object[keys[:pipfile]]
35
-
36
- pipfile_object.fetch(keys[:pipfile]).each do |dep_name, _|
37
- next if excluded_names.include?(normalise(dep_name))
38
-
39
- freeze_dependency(dep_name, pipfile_object, lockfile, keys)
40
- end
41
- end
42
-
43
- TomlRB.dump(pipfile_object)
44
- end
45
-
46
- # rubocop:disable Metrics/PerceivedComplexity
47
- def freeze_dependency(dep_name, pipfile_object, lockfile, keys)
48
- locked_version = version_from_lockfile(
49
- lockfile,
50
- keys[:lockfile],
51
- normalise(dep_name)
52
- )
53
- locked_ref = ref_from_lockfile(
54
- lockfile,
55
- keys[:lockfile],
56
- normalise(dep_name)
57
- )
58
-
59
- pipfile_req = pipfile_object[keys[:pipfile]][dep_name]
60
- if pipfile_req.is_a?(Hash) && locked_version
61
- pipfile_req["version"] = "==#{locked_version}"
62
- elsif pipfile_req.is_a?(Hash) && locked_ref && !pipfile_req["ref"]
63
- pipfile_req["ref"] = locked_ref
64
- elsif locked_version
65
- pipfile_object[keys[:pipfile]][dep_name] = "==#{locked_version}"
66
- end
67
- end
68
- # rubocop:enable Metrics/PerceivedComplexity
69
-
70
- def update_python_requirement(requirement)
71
- pipfile_object = TomlRB.parse(pipfile_content)
72
-
73
- pipfile_object["requires"] ||= {}
74
- pipfile_object["requires"].delete("python_full_version")
75
- pipfile_object["requires"].delete("python_version")
76
- pipfile_object["requires"]["python_full_version"] = requirement
77
-
78
- TomlRB.dump(pipfile_object)
79
- end
80
-
81
- private
82
-
83
- attr_reader :pipfile_content
84
-
85
- def version_from_lockfile(lockfile, dep_type, dep_name)
86
- details = JSON.parse(lockfile.content).
87
- dig(dep_type, normalise(dep_name))
88
-
89
- case details
90
- when String then details.gsub(/^==/, "")
91
- when Hash then details["version"]&.gsub(/^==/, "")
92
- end
93
- end
94
-
95
- def ref_from_lockfile(lockfile, dep_type, dep_name)
96
- details = JSON.parse(lockfile.content).
97
- dig(dep_type, normalise(dep_name))
98
-
99
- case details
100
- when Hash then details["ref"]
101
- end
102
- end
103
-
104
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
105
- def normalise(name)
106
- name.downcase.gsub(/[-_.]+/, "-")
107
- end
108
-
109
- def pipfile_sources
110
- @pipfile_sources ||=
111
- TomlRB.parse(pipfile_content).fetch("source", []).
112
- map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
113
- end
114
-
115
- def config_variable_sources(credentials)
116
- @config_variable_sources ||=
117
- credentials.
118
- select { |cred| cred["type"] == "python_index" }.
119
- map { |cred| { "url" => cred["index-url"] } }
120
- end
121
- end
122
- end
123
- end
124
- end
125
- end