Skip to content

Commit fbb6910

Browse files
committed
Refactor Gradle wrapper updater to improve command execution and error handling
- Consolidate wrapper update logic into dedicated methods for clarity. - Ensure both wrapper update commands are executed in sequence. - Add error handling for wrapper task failures, raising DependencyFileNotResolvable. - Update specs to verify command execution and handle wrapper updates correctly.
1 parent 7448483 commit fbb6910

2 files changed

Lines changed: 257 additions & 41 deletions

File tree

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

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -42,39 +42,75 @@ def update_files(build_file)
4242
# We only run this updater if it's a distribution dependency
4343
return [] unless Distributions.distribution_requirements?(dependency.requirements)
4444

45-
local_files = dependency_files.select do |file|
46-
file.directory == build_file.directory && target_file?(file)
47-
end
45+
# Find all wrapper files - they can span multiple directories
46+
local_files = dependency_files.select { |file| target_file?(file) }
4847

4948
# If we don't have any files in the build files don't generate one
50-
return [] unless local_files.any?
49+
return dependency_files unless local_files.any?
5150

5251
updated_files = dependency_files.dup
5352
SharedHelpers.in_a_temporary_directory do |temp_dir|
5453
populate_temp_directory(temp_dir)
55-
cwd = File.join(temp_dir, base_path(build_file))
56-
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)
64-
65-
Dir.chdir(cwd) do
66-
SharedHelpers.run_shell_command(command, cwd: cwd)
67-
update_files_content(temp_dir, local_files, updated_files)
68-
rescue SharedHelpers::HelperSubprocessFailed => e
69-
puts "Failed to update files: #{e.message}"
70-
return updated_files
71-
end
54+
update_wrapper_files(build_file, temp_dir, local_files, updated_files)
7255
end
7356
updated_files
7457
end
7558

7659
private
7760

61+
sig do
62+
params(
63+
build_file: Dependabot::DependencyFile,
64+
temp_dir: T.any(Pathname, String),
65+
local_files: T::Array[Dependabot::DependencyFile],
66+
updated_files: T::Array[Dependabot::DependencyFile]
67+
).void
68+
end
69+
def update_wrapper_files(build_file, temp_dir, local_files, updated_files)
70+
cwd = File.join(temp_dir, base_path(build_file))
71+
72+
# Create gradle.properties file with proxy settings
73+
properties_filename = File.join(temp_dir, build_file.directory, "gradle.properties")
74+
write_properties_file(properties_filename)
75+
76+
Dir.chdir(cwd) do
77+
run_wrapper_tasks(cwd)
78+
update_file_contents(temp_dir, local_files, updated_files)
79+
rescue SharedHelpers::HelperSubprocessFailed => e
80+
handle_wrapper_update_error(e)
81+
end
82+
end
83+
84+
sig { params(cwd: String).void }
85+
def run_wrapper_tasks(cwd)
86+
gradlew_script = File.exist?("gradlew.bat") && !File.exist?("gradlew") ? "gradlew.bat" : "./gradlew"
87+
88+
# First run: Update wrapper with new version and download new distribution
89+
first_command = Shellwords.join([gradlew_script, "--no-daemon", "--stacktrace"] + command_args)
90+
SharedHelpers.run_shell_command(first_command, cwd: cwd)
91+
92+
# Second run: Regenerate wrapper binaries (gradlew, gradlew.bat, gradle-wrapper.jar)
93+
second_command = Shellwords.join([gradlew_script, "--no-daemon", "--stacktrace", "wrapper"])
94+
SharedHelpers.run_shell_command(second_command, cwd: cwd)
95+
end
96+
97+
sig do
98+
params(
99+
temp_dir: T.any(Pathname, String),
100+
local_files: T::Array[Dependabot::DependencyFile],
101+
updated_files: T::Array[Dependabot::DependencyFile]
102+
).void
103+
end
104+
def update_file_contents(temp_dir, local_files, updated_files)
105+
local_files.each do |file|
106+
file_path = File.join(temp_dir, file.directory, file.name)
107+
f_content = file.binary? ? File.binread(file_path) : File.read(file_path)
108+
tmp_file = file.dup
109+
tmp_file.content = file.binary? ? Base64.encode64(f_content) : f_content
110+
updated_files[T.must(updated_files.index(file))] = tmp_file
111+
end
112+
end
113+
78114
sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
79115
def target_file?(file)
80116
@target_files.any? { |r| "/#{file.name}".end_with?(r) }
@@ -110,31 +146,32 @@ def base_path(build_file)
110146
File.dirname(File.join(build_file.directory, build_file.name)).delete_suffix("/gradle/wrapper")
111147
end
112148

