dependabot-gradle 0.389.0 → 0.391.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: bbe1f38539a5dd887e3c8fa64bb88e70766c846ca198c504780303b63464c8ab
4
- data.tar.gz: e5618a45ebe2bd554fb3edaf73b89e5472d5d334919676cac60e154a38b6853f
3
+ metadata.gz: dc95dfd45dcabce388680e617eaffd9ba19b84ef8a729cffd616cfb06ad4cb43
4
+ data.tar.gz: 67e2f381c9cabd09d54a93b0c153ad7e64a1344a652954e0d59a5008dfd7d56e
5
5
  SHA512:
6
- metadata.gz: 0ef872f7affa834445ba93e8f1c47ced0bd9c77165edf43b07eac7de49f7b1d38bf22a80826ed1bf3849b922bd43ca4c1cfd1d72ad4da5c2e38afc7ed41bfdcb
7
- data.tar.gz: 44b02a0f2b977a165d499c33405ddd12fc53424af928fcfa358ecd8c0184b592f60a5ed4455db2465b853b0a987c0c7daa07f247f4c3f15c55905b6425b4341d
6
+ metadata.gz: 31931d172c06b4afca6bb258af47b1e6420fc59c46ac3dc63d483e5c5e18395a775dcbe6fcd8ae77a38f611378e5cb4d84db26d26851ca52e36dc20413b4f220
7
+ data.tar.gz: b2faa2bcf5a79fb88d2e085ff504ad55028cfcd8f8f6851d54815a902137fa19b91eef51d1e1eb60c514d1f8e4f039b1d588f75d1d037b43aae58cf700863013
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/dependency_requirement"
@@ -14,7 +14,7 @@ module Dependabot
14
14
  sig { params(requirements: T::Array[Dependabot::DependencyRequirement]).returns(T::Boolean) }
15
15
  def self.distribution_requirements?(requirements)
16
16
  requirements.any? do |req|
17
- req.dig(:source, :type) == DISTRIBUTION_DEPENDENCY_TYPE
17
+ req.source_string("type") == DISTRIBUTION_DEPENDENCY_TYPE
18
18
  end
19
19
  end
20
20
  end
@@ -33,6 +33,22 @@ module Dependabot
33
33
  subprojects.uniq.map { |name| process_subproject_name(name) }
34
34
  end
35
35
 
36
+ # Returns a map of filesystem directory path => Gradle project name for all declared subprojects.
37
+ # For example: { 'app' => ':app', 'subprojects/chrome-trace' => ':chrome-trace' }
38
+ # This correctly handles custom projectDir mappings in settings files.
39
+ sig { returns(T::Hash[String, String]) }
40
+ def subproject_path_to_name_map
41
+ subprojects = T.let([], T::Array[String])
42
+ process_include_functions(subprojects)
43
+ subprojects.uniq.each_with_object(T.let({}, T::Hash[String, String])) do |name, map|
44
+ path = process_subproject_name(name)
45
+ next unless path
46
+
47
+ # Normalise name to Gradle colon-separated project path (e.g. ':app', ':sub:module')
48
+ map[path] = ":#{name.sub(/^:/, '')}"
49
+ end
50
+ end
51
+
36
52
  private
37
53
 
38
54
  sig { params(subprojects: T::Array[String]).void }
@@ -16,13 +16,13 @@ module Dependabot
16
16
  require_relative "file_parser"
17
17
  require_relative "file_fetcher/settings_file_parser"
18
18
 
19
- SUPPORTED_LOCK_FILE_NAMES = T.let(%w(gradle.lockfile).freeze, T::Array[String])
19
+ SUPPORTED_LOCK_FILE_NAMES = %w(gradle.lockfile).freeze
20
20
 
21
21
  SUPPORTED_BUILD_FILE_NAMES =
22
- T.let(%w(build.gradle build.gradle.kts).freeze, T::Array[String])
22
+ %w(build.gradle build.gradle.kts).freeze
23
23
 
24
24
  SUPPORTED_SETTINGS_FILE_NAMES =
25
- T.let(%w(settings.gradle settings.gradle.kts).freeze, T::Array[String])
25
+ %w(settings.gradle settings.gradle.kts).freeze
26
26
 
27
27
  SUPPORTED_WRAPPER_FILES_PATH = %w(
28
28
  gradlew
@@ -33,7 +33,10 @@ module Dependabot
33
33
 
34
34
  # For now Gradle only supports library .toml files in the main gradle folder
35
35
  SUPPORTED_VERSION_CATALOG_FILE_PATH =
36
- T.let(%w(/gradle/libs.versions.toml).freeze, T::Array[String])
36
+ %w(/gradle/libs.versions.toml).freeze
37
+
38
+ PLUGIN_SOURCE_SET_DIRS =
39
+ %w(src/main/java src/main/kotlin src/main/groovy src/main/resources).freeze
37
40
 
38
41
  sig do
39
42
  override
@@ -49,7 +52,7 @@ module Dependabot
49
52
  def initialize(source:, credentials:, repo_contents_path: nil, options: {}, update_config: nil)
50
53
  super
51
54
 
52
- @lockfile_name = T.let(T.must(SUPPORTED_LOCK_FILE_NAMES.first), String)
55
+ @lockfile_name = T.let(SUPPORTED_LOCK_FILE_NAMES.first, String)
53
56
  @buildfile_name = T.let(nil, T.nilable(String))
54
57
  end
55
58
 
@@ -88,10 +91,74 @@ module Dependabot
88
91
  files += subproject_buildfiles(root_dir)
89
92
  files += subproject_lockfiles(root_dir)
90
93
  files += dependency_script_plugins(root_dir)
94
+ files += convention_plugin_source_files(files, root_dir) if fetch_plugin_sources_for_lockfiles?
95
+
91
96
  files + included_builds(root_dir)
92
97
  .flat_map { |dir| all_buildfiles_in_build(dir) }
93
98
  end
94
99
 
100
+ sig { returns(T::Boolean) }
101
+ def fetch_plugin_sources_for_lockfiles?
102
+ Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
103
+ end
104
+
105
+ # Only fetch source trees for *nested* project directories (e.g. "build-logic/convention"
106
+ # or "included/convention"), plus the root of a non-top-level build (e.g. "build-logic" or
107
+ # "buildSrc" itself), never the top-level repo root ("app" style modules of the outermost
108
+ # build). This targets the common convention-plugin module pattern - whether declared at an
109
+ # included/buildSrc build's own root or nested within it - without recursively scanning
110
+ # every ordinary top-level application module of the outermost build.
111
+ sig { params(files: T::Array[DependencyFile], root_dir: String).returns(T::Array[DependencyFile]) }
112
+ def convention_plugin_source_files(files, root_dir)
113
+ nested_plugin_project_dirs(files, root_dir).flat_map do |project_dir|
114
+ PLUGIN_SOURCE_SET_DIRS.flat_map do |relative_dir|
115
+ fetch_tree_support_files(clean_join([project_dir, relative_dir]))
116
+ end
117
+ end
118
+ end
119
+
120
+ sig { params(files: T::Array[DependencyFile], root_dir: String).returns(T::Array[String]) }
121
+ def nested_plugin_project_dirs(files, root_dir)
122
+ buildfile_dirs(files).select { |dir| plugin_source_eligible_dir?(dir, root_dir) }
123
+ end
124
+
125
+ # A directory is eligible for convention-plugin source scanning if it's nested below the
126
+ # outermost repo root (contains a "/"), or if it's the own root of a build that is itself
127
+ # nested (i.e. root_dir isn't the outermost "."), such as an included build or buildSrc.
128
+ sig { params(dir: String, root_dir: String).returns(T::Boolean) }
129
+ def plugin_source_eligible_dir?(dir, root_dir)
130
+ return true if root_dir != "." && dir == root_dir
131
+
132
+ dir.include?("/")
133
+ end
134
+
135
+ sig { params(files: T::Array[DependencyFile]).returns(T::Array[String]) }
136
+ def buildfile_dirs(files)
137
+ files.filter_map do |file|
138
+ next unless SUPPORTED_BUILD_FILE_NAMES.include?(File.basename(file.name))
139
+
140
+ clean_join([File.dirname(file.name)])
141
+ end
142
+ end
143
+
144
+ sig { params(dir: String).returns(T::Array[DependencyFile]) }
145
+ def fetch_tree_support_files(dir)
146
+ entries = repo_contents(dir: dir, raise_errors: false)
147
+ return [] if entries.empty?
148
+
149
+ entries.flat_map do |entry|
150
+ entry_path = clean_join([dir, entry.name])
151
+ if entry.type == "dir"
152
+ fetch_tree_support_files(entry_path)
153
+ else
154
+ file = fetch_support_file(entry_path)
155
+ file ? [file] : []
156
+ end
157
+ end
158
+ rescue Dependabot::DependencyFileNotFound
159
+ []
160
+ end
161
+
95
162
  sig { params(root_dir: String).returns(T::Array[String]) }
96
163
  def included_builds(root_dir)
97
164
  builds = []
@@ -73,7 +73,7 @@ module Dependabot
73
73
 
74
74
  sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
75
75
  def initialize(dependency_files:)
76
- @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
76
+ @dependency_files = dependency_files
77
77
  @properties = T.let({}, T::Hash[String, T::Hash[String, T::Hash[Symbol, String]]])
78
78
  @top_level_buildfile = T.let(nil, T.nilable(Dependabot::DependencyFile))
79
79
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -11,8 +11,8 @@ module Dependabot
11
11
  class RepositoriesFinder
12
12
  extend T::Sig
13
13
 
14
- SUPPORTED_BUILD_FILE_NAMES = T.let(%w(build.gradle build.gradle.kts).freeze, T::Array[String])
15
- SUPPORTED_SETTINGS_FILE_NAMES = T.let(%w(settings.gradle settings.gradle.kts).freeze, T::Array[String])
14
+ SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts).freeze
15
+ SUPPORTED_SETTINGS_FILE_NAMES = %w(settings.gradle settings.gradle.kts).freeze
16
16
 
