dependabot-gradle 0.344.1 → 0.346.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: 07ce668445eeab438ae97771737d7258350d7dd18860a83f5e819cb2204c13ea
4
- data.tar.gz: 48e36a06c18360262a74bcc8ff982e3ec1178610afc949b562f881e2d81ed50c
3
+ metadata.gz: c0e7bd82eab187986339ffedc9f8373d0b14bd54d89fdb1753814532baf8f0e5
4
+ data.tar.gz: 22e4355175c077fb3a95461d5f8e4970ec3c095a43db830caf368fe1f64ace6d
5
5
  SHA512:
6
- metadata.gz: 8824be2c33fffcf1dff6dd8208af5f2da3181c58199c4178b6066f9ffe2593853768003cc38fddba19fb2e9b2b045220632b14bfebc26de076390c2933d9392e
7
- data.tar.gz: a60d58f4853ddf645f1eb054e51fcff7e3185be198b036d87908ac72936c25195490478c28d0134eefe5e8dc5331e59c9559147ea2571db8ad75f5990f0a4864
6
+ metadata.gz: 015aead61df769d95c51d9f23b2383f6bca808335de15c2a2531af3d3b9e4eb53fe7887244ec576e64ece0fc304d095659f418c4b2227df39cbca96c713bd2cc
7
+ data.tar.gz: de0365699f56cc9be2fcbf043b447922bbd64c8d7df95095dc4f6d7fd8bcff5c11c10db0aa0fc8273881d75d4d6231e89acb30728c709fc3e97a75be1f557d11
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Gradle
6
+ module Distributions
7
+ extend T::Sig
8
+
9
+ DISTRIBUTION_REPOSITORY_URL = "https://services.gradle.org"
10
+ DISTRIBUTION_DEPENDENCY_TYPE = "gradle-distribution"
11
+
12
+ sig { params(requirements: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Boolean) }
13
+ def self.distribution_requirements?(requirements)
14
+ requirements.any? do |req|
15
+ req.dig(:source, :type) == DISTRIBUTION_DEPENDENCY_TYPE
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -24,6 +24,13 @@ module Dependabot
24
24
  SUPPORTED_SETTINGS_FILE_NAMES =
25
25
  T.let(%w(settings.gradle settings.gradle.kts).freeze, T::Array[String])
26
26
 
27
+ SUPPORTED_WRAPPER_FILES_PATH = %w(
28
+ gradlew
29
+ gradlew.bat
30
+ gradle/wrapper/gradle-wrapper.jar
31
+ gradle/wrapper/gradle-wrapper.properties
32
+ ).freeze
33
+
27
34
  # For now Gradle only supports library .toml files in the main gradle folder
28
35
  SUPPORTED_VERSION_CATALOG_FILE_PATH =
29
36
  T.let(%w(/gradle/libs.versions.toml).freeze, T::Array[String])
@@ -76,6 +83,7 @@ module Dependabot
76
83
  def all_buildfiles_in_build(root_dir)
77
84
  files = [buildfile(root_dir), settings_file(root_dir), version_catalog_file(root_dir), lockfile(root_dir)]
78
85
  .compact
86
+ files += wrapper_files(root_dir)
79
87
  files += subproject_buildfiles(root_dir)
80
88
  files += subproject_lockfiles(root_dir)
81
89
  files += dependency_script_plugins(root_dir)
@@ -172,6 +180,25 @@ module Dependabot
172
180
  end
173
181
  end
174
182
 
183
+ sig { params(dir: String).returns(T::Array[DependencyFile]) }
184
+ def wrapper_files(dir)
185
+ return [] unless Experiments.enabled?(:gradle_wrapper_updater)
186
+
187
+ SUPPORTED_WRAPPER_FILES_PATH.filter_map do |filename|
188
+ file = fetch_file_if_present(File.join(dir, filename))
189
+ next unless file
190
+
191
+ if file.name.end_with?(".jar")
192
+ file.content = Base64.encode64(T.must(file.content)) if file.content
193
+ file.content_encoding = DependencyFile::ContentEncoding::BASE64
194
+ end
195
+ file
196
+ rescue Dependabot::DependencyFileNotFound
197
+ # Gradle itself doesn't worry about missing subprojects, so we don't
198
+ nil
199
+ end
200
+ end
201
+
175
202
  sig { params(root_dir: String).returns(T.nilable(DependencyFile)) }
