Module: Formulary

Extended by:
Cachable
Defined in:
Library/Homebrew/formulary.rb

Overview

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

Defined Under Namespace

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

Class Method Summary collapse

Methods included from Cachable

cache, clear_cache

Class Method Details

.canonical_name(ref) ⇒ Object



341
342
343
344
345
346
347
# File 'Library/Homebrew/formulary.rb', line 341

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



61
62
63
64
65
66
67
# File 'Library/Homebrew/formulary.rb', line 61

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

.core_path(name) ⇒ Object



421
422
423
# File 'Library/Homebrew/formulary.rb', line 421

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

.ensure_utf8_encoding(io) ⇒ Object



52
53
54
# File 'Library/Homebrew/formulary.rb', line 52

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

.factory(ref, spec = :stable, alias_path: nil, from: nil) ⇒ Object

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

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


279
280
281
# File 'Library/Homebrew/formulary.rb', line 279

def self.factory(ref, spec = :stable, alias_path: nil, from: nil)
  loader_for(ref, from: from).get_formula(spec, alias_path: alias_path)
end

.find_with_priority(ref, spec = :stable) ⇒ Object



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'Library/Homebrew/formulary.rb', line 437

def self.find_with_priority(ref, spec = :stable)
  possible_pinned_tap_formulae = tap_paths(ref, Dir["#{HOMEBREW_LIBRARY}/PinnedTaps/*/*/"]).map(&:realpath)
  if possible_pinned_tap_formulae.size > 1
    raise TapFormulaAmbiguityError.new(ref, possible_pinned_tap_formulae)
  end

  if possible_pinned_tap_formulae.size == 1
    selected_formula = factory(possible_pinned_tap_formulae.first, spec)
    if core_path(ref).file?
      opoo <<~EOS
        #{ref} is provided by core, but is now shadowed by #{selected_formula.full_name}.
        To refer to the core formula, use Homebrew/core/#{ref} instead.
      EOS
    end
    selected_formula
  else
    factory(ref, spec)
  end
end

.formula_class_defined?(path) ⇒ Boolean

Returns:

  • (Boolean)


11
12
13
# File 'Library/Homebrew/formulary.rb', line 11

def self.formula_class_defined?(path)
  cache.key?(path)
end

.formula_class_get(path) ⇒ Object



15
16
17
# File 'Library/Homebrew/formulary.rb', line 15

def self.formula_class_get(path)
  cache.fetch(path)
end

.from_contents(name, path, contents, spec = :stable) ⇒ Object

Return a Formula instance directly from contents



324
325
326
# File 'Library/Homebrew/formulary.rb', line 324

def self.from_contents(name, path, contents, spec = :stable)
  FormulaContentsLoader.new(name, path, contents).get_formula(spec)
end

.from_keg(keg, spec = nil, alias_path: nil) ⇒ Object

Return a Formula instance for the given keg. It will auto resolve formula's spec when requested spec is nil



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'Library/Homebrew/formulary.rb', line 302

def self.from_keg(keg, spec = nil, alias_path: nil)
  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)
  else
    begin
      factory("#{tap}/#{keg.rack.basename}", spec, alias_path: alias_path, from: :keg)
    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)
    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) ⇒ Object

Return a Formula instance for the given rack. It will auto resolve formula's spec when requested spec is nil

The :alias_path option 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.



289
290
291
292
293
294
295
296
297
298
# File 'Library/Homebrew/formulary.rb', line 289

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

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

.load_formula(name, path, contents, namespace) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'Library/Homebrew/formulary.rb', line 19

def self.load_formula(name, path, contents, namespace)
  if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"]
    raise "Formula loading disabled by HOMEBREW_DISABLE_LOAD_FORMULA!"
  end

  mod = Module.new
  const_set(namespace, mod)
  begin
    mod.module_eval(contents, path)
  rescue ScriptError => e
    raise FormulaUnreadableError.new(name, e)
  end
  class_name = class_s(name)

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

.load_formula_from_path(name, path) ⇒ Object



44
45
46
47
48
49
# File 'Library/Homebrew/formulary.rb', line 44

def self.load_formula_from_path(name, path)
  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)
  cache[path] = klass
end

.loader_for(ref, from: nil) ⇒ Object



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'Library/Homebrew/formulary.rb', line 353

def self.loader_for(ref, from: nil)
  case ref
  when Pathname::BOTTLE_EXTNAME_RX
    return BottleLoader.new(ref)
  when %r{(https?|ftp|file)://}
    return FromUrlLoader.new(ref)
  when HOMEBREW_TAP_FORMULA_REGEX
    return TapLoader.new(ref, from: from)
  end

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

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

  possible_alias = CoreTap.instance.alias_dir/ref
  return AliasLoader.new(possible_alias) if possible_alias.file?

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

  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

  if newref = CoreTap.instance.formula_renames[ref]
    formula_with_that_oldname = core_path(newref)
    if formula_with_that_oldname.file?
      return FormulaLoader.new(newref, formula_with_that_oldname)
    end
  end

  possible_tap_newname_formulae = []
  Tap.each do |tap|
    if newref = tap.formula_renames[ref]
      possible_tap_newname_formulae << "#{tap.name}/#{newref}"
    end
  end

  if possible_tap_newname_formulae.size > 1
    raise TapFormulaWithOldnameAmbiguityError.new(ref, possible_tap_newname_formulae)
  end

  unless possible_tap_newname_formulae.empty?
    return TapLoader.new(possible_tap_newname_formulae.first, from: from)
  end

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

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

  NullLoader.new(ref)
end

.path(ref) ⇒ Object



349
350
351
# File 'Library/Homebrew/formulary.rb', line 349

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

.tap_paths(name, taps = Dir[HOMEBREW_LIBRARY/"Taps/*/*/"]) ⇒ Object



425
426
427
428
429
430
431
432
433
434
435
# File 'Library/Homebrew/formulary.rb', line 425

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}",
                  ]).detect(&:file?)
  end.compact
end

.to_rack(ref) ⇒ Object



328
329
330
331
332
333
334
335
336
337
338
339
# File 'Library/Homebrew/formulary.rb', line 328

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