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,164 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dependabot/dependency"
4
- require "dependabot/errors"
5
- require "dependabot/file_parsers/base/dependency_set"
6
- require "dependabot/file_parsers/python/pip"
7
- require "dependabot/shared_helpers"
8
-
9
- module Dependabot
10
- module FileParsers
11
- module Python
12
- class Pip
13
- class SetupFileParser
14
- INSTALL_REQUIRES_REGEX =
15
- /install_requires\s*=\s*(\[.*?\])[,)\s]/m.freeze
16
- SETUP_REQUIRES_REGEX = /setup_requires\s*=\s*(\[.*?\])[,)\s]/m.freeze
17
- TESTS_REQUIRE_REGEX = /tests_require\s*=\s*(\[.*?\])[,)\s]/m.freeze
18
- EXTRAS_REQUIRE_REGEX = /extras_require\s*=\s*(\{.*?\})[,)\s]/m.freeze
19
-
20
- def initialize(dependency_files:)
21
- @dependency_files = dependency_files
22
- end
23
-
24
- def dependency_set
25
- dependencies = Dependabot::FileParsers::Base::DependencySet.new
26
-
27
- parsed_setup_file.each do |dep|
28
- # If a requirement has a `<` or `<=` marker then updating it is
29
- # probably blocked. Ignore it.
30
- next if dep["markers"].include?("<")
31
-
32
- # If the requirement is our inserted version, ignore it
33
- # (we wouldn't be able to update it)
34
- next if dep["version"] == "0.0.1+dependabot"
35
-
36
- dependencies <<
37
- Dependency.new(
38
- name: normalised_name(dep["name"]),
39
- version: dep["version"]&.include?("*") ? nil : dep["version"],
40
- requirements: [{
41
- requirement: dep["requirement"],
42
- file: Pathname.new(dep["file"]).cleanpath.to_path,
43
- source: nil,
44
- groups: [dep["requirement_type"]]
45
- }],
46
- package_manager: "pip"
47
- )
48
- end
49
- dependencies
50
- end
51
-
52
- private
53
-
54
- attr_reader :dependency_files
55
-
56
- def parsed_setup_file
57
- SharedHelpers.in_a_temporary_directory do
58
- write_temporary_dependency_files
59
-
60
- requirements = SharedHelpers.run_helper_subprocess(
61
- command: "pyenv exec python #{python_helper_path}",
62
- function: "parse_setup",
63
- args: [Dir.pwd]
64
- )
65
-
66
- check_requirements(requirements)
67
- requirements
68
- end
69
- rescue SharedHelpers::HelperSubprocessFailed => error
70
- if error.message.start_with?("InstallationError")
71
- raise Dependabot::DependencyFileNotEvaluatable, error.message
72
- end
73
-
74
- parsed_sanitized_setup_file
75
- end
76
-
77
- def parsed_sanitized_setup_file
78
- SharedHelpers.in_a_temporary_directory do
79
- write_sanitized_setup_file
80
-
81
- requirements = SharedHelpers.run_helper_subprocess(
82
- command: "pyenv exec python #{python_helper_path}",
83
- function: "parse_setup",
84
- args: [Dir.pwd]
85
- )
86
-
87
- check_requirements(requirements)
88
- requirements
89
- end
90
- rescue SharedHelpers::HelperSubprocessFailed
91
- # Assume there are no dependencies in setup.py files that fail to
92
- # parse. This isn't ideal, and we should continue to improve
93
- # parsing, but there are a *lot* of things that can go wrong at
94
- # the moment!
95
- []
96
- end
97
-
98
- def check_requirements(requirements)
99
- requirements.each do |dep|
100
- next unless dep["requirement"]
101
-
102
- Utils::Python::Requirement.new(dep["requirement"].split(","))
103
- rescue Gem::Requirement::BadRequirementError => error
104
- raise Dependabot::DependencyFileNotEvaluatable, error.message
105
- end
106
- end
107
-
108
- def write_temporary_dependency_files
109
- dependency_files.
110
- reject { |f| f.name == ".python-version" }.
111
- each do |file|
112
- path = file.name
113
- FileUtils.mkdir_p(Pathname.new(path).dirname)
114
- File.write(path, file.content)
115
- end
116
- end
117
-
118
- # Write a setup.py with only entries for the requires fields.
119
- #
120
- # This sanitization is far from perfect (it will fail if any of the
121
- # entries are dynamic), but it is an alternative approach to the one
122
- # used in parser.py which sometimes succeeds when that has failed.
123
- def write_sanitized_setup_file
124
- original_content = setup_file.content
125
-
126
- install_requires =
127
- original_content.match(INSTALL_REQUIRES_REGEX)&.captures&.first
128
- setup_requires =
129
- original_content.match(SETUP_REQUIRES_REGEX)&.captures&.first
130
- tests_require =
131
- original_content.match(TESTS_REQUIRE_REGEX)&.captures&.first
132
- extras_require =
133
- original_content.match(EXTRAS_REQUIRE_REGEX)&.captures&.first
134
-
135
- tmp = "from setuptools import setup\n\n"\
136
- "setup(name=\"sanitized-package\",version=\"0.0.1\","
137
-
138
- tmp += "install_requires=#{install_requires}," if install_requires
139
- tmp += "setup_requires=#{setup_requires}," if setup_requires
140
- tmp += "tests_require=#{tests_require}," if tests_require
141
- tmp += "extras_require=#{extras_require}," if extras_require
142
- tmp += ")"
143
-
144
- File.write("setup.py", tmp)
145
- end
146
-
147
- def python_helper_path
148
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
149
- File.join(project_root, "helpers/python/run.py")
150
- end
151
-
152
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
153
- def normalised_name(name)
154
- name.downcase.gsub(/[-_.]+/, "-")
155
- end
156
-
157
- def setup_file
158
- dependency_files.find { |f| f.name == "setup.py" }
159
- end
160
- end
161
- end
162
- end
163
- end
164
- end
@@ -1,147 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dependabot/file_updaters/base"
4
- require "dependabot/shared_helpers"
5
-
6
- module Dependabot
7
- module FileUpdaters
8
- module Python
9
- class Pip < Dependabot::FileUpdaters::Base
10
- require_relative "pip/pipfile_file_updater"
11
- require_relative "pip/pip_compile_file_updater"
12
- require_relative "pip/poetry_file_updater"
13
- require_relative "pip/requirement_file_updater"
14
-
15
- def self.updated_files_regex
16
- [
17
- /^Pipfile$/,
18
- /^Pipfile\.lock$/,
19
- /.*\.txt$/,
20
- /.*\.in$/,
21
- /^setup\.py$/,
22
- /^pyproject\.toml$/,
23
- /^pyproject\.lock$/
24
- ]
25
- end
26
-
27
- def updated_dependency_files
28
- updated_files =
29
- case resolver_type
30
- when :pipfile then updated_pipfile_based_files
31
- when :poetry then updated_poetry_based_files
32
- when :pip_compile then updated_pip_compile_based_files
33
- when :requirements then updated_requirement_based_files
34
- else raise "Unexpected resolver type: #{resolver_type}"
35
- end
36
-
37
- if updated_files.none? ||
38
- updated_files.sort_by(&:name) == dependency_files.sort_by(&:name)
39
- raise "No files have changed!"
40
- end
41
-
42
- updated_files
43
- end
44
-
45
- private
46
-
47
- def resolver_type
48
- reqs = dependencies.flat_map(&:requirements)
49
- req_files = reqs.map { |r| r.fetch(:file) }
50
-
51
- # If there are no requirements then this is a sub-dependency. It
52
- # must come from one of Pipenv, Poetry or pip-tools, and can't come
53
- # from the first two unless they have a lockfile.
54
- return subdependency_resolver if reqs.none?
55
-
56
- # Otherwise, this is a top-level dependency, and we can figure out
57
- # which resolver to use based on the filename of its requirements
58
- return :pipfile if req_files.any? { |f| f == "Pipfile" }
59
- return :poetry if req_files.any? { |f| f == "pyproject.toml" }
60
- return :pip_compile if req_files.any? { |f| f.end_with?(".in") }
61
-
62
- # Finally, we should only ever be updating a requirements.txt file if
63
- # some requirements have changed. Otherwise, this must be a case where
64
- # we have a requirements.txt *and* some other resolver of which the
65
- # dependency is a sub-dependency.
66
- changed_reqs = reqs - dependencies.flat_map(&:previous_requirements)
67
- changed_reqs.none? ? subdependency_resolver : :requirements
68
- end
69
-
70
- def subdependency_resolver
71
- return :pipfile if pipfile_lock
72
- return :poetry if poetry_lock || pyproject_lock
73
- return :pip_compile if pip_compile_files.any?
74
-
75
- raise "Claimed to be a sub-dependency, but no lockfile exists!"
76
- end
77
-
78
- def updated_pipfile_based_files
79
- PipfileFileUpdater.new(
80
- dependencies: dependencies,
81
- dependency_files: dependency_files,
82
- credentials: credentials
83
- ).updated_dependency_files
84
- end
85
-
86
- def updated_poetry_based_files
87
- PoetryFileUpdater.new(
88
- dependencies: dependencies,
89
- dependency_files: dependency_files,
90
- credentials: credentials
91
- ).updated_dependency_files
92
- end
93
-
94
- def updated_pip_compile_based_files
95
- PipCompileFileUpdater.new(
96
- dependencies: dependencies,
97
- dependency_files: dependency_files,
98
- credentials: credentials
99
- ).updated_dependency_files
100
- end
101
-
102
- def updated_requirement_based_files
103
- RequirementFileUpdater.new(
104
- dependencies: dependencies,
105
- dependency_files: dependency_files,
106
- credentials: credentials
107
- ).updated_dependency_files
108
- end
109
-
110
- def check_required_files
111
- filenames = dependency_files.map(&:name)
112
- return if filenames.any? { |name| name.end_with?(".txt", ".in") }
113
- return if pipfile
114
- return if pyproject
115
- return if get_original_file("setup.py")
116
-
117
- raise "No requirements.txt or setup.py!"
118
- end
119
-
120
- def pipfile
121
- @pipfile ||= get_original_file("Pipfile")
122
- end
123
-
124
- def pipfile_lock
125
- @pipfile_lock ||= get_original_file("Pipfile.lock")
126
- end
127
-
128
- def pyproject
129
- @pyproject ||= get_original_file("pyproject.toml")
130
- end
131
-
132
- def pyproject_lock
133
- @pyproject_lock ||= get_original_file("pyproject.lock")
134
- end
135
-
136
- def poetry_lock
137
- @poetry_lock ||= get_original_file("poetry.lock")
138
- end
139
-
140
- def pip_compile_files
141
- @pip_compile_files ||=
142
- dependency_files.select { |f| f.name.end_with?(".in") }
143
- end
144
- end
145
- end
146
- end
147
- end
@@ -1,363 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "python_requirement_parser"
4
- require "dependabot/file_fetchers/python/pip"
5
- require "dependabot/file_updaters/python/pip"
6
- require "dependabot/shared_helpers"
7
-
8
- # rubocop:disable Metrics/ClassLength
9
- module Dependabot
10
- module FileUpdaters
11
- module Python
12
- class Pip
13
- class PipCompileFileUpdater
14
- require_relative "requirement_replacer"
15
- require_relative "requirement_file_updater"
16
- require_relative "setup_file_sanitizer"
17
-
18
- UNSAFE_PACKAGES = %w(setuptools distribute pip).freeze
19
-
20
- attr_reader :dependencies, :dependency_files, :credentials
21
-
22
- def initialize(dependencies:, dependency_files:, credentials:)
23
- @dependencies = dependencies
24
- @dependency_files = dependency_files
25
- @credentials = credentials
26
- end
27
-
28
- def updated_dependency_files
29
- return @updated_dependency_files if @update_already_attempted
30
-
31
- @update_already_attempted = true
32
- @updated_dependency_files ||= fetch_updated_dependency_files
33
- end
34
-
35
- private
36
-
37
- def dependency
38
- # For now, we'll only ever be updating a single dependency
39
- dependencies.first
40
- end
41
-
42
- def fetch_updated_dependency_files
43
- updated_compiled_files = compile_new_requirement_files
44
- updated_manifest_files = update_manifest_files
45
-
46
- updated_files = updated_compiled_files + updated_manifest_files
47
- updated_uncompiled_files = update_uncompiled_files(updated_files)
48
-
49
- [
50
- *updated_manifest_files,
51
- *updated_compiled_files,
52
- *updated_uncompiled_files
53
- ]
54
- end
55
-
56
- def compile_new_requirement_files
57
- SharedHelpers.in_a_temporary_directory do
58
- write_updated_dependency_files
59
-
60
- filenames_to_compile.each do |filename|
61
- # Shell out to pip-compile, generate a new set of requirements.
62
- # This is slow, as pip-compile needs to do installs.
63
- run_command(
64
- "pyenv exec pip-compile #{pip_compile_options(filename)} "\
65
- "-P #{dependency.name} #{filename}"
66
- )
67
- end
68
-
69
- dependency_files.map do |file|
70
- next unless file.name.end_with?(".txt")
71
-
72
- updated_content = File.read(file.name)
73
-
74
- updated_content =
75
- replace_header_with_original(updated_content, file.content)
76
- next if updated_content == file.content
77
-
78
- file.dup.tap { |f| f.content = updated_content }
79
- end.compact
80
- end
81
- end
82
-
83
- def update_manifest_files
84
- dependency_files.map do |file|
85
- next unless file.name.end_with?(".in")
86
-
87
- file = file.dup
88
- updated_content = update_dependency_requirement(file)
89
- next if updated_content == file.content
90
-
91
- file.content = updated_content
92
- file
93
- end.compact
94
- end
95
-
96
- def update_uncompiled_files(updated_files)
97
- updated_filenames = updated_files.map(&:name)
98
- old_reqs = dependency.previous_requirements.
99
- reject { |r| updated_filenames.include?(r[:file]) }
100
- new_reqs = dependency.requirements.
101
- reject { |r| updated_filenames.include?(r[:file]) }
102
-
103
- return [] if new_reqs.none?
104
-
105
- files = dependency_files.
106
- reject { |file| updated_filenames.include?(file.name) }
107
-
108
- args = dependency.to_h
109
- args = Hash[args.keys.map { |k| [k.to_sym, args[k]] }]
110
- args[:requirements] = new_reqs
111
- args[:previous_requirements] = old_reqs
112
-
113
- RequirementFileUpdater.new(
114
- dependencies: [Dependency.new(**args)],
115
- dependency_files: files,
116
- credentials: credentials
117
- ).updated_dependency_files
118
- end
119
-
120
- def run_command(command)
121
- command = command.dup
122
- raw_response = nil
123
- IO.popen(command, err: %i(child out)) do |process|
124
- raw_response = process.read
125
- end
126
-
127
- # Raise an error with the output from the shell session if
128
- # pip-compile returns a non-zero status
129
- return if $CHILD_STATUS.success?
130
-
131
- raise SharedHelpers::HelperSubprocessFailed.new(
132
- raw_response,
133
- command
134
- )
135
- rescue SharedHelpers::HelperSubprocessFailed => error
136
- original_error ||= error
137
- msg = error.message
138
-
139
- relevant_error =
140
- if error_suggests_bad_python_version?(msg) then original_error
141
- else error
142
- end
143
-
144
- raise relevant_error unless error_suggests_bad_python_version?(msg)
145
- raise relevant_error if File.exist?(".python-version")
146
-
147
- command = "pyenv local 2.7.15 && " + command
148
- retry
149
- ensure
150
- FileUtils.remove_entry(".python-version", true)
151
- end
152
-
153
- def error_suggests_bad_python_version?(message)
154
- return true if message.include?("not find a version that satisfies")
155
-
156
- message.include?('Command "python setup.py egg_info" failed')
157
- end
158
-
159
- def write_updated_dependency_files
160
- dependency_files.each do |file|
161
- next if file.name == ".python-version"
162
-
163
- path = file.name
164
- FileUtils.mkdir_p(Pathname.new(path).dirname)
165
- File.write(path, freeze_dependency_requirement(file))
166
- end
167
-
168
- setup_files.each do |file|
169
- path = file.name
170
- FileUtils.mkdir_p(Pathname.new(path).dirname)
171
- File.write(path, sanitized_setup_file_content(file))
172
- end
173
-
174
- setup_cfg_files.each do |file|
175
- path = file.name
176
- FileUtils.mkdir_p(Pathname.new(path).dirname)
177
- File.write(path, "[metadata]\nname = sanitized-package\n")
178
- end
179
- end
180
-
181
- def sanitized_setup_file_content(file)
182
- @sanitized_setup_file_content ||= {}
183
- if @sanitized_setup_file_content[file.name]
184
- return @sanitized_setup_file_content[file.name]
185
- end
186
-
187
- @sanitized_setup_file_content[file.name] =
188
- SetupFileSanitizer.
189
- new(setup_file: file, setup_cfg: setup_cfg(file)).
190
- sanitized_content
191
- end
192
-
193
- def setup_cfg(file)
194
- dependency_files.find do |f|
195
- f.name == file.name.sub(/\.py$/, ".cfg")
196
- end
197
- end
198
-
199
- def freeze_dependency_requirement(file)
200
- return file.content unless file.name.end_with?(".in")
201
-
202
- old_req = dependency.previous_requirements.
203
- find { |r| r[:file] == file.name }
204
-
205
- return file.content unless old_req
206
- return file.content if old_req == "==#{dependency.version}"
207
-
208
- RequirementReplacer.new(
209
- content: file.content,
210
- dependency_name: dependency.name,
211
- old_requirement: old_req[:requirement],
212
- new_requirement: "==#{dependency.version}"
213
- ).updated_content
214
- end
215
-
216
- def update_dependency_requirement(file)
217
- return file.content unless file.name.end_with?(".in")
218
-
219
- old_req = dependency.previous_requirements.
220
- find { |r| r[:file] == file.name }
221
- new_req = dependency.requirements.
222
- find { |r| r[:file] == file.name }
223
- return file.content unless old_req&.fetch(:requirement)
224
- return file.content if old_req == new_req
225
-
226
- RequirementReplacer.new(
227
- content: file.content,
228
- dependency_name: dependency.name,
229
- old_requirement: old_req[:requirement],
230
- new_requirement: new_req[:requirement]
231
- ).updated_content
232
- end
233
-
234
- def replace_header_with_original(updated_content, original_content)
235
- original_header_lines =
236
- original_content.lines.take_while { |l| l.start_with?("#") }
237
-
238
- updated_content_lines =
239
- updated_content.lines.drop_while { |l| l.start_with?("#") }
240
-
241
- [*original_header_lines, *updated_content_lines].join
242
- end
243
-
244
- def pip_compile_options(filename)
245
- current_requirements_file_name = filename.sub(/\.in$/, ".txt")
246
-
247
- requirements_file =
248
- dependency_files.
249
- find { |f| f.name == current_requirements_file_name }
250
-
251
- return unless requirements_file
252
-
253
- options = ""
254
-
255
- if requirements_file.content.include?("--hash=sha")
256
- options += " --generate-hashes"
257
- end
258
-
259
- if includes_unsafe_packages?(requirements_file.content)
260
- options += " --allow-unsafe"
261
- end
262
-
263
- unless requirements_file.content.include?("# via ")
264
- options += " --no-annotate"
265
- end
266
-
267
- unless requirements_file.content.include?("autogenerated by pip-c")
268
- options += " --no-header"
269
- end
270
-
271
- options.strip
272
- end
273
-
274
- def includes_unsafe_packages?(content)
275
- UNSAFE_PACKAGES.any? { |n| content.match?(/^#{Regexp.quote(n)}==/) }
276
- end
277
-
278
- def filenames_to_compile
279
- files_from_reqs =
280
- dependency.requirements.
281
- map { |r| r[:file] }.
282
- select { |fn| fn.end_with?(".in") }
283
-
284
- files_from_compiled_files =
285
- pip_compile_files.map(&:name).select do |fn|
286
- compiled_file = dependency_files.
287
- find { |f| f.name == fn.gsub(/\.in$/, ".txt") }
288
- compiled_file_includes_dependency?(compiled_file)
289
- end
290
-
291
- filenames = [*files_from_reqs, *files_from_compiled_files].uniq
292
-
293
- order_filenames_for_compilation(filenames)
294
- end
295
-
296
- def compiled_file_includes_dependency?(compiled_file)
297
- return false unless compiled_file
298
-
299
- regex = PythonRequirementParser::INSTALL_REQ_WITH_REQUIREMENT
300
-
301
- matches = []
302
- compiled_file.content.scan(regex) { matches << Regexp.last_match }
303
- matches.any? { |m| normalise(m[:name]) == dependency.name }
304
- end
305
-
306
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
307
- def normalise(name)
308
- name.downcase.gsub(/[-_.]+/, "-")
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 setup_files
348
- dependency_files.select { |f| f.name.end_with?("setup.py") }
349
- end
350
-
351
- def pip_compile_files
352
- dependency_files.select { |f| f.name.end_with?(".in") }
353
- end
354
-
355
- def setup_cfg_files
356
- dependency_files.select { |f| f.name.end_with?("setup.cfg") }
357
- end
358
- end
359
- end
360
- end
361
- end
362
- end
363
- # rubocop:enable Metrics/ClassLength