dependabot-gradle 0.375.0 → 0.376.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: 32cf71eceb94cce61d93df445d82e2a7c850239262817e12bb32951c40bb6993
4
- data.tar.gz: 8c284ae6607e962e164ec4b62f27f8c1e3e579c182f2c41b735ce6b8a3acd36a
3
+ metadata.gz: 927977ff944f07aab10b9338ca71c9540520dc07a97f3db8cf2aa9b27d325d9f
4
+ data.tar.gz: b618b2c6aaeb01de38041a829a0b1f650604df9791889323fe8066388b3e3aeb
5
5
  SHA512:
6
- metadata.gz: 1b8f8766524956880f2d857f5e4766d5caf0faf38535e83a445bb22eb8a302f01858586fe78d056594a4be5562b85733bf538f11fc637682b891649ce22580bf
7
- data.tar.gz: 41cc0d7e7791dffed1f27d7c280a0eff38d8eec076652da7dcce6a4d138e7d0ea8a98895250268b23dadf48fa3ff0cda7ed67d438b049b393073b420fbf2f9cb
6
+ metadata.gz: 648c6a0d23f8ae1f8026a10d19e66766a14c29be9e4fe3b46cc6386b0b24e82328e357b9504b40b61612322cddff3ab4ad786beca3a1d5d70dc9e42b321aebaf
7
+ data.tar.gz: 289b073da8a00a76bf0f2b636e8429d76a4472eba85057e4d4630b981b30554a674cbdd23879f3446a78f2a874903089ac00dfdd1f4de44072393245c7ec82e1
@@ -1,10 +1,10 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require "fileutils"
5
5
  require "shellwords"
6
+ require "sorbet-runtime"
6
7
 
7
- require "dependabot/gradle/file_parser"
8
8
  require "dependabot/gradle/file_updater"
9
9
 
10
10
  module Dependabot
@@ -13,6 +13,8 @@ module Dependabot
13
13
  class LockfileUpdater
14
14
  extend T::Sig
15
15
 
16
+ INIT_SCRIPT_TASK_NAME = T.let("dependabotResolveAll", String)
17
+
16
18
  sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
17
19
  def initialize(dependency_files:)
18
20
  @dependency_files = dependency_files
@@ -20,67 +22,159 @@ module Dependabot
20
22
 
21
23
  sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
22
24
  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
25
+ root_dir = determine_root_dir(build_file: build_file)
26
+ lockfiles = lockfiles_for_root(root_dir)
26
27
 
27
- # If we don't have any lockfiles in the build files don't generate one
28
- return dependency_files unless local_lockfiles.any?
28
+ return dependency_files unless lockfiles.any?
29
29
 
30
30
  updated_files = dependency_files.dup
31
+
31
32
  SharedHelpers.in_a_temporary_directory do |temp_dir|
32
33
  populate_temp_directory(temp_dir)
33
- cwd = File.join(temp_dir, build_file.directory, build_file.name)
34
- cwd = if build_file.path.end_with?("/gradle/libs.versions.toml")
35
- File.dirname(cwd, 2)
36
- else
37
- File.dirname(cwd)
38
- end
39
-
40
- # Create gradle.properties file with proxy settings
41
- # Would prefer to use command line arguments, but they don't work.
42
- properties_filename = File.join(temp_dir, build_file.directory, "gradle.properties")
43
- write_properties_file(properties_filename)
34
+
35
+ cwd = File.join(temp_dir, root_dir == "/" ? "" : root_dir.delete_prefix("/"))
36
+ FileUtils.mkdir_p(cwd)
37
+
38
+ write_properties_file(File.join(cwd, "gradle.properties"))
39
+
40
+ init_script_path = File.join(cwd, "dependabot-locking.init.gradle")
41
+ write_init_script(init_script_path)
44
42
 
45
43
  command_parts = [
46
44
  "gradle",
47
- "dependencies",
48
- "--no-daemon",
49
- "--write-locks"
45
+ "--init-script", init_script_path,
46
+ INIT_SCRIPT_TASK_NAME,
47
+ "--write-locks",
48
+ "--no-daemon"
50
49
  ]
51
50
  command = Shellwords.join(command_parts)
52
51
 
53
- Dir.chdir(cwd) do
54
- SharedHelpers.run_shell_command(command, cwd: cwd)
55
- update_lockfiles_content(temp_dir, local_lockfiles, updated_files)
56
- rescue SharedHelpers::HelperSubprocessFailed => e
57
- puts "Failed to update lockfiles: #{e.message}"
58
- return updated_files
59
- end
52
+ SharedHelpers.run_shell_command(command, cwd: cwd)
53
+
54
+ update_lockfiles_content(temp_dir, lockfiles, updated_files)
55
+ rescue SharedHelpers::HelperSubprocessFailed => e
56
+ Dependabot.logger.error("Failed to update lockfiles: #{e.message}")
57
+ return updated_files
60
58
  end
