Module: Formulary Private

Extended by:
Cachable
Defined in:
formulary.rb

Overview

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

The Formulary is responsible for creating instances of Formula. It is not meant to be used directly from formulae.

Defined Under Namespace

Classes: AliasAPILoader, AliasLoader, BottleLoader, FormulaAPILoader, FormulaContentsLoader, FormulaLoader, FromPathLoader, FromUrlLoader, NullLoader, TapLoader

Constant Summary collapse

URL_START_REGEX =

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

%r{(https?|ftp|file)://}.freeze

Class Method Summary collapse

Methods included from Cachable

cache, clear_cache

Class Method Details

.canonical_name(ref) ⇒ Object

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.



645
646
647
648
649
650
651
# File 'formulary.rb', line 645

def self.canonical_name(ref)
  loader_for(ref).name
rescue TapFormulaAmbiguityError
  # If there are multiple tap formulae with the name of ref,
  # then ref is the canonical name
  ref.downcase
end

.class_s(name) ⇒ Object

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.



263
264
265
266
267
268
269
# File 'formulary.rb', line 263

def self.class_s(name)
  class_name = name.capitalize
  class_name.gsub!(/[-_.\s]([a-zA-Z0-9])/) { Regexp.last_match(1).upcase }
  class_name.tr!("+", "x")
  class_name.sub!(/(.)@(\d)/, "\\1AT\\2")
  class_name
end

.clear_cacheObject

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.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'formulary.rb', line 47

def self.clear_cache
  cache.each do |type, cached_objects|
    next if type == :formulary_factory

    cached_objects.each_value do |klass|
      namespace = klass.name.deconstantize
      next if namespace.deconstantize != name

      remove_const(namespace.demodulize)
    end
  end

  super
end

.convert_to_deprecate_disable_reason_string_or_symbol(string) ⇒ Object

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.



277
278
279
280
281
282
# File 'formulary.rb', line 277

def self.convert_to_deprecate_disable_reason_string_or_symbol(string)
  require "deprecate_disable"
  return string unless DeprecateDisable::DEPRECATE_DISABLE_REASONS.keys.map(&:to_s).include?(string)

  string.to_sym
end

.convert_to_string_or_symbol(string) ⇒ Object

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.



271
272
273
274
275
# File 'formulary.rb', line 271

def self.convert_to_string_or_symbol(string)
  return string[1..].to_sym if string.start_with?(":")

  string
end

.core_alias_path(name) ⇒ Object

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.



724
725
726
# File 'formulary.rb', line 724

def self.core_alias_path(name)
  CoreTap.instance.alias_dir/name.to_s.downcase
end

.core_path(name) ⇒ Object

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.



720
721
722
# File 'formulary.rb', line 720

def self.core_path(name)
  CoreTap.instance.formula_dir/"#{name.to_s.downcase}.rb"
end

.enable_factory_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.



23
24
25
# File 'formulary.rb', line 23

def self.enable_factory_cache!
  @factory_cache = true
end

.ensure_utf8_encoding(io) ⇒ Object

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.



259
260
261
# File 'formulary.rb', line 259

def self.ensure_utf8_encoding(io)
  io.set_encoding(Encoding::UTF_8)
end

.factory(ref, spec = :stable, alias_path: nil, from: nil, force_bottle: false, flags: [], ignore_errors: false) ⇒ Object

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.

Return a Formula instance for the given reference. ref is a string containing:

  • a formula name
  • a formula pathname
  • a formula URL
  • a local bottle reference

Raises:

  • (ArgumentError)


548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'formulary.rb', line 548

def self.factory(
  ref, spec = :stable, alias_path: nil, from: nil,
  force_bottle: false, flags: [], ignore_errors: false
)
  raise ArgumentError, "Formulae must have a ref!" unless ref

  cache_key = "#{ref}-#{spec}-#{alias_path}-#{from}"
  if factory_cached? && cache[:formulary_factory] &&
     cache[:formulary_factory][cache_key]
    return cache[:formulary_factory][cache_key]
  end

  formula = loader_for(ref, from: from).get_formula(spec, alias_path: alias_path,
                                                    force_bottle: force_bottle, flags: flags,
                                                    ignore_errors: ignore_errors)
  if factory_cached?
    cache[:formulary_factory] ||= {}
    cache[:formulary_factory][cache_key] ||= formula
  end
  formula
end

.factory_cached?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)