113-
sig do
114-
params(
115-
temp_dir: T.any(Pathname, String),
116-
local_files: T::Array[Dependabot::DependencyFile],
117-
updated_files: T::Array[Dependabot::DependencyFile]
118-
).void
119-
end
120-
def update_files_content(temp_dir, local_files, updated_files)
121-
local_files.each do |file|
122-
f_content = File.read(File.join(temp_dir, file.directory, file.name))
123-
tmp_file = file.dup
124-
tmp_file.content = tmp_file.binary? ? Base64.encode64(f_content) : f_content
125-
updated_files[T.must(updated_files.index(file))] = tmp_file
126-
end
127-
end
128-
129149
sig { params(temp_dir: T.any(Pathname, String)).void }
130150
def populate_temp_directory(temp_dir)
131151
files_to_populate.each do |file|
132152
in_path_name = File.join(temp_dir, file.directory, file.name)
133153
FileUtils.mkdir_p(File.dirname(in_path_name))
134-
File.write(in_path_name, file.content)
154+
155+
# Write file content - binary files need special handling
156+
if file.binary?
157+
File.binwrite(in_path_name, Base64.decode64(T.must(file.content)))
158+
else
159+
File.write(in_path_name, file.content)
160+
end
161+
162+
# Make gradlew scripts executable so they can be run
163+
FileUtils.chmod(0o755, in_path_name) if file.name.end_with?("gradlew", "gradlew.bat")
135164
end
136165
end
137166

167+
sig { params(error: SharedHelpers::HelperSubprocessFailed).returns(T.noreturn) }
168+
def handle_wrapper_update_error(error)
169+
# Gradle wrapper update failures typically indicate build compatibility issues
170+
# with the new Gradle version. Raise as DependencyFileNotResolvable so the
171+
# service layer can handle appropriately.
172+
raise Dependabot::DependencyFileNotResolvable, error.message
173+
end
174+
138175
sig { params(file_name: String).void }
139176
def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
140177
http_proxy = ENV.fetch("HTTP_PROXY", nil)

gradle/spec/dependabot/gradle/file_updater_spec.rb

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,20 +716,28 @@
716716
end
717717

718718
its(:content) do
719-
expected_command = "gradle --no-daemon --stacktrace wrapper --no-validate-url --gradle-version 9.0.0"
719+
# First command: Update wrapper with new version
720+
first_cmd = "./gradlew --no-daemon --stacktrace wrapper --no-validate-url " \
721+
"--gradle-version 9.0.0"
722+
# Second command: Regenerate wrapper binaries
723+
second_cmd = "./gradlew --no-daemon --stacktrace wrapper"
720724

721725
is_expected.to include(
722726
"distributionUrl=https\\://services.gradle.org/distributions/gradle-9.0.0-#{type}.zip"
723727
)
724728

725729
if checksum
726-
expected_command += " --gradle-distribution-sha256-sum #{updated_checksum}"
730+
first_cmd += " --gradle-distribution-sha256-sum #{updated_checksum}"
727731
is_expected.to include("distributionSha256Sum=#{updated_checksum}")
728732
else
729733
is_expected.not_to include("distributionSha256Sum=")
730734
end
731735

732-
expect(Dependabot::SharedHelpers).to have_received(:run_shell_command).with(expected_command, cwd: anything)
736+
# Verify both commands were called in sequence
737+
expect(Dependabot::SharedHelpers)
738+
.to have_received(:run_shell_command).with(first_cmd, cwd: anything).ordered
739+
expect(Dependabot::SharedHelpers)
740+
.to have_received(:run_shell_command).with(second_cmd, cwd: anything).ordered
733741
end
734742
end
735743

