dependabot-gradle 0.84.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/utils"
4
+ require "dependabot/gradle/version"
5
+
6
+ module Dependabot
7
+ module Gradle
8
+ class Requirement < Gem::Requirement
9
+ quoted = OPS.keys.map { |k| Regexp.quote k }.join("|")
10
+ PATTERN_RAW =
11
+ "\\s*(#{quoted})?\\s*(#{Gradle::Version::VERSION_PATTERN})\\s*"
12
+ PATTERN = /\A#{PATTERN_RAW}\z/.freeze
13
+
14
+ def self.parse(obj)
15
+ return ["=", Gradle::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] || "=", Gradle::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 = Gradle::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("gradle", Dependabot::Gradle::Requirement)
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/update_checkers"
4
+ require "dependabot/update_checkers/base"
5
+ require "dependabot/gradle/file_parser"
6
+
7
+ module Dependabot
8
+ module Gradle
9
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
10
+ require_relative "update_checker/requirements_updater"
11
+ require_relative "update_checker/version_finder"
12
+ require_relative "update_checker/multi_dependency_updater"
13
+
14
+ def latest_version
15
+ latest_version_details&.fetch(:version)
16
+ end
17
+
18
+ def latest_resolvable_version
19
+ # TODO: Resolve the build.gradle to find the latest version we could
20
+ # update to without updating any other dependencies at the same time.
21
+ #
22
+ # The above is hard. Currently we just return the latest version and
23
+ # hope (hence this package manager is in beta!)
24
+ return nil if version_comes_from_multi_dependency_property?
25
+ return nil if version_comes_from_dependency_set?
26
+
27
+ latest_version
28
+ end
29
+
30
+ def latest_resolvable_version_with_no_unlock
31
+ # Irrelevant, since Gradle has a single dependency file.
32
+ #
33
+ # For completeness we ought to resolve the build.gradle and return the
34
+ # latest version that satisfies the current constraint AND any
35
+ # constraints placed on it by other dependencies. Seeing as we're
36
+ # never going to take any action as a result, though, we just return
37
+ # nil.
38
+ nil
39
+ end
40
+
41
+ def updated_requirements
42
+ property_names =
43
+ declarations_using_a_property.
44
+ map { |req| req.dig(:metadata, :property_name) }
45
+
46
+ RequirementsUpdater.new(
47
+ requirements: dependency.requirements,
48
+ latest_version: latest_version&.to_s,
49
+ source_url: latest_version_details&.fetch(:source_url),
50
+ properties_to_update: property_names
51
+ ).updated_requirements
52
+ end
53
+
54
+ def requirements_unlocked_or_can_be?
55
+ # If the dependency version come from a property we couldn't
56
+ # interpolate then there's nothing we can do.
57
+ !dependency.version.include?("$")
58
+ end
59
+
60
+ private
61
+
62
+ def latest_version_resolvable_with_full_unlock?
63
+ unless version_comes_from_multi_dependency_property? ||
64
+ version_comes_from_dependency_set?
65
+ return false
66
+ end
67
+
68
+ multi_dependency_updater.update_possible?
69
+ end
70
+
71
+ def updated_dependencies_after_full_unlock
72
+ multi_dependency_updater.updated_dependencies
73
+ end
74
+
75
+ def numeric_version_up_to_date?
76
+ return false unless version_class.correct?(dependency.version)
77
+
78
+ super
79
+ end
80
+
81
+ def numeric_version_can_update?(requirements_to_unlock:)
82
+ return false unless version_class.correct?(dependency.version)
83
+
84
+ super
85
+ end
86
+
87
+ def latest_version_details
88
+ @latest_version_details ||= version_finder.latest_version_details
89
+ end
90
+
91
+ def version_finder
92
+ @version_finder ||=
93
+ VersionFinder.new(
94
+ dependency: dependency,
95
+ dependency_files: dependency_files,
96
+ ignored_versions: ignored_versions
97
+ )
98
+ end
99
+
100
+ def multi_dependency_updater
101
+ @multi_dependency_updater ||=
102
+ MultiDependencyUpdater.new(
103
+ dependency: dependency,
104
+ dependency_files: dependency_files,
105
+ target_version_details: latest_version_details,
106
+ ignored_versions: ignored_versions
107
+ )
108
+ end
109
+
110
+ def version_comes_from_multi_dependency_property?
111
+ declarations_using_a_property.any? do |requirement|
112
+ property_name = requirement.fetch(:metadata).fetch(:property_name)
113
+
114
+ all_property_based_dependencies.any? do |dep|
115
+ next false if dep.name == dependency.name
116
+
117
+ dep.requirements.any? do |req|
118
+ req.dig(:metadata, :property_name) == property_name
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def version_comes_from_dependency_set?
125
+ dependency.requirements.any? do |req|
126
+ req.dig(:metadata, :dependency_set)
127
+ end
128
+ end
129
+
130
+ def declarations_using_a_property
131
+ @declarations_using_a_property ||=
132
+ dependency.requirements.
133
+ select { |req| req.dig(:metadata, :property_name) }
134
+ end
135
+
136
+ def all_property_based_dependencies
137
+ @all_property_based_dependencies ||=
138
+ Gradle::FileParser.new(
139
+ dependency_files: dependency_files,
140
+ source: nil
141
+ ).parse.select do |dep|
142
+ dep.requirements.any? { |req| req.dig(:metadata, :property_name) }
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ Dependabot::UpdateCheckers.register("gradle", Dependabot::Gradle::UpdateChecker)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/gradle/file_parser"
4
+ require "dependabot/gradle/update_checker"
5
+
6
+ module Dependabot
7
+ module Gradle
8
+ class UpdateChecker
9
+ class MultiDependencyUpdater
10
+ require_relative "version_finder"
11
+ require_relative "requirements_updater"
12
+
13
+ def initialize(dependency:, dependency_files:,
14
+ target_version_details:, ignored_versions:)
15
+ @dependency = dependency
16
+ @dependency_files = dependency_files
17
+ @target_version = target_version_details&.fetch(:version)
18
+ @source_url = target_version_details&.fetch(:source_url)
19
+ @ignored_versions = ignored_versions
20
+ end
21
+
22
+ def update_possible?
23
+ return false unless target_version
24
+
25
+ @update_possible ||=
26
+ dependencies_to_update.all? do |dep|
27
+ VersionFinder.new(
28
+ dependency: dep,
29
+ dependency_files: dependency_files,
30
+ ignored_versions: ignored_versions
31
+ ).versions.
32
+ map { |v| v.fetch(:version) }.
33
+ include?(target_version)
34
+ end
35
+ end
36
+
37
+ def updated_dependencies
38
+ raise "Update not possible!" unless update_possible?
39
+
40
+ @updated_dependencies ||=
41
+ dependencies_to_update.map do |dep|
42
+ Dependency.new(
43
+ name: dep.name,
44
+ version: target_version.to_s,
45
+ requirements: updated_requirements(dep),
46
+ previous_version: dep.version,
47
+ previous_requirements: dep.requirements,
48
+ package_manager: dep.package_manager
49
+ )
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :dependency, :dependency_files, :target_version,
56
+ :source_url, :ignored_versions
57
+
58
+ def dependencies_to_update
59
+ @dependencies_to_update ||=
60
+ Gradle::FileParser.new(
61
+ dependency_files: dependency_files,
62
+ source: nil
63
+ ).parse.select do |dep|
64
+ dep.requirements.any? do |r|
65
+ tmp_p_name = r.dig(:metadata, :property_name)
66
+ tmp_dep_set = r.dig(:metadata, :dependency_set)
67
+ next true if property_name && tmp_p_name == property_name
68
+
69
+ dependency_set && tmp_dep_set == dependency_set
70
+ end
71
+ end
72
+ end
73
+
74
+ def property_name
75
+ @property_name ||= dependency.requirements.
76
+ find { |r| r.dig(:metadata, :property_name) }&.
77
+ dig(:metadata, :property_name)
78
+ end
79
+
80
+ def dependency_set
81
+ @dependency_set ||= dependency.requirements.
82
+ find { |r| r.dig(:metadata, :dependency_set) }&.
83
+ dig(:metadata, :dependency_set)
84
+ end
85
+
86
+ def pom
87
+ dependency_files.find { |f| f.name == "pom.xml" }
88
+ end
89
+
90
+ def updated_requirements(dep)
91
+ @updated_requirements ||= {}
92
+ @updated_requirements[dep.name] ||=
93
+ RequirementsUpdater.new(
94
+ requirements: dep.requirements,
95
+ latest_version: target_version.to_s,
96
+ source_url: source_url,
97
+ properties_to_update: [property_name].compact
98
+ ).updated_requirements
99
+ end
100
+ end
101
+ end
102
+ end
103
+ 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/gradle/update_checker"
9
+ require "dependabot/gradle/version"
10
+ require "dependabot/gradle/requirement"
11
+
12
+ module Dependabot
13
+ module Gradle
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
+ Gradle::Version
78
+ end
79
+
80
+ def requirement_class
81
+ Gradle::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