dependabot-maven 0.85.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/dependabot/maven/file_fetcher.rb +128 -0
- data/lib/dependabot/maven/file_parser/property_value_finder.rb +164 -0
- data/lib/dependabot/maven/file_parser/repositories_finder.rb +186 -0
- data/lib/dependabot/maven/file_parser.rb +253 -0
- data/lib/dependabot/maven/file_updater/declaration_finder.rb +142 -0
- data/lib/dependabot/maven/file_updater/property_value_updater.rb +59 -0
- data/lib/dependabot/maven/file_updater.rb +156 -0
- data/lib/dependabot/maven/metadata_finder.rb +175 -0
- data/lib/dependabot/maven/requirement.rb +110 -0
- data/lib/dependabot/maven/update_checker/property_updater.rb +125 -0
- data/lib/dependabot/maven/update_checker/requirements_updater.rb +90 -0
- data/lib/dependabot/maven/update_checker/version_finder.rb +223 -0
- data/lib/dependabot/maven/update_checker.rb +160 -0
- data/lib/dependabot/maven/version.rb +181 -0
- data/lib/dependabot/maven.rb +11 -0
- metadata +185 -0
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/metadata_finders"
|
5
|
+
require "dependabot/metadata_finders/base"
|
6
|
+
require "dependabot/file_fetchers/base"
|
7
|
+
require "dependabot/maven/file_parser"
|
8
|
+
require "dependabot/maven/file_parser/repositories_finder"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Maven
|
12
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
13
|
+
DOT_SEPARATOR_REGEX = %r{\.(?:(?!\d+[.\/])+)}.freeze
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def look_up_source
|
18
|
+
tmp_source = look_up_source_in_pom(dependency_pom_file)
|
19
|
+
return tmp_source if tmp_source
|
20
|
+
|
21
|
+
return unless (parent = parent_pom_file(dependency_pom_file))
|
22
|
+
|
23
|
+
tmp_source = look_up_source_in_pom(parent)
|
24
|
+
return unless tmp_source
|
25
|
+
|
26
|
+
artifact = dependency.name.split(":").last
|
27
|
+
return tmp_source if tmp_source.repo.end_with?(artifact)
|
28
|
+
return tmp_source if repo_has_subdir_for_dep?(tmp_source)
|
29
|
+
end
|
30
|
+
|
31
|
+
def repo_has_subdir_for_dep?(tmp_source)
|
32
|
+
@repo_has_subdir_for_dep ||= {}
|
33
|
+
if @repo_has_subdir_for_dep.key?(tmp_source)
|
34
|
+
return @repo_has_subdir_for_dep[tmp_source]
|
35
|
+
end
|
36
|
+
|
37
|
+
artifact = dependency.name.split(":").last
|
38
|
+
fetcher =
|
39
|
+
FileFetchers::Base.new(source: tmp_source, credentials: credentials)
|
40
|
+
|
41
|
+
@repo_has_subdir_for_dep[tmp_source] =
|
42
|
+
fetcher.send(:repo_contents, raise_errors: false).
|
43
|
+
select { |f| f.type == "dir" }.
|
44
|
+
any? { |f| artifact.end_with?(f.name) }
|
45
|
+
rescue Dependabot::RepoNotFound
|
46
|
+
@repo_has_subdir_for_dep[tmp_source] = false
|
47
|
+
end
|
48
|
+
|
49
|
+
def look_up_source_in_pom(pom)
|
50
|
+
potential_source_urls = [
|
51
|
+
pom.at_css("project > url")&.content,
|
52
|
+
pom.at_css("project > scm > url")&.content,
|
53
|
+
pom.at_css("project > issueManagement > url")&.content
|
54
|
+
].compact
|
55
|
+
|
56
|
+
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
57
|
+
source_url ||= source_from_anywhere_in_pom(pom)
|
58
|
+
source_url = substitute_property_in_source_url(source_url, pom)
|
59
|
+
|
60
|
+
Source.from_url(source_url)
|
61
|
+
end
|
62
|
+
|
63
|
+
def substitute_property_in_source_url(source_url, pom)
|
64
|
+
return unless source_url
|
65
|
+
return source_url unless source_url.include?("${")
|
66
|
+
|
67
|
+
regex = Maven::FileParser::PROPERTY_REGEX
|
68
|
+
property_name = source_url.match(regex).named_captures["property"]
|
69
|
+
doc = pom.dup
|
70
|
+
doc.remove_namespaces!
|
71
|
+
nm = property_name.sub(/^pom\./, "").sub(/^project\./, "")
|
72
|
+
property_value =
|
73
|
+
loop do
|
74
|
+
candidate_node =
|
75
|
+
doc.at_xpath("/project/#{nm}") ||
|
76
|
+
doc.at_xpath("/project/properties/#{nm}") ||
|
77
|
+
doc.at_xpath("/project/profiles/profile/properties/#{nm}")
|
78
|
+
break candidate_node.content if candidate_node
|
79
|
+
break unless nm.match?(DOT_SEPARATOR_REGEX)
|
80
|
+
|
81
|
+
nm = nm.sub(DOT_SEPARATOR_REGEX, "/")
|
82
|
+
end
|
83
|
+
|
84
|
+
source_url.gsub("${#{property_name}}", property_value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def source_from_anywhere_in_pom(pom)
|
88
|
+
github_urls = []
|
89
|
+
pom.to_s.scan(Source::SOURCE_REGEX) do
|
90
|
+
github_urls << Regexp.last_match.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
github_urls.find do |url|
|
94
|
+
repo = Source.from_url(url).repo
|
95
|
+
repo.end_with?(dependency.name.split(":").last)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def dependency_pom_file
|
100
|
+
return @dependency_pom_file unless @dependency_pom_file.nil?
|
101
|
+
|
102
|
+
artifact_id = dependency.name.split(":").last
|
103
|
+
response = Excon.get(
|
104
|
+
"#{maven_repo_dependency_url}/"\
|
105
|
+
"#{dependency.version}/"\
|
106
|
+
"#{artifact_id}-#{dependency.version}.pom",
|
107
|
+
headers: auth_details,
|
108
|
+
idempotent: true,
|
109
|
+
**SharedHelpers.excon_defaults
|
110
|
+
)
|
111
|
+
|
112
|
+
@dependency_pom_file = Nokogiri::XML(response.body)
|
113
|
+
rescue Excon::Error::Timeout
|
114
|
+
@dependency_pom_file = Nokogiri::XML("")
|
115
|
+
end
|
116
|
+
|
117
|
+
def parent_pom_file(pom)
|
118
|
+
doc = pom.dup
|
119
|
+
doc.remove_namespaces!
|
120
|
+
group_id = doc.at_xpath("/project/parent/groupId")&.content&.strip
|
121
|
+
artifact_id =
|
122
|
+
doc.at_xpath("/project/parent/artifactId")&.content&.strip
|
123
|
+
version = doc.at_xpath("/project/parent/version")&.content&.strip
|
124
|
+
|
125
|
+
return unless artifact_id && group_id && version
|
126
|
+
|
127
|
+
response = Excon.get(
|
128
|
+
"#{maven_repo_url}/#{group_id.tr('.', '/')}/#{artifact_id}/"\
|
129
|
+
"#{version}/"\
|
130
|
+
"#{artifact_id}-#{version}.pom",
|
131
|
+
headers: auth_details,
|
132
|
+
idempotent: true,
|
133
|
+
**SharedHelpers.excon_defaults
|
134
|
+
)
|
135
|
+
|
136
|
+
Nokogiri::XML(response.body)
|
137
|
+
end
|
138
|
+
|
139
|
+
def maven_repo_url
|
140
|
+
source = dependency.requirements.
|
141
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
142
|
+
|
143
|
+
source&.fetch(:url, nil) ||
|
144
|
+
source&.fetch("url") ||
|
145
|
+
Maven::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
|
146
|
+
end
|
147
|
+
|
148
|
+
def maven_repo_dependency_url
|
149
|
+
group_id, artifact_id = dependency.name.split(":")
|
150
|
+
|
151
|
+
"#{maven_repo_url}/#{group_id.tr('.', '/')}/#{artifact_id}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def auth_details
|
155
|
+
cred =
|
156
|
+
credentials.select { |c| c["type"] == "maven_repository" }.
|
157
|
+
find do |c|
|
158
|
+
cred_url = c.fetch("url").gsub(%r{/+$}, "")
|
159
|
+
next false unless cred_url == maven_repo_url
|
160
|
+
|
161
|
+
c.fetch("username", nil)
|
162
|
+
end
|
163
|
+
|
164
|
+
return {} unless cred
|
165
|
+
|
166
|
+
token = cred.fetch("username") + ":" + cred.fetch("password")
|
167
|
+
encoded_token = Base64.encode64(token).delete("\n")
|
168
|
+
{ "Authorization" => "Basic #{encoded_token}" }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
Dependabot::MetadataFinders.
|
175
|
+
register("maven", Dependabot::Maven::MetadataFinder)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils"
|
4
|
+
require "dependabot/maven/version"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Maven
|
8
|
+
class Requirement < Gem::Requirement
|
9
|
+
quoted = OPS.keys.map { |k| Regexp.quote k }.join("|")
|
10
|
+
PATTERN_RAW =
|
11
|
+
"\\s*(#{quoted})?\\s*(#{Maven::Version::VERSION_PATTERN})\\s*"
|
12
|
+
PATTERN = /\A#{PATTERN_RAW}\z/.freeze
|
13
|
+
|
14
|
+
def self.parse(obj)
|
15
|
+
return ["=", Maven::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
16
|
+
|
17
|
+
unless (matches = PATTERN.match(obj.to_s))
|
18
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
19
|
+
raise BadRequirementError, msg
|
20
|
+
end
|
21
|
+
|
22
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
23
|
+
|
24
|
+
[matches[1] || "=", Maven::Version.new(matches[2])]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.requirements_array(requirement_string)
|
28
|
+
split_java_requirement(requirement_string).map do |str|
|
29
|
+
new(str)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(*requirements)
|
34
|
+
requirements = requirements.flatten.flat_map do |req_string|
|
35
|
+
convert_java_constraint_to_ruby_constraint(req_string)
|
36
|
+
end
|
37
|
+
|
38
|
+
super(requirements)
|
39
|
+
end
|
40
|
+
|
41
|
+
def satisfied_by?(version)
|
42
|
+
version = Maven::Version.new(version.to_s)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.split_java_requirement(req_string)
|
49
|
+
req_string.split(/(?<=\]|\)),/).flat_map do |str|
|
50
|
+
next str if str.start_with?("(", "[")
|
51
|
+
|
52
|
+
exacts, *rest = str.split(/,(?=\[|\()/)
|
53
|
+
[*exacts.split(","), *rest]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
private_class_method :split_java_requirement
|
57
|
+
|
58
|
+
def convert_java_constraint_to_ruby_constraint(req_string)
|
59
|
+
return unless req_string
|
60
|
+
|
61
|
+
if self.class.send(:split_java_requirement, req_string).count > 1
|
62
|
+
raise "Can't convert multiple Java reqs to a single Ruby one"
|
63
|
+
end
|
64
|
+
|
65
|
+
if req_string&.include?(",")
|
66
|
+
return convert_java_range_to_ruby_range(req_string)
|
67
|
+
end
|
68
|
+
|
69
|
+
convert_java_equals_req_to_ruby(req_string)
|
70
|
+
end
|
71
|
+
|
72
|
+
def convert_java_range_to_ruby_range(req_string)
|
73
|
+
lower_b, upper_b = req_string.split(",").map(&:strip)
|
74
|
+
|
75
|
+
lower_b =
|
76
|
+
if ["(", "["].include?(lower_b) then nil
|
77
|
+
elsif lower_b.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}"
|
78
|
+
else ">= #{lower_b.sub(/\[\s*/, '').strip}"
|
79
|
+
end
|
80
|
+
|
81
|
+
upper_b =
|
82
|
+
if [")", "]"].include?(upper_b) then nil
|
83
|
+
elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
|
84
|
+
else "<= #{upper_b.sub(/\s*\]/, '').strip}"
|
85
|
+
end
|
86
|
+
|
87
|
+
[lower_b, upper_b].compact
|
88
|
+
end
|
89
|
+
|
90
|
+
def convert_java_equals_req_to_ruby(req_string)
|
91
|
+
return convert_wildcard_req(req_string) if req_string&.include?("+")
|
92
|
+
|
93
|
+
# If a soft requirement is being used, treat it as an equality matcher
|
94
|
+
return req_string unless req_string&.start_with?("[")
|
95
|
+
|
96
|
+
req_string.gsub(/[\[\]\(\)]/, "")
|
97
|
+
end
|
98
|
+
|
99
|
+
def convert_wildcard_req(req_string)
|
100
|
+
version = req_string.gsub(/(?:\.|^)\+/, "")
|
101
|
+
return ">= 0" if version.empty?
|
102
|
+
|
103
|
+
"~> #{version}.0"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
Dependabot::Utils.
|
110
|
+
register_requirement_class("maven", Dependabot::Maven::Requirement)
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/maven/file_parser"
|
4
|
+
require "dependabot/maven/update_checker"
|
5
|
+
require "dependabot/maven/file_updater/declaration_finder"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module Maven
|
9
|
+
class UpdateChecker
|
10
|
+
class PropertyUpdater
|
11
|
+
require_relative "requirements_updater"
|
12
|
+
require_relative "version_finder"
|
13
|
+
|
14
|
+
def initialize(dependency:, dependency_files:, credentials:,
|
15
|
+
target_version_details:, ignored_versions:)
|
16
|
+
@dependency = dependency
|
17
|
+
@dependency_files = dependency_files
|
18
|
+
@credentials = credentials
|
19
|
+
@ignored_versions = ignored_versions
|
20
|
+
@target_version = target_version_details&.fetch(:version)
|
21
|
+
@source_url = target_version_details&.fetch(:source_url)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_possible?
|
25
|
+
return false unless target_version
|
26
|
+
|
27
|
+
@update_possible ||=
|
28
|
+
dependencies_using_property.all? do |dep|
|
29
|
+
versions = VersionFinder.new(
|
30
|
+
dependency: dep,
|
31
|
+
dependency_files: dependency_files,
|
32
|
+
credentials: credentials,
|
33
|
+
ignored_versions: ignored_versions
|
34
|
+
).versions.map { |v| v.fetch(:version) }
|
35
|
+
|
36
|
+
versions.include?(target_version) || versions.none?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def updated_dependencies
|
41
|
+
raise "Update not possible!" unless update_possible?
|
42
|
+
|
43
|
+
@updated_dependencies ||=
|
44
|
+
dependencies_using_property.map do |dep|
|
45
|
+
Dependency.new(
|
46
|
+
name: dep.name,
|
47
|
+
version: updated_version(dep),
|
48
|
+
requirements: updated_requirements(dep),
|
49
|
+
previous_version: dep.version,
|
50
|
+
previous_requirements: dep.requirements,
|
51
|
+
package_manager: dep.package_manager
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :dependency, :dependency_files, :target_version,
|
59
|
+
:source_url, :credentials, :ignored_versions
|
60
|
+
|
61
|
+
def dependencies_using_property
|
62
|
+
@dependencies_using_property ||=
|
63
|
+
Maven::FileParser.new(
|
64
|
+
dependency_files: dependency_files,
|
65
|
+
source: nil
|
66
|
+
).parse.select do |dep|
|
67
|
+
dep.requirements.any? do |r|
|
68
|
+
next unless r.dig(:metadata, :property_name) == property_name
|
69
|
+
|
70
|
+
r.dig(:metadata, :property_source) == property_source
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def property_name
|
76
|
+
@property_name ||= dependency.requirements.
|
77
|
+
find { |r| r.dig(:metadata, :property_name) }&.
|
78
|
+
dig(:metadata, :property_name)
|
79
|
+
|
80
|
+
raise "No requirement with a property name!" unless @property_name
|
81
|
+
|
82
|
+
@property_name
|
83
|
+
end
|
84
|
+
|
85
|
+
def property_source
|
86
|
+
@property_source ||=
|
87
|
+
dependency.requirements.
|
88
|
+
find { |r| r.dig(:metadata, :property_name) == property_name }&.
|
89
|
+
dig(:metadata, :property_source)
|
90
|
+
end
|
91
|
+
|
92
|
+
def version_string(dep)
|
93
|
+
declaring_requirement =
|
94
|
+
dep.requirements.
|
95
|
+
find { |r| r.dig(:metadata, :property_name) == property_name }
|
96
|
+
|
97
|
+
Maven::FileUpdater::DeclarationFinder.new(
|
98
|
+
dependency: dep,
|
99
|
+
declaring_requirement: declaring_requirement,
|
100
|
+
dependency_files: dependency_files
|
101
|
+
).declaration_nodes.first.at_css("version")&.content
|
102
|
+
end
|
103
|
+
|
104
|
+
def pom
|
105
|
+
dependency_files.find { |f| f.name == "pom.xml" }
|
106
|
+
end
|
107
|
+
|
108
|
+
def updated_version(dep)
|
109
|
+
version_string(dep).gsub("${#{property_name}}", target_version.to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
def updated_requirements(dep)
|
113
|
+
@updated_requirements ||= {}
|
114
|
+
@updated_requirements[dep.name] ||=
|
115
|
+
RequirementsUpdater.new(
|
116
|
+
requirements: dep.requirements,
|
117
|
+
latest_version: updated_version(dep),
|
118
|
+
source_url: source_url,
|
119
|
+
properties_to_update: [property_name]
|
120
|
+
).updated_requirements
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#######################################################
|
4
|
+
# For more details on Maven version constraints, see: #
|
5
|
+
# https://maven.apache.org/pom.html#Dependencies #
|
6
|
+
#######################################################
|
7
|
+
|
8
|
+
require "dependabot/maven/update_checker"
|
9
|
+
require "dependabot/maven/version"
|
10
|
+
require "dependabot/maven/requirement"
|
11
|
+
|
12
|
+
module Dependabot
|
13
|
+
module Maven
|
14
|
+
class UpdateChecker
|
15
|
+
class RequirementsUpdater
|
16
|
+
def initialize(requirements:, latest_version:, source_url:,
|
17
|
+
properties_to_update:)
|
18
|
+
@requirements = requirements
|
19
|
+
@source_url = source_url
|
20
|
+
@properties_to_update = properties_to_update
|
21
|
+
return unless latest_version
|
22
|
+
|
23
|
+
@latest_version = version_class.new(latest_version)
|
24
|
+
end
|
25
|
+
|
26
|
+
def updated_requirements
|
27
|
+
return requirements unless latest_version
|
28
|
+
|
29
|
+
# Note: Order is important here. The FileUpdater needs the updated
|
30
|
+
# requirement at index `i` to correspond to the previous requirement
|
31
|
+
# at the same index.
|
32
|
+
requirements.map do |req|
|
33
|
+
next req if req.fetch(:requirement).nil?
|
34
|
+
next req if req.fetch(:requirement).include?(",")
|
35
|
+
|
36
|
+
property_name = req.dig(:metadata, :property_name)
|
37
|
+
if property_name && !properties_to_update.include?(property_name)
|
38
|
+
next req
|
39
|
+
end
|
40
|
+
|
41
|
+
new_req = update_requirement(req[:requirement])
|
42
|
+
req.merge(requirement: new_req, source: updated_source)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :requirements, :latest_version, :source_url,
|
49
|
+
:properties_to_update
|
50
|
+
|
51
|
+
def update_requirement(req_string)
|
52
|
+
if req_string.include?(".+")
|
53
|
+
update_dynamic_requirement(req_string)
|
54
|
+
else
|
55
|
+
# Since range requirements are excluded this must be exact
|
56
|
+
update_exact_requirement(req_string)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def update_exact_requirement(req_string)
|
61
|
+
old_version = requirement_class.new(req_string).
|
62
|
+
requirements.first.last
|
63
|
+
req_string.gsub(old_version.to_s, latest_version.to_s)
|
64
|
+
end
|
65
|
+
|
66
|
+
# This is really only a Gradle thing, but Gradle relies on this
|
67
|
+
# RequirementsUpdater too
|
68
|
+
def update_dynamic_requirement(req_string)
|
69
|
+
precision = req_string.split(".").take_while { |s| s != "+" }.count
|
70
|
+
|
71
|
+
version_parts = latest_version.segments.first(precision)
|
72
|
+
|
73
|
+
version_parts.join(".") + ".+"
|
74
|
+
end
|
75
|
+
|
76
|
+
def version_class
|
77
|
+
Maven::Version
|
78
|
+
end
|
79
|
+
|
80
|
+
def requirement_class
|
81
|
+
Maven::Requirement
|
82
|
+
end
|
83
|
+
|
84
|
+
def updated_source
|
85
|
+
{ type: "maven_repo", url: source_url }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|