176
203
  def version_catalog_file(root_dir)
177
204
  return nil unless root_dir == "."
@@ -0,0 +1,90 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/gradle/file_parser"
5
+ require "dependabot/gradle/distributions"
6
+ require "sorbet-runtime"
7
+
8
+ module Dependabot
9
+ module Gradle
10
+ class FileParser
11
+ class DistributionsFinder
12
+ extend T::Sig
13
+
14
+ DISTRIBUTION_URL_REGEX =
15
+ /.*?(?<version>(\d+(?:\.\d+){1,3}(?:-(?!bin|all)\w++)*(?:\+\w++)*))(?:-bin|-all)?.*?/
16
+
17
+ sig { params(properties_file: DependencyFile).returns(T.nilable(Dependency)) }
18
+ def self.resolve_dependency(properties_file) # rubocop:disable Metrics/MethodLength
19
+ content = properties_file.content
20
+ return nil unless content
21
+
22
+ distribution_url, checksum = load_properties(content)
23
+ match = distribution_url&.match(DISTRIBUTION_URL_REGEX)&.named_captures
24
+ return nil unless match
25
+
26
+ version = match.fetch("version")
27
+
28
+ requirements = T.let(
29
+ [{
30
+ requirement: version,
31
+ file: properties_file.name,
32
+ source: {
33
+ type: Distributions::DISTRIBUTION_DEPENDENCY_TYPE,
34
+ url: distribution_url,
35
+ property: "distributionUrl"
36
+ },
37
+ groups: []
38
+ }],
39
+ T::Array[T::Hash[Symbol, T.untyped]]
40
+ )
41
+
42
+ if checksum
43
+ requirements << {
44
+ requirement: checksum,
45
+ file: properties_file.name,
46
+ source: {
47
+ type: Distributions::DISTRIBUTION_DEPENDENCY_TYPE,
48
+ url: "#{distribution_url}.sha256",
49
+ property: "distributionSha256Sum"
50
+ },
51
+ groups: []
52
+ }
53
+ end
54
+
55
+ Dependency.new(
56
+ name: "gradle-wrapper",
57
+ version: version,
58
+ requirements: requirements,
59
+ package_manager: "gradle"
60
+ )
61
+ end
62
+
63
+ sig { params(properties_content: String).returns(T::Array[T.nilable(String)]) }
64
+ def self.load_properties(properties_content)
65
+ distribution_url = T.let(nil, T.nilable(String))
66
+ checksum = T.let(nil, T.nilable(String))
67
+
68
+ properties_content.lines.each do |line|
69
+ (key, value) = line.split("=", 2).map(&:strip)
70
+ next unless key && value
71
+
72
+ case key
73
+ when "distributionUrl"
74
+ distribution_url = value.gsub("\\:", ":")
75
+ when "distributionSha256Sum"
76
+ checksum = value
77
+ else
78
+ next
79
+ end
80
+ break if distribution_url && checksum
81
+ end
82
+
83
+ [distribution_url, checksum]
84
+ end
85
+
86
+ private_class_method :load_properties
87
+ end
88
+ end
89
+ end
90
+ end
@@ -25,6 +25,7 @@ module Dependabot
25
25
  extend T::Sig
26
26
 
27
27
  require "dependabot/file_parsers/base/dependency_set"
28
+ require_relative "file_parser/distributions_finder"
28
29
  require_relative "file_parser/property_value_finder"
29
30
 
30
31
  SUPPORTED_BUILD_FILE_NAMES = T.let(
@@ -59,6 +60,11 @@ module Dependabot
59
60
  script_plugin_files.each do |plugin_file|
60
61
  dependency_set += buildfile_dependencies(plugin_file)
61
62
  end
63
+ if Experiments.enabled?(:gradle_wrapper_updater)
64
+ wrapper_properties_file.each do |properties_file|
65
+ dependency_set += wrapper_properties_dependencies(properties_file)
66
+ end
67
+ end
62
68
  version_catalog_file.each do |toml_file|
63
69
  dependency_set += version_catalog_dependencies(toml_file)
64
70
  end
@@ -119,6 +125,14 @@ module Dependabot
119
125
  )
