1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
module Bundler::PubGrub
class Incompatibility
ConflictCause = Struct.new(:incompatibility, :satisfier) do
alias_method :conflict, :incompatibility
alias_method :other, :satisfier
end
InvalidDependency = Struct.new(:package, :constraint) do
end
NoVersions = Struct.new(:constraint) do
end
attr_reader :terms, :cause
def initialize(terms, cause:, custom_explanation: nil)
@cause = cause
@terms = cleanup_terms(terms)
@custom_explanation = custom_explanation
if cause == :dependency && @terms.length != 2
raise ArgumentError, "a dependency Incompatibility must have exactly two terms. Got #{@terms.inspect}"
end
end
def hash
cause.hash ^ terms.hash
end
def eql?(other)
cause.eql?(other.cause) &&
terms.eql?(other.terms)
end
def failure?
terms.empty? || (terms.length == 1 && Package.root?(terms[0].package) && terms[0].positive?)
end
def conflict?
ConflictCause === cause
end
# Returns all external incompatibilities in this incompatibility's
# derivation graph
def external_incompatibilities
if conflict?
[
cause.conflict,
cause.other
].flat_map(&:external_incompatibilities)
else
[this]
end
end
def to_s
return @custom_explanation if @custom_explanation
case cause
when :root
"(root dependency)"
when :dependency
"#{terms[0].to_s(allow_every: true)} depends on #{terms[1].invert}"
when Bundler::PubGrub::Incompatibility::InvalidDependency
"#{terms[0].to_s(allow_every: true)} depends on unknown package #{cause.package}"
when Bundler::PubGrub::Incompatibility::NoVersions
"no versions satisfy #{cause.constraint}"
when Bundler::PubGrub::Incompatibility::ConflictCause
if failure?
"version solving has failed"
elsif terms.length == 1
term = terms[0]
if term.positive?
if term.constraint.any?
"#{term.package} cannot be used"
else
"#{term.to_s(allow_every: true)} cannot be used"
end
else
"#{term.invert} is required"
end
else
if terms.all?(&:positive?)
if terms.length == 2
"#{terms[0].to_s(allow_every: true)} is incompatible with #{terms[1]}"
else
"one of #{terms.map(&:to_s).join(" or ")} must be false"
end
elsif terms.all?(&:negative?)
if terms.length == 2
"either #{terms[0].invert} or #{terms[1].invert}"
else
"one of #{terms.map(&:invert).join(" or ")} must be true";
end
else
positive = terms.select(&:positive?)
negative = terms.select(&:negative?).map(&:invert)
if positive.length == 1
"#{positive[0].to_s(allow_every: true)} requires #{negative.join(" or ")}"
else
"if #{positive.join(" and ")} then #{negative.join(" or ")}"
end
end
end
else
raise "unhandled cause: #{cause.inspect}"
end
end
def inspect
"#<#{self.class} #{to_s}>"
end
def pretty_print(q)
q.group 2, "#<#{self.class}", ">" do
q.breakable
q.text to_s
q.breakable
q.text " caused by "
q.pp @cause
end
end
private
def cleanup_terms(terms)
terms.each do |term|
raise "#{term.inspect} must be a term" unless term.is_a?(Term)
end
if terms.length != 1 && ConflictCause === cause
terms = terms.reject do |term|
term.positive? && Package.root?(term.package)
end
end
# Optimized simple cases
return terms if terms.length <= 1
return terms if terms.length == 2 && terms[0].package != terms[1].package
terms.group_by(&:package).map do |package, common_terms|
common_terms.inject do |acc, term|
acc.intersect(term)
end
end
end
end
end
|