dependabot-maven 0.399.0 → 0.400.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 +4 -4
- data/lib/dependabot/maven/file_parser/maven_dependency_parser.rb +147 -53
- data/lib/dependabot/maven/file_parser/repositories_finder.rb +20 -10
- data/lib/dependabot/maven/file_parser.rb +25 -138
- data/lib/dependabot/maven/native_helpers.rb +51 -72
- data/lib/dependabot/maven/shared/maven_settings.rb +111 -0
- data/lib/dependabot/maven/update_checker/property_updater.rb +30 -13
- data/lib/dependabot/maven/update_checker.rb +46 -18
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d916bf0c52cb0878f5ffaa396bb644c5f6529a9be503175c359395a5acb905ff
|
|
4
|
+
data.tar.gz: 0d093a3b4689ba1f54f7e6ef9f97c3a10288252a63ae2da3c122d3f30df30a1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5d2b4f159ef96b9a9a5dedefd8c01e41943b97b39376d62636dc919fc3bc9d9cb693b9b29dc5528aa657dd97726f24884a13bfc96a159b0fc4afdff7fa23e8d
|
|
7
|
+
data.tar.gz: 071b060c8356276b51e441500e4318b055560b44c5a28b13d1c6e7d43154009aff748cfb56bf4fd39642c4a0888f763aab452ae23c7bb8ea5f8182137e3a5d1a
|
|
@@ -3,24 +3,35 @@
|
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
5
|
|
|
6
|
+
require "dependabot/dependency"
|
|
7
|
+
require "dependabot/dependency_requirement"
|
|
6
8
|
require "dependabot/maven/file_parser"
|
|
7
9
|
require "dependabot/maven/native_helpers"
|
|
10
|
+
require "dependabot/maven/file_parser/repositories_finder"
|
|
11
|
+
require "dependabot/maven/shared/maven_settings"
|
|
8
12
|
|
|
9
13
|
module Dependabot
|
|
10
14
|
module Maven
|
|
11
15
|
class FileParser
|
|
16
|
+
# The `mvn dependency:tree` scan: runs Maven, reads its JSON output into
|
|
17
|
+
# dependencies, and merges them with the XML-declared requirements.
|
|
12
18
|
class MavenDependencyParser
|
|
13
19
|
extend T::Sig
|
|
14
20
|
|
|
15
21
|
require "dependabot/file_parsers/base/dependency_set"
|
|
16
22
|
|
|
17
23
|
DEPENDENCY_OUTPUT_FILE = "dependency-tree-output.json"
|
|
24
|
+
REGISTRY_MIRROR_ID = "dependabot-registry-mirror"
|
|
18
25
|
|
|
26
|
+
# Runs `mvn dependency:tree` and returns every resolved dependency, or nil when the
|
|
27
|
+
# scan fails so the caller can fall back to the XML-only parse.
|
|
19
28
|
sig do
|
|
20
|
-
params(
|
|
21
|
-
|
|
29
|
+
params(
|
|
30
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
31
|
+
credentials: T::Array[Dependabot::Credential]
|
|
32
|
+
).returns(T.nilable(Dependabot::FileParsers::Base::DependencySet))
|
|
22
33
|
end
|
|
23
|
-
def self.build_dependency_set(dependency_files)
|
|
34
|
+
def self.build_dependency_set(dependency_files, credentials: [])
|
|
24
35
|
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
|
25
36
|
|
|
26
37
|
# Copy only pom.xml files to a temporary directory to
|
|
@@ -31,78 +42,69 @@ module Dependabot
|
|
|
31
42
|
|
|
32
43
|
dependency_files.each do |pom|
|
|
33
44
|
pom_path = File.join(project_directory, pom.name)
|
|
34
|
-
|
|
35
|
-
FileUtils.mkdir_p(pom_dir)
|
|
45
|
+
FileUtils.mkdir_p(File.dirname(pom_path))
|
|
36
46
|
File.write(pom_path, pom.content)
|
|
37
47
|
end
|
|
38
48
|
|
|
39
|
-
Dir.chdir(project_directory)
|
|
40
|
-
NativeHelpers.run_mvn_dependency_tree_plugin(DEPENDENCY_OUTPUT_FILE)
|
|
41
|
-
end
|
|
49
|
+
Dir.chdir(project_directory) { run_scan(credentials) }
|
|
42
50
|
|
|
43
51
|
# mvn CLI outputs dependency tree for each pom.xml file, collect them
|
|
44
52
|
# add into single dependency set
|
|
45
53
|
dependency_files.each do |pom|
|
|
46
|
-
|
|
47
|
-
pom_dir = File.dirname(pom_path)
|
|
48
|
-
output_file = File.join(pom_dir, DEPENDENCY_OUTPUT_FILE)
|
|
54
|
+
output_file = File.join(File.dirname(File.join(project_directory, pom.name)), DEPENDENCY_OUTPUT_FILE)
|
|
49
55
|
|
|
50
56
|
# If we run updater from sub-module, parent file might be included in dependency files,
|
|
51
57
|
# but mvn CLI will not generate dependency tree for it unless we start from the parent.
|
|
52
58
|
# In that case we can just skip it and focus only on current file and it's sub-modules.
|
|
53
|
-
unless File.exist?(output_file)
|
|
54
|
-
Dependabot.logger.warn("Dependency tree output file not found: #{output_file}")
|
|
55
|
-
next
|
|
56
|
-
end
|
|
59
|
+
next unless File.exist?(output_file)
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
extract_dependencies_from_tree(pom, dependency_set, dependency_tree)
|
|
61
|
+
add_tree(dependency_set, pom, JSON.parse(File.read(output_file)))
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
dependency_set
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
# Never fail the job or log Maven output (it can include registry URLs) from here.
|
|
68
|
+
Dependabot.logger.warn("Maven dependency tree scan failed (#{e.class}); using declared dependencies only")
|
|
69
|
+
nil
|
|
64
70
|
end
|
|
65
71
|
|
|
72
|
+
# Merges the requirements of one dependency found by both the XML parse and the scan.
|
|
73
|
+
#
|
|
74
|
+
# Each declared (XML) requirement is paired with the scanned requirement from the same
|
|
75
|
+
# POM. The XML keeps its requirement, file, groups and source, because the file updater
|
|
76
|
+
# matches them against the XML node; the scan only adds metadata Maven resolved.
|
|
77
|
+
# Scanned requirements with no declaration in that POM (inherited or transitive) are
|
|
78
|
+
# kept as they are.
|
|
66
79
|
sig do
|
|
67
|
-
params(
|
|
68
|
-
|
|
69
|
-
dependency_set: Dependabot::FileParsers::Base::DependencySet,
|
|
70
|
-
dependency_tree: T::Hash[String, Object]
|
|
71
|
-
).void
|
|
80
|
+
params(requirements: T::Array[Dependabot::DependencyRequirement])
|
|
81
|
+
.returns(T::Array[Dependabot::DependencyRequirement])
|
|
72
82
|
end
|
|
73
|
-
def self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
version = node["version"]
|
|
79
|
-
type = node["type"]
|
|
80
|
-
classifier = node["classifier"].to_s.empty? ? nil : node["classifier"].to_s
|
|
81
|
-
scope = node["scope"]
|
|
82
|
-
|
|
83
|
-
groups = scope == "test" ? ["test"] : []
|
|
84
|
-
dependency_set << Dependabot::Dependency.new(
|
|
85
|
-
name: "#{group_id}:#{artifact_id}",
|
|
86
|
-
version: version,
|
|
87
|
-
package_manager: "maven",
|
|
88
|
-
requirements: [{
|
|
89
|
-
requirement: version,
|
|
90
|
-
file: nil,
|
|
91
|
-
groups: groups,
|
|
92
|
-
source: nil,
|
|
93
|
-
metadata: {
|
|
94
|
-
packaging_type: type,
|
|
95
|
-
classifier: classifier,
|
|
96
|
-
pom_file: pom.name
|
|
97
|
-
}
|
|
98
|
-
}]
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
children = node["children"]
|
|
102
|
-
children.each { |child| traverse_tree.call(child) } if children.is_a?(Array)
|
|
83
|
+
def self.merge_requirements(requirements)
|
|
84
|
+
declared, scanned = requirements.partition(&:file)
|
|
85
|
+
merged = declared.map do |declared_req|
|
|
86
|
+
index = scanned.index { |req| req.metadata_string("pom_file") == declared_req.file }
|
|
87
|
+
index ? merge_requirement_pair(declared_req, T.must(scanned.delete_at(index))) : declared_req
|
|
103
88
|
end
|
|
89
|
+
merged + scanned
|
|
90
|
+
end
|
|
104
91
|
|
|
105
|
-
|
|
92
|
+
# Uses the job's registries the same way Dependabot's own Maven lookups do: a
|
|
93
|
+
# `replaces-base` registry stands in for Central, and other registries are added.
|
|
94
|
+
# Repositories declared in the POMs are left to Maven.
|
|
95
|
+
sig { params(credentials: T::Array[Dependabot::Credential]).void }
|
|
96
|
+
def self.run_scan(credentials)
|
|
97
|
+
finder = RepositoriesFinder.new(pom_fetcher: nil, credentials: credentials)
|
|
98
|
+
base_url = finder.replaces_base_url&.strip&.chomp("/")
|
|
99
|
+
mirror = base_url && Shared::MavenSettings::Mirror.new(
|
|
100
|
+
id: REGISTRY_MIRROR_ID, url: base_url, mirror_of: RepositoriesFinder::CENTRAL_REPO_ID
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
NativeHelpers.run_mvn_dependency_tree_plugin(
|
|
104
|
+
DEPENDENCY_OUTPUT_FILE,
|
|
105
|
+
mirror: mirror,
|
|
106
|
+
repository_urls: finder.urls_from_credentials - [base_url]
|
|
107
|
+
)
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
sig do
|
|
@@ -124,6 +126,98 @@ module Dependabot
|
|
|
124
126
|
|
|
125
127
|
base_depth_path
|
|
126
128
|
end
|
|
129
|
+
|
|
130
|
+
# The root node is the module itself and is skipped. Depth-one nodes are direct
|
|
131
|
+
# dependencies, including ones inherited from a parent POM. Deeper nodes are
|
|
132
|
+
# transitive and record the dependency that pulled them in as `pulled_in_by`.
|
|
133
|
+
sig do
|
|
134
|
+
params(
|
|
135
|
+
dependency_set: Dependabot::FileParsers::Base::DependencySet,
|
|
136
|
+
pom: Dependabot::DependencyFile,
|
|
137
|
+
tree: Object
|
|
138
|
+
).void
|
|
139
|
+
end
|
|
140
|
+
def self.add_tree(dependency_set, pom, tree)
|
|
141
|
+
tree_children(tree).each { |node| add_tree_node(dependency_set, pom, node, pulled_in_by: nil) }
|
|
142
|
+
end
|
|
143
|
+
private_class_method :add_tree
|
|
144
|
+
|
|
145
|
+
sig do
|
|
146
|
+
params(
|
|
147
|
+
dependency_set: Dependabot::FileParsers::Base::DependencySet,
|
|
148
|
+
pom: Dependabot::DependencyFile,
|
|
149
|
+
node: T::Hash[String, Object],
|
|
150
|
+
pulled_in_by: T.nilable(String)
|
|
151
|
+
).void
|
|
152
|
+
end
|
|
153
|
+
def self.add_tree_node(dependency_set, pom, node, pulled_in_by:)
|
|
154
|
+
group_id = node_string(node, "groupId")
|
|
155
|
+
artifact_id = node_string(node, "artifactId")
|
|
156
|
+
version = node_string(node, "version")
|
|
157
|
+
return unless group_id && artifact_id && version
|
|
158
|
+
|
|
159
|
+
name = "#{group_id}:#{artifact_id}"
|
|
160
|
+
scope = node_string(node, "scope")
|
|
161
|
+
dependency_set << Dependabot::Dependency.new(
|
|
162
|
+
name: name,
|
|
163
|
+
version: version,
|
|
164
|
+
package_manager: "maven",
|
|
165
|
+
requirements: [{
|
|
166
|
+
# Transitive dependencies keep this "declared-looking" shape on purpose:
|
|
167
|
+
# the file updater pins them through `pom_file` (see FileUpdater).
|
|
168
|
+
requirement: version,
|
|
169
|
+
file: nil,
|
|
170
|
+
groups: scope == "test" ? ["test"] : [],
|
|
171
|
+
source: nil,
|
|
172
|
+
metadata: {
|
|
173
|
+
packaging_type: node_string(node, "type"),
|
|
174
|
+
classifier: node_string(node, "classifier"),
|
|
175
|
+
scope: scope,
|
|
176
|
+
pom_file: pom.name,
|
|
177
|
+
pulled_in_by: pulled_in_by
|
|
178
|
+
}.compact
|
|
179
|
+
}]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
tree_children(node).each { |child| add_tree_node(dependency_set, pom, child, pulled_in_by: name) }
|
|
183
|
+
end
|
|
184
|
+
private_class_method :add_tree_node
|
|
185
|
+
|
|
186
|
+
sig { params(node: Object).returns(T::Array[T::Hash[String, Object]]) }
|
|
187
|
+
def self.tree_children(node)
|
|
188
|
+
return [] unless node.is_a?(Hash)
|
|
189
|
+
|
|
190
|
+
value = node["children"]
|
|
191
|
+
value.is_a?(Array) ? value.grep(Hash) : []
|
|
192
|
+
end
|
|
193
|
+
private_class_method :tree_children
|
|
194
|
+
|
|
195
|
+
sig { params(node: T::Hash[String, Object], key: String).returns(T.nilable(String)) }
|
|
196
|
+
def self.node_string(node, key)
|
|
197
|
+
value = node[key]
|
|
198
|
+
value.is_a?(String) && !value.empty? ? value : nil
|
|
199
|
+
end
|
|
200
|
+
private_class_method :node_string
|
|
201
|
+
|
|
202
|
+
sig do
|
|
203
|
+
params(
|
|
204
|
+
declared: Dependabot::DependencyRequirement,
|
|
205
|
+
scanned: Dependabot::DependencyRequirement
|
|
206
|
+
).returns(Dependabot::DependencyRequirement)
|
|
207
|
+
end
|
|
208
|
+
def self.merge_requirement_pair(declared, scanned)
|
|
209
|
+
declared_metadata = declared.metadata || {}
|
|
210
|
+
metadata = (scanned.metadata || {}).merge(declared_metadata) { |_key, scan, xml| xml.nil? ? scan : xml }
|
|
211
|
+
|
|
212
|
+
Dependabot::DependencyRequirement.create(
|
|
213
|
+
requirement: declared.requirement,
|
|
214
|
+
file: declared.file,
|
|
215
|
+
groups: declared.groups,
|
|
216
|
+
source: declared.source,
|
|
217
|
+
metadata: metadata
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
private_class_method :merge_requirement_pair
|
|
127
221
|
end
|
|
128
222
|
end
|
|
129
223
|
end
|
|
@@ -24,6 +24,9 @@ module Dependabot
|
|
|
24
24
|
|
|
25
25
|
REPOSITORY_SELECTOR = "repositories > repository, " \
|
|
26
26
|
"pluginRepositories > pluginRepository"
|
|
27
|
+
CREDENTIAL_TYPE = "maven_repository"
|
|
28
|
+
CENTRAL_REPO_ID = "central"
|
|
29
|
+
CENTRAL_REPO_URL = "https://repo.maven.apache.org/maven2"
|
|
27
30
|
RepositoryEntry = T.type_alias { T::Hash[Symbol, T.nilable(T.any(String, T::Boolean))] }
|
|
28
31
|
|
|
29
32
|
sig do
|
|
@@ -51,8 +54,22 @@ module Dependabot
|
|
|
51
54
|
|
|
52
55
|
sig { returns(String) }
|
|
53
56
|
def central_repo_url
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
replaces_base_url || CENTRAL_REPO_URL
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The URL of the credential registry that replaces Maven Central, if any.
|
|
61
|
+
sig { returns(T.nilable(String)) }
|
|
62
|
+
def replaces_base_url
|
|
63
|
+
base = @credentials.find { |cred| cred["type"] == CREDENTIAL_TYPE && cred.replaces_base? }
|
|
64
|
+
base && base["url"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The URLs of all `maven_repository` credentials, without trailing slashes.
|
|
68
|
+
sig { returns(T::Array[String]) }
|
|
69
|
+
def urls_from_credentials
|
|
70
|
+
@credentials
|
|
71
|
+
.select { |cred| cred["type"] == CREDENTIAL_TYPE }
|
|
72
|
+
.filter_map { |cred| cred["url"]&.strip&.gsub(%r{/$}, "") }
|
|
56
73
|
end
|
|
57
74
|
|
|
58
75
|
# Collect all repository URLs from this POM and its parents
|
|
@@ -90,7 +107,7 @@ module Dependabot
|
|
|
90
107
|
# always inherited from.
|
|
91
108
|
sig { returns(T::Hash[Symbol, String]) }
|
|
92
109
|
def super_pom
|
|
93
|
-
{ url: central_repo_url, id:
|
|
110
|
+
{ url: central_repo_url, id: CENTRAL_REPO_ID }
|
|
94
111
|
end
|
|
95
112
|
|
|
96
113
|
sig { params(entry: Nokogiri::XML::Node).returns(T::Hash[Symbol, T.nilable(String)]) }
|
|
@@ -270,13 +287,6 @@ module Dependabot
|
|
|
270
287
|
end
|
|
271
288
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
272
289
|
|
|
273
|
-
sig { returns(T::Array[String]) }
|
|
274
|
-
def urls_from_credentials
|
|
275
|
-
@credentials
|
|
276
|
-
.select { |cred| cred["type"] == "maven_repository" }
|
|
277
|
-
.filter_map { |cred| cred["url"]&.strip&.gsub(%r{/$}, "") }
|
|
278
|
-
end
|
|
279
|
-
|
|
280
290
|
sig { params(value: String).returns(T::Boolean) }
|
|
281
291
|
def contains_property?(value)
|
|
282
292
|
value.match?(property_regex)
|
|
@@ -46,7 +46,7 @@ module Dependabot
|
|
|
46
46
|
|
|
47
47
|
sig { override.returns(T::Array[Dependabot::Dependency]) }
|
|
48
48
|
def parse
|
|
49
|
-
if
|
|
49
|
+
if scan_dependency_tree?
|
|
50
50
|
parse_with_transitive_dependencies
|
|
51
51
|
else
|
|
52
52
|
parse_standard_dependencies
|
|
@@ -67,26 +67,39 @@ module Dependabot
|
|
|
67
67
|
|
|
68
68
|
private
|
|
69
69
|
|
|
70
|
+
# Maven's own reparses (update checker, property updater) pass `skip_dependency_tree: true`:
|
|
71
|
+
# they only need declared dependencies, so they skip the expensive scan.
|
|
72
|
+
sig { returns(T::Boolean) }
|
|
73
|
+
def scan_dependency_tree?
|
|
74
|
+
return false if T.cast(options[:skip_dependency_tree], Object) == true
|
|
75
|
+
|
|
76
|
+
Dependabot::Experiments.enabled?(:maven_transitive_dependencies)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Starts from the XML-only result and adds what `mvn dependency:tree` resolves.
|
|
80
|
+
# If the scan fails, the result is the same as with the experiment off.
|
|
70
81
|
sig { returns(T::Array[Dependabot::Dependency]) }
|
|
71
82
|
def parse_with_transitive_dependencies
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
declared = parse_standard_dependencies
|
|
84
|
+
tree = MavenDependencyParser.build_dependency_set(pomfiles, credentials: credentials)
|
|
85
|
+
return declared unless tree
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
dependency_set = DependencySet.new(declared)
|
|
88
|
+
tree.dependencies.each do |dep|
|
|
89
|
+
next if internal_dependency_names.include?(dep.name)
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
tree.all_versions_for_name(dep.name).each { |version| dependency_set << version }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
dependency_set.dependencies.map do |dep|
|
|
95
|
+
Dependabot::Dependency.new(
|
|
83
96
|
name: dep.name,
|
|
84
97
|
version: dep.version,
|
|
85
98
|
package_manager: "maven",
|
|
86
|
-
requirements: requirements
|
|
99
|
+
requirements: MavenDependencyParser.merge_requirements(dep.requirements),
|
|
100
|
+
metadata: dep.metadata
|
|
87
101
|
)
|
|
88
102
|
end
|
|
89
|
-
dependencies
|
|
90
103
|
end
|
|
91
104
|
|
|
92
105
|
sig { returns(T::Array[Dependabot::Dependency]) }
|
|
@@ -527,132 +540,6 @@ module Dependabot
|
|
|
527
540
|
raise "No pom.xml!" unless get_original_file("pom.xml")
|
|
528
541
|
end
|
|
529
542
|
|
|
530
|
-
# Merge dependency scan requirements with file parsing requirements.
|
|
531
|
-
# Since dependency scan evaluates properties, we need to combine results with XML parsing,
|
|
532
|
-
# so we know when certain requirement not a literal value and can differentiate transitive dependencies
|
|
533
|
-
# from direct dependencies.
|
|
534
|
-
sig do
|
|
535
|
-
params(requirements: T::Array[Dependabot::DependencyRequirement])
|
|
536
|
-
.returns(T::Array[Dependabot::DependencyRequirement])
|
|
537
|
-
end
|
|
538
|
-
def merge_requirements(requirements)
|
|
539
|
-
return requirements if requirements.length <= 1
|
|
540
|
-
|
|
541
|
-
merged = []
|
|
542
|
-
used_indices = Set.new
|
|
543
|
-
requirements.each_with_index do |dep_scan_req, i|
|
|
544
|
-
merge_requirement_at(requirements, dep_scan_req, i, merged, used_indices)
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
merged
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
sig do
|
|
551
|
-
params(
|
|
552
|
-
requirements: T::Array[Dependabot::DependencyRequirement],
|
|
553
|
-
dep_scan_req: Dependabot::DependencyRequirement,
|
|
554
|
-
index: Integer,
|
|
555
|
-
merged: T::Array[Dependabot::DependencyRequirement],
|
|
556
|
-
used_indices: T::Set[Integer]
|
|
557
|
-
).void
|
|
558
|
-
end
|
|
559
|
-
def merge_requirement_at(requirements, dep_scan_req, index, merged, used_indices)
|
|
560
|
-
return if used_indices.include?(index)
|
|
561
|
-
|
|
562
|
-
pom_file = dep_scan_req.metadata_string("pom_file")
|
|
563
|
-
return merged << dep_scan_req if pom_file.nil?
|
|
564
|
-
|
|
565
|
-
merge_matching_requirement(requirements, dep_scan_req, index, merged, used_indices)
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
sig do
|
|
569
|
-
params(
|
|
570
|
-
requirements: T::Array[Dependabot::DependencyRequirement],
|
|
571
|
-
dep_scan_req: Dependabot::DependencyRequirement,
|
|
572
|
-
index: Integer,
|
|
573
|
-
merged: T::Array[Dependabot::DependencyRequirement],
|
|
574
|
-
used_indices: T::Set[Integer]
|
|
575
|
-
).void
|
|
576
|
-
end
|
|
577
|
-
def merge_matching_requirement(requirements, dep_scan_req, index, merged, used_indices)
|
|
578
|
-
# Look for another requirement where pom_file matches property_source
|
|
579
|
-
pom_file = T.must(dep_scan_req.metadata_string("pom_file"))
|
|
580
|
-
match_index = matching_requirement_index(requirements, pom_file, index, used_indices)
|
|
581
|
-
return merge_unmatched_requirement(dep_scan_req, index, merged, used_indices) unless match_index
|
|
582
|
-
|
|
583
|
-
parsing_req = T.must(requirements[match_index])
|
|
584
|
-
merged << merge_requirement(dep_scan_req, parsing_req)
|
|
585
|
-
used_indices.add(index)
|
|
586
|
-
used_indices.add(match_index)
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
sig do
|
|
590
|
-
params(
|
|
591
|
-
dep_scan_req: Dependabot::DependencyRequirement,
|
|
592
|
-
index: Integer,
|
|
593
|
-
merged: T::Array[Dependabot::DependencyRequirement],
|
|
594
|
-
used_indices: T::Set[Integer]
|
|
595
|
-
).void
|
|
596
|
-
end
|
|
597
|
-
def merge_unmatched_requirement(dep_scan_req, index, merged, used_indices)
|
|
598
|
-
merged << dep_scan_req
|
|
599
|
-
used_indices.add(index)
|
|
600
|
-
end
|
|
601
|
-
|
|
602
|
-
sig do
|
|
603
|
-
params(
|
|
604
|
-
requirements: T::Array[Dependabot::DependencyRequirement],
|
|
605
|
-
pom_file: String,
|
|
606
|
-
index: Integer,
|
|
607
|
-
used_indices: T::Set[Integer]
|
|
608
|
-
).returns(T.nilable(Integer))
|
|
609
|
-
end
|
|
610
|
-
def matching_requirement_index(requirements, pom_file, index, used_indices)
|
|
611
|
-
requirements.find_index.with_index do |parsing_req, i|
|
|
612
|
-
i > index && !used_indices.include?(i) && pom_file == parsing_req.file
|
|
613
|
-
end
|
|
614
|
-
end
|
|
615
|
-
|
|
616
|
-
sig do
|
|
617
|
-
params(
|
|
618
|
-
dep_scan_req: Dependabot::DependencyRequirement,
|
|
619
|
-
parsing_req: Dependabot::DependencyRequirement
|
|
620
|
-
).returns(Dependabot::DependencyRequirement)
|
|
621
|
-
end
|
|
622
|
-
def merge_requirement(dep_scan_req, parsing_req)
|
|
623
|
-
# We prefer file and requirement properties from parsed requirements,
|
|
624
|
-
# because they include correct file and not evaluated property value.
|
|
625
|
-
merged_req = {
|
|
626
|
-
requirement: parsing_req.requirement,
|
|
627
|
-
file: parsing_req.file,
|
|
628
|
-
groups: [*dep_scan_req.groups, *parsing_req.groups].uniq.compact,
|
|
629
|
-
source: dep_scan_req.source,
|
|
630
|
-
metadata: merge_metadata(T.must(dep_scan_req.metadata), T.must(parsing_req.metadata))
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
Dependabot::DependencyRequirement.create(merged_req)
|
|
634
|
-
end
|
|
635
|
-
|
|
636
|
-
# Merge metadata from two requirements, combining all keys
|
|
637
|
-
sig do
|
|
638
|
-
params(
|
|
639
|
-
metadata1: Dependabot::DependencyRequirement::ObjectHash,
|
|
640
|
-
metadata2: Dependabot::DependencyRequirement::ObjectHash
|
|
641
|
-
).returns(Dependabot::DependencyRequirement::ObjectHash)
|
|
642
|
-
end
|
|
643
|
-
def merge_metadata(metadata1, metadata2)
|
|
644
|
-
metadata1.merge(metadata2) do |_key, old_value, new_value|
|
|
645
|
-
case [old_value, new_value]
|
|
646
|
-
in [nil, new_value] then new_value
|
|
647
|
-
in [old_value, nil] then old_value
|
|
648
|
-
in [old_value, new_value] if old_value == new_value then old_value
|
|
649
|
-
else
|
|
650
|
-
# If values differ, combine them
|
|
651
|
-
[*old_value, *new_value].uniq
|
|
652
|
-
end
|
|
653
|
-
end
|
|
654
|
-
end
|
|
655
|
-
|
|
656
543
|
sig { params(doc: Nokogiri::XML::Document).returns(Nokogiri::XML::NodeSet) }
|
|
657
544
|
def plugin_nodes(doc)
|
|
658
545
|
doc.css(PLUGIN_SELECTOR, PLUGIN_ARTIFACT_ITEMS_SELECTOR)
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "fileutils"
|
|
5
|
-
require "open3"
|
|
6
|
-
require "tempfile"
|
|
7
|
-
require "uri"
|
|
8
4
|
require "sorbet-runtime"
|
|
9
5
|
require "nokogiri"
|
|
10
6
|
require "dependabot/errors"
|
|
11
7
|
require "dependabot/command_helpers"
|
|
12
8
|
require "dependabot/shared_helpers"
|
|
9
|
+
require "dependabot/maven/shared/maven_settings"
|
|
13
10
|
|
|
14
11
|
module Dependabot
|
|
15
12
|
module Maven
|
|
@@ -49,33 +46,46 @@ module Dependabot
|
|
|
49
46
|
|
|
50
47
|
DEPENDENCY_PLUGIN_VERSION = T.let(version, T.nilable(String))
|
|
51
48
|
|
|
49
|
+
# Inactivity timeout for the dependency tree scan. In batch mode Maven still logs each
|
|
50
|
+
# artifact download as it starts and finishes, and the tree only fetches small POMs,
|
|
51
|
+
# so this only trips when Maven is stuck (e.g. an unreachable registry).
|
|
52
|
+
DEPENDENCY_TREE_TIMEOUT = CommandHelpers::TIMEOUTS::DEFAULT
|
|
53
|
+
|
|
54
|
+
WRAPPER_MIRROR_ID = "dependabot-wrapper-mirror"
|
|
55
|
+
# Every remote repository except localhost and file-based ones.
|
|
56
|
+
WRAPPER_MIRROR_OF = "external:*"
|
|
57
|
+
|
|
58
|
+
# Runs `mvn dependency:tree` in the current directory, writing one JSON tree per module
|
|
59
|
+
# to `output_file`. Registries are set through a generated settings file.
|
|
60
|
+
# Raises `SharedHelpers::HelperSubprocessFailed` on failure or timeout.
|
|
52
61
|
sig do
|
|
53
|
-
params(
|
|
62
|
+
params(
|
|
63
|
+
output_file: String,
|
|
64
|
+
mirror: T.nilable(Shared::MavenSettings::Mirror),
|
|
65
|
+
repository_urls: T::Array[String]
|
|
66
|
+
).void
|
|
54
67
|
end
|
|
55
|
-
def self.run_mvn_dependency_tree_plugin(
|
|
68
|
+
def self.run_mvn_dependency_tree_plugin(output_file, mirror: nil, repository_urls: [])
|
|
56
69
|
raise DependabotError, "Could not resolve maven-dependency-plugin version" unless DEPENDENCY_PLUGIN_VERSION
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
|
|
71
|
+
# Without a proxy, the proxy block would point Maven at an unknown host.
|
|
72
|
+
proxy_env = Shared::MavenSettings.proxy_env
|
|
73
|
+
Shared::MavenSettings.with_file(
|
|
74
|
+
mirror: mirror, repository_urls: repository_urls, proxy: proxy_env.any?
|
|
75
|
+
) do |settings_path|
|
|
76
|
+
command = [
|
|
77
|
+
"mvn",
|
|
78
|
+
"dependency:#{DEPENDENCY_PLUGIN_VERSION}:tree",
|
|
79
|
+
"-DoutputFile=#{output_file}",
|
|
80
|
+
"-DoutputType=json",
|
|
81
|
+
"-B",
|
|
82
|
+
"-s",
|
|
83
|
+
settings_path
|
|
84
|
+
]
|
|
85
|
+
SharedHelpers.run_shell_command(
|
|
86
|
+
command, env: proxy_env, timeout: DEPENDENCY_TREE_TIMEOUT
|
|
87
|
+
)
|
|
76
88
|
end
|
|
77
|
-
|
|
78
|
-
raise DependabotError, "mvn CLI failed with an unhandled error"
|
|
79
89
|
end
|
|
80
90
|
|
|
81
91
|
# Runs the Maven Wrapper plugin in the given directory to regenerate
|
|
@@ -133,68 +143,37 @@ module Dependabot
|
|
|
133
143
|
# resolved version via a generated settings mirror; without it `mvn` falls back to Central
|
|
134
144
|
# and hangs behind a no-egress registry. Absent when no version resolved, leaving the baked
|
|
135
145
|
# Central default. The bounded timeout is explained on WRAPPER_DOWNLOAD_TIMEOUT.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
begin
|
|
139
|
-
output = SharedHelpers.run_shell_command(
|
|
146
|
+
run = lambda do |settings_args|
|
|
147
|
+
SharedHelpers.run_shell_command(
|
|
140
148
|
cmd + settings_args,
|
|
141
149
|
env: env,
|
|
142
150
|
cwd: run_cwd,
|
|
143
151
|
timeout: WRAPPER_DOWNLOAD_TIMEOUT
|
|
144
152
|
)
|
|
145
|
-
ensure
|
|
146
|
-
settings_file&.close! # deletes the temp file
|
|
147
153
|
end
|
|
154
|
+
output = if registry_base
|
|
155
|
+
Shared::MavenSettings.with_file(mirror: wrapper_mirror(registry_base)) do |path|
|
|
156
|
+
run.call(["-s", path])
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
run.call([])
|
|
160
|
+
end
|
|
148
161
|
Dependabot.logger.info("mvn wrapper output: STDOUT:#{output}")
|
|
149
162
|
output
|
|
150
163
|
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
151
164
|
# `run_shell_command` raises HelperSubprocessFailed on a non-zero exit, and the
|
|
152
165
|
# updater sanitizes that into an opaque `SubprocessFailed` that only reports the
|
|
153
166
|
# command and hides the real Maven output. Log the full output and re-raise a
|
|
154
|
-
# classified Dependabot error so operators get an actionable message
|
|
155
|
-
# the `run_mvn_dependency_tree_plugin` path.
|
|
167
|
+
# classified Dependabot error so operators get an actionable message.
|
|
156
168
|
Dependabot.logger.warn("mvn wrapper command failed:\n#{e.message}")
|
|
157
169
|
handle_wrapper_error(e)
|
|
158
170
|
end
|
|
159
171
|
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def self.wrapper_settings_file(registry_base)
|
|
166
|
-
file = Tempfile.new(["dependabot-mvn-wrapper-settings", ".xml"])
|
|
167
|
-
file.write(wrapper_settings_xml(registry_base))
|
|
168
|
-
file.close
|
|
169
|
-
file
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Builds the settings XML used for the wrapper invocation: the same proxy block as
|
|
173
|
-
# the baked settings (auth is injected by the proxy) plus a `mirrorOf=external:*`
|
|
174
|
-
# mirror so plugin, transitive POM, and distribution resolution all route through
|
|
175
|
-
# the resolved base instead of Central.
|
|
176
|
-
sig { params(registry_base: String).returns(String) }
|
|
177
|
-
def self.wrapper_settings_xml(registry_base)
|
|
178
|
-
<<~XML
|
|
179
|
-
<settings>
|
|
180
|
-
<proxies>
|
|
181
|
-
<proxy>
|
|
182
|
-
<id>dependabot-proxy</id>
|
|
183
|
-
<active>true</active>
|
|
184
|
-
<protocol>http</protocol>
|
|
185
|
-
<host>${env.PROXY_HOST}</host>
|
|
186
|
-
<port>1080</port>
|
|
187
|
-
</proxy>
|
|
188
|
-
</proxies>
|
|
189
|
-
<mirrors>
|
|
190
|
-
<mirror>
|
|
191
|
-
<id>dependabot-wrapper-mirror</id>
|
|
192
|
-
<mirrorOf>external:*</mirrorOf>
|
|
193
|
-
<url>#{registry_base.encode(xml: :text)}</url>
|
|
194
|
-
</mirror>
|
|
195
|
-
</mirrors>
|
|
196
|
-
</settings>
|
|
197
|
-
XML
|
|
172
|
+
# The wrapper has no repositories of its own, so every external repository (plugin,
|
|
173
|
+
# transitive POMs and the distribution) is routed to the registry that served the version.
|
|
174
|
+
sig { params(registry_base: String).returns(Shared::MavenSettings::Mirror) }
|
|
175
|
+
def self.wrapper_mirror(registry_base)
|
|
176
|
+
Shared::MavenSettings::Mirror.new(id: WRAPPER_MIRROR_ID, url: registry_base, mirror_of: WRAPPER_MIRROR_OF)
|
|
198
177
|
end
|
|
199
178
|
|
|
200
179
|
# Classifies a failed Maven Wrapper invocation into an actionable Dependabot
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "sorbet-runtime"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module Maven
|
|
10
|
+
module Shared
|
|
11
|
+
# Builds the temporary Maven `settings.xml` files passed to native `mvn` runs with `-s`.
|
|
12
|
+
# They keep the proxy block from the baked `~/.m2/settings.xml` (the Dependabot proxy
|
|
13
|
+
# injects registry auth, so no credentials are written here) and can add a mirror and
|
|
14
|
+
# extra repositories.
|
|
15
|
+
module MavenSettings
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
PROXIES_XML = <<~XML
|
|
19
|
+
<proxies>
|
|
20
|
+
<proxy>
|
|
21
|
+
<id>dependabot-proxy</id>
|
|
22
|
+
<active>true</active>
|
|
23
|
+
<protocol>http</protocol>
|
|
24
|
+
<host>${env.PROXY_HOST}</host>
|
|
25
|
+
<port>1080</port>
|
|
26
|
+
</proxy>
|
|
27
|
+
</proxies>
|
|
28
|
+
XML
|
|
29
|
+
|
|
30
|
+
# A settings `<mirror>` that routes matching repositories to one registry.
|
|
31
|
+
class Mirror < T::ImmutableStruct
|
|
32
|
+
const :id, String
|
|
33
|
+
const :url, String
|
|
34
|
+
const :mirror_of, String
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Writes a settings file, yields its path, and deletes it afterwards.
|
|
38
|
+
sig do
|
|
39
|
+
type_parameters(:R)
|
|
40
|
+
.params(
|
|
41
|
+
mirror: T.nilable(Mirror),
|
|
42
|
+
repository_urls: T::Array[String],
|
|
43
|
+
proxy: T::Boolean,
|
|
44
|
+
blk: T.proc.params(path: String).returns(T.type_parameter(:R))
|
|
45
|
+
)
|
|
46
|
+
.returns(T.type_parameter(:R))
|
|
47
|
+
end
|
|
48
|
+
def self.with_file(mirror: nil, repository_urls: [], proxy: true, &blk)
|
|
49
|
+
file = Tempfile.new(["dependabot-mvn-settings", ".xml"])
|
|
50
|
+
file.write(xml(mirror: mirror, repository_urls: repository_urls, proxy: proxy))
|
|
51
|
+
file.close
|
|
52
|
+
yield T.must(file.path)
|
|
53
|
+
ensure
|
|
54
|
+
file&.close!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
sig do
|
|
58
|
+
params(mirror: T.nilable(Mirror), repository_urls: T::Array[String], proxy: T::Boolean).returns(String)
|
|
59
|
+
end
|
|
60
|
+
def self.xml(mirror: nil, repository_urls: [], proxy: true)
|
|
61
|
+
sections = proxy ? [PROXIES_XML] : []
|
|
62
|
+
sections << mirrors_xml(mirror) if mirror
|
|
63
|
+
sections << repositories_xml(repository_urls) if repository_urls.any?
|
|
64
|
+
"<settings>\n#{sections.join.gsub(/^(?=.)/, ' ')}</settings>\n"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The environment the proxy block reads its host from.
|
|
68
|
+
sig { returns(T::Hash[String, String]) }
|
|
69
|
+
def self.proxy_env
|
|
70
|
+
proxy = ENV.fetch("HTTPS_PROXY", nil)
|
|
71
|
+
proxy ? { "PROXY_HOST" => URI.parse(proxy).host.to_s } : {}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sig { params(mirror: Mirror).returns(String) }
|
|
75
|
+
def self.mirrors_xml(mirror)
|
|
76
|
+
<<~XML
|
|
77
|
+
<mirrors>
|
|
78
|
+
<mirror>
|
|
79
|
+
<id>#{mirror.id.encode(xml: :text)}</id>
|
|
80
|
+
<mirrorOf>#{mirror.mirror_of.encode(xml: :text)}</mirrorOf>
|
|
81
|
+
<url>#{mirror.url.encode(xml: :text)}</url>
|
|
82
|
+
</mirror>
|
|
83
|
+
</mirrors>
|
|
84
|
+
XML
|
|
85
|
+
end
|
|
86
|
+
private_class_method :mirrors_xml
|
|
87
|
+
|
|
88
|
+
# Adds each URL as both a repository and a plugin repository, in an always-active profile.
|
|
89
|
+
sig { params(urls: T::Array[String]).returns(String) }
|
|
90
|
+
def self.repositories_xml(urls)
|
|
91
|
+
entries = urls.each_with_index.map do |url, index|
|
|
92
|
+
"<id>dependabot-registry-#{index + 1}</id><url>#{url.encode(xml: :text)}</url>"
|
|
93
|
+
end
|
|
94
|
+
<<~XML
|
|
95
|
+
<profiles>
|
|
96
|
+
<profile>
|
|
97
|
+
<id>dependabot-registries</id>
|
|
98
|
+
<repositories>#{entries.map { |e| "<repository>#{e}</repository>" }.join}</repositories>
|
|
99
|
+
<pluginRepositories>#{entries.map { |e| "<pluginRepository>#{e}</pluginRepository>" }.join}</pluginRepositories>
|
|
100
|
+
</profile>
|
|
101
|
+
</profiles>
|
|
102
|
+
<activeProfiles>
|
|
103
|
+
<activeProfile>dependabot-registries</activeProfile>
|
|
104
|
+
</activeProfiles>
|
|
105
|
+
XML
|
|
106
|
+
end
|
|
107
|
+
private_class_method :repositories_xml
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -59,6 +59,7 @@ module Dependabot
|
|
|
59
59
|
next false if version_string(dep).nil?
|
|
60
60
|
|
|
61
61
|
next false if includes_property_reference?(updated_version(dep))
|
|
62
|
+
next false unless writable_requirements?(dep)
|
|
62
63
|
|
|
63
64
|
releases = VersionFinder.new(
|
|
64
65
|
dependency: dep,
|
|
@@ -124,13 +125,10 @@ module Dependabot
|
|
|
124
125
|
@dependencies_using_property ||= T.let(
|
|
125
126
|
Maven::FileParser.new(
|
|
126
127
|
dependency_files: dependency_files,
|
|
127
|
-
source: nil
|
|
128
|
+
source: nil,
|
|
129
|
+
options: { skip_dependency_tree: true }
|
|
128
130
|
).parse.select do |dep|
|
|
129
|
-
dep.requirements.any?
|
|
130
|
-
next unless r.metadata_string("property_name") == property_name
|
|
131
|
-
|
|
132
|
-
r.metadata_string("property_source") == property_source
|
|
133
|
-
end
|
|
131
|
+
dep.requirements.any? { |requirement| uses_property?(requirement) }
|
|
134
132
|
end,
|
|
135
133
|
T.nilable(T::Array[Dependabot::Dependency])
|
|
136
134
|
)
|
|
@@ -233,12 +231,7 @@ module Dependabot
|
|
|
233
231
|
|
|
234
232
|
sig { params(dep: Dependabot::Dependency).returns(Dependabot::DependencyRequirement) }
|
|
235
233
|
def declaring_property_requirement(dep)
|
|
236
|
-
declaring_requirement =
|
|
237
|
-
dep.requirements.find do |r|
|
|
238
|
-
next false unless r.metadata_string("property_name") == property_name
|
|
239
|
-
|
|
240
|
-
r.metadata_string("property_source") == property_source
|
|
241
|
-
end
|
|
234
|
+
declaring_requirement = dep.requirements.find { |requirement| uses_property?(requirement) }
|
|
242
235
|
|
|
243
236
|
return declaring_requirement if declaring_requirement
|
|
244
237
|
|
|
@@ -246,6 +239,12 @@ module Dependabot
|
|
|
246
239
|
"Requirement not found for property #{property_name} from #{property_source || 'unknown source'}"
|
|
247
240
|
end
|
|
248
241
|
|
|
242
|
+
sig { params(requirement: Dependabot::DependencyRequirement).returns(T::Boolean) }
|
|
243
|
+
def uses_property?(requirement)
|
|
244
|
+
requirement.metadata_string("property_name") == property_name &&
|
|
245
|
+
requirement.metadata_string("property_source") == property_source
|
|
246
|
+
end
|
|
247
|
+
|
|
249
248
|
sig { params(dep: Dependabot::Dependency).returns(T::Array[Dependabot::DependencyRequirement]) }
|
|
250
249
|
def updated_requirements(dep)
|
|
251
250
|
@updated_requirements ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependabot::DependencyRequirement]]))
|
|
@@ -255,7 +254,25 @@ module Dependabot
|
|
|
255
254
|
latest_version: updated_version(dep),
|
|
256
255
|
source_url: source_url,
|
|
257
256
|
properties_to_update: [property_name]
|
|
258
|
-
).updated_requirements
|
|
257
|
+
).updated_requirements.map.with_index do |requirement, index|
|
|
258
|
+
next requirement unless requirement.metadata_string("property_name")
|
|
259
|
+
|
|
260
|
+
uses_property?(requirement) ? requirement : dep.requirements.fetch(index)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
sig { params(dep: Dependabot::Dependency).returns(T::Boolean) }
|
|
265
|
+
def writable_requirements?(dep)
|
|
266
|
+
updated = updated_requirements(dep)
|
|
267
|
+
writable = dep.requirements.each_with_index.any? do |requirement, index|
|
|
268
|
+
uses_property?(requirement) && updated.fetch(index).requirement != requirement.requirement
|
|
269
|
+
end
|
|
270
|
+
return true if writable
|
|
271
|
+
|
|
272
|
+
Dependabot.logger.info(
|
|
273
|
+
"Cannot update shared property #{property_name}: no writable requirement changes for #{dep.name}"
|
|
274
|
+
)
|
|
275
|
+
false
|
|
259
276
|
end
|
|
260
277
|
|
|
261
278
|
sig { returns(Dependabot::Maven::FileParser::PropertyValueFinder) }
|
|
@@ -70,12 +70,14 @@ module Dependabot
|
|
|
70
70
|
# Maven's version resolution algorithm is very simple: it just uses
|
|
71
71
|
# the version defined "closest", with the first declaration winning
|
|
72
72
|
# if two declarations are equally close. As a result, we can just
|
|
73
|
-
# return
|
|
73
|
+
# return the latest version with writable requirements unless dealing
|
|
74
|
+
# with a property dep.
|
|
74
75
|
# https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Transitive_Dependencies
|
|
75
76
|
return nil if version_comes_from_multi_dependency_property?
|
|
76
|
-
return nil if
|
|
77
|
+
return nil if version_comes_from_parent_version?
|
|
77
78
|
|
|
78
|
-
latest_version
|
|
79
|
+
version = latest_version
|
|
80
|
+
version if writable_requirements?(version)
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
sig { override.returns(T.nilable(Dependabot::Version)) }
|
|
@@ -88,7 +90,10 @@ module Dependabot
|
|
|
88
90
|
|
|
89
91
|
sig { override.returns(T.nilable(Dependabot::Version)) }
|
|
90
92
|
def lowest_resolvable_security_fix_version
|
|
91
|
-
|
|
93
|
+
return nil if version_comes_from_parent_version?
|
|
94
|
+
|
|
95
|
+
version = lowest_security_fix_version
|
|
96
|
+
version if writable_requirements?(version)
|
|
92
97
|
end
|
|
93
98
|
|
|
94
99
|
sig { override.returns(T.nilable(Dependabot::Version)) }
|
|
@@ -105,16 +110,7 @@ module Dependabot
|
|
|
105
110
|
|
|
106
111
|
sig { override.returns(T::Array[Dependabot::DependencyRequirement]) }
|
|
107
112
|
def updated_requirements
|
|
108
|
-
|
|
109
|
-
declarations_using_a_property
|
|
110
|
-
.filter_map { |req| req.metadata_string("property_name") }
|
|
111
|
-
|
|
112
|
-
RequirementsUpdater.new(
|
|
113
|
-
requirements: dependency.requirements,
|
|
114
|
-
latest_version: preferred_resolvable_version&.to_s,
|
|
115
|
-
source_url: preferred_version_details&.fetch(:source_url),
|
|
116
|
-
properties_to_update: property_names
|
|
117
|
-
).updated_requirements
|
|
113
|
+
updated_requirements_for(preferred_resolvable_version)
|
|
118
114
|
end
|
|
119
115
|
|
|
120
116
|
sig { override.returns(T::Boolean) }
|
|
@@ -136,9 +132,40 @@ module Dependabot
|
|
|
136
132
|
|
|
137
133
|
private
|
|
138
134
|
|
|
135
|
+
sig { params(version: T.nilable(Dependabot::Version)).returns(T::Boolean) }
|
|
136
|
+
def writable_requirements?(version)
|
|
137
|
+
return false unless version
|
|
138
|
+
return false unless requirements_unlocked_or_can_be?
|
|
139
|
+
|
|
140
|
+
updated = updated_requirements_for(version)
|
|
141
|
+
return true if updated.map(&:requirement) != dependency.requirements.map(&:requirement)
|
|
142
|
+
|
|
143
|
+
Dependabot.logger.info(
|
|
144
|
+
"Cannot update #{dependency.name} to #{version}: no writable requirement changes " \
|
|
145
|
+
"(requirements may be inherited or ranges)"
|
|
146
|
+
)
|
|
147
|
+
false
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
sig do
|
|
151
|
+
params(version: T.nilable(T.any(String, Gem::Version))).returns(T::Array[Dependabot::DependencyRequirement])
|
|
152
|
+
end
|
|
153
|
+
def updated_requirements_for(version)
|
|
154
|
+
property_names =
|
|
155
|
+
declarations_using_a_property
|
|
156
|
+
.filter_map { |req| req.metadata_string("property_name") }
|
|
157
|
+
|
|
158
|
+
RequirementsUpdater.new(
|
|
159
|
+
requirements: dependency.requirements,
|
|
160
|
+
latest_version: version&.to_s,
|
|
161
|
+
source_url: preferred_version_details&.fetch(:source_url),
|
|
162
|
+
properties_to_update: property_names
|
|
163
|
+
).updated_requirements
|
|
164
|
+
end
|
|
165
|
+
|
|
139
166
|
sig { override.returns(T::Boolean) }
|
|
140
167
|
def latest_version_resolvable_with_full_unlock?
|
|
141
|
-
return false if
|
|
168
|
+
return false if version_comes_from_parent_version?
|
|
142
169
|
return false unless version_comes_from_multi_dependency_property?
|
|
143
170
|
|
|
144
171
|
property_updater.update_possible?
|
|
@@ -244,16 +271,17 @@ module Dependabot
|
|
|
244
271
|
@all_property_based_dependencies ||=
|
|
245
272
|
Maven::FileParser.new(
|
|
246
273
|
dependency_files: dependency_files,
|
|
247
|
-
source: nil
|
|
274
|
+
source: nil,
|
|
275
|
+
options: { skip_dependency_tree: true }
|
|
248
276
|
).parse.select do |dep|
|
|
249
277
|
dep.requirements.any? { |req| req.metadata_string("property_name") }
|
|
250
278
|
end
|
|
251
279
|
end
|
|
252
280
|
|
|
253
281
|
sig { returns(T::Boolean) }
|
|
254
|
-
def
|
|
282
|
+
def version_comes_from_parent_version?
|
|
255
283
|
declarations_using_a_property.any? do |requirement|
|
|
256
|
-
requirement.metadata_string("property_name") == "
|
|
284
|
+
requirement.metadata_string("property_name")&.sub(/\A(?:pom|project)\./, "") == "parent.version"
|
|
257
285
|
end
|
|
258
286
|
end
|
|
259
287
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-maven
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.400.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.400.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.
|
|
25
|
+
version: 0.400.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rexml
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -278,6 +278,7 @@ files:
|
|
|
278
278
|
- lib/dependabot/maven/pom.xml
|
|
279
279
|
- lib/dependabot/maven/requirement.rb
|
|
280
280
|
- lib/dependabot/maven/shared/base_version_finder.rb
|
|
281
|
+
- lib/dependabot/maven/shared/maven_settings.rb
|
|
281
282
|
- lib/dependabot/maven/shared/property_value_finding.rb
|
|
282
283
|
- lib/dependabot/maven/shared/shared_metadata_finder.rb
|
|
283
284
|
- lib/dependabot/maven/shared/shared_package_details_fetcher.rb
|
|
@@ -297,7 +298,7 @@ licenses:
|
|
|
297
298
|
- MIT
|
|
298
299
|
metadata:
|
|
299
300
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
300
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
301
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.400.0
|
|
301
302
|
rdoc_options: []
|
|
302
303
|
require_paths:
|
|
303
304
|
- lib
|