17
17
  # The Central Repo doesn't have special status for Gradle, but until
18
18
  # we're confident we're selecting repos correctly it's wise to include
@@ -37,8 +37,8 @@ module Dependabot
37
37
  ).void
38
38
  end
39
39
  def initialize(dependency_files:, target_dependency_file:, credentials: [])
40
- @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
41
- @credentials = T.let(credentials, T::Array[Dependabot::Credential])
40
+ @dependency_files = dependency_files
41
+ @credentials = credentials
42
42
  raise "No target file!" unless target_dependency_file
43
43
 
44
44
  @target_dependency_file = T.let(target_dependency_file, Dependabot::DependencyFile)
@@ -28,28 +28,22 @@ module Dependabot
28
28
  require_relative "file_parser/distributions_finder"
29
29
  require_relative "file_parser/property_value_finder"
30
30
 
31
- SUPPORTED_BUILD_FILE_NAMES = T.let(
32
- %w(build.gradle build.gradle.kts settings.gradle settings.gradle.kts).freeze,
33
- T::Array[String]
34
- )
31
+ SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts settings.gradle settings.gradle.kts).freeze
35
32
 
36
- PROPERTY_REGEX = T.let(
37
- /
33
+ PROPERTY_REGEX = /
38
34
  (?:\$\{property\((?<property_name>[^:\s]*?)\)\})|
39
35
  (?:\$\{(?<property_name>[^:\s]*?)\})|
40
36
  (?:\$(?<property_name>[^:\s"']*))
41
- /x,
42
- Regexp
43
- )
44
-
45
- PART = T.let(%r{[^\s,@'":/\\]+}, Regexp)
46
- VSN_PART = T.let(%r{[^\s,'":/\\]+}, Regexp)
47
- DEPENDENCY_DECLARATION_REGEX = T.let(/(?:\(|\s)\s*['"](?<declaration>#{PART}:#{PART}:#{VSN_PART})['"]/o, Regexp)
48
-
49
- DEPENDENCY_SET_DECLARATION_REGEX = T.let(/(?:^|\s)dependencySet\((?<arguments>[^\)]+)\)\s*\{/, Regexp)
50
- DEPENDENCY_SET_ENTRY_REGEX = T.let(/entry\s+['"](?<name>#{PART})['"]/o, Regexp)
51
- PLUGIN_BLOCK_DECLARATION_REGEX = T.let(/(?:^|\s)plugins\s*\{/, Regexp)
52
- PLUGIN_ID_REGEX = T.let(/['"](?<id>#{PART})['"]/o, Regexp)
37
+ /x
38
+
39
+ PART = %r{[^\s,@'":/\\]+}
40
+ VSN_PART = %r{[^\s,'":/\\]+}
41
+ DEPENDENCY_DECLARATION_REGEX = /(?:\(|\s)\s*['"](?<declaration>#{PART}:#{PART}:#{VSN_PART})['"]/o
42
+
43
+ DEPENDENCY_SET_DECLARATION_REGEX = /(?:^|\s)dependencySet\((?<arguments>[^\)]+)\)\s*\{/
44
+ DEPENDENCY_SET_ENTRY_REGEX = /entry\s+['"](?<name>#{PART})['"]/o
45
+ PLUGIN_BLOCK_DECLARATION_REGEX = /(?:^|\s)plugins\s*\{/
46
+ PLUGIN_ID_REGEX = /['"](?<id>#{PART})['"]/o
53
47
 
54
48
  sig { override.returns(T::Array[Dependabot::Dependency]) }
55
49
  def parse
@@ -7,6 +7,7 @@ require "shellwords"
7
7
  require "sorbet-runtime"
8
8
 
9
9
  require "dependabot/gradle/file_updater"
10
+ require "dependabot/gradle/file_fetcher/settings_file_parser"
10
11
 
11
12
  module Dependabot
12
13
  module Gradle
@@ -14,15 +15,29 @@ module Dependabot
14
15
  class LockfileUpdater
15
16
  extend T::Sig
16
17
 
17
- INIT_SCRIPT_TASK_NAME = T.let("dependabotResolveAll", String)
18
-
19
- sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
20
- def initialize(dependency_files:)
18
+ INIT_SCRIPT_TASK_NAME = "dependabotResolveAll"
19
+ GRADLE_JVMARGS_ATTEMPTS = [
20
+ "-Xmx1536m -Dfile.encoding=UTF-8",
21
+ "-Xmx2048m -Dfile.encoding=UTF-8"
22
+ ].freeze
23
+ sig do
24
+ params(
25
+ dependency_files: T::Array[Dependabot::DependencyFile],
26
+ repo_contents_path: T.nilable(String)
27
+ ).void
28
+ end
29
+ def initialize(dependency_files:, repo_contents_path: nil)
21
30
  @dependency_files = dependency_files
31
+ @repo_contents_path = repo_contents_path
22
32
  end
23
33
 
24
- sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
25
- def update_lockfiles(build_file)
34
+ sig do
35
+ params(
36
+ build_file: Dependabot::DependencyFile,
37
+ build_files: T::Array[Dependabot::DependencyFile]
38
+ ).returns(T::Array[Dependabot::DependencyFile])
39
+ end
40
+ def update_lockfiles(build_file, build_files: [build_file])
26
41
  root_dir = determine_root_dir(build_file: build_file)
27
42
  lockfiles = lockfiles_for_root(root_dir)
28
43
 
@@ -36,7 +51,7 @@ module Dependabot
36
51
  cwd = File.join(temp_dir, root_dir == "/" ? "" : root_dir.delete_prefix("/"))
37
52
  FileUtils.mkdir_p(cwd)
38
53
 
39
- write_properties_file(File.join(cwd, "gradle.properties"))
54
+ properties_file_path = File.join(cwd, "gradle.properties")
40
55
 
41
56
  init_script_path = File.join(cwd, "dependabot-locking.init.gradle")
42
57
  write_init_script(init_script_path)
@@ -44,12 +59,16 @@ module Dependabot
44
59
  command_parts = [
45
60
  gradle_executable_for(cwd: cwd, workspace_root: temp_dir.to_s),
46
61
  "--init-script", init_script_path,
47
- INIT_SCRIPT_TASK_NAME,
62
+ *lockfile_tasks_for(build_files),
48
63
  "--write-locks",
49
64
  "--no-daemon"
50
65
  ]
51
66
  command = Shellwords.join(command_parts)
52
- SharedHelpers.run_shell_command(command, cwd: cwd)
67
+ run_lockfile_update_with_retry(
68
+ command: command,
69
+ cwd: cwd,
70
+ properties_file_path: properties_file_path
71
+ )
53
72
 
54
73
  update_lockfiles_content(temp_dir, lockfiles, updated_files)
55
74
  rescue SharedHelpers::HelperSubprocessFailed => e
@@ -171,6 +190,8 @@ module Dependabot
171
190
 
172
191
  sig { params(temp_dir: T.any(Pathname, String)).void }
173
192
  def populate_temp_directory(temp_dir)
193
+ copy_repo_contents_to_temp_dir(temp_dir, @repo_contents_path)
194
+
174
195
  @dependency_files.each do |file|
175
196
  # Handle "/" directory as root - File.join treats "/" as absolute path and ignores prior components
176
197
  relative_dir = file.directory == "/" ? "" : file.directory
@@ -180,8 +201,33 @@ module Dependabot
180
201
  end
181
202
  end
182
203
 
183
- sig { params(file_name: String).void }
184
- def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
204
+ sig { params(temp_dir: T.any(Pathname, String), repo_contents_path: T.nilable(String)).void }
205
+ def copy_repo_contents_to_temp_dir(temp_dir, repo_contents_path)
206
+ return if repo_contents_path.nil? || repo_contents_path.strip.empty?
207
+
208
+ source_dir = Pathname.new(repo_contents_path).expand_path
209
+ return unless source_dir.directory?
210
+
211
+ # Use the full checkout when available to ensure Gradle can compile
212
+ # convention plugin implementations from non-manifest source trees.
213
+ Dir.each_child(source_dir.to_s) do |entry|
214
+ entry = T.let(entry, String)
215
+ source_entry = File.join(source_dir.to_s, entry)
216
+ dest_entry = File.join(temp_dir.to_s, entry)
217
+
218
+ if entry == ".git"
219
+ # Some convention plugins shell out to `git` (e.g. for version derivation) and
220
+ # fail without a working repository. Symlink instead of copying to avoid the
221
+ # disk/time cost of duplicating the whole history for every lockfile update.
222
+ File.symlink(source_entry, dest_entry)
223
+ else
224
+ FileUtils.cp_r(source_entry, dest_entry)
225
+ end
226
+ end
227
+ end
228
+
229
+ sig { params(file_name: String, jvmargs: String, base_content: String).void }
230
+ def write_properties_file(file_name, jvmargs:, base_content:) # rubocop:disable Metrics/PerceivedComplexity
185
231
  http_proxy = ENV.fetch("HTTP_PROXY", nil)
186
232
  https_proxy = ENV.fetch("HTTPS_PROXY", nil)
187
233
  http_split = http_proxy&.split(":")
@@ -191,9 +237,20 @@ module Dependabot
191
237
  http_proxy_port = http_split&.fetch(2) || "1080"
192
238
  https_proxy_port = https_split&.fetch(2) || "1080"
193
239
 
194
- existing_content = File.exist?(file_name) ? File.read(file_name) : ""
240
+ existing_jvmargs = base_content[/^org\.gradle\.jvmargs=(.*)$/, 1]
241
+ # Preserve any existing jvmargs (e.g. --add-opens flags) by merging rather than
242
+ # replacing them outright; the raw line is dropped from existing_content below
243
+ # since combined_jvmargs already includes its value.
244
+ has_existing_jvmargs = existing_jvmargs && !existing_jvmargs.strip.empty?
245
+ combined_jvmargs = has_existing_jvmargs ? "#{existing_jvmargs.strip} #{jvmargs}" : jvmargs
246
+ existing_content = base_content.gsub(/^org\.gradle\.jvmargs=.*$\n?/, "")
195
247
 
196
248
  proxy_properties = "
249
+ org.gradle.jvmargs=#{combined_jvmargs}
250
+ org.gradle.workers.max=1
251
+ org.gradle.java.installations.auto-download=false
252
+ org.gradle.java.installations.paths=#{installed_gradle_toolchain_paths}
253
+ kotlin.compiler.execution.strategy=in-process
197
254
  systemProp.http.proxyHost=#{http_proxy_host}
198
255
  systemProp.http.proxyPort=#{http_proxy_port}
199
256
  systemProp.https.proxyHost=#{https_proxy_host}
@@ -203,24 +260,160 @@ systemProp.https.proxyPort=#{https_proxy_port}"
203
260
  File.write(file_name, existing_content + separator + proxy_properties)
204
261
  end
205
262
 
263
+ sig { params(command: String, cwd: String, properties_file_path: String).void }
264
+ def run_lockfile_update_with_retry(command:, cwd:, properties_file_path:)
265
+ base_content = File.exist?(properties_file_path) ? File.read(properties_file_path) : ""
266
+
267
+ catch(:success) do
268
+ GRADLE_JVMARGS_ATTEMPTS.each_with_index do |jvmargs, index|
269
+ write_properties_file(properties_file_path, jvmargs: jvmargs, base_content: base_content)
270
+ begin
271
+ SharedHelpers.run_shell_command(command, cwd: cwd)
272
+ throw :success
273
+ rescue SharedHelpers::HelperSubprocessFailed => e
274
+ # If this is the last attempt, re-raise the error
275
+ raise e if index == GRADLE_JVMARGS_ATTEMPTS.length - 1
276
+
277
+ # Only retry if it's a retryable failure
278
+ raise e unless retryable_daemon_failure?(e)
279
+
280
+ Dependabot.logger.warn(
281
+ "Gradle build failed with jvmargs '#{jvmargs}'. Retrying once with larger heap size..."
282
+ )
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ sig { params(error: SharedHelpers::HelperSubprocessFailed).returns(T::Boolean) }
289
+ def retryable_daemon_failure?(error)
290
+ message = error.message.downcase
291
+ # Retry if daemon disappeared
292
+ if message.include?("daemon disappeared") || message.include?("build daemon disappeared unexpectedly")
293
+ return true
294
+ end
295
+
296
+ # Retry if process was killed (SIGKILL)
297
+ return true if T.cast(error.error_context[:process_termsig], T.nilable(Integer)) == SharedHelpers::SIGKILL
298
+
299
+ false
300
+ end
301
+
302
+ sig { returns(String) }
303
+ def installed_gradle_toolchain_paths
304
+ Dir.glob("/usr/lib/jvm/java-*-openjdk-*").join(",")
305
+ end
306
+
206
307
  sig { params(file_name: String).void }
207
308
  def write_init_script(file_name)
208
- # Resolve all resolvable configurations across all loaded projects so
209
- # Gradle rewrites every relevant lockfile in one invocation.
309
+ # Resolve every lockable configuration in one invocation. Not from the task action: that
310
+ # needs the project, which the configuration cache forbids.
210
311
  script_content = <<~GRADLE
211
312
  allprojects {
212
313
  if (tasks.findByName("#{INIT_SCRIPT_TASK_NAME}") == null) {
213
- tasks.register("#{INIT_SCRIPT_TASK_NAME}") {
214
- doLast {
215
- configurations.findAll { it.canBeResolved }.each { it.resolve() }
216
- }
217
- }
314
+ tasks.create("#{INIT_SCRIPT_TASK_NAME}") { }
315
+ }
316
+ }
317
+
318
+ gradle.projectsEvaluated {
319
+ // Prepare every project before resolving anything: with a project dependency,
320
+ // resolving one project computes the other project's javaCompiler.
321
+ #{compiler_fallback_snippet}
322
+
323
+ gradle.rootProject.allprojects { target ->
324
+ target.configurations.findAll {
325
+ it.canBeResolved &&
326
+ it.resolutionStrategy.dependencyLockingEnabled &&
327
+ it.allDependencies.any { dependency ->
328
+ dependency instanceof org.gradle.api.artifacts.ModuleDependency
329
+ }
330
+ }.each { it.incoming.resolutionResult.allDependencies }
218
331
  }
219
332
  }
220
333
  GRADLE
221
334
  File.write(file_name, script_content)
222
335
  end
223
336
 
337
+ # Resolving computes the compile task's javaCompiler, which fails when the toolchain is
338
+ # missing. Only the compiler is swapped, so resolution is unaffected.
339
+ sig { returns(String) }
340
+ def compiler_fallback_snippet
341
+ <<~GRADLE.gsub(/^/, " ")
342
+ gradle.rootProject.allprojects { target ->
343
+ // By name: JavaToolchainService only exists from 6.7, locking from 4.8.
344
+ def javaExtension = target.extensions.findByName("java")
345
+ def toolchainService = target.extensions.findByName("javaToolchains")
346
+ if (javaExtension != null && toolchainService != null) {
347
+ def toolchain = javaExtension.toolchain
348
+ // Toolchains landed in 6.7, vendor and implementation in 6.8, native image in 8.14.
349
+ def constraints = ["vendor", "implementation", "nativeImageCapable"].collectEntries { name ->
350
+ [(name): toolchain.hasProperty(name) ? toolchain."$name".getOrNull() : null]
351
+ }
352
+ if (toolchain.languageVersion.present) {
353
+ def requested = toolchain.languageVersion.get()
354
+ try {
355
+ // Throwaway spec: compilerFor finalizes whatever spec it is given.
356
+ toolchainService.compilerFor { spec ->
357
+ spec.languageVersion.set(requested)
358
+ constraints.each { name, value -> if (value != null) spec."$name".set(value) }
359
+ }.get()
360
+ } catch (Exception ignored) {
361
+ def fallback = JavaLanguageVersion.of(JavaVersion.current().majorVersion)
362
+ target.logger.lifecycle("Dependabot: Java toolchain " + requested +
363
+ " is not available, compiling with " + fallback + " to resolve dependencies.")
364
+ def c = toolchainService.compilerFor { s -> s.languageVersion.set(fallback) }
365
+ target.tasks.withType(JavaCompile).configureEach { t -> t.javaCompiler.set(c) }
366
+ // Kotlin asks for its own toolchain; convention plugins compile during config.
367
+ def l = toolchainService.launcherFor { s -> s.languageVersion.set(fallback) }
368
+ target.tasks.matching { it.hasProperty("kotlinJavaToolchain") }
369
+ .configureEach { t -> t.kotlinJavaToolchain.toolchain.use(l) }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ GRADLE
375
+ end
376
+
377
+ sig { params(build_files: T::Array[Dependabot::DependencyFile]).returns(T::Array[String]) }
378
+ def lockfile_tasks_for(build_files)
379
+ build_files.filter_map { |build_file| dependency_task_for(build_file) }.uniq << INIT_SCRIPT_TASK_NAME
380
+ end
381
+
382
+ sig { params(build_file: Dependabot::DependencyFile).returns(T.nilable(String)) }
383
+ def dependency_task_for(build_file)
384
+ settings_file = find_settings_file(build_file)
385
+ return nil unless settings_file
386
+
387
+ root_dir = determine_root_dir(build_file: build_file)
388
+ file_path = normalized_file_path(build_file)
389
+ relative_path = path_relative_to_root(file_path, root_dir)
390
+
391
+ return nil if relative_path.start_with?("gradle/")
392
+
393
+ dirname = File.dirname(relative_path)
394
+ return nil if dirname == "." || dirname.empty?
395
+
396
+ # Look up the canonical Gradle project name from the settings file to handle repos
397
+ # that use custom projectDir mappings
398
+ # (e.g. project(':chrome-trace').projectDir = file('subprojects/chrome-trace')).
399
+ # Deriving the task from the filesystem path alone would produce a wrong Gradle task path.
400
+ parser = FileFetcher::SettingsFileParser.new(settings_file: settings_file)
401
+ project_name = parser.subproject_path_to_name_map[dirname]
402
+ return nil unless project_name
403
+
404
+ "#{project_name}:dependencies"
405
+ end
406
+
407
+ sig { params(file_path: String, root_dir: String).returns(String) }
408
+ def path_relative_to_root(file_path, root_dir)
409
+ return file_path.sub(%r{^/}, "") if root_dir == "/"
410
+
411
+ root_prefix = "#{root_dir}/"
412
+ return file_path.delete_prefix(root_prefix) if file_path.start_with?(root_prefix)
413
+
414
+ file_path.sub(%r{^/}, "")
415
+ end
416
+
224
417
  sig { params(cwd: String, workspace_root: String).returns(String) }
225
418
  def gradle_executable_for(cwd:, workspace_root:)
226
419
  cwd_path = Pathname.new(cwd).expand_path
@@ -79,19 +79,19 @@ module Dependabot
79
79
 
80
80
  sig { returns(String) }
81
81
  def version
82
- T.let(T.must(@requirements[0])[:requirement], String)
82
+ T.must(T.must(@requirements[0]).requirement_string)
83
83
  end
84
84
 
85
85
  sig { returns(T.nilable(String)) }
86
86
  def checksum
87
87
  return nil unless @requirements.size > 1
88
88
 
89
- T.let(T.must(@requirements[1])[:requirement], String)
89
+ T.must(T.must(@requirements[1]).requirement_string)
90
90
  end
91
91
 
92
92
  sig { returns(T.nilable(String)) }
93
93
  def distribution_type
94
- url = T.let(T.must(@requirements[0])[:source], T::Hash[Symbol, String])[:url]
94
+ url = T.must(@requirements[0]).source_string("url")
95
95
  # Anchor to the `-bin.zip` / `-all.zip` filename suffix so a path segment such as a
96
96
  # mirror host (e.g. https://binaries.example.com/...) can't false-match `bin`/`all`.
97
97
  url&.match(/-(bin|all)\.zip/)&.captures&.first
@@ -23,13 +23,10 @@ module Dependabot
23
23
  # Anchored to the `gradle-<version>-(bin|all).zip` filename so host/port numbers in custom
24
24
  # mirror URLs are never mistaken for the version. The captured token is validated with
25
25
  # Version.correct? so non-version matches (and RC/milestone names) are handled safely.
26
- DISTRIBUTION_URL_VERSION_REGEX = T.let(
27
- /gradle-(?<version>.+?)-(?:bin|all)\.zip/,
28
- Regexp
29
- )
26
+ DISTRIBUTION_URL_VERSION_REGEX = /gradle-(?<version>.+?)-(?:bin|all)\.zip/
30
27
 
31
28
  # Matches the version printed by `gradle --version`, e.g. "Gradle 9.2.1".
32
- GRADLE_VERSION_OUTPUT_REGEX = T.let(/^Gradle\s+(?<version>\d+(?:\.\d+){1,2}(?:-[\w.]+)?)/, Regexp)
29
+ GRADLE_VERSION_OUTPUT_REGEX = /^Gradle\s+(?<version>\d+(?:\.\d+){1,2}(?:-[\w.]+)?)/
33
30
 
34
31
  sig { params(distribution_url: T.nilable(String)).returns(T.nilable(Dependabot::Gradle::Version)) }
35
32
  def self.from_distribution_url(distribution_url)
@@ -29,8 +29,8 @@ module Dependabot
29
29
 
30
30
  # Properties files use `=`, `:` or whitespace as the key/value separator. Gradle always
31
31
  # writes `=`, but we parse all three so user-authored files are handled faithfully.
32
- KEY_VALUE_REGEX = T.let(/\A(\s*)([^\s:=]+)(\s*[:=]\s*|\s+)(.*)\z/, Regexp)
33
- COMMENT_REGEX = T.let(/\A\s*[#!]/, Regexp)
32
+ KEY_VALUE_REGEX = /\A(\s*)([^\s:=]+)(\s*[:=]\s*|\s+)(.*)\z/
33
+ COMMENT_REGEX = /\A\s*[#!]/
34
34
 
35
35
  sig { params(content: String).returns(PropertiesDocument) }
36
36
  def self.parse(content)
@@ -22,10 +22,7 @@ module Dependabot
22
22
 
23
23
  # Keys whose value is owned by the update itself and therefore taken from the regenerated
24
24
  # file. Everything not listed here is preserved verbatim from the user's original file.
25
- MANAGED_KEYS = T.let(
26
- %w(distributionUrl distributionSha256Sum).freeze,
27
- T::Array[String]
28
- )
25
+ MANAGED_KEYS = %w(distributionUrl distributionSha256Sum).freeze
29
26
 
30
27
  # Reconciles the regenerated wrapper properties back onto the user's original file.
31
28
  #
@@ -59,7 +59,7 @@ module Dependabot
59
59
 
60
60
  # we only run this updater if the build file has a requirement for this dependency
61
61
  target_requirements = dependency.requirements.select do |req|
62
- T.let(req[:file], String) == build_file.name
62
+ req.file == build_file.name
63
63
  end
64
64
  return [] unless target_requirements.any?
65
65
 
@@ -17,7 +17,7 @@ module Dependabot
17
17
  require_relative "file_updater/lockfile_updater"
18
18
  require_relative "file_updater/wrapper_updater"
19
19
 
20
- SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts gradle.lockfile).freeze
20
+ SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts).freeze
21
21
 
22
22
  sig { override.returns(T::Array[::Dependabot::DependencyFile]) }
23
23
  def updated_dependency_files
@@ -74,10 +74,10 @@ module Dependabot
74
74
  .reject { |new_req, old_req| new_req == old_req }
75
75
  # Loop through each changed requirement and update the buildfiles
76
76
  reqs.each do |new_req, old_req|
77
- raise "Bad req match" if old_req.nil? || T.let(new_req[:file], String) != T.let(old_req[:file], String)
78
- next if T.let(new_req[:requirement], String) == T.let(old_req[:requirement], String)
77
+ raise "Bad req match" if old_req.nil? || new_req.file != old_req.file
78
+ next if new_req.requirement == old_req.requirement
79
79
 
80
- buildfile = files.find { |f| f.name == T.let(new_req.fetch(:file), String) }
80
+ buildfile = files.find { |f| f.name == new_req.file }
81
81
 
82
82
  # Currently, Dependabot assumes that Gradle projects using Gradle submodules are all in a single
83
83
  # repo. However, some projects are actually using git submodule references for the Gradle submodules.
@@ -88,16 +88,18 @@ module Dependabot
88
88
 
89
89
  raise DependencyFileNotResolvable, "No build file found to update the dependency" if buildfile.nil?
90
90
 
91
- metadata = symbol_object_hash(T.cast(new_req[:metadata], T.nilable(Object)))
92
- if metadata && metadata[:property_name].is_a?(String)
91
+ if new_req.metadata_string("property_name")
93
92
  files = update_files_for_property_change(files, old_req, new_req)
94
- elsif metadata && symbol_object_hash(metadata[:dependency_set])
93
+ elsif new_req.metadata_string_hash("dependency_set")
95
94
  files = update_files_for_dep_set_change(files, old_req, new_req)
96
95
  else
97
96
  files[T.must(files.index(buildfile))] = update_version_in_buildfile(dependency, buildfile, old_req, new_req)
98
97
  end
99
98
 
100
- buildfiles_processed[buildfile.name] = buildfile
99
+ # Always look up the updated buildfile from files after any modification
100
+ # to ensure buildfiles_processed contains the latest version
101
+ updated_buildfile = files.find { |f| f.name == buildfile.name }
102
+ buildfiles_processed[buildfile.name] = T.must(updated_buildfile)
101
103
  end
102
104
 
103
105
  # runs native updaters (e.g. wrapper, lockfile) on relevant build files updated
@@ -122,17 +124,28 @@ module Dependabot
122
124
  ).void
123
125
  end
124
126
  def update_lockfiles_for_buildfiles(files, buildfiles_processed)
125
- lockfile_roots_processed = T.let(Set.new, T::Set[String])
127
+ # Support files (e.g. fetched convention plugin sources) are excluded from
128
+ # `buildfiles`/`files`, but Gradle still needs them on disk to compile
129
+ # plugins referenced by included/buildSrc builds, so pass them through too.
130
+ support_files = dependency_files.select(&:support_file?)
131
+ lockfile_updater = LockfileUpdater.new(
132
+ dependency_files: files + support_files,
133
+ repo_contents_path: repo_contents_path
134
+ )
135
+ buildfiles_by_root = T.let({}, T::Hash[String, T::Array[Dependabot::DependencyFile]])
126
136
 
127
137
  buildfiles_processed.each_value do |buildfile|
128
- lockfile_updater = LockfileUpdater.new(dependency_files: files)
129
138
  root_dir = lockfile_updater.determine_root_dir(build_file: buildfile)
130
- next if lockfile_roots_processed.include?(root_dir)
139
+ buildfiles_by_root[root_dir] ||= []
140
+ T.must(buildfiles_by_root[root_dir]) << buildfile
141
+ end
131
142
 
132
- lockfile_roots_processed.add(root_dir)
143
+ buildfiles_by_root.each_value do |buildfiles|
144
+ updated_files = lockfile_updater.update_lockfiles(T.must(buildfiles.first), build_files: buildfiles)
133
145
 
134
- updated_files = lockfile_updater.update_lockfiles(buildfile)
135
- replace_updated_files(files, updated_files)
146
+ # Support files are only needed to populate Gradle's working directory;
147
+ # they must never be reintroduced into the buildfiles-only result set.
148
+ replace_updated_files(files, updated_files.reject(&:support_file?))
136
149
  end
137
150
  end
138
151
  sig do
@@ -156,47 +169,45 @@ module Dependabot
156
169
  sig do
157
170
  params(
158
171
  buildfiles: T::Array[Dependabot::DependencyFile],
159
- old_req: T::Hash[Symbol, Object],
160
- new_req: T::Hash[Symbol, Object]
172
+ old_req: Dependabot::DependencyRequirement,
173
+ new_req: Dependabot::DependencyRequirement
161
174
  )
162
175
  .returns(T::Array[Dependabot::DependencyFile])
163
176
  end
164
177
  def update_files_for_property_change(buildfiles, old_req, new_req)
165
178
  files = buildfiles.dup
166
- metadata = symbol_object_hash_value(new_req, :metadata)
167
- property_name = string_value(metadata, :property_name)
168
- file = string_value(new_req, :file)
179
+ property_name = T.must(new_req.metadata_string("property_name"))
180
+ file = T.must(new_req.file)
169
181
  buildfile = T.must(files.find { |f| f.name == file })
170
182
 
171
183
  PropertyValueUpdater.new(dependency_files: files)
172
184
  .update_files_for_property_change(
173
185
  property_name: property_name,
174
186
  callsite_buildfile: buildfile,
175
- previous_value: string_value(old_req, :requirement),
176
- updated_value: string_value(new_req, :requirement)
187
+ previous_value: T.must(old_req.requirement_string),
188
+ updated_value: T.must(new_req.requirement_string)
177
189
  )
178
190
  end
179
191
 
180
192
  sig do
181
193
  params(
182
194
  buildfiles: T::Array[Dependabot::DependencyFile],
183
- old_req: T::Hash[Symbol, Object],
184
- new_req: T::Hash[Symbol, Object]
195
+ old_req: Dependabot::DependencyRequirement,
196
+ new_req: Dependabot::DependencyRequirement
185
197
  )
186
198
  .returns(T::Array[Dependabot::DependencyFile])
187
199
  end
188
200
  def update_files_for_dep_set_change(buildfiles, old_req, new_req)
189
201
  files = buildfiles.dup
190
- metadata = symbol_object_hash_value(new_req, :metadata)
191
- dependency_set = symbol_string_hash_value(metadata, :dependency_set)
192
- buildfile = T.must(files.find { |f| f.name == string_value(new_req, :file) })
202
+ dependency_set = T.must(new_req.metadata_string_hash("dependency_set"))
203
+ buildfile = T.must(files.find { |f| f.name == new_req.file })
193
204
 
194
205
  DependencySetUpdater.new(dependency_files: files)
195
206
  .update_files_for_dep_set_change(
196
207
  dependency_set: dependency_set,
197
208
  buildfile: buildfile,
198
- previous_requirement: string_value(old_req, :requirement),
199
- updated_requirement: string_value(new_req, :requirement)
209
+ previous_requirement: T.must(old_req.requirement_string),
210
+ updated_requirement: T.must(new_req.requirement_string)
200
211
  )
201
212
  end
202
213
 
@@ -204,8 +215,8 @@ module Dependabot
204
215
  params(
205
216
  dependency: Dependabot::Dependency,
206
217
  buildfile: Dependabot::DependencyFile,
207
- previous_req: T::Hash[Symbol, Object],
208
- requirement: T::Hash[Symbol, Object]
218
+ previous_req: Dependabot::DependencyRequirement,
219
+ requirement: Dependabot::DependencyRequirement
209
220
  )
210
221
  .returns(Dependabot::DependencyFile)
211
222
  end
@@ -235,13 +246,13 @@ module Dependabot
235
246
  sig do
236
247
  params(
237
248
  dependency: Dependabot::Dependency,
238
- requirement: T::Hash[Symbol, Object]
249
+ requirement: Dependabot::DependencyRequirement
239
250
  ).returns(T::Array[String])
240
251
  end
241
252
  def original_buildfile_declarations(dependency, requirement)
242
253
  # This implementation is limited to declarations that appear on a
243
254
  # single line.
244
- file = string_value(requirement, :file)
255
+ file = T.must(requirement.file)
245
256
  buildfile = T.must(buildfiles.find { |f| f.name == file })
246
257
 
247
258
  T.must(buildfile.content).lines.select do |line|
@@ -252,11 +263,7 @@ module Dependabot
252
263
  dep_parts = dependency.name.split(":")
253
264
  next false unless line.include?(T.must(dep_parts.first)) || line.include?(T.must(dep_parts.last))
254
265
  elsif file.end_with?(".properties")
255
- source = requirement[:source]
256
- property = T.let(nil, T.nilable(String))
257
- source_hash = symbol_object_hash(source)
258
- source_property = source_hash&.[](:property)
259
- property = source_property if source_property.is_a?(String)
266
+ property = requirement.source_string("property")
260
267
  next false unless property && line.start_with?(property)
261
268
  elsif file.end_with?(".toml")
262
269
  next false unless line.include?(dependency.name)
@@ -266,7 +273,7 @@ module Dependabot
266
273
  next false unless line.match?(name_regex)
267
274
  end
268
275
 
269
- line.include?(string_value(requirement, :requirement))
276
+ line.include?(T.must(requirement.requirement_string))
270
277
  end
271
278
  end
272
279
  # rubocop:enable Metrics/AbcSize
@@ -301,57 +308,17 @@ module Dependabot
301
308
  sig do
302
309
  params(
303
310
  original_buildfile_declaration: String,
304
- previous_req: T::Hash[Symbol, Object],
305
- requirement: T::Hash[Symbol, Object]
311
+ previous_req: Dependabot::DependencyRequirement,
312
+ requirement: Dependabot::DependencyRequirement
306
313
  ).returns(String)
307
314
  end
308
315
  def updated_buildfile_declaration(original_buildfile_declaration, previous_req, requirement)
309
- original_req_string = string_value(previous_req, :requirement)
310
- new_req_string = string_value(requirement, :requirement)
316
+ original_req_string = T.must(previous_req.requirement_string)
317
+ new_req_string = T.must(requirement.requirement_string)
311
318
 
312
319
  original_buildfile_declaration.gsub(original_req_string, new_req_string)
313
320
  end
314
321
 
315
- sig { params(hash: T::Hash[Symbol, Object], key: Symbol).returns(String) }
316
- def string_value(hash, key)
317
- value = hash.fetch(key)
318
- raise TypeError, "Expected #{key} to be a String" unless value.is_a?(String)
319
-
320
- value
321
- end
322
-
323
- sig { params(hash: T::Hash[Symbol, Object], key: Symbol).returns(T::Hash[Symbol, Object]) }
324
- def symbol_object_hash_value(hash, key)
325
- value = hash.fetch(key)
326
- raise TypeError, "Expected #{key} to be a Hash" unless value.is_a?(Hash)
327
-
328
- value
329
- end
330
-
331
- sig { params(hash: T::Hash[Symbol, Object], key: Symbol).returns(T::Hash[Symbol, String]) }
332
- def symbol_string_hash_value(hash, key)
333
- value = hash.fetch(key)
334
- raise TypeError, "Expected #{key} to be a Hash" unless value.is_a?(Hash)
335
-
336
- result = T.let({}, T::Hash[Symbol, String])
337
- value.each_pair do |hash_key_object, hash_value_object|
338
- hash_key = T.cast(hash_key_object, T.nilable(Object))
339
- hash_value = T.cast(hash_value_object, T.nilable(Object))
340
- raise TypeError, "Expected #{key} keys and values to be Symbols and Strings" unless hash_key.is_a?(Symbol) &&
341
- hash_value.is_a?(String)
342
-
343
- result[hash_key] = hash_value
344
- end
345
- result
346
- end
347
-
348
- sig { params(value: T.nilable(Object)).returns(T.nilable(T::Hash[Symbol, Object])) }
349
- def symbol_object_hash(value)
350
- return unless value.is_a?(Hash)
351
-
352
- value
353
- end
354
-
355
322
  sig { returns(T::Array[Dependabot::DependencyFile]) }
356
323
  def buildfiles
357
324
  @buildfiles ||= T.let(dependency_files.reject(&:support_file?), T.nilable(T::Array[Dependabot::DependencyFile]))
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -66,11 +66,12 @@ module Dependabot
66
66
 
67
67
  sig { override.returns(String) }
68
68
  def maven_repo_url
69
- source = dependency.requirements
70
- .find { |r| r.fetch(:source) }&.fetch(:source)
69
+ requirement = dependency.requirements.find(&:source)
70
+ return Gradle::FileParser::RepositoriesFinder::CENTRAL_REPO_URL unless requirement
71
71
 
72
- source&.fetch(:url, nil) ||
73
- source&.fetch("url") ||
72
+ raise TypeError, "Gradle repository source must be a hash" if requirement.source_hash.nil?
73
+
74
+ requirement.source_string("url") ||
74
75
  Gradle::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
75
76
  end
76
77
 
@@ -81,12 +82,12 @@ module Dependabot
81
82
 
82
83
  sig { returns(T::Boolean) }
83
84
  def plugin?
84
- dependency.requirements.any? { |r| r.fetch(:groups).include? "plugins" }
85
+ dependency.requirements.any? { |r| r.groups&.include?("plugins") }
85
86
  end
86
87
 
87
88
  sig { returns(T::Boolean) }
88
89
  def kotlin_plugin?
89
- plugin? && dependency.requirements.any? { |r| r.fetch(:groups).include? "kotlin" }
90
+ plugin? && dependency.requirements.any? { |r| r.groups&.include?("kotlin") }
90
91
  end
91
92
  end
92
93
  end
@@ -86,7 +86,7 @@ module Dependabot
86
86
  @dependency_files = dependency_files
87
87
  @credentials = credentials
88
88
  @forbidden_urls = forbidden_urls
89
- @cooldown_options = T.let(cooldown_options, T.nilable(Dependabot::Package::ReleaseCooldownOptions))
89
+ @cooldown_options = cooldown_options
90
90
  @repositories = T.let(nil, T.nilable(T::Array[T::Hash[String, Object]]))
91
91
  @google_version_details = T.let(nil, T.nilable(Nokogiri::XML::Document))
92
92
  @dependency_repository_details = T.let(nil, T.nilable(T::Array[T::Hash[String, Object]]))
@@ -346,7 +346,7 @@ module Dependabot
346
346
  def dependency_repository_details
347
347
  requirement_files =
348
348
  dependency.requirements
349
- .map { |r| r.fetch(:file) }
349
+ .filter_map(&:file)
350
350
  .map { |nm| dependency_files.find { |f| f.name == nm } }
351
351
 
352
352
  @dependency_repository_details ||=
@@ -396,7 +396,7 @@ module Dependabot
396
396
 
397
397
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
398
398
  def pom
399
- filename = T.must(dependency.requirements.first).fetch(:file)
399
+ filename = T.must(dependency.requirements.first).file
400
400
  dependency_files.find { |f| f.name == filename }
401
401
  end
402
402
 
@@ -425,12 +425,12 @@ module Dependabot
425
425
 
426
426
  sig { returns(T::Boolean) }
427
427
  def plugin?
428
- dependency.requirements.any? { |r| r.fetch(:groups).include? "plugins" }
428
+ dependency.requirements.any? { |r| r.groups&.include?("plugins") }
429
429
  end
430
430
 
431
431
  sig { returns(T.nilable(T::Boolean)) }
432
432
  def kotlin_plugin?
433
- plugin? && dependency.requirements.any? { |r| r.fetch(:groups).include? "kotlin" }
433
+ plugin? && dependency.requirements.any? { |r| r.groups&.include?("kotlin") }
434
434
  end
435
435
 
436
436
  sig { returns(T::Boolean) }
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -32,9 +32,9 @@ module Dependabot
32
32
  ignored_versions:,
33
33
  raise_on_ignored: false
34
34
  )
35
- @dependency = T.let(dependency, Dependabot::Dependency)
36
- @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
37
- @credentials = T.let(credentials, T::Array[Dependabot::Credential])
35
+ @dependency = dependency
36
+ @dependency_files = dependency_files
37
+ @credentials = credentials
38
38
  @target_version = T.let(
39
39
  version_from_details(target_version_details),
40
40
  T.nilable(Dependabot::Gradle::Version)
@@ -43,14 +43,14 @@ module Dependabot
43
43
  source_url_from_details(target_version_details),
44
44
  T.nilable(String)
45
45
  )
46
- @ignored_versions = T.let(ignored_versions, T::Array[String])
47
- @raise_on_ignored = T.let(raise_on_ignored, T::Boolean)
46
+ @ignored_versions = ignored_versions
47
+ @raise_on_ignored = raise_on_ignored
48
48
  @update_possible = T.let(nil, T.nilable(T::Boolean))
49
49
  @updated_dependencies = T.let(nil, T.nilable(T::Array[Dependabot::Dependency]))
50
50
  @dependencies_to_update = T.let(nil, T.nilable(T::Array[Dependabot::Dependency]))
51
51
  @property_name = T.let(nil, T.nilable(String))
52
52
  @dependency_set = T.let(nil, T.nilable(T::Hash[Symbol, String]))
53
- @updated_requirements = T.let({}, T::Hash[String, T::Array[T::Hash[Symbol, Object]]])
53
+ @updated_requirements = T.let({}, T::Hash[String, T::Array[Dependabot::DependencyRequirement]])
54
54
  end
55
55
  sig { returns(T::Boolean) }
56
56
  def update_possible?
@@ -128,8 +128,8 @@ module Dependabot
128
128
  source: nil
129
129
  ).parse.select do |dep|
130
130
  dep.requirements.any? do |r|
131
- tmp_p_name = r.dig(:metadata, :property_name)
132
- tmp_dep_set = r.dig(:metadata, :dependency_set)
131
+ tmp_p_name = r.metadata_string("property_name")
132
+ tmp_dep_set = r.metadata_string_hash("dependency_set")
133
133
  next true if property_name && tmp_p_name == property_name
134
134
 
135
135
  dependency_set && tmp_dep_set == dependency_set
@@ -140,18 +140,18 @@ module Dependabot
140
140
  sig { returns(T.nilable(String)) }
141
141
  def property_name
142
142
  @property_name ||= dependency.requirements
143
- .find { |r| r.dig(:metadata, :property_name) }
144
- &.dig(:metadata, :property_name)
143
+ .find { |r| r.metadata_string("property_name") }
144
+ &.metadata_string("property_name")
145
145
  end
146
146
 
147
147
  sig { returns(T.nilable(T::Hash[Symbol, String])) }
148
148
  def dependency_set
149
149
  @dependency_set ||= dependency.requirements
150
- .find { |r| r.dig(:metadata, :dependency_set) }
151
- &.dig(:metadata, :dependency_set)
150
+ .find { |r| r.metadata_string_hash("dependency_set") }
151
+ &.metadata_string_hash("dependency_set")
152
152
  end
153
153
 
154
- sig { params(dep: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, Object]]) }
154
+ sig { params(dep: Dependabot::Dependency).returns(T::Array[Dependabot::DependencyRequirement]) }
155
155
  def updated_requirements(dep)
156
156
  @updated_requirements[dep.name] ||=
157
157
  RequirementsUpdater.new(
@@ -61,13 +61,14 @@ module Dependabot
61
61
  # requirement at index `i` to correspond to the previous requirement
62
62
  # at the same index.
63
63
  requirements.map do |req|
64
- next req if req.fetch(:requirement).nil?
65
- next req if req.fetch(:requirement).include?(",")
64
+ requirement = req.requirement_string
65
+ next req if requirement.nil?
66
+ next req if requirement.include?(",")
66
67
 
67
- property_name = req.dig(:metadata, :property_name)
68
+ property_name = req.metadata_string("property_name")
68
69
  next req if property_name && !properties_to_update.include?(property_name)
69
70
 
70
- new_req = update_requirement(req[:requirement])
71
+ new_req = update_requirement(requirement)
71
72
  Dependabot::DependencyRequirement.create(req.merge(requirement: new_req, source: updated_source))
72
73
  end
73
74
  end
@@ -124,27 +125,29 @@ module Dependabot
124
125
  distribution_url = T.let(nil, T.nilable(String))
125
126
 
126
127
  requirements.map do |req|
127
- source = req[:source]
128
+ source = req.source_hash
128
129
  next req unless source
129
130
 
130
- case source[:property]
131
+ case req.source_string("property")
131
132
  when "distributionUrl"
132
- requirement = req[:requirement]
133
+ requirement = T.must(req.requirement_string)
133
134
  version = update_exact_requirement(requirement)
134
- distribution_url = source[:url].gsub(requirement, version)
135
+ distribution_url = T.must(req.source_string("url")).gsub(requirement, version)
136
+ updated_source = set_source_string(source.dup, "url", distribution_url)
135
137
 
136
138
  Dependabot::DependencyRequirement.create(
137
139
  req.merge(
138
140
  requirement: version,
139
- source: source.merge(url: distribution_url)
141
+ source: updated_source
140
142
  )
141
143
  )
142
144
  when "distributionSha256Sum"
143
145
  checksum_url, checksum = Gradle::Package::DistributionsFetcher.resolve_checksum(T.must(distribution_url))
146
+ updated_source = set_source_string(source.dup, "url", checksum_url)
144
147
  Dependabot::DependencyRequirement.create(
145
148
  req.merge(
146
149
  requirement: checksum,
147
- source: source.merge(url: checksum_url)
150
+ source: updated_source
148
151
  )
149
152
  )
150
153
  else
@@ -153,6 +156,23 @@ module Dependabot
153
156
  end
154
157
  end
155
158
 
159
+ sig do
160
+ params(
161
+ source: Dependabot::DependencyRequirement::ObjectHash,
162
+ key: String,
163
+ value: T.nilable(String)
164
+ ).returns(Dependabot::DependencyRequirement::ObjectHash)
165
+ end
166
+ def set_source_string(source, key, value)
167
+ symbol_key = key.to_sym
168
+ if source.key?(key) && !source.key?(symbol_key)
169
+ source[key] = value
170
+ else
171
+ source[symbol_key] = value
172
+ end
173
+ source
174
+ end
175
+
156
176
  sig { override.returns(T::Class[Version]) }
157
177
  def version_class
158
178
  Gradle::Version
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -206,34 +206,30 @@ module Dependabot
206
206
  next false if dep.name == dependency.name
207
207
 
208
208
  dep.requirements.any? do |req|
209
- req.dig(:metadata, :property_name) == property_name
209
+ req.metadata_string("property_name") == property_name
210
210
  end
211
211
  end
212
212
  end
213
213
  end
214
214
 
215
- sig { params(requirement: T::Hash[Symbol, Object]).returns(T.nilable(String)) }
215
+ sig { params(requirement: Dependabot::DependencyRequirement).returns(T.nilable(String)) }
216
216
  def property_name_from_requirement(requirement)
217
- metadata = requirement[:metadata]
218
- return unless metadata.is_a?(Hash)
219
-
220
- property_name = metadata[:property_name]
221
- property_name if property_name.is_a?(String)
217
+ requirement.metadata_string("property_name")
222
218
  end
223
219
 
224
220
  sig { returns(T::Boolean) }
225
221
  def version_comes_from_dependency_set?
226
222
  dependency.requirements.any? do |req|
227
- req.dig(:metadata, :dependency_set)
223
+ req.metadata_string_hash("dependency_set")
228
224
  end
229
225
  end
230
226
 
231
- sig { returns(T::Array[T::Hash[Symbol, Object]]) }
227
+ sig { returns(T::Array[Dependabot::DependencyRequirement]) }
232
228
  def declarations_using_a_property
233
229
  @declarations_using_a_property ||= T.let(
234
230
  dependency.requirements
235
- .select { |req| req.dig(:metadata, :property_name) },
236
- T.nilable(T::Array[T::Hash[Symbol, Object]])
231
+ .select { |req| req.metadata_string("property_name") },
232
+ T.nilable(T::Array[Dependabot::DependencyRequirement])
237
233
  )
238
234
  end
239
235
 
@@ -244,7 +240,7 @@ module Dependabot
244
240
  dependency_files: dependency_files,
245
241
  source: nil
246
242
  ).parse.select do |dep|
247
- dep.requirements.any? { |req| req.dig(:metadata, :property_name) }
243
+ dep.requirements.any? { |req| req.metadata_string("property_name") }
248
244
  end,
249
245
  T.nilable(T::Array[Dependabot::Dependency])
250
246
  )
@@ -15,7 +15,7 @@ module Dependabot
15
15
  class Version < Dependabot::Version
16
16
  extend T::Sig
17
17
 
18
- NULL_VALUES = T.let(%w(0 final ga).freeze, T::Array[String])
18
+ NULL_VALUES = %w(0 final ga).freeze
19
19
  PREFIXED_TOKEN_HIERARCHY = T.let(
20
20
  {
21
21
  "." => { qualifier: 1, number: 4 },
@@ -39,13 +39,10 @@ module Dependabot
39
39
  }.freeze,
40
40
  T::Hash[String, Integer]
41
41
  )
42
- VERSION_PATTERN = T.let(
43
- "[0-9a-zA-Z]+" \
44
- '(?>\.[0-9a-zA-Z]*)*' \
45
- '([_\-\+][0-9A-Za-z_-]*(\.[0-9A-Za-z_-]*)*)?',
46
- String
47
- )
48
- ANCHORED_VERSION_PATTERN = T.let(/\A\s*(#{VERSION_PATTERN})?\s*\z/, Regexp)
42
+ VERSION_PATTERN = "[0-9a-zA-Z]+" \
43
+ '(?>\.[0-9a-zA-Z]*)*' \
44
+ '([_\-\+][0-9A-Za-z_-]*(\.[0-9A-Za-z_-]*)*)?'
45
+ ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/
49
46
 
50
47
  sig { override.params(version: Object).returns(T::Boolean) }
51
48
  def self.correct?(version)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-gradle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.389.0
4
+ version: 0.391.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.389.0
18
+ version: 0.391.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.389.0
25
+ version: 0.391.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dependabot-maven
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.389.0
32
+ version: 0.391.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.389.0
39
+ version: 0.391.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -291,7 +291,7 @@ licenses:
291
291
  - MIT
292
292
  metadata:
293
293
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
294
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.389.0
294
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.391.0
295
295
  rdoc_options: []
296
296
  require_paths:
297
297
  - lib