59
+
61
60
  updated_files
62
61
  end
63
62
 
63
+ sig { params(build_file: Dependabot::DependencyFile).returns(String) }
64
+ def determine_root_dir(build_file:)
65
+ settings_file = find_settings_file(build_file)
66
+ return normalized_directory_path(settings_file) if settings_file
67
+
68
+ file_path = normalized_file_path(build_file)
69
+ return normalize_path(File.dirname(file_path, 2)) if file_path.end_with?("/gradle/libs.versions.toml")
70
+
71
+ normalized_directory_path(build_file)
72
+ end
73
+
74
+ sig { params(file: Dependabot::DependencyFile).returns(String) }
75
+ def normalized_directory_path(file)
76
+ file_path = normalized_file_path(file)
77
+ dir = File.dirname(file_path)
78
+ dir == "/" ? "/" : normalize_path(dir)
79
+ end
80
+
81
+ sig { params(root_dir: String).returns(T::Array[Dependabot::DependencyFile]) }
82
+ def lockfiles_for_root(root_dir)
83
+ sub_build_roots = sub_build_roots_for(root_dir)
84
+
85
+ dependency_files.select do |file|
86
+ next false unless file.name.end_with?(".lockfile")
87
+
88
+ file_path = normalized_file_path(file)
89
+ next false unless path_under_root?(file_path, root_dir)
90
+
91
+ sub_build_roots.none? { |sub_root| file_path.start_with?("#{sub_root}/") || file_path == sub_root }
92
+ end
93
+ end
94
+
64
95
  sig do
65
96
  params(
66
97
  temp_dir: T.any(Pathname, String),
67
- local_lockfiles: T::Array[Dependabot::DependencyFile],
98
+ lockfiles: T::Array[Dependabot::DependencyFile],
68
99
  updated_lockfiles: T::Array[Dependabot::DependencyFile]
69
100
  ).void
70
101
  end
71
- def update_lockfiles_content(temp_dir, local_lockfiles, updated_lockfiles)
72
- local_lockfiles.each do |file|
73
- f_content = File.read(File.join(temp_dir, file.directory, file.name))
102
+ def update_lockfiles_content(temp_dir, lockfiles, updated_lockfiles)
103
+ lockfiles.each do |file|
104
+ # Handle "/" directory as root - File.join treats "/" as absolute path and ignores prior components
105
+ relative_dir = file.directory == "/" ? "" : file.directory
106
+ lockfile_path = File.join(temp_dir, relative_dir, file.name)
107
+
108
+ unless File.exist?(lockfile_path)
109
+ Dependabot.logger.warn(
110
+ "Lockfile #{file.name} was not regenerated by Gradle after a successful lockfile update run. " \
111
+ "Preserving existing lockfile."
112
+ )
113
+ next
114
+ end
115
+
116
+ content = File.read(lockfile_path)
117
+ next if content == file.content
118
+
74
119
  tmp_file = file.dup
75
- tmp_file.content = f_content
76
- updated_lockfiles[T.must(updated_lockfiles.index(file))] = tmp_file
120
+ tmp_file.content = content
121
+
122
+ index = updated_lockfiles.find_index { |f| f.name == file.name }
123
+ if index
124
+ updated_lockfiles[index] = tmp_file
125
+ else
126
+ updated_lockfiles << tmp_file
127
+ end
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
134
+ attr_reader :dependency_files
135
+
136
+ sig { params(file_path: String, root_dir: String).returns(T::Boolean) }
137
+ def path_under_root?(file_path, root_dir)
138
+ root_dir == "/" || file_path == root_dir || file_path.start_with?("#{root_dir}/")
139
+ end
140
+
141
+ # Find all sub-build roots (settings files deeper than root_dir) so we can
142
+ # exclude lockfiles that belong to an included/composite build.
143
+ sig { params(root_dir: String).returns(T::Array[String]) }
144
+ def sub_build_roots_for(root_dir)
145
+ dependency_files.filter_map do |f|
146
+ basename = File.basename(f.name)
147
+ next unless basename == "settings.gradle" || basename == "settings.gradle.kts"
148
+
149
+ dir = normalized_directory_path(f)
150
+ next if dir == root_dir
151
+
152
+ dir if path_under_root?(dir, root_dir)
77
153
  end
78
154
  end
79
155
 