27
28
29
# File 'formulary.rb', line 27

def self.factory_cached?
  !@factory_cache.nil?
end

.formula_class_defined_from_api?(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.

Returns:

  • (Boolean)


35
36
37
# File 'formulary.rb', line 35

def self.formula_class_defined_from_api?(name)
  cache.key?(:api) && cache[:api].key?(name)
end

.formula_class_defined_from_path?(path) ⇒ 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)


31
32
33
# File 'formulary.rb', line 31

def self.formula_class_defined_from_path?(path)
  cache.key?(:path) && cache[:path].key?(path)
end

.formula_class_get_from_api(name) ⇒ Object

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.



43
44
45
# File 'formulary.rb', line 43

def self.formula_class_get_from_api(name)
  cache[:api].fetch(name)
end

.formula_class_get_from_path(path) ⇒ Object

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.



39
40
41
# File 'formulary.rb', line 39

def self.formula_class_get_from_path(path)
  cache[:path].fetch(path)
end

.from_contents(name, path, contents, spec = :stable, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false) ⇒ Object

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.

Return a Formula instance directly from contents.



623
624
625
626
627
628
629
630
# File 'formulary.rb', line 623

def self.from_contents(
  name, path, contents, spec = :stable, alias_path: nil,
  force_bottle: false, flags: [], ignore_errors: false
)
  FormulaContentsLoader.new(name, path, contents)
                       .get_formula(spec, alias_path: alias_path, force_bottle: force_bottle,
                                    flags: flags, ignore_errors: ignore_errors)
end

.from_keg(keg, spec = nil, alias_path: nil, force_bottle: false, flags: []) ⇒ Object

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.

Return a Formula instance for the given keg.

Parameters:

  • spec (defaults to: nil)

    when nil, will auto resolve the formula’s spec.



598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'formulary.rb', line 598

def self.from_keg(keg, spec = nil, alias_path: nil, force_bottle: false, flags: [])
  tab = Tab.for_keg(keg)
  tap = tab.tap
  spec ||= tab.spec

  f = if tap.nil?
    factory(keg.rack.basename.to_s, spec, alias_path: alias_path, from: :keg,
            force_bottle: force_bottle, flags: flags)
  else
    begin
      factory("#{tap}/#{keg.rack.basename}", spec, alias_path: alias_path, from: :keg,
              force_bottle: force_bottle, flags: flags)
    rescue FormulaUnavailableError
      # formula may be migrated to different tap. Try to search in core and all taps.
      factory(keg.rack.basename.to_s, spec, alias_path: alias_path, from: :keg,
              force_bottle: force_bottle, flags: flags)
    end
  end
  f.build = tab
  f.build.used_options = Tab.remap_deprecated_options(f.deprecated_options, tab.used_options).as_flags
  f.version.update_commit(keg.version.version.commit) if f.head? && keg.version.head?
  f
end

.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags: []) ⇒ Object

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.

Return a Formula instance for the given rack.

Parameters:

  • spec (defaults to: nil)

    when nil, will auto resolve the formula’s spec.

  • :alias_path

    will be used if the formula is found not to be installed, and discarded if it is installed because the alias_path used to install the formula will be set instead.



576
577
578
579
580
581
582
583
584
585
586
# File 'formulary.rb', line 576

