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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
require "dependable"
# A dependency on another Homebrew formula.
class Dependency
extend Forwardable
include Dependable
attr_reader :name, :tags, :env_proc, :option_names
DEFAULT_ENV_PROC = proc {}
def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name])
@name = name
@tags = tags
@env_proc = env_proc
@option_names = option_names
end
def to_s
name
end
def ==(other)
instance_of?(other.class) && name == other.name && tags == other.tags
end
alias eql? ==
def hash
name.hash ^ tags.hash
end
def to_formula
formula = Formulary.factory(name)
formula.build = BuildOptions.new(options, formula.options)
formula
end
delegate installed?: :to_formula
def satisfied?(inherited_options)
installed? && missing_options(inherited_options).empty?
end
def missing_options(inherited_options)
formula = to_formula
required = options
required |= inherited_options
required &= formula.options.to_a
required -= Tab.for_formula(formula).used_options
required
end
def modify_build_environment
env_proc&.call
end
def inspect
"#<#{self.class.name}: #{name.inspect} #{tags.inspect}>"
end
# Define marshaling semantics because we cannot serialize @env_proc
def _dump(*)
Marshal.dump([name, tags])
end
def self._load(marshaled)
new(*Marshal.load(marshaled)) # rubocop:disable Security/MarshalLoad
end
class << self
# Expand the dependencies of dependent recursively, optionally yielding
# [dependent, dep] pairs to allow callers to apply arbitrary filters to
# the list.
# The default filter, which is applied when a block is not given, omits
# optionals and recommendeds based on what the dependent has asked for.
def expand(dependent, deps = dependent.deps, &block)
# Keep track dependencies to avoid infinite cyclic dependency recursion.
@expand_stack ||= []
@expand_stack.push dependent.name
expanded_deps = []
deps.each do |dep|
next if dependent.name == dep.name
case action(dependent, dep, &block)
when :prune
next
when :skip
next if @expand_stack.include? dep.name
expanded_deps.concat(expand(dep.to_formula, &block))
when :keep_but_prune_recursive_deps
expanded_deps << dep
else
next if @expand_stack.include? dep.name
expanded_deps.concat(expand(dep.to_formula, &block))
expanded_deps << dep
end
end
merge_repeats(expanded_deps)
ensure
@expand_stack.pop
end
def action(dependent, dep, &_block)
catch(:action) do
if block_given?
yield dependent, dep
elsif dep.optional? || dep.recommended?
prune unless dependent.build.with?(dep)
end
end
end
# Prune a dependency and its dependencies recursively
def prune
throw(:action, :prune)
end
# Prune a single dependency but do not prune its dependencies
def skip
throw(:action, :skip)
end
# Keep a dependency, but prune its dependencies
def keep_but_prune_recursive_deps
throw(:action, :keep_but_prune_recursive_deps)
end
def merge_repeats(all)
grouped = all.group_by(&:name)
all.map(&:name).uniq.map do |name|
deps = grouped.fetch(name)
dep = deps.first
tags = merge_tags(deps)
option_names = deps.flat_map(&:option_names).uniq
dep.class.new(name, tags, dep.env_proc, option_names)
end
end
private
def merge_tags(deps)
options = deps.flat_map(&:option_tags).uniq
merge_necessity(deps) + merge_temporality(deps) + options
end
def merge_necessity(deps)
# Cannot use `deps.any?(&:required?)` here due to its definition.
if deps.any? { |dep| !dep.recommended? && !dep.optional? }
[] # Means required dependency.
elsif deps.any?(&:recommended?)
[:recommended]
else # deps.all?(&:optional?)
[:optional]
end
end
def merge_temporality(deps)
if deps.all?(&:build?)
[:build]
elsif deps.all?(&:run?)
[:run]
else
[] # Means both build and runtime dependency.
end
end
end
end
class TapDependency < Dependency
attr_reader :tap
def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name.split("/").last])
@tap = Tap.fetch(name.rpartition("/").first)
super(name, tags, env_proc, option_names)
end
def installed?
super
rescue FormulaUnavailableError
false
end
end
|