156
+ sig { params(file: Dependabot::DependencyFile).returns(String) }
157
+ def normalized_file_path(file)
158
+ # Handle "/" directory as root - File.join treats "/" as absolute path and ignores prior components
159
+ relative_dir = file.directory == "/" ? "" : file.directory
160
+ path = relative_dir.empty? ? file.name : File.join(relative_dir, file.name)
161
+ normalize_path(path)
162
+ end
163
+
164
+ sig { params(path: String).returns(String) }
165
+ def normalize_path(path)
166
+ normalized = path.squeeze("/")
167
+ normalized = "/#{normalized}" unless normalized.start_with?("/")
168
+ normalized = normalized.sub(%r{/$}, "")
169
+ normalized.empty? ? "/" : normalized
170
+ end
171
+
80
172
  sig { params(temp_dir: T.any(Pathname, String)).void }
81
173
  def populate_temp_directory(temp_dir)
82
174
  @dependency_files.each do |file|
83
- in_path_name = File.join(temp_dir, file.directory, file.name)
175
+ # Handle "/" directory as root - File.join treats "/" as absolute path and ignores prior components
176
+ relative_dir = file.directory == "/" ? "" : file.directory
177
+ in_path_name = File.join(temp_dir, relative_dir, file.name)
84
178
  FileUtils.mkdir_p(File.dirname(in_path_name))
85
179
  File.write(in_path_name, file.content)
86
180
  end
@@ -96,6 +190,7 @@ module Dependabot
96
190
  https_proxy_host = https_split&.fetch(1, nil)&.gsub("//", "") || "host.docker.internal"
97
191
  http_proxy_port = http_split&.fetch(2) || "1080"
98
192
  https_proxy_port = https_split&.fetch(2) || "1080"
193
+
99
194
  properties_content = "
100
195
  systemProp.http.proxyHost=#{http_proxy_host}
101
196
  systemProp.http.proxyPort=#{http_proxy_port}
@@ -104,10 +199,46 @@ systemProp.https.proxyPort=#{https_proxy_port}"
104
199
  File.write(file_name, properties_content)
105
200
  end
106
201
 
107
- private
202
+ sig { params(file_name: String).void }
203
+ def write_init_script(file_name)
204
+ # Resolve all resolvable configurations across all loaded projects so
205
+ # Gradle rewrites every relevant lockfile in one invocation.
206
+ script_content = <<~GRADLE
207
+ allprojects {
208
+ if (tasks.findByName("#{INIT_SCRIPT_TASK_NAME}") == null) {
209
+ tasks.register("#{INIT_SCRIPT_TASK_NAME}") {
210
+ doLast {
211
+ configurations.findAll { it.canBeResolved }.each { it.resolve() }
212
+ }
213
+ }
214
+ }
215
+ }
216
+ GRADLE
217
+ File.write(file_name, script_content)
218
+ end
108
219
 
109
- sig { returns(T::Array[Dependabot::DependencyFile]) }
110
- attr_reader :dependency_files
220
+ sig { params(build_file: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) }
221
+ def find_settings_file(build_file)
222
+ settings_files = dependency_files.select do |f|
223
+ basename = File.basename(f.name)
224
+ basename == "settings.gradle" || basename == "settings.gradle.kts"
225
+ end
226
+
227
+ return nil if settings_files.empty?
228
+
229
+ build_dir = normalized_directory_path(build_file)
230
+
231
+ ancestor_settings = settings_files.select do |f|
232
+ settings_dir = normalized_directory_path(f)
233
+ path_under_root?(build_dir, settings_dir)
234
+ end
235
+
236
+ return nil if ancestor_settings.empty?
237
+
238
+ ancestor_settings.max_by do |f|
239
+ normalized_directory_path(f).split("/").count { |element| !element.empty? }
240
+ end
241
+ end
111
242
  end
112
243
  end
113
244
  end
@@ -5,81 +5,22 @@ require "sorbet-runtime"
5
5
 
6
6
  require "dependabot/gradle/file_updater"
7
7
  require "dependabot/gradle/file_parser/property_value_finder"
8
+ require "dependabot/maven/shared/shared_property_value_updater"
8
9
 
9
10
  module Dependabot
10
11
  module Gradle
11
12
  class FileUpdater
12
- class PropertyValueUpdater
13
+ class PropertyValueUpdater < Dependabot::Maven::Shared::SharedPropertyValueUpdater
13
14
  extend T::Sig
14
15
 