@@ -745,6 +753,177 @@
745753
"all",
746754
"443c9c8ee2ac1ee0e11881a40f2376d79c66386264a44b24a9f8ca67e633375f",
747755
"f759b8dd5204e2e3fa4ca3e73f452f087153cf81bac9561eeb854229cc2c5365"
756+
757+
context "when wrapper task fails" do
758+
let(:buildfile) { wrapper_file }
759+
let(:wrapper_file) do
760+
Dependabot::DependencyFile.new(
761+
name: "gradle/wrapper/gradle-wrapper.properties",
762+
content: fixture("wrapper_files", "gradle-wrapper-8.14.2-bin.properties")
763+
)
764+
end
765+
766+
let(:dependency) do
767+
Dependabot::Dependency.new(
768+
name: "gradle-wrapper",
769+
version: "9.0.0",
770+
previous_version: "8.14.2",
771+
requirements: [{
772+
file: "gradle/wrapper/gradle-wrapper.properties",
773+
requirement: "9.0.0",
774+
groups: [],
775+
source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" }
776+
}],
777+
previous_requirements: [{
778+
file: "gradle/wrapper/gradle-wrapper.properties",
779+
requirement: "8.14.2",
780+
groups: [],
781+
source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" }
782+
}],
783+
package_manager: "gradle"
784+
)
785+
end
786+
787+
before do
788+
allow(Dependabot::SharedHelpers).to receive(:run_shell_command)
789+
.and_raise(
790+
Dependabot::SharedHelpers::HelperSubprocessFailed.new(
791+
message: "Gradle wrapper task failed",
792+
error_context: {}
793+
)
794+
)
795+
end
796+
797+
it "raises DependencyFileNotResolvable" do
798+
expect { updated_files }.to raise_error(Dependabot::DependencyFileNotResolvable)
799+
end
800+
end
801+
802+
context "when updating a non-wrapper dependency" do
803+
let(:buildfile) do
804+
Dependabot::DependencyFile.new(
805+
name: "build.gradle",
806+
content: fixture("buildfiles", "basic_build.gradle")
807+
)
808+
end
809+
810+
let(:dependency_files) { [buildfile, wrapper_file] }
811+
812+
let(:wrapper_file) do
813+
Dependabot::DependencyFile.new(
814+
name: "gradle/wrapper/gradle-wrapper.properties",
815+
content: fixture("wrapper_files", "gradle-wrapper-8.14.2-bin.properties")
816+
)
817+
end
818+
819+
let(:dependency) do
820+
Dependabot::Dependency.new(
821+
name: "co.aikar:acf-paper",
822+
version: "0.6.0-SNAPSHOT",
823+
requirements: [{
824+
file: "build.gradle",
825+
requirement: "0.6.0-SNAPSHOT",
826+
groups: [],
827+
source: nil,
828+
metadata: nil
829+
}],
830+
previous_requirements: [{
831+
file: "build.gradle",
832+
requirement: "0.5.0-SNAPSHOT",
833+
groups: [],
834+
source: nil,
835+
metadata: nil
836+
}],
837+
package_manager: "gradle"
838+
)
839+
end
840+
841+
it "does not run wrapper updater" do
842+
# Should not call run_shell_command for wrapper tasks
843+
updated_files
844+
expect(Dependabot::SharedHelpers).not_to have_received(:run_shell_command)
845+
end
846+
847+
it "updates only the build file" do
848+
expect(updated_files.length).to eq(1)
849+
expect(updated_files.first.name).to eq("build.gradle")
850+
end
851+
end
852+
853+
context "with all wrapper files including binaries" do
854+
let(:buildfile) { wrapper_file }
855+
856+
let(:wrapper_file) do
857+
Dependabot::DependencyFile.new(
858+
name: "gradle/wrapper/gradle-wrapper.properties",
859+
content: fixture("wrapper_files", "gradle-wrapper-8.14.2-bin.properties")
860+
)
861+
end
862+
863+
let(:gradlew_file) do
864+
Dependabot::DependencyFile.new(
865+
name: "gradlew",
866+
content: "#!/bin/sh\necho 'gradlew script'"
867+
)
868+
end
869+
870+
let(:gradlew_bat_file) do
871+
Dependabot::DependencyFile.new(
872+
name: "gradlew.bat",
873+
content: "@echo off\necho gradlew.bat"
874+
)
875+
end
876+
877+
let(:jar_file) do
878+
file = Dependabot::DependencyFile.new(
879+
name: "gradle/wrapper/gradle-wrapper.jar",
880+
content: Base64.encode64("fake jar content")
881+
)
882+
file.content_encoding = Dependabot::DependencyFile::ContentEncoding::BASE64
883+
file
884+
end
885+
886+
let(:dependency_files) { [wrapper_file, gradlew_file, gradlew_bat_file, jar_file] }
887+
888+
let(:dependency) do
889+
Dependabot::Dependency.new(
890+
name: "gradle-wrapper",
891+
version: "9.0.0",
892+
previous_version: "8.14.2",
893+
requirements: [{
894+
file: "gradle/wrapper/gradle-wrapper.properties",
895+
requirement: "9.0.0",
896+
groups: [],
897+
source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" }
898+
}],
899+
previous_requirements: [{
900+
file: "gradle/wrapper/gradle-wrapper.properties",
901+
requirement: "8.14.2",
902+
groups: [],
903+
source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" }
904+
}],
905+
package_manager: "gradle"
906+
)
907+
end
908+
909+
before do
910+
allow(Dependabot::SharedHelpers).to receive(:run_shell_command)
911+
end
912+
913+
it "processes all wrapper files" do
914+
# When commands are mocked, files don't actually change content,
915+
# so they may be filtered out. The key is that the wrapper updater
916+
# processes all wrapper files by running the wrapper task.
917+
result = updated_files
918+
# At minimum, the properties file should be in the result
919+
expect(result.map(&:name)).to include("gradle/wrapper/gradle-wrapper.properties")
920+
end
921+
922+
it "runs wrapper task twice" do
923+
updated_files
924+
expect(Dependabot::SharedHelpers).to have_received(:run_shell_command).twice
925+
end
926+
end
748927
end
749928

750929
context "with a version catalog" do

0 commit comments

Comments
 (0)