def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags: [])
  kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : []
  keg = kegs.find(&:linked?) || kegs.find(&:optlinked?) || kegs.max_by(&:version)

  if keg
    from_keg(keg, spec, alias_path: alias_path, force_bottle: force_bottle, flags: flags)
  else
    factory(rack.basename.to_s, spec || :stable, alias_path: alias_path, from: :rack,
            force_bottle: force_bottle, flags: flags)
  end
end

.keg_only?(rack) ⇒ 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.

Return whether given rack is keg-only.

Returns:

  • (Boolean)


589
590
591
592
593
# File 'formulary.rb', line 589

def self.keg_only?(rack)
  Formulary.from_rack(rack).keg_only?
rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
  false
end

.load_formula(name, path, contents, namespace, flags:, ignore_errors:) ⇒ Object

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.



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
# File 'formulary.rb', line 76

def self.load_formula(name, path, contents, namespace, flags:, ignore_errors:)
  raise "Formula loading disabled by HOMEBREW_DISABLE_LOAD_FORMULA!" if Homebrew::EnvConfig.disable_load_formula?

  require "formula"
  require "ignorable"

  mod = Module.new
  remove_const(namespace) if const_defined?(namespace)
  const_set(namespace, mod)

  eval_formula = lambda do
    # Set `BUILD_FLAGS` in the formula's namespace so we can
    # access them from within the formula's class scope.
    mod.const_set(:BUILD_FLAGS, flags)
    mod.module_eval(contents, path)
  rescue NameError, ArgumentError, ScriptError, MethodDeprecatedError, MacOSVersionError => e
    if e.is_a?(Ignorable::ExceptionMixin)
      e.ignore
    else
      remove_const(namespace)
      raise FormulaUnreadableError.new(name, e)
    end
  end
  if ignore_errors
    Ignorable.hook_raise(&eval_formula)
  else
    eval_formula.call
  end

  class_name = class_s(name)

  begin
    mod.const_get(class_name)
  rescue NameError => e
    class_list = mod.constants
                    .map { |const_name| mod.const_get(const_name) }
                    .select { |const| const.is_a?(Class) }
    new_exception = FormulaClassUnavailableError.new(name, path, class_name, class_list)
    remove_const(namespace)
    raise new_exception, "", e.backtrace
  end
end

.load_formula_from_api(name, flags:) ⇒ Object

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.



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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'formulary.rb', line 127

def self.load_formula_from_api(name, flags:)
  namespace = "FormulaNamespaceAPI#{Digest::MD5.hexdigest(name)}"

  mod = Module.new
  remove_const(namespace) if const_defined?(namespace)
  const_set(namespace, mod)

  mod.const_set(:BUILD_FLAGS, flags)

  class_s = Formulary.class_s(name)
  json_formula = Homebrew::API::Formula.all_formulae[name]

  if (bottle_tag = Utils::Bottles.tag.to_s.presence) &&
     (variations = json_formula["variations"].presence) &&
     (variation = variations[bottle_tag].presence)
    json_formula = json_formula.merge(variation)
  end

  uses_from_macos_names = json_formula["uses_from_macos"].map do |dep|
    next dep unless dep.is_a? Hash

    dep.keys.first
  end

  klass = Class.new(::Formula) do
    desc json_formula["desc"]
    homepage json_formula["homepage"]
    license json_formula["license"]
    revision json_formula["revision"]
    version_scheme json_formula["version_scheme"]

    if (urls_stable = json_formula["urls"]["stable"]).present?
      stable do
        url urls_stable["url"]
        version json_formula["versions"]["stable"]
      end
    end

    if (bottles_stable = json_formula["bottle"]["stable"]).present?
      bottle do
        root_url bottles_stable["root_url"]
        rebuild bottles_stable["rebuild"]
        bottles_stable["files"].each do |tag, bottle_spec|
          cellar = Formulary.convert_to_string_or_symbol bottle_spec["cellar"]
          sha256 cellar: cellar, tag.to_sym => bottle_spec["sha256"]
        end
      end
    end

    if (keg_only_reason = json_formula["keg_only_reason"]).present?
      reason = Formulary.convert_to_string_or_symbol keg_only_reason["reason"]
      keg_only reason, keg_only_reason["explanation"]
    end

    if (deprecation_date = json_formula["deprecation_date"]).present?
      reason = Formulary.convert_to_deprecate_disable_reason_string_or_symbol json_formula["deprecation_reason"]
      deprecate! date: deprecation_date, because: reason
    end

    if (disable_date = json_formula["disable_date"]).present?
      reason = Formulary.convert_to_deprecate_disable_reason_string_or_symbol json_formula["disable_reason"]
      disable! date: disable_date, because: reason
    end

    json_formula["dependencies"].each do |dep|
      next if uses_from_macos_names.include? dep

      depends_on dep
    end

    [:build, :test, :recommended, :optional].each do |type|
      json_formula["#{type}_dependencies"].each do |dep|
        next if uses_from_macos_names.include? dep

        depends_on dep => type
      end
    end

    json_formula["uses_from_macos"].each do |dep|
      dep = dep.deep_transform_values(&:to_sym) if dep.is_a?(Hash)
      uses_from_macos dep
    end

    def install
      raise "Cannot build from source from abstract formula."
    end

    @caveats_string = json_formula["caveats"]
    def caveats
      self.class.instance_variable_get(:@caveats_string)
    end
  end

  klass.loaded_from_api = true
  mod.const_set(class_s, klass)

  cache[:api] ||= {}
  cache[:api][name] = klass