15
- sig { params(dependency_files: T::Array[DependencyFile]).void }
16
- def initialize(dependency_files:)
17
- @dependency_files = dependency_files
18
- @property_value_finder = T.let(nil, T.nilable(Gradle::FileParser::PropertyValueFinder))
19
- end
20
-
21
- sig do
22
- params(
23
- property_name: String,
24
- callsite_buildfile: DependencyFile,
25
- previous_value: String,
26
- updated_value: String
27
- )
28
- .returns(T::Array[DependencyFile])
29
- end
30
- def update_files_for_property_change(
31
- property_name:,
32
- callsite_buildfile:,
33
- previous_value:,
34
- updated_value:
35
- )
36
- declaration_details = T.must(
37
- property_value_finder.property_details(
38
- property_name: property_name,
39
- callsite_buildfile: callsite_buildfile
40
- )
41
- )
42
- declaration_string = declaration_details.fetch(:declaration_string)
43
- filename = declaration_details.fetch(:file)
44
-
45
- file_to_update = T.must(dependency_files.find { |f| f.name == filename })
46
- updated_content = T.must(file_to_update.content).sub(
47
- declaration_string,
48
- declaration_string.sub(
49
- previous_value_regex(previous_value),
50
- updated_value
51
- )
52
- )
53
-
54
- updated_files = dependency_files.dup
55
- updated_files[T.must(updated_files.index(file_to_update))] =
56
- update_file(file: file_to_update, content: updated_content)
57
-
58
- updated_files
59
- end
60
-
61
16
  private
62
17
 
63
- sig { returns(T::Array[DependencyFile]) }
64
- attr_reader :dependency_files
65
-
66
- sig { returns(Gradle::FileParser::PropertyValueFinder) }
18
+ sig { override.returns(Gradle::FileParser::PropertyValueFinder) }
67
19
  def property_value_finder
68
- @property_value_finder ||=
69
- Gradle::FileParser::PropertyValueFinder
70
- .new(dependency_files: dependency_files)
71
- end
72
-
73
- sig { params(file: DependencyFile, content: String).returns(DependencyFile) }
74
- def update_file(file:, content:)
75
- updated_file = file.dup
76
- updated_file.content = content
77
- updated_file
78
- end
79
-
80
- sig { params(previous_value: String).returns(Regexp) }
81
- def previous_value_regex(previous_value)
82
- /(?<=['"])#{Regexp.quote(previous_value)}(?=['"])/
20
+ @property_value_finder ||= T.let(
21
+ Gradle::FileParser::PropertyValueFinder.new(dependency_files: dependency_files),
22
+ T.nilable(Gradle::FileParser::PropertyValueFinder)
23
+ )
83
24
  end
84
25
  end
85
26
  end
@@ -107,17 +107,34 @@ module Dependabot
107
107
  replace_updated_files(files, updated_files)
108
108
  end
109
109
  if Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
110
- buildfiles_processed.each_value do |buildfile|
111
- lockfile_updater = LockfileUpdater.new(dependency_files: files)
112
- updated_files = lockfile_updater.update_lockfiles(buildfile)
113
- replace_updated_files(files, updated_files)
114
- end
110
+ update_lockfiles_for_buildfiles(files, buildfiles_processed)
115
111
  end
116
112
 
117
113
  files
118
114
  end
119
115
  # rubocop:enable Metrics/PerceivedComplexity
120
116
  # rubocop:enable Metrics/AbcSize
117
+
118
+ sig do
119
+ params(
120
+ files: T::Array[Dependabot::DependencyFile],
121
+ buildfiles_processed: T::Hash[String, Dependabot::DependencyFile]
122
+ ).void
123
+ end
124
+ def update_lockfiles_for_buildfiles(files, buildfiles_processed)
125
+ lockfile_roots_processed = T.let(Set.new, T::Set[String])
126
+
127
+ buildfiles_processed.each_value do |buildfile|
128
+ lockfile_updater = LockfileUpdater.new(dependency_files: files)
129
+ root_dir = lockfile_updater.determine_root_dir(build_file: buildfile)
130
+ next if lockfile_roots_processed.include?(root_dir)
131
+
132
+ lockfile_roots_processed.add(root_dir)
133
+
134
+ updated_files = lockfile_updater.update_lockfiles(buildfile)
135
+ replace_updated_files(files, updated_files)
136
+ end
137
+ end
121
138
  sig do
122
139
  params(
123
140
  files: T::Array[Dependabot::DependencyFile],
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.375.0
4
+ version: 0.376.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.375.0
18
+ version: 0.376.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.375.0
25
+ version: 0.376.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.375.0
32
+ version: 0.376.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.375.0
39
+ version: 0.376.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -285,7 +285,7 @@ licenses:
285
285
  - MIT
286
286
  metadata:
287
287
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
288
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.375.0
288
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.376.0
289
289
  rdoc_options: []
290
290
  require_paths:
291
291
  - lib