Skip to content

Commit 77efa52

Browse files
authored
Fix: Gradle wrapper scripts not getting updated (#13579)
1 parent 6e8efc9 commit 77efa52

File tree

3 files changed

+137
-45
lines changed

3 files changed

+137
-45
lines changed

gradle/lib/dependabot/gradle/file_updater.rb

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ def original_file
5656
end
5757

5858
# rubocop:disable Metrics/AbcSize
59-
# rubocop:disable Metrics/CyclomaticComplexity
6059
# rubocop:disable Metrics/PerceivedComplexity
61-
# rubocop:disable Metrics/MethodLength
6260
sig do
6361
params(buildfiles: T::Array[Dependabot::DependencyFile], dependency: Dependabot::Dependency)
6462
.returns(T::Array[Dependabot::DependencyFile])
@@ -103,33 +101,42 @@ def update_buildfiles_for_dependency(buildfiles:, dependency:)
103101
end
104102

105103
# 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_wrapper_updater)
104+
if Dependabot::Experiments.enabled?(:gradle_wrapper_updater)
105+
buildfiles_processed.each_value do |buildfile|
109106
wrapper_updater = WrapperUpdater.new(dependency_files: files, dependency: dependency)
110-
updated_files += wrapper_updater.update_files(buildfile)
107+
updated_files = wrapper_updater.update_files(buildfile)
108+
replace_updated_files(files, updated_files)
111109
end
112-
if Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
110+
end
111+
if Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
112+
buildfiles_processed.each_value do |buildfile|
113113
lockfile_updater = LockfileUpdater.new(dependency_files: files)
114-
updated_files += lockfile_updater.update_lockfiles(buildfile)
114+
updated_files = lockfile_updater.update_lockfiles(buildfile)
115+
replace_updated_files(files, updated_files)
115116
end
116117
end
117118

119+
files
120+
end
121+
# rubocop:enable Metrics/PerceivedComplexity
122+
# rubocop:enable Metrics/AbcSize
123+
sig do
124+
params(
125+
files: T::Array[Dependabot::DependencyFile],
126+
updated_files: T::Array[Dependabot::DependencyFile]
127+
).returns(T::Array[Dependabot::DependencyFile])
128+
end
129+
def replace_updated_files(files, updated_files)
118130
updated_files.each do |file|
119-
existing_file = files.find { |f| f.name == file.name && f.directory == file.directory }
131+
existing_file = files.find { |f| f.name == file.name }
120132
if existing_file.nil?
121133
files << file
122134
else
123135
files[T.must(files.index(existing_file))] = file
124136
end
125137
end
126-
127138
files
128139
end
129-
# rubocop:enable Metrics/PerceivedComplexity
130-
# rubocop:enable Metrics/CyclomaticComplexity
131-
# rubocop:enable Metrics/AbcSize
132-
# rubocop:enable Metrics/MethodLength
133140

134141
sig do
135142
params(

gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def initialize(dependency_files:, dependency:)
3737
)
3838
end
3939

40+
# rubocop:disable Metrics/AbcSize
41+
# rubocop:disable Metrics/MethodLength
42+
# rubocop:disable Metrics/PerceivedComplexity
4043
sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
4144
def update_files(build_file)
4245
# We only run this updater if it's a distribution dependency
@@ -49,29 +52,57 @@ def update_files(build_file)
4952
# If we don't have any files in the build files don't generate one
5053
return [] unless local_files.any?
5154

55+
# we only run this updater if the build file has a requirement for this dependency
56+
target_requirements = dependency.requirements.select do |req|
57+
T.let(req[:file], String) == build_file.name
58+
end
59+
return [] unless target_requirements.any?
60+
5261
updated_files = dependency_files.dup
5362
SharedHelpers.in_a_temporary_directory do |temp_dir|
5463
populate_temp_directory(temp_dir)
5564
cwd = File.join(temp_dir, base_path(build_file))
5665

57-
# Create gradle.properties file with proxy settings
58-
# Would prefer to use command line arguments, but they don't work.
59-
properties_filename = File.join(temp_dir, build_file.directory, "gradle.properties")
60-
write_properties_file(properties_filename)
61-
62-
command_parts = %w(gradle --no-daemon --stacktrace) + command_args
63-
command = Shellwords.join(command_parts)
66+
has_local_script = File.exist?(File.join(cwd, "./gradlew"))
67+
command_parts = %w(--no-daemon --stacktrace) + command_args(target_requirements)
68+
command = Shellwords.join([has_local_script ? "./gradlew" : "gradle"] + command_parts)
6469

6570
Dir.chdir(cwd) do
66-
SharedHelpers.run_shell_command(command, cwd: cwd)
71+
FileUtils.chmod("+x", "./gradlew") if has_local_script
72+
73+
properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties")
74+
validate_option = get_validate_distribution_url_option(properties_file)
75+
env = { "JAVA_OPTS" => proxy_args.join(" ") } # set proxy for gradle execution
76+
77+
begin
78+
# first attempt: run the wrapper task via the local gradle wrapper (if present)
79+
# `gradle-wrapper.jar` might be too old to run on host's Java version
80+
SharedHelpers.run_shell_command(command, cwd: cwd, env: env)
81+
rescue SharedHelpers::HelperSubprocessFailed => e
82+
raise e unless has_local_script # already field with system one, there is no point to retry
83+
84+
Dependabot.logger.warn("Running #{command} failed, retrying first with system Gradle: #{e.message}")
85+
86+
# second attempt: run the wrapper task via system gradle and then retry via local wrapper
87+
system_command = Shellwords.join(["gradle"] + command_parts)
88+
SharedHelpers.run_shell_command(system_command, cwd: cwd, env: env) # run via system gradle
89+
SharedHelpers.run_shell_command(command, cwd: cwd, env: env) # retry via local wrapper
90+
end
91+
92+
# Restore previous validateDistributionUrl option if it existed
93+
override_validate_distribution_url_option(properties_file, validate_option)
94+
6795
update_files_content(temp_dir, local_files, updated_files)
6896
rescue SharedHelpers::HelperSubprocessFailed => e
69-
puts "Failed to update files: #{e.message}"
97+
Dependabot.logger.error("Failed to update files: #{e.message}")
7098
return updated_files
7199
end
72100
end
73101
updated_files
74102
end
103+
# rubocop:enable Metrics/AbcSize
104+
# rubocop:enable Metrics/MethodLength
105+
# rubocop:enable Metrics/PerceivedComplexity
75106

76107
private
77108

@@ -80,12 +111,20 @@ def target_file?(file)
80111
@target_files.any? { |r| "/#{file.name}".end_with?(r) }
81112
end
82113

83-
sig { returns(T::Array[String]) }
84-
def command_args
85-
version = T.let(dependency.requirements[0]&.[](:requirement), String)
86-
checksum = T.let(dependency.requirements[1]&.[](:requirement), String) if dependency.requirements.size > 1
87-
88-
args = %W(wrapper --no-validate-url --gradle-version #{version})
114+
sig { params(requirements: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Array[String]) }
115+
def command_args(requirements)
116+
version = T.let(requirements[0]&.[](:requirement), String)
117+
checksum = T.let(requirements[1]&.[](:requirement), String) if dependency.requirements.size > 1
118+
distribution_url = T.let(requirements[0]&.[](:source), T::Hash[Symbol, String])[:url]
119+
distribution_type = distribution_url&.match(/\b(bin|all)\b/)&.captures&.first
120+
121+
# --no-validate-url is required to bypass HTTP proxy issues when running ./gradlew
122+
# This prevents validation failures during the wrapper update process
123+
# Note: This temporarily sets validateDistributionUrl=false in gradle-wrapper.properties
124+
# The original value is restored after the wrapper task completes
125+
# see method `get_validate_distribution_url_option` for more details
126+
args = %W(wrapper --gradle-version #{version} --no-validate-url) # see
127+
args += %W(--distribution-type #{distribution_type}) if distribution_type
89128
args += %W(--gradle-distribution-sha256-sum #{checksum}) if checksum
90129
args
91130
end
@@ -135,8 +174,35 @@ def populate_temp_directory(temp_dir)
135174
end
136175
end
137176

138-
sig { params(file_name: String).void }
139-
def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
177+
# This is a consequence of the lack of proper proxy support in Gradle Wrapper
178+
# During the update process, Gradle Wrapper logic will try to validate the distribution URL
179+
# by performing an HTTP request. If the environment requires a proxy, this validation will fail
180+
# We need to add the `--no-validate-url` the commandline args to disable this validation
181+
# However, this change is persistent in the `gradle-wrapper.properties` file
182+
# To avoid side effects, we read the existing value before the update and restore it afterward
183+
sig { params(properties_file: T.any(Pathname, String)).returns(T.nilable(String)) }
184+
def get_validate_distribution_url_option(properties_file)
185+
return nil unless File.exist?(properties_file)
186+
187+
properties_content = File.read(properties_file)
188+
properties_content.match(/^validateDistributionUrl=(.*)$/)&.captures&.first
189+
end
190+
191+
sig { params(properties_file: T.any(Pathname, String), value: T.nilable(String)).void }
192+
def override_validate_distribution_url_option(properties_file, value)
193+
return unless File.exist?(properties_file)
194+
195+
properties_content = File.read(properties_file)
196+
updated_content = properties_content.gsub(
197+
/^validateDistributionUrl=(.*)\n/,
198+
value ? "validateDistributionUrl=#{value}\n" : ""
199+
)
200+
File.write(properties_file, updated_content)
201+
end
202+
203+
# rubocop:disable Metrics/PerceivedComplexity
204+
sig { returns(T::Array[String]) }
205+
def proxy_args
140206
http_proxy = ENV.fetch("HTTP_PROXY", nil)
141207
https_proxy = ENV.fetch("HTTPS_PROXY", nil)
142208
http_split = http_proxy&.split(":")
@@ -145,13 +211,15 @@ def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexi
145211
https_proxy_host = https_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
146212
http_proxy_port = http_split&.fetch(2) || "1080"
147213
https_proxy_port = https_split&.fetch(2) || "1080"
148-
properties_content = "
149-
systemProp.http.proxyHost=#{http_proxy_host}
150-
systemProp.http.proxyPort=#{http_proxy_port}
151-
systemProp.https.proxyHost=#{https_proxy_host}
152-
systemProp.https.proxyPort=#{https_proxy_port}"
153-
File.write(file_name, properties_content)
214+
215+
args = []
216+
args += %W(-Dhttp.proxyHost=#{http_proxy_host}) if http_proxy_host
217+
args += %W(-Dhttp.proxyPort=#{http_proxy_port}) if http_proxy_port
218+
args += %W(-Dhttps.proxyHost=#{https_proxy_host}) if https_proxy_host
219+
args += %W(-Dhttps.proxyPort=#{https_proxy_port}) if https_proxy_port
220+
args
154221
end
222+
# rubocop:enable Metrics/PerceivedComplexity
155223

156224
sig { returns(T::Array[Dependabot::DependencyFile]) }
157225
attr_reader :dependency_files

gradle/spec/dependabot/gradle/file_updater_spec.rb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -670,19 +670,23 @@
670670
)
671671
end
672672

673+
let(:distribution_url) do
674+
"https\\://services.gradle.org/distributions/gradle-9.0.0-#{type}.zip"
675+
end
676+
673677
let(:dependency) do
674678
requirements = [{
675679
file: "gradle/wrapper/gradle-wrapper.properties",
676680
requirement: "9.0.0",
677681
groups: [],
678-
source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" }
682+
source: { type: "gradle-distribution", url: distribution_url, property: "distributionUrl" }
679683
}]
680684
if checksum
681685
requirements << {
682686
file: "gradle/wrapper/gradle-wrapper.properties",
683687
requirement: updated_checksum,
684688
groups: [],
685-
source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionSha256Sum" }
689+
source: { type: "gradle-distribution", url: distribution_url, property: "distributionSha256Sum" }
686690
}
687691
end
688692

@@ -713,14 +717,23 @@
713717

714718
before do
715719
allow(Dependabot::SharedHelpers).to receive(:run_shell_command)
720+
allow(File).to receive(:exist?).and_return(true)
721+
allow(FileUtils).to receive(:chmod)
716722
end
717723

718724
its(:content) do
719-
expected_command = "gradle --no-daemon --stacktrace wrapper --no-validate-url --gradle-version 9.0.0"
720-
721-
is_expected.to include(
722-
"distributionUrl=https\\://services.gradle.org/distributions/gradle-9.0.0-#{type}.zip"
723-
)
725+
expected_command = %W(
726+
./gradlew --no-daemon --stacktrace wrapper --gradle-version 9.0.0 --no-validate-url
727+
--distribution-type #{type}
728+
).join(" ")
729+
expected_env = { "JAVA_OPTS" => %w(
730+
-Dhttp.proxyHost=host.docker.internal
731+
-Dhttp.proxyPort=1080
732+
-Dhttps.proxyHost=host.docker.internal
733+
-Dhttps.proxyPort=1080
734+
).join(" ") }
735+
736+
is_expected.to include("distributionUrl=#{distribution_url}")
724737

725738
if checksum
726739
expected_command += " --gradle-distribution-sha256-sum #{updated_checksum}"
@@ -729,7 +742,11 @@
729742
is_expected.not_to include("distributionSha256Sum=")
730743
end
731744

732-
expect(Dependabot::SharedHelpers).to have_received(:run_shell_command).with(expected_command, cwd: anything)
745+
expect(Dependabot::SharedHelpers).to have_received(:run_shell_command).with(
746+
expected_command,
747+
cwd: anything,
748+
env: expected_env
749+
)
733750
end
734751
end
735752

0 commit comments

Comments
 (0)