dependabot-gradle 0.84.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9255569d1d2be72d9e6fcfce5fbd4a08b426e4ef01f0977bd31715f6f5ac62bf
4
+ data.tar.gz: 68c4fd51846ab0203004d13a436a78e7a645f52eb28f7832bcd115202fc6aae4
5
+ SHA512:
6
+ metadata.gz: b0850f35295906b35c442821a2a93bee885bc5cd2fd53e94640230902b31c71c15f3f9fa99c1111c755983e42c4bd20fc4fc2222a837170150c1ac77a76284f3
7
+ data.tar.gz: 7f2c01f94d0465c5250bdf0ff20fcdee1d4c5752c5f21d20bc098d8d0220505ec0cfc747a9139299d1c8210a77fb49389d62218fb56133d533b7cb3adbbebdac
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # These all need to be required so the various classes can be registered in a
4
+ # lookup table of package manager names to concrete classes.
5
+ require "dependabot/gradle/file_fetcher"
6
+ require "dependabot/gradle/file_parser"
7
+ require "dependabot/gradle/update_checker"
8
+ require "dependabot/gradle/file_updater"
9
+ require "dependabot/gradle/metadata_finder"
10
+ require "dependabot/gradle/requirement"
11
+ require "dependabot/gradle/version"
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/file_fetchers"
4
+ require "dependabot/file_fetchers/base"
5
+
6
+ module Dependabot
7
+ module Gradle
8
+ class FileFetcher < Dependabot::FileFetchers::Base
9
+ require_relative "file_fetcher/settings_file_parser"
10
+
11
+ def self.required_files_in?(filenames)
12
+ filenames.include?("build.gradle")
13
+ end
14
+
15
+ def self.required_files_message
16
+ "Repo must contain a build.gradle."
17
+ end
18
+
19
+ private
20
+
21
+ def fetch_files
22
+ fetched_files = []
23
+ fetched_files << buildfile
24
+ fetched_files += subproject_buildfiles
25
+ fetched_files
26
+ end
27
+
28
+ def buildfile
29
+ @buildfile ||= fetch_file_from_host("build.gradle")
30
+ end
31
+
32
+ def subproject_buildfiles
33
+ return [] unless settings_file
34
+
35
+ subproject_paths =
36
+ SettingsFileParser.
37
+ new(settings_file: settings_file).
38
+ subproject_paths
39
+
40
+ subproject_paths.map do |path|
41
+ fetch_file_from_host(File.join(path, "build.gradle"))
42
+ rescue Dependabot::DependencyFileNotFound
43
+ # Gradle itself doesn't worry about missing subprojects, so we don't
44
+ nil
45
+ end.compact
46
+ end
47
+
48
+ def settings_file
49
+ @settings_file ||= fetch_file_from_host("settings.gradle")
50
+ rescue Dependabot::DependencyFileNotFound
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ Dependabot::FileFetchers.register("gradle", Dependabot::Gradle::FileFetcher)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/gradle/file_fetcher"
4
+
5
+ module Dependabot
6
+ module Gradle
7
+ class FileFetcher
8
+ class SettingsFileParser
9
+ INCLUDE_ARGS_REGEX =
10
+ /(?:^|\s)include(?:\(|\s)(\s*[^\s,\)]+(?:,\s*[^\s,\)]+)*)/.freeze
11
+
12
+ def initialize(settings_file:)
13
+ @settings_file = settings_file
14
+ end
15
+
16
+ def subproject_paths
17
+ subprojects = []
18
+
19
+ comment_free_content.scan(function_regex("include")) do
20
+ args = Regexp.last_match.named_captures.fetch("args")
21
+ args = args.split(",")
22
+ args = args.map { |p| p.gsub(/["']/, "").strip }.compact
23
+ subprojects += args
24
+ end
25
+
26
+ subprojects = subprojects.uniq
27
+
28
+ subproject_dirs = subprojects.map do |proj|
29
+ if comment_free_content.match?(project_dir_regex(proj))
30
+ comment_free_content.match(project_dir_regex(proj)).
31
+ named_captures.fetch("path").sub(%r{^/}, "")
32
+ else
33
+ proj.tr(":", "/").sub(%r{^/}, "")
34
+ end
35
+ end
36
+
37
+ subproject_dirs.uniq
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :settings_file
43
+
44
+ def comment_free_content
45
+ settings_file.content.
46
+ gsub(%r{(?<=^|\s)//.*$}, "\n").
47
+ gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")
48
+ end
49
+
50
+ def function_regex(function_name)
51
+ /
52
+ (?:^|\s)#{Regexp.quote(function_name)}(?:\(|\s)
53
+ (?<args>\s*[^\s,\)]+(?:,\s*[^\s,\)]+)*)
54
+ /mx
55
+ end
56
+
57
+ def project_dir_regex(proj)
58
+ prefixed_proj = Regexp.quote(":#{proj.gsub(/^:/, '')}")
59
+ /['"]#{prefixed_proj}['"].*dir\s*=.*['"](?<path>.*?)['"]/i
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/dependency"
4
+ require "dependabot/file_parsers"
5
+ require "dependabot/file_parsers/base"
6
+ require "dependabot/shared_helpers"
7
+
8
+ # The best Gradle documentation is at:
9
+ # - https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.
10
+ # DependencyHandler.html
11
+ module Dependabot
12
+ module Gradle
13
+ class FileParser < Dependabot::FileParsers::Base
14
+ require "dependabot/file_parsers/base/dependency_set"
15
+ require_relative "file_parser/property_value_finder"
16
+
17
+ PROPERTY_REGEX =
18
+ /
19
+ (?:\$\{property\((?<property_name>[^:\s]*?)\)\})|
20
+ (?:\$\{(?<property_name>[^:\s]*?)\})|
21
+ (?:\$(?<property_name>[^:\s]*))
22
+ /x.freeze
23
+
24
+ PART = %r{[^\s,@'":/\\]+}.freeze
25
+ VSN_PART = %r{[^\s,'":/\\]+}.freeze
26
+ DEPENDENCY_DECLARATION_REGEX =
27
+ /(?:\(|\s)\s*['"](?<declaration>#{PART}:#{PART}:#{VSN_PART})['"]/.
28
+ freeze
29
+ DEPENDENCY_SET_DECLARATION_REGEX =
30
+ /(?:^|\s)dependencySet\((?<arguments>[^\)]+)\)\s*\{/.freeze
31
+ DEPENDENCY_SET_ENTRY_REGEX = /entry\s+['"](?<name>#{PART})['"]/.freeze
32
+
33
+ def parse
34
+ dependency_set = DependencySet.new
35
+ buildfiles.each do |buildfile|
36
+ dependency_set += buildfile_dependencies(buildfile)
37
+ end
38
+ dependency_set.dependencies
39
+ end
40
+
41
+ private
42
+
43
+ def map_value_regex(key)
44
+ /(?:^|\s|,|\()#{Regexp.quote(key)}:\s*['"](?<value>[^'"]+)['"]/
45
+ end
46
+
47
+ def buildfile_dependencies(buildfile)
48
+ dependency_set = DependencySet.new
49
+
50
+ dependency_set += shortform_buildfile_dependencies(buildfile)
51
+ dependency_set += keyword_arg_buildfile_dependencies(buildfile)
52
+ dependency_set += dependency_set_dependencies(buildfile)
53
+
54
+ dependency_set
55
+ end
56
+
57
+ def shortform_buildfile_dependencies(buildfile)
58
+ dependency_set = DependencySet.new
59
+
60
+ prepared_content(buildfile).scan(DEPENDENCY_DECLARATION_REGEX) do
61
+ declaration = Regexp.last_match.named_captures.fetch("declaration")
62
+
63
+ group, name, version = declaration.split(":")
64
+ details = { group: group, name: name, version: version }
65
+
66
+ dep = dependency_from(details_hash: details, buildfile: buildfile)
67
+ dependency_set << dep if dep
68
+ end
69
+
70
+ dependency_set
71
+ end
72
+
73
+ def keyword_arg_buildfile_dependencies(buildfile)
74
+ dependency_set = DependencySet.new
75
+
76
+ prepared_content(buildfile).lines.each do |line|
77
+ name = argument_from_string(line, "name")
78
+ group = argument_from_string(line, "group")
79
+ version = argument_from_string(line, "version")
80
+ next unless name && group && version
81
+
82
+ details = { name: name, group: group, version: version }
83
+
84
+ dep = dependency_from(details_hash: details, buildfile: buildfile)
85
+ dependency_set << dep if dep
86
+ end
87
+
88
+ dependency_set
89
+ end
90
+
91
+ def dependency_set_dependencies(buildfile)
92
+ dependency_set = DependencySet.new
93
+
94
+ dependency_set_blocks = []
95
+
96
+ prepared_content(buildfile).scan(DEPENDENCY_SET_DECLARATION_REGEX) do
97
+ mch = Regexp.last_match
98
+ dependency_set_blocks <<
99
+ {
100
+ arguments: mch.named_captures.fetch("arguments"),
101
+ block: mch.post_match[0..closing_bracket_index(mch.post_match)]
102
+ }
103
+ end
104
+
105
+ dependency_set_blocks.each do |blk|
106
+ group = argument_from_string(blk[:arguments], "group")
107
+ version = argument_from_string(blk[:arguments], "version")
108
+
109
+ next unless group && version
110
+
111
+ blk[:block].scan(DEPENDENCY_SET_ENTRY_REGEX).flatten.each do |name|
112
+ dep = dependency_from(
113
+ details_hash: { group: group, name: name, version: version },
114
+ buildfile: buildfile,
115
+ in_dependency_set: true
116
+ )
117
+ dependency_set << dep if dep
118
+ end
119
+ end
120
+
121
+ dependency_set
122
+ end
123
+
124
+ def argument_from_string(string, arg_name)
125
+ string.
126
+ match(map_value_regex(arg_name))&.
127
+ named_captures&.
128
+ fetch("value")
129
+ end
130
+
131
+ def dependency_from(details_hash:, buildfile:, in_dependency_set: false)
132
+ group = evaluated_value(details_hash[:group], buildfile)
133
+ name = evaluated_value(details_hash[:name], buildfile)
134
+ version = evaluated_value(details_hash[:version], buildfile)
135
+
136
+ dependency_name = "#{group}:#{name}"
137
+
138
+ # If we can't evaluate a property they we won't be able to
139
+ # update this dependency
140
+ return if "#{dependency_name}:#{version}".match?(PROPERTY_REGEX)
141
+
142
+ Dependency.new(
143
+ name: dependency_name,
144
+ version: version,
145
+ requirements: [{
146
+ requirement: version,
147
+ file: buildfile.name,
148
+ source: nil,
149
+ groups: [],
150
+ metadata: dependency_metadata(details_hash, in_dependency_set)
151
+ }],
152
+ package_manager: "gradle"
153
+ )
154
+ end
155
+
156
+ def dependency_metadata(details_hash, in_dependency_set)
157
+ version_property_name =
158
+ details_hash[:version].
159
+ match(PROPERTY_REGEX)&.
160
+ named_captures&.fetch("property_name")
161
+
162
+ return unless version_property_name || in_dependency_set
163
+
164
+ metadata = {}
165
+ if version_property_name
166
+ metadata[:property_name] = version_property_name
167
+ end
168
+ if in_dependency_set
169
+ metadata[:dependency_set] = {
170
+ group: details_hash[:group],
171
+ version: details_hash[:version]
172
+ }
173
+ end
174
+ metadata
175
+ end
176
+
177
+ def evaluated_value(value, buildfile)
178
+ return value unless value.scan(PROPERTY_REGEX).count == 1
179
+
180
+ property_name = value.match(PROPERTY_REGEX).
181
+ named_captures.fetch("property_name")
182
+ property_value = property_value_finder.property_value(
183
+ property_name: property_name,
184
+ callsite_buildfile: buildfile
185
+ )
186
+
187
+ return value unless property_value
188
+
189
+ value.gsub(PROPERTY_REGEX, property_value)
190
+ end
191
+
192
+ def property_value_finder
193
+ @property_value_finder ||=
194
+ PropertyValueFinder.new(dependency_files: dependency_files)
195
+ end
196
+
197
+ def prepared_content(buildfile)
198
+ # Remove any comments
199
+ prepared_content =
200
+ buildfile.content.
201
+ gsub(%r{(?<=^|\s)//.*$}, "\n").
202
+ gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")
203
+
204
+ # Remove the dependencyVerification section added by Gradle Witness
205
+ # (TODO: Support updating this in the FileUpdater)
206
+ prepared_content.dup.scan(/dependencyVerification\s*{/) do
207
+ mtch = Regexp.last_match
208
+ block = mtch.post_match[0..closing_bracket_index(mtch.post_match)]
209
+ prepared_content.gsub!(block, "")
210
+ end
211
+
212
+ prepared_content
213
+ end
214
+
215
+ def closing_bracket_index(string)
216
+ closes_required = 1
217
+
218
+ string.chars.each_with_index do |char, index|
219
+ closes_required += 1 if char == "{"
220
+ closes_required -= 1 if char == "}"
221
+ return index if closes_required.zero?
222
+ end
223
+ end
224
+
225
+ def buildfiles
226
+ @buildfiles ||=
227
+ dependency_files.select { |f| f.name.end_with?("build.gradle") }
228
+ end
229
+
230
+ def check_required_files
231
+ raise "No build.gradle!" unless get_original_file("build.gradle")
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ Dependabot::FileParsers.register("gradle", Dependabot::Gradle::FileParser)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/gradle/file_parser"
4
+
5
+ module Dependabot
6
+ module Gradle
7
+ class FileParser
8
+ class PropertyValueFinder
9
+ PROPERTY_DECLARATION_REGEX =
10
+ /(?:^|\s+|ext.)(?<name>[^\s=]+)\s*=\s*['"](?<value>[^\s]+)['"]/.
11
+ freeze
12
+
13
+ def initialize(dependency_files:)
14
+ @dependency_files = dependency_files
15
+ end
16
+
17
+ def property_details(property_name:, callsite_buildfile:)
18
+ # If the root project was specified, just look in the top-level
19
+ # buildfile
20
+ if property_name.start_with?("rootProject.")
21
+ property_name = property_name.sub("rootProject.", "")
22
+ return properties(top_level_buildfile).fetch(property_name, nil)
23
+ end
24
+
25
+ # If this project was specified strip the specifier
26
+ if property_name.start_with?("project.")
27
+ property_name = property_name.sub("project.", "")
28
+ end
29
+
30
+ # If a `properties` prefix was specified strip that out, too
31
+ if property_name.start_with?("properties.")
32
+ property_name = property_name.sub("properties.", "")
33
+ end
34
+
35
+ # Look for a property in the callsite buildfile. If that fails, look
36
+ # for the property in the top-level buildfile
37
+ if properties(callsite_buildfile).fetch(property_name, nil)
38
+ return properties(callsite_buildfile).fetch(property_name)
39
+ end
40
+
41
+ properties(top_level_buildfile).fetch(property_name, nil)
42
+ end
43
+
44
+ def property_value(property_name:, callsite_buildfile:)
45
+ property_details(
46
+ property_name: property_name,
47
+ callsite_buildfile: callsite_buildfile
48
+ )&.fetch(:value)
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :dependency_files
54
+
55
+ def properties(buildfile)
56
+ @properties ||= {}
57
+ return @properties[buildfile.name] if @properties[buildfile.name]
58
+
59
+ @properties[buildfile.name] = {}
60
+ prepared_content(buildfile).scan(PROPERTY_DECLARATION_REGEX) do
61
+ declaration_string = Regexp.last_match.to_s.strip
62
+ captures = Regexp.last_match.named_captures
63
+ name = captures.fetch("name").sub(/^ext\./, "")
64
+ @properties[buildfile.name][name] = {
65
+ value: captures.fetch("value"),
66
+ declaration_string: declaration_string,
67
+ file: buildfile.name
68
+ }
69
+ end
70
+
71
+ @properties[buildfile.name]
72
+ end
73
+
74
+ def prepared_content(buildfile)
75
+ # Remove any comments
76
+ buildfile.content.
77
+ gsub(%r{(?<=^|\s)//.*$}, "\n").
78
+ gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")
79
+ end
80
+
81
+ def top_level_buildfile
82
+ @top_level_buildfile ||=
83
+ dependency_files.find { |f| f.name == "build.gradle" }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end