120
126
  end
121
127
 
128
+ sig { params(properties_file: Dependabot::DependencyFile).returns(DependencySet) }
129
+ def wrapper_properties_dependencies(properties_file)
130
+ dependency_set = DependencySet.new
131
+ dependency = DistributionsFinder.resolve_dependency(properties_file)
132
+ dependency_set << dependency if dependency
133
+ dependency_set
134
+ end
135
+
122
136
  sig { params(toml_file: Dependabot::DependencyFile).returns(DependencySet) }
123
137
  def version_catalog_dependencies(toml_file)
124
138
  dependency_set = DependencySet.new
@@ -544,6 +558,14 @@ module Dependabot
544
558
  )
545
559
  end
546
560
 
561
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
562
+ def wrapper_properties_file
563
+ @wrapper_properties_file ||= T.let(
564
+ dependency_files.select { |f| f.name.end_with?("gradle-wrapper.properties") },
565
+ T.nilable(T::Array[Dependabot::DependencyFile])
566
+ )
567
+ end
568
+
547
569
  sig { returns(T::Array[Dependabot::DependencyFile]) }
548
570
  def version_catalog_file
549
571
  @version_catalog_file ||= T.let(
@@ -0,0 +1,161 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "shellwords"
6
+
7
+ require "dependabot/gradle/distributions"
8
+
9
+ module Dependabot
10
+ module Gradle
11
+ class FileUpdater
12
+ class WrapperUpdater
13
+ extend T::Sig
14
+ include Dependabot::Gradle::Distributions
15
+
16
+ sig { params(dependency_files: T::Array[Dependabot::DependencyFile], dependency: Dependabot::Dependency).void }
17
+ def initialize(dependency_files:, dependency:)
18
+ @dependency_files = dependency_files
19
+ @dependency = dependency
20
+ @target_files = T.let(
21
+ %w(
22
+ /gradlew
23
+ /gradlew.bat
24
+ /gradle/wrapper/gradle-wrapper.properties
25
+ /gradle/wrapper/gradle-wrapper.jar
26
+ ),
27
+ T::Array[String]
28
+ )
29
+ @build_files = T.let(
30
+ %w(
31
+ build.gradle
32
+ build.gradle.kts
33
+ settings.gradle
34
+ settings.gradle.kts
35
+ ),
36
+ T::Array[String]
37
+ )
38
+ end
39
+
40
+ sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
41
+ def update_files(build_file)
42
+ local_files = dependency_files.select do |file|
43
+ file.directory == build_file.directory && target_file?(file)
44
+ end
45
+
46
+ # If we don't have any files in the build files don't generate one
47
+ return dependency_files unless local_files.any?
48
+
49
+ updated_files = dependency_files.dup
50
+ SharedHelpers.in_a_temporary_directory do |temp_dir|
51
+ populate_temp_directory(temp_dir)
52
+ cwd = File.join(temp_dir, base_path(build_file))
53
+
54
+ # Create gradle.properties file with proxy settings
55
+ # Would prefer to use command line arguments, but they don't work.
56
+ properties_filename = File.join(temp_dir, build_file.directory, "gradle.properties")
57
+ write_properties_file(properties_filename)
58
+
59
+ command_parts = %w(gradle --no-daemon --stacktrace) + command_args
60
+ command = Shellwords.join(command_parts)
61
+
62
+ Dir.chdir(cwd) do
63
+ SharedHelpers.run_shell_command(command, cwd: cwd)
64
+ update_files_content(temp_dir, local_files, updated_files)
65
+ rescue SharedHelpers::HelperSubprocessFailed => e
66
+ puts "Failed to update files: #{e.message}"
67
+ return updated_files
68
+ end
69
+ end
70
+ updated_files
71
+ end
72
+
73
+ private
74
+
75
+ sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
76
+ def target_file?(file)
77
+ @target_files.any? { |r| "/#{file.name}".end_with?(r) }
78
+ end
79
+
80
+ sig { returns(T::Array[String]) }
81
+ def command_args
82
+ version = T.let(dependency.requirements[0]&.[](:requirement), String)
83
+ checksum = T.let(dependency.requirements[1]&.[](:requirement), String) if dependency.requirements.size > 1
84
+
85
+ args = %W(wrapper --no-validate-url --gradle-version #{version})
86
+ args += %W(--gradle-distribution-sha256-sum #{checksum}) if checksum
87
+ args
88
+ end
89
+
90
+ # Gradle builds can be complex, to maximize the chances of a successful we just keep related wrapper files
91
+ # and produce a minimal build for it to run (losing any customisations of the `wrapper` task in the process)
92
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
93
+ def files_to_populate
94
+ @dependency_files.filter_map do |f|
95
+ next f if target_file?(f)
96
+ next Dependabot::DependencyFile.new(directory: f.directory, name: f.name, content: "") if build_file?(f)
97
+ end
98
+ end
99
+
100
+ sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
101
+ def build_file?(file)
102
+ @build_files.include?(File.basename(file.name))
103
+ end
104
+
105
+ sig { params(build_file: Dependabot::DependencyFile).returns(String) }
106
+ def base_path(build_file)
107
+ File.dirname(File.join(build_file.directory, build_file.name)).delete_suffix("/gradle/wrapper")
108
+ end
109
+
110
+ sig do
111
+ params(
112
+ temp_dir: T.any(Pathname, String),
113
+ local_files: T::Array[Dependabot::DependencyFile],
114
+ updated_files: T::Array[Dependabot::DependencyFile]
115
+ ).void
116
+ end
117
+ def update_files_content(temp_dir, local_files, updated_files)
118
+ local_files.each do |file|
119
+ f_content = File.read(File.join(temp_dir, file.directory, file.name))
120
+ tmp_file = file.dup
121
+ tmp_file.content = tmp_file.binary? ? Base64.encode64(f_content) : f_content
122
+ updated_files[T.must(updated_files.index(file))] = tmp_file
123
+ end
124
+ end
125
+
126
+ sig { params(temp_dir: T.any(Pathname, String)).void }
127
+ def populate_temp_directory(temp_dir)
128
+ files_to_populate.each do |file|
129
+ in_path_name = File.join(temp_dir, file.directory, file.name)
130
+ FileUtils.mkdir_p(File.dirname(in_path_name))
131
+ File.write(in_path_name, file.content)
132
+ end
133
+ end
134
+
135
+ sig { params(file_name: String).void }
136
+ def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
137
+ http_proxy = ENV.fetch("HTTP_PROXY", nil)
138
+ https_proxy = ENV.fetch("HTTPS_PROXY", nil)
139
+ http_split = http_proxy&.split(":")
140
+ https_split = https_proxy&.split(":")
141
+ http_proxy_host = http_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
142
+ https_proxy_host = https_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
143
+ http_proxy_port = http_split&.fetch(2) || "1080"
144
+ https_proxy_port = https_split&.fetch(2) || "1080"
145
+ properties_content = "
146
+ systemProp.http.proxyHost=#{http_proxy_host}
147
+ systemProp.http.proxyPort=#{http_proxy_port}
148
+ systemProp.https.proxyHost=#{https_proxy_host}
149
+ systemProp.https.proxyPort=#{https_proxy_port}"
150
+ File.write(file_name, properties_content)
151
+ end
152
+
153
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
154
+ attr_reader :dependency_files
155
+
156
+ sig { returns(Dependabot::Dependency) }
157
+ attr_reader :dependency
158
+ end
159
+ end
160
+ end
161
+ end
@@ -15,6 +15,7 @@ module Dependabot
15
15
  require_relative "file_updater/dependency_set_updater"
16
16
  require_relative "file_updater/property_value_updater"
17
17
  require_relative "file_updater/lockfile_updater"
18
+ require_relative "file_updater/wrapper_updater"
18
19
 
19
20
  SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts gradle.lockfile).freeze
20
21
 
@@ -57,6 +58,7 @@ module Dependabot
57
58
  # rubocop:disable Metrics/AbcSize
58
59
  # rubocop:disable Metrics/CyclomaticComplexity
59
60
  # rubocop:disable Metrics/PerceivedComplexity
61
+ # rubocop:disable Metrics/MethodLength
60
62
  sig do
61
63
  params(buildfiles: T::Array[Dependabot::DependencyFile], dependency: Dependabot::Dependency)
62
64
  .returns(T::Array[Dependabot::DependencyFile])
@@ -64,6 +66,10 @@ module Dependabot
64
66
  def update_buildfiles_for_dependency(buildfiles:, dependency:)
65
67
  files = buildfiles.dup
66
68
 
69
+ # dependencies may have multiple requirements targeting the same file or build dir
70
+ # we keep the last one by path to later run its native helpers
71
+ buildfiles_processed = T.let({}, T::Hash[String, Dependabot::DependencyFile])
72
+
67
73
  # The UpdateChecker ensures the order of requirements is preserved
68
74
  # when updating, so we can zip them together in new/old pairs.
69
75
  reqs = dependency.requirements.zip(T.must(dependency.previous_requirements))
@@ -93,17 +99,28 @@ module Dependabot
93
99
  files[T.must(files.index(buildfile))] = update_version_in_buildfile(dependency, buildfile, old_req, new_req)
94
100
  end
95
101
 
96
- next unless Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
97
-
98
- lockfile_updater = LockfileUpdater.new(dependency_files: files)
99
- lockfiles = lockfile_updater.update_lockfiles(buildfile)
100
- lockfiles.each do |lockfile|
101
- existing_file = files.find { |f| f.name == lockfile.name && f.directory == lockfile.directory }
102
- if existing_file.nil?
103
- files << lockfile
104
- else
105
- files[T.must(files.index(existing_file))] = lockfile
106
- end
102
+ buildfiles_processed[buildfile.name] = buildfile
103
+ end
104
+
105
+ # runs native updaters (e.g. wrapper, lockfile) on relevant build files updated
106
+ updated_files = T.let([], T::Array[Dependabot::DependencyFile])
107
+ buildfiles_processed.each do |_, buildfile|
108
+ if Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
109
+ lockfile_updater = LockfileUpdater.new(dependency_files: files)
110
+ updated_files += lockfile_updater.update_lockfiles(buildfile)
111
+ end
112
+ if Dependabot::Experiments.enabled?(:gradle_wrapper_updater)
113
+ wrapper_updater = WrapperUpdater.new(dependency_files: files, dependency: dependency)
114
+ updated_files += wrapper_updater.update_files(buildfile)
115
+ end
116
+ end
117
+
118
+ updated_files.each do |file|
119
+ existing_file = files.find { |f| f.name == file.name && f.directory == file.directory }
120
+ if existing_file.nil?
121
+ files << file
122
+ else
123
+ files[T.must(files.index(existing_file))] = file
107
124
  end
108
125
  end
109
126
 
@@ -112,6 +129,7 @@ module Dependabot
112
129
  # rubocop:enable Metrics/PerceivedComplexity
113
130
  # rubocop:enable Metrics/CyclomaticComplexity
114
131
  # rubocop:enable Metrics/AbcSize
132
+ # rubocop:enable Metrics/MethodLength
115
133
 
116
134
  sig do
117
135
  params(
@@ -191,6 +209,7 @@ module Dependabot
191
209
  end
192
210
 
193
211
  # rubocop:disable Metrics/AbcSize
212
+ # rubocop:disable Metrics/PerceivedComplexity
194
213
  sig do
195
214
  params(
196
215
  dependency: Dependabot::Dependency,
@@ -209,6 +228,10 @@ module Dependabot
209
228
  if dependency.name.include?(":")
210
229
  dep_parts = dependency.name.split(":")
211
230
  next false unless line.include?(T.must(dep_parts.first)) || line.include?(T.must(dep_parts.last))
231
+ elsif T.let(requirement.fetch(:file), String).end_with?(".properties")
232
+ property = T.let(requirement, T::Hash[Symbol, T.nilable(T::Hash[Symbol, T.nilable(String)])])
233
+ .dig(:source, :property)
234
+ next false unless !property.nil? && line.start_with?(property)
212
235
  elsif T.let(requirement.fetch(:file), String).end_with?(".toml")
213
236
  next false unless line.include?(dependency.name)
214
237
  else
@@ -221,6 +244,7 @@ module Dependabot
221
244
  end
222
245
  end
223
246
  # rubocop:enable Metrics/AbcSize
247
+ # rubocop:enable Metrics/PerceivedComplexity
224
248
 
225
249
  sig { params(string: String, buildfile: Dependabot::DependencyFile).returns(String) }
226
250
  def evaluate_properties(string, buildfile)
@@ -5,6 +5,7 @@ require "nokogiri"
5
5
  require "sorbet-runtime"
6
6
 
7
7
  require "dependabot/file_fetchers/base"
8
+ require "dependabot/gradle/distributions"
8
9
  require "dependabot/gradle/file_fetcher"
9
10
  require "dependabot/gradle/file_parser/repositories_finder"
10
11
  require "dependabot/maven/utils/auth_headers_finder"
@@ -25,6 +26,8 @@ module Dependabot
25
26
 
26
27
  sig { override.returns(T.nilable(Dependabot::Source)) }
27
28
  def look_up_source
29
+ return nil if Distributions.distribution_requirements?(dependency.requirements)
30
+
28
31
  tmp_source = look_up_source_in_pom(dependency_pom_file)
29
32
  return tmp_source if tmp_source
30
33
 
@@ -0,0 +1,67 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/gradle/version"
5
+ require "dependabot/gradle/distributions"
6
+ require "sorbet-runtime"
7
+
8
+ module Dependabot
9
+ module Gradle
10
+ module Package
11
+ class DistributionsFetcher
12
+ extend T::Sig
13
+
14
+ @available_versions = T.let([], T::Array[T::Hash[String, T.untyped]])
15
+ @distributions_checksums = T.let({}, T::Hash[String, T::Array[String]])
16
+
17
+ sig { returns(T.any(T::Array[T::Hash[String, T.untyped]], T::Array[T::Hash[Symbol, T.untyped]])) }
18
+ def self.available_versions
19
+ return @available_versions if @available_versions.any?
20
+
21
+ response = Dependabot::RegistryClient.get(url: "https://services.gradle.org/versions/all")
22
+ versions = T.let(
23
+ JSON.parse(
24
+ T.let(response.body, String),
25
+ object_class: OpenStruct
26
+ ),
27
+ T::Array[OpenStruct]
28
+ )
29
+ @available_versions +=
30
+ versions
31
+ .select { |v| release_version?(version: v) }
32
+ .uniq { |v| v[:version] }
33
+ .map do |v|
34
+ {
35
+ version: v[:version],
36
+ build_time: v[:buildTime]
37
+ }
38
+ end
39
+ end
40
+
41
+ sig { params(version: OpenStruct).returns(T::Boolean) }
42
+ def self.release_version?(version:)
43
+ Gradle::Version.correct?(T.let(version[:version], String)) &&
44
+ T.let(version[:broken], T::Boolean) == false &&
45
+ T.let(version[:snapshot], T::Boolean) == false &&
46
+ T.let(version[:rcFor], String) == "" &&
47
+ T.let(version[:milestoneFor], String) == "" &&
48
+ /.*-(rc|milestone)-.*/.match?(T.let(version[:version], String)) == false
49
+ end
50
+
51
+ sig { params(distribution_url: String).returns(T.nilable(T::Array[String])) }
52
+ def self.resolve_checksum(distribution_url)
53
+ cached = @distributions_checksums[distribution_url]
54
+ return cached if cached
55
+
56
+ checksum_url = "#{distribution_url}.sha256"
57
+ checksum = T.let(Dependabot::RegistryClient.get(url: checksum_url).body, String).strip
58
+ return nil unless checksum.match?(/\A[a-f0-9]{64}\z/)
59
+
60
+ @distributions_checksums[distribution_url] = [checksum_url, checksum]
61
+ end
62
+
63
+ private_class_method :release_version?
64
+ end
65
+ end
66
+ end
67
+ end
@@ -8,6 +8,7 @@ require "dependabot/gradle/file_parser/repositories_finder"
8
8
  require "dependabot/gradle/update_checker"
9
9
  require "dependabot/gradle/version"
10
10
  require "dependabot/gradle/requirement"
11
+ require "dependabot/gradle/distributions"
11
12
  require "dependabot/maven/utils/auth_headers_finder"
12
13
  require "sorbet-runtime"
13
14
  require "dependabot/gradle/metadata_finder"
@@ -65,6 +66,7 @@ module Dependabot
65
66
  repositories.map do |repository_details|
66
67
  url = repository_details.fetch("url")
67
68
 
69
+ next distribution_version_details if url == Gradle::Distributions::DISTRIBUTION_REPOSITORY_URL
68
70
  next google_version_details if url == Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO
69
71
 
70
72
  dependency_metadata(repository_details).css("versions > version")
@@ -83,7 +85,7 @@ module Dependabot
83
85
 
84
86
  package_releases << {
85
87
  version: Gradle::Version.new(version),
86
- released_at: release_date_info.none? ? nil : (release_date_info[version]&.fetch(:release_date) || nil),
88
+ released_at: info[:released_at] || release_date_info[version]&.fetch(:release_date),
87
89
  source_url: info[:source_url]
88
90
  }
89
91
  end
@@ -136,7 +138,9 @@ module Dependabot
136
138
  def repositories
137
139
  return @repositories if @repositories
138
140
 
139
- details = if plugin?
141
+ details = if distribution?
142
+ distribution_repository_details
143
+ elsif plugin?
140
144
  plugin_repository_details + credentials_repository_details
141
145
  else
142
146
  dependency_repository_details + credentials_repository_details
@@ -151,6 +155,27 @@ module Dependabot
151
155
  end
152
156
  end
153
157
 
158
+ sig { returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
159
+ def distribution_version_details
160
+ return nil unless Experiments.enabled?(:gradle_wrapper_updater)
161
+
162
+ DistributionsFetcher.available_versions.map do |info|
163
+ release_date = begin
164
+ Time.parse(info[:build_time])
165
+ rescue StandardError
166
+ nil
167
+ end
168
+
169
+ {
170
+ version: info[:version],
171
+ released_at: release_date,
172
+ source_url: Distributions::DISTRIBUTION_REPOSITORY_URL
173
+ }
174
+ end
175
+ rescue StandardError
176
+ nil
177
+ end
178
+
154
179
  sig { returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
155
180
  def google_version_details
156
181
  url = Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO
@@ -276,6 +301,14 @@ module Dependabot
276
301
  }] + dependency_repository_details
277
302
  end
278
303
 
304
+ sig { returns(T::Array[T::Hash[String, T.untyped]]) }
305
+ def distribution_repository_details
306
+ [{
307
+ "url" => Gradle::Distributions::DISTRIBUTION_REPOSITORY_URL,
308
+ "auth_headers" => {}
309
+ }]
310
+ end
311
+
279
312
  sig { params(comparison_version: T.untyped).returns(T::Boolean) }
280
313
  def matches_dependency_version_type?(comparison_version)
281
314
  return true unless dependency.version
@@ -336,6 +369,11 @@ module Dependabot
336
369
  plugin? && dependency.requirements.any? { |r| r.fetch(:groups).include? "kotlin" }
337
370
  end
338
371
 
372
+ sig { returns(T::Boolean) }
373
+ def distribution?
374
+ Distributions.distribution_requirements?(dependency.requirements)
375
+ end
376
+
339
377
  sig { returns(T::Array[String]) }
340
378
  def central_repo_urls
341
379
  central_url_without_protocol =
@@ -9,6 +9,8 @@
9
9
  require "sorbet-runtime"
10
10
 
11
11
  require "dependabot/requirements_updater/base"
12
+ require "dependabot/gradle/distributions"
13
+ require "dependabot/gradle/package/distributions_fetcher"
12
14
  require "dependabot/gradle/update_checker"
13
15
  require "dependabot/gradle/version"
14
16
  require "dependabot/gradle/requirement"
@@ -46,11 +48,13 @@ module Dependabot
46
48
  return unless latest_version
47
49
 
48
50
  @latest_version = T.let(version_class.new(latest_version), Version)
51
+ @is_distribution = T.let(Distributions.distribution_requirements?(requirements), T::Boolean)
49
52
  end
50
53
 
51
54
  sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
52
55
  def updated_requirements
53
56
  return requirements unless latest_version
57
+ return updated_distribution_requirements if @is_distribution
54
58
 
55
59
  # NOTE: Order is important here. The FileUpdater needs the updated
56
60
  # requirement at index `i` to correspond to the previous requirement
@@ -114,6 +118,38 @@ module Dependabot
114
118
  end
115
119
  end
116
120
 
121
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
122
+ def updated_distribution_requirements
123
+ return requirements unless Experiments.enabled?(:gradle_wrapper_updater)
124
+
125
+ distribution_url = T.let(nil, T.nilable(String))
126
+
127
+ requirements.map do |req|
128
+ source = req[:source]
129
+ next req unless source
130
+
131
+ case source[:property]
132
+ when "distributionUrl"
133
+ requirement = req[:requirement]
134
+ version = update_exact_requirement(requirement)
135
+ distribution_url = source[:url].gsub(requirement, version)
136
+
137
+ req.merge(
138
+ requirement: version,
139
+ source: source.merge(url: distribution_url)
140
+ )
141
+ when "distributionSha256Sum"
142
+ checksum_url, checksum = Gradle::Package::DistributionsFetcher.resolve_checksum(T.must(distribution_url))
143
+ req.merge(
144
+ requirement: checksum,
145
+ source: source.merge(url: checksum_url)
146
+ )
147
+ else
148
+ next req
149
+ end
150
+ end
151
+ end
152
+
117
153
  sig { override.returns(T::Class[Version]) }
118
154
  def version_class
119
155
  Gradle::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.344.1
4
+ version: 0.346.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.344.1
18
+ version: 0.346.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.344.1
25
+ version: 0.346.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.344.1
32
+ version: 0.346.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.344.1
39
+ version: 0.346.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -256,17 +256,21 @@ extensions: []
256
256
  extra_rdoc_files: []
257
257
  files:
258
258
  - lib/dependabot/gradle.rb
259
+ - lib/dependabot/gradle/distributions.rb
259
260
  - lib/dependabot/gradle/file_fetcher.rb
260
261
  - lib/dependabot/gradle/file_fetcher/settings_file_parser.rb
261
262
  - lib/dependabot/gradle/file_parser.rb
263
+ - lib/dependabot/gradle/file_parser/distributions_finder.rb
262
264
  - lib/dependabot/gradle/file_parser/property_value_finder.rb
263
265
  - lib/dependabot/gradle/file_parser/repositories_finder.rb
264
266
  - lib/dependabot/gradle/file_updater.rb
265
267
  - lib/dependabot/gradle/file_updater/dependency_set_updater.rb
266
268
  - lib/dependabot/gradle/file_updater/lockfile_updater.rb
267
269
  - lib/dependabot/gradle/file_updater/property_value_updater.rb
270
+ - lib/dependabot/gradle/file_updater/wrapper_updater.rb
268
271
  - lib/dependabot/gradle/language.rb
269
272
  - lib/dependabot/gradle/metadata_finder.rb
273
+ - lib/dependabot/gradle/package/distributions_fetcher.rb
270
274
  - lib/dependabot/gradle/package/package_details_fetcher.rb
271
275
  - lib/dependabot/gradle/package_manager.rb
272
276
  - lib/dependabot/gradle/requirement.rb
@@ -280,7 +284,7 @@ licenses:
280
284
  - MIT
281
285
  metadata:
282
286
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
283
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.344.1
287
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.346.0
284
288
  rdoc_options: []
285
289
  require_paths:
286
290
  - lib