end

.load_formula_from_path(name, path, flags:, ignore_errors:) ⇒ Object

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.



119
120
121
122
123
124
125
# File 'formulary.rb', line 119

def self.load_formula_from_path(name, path, flags:, ignore_errors:)
  contents = path.open("r") { |f| ensure_utf8_encoding(f).read }
  namespace = "FormulaNamespace#{Digest::MD5.hexdigest(path.to_s)}"
  klass = load_formula(name, path, contents, namespace, flags: flags, ignore_errors: ignore_errors)
  cache[:path] ||= {}
  cache[:path][path] = klass
end

.loader_for(ref, from: nil) ⇒ Object

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.



657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
# File 'formulary.rb', line 657

def self.loader_for(ref, from: nil)
  case ref
  when HOMEBREW_BOTTLES_EXTNAME_REGEX
    return BottleLoader.new(ref)
  when URL_START_REGEX
    return FromUrlLoader.new(ref)
  when HOMEBREW_TAP_FORMULA_REGEX
    if ref.start_with?("homebrew/core/") && Homebrew::EnvConfig.install_from_api?
      name = ref.split("/", 3).last
      return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
      return AliasAPILoader.new(name) if Homebrew::API::Formula.all_aliases.key?(name)
    end

    return TapLoader.new(ref, from: from)
  end

  pathname_ref = Pathname.new(ref)
  return FromPathLoader.new(ref) if File.extname(ref) == ".rb" && pathname_ref.expand_path.exist?

  if Homebrew::EnvConfig.install_from_api?
    return FormulaAPILoader.new(ref) if Homebrew::API::Formula.all_formulae.key?(ref)
    return AliasAPILoader.new(ref) if Homebrew::API::Formula.all_aliases.key?(ref)
  end

  formula_with_that_name = core_path(ref)
  return FormulaLoader.new(ref, formula_with_that_name) if formula_with_that_name.file?

  possible_alias = if pathname_ref.absolute?
    pathname_ref
  else
    core_alias_path(ref)
  end
  return AliasLoader.new(possible_alias) if possible_alias.symlink?

  possible_tap_formulae = tap_paths(ref)
  raise TapFormulaAmbiguityError.new(ref, possible_tap_formulae) if possible_tap_formulae.size > 1

  if possible_tap_formulae.size == 1
    path = possible_tap_formulae.first.resolved_path
    name = path.basename(".rb").to_s
    return FormulaLoader.new(name, path)
  end

  return TapLoader.new("#{CoreTap.instance}/#{ref}", from: from) if CoreTap.instance.formula_renames.key?(ref)

  possible_taps = Tap.select { |tap| tap.formula_renames.key?(ref) }

  if possible_taps.size > 1
    possible_tap_newname_formulae = possible_taps.map { |tap| "#{tap}/#{tap.formula_renames[ref]}" }
    raise TapFormulaWithOldnameAmbiguityError.new(ref, possible_tap_newname_formulae)
  end

  return TapLoader.new("#{possible_taps.first}/#{ref}", from: from) unless possible_taps.empty?

  possible_keg_formula = Pathname.new("#{HOMEBREW_PREFIX}/opt/#{ref}/.brew/#{ref}.rb")
  return FormulaLoader.new(ref, possible_keg_formula) if possible_keg_formula.file?

  possible_cached_formula = Pathname.new("#{HOMEBREW_CACHE_FORMULA}/#{ref}.rb")
  return FormulaLoader.new(ref, possible_cached_formula) if possible_cached_formula.file?

  NullLoader.new(ref)
end

.path(ref) ⇒ Object

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.



653
654
655
# File 'formulary.rb', line 653

def self.path(ref)
  loader_for(ref).path
end

.resolve(name, spec: nil, force_bottle: false, flags: []) ⇒ Object

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.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'formulary.rb', line 227

def self.resolve(name, spec: nil, force_bottle: false, flags: [])
  if name.include?("/") || File.exist?(name)
    f = factory(name, *spec, force_bottle: force_bottle, flags: flags)
    if f.any_version_installed?
      tab = Tab.for_formula(f)
      resolved_spec = spec || tab.spec
      f.active_spec = resolved_spec if f.send(resolved_spec)
      f.build = tab
      if f.head? && tab.tabfile
        k = Keg.new(tab.tabfile.parent)
        f.version.update_commit(k.version.version.commit) if k.version.head?
      end
    end
  else
    rack = to_rack(name)
    alias_path = factory(name, force_bottle: force_bottle, flags: flags).alias_path
    f = from_rack(rack, *spec, alias_path: alias_path, force_bottle: force_bottle, flags: flags)
  end

  # If this formula was installed with an alias that has since changed,
  # then it was specified explicitly in ARGV. (Using the alias would
  # instead have found the new formula.)
  #
  # Because of this, the user is referring to this specific formula,
  # not any formula targeted by the same alias, so in this context
  # the formula shouldn't be considered outdated if the alias used to
  # install it has changed.
  f.follow_installed_alias = false

  f
end

.tap_paths(name, taps = ) ⇒ Object

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.



728
729
730
731
732
733
734
735
736
737
738
# File 'formulary.rb', line 728

def self.tap_paths(name, taps = Dir[HOMEBREW_LIBRARY/"Taps/*/*/"])
  name = name.to_s.downcase
  taps.map do |tap|
    Pathname.glob([
      "#{tap}Formula/#{name}.rb",
      "#{tap}HomebrewFormula/#{name}.rb",
      "#{tap}#{name}.rb",
      "#{tap}Aliases/#{name}",
    ]).find(&:file?)
  end.compact
end

.to_rack(ref) ⇒ Object

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.



632
633
634
635
636
637
638
639
640
641
642
643
# File 'formulary.rb', line 632

def self.to_rack(ref)
  # If using a fully-scoped reference, check if the formula can be resolved.
  factory(ref) if ref.include? "/"

  # Check whether the rack with the given name exists.
  if (rack = HOMEBREW_CELLAR/File.basename(ref, ".rb")).directory?
    return rack.resolved_path
  end

  # Use canonical name to locate rack.
  (HOMEBREW_CELLAR/canonical_name(ref)).resolved_path
end