Class: Dependency

Inherits:
Object show all
Includes:
Dependable
Defined in:
dependency.rb

Overview

This class is part of an internal API. This class may only be used internally in repositories owned by Homebrew, except in casks or formulae. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

A dependency on another Homebrew formula.

Direct Known Subclasses

UsesFromMacOSDependency

Constant Summary

Constants included from Dependable

Dependable::KEEP_BUT_PRUNE_RECURSIVE_DEPS, Dependable::PRUNE, Dependable::RESERVED_TAGS, Dependable::SKIP

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Dependable

#build?, #implicit?, #no_linkage?, #option_tags, #optional?, #options, #prune_from_option?, #prune_if_build_and_not_dependent?, #recommended?, #required?, #test?

Constructor Details

#initialize(name, tags = []) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:



23
24
25
26
27
28
29
30
31
# File 'dependency.rb', line 23

def initialize(name, tags = [])
  @name = name
  @tags = T.let(Array(tags), T::Array[T.any(Symbol, String)])
  @tap = T.let(nil, T.nilable(Tap))

  return unless (tap_with_name = Tap.with_formula_name(name))

  @tap, = tap_with_name
end

Class Attribute Details

.expand_stack ⇒ Array<String, Symbol>? (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



183
184
185
# File 'dependency.rb', line 183

def expand_stack
  @expand_stack
end

Instance Attribute Details

#name ⇒ String (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



14
15
16
# File 'dependency.rb', line 14

def name
  @name
end

#tags ⇒ Array<Symbol, String, Array<T.untyped>> (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



20
21
22
# File 'dependency.rb', line 20

def tags
  @tags
end

#tap ⇒ Tap? (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



17
18
19
# File 'dependency.rb', line 17

def tap
  @tap
end

Class Method Details

.action(dependent, dep, &block) ⇒ Symbol?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

CaskDependent may not be initialized yet, so we don't use a runtime sig

Parameters:

Returns:



261
262
263
264
265
266
267
# File 'dependency.rb', line 261

def action(dependent, dep, &block)
  if block
    yield dependent, dep
  elsif dep.optional? || dep.recommended?
    Dependable::PRUNE unless T.cast(dependent, Formula).build.with?(dep)
  end
end

.cache(key, cache_timestamp: nil) ⇒ Hash{String, Symbol => T.untyped}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • key (String, nil)
  • cache_timestamp (Time, nil) (defaults to: nil)

Returns:



286
287
288
289
290
291
292
293
294
295
296
# File 'dependency.rb', line 286

def cache(key, cache_timestamp: nil)
  @cache = T.let(@cache, T.nilable(T::Hash[Symbol, T.untyped]))
  @cache ||= { timestamped: {}, not_timestamped: {} }

  if cache_timestamp
    @cache[:timestamped][cache_timestamp] ||= {}
    @cache[:timestamped][cache_timestamp][key] ||= {}
  else
    @cache[:not_timestamped][key] ||= {}
  end
end

.clear_cache ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



299
300
301
302
303
304
305
# File 'dependency.rb', line 299

def clear_cache
  return unless @cache

  # No need to clear the timestamped cache as it's timestamped, and doing so causes problems in `expand`.
  # See https://github.com/Homebrew/brew/pull/20896#issuecomment-3419257460
  @cache[:not_timestamped].clear
end

.delete_timestamped_cache_entry(key, cache_timestamp) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



308
309
310
311
312
313
314
# File 'dependency.rb', line 308

def delete_timestamped_cache_entry(key, cache_timestamp)
  return unless @cache
  return unless (timestamp_entry = @cache[:timestamped][cache_timestamp])

  timestamp_entry.delete(key)
  @cache[:timestamped].delete(cache_timestamp) if timestamp_entry.empty?
end

.expand(dependent, deps = dependent.deps, cache_key: nil, cache_timestamp: nil, formula_cache: nil, &block) ⇒ Array<Dependency>

This method is part of an internal API. This method may only be used internally in repositories owned by Homebrew, except in casks or formulae. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Expand the dependencies of each 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 recommends based on what the dependent has asked for

Parameters:

Returns:

Raises:

  • (ArgumentError)


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'dependency.rb', line 204

def expand(dependent, deps = dependent.deps, cache_key: nil, cache_timestamp: nil, formula_cache: nil, &block)
  raise ArgumentError, "formula_cache requires cache_key" if formula_cache && cache_key.blank?

  # Keep track dependencies to avoid infinite cyclic dependency recursion.
  @expand_stack ||= T.let([], T.nilable(T::Array[T.any(String, Symbol)]))
  @expand_stack.push dependent.name

  begin
    if cache_key.present? && (entry = cache(cache_key, cache_timestamp:)[cache_id dependent])
      return entry.dup
    end

    expanded_deps = []

    deps.each do |dep|
      next if dependent.name == dep.name

      case action(dependent, dep, &block)
      when Dependable::PRUNE
        next
      when Dependable::SKIP
        next if @expand_stack.include? dep.name

        expanded_deps.concat(expand(formula_for_dependency(dep, formula_cache),
                                    cache_key:, cache_timestamp:, formula_cache:,
                                    &block))
      when Dependable::KEEP_BUT_PRUNE_RECURSIVE_DEPS
        expanded_deps << dep
      else
        next if @expand_stack.include? dep.name

        dep_formula = formula_for_dependency(dep, formula_cache)
        expanded_deps.concat(expand(dep_formula, cache_key:, cache_timestamp:, formula_cache:, &block))

        # Fixes names for renamed/aliased formulae.
        dep = dep.dup_with_formula_name(dep_formula)
        expanded_deps << dep
      end
    end

    expanded_deps = merge_repeats(expanded_deps)
    cache(cache_key, cache_timestamp:)[cache_id dependent] = expanded_deps.dup if cache_key.present?
    expanded_deps
  ensure
    @expand_stack.pop
  end
end

.merge_repeats(all) ⇒ Array<Dependency>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'dependency.rb', line 270

def merge_repeats(all)
  grouped = all.group_by(&:name)

  all.map(&:name).uniq.filter_map do |name|
    deps = grouped.fetch(name)
    dep  = deps.first
    next unless dep

    tags = merge_tags(deps)
    kwargs = {}
    kwargs[:bounds] = T.cast(dep, UsesFromMacOSDependency).bounds if dep.uses_from_macos?
    dep.class.new(name, tags, **kwargs)
  end
end

Instance Method Details

#dup_with_formula_name(formula) ⇒ T.self_type

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (T.self_type)


177
178
179
# File 'dependency.rb', line 177

def dup_with_formula_name(formula)
  self.class.new(formula.full_name.to_s, tags)
end

#formula_name? ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Whether #name is a formula name rather than a path, URL or bottle that Formulary would load. Names from install receipts and bottle manifests can be written by sandboxed package code.

Returns:

  • (Boolean)


69
70
71
72
73
74
# File 'dependency.rb', line 69

def formula_name?
  formula_name = tap ? Utils.name_from_full_name(name) : name
  formula_name.match?(/\A#{HOMEBREW_TAP_FORMULA_NAME_REGEX}\z/o) &&
    Utils::INVALID_PATH_COMPONENTS.exclude?(formula_name) &&
    File.extname(formula_name) != ".rb" && !formula_name.match?(HOMEBREW_BOTTLES_EXTNAME_REGEX)
end

#installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil, bottle_os_version: nil) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • minimum_version (Version, nil) (defaults to: nil)
  • minimum_revision (Integer, nil) (defaults to: nil)
  • minimum_compatibility_version (Integer, nil) (defaults to: nil)
  • bottle_os_version (String, nil) (defaults to: nil)

Returns:

  • (Boolean)


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
# File 'dependency.rb', line 84

def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil,
               bottle_os_version: nil)
  formula = begin
    to_installed_formula
  rescue FormulaUnavailableError
    nil
  end
  return false unless formula

  # If the opt prefix doesn't exist: we likely have an incomplete installation.
  return false unless formula.opt_prefix.exist?

  return true if formula.latest_version_installed?

  return false if minimum_version.blank? && minimum_compatibility_version.blank?

  installed_keg = formula.any_installed_keg
  return false unless installed_keg

  # If the keg name doesn't match, we may have moved from an alias to a full formula and need to upgrade.
  return false unless formula.possible_names.include?(installed_keg.name)

  # If both the formula and minimum dependency have a compatibility_version set,
  # and they match, the dependency is satisfied regardless of version/revision.
  if minimum_compatibility_version.present? && formula.compatibility_version.present?
    installed_tab = Tab.for_keg(installed_keg)
    installed_compatibility_version = installed_tab.source.dig("versions", "compatibility_version")

    # If installed version has same compatibility_version as required, it's compatible
    return true if installed_compatibility_version == minimum_compatibility_version &&
                   formula.compatibility_version == minimum_compatibility_version
  end

  return false if minimum_version.blank?

  installed_version = installed_keg.version

  # Tabs prior to 4.1.18 did not have revision or pkg_version fields.
  # As a result, we have to be more conversative when we do not have
  # a minimum revision from the tab and assume that if the formula has a
  # the same version and a non-zero revision that it needs upgraded.
  if minimum_revision.present?
    minimum_pkg_version = PkgVersion.new(minimum_version, minimum_revision)
    installed_version >= minimum_pkg_version
  elsif installed_version.version == minimum_version
    formula.revision.zero?
  else
    installed_version.version > minimum_version
  end
end

#missing_options ⇒ Options

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



150
151
152
153
154
155
156
# File 'dependency.rb', line 150

def missing_options
  formula = to_installed_formula
  required = options
  required &= formula.options.to_a
  required -= Tab.for_formula(formula).used_options
  required
end

#option_names ⇒ Array<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



159
160
161
# File 'dependency.rb', line 159

def option_names
  [Utils.name_from_full_name(name)].freeze
end

#satisfied?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil, bottle_os_version: nil) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • minimum_version (Version, nil) (defaults to: nil)
  • minimum_revision (Integer, nil) (defaults to: nil)
  • minimum_compatibility_version (Integer, nil) (defaults to: nil)
  • bottle_os_version (String, nil) (defaults to: nil)

Returns:

  • (Boolean)


143
144
145
146
147
# File 'dependency.rb', line 143

def satisfied?(minimum_version: nil, minimum_revision: nil,
               minimum_compatibility_version: nil, bottle_os_version: nil)
  installed?(minimum_version:, minimum_revision:, minimum_compatibility_version:, bottle_os_version:) &&
    missing_options.empty?
end

#to_formula ⇒ Formula

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

Raises:



58
59
60
61
62
63
64
# File 'dependency.rb', line 58

def to_formula
  raise FormulaUnavailableError, name unless formula_name?

  formula = Formulary.factory(name, warn: false)
  formula.build = BuildOptions.new(options, formula.options)
  formula
end

#to_installed_formula ⇒ Formula

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

Raises:



49
50
51
52
53
54
55
# File 'dependency.rb', line 49

def to_installed_formula
  raise FormulaUnavailableError, name unless formula_name?

  formula = Formulary.resolve(name)
  formula.build = BuildOptions.new(options, formula.options)
  formula
end

#uses_from_macos? ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


164
165
166
# File 'dependency.rb', line 164

def uses_from_macos?
  false
end