Skip to content

Commit 321013e

Browse files
committed
Updating Gradle binaries support
1 parent 2a64b10 commit 321013e

8 files changed

Lines changed: 290 additions & 151 deletions

File tree

.github/workflows/smoke.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ concurrency:
1515
env:
1616
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1717
SMOKE_TEST_OWNER: gmazzo
18-
SMOKE_TEST_BRANCH: gradle-wrapper-support
18+
SMOKE_TEST_BRANCH: gradle-wrapper-support-binaries
1919
jobs:
2020
discover:
2121
runs-on: ubuntu-latest

gradle/lib/dependabot/gradle/file_fetcher.rb

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ class FileFetcher < Dependabot::FileFetchers::Base
2323
SUPPORTED_SETTINGS_FILE_NAMES =
2424
T.let(%w(settings.gradle settings.gradle.kts).freeze, T::Array[String])
2525

26-
SUPPORTED_WRAPPER_PROPERTIES_FILE_PATH =
27-
%w(gradle/wrapper/gradle-wrapper.properties).freeze
26+
SUPPORTED_WRAPPER_FILES_PATH = %w(
27+
gradlew
28+
gradlew.bat
29+
gradle/wrapper/gradle-wrapper.jar
30+
gradle/wrapper/gradle-wrapper.properties
31+
).freeze
2832

2933
# For now Gradle only supports library .toml files in the main gradle folder
3034
SUPPORTED_VERSION_CATALOG_FILE_PATH =
@@ -69,13 +73,9 @@ def fetch_files
6973

7074
sig { params(root_dir: String).returns(T::Array[DependencyFile]) }
7175
def all_buildfiles_in_build(root_dir)
72-
files = [
73-
buildfile(root_dir),
74-
settings_file(root_dir),
75-
wrapper_properties_file(root_dir),
76-
version_catalog_file(root_dir),
77-
lockfile(root_dir)
78-
].compact
76+
files = [buildfile(root_dir), settings_file(root_dir), version_catalog_file(root_dir), lockfile(root_dir)]
77+
.compact
78+
files += wrapper_files(root_dir)
7979
files += subproject_buildfiles(root_dir)
8080
files += subproject_lockfiles(root_dir)
8181
files += dependency_script_plugins(root_dir)
@@ -147,12 +147,14 @@ def subproject_buildfiles(root_dir)
147147
end
148148
end
149149

150-
sig { params(root_dir: String).returns(T.nilable(DependencyFile)) }
151-
def wrapper_properties_file(root_dir)
152-
gradle_wrapper_properties_file(root_dir)
153-
rescue Dependabot::DependencyFileNotFound
154-
# Wrapper file is optional for Gradle
155-
nil
150+
sig { params(root_dir: String).returns(T::Array[DependencyFile]) }
151+
def wrapper_files(root_dir)
152+
SUPPORTED_WRAPPER_FILES_PATH.filter_map do |path|
153+
find_first(root_dir, [path])
154+
rescue Dependabot::DependencyFileNotFound
155+
# Wrapper files are optional for Gradle
156+
nil
157+
end
156158
end
157159

158160
sig { params(root_dir: String).returns(T.nilable(DependencyFile)) }
@@ -207,11 +209,6 @@ def buildfile(dir)
207209
file
208210
end
209211

210-
sig { params(dir: String).returns(T.nilable(DependencyFile)) }
211-
def gradle_wrapper_properties_file(dir)
212-
find_first(dir, SUPPORTED_WRAPPER_PROPERTIES_FILE_PATH)
213-
end
214-
215212
sig { params(dir: String).returns(T.nilable(DependencyFile)) }
216213
def gradle_toml_file(dir)
217214
find_first(dir, SUPPORTED_VERSION_CATALOG_FILE_PATH)

gradle/lib/dependabot/gradle/file_updater.rb

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class FileUpdater < Dependabot::FileUpdaters::Base
1515
require_relative "file_updater/dependency_set_updater"
1616
require_relative "file_updater/property_value_updater"
1717
require_relative "file_updater/lockfile_updater"
18+
require_relative "file_updater/wrapper_updater"
1819

1920
SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts gradle.lockfile).freeze
2021

@@ -23,8 +24,9 @@ def self.updated_files_regex
2324
[
2425
# Matches build.gradle or build.gradle.kts in root directory
2526
%r{(^|.*/)build\.gradle(\.kts)?$},
26-
# Matches gradle/wrapper/gradle-wrapper.properties in root or any subdirectory
27-
%r{(^|.*/)?gradle/wrapper/gradle-wrapper.properties$},
27+
# Matches gradle-wrapper files in root or any subdirectory
28+
%r{(^|.*/)?gradlew(\.bat)?$},
29+
%r{(^|.*/)?gradle/wrapper/gradle-wrapper\.(properties|jar)$},
2830
# Matches gradle/libs.versions.toml in root or any subdirectory
2931
%r{(^|.*/)?gradle/libs\.versions\.toml$},
3032
# Matches settings.gradle or settings.gradle.kts in root or any subdirectory
@@ -81,6 +83,10 @@ def original_file
8183
def update_buildfiles_for_dependency(buildfiles:, dependency:)
8284
files = buildfiles.dup
8385

86+
# dependencies may have multiple requirements targeting the same file or build dir
87+
# we keep the last one by path to later run its native helpers
88+
buildfiles_processed = T.let({}, T::Hash[String, Dependabot::DependencyFile])
89+
8490
# The UpdateChecker ensures the order of requirements is preserved
8591
# when updating, so we can zip them together in new/old pairs.
8692
reqs = dependency.requirements.zip(T.must(dependency.previous_requirements))
@@ -110,16 +116,20 @@ def update_buildfiles_for_dependency(buildfiles:, dependency:)
110116
files[T.must(files.index(buildfile))] = update_version_in_buildfile(dependency, buildfile, old_req, new_req)
111117
end
112118

113-
next unless Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
119+
buildfiles_processed[buildfile.name] = buildfile
120+
end
121+
122+
# runs native updaters (e.g. wrapper, lockfile) on relevant build files updated
123+
updaters = native_updaters(files, dependency)
124+
buildfiles_processed.each do |_, buildfile|
125+
updated_files = updaters.flat_map { |updater| updater.update_files(buildfile) }
114126

115-
lockfile_updater = LockfileUpdater.new(dependency_files: files)
116-
lockfiles = lockfile_updater.update_lockfiles(buildfile)
117-
lockfiles.each do |lockfile|
118-
existing_file = files.find { |f| f.name == lockfile.name && f.directory == lockfile.directory }
127+
updated_files.each do |file|
128+
existing_file = files.find { |f| f.name == file.name && f.directory == file.directory }
119129
if existing_file.nil?
120-
files << lockfile
130+
files << file
121131
else
122-
files[T.must(files.index(existing_file))] = lockfile
132+
files[T.must(files.index(existing_file))] = file
123133
end
124134
end
125135
end
@@ -130,6 +140,18 @@ def update_buildfiles_for_dependency(buildfiles:, dependency:)
130140
# rubocop:enable Metrics/CyclomaticComplexity
131141
# rubocop:enable Metrics/AbcSize
132142

143+
sig do
144+
params(files: T::Array[Dependabot::DependencyFile],
145+
dependency: Dependabot::Dependency).returns(T::Array[GradleUpdaterBase])
146+
end
147+
def native_updaters(files, dependency)
148+
updaters = T.let([], T::Array[GradleUpdaterBase])
149+
updaters << LockfileUpdater.new(dependency_files: files) if Experiments.enabled?(:gradle_lockfile_updater)
150+
updaters << WrapperUpdater.new(dependency_files: files, dependency: dependency) if
151+
Experiments.enabled?(:gradle_wrapper_updater)
152+
updaters
153+
end
154+
133155
sig do
134156
params(
135157
buildfiles: T::Array[Dependabot::DependencyFile],
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# typed: strong
2+
# frozen_string_literal: true
3+
4+
require "sorbet-runtime"
5+
require "shellwords"
6+
7+
require "dependabot/gradle/file_parser"
8+
require "dependabot/gradle/file_updater"
9+
10+
module Dependabot
11+
module Gradle
12+
class FileUpdater
13+
class GradleUpdaterBase
14+
extend T::Sig
15+
16+
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
17+
def initialize(dependency_files:)
18+
@dependency_files = dependency_files
19+
end
20+
21+
sig { params(_file: Dependabot::DependencyFile).returns(T::Boolean) }
22+
def target_file?(_file)
23+
raise NotImplementedError
24+
end
25+
26+
sig { returns(T::Array[String]) }
27+
def command_args
28+
raise NotImplementedError
29+
end
30+
31+
sig { params(temp_dir: T.any(Pathname, String), build_file: Dependabot::DependencyFile).returns(String) }
32+
def working_dir(temp_dir, build_file)
33+
File.dirname(File.join(temp_dir, build_file.directory, build_file.name))
34+
end
35+
36+
sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
37+
def update_files(build_file)
38+
local_files = dependency_files.select do |file|
39+
file.directory == build_file.directory && target_file?(file)
40+
end
41+
42+
# If we don't have any files in the build files don't generate one
43+
return dependency_files unless local_files.any?
44+
45+
updated_files = dependency_files.dup
46+
SharedHelpers.in_a_temporary_directory do |temp_dir|
47+
populate_temp_directory(temp_dir, @dependency_files)
48+
cwd = working_dir(temp_dir, build_file)
49+
50+
# Create gradle.properties file with proxy settings
51+
# Would prefer to use command line arguments, but they don't work.
52+
properties_filename = File.join(cwd, "gradle.properties")
53+
write_properties_file(properties_filename)
54+
55+
command = Shellwords.join(%w(gradle --no-daemon) + command_args)
56+
57+
Dir.chdir(cwd) do
58+
SharedHelpers.run_shell_command(command, cwd: cwd)
59+
update_files_content(temp_dir, local_files, updated_files)
60+
rescue SharedHelpers::HelperSubprocessFailed => e
61+
puts "Failed to update files: #{e.message}"
62+
return updated_files
63+
end
64+
end
65+
updated_files
66+
end
67+
68+
private
69+
70+
sig do
71+
params(
72+
temp_dir: T.any(Pathname, String),
73+
local_files: T::Array[Dependabot::DependencyFile],
74+
updated_files: T::Array[Dependabot::DependencyFile]
75+
).void
76+
end
77+
def update_files_content(temp_dir, local_files, updated_files)
78+
local_files.each do |file|
79+
f_content = File.read(File.join(temp_dir, file.directory, file.name))
80+
tmp_file = file.dup
81+
tmp_file.content = f_content
82+
updated_files[T.must(updated_files.index(file))] = tmp_file
83+
end
84+
end
85+
86+
sig { params(temp_dir: T.any(Pathname, String), files: T::Array[Dependabot::DependencyFile]).void }
87+
def populate_temp_directory(temp_dir, files)
88+
files.each do |file|
89+
in_path_name = File.join(temp_dir, file.directory, file.name)
90+
FileUtils.mkdir_p(File.dirname(in_path_name))
91+
File.write(in_path_name, file.content)
92+
end
93+
end
94+
95+
sig { params(file_name: String).void }
96+
def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
97+
http_proxy = ENV.fetch("HTTP_PROXY", nil)
98+
https_proxy = ENV.fetch("HTTPS_PROXY", nil)
99+
http_split = http_proxy&.split(":")
100+
https_split = https_proxy&.split(":")
101+
http_proxy_host = http_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
102+
https_proxy_host = https_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
103+
http_proxy_port = http_split&.fetch(2) || "1080"
104+
https_proxy_port = https_split&.fetch(2) || "1080"
105+
properties_content = "
106+
systemProp.http.proxyHost=#{http_proxy_host}
107+
systemProp.http.proxyPort=#{http_proxy_port}
108+
systemProp.https.proxyHost=#{https_proxy_host}
109+
systemProp.https.proxyPort=#{https_proxy_port}"
110+
File.write(file_name, properties_content)
111+
end
112+
113+
sig { returns(T::Array[Dependabot::DependencyFile]) }
114+
attr_reader :dependency_files
115+
end
116+
end
117+
end
118+
end

gradle/lib/dependabot/gradle/file_updater/lockfile_updater.rb

Lines changed: 8 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -4,106 +4,23 @@
44
require "sorbet-runtime"
55
require "shellwords"
66

7-
require "dependabot/gradle/file_parser"
8-
require "dependabot/gradle/file_updater"
7+
require "dependabot/gradle/file_updater/gradle_updater_base"
98

109
module Dependabot
1110
module Gradle
1211
class FileUpdater
13-
class LockfileUpdater
12+
class LockfileUpdater < GradleUpdaterBase
1413
extend T::Sig
1514

16-
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
17-
def initialize(dependency_files:)
18-
@dependency_files = dependency_files
15+
sig { override.params(file: Dependabot::DependencyFile).returns(T::Boolean) }
16+
def target_file?(file)
17+
file.name.end_with?(".lockfile")
1918
end
2019

21-
sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
22-
def update_lockfiles(build_file)
23-
local_lockfiles = dependency_files.select do |file|
24-
file.directory == build_file.directory && file.name.end_with?(".lockfile")
25-
end
26-
27-
# If we don't have any lockfiles in the build files don't generate one
28-
return dependency_files unless local_lockfiles.any?
29-
30-
updated_files = dependency_files.dup
31-
SharedHelpers.in_a_temporary_directory do |temp_dir|
32-
populate_temp_directory(temp_dir)
33-
cwd = File.join(temp_dir, build_file.directory, build_file.name)
34-
cwd = File.dirname(cwd)
35-
36-
# Create gradle.properties file with proxy settings
37-
# Would prefer to use command line arguments, but they don't work.
38-
properties_filename = File.join(temp_dir, build_file.directory, "gradle.properties")
39-
write_properties_file(properties_filename)
40-
41-
command_parts = [
42-
"gradle",
43-
"dependencies",
44-
"--no-daemon",
45-
"--write-locks"
46-
]
47-
command = Shellwords.join(command_parts)
48-
49-
Dir.chdir(cwd) do
50-
SharedHelpers.run_shell_command(command, cwd: cwd)
51-
update_lockfiles_content(temp_dir, local_lockfiles, updated_files)
52-
rescue SharedHelpers::HelperSubprocessFailed => e
53-
puts "Failed to update lockfiles: #{e.message}"
54-
return updated_files
55-
end
56-
end
57-
updated_files
58-
end
59-
60-
sig do
61-
params(
62-
temp_dir: T.any(Pathname, String),
63-
local_lockfiles: T::Array[Dependabot::DependencyFile],
64-
updated_lockfiles: T::Array[Dependabot::DependencyFile]
65-
).void
20+
sig { override.returns(T::Array[String]) }
21+
def command_args
22+
%w(dependencies --write-locks)
6623
end
67-
def update_lockfiles_content(temp_dir, local_lockfiles, updated_lockfiles)
68-
local_lockfiles.each do |file|
69-
f_content = File.read(File.join(temp_dir, file.directory, file.name))
70-
tmp_file = file.dup
71-
tmp_file.content = f_content
72-
updated_lockfiles[T.must(updated_lockfiles.index(file))] = tmp_file
73-
end
74-
end
75-
76-
sig { params(temp_dir: T.any(Pathname, String)).void }
77-
def populate_temp_directory(temp_dir)
78-
@dependency_files.each do |file|
79-
in_path_name = File.join(temp_dir, file.directory, file.name)
80-
FileUtils.mkdir_p(File.dirname(in_path_name))
81-
File.write(in_path_name, file.content)
82-
end
83-
end
84-
85-
sig { params(file_name: String).void }
86-
def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
87-
http_proxy = ENV.fetch("HTTP_PROXY", nil)
88-
https_proxy = ENV.fetch("HTTPS_PROXY", nil)
89-
http_split = http_proxy&.split(":")
90-
https_split = https_proxy&.split(":")
91-
http_proxy_host = http_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
92-
https_proxy_host = https_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
93-
http_proxy_port = http_split&.fetch(2) || "1080"
94-
https_proxy_port = https_split&.fetch(2) || "1080"
95-
properties_content = "
96-
systemProp.http.proxyHost=#{http_proxy_host}
97-
systemProp.http.proxyPort=#{http_proxy_port}
98-
systemProp.https.proxyHost=#{https_proxy_host}
99-
systemProp.https.proxyPort=#{https_proxy_port}"
100-
File.write(file_name, properties_content)
101-
end
102-
103-
private
104-
105-
sig { returns(T::Array[Dependabot::DependencyFile]) }
106-
attr_reader :dependency_files
10724
end
10825
end
10926
end

0 commit comments

Comments
 (0)