brew/Library/Homebrew/formulary.rb

320 lines
8.7 KiB
Ruby
Raw Normal View History

require "digest/md5"
require "formula_renames"
# The Formulary is responsible for creating instances of Formula.
2014-05-02 07:39:23 -07:00
# It is not meant to be used directy from formulae.
class Formulary
FORMULAE = {}
2015-01-01 01:21:59 -05:00
def self.formula_class_defined?(path)
FORMULAE.key?(path)
2013-06-18 10:11:06 -07:00
end
def self.formula_class_get(path)
FORMULAE.fetch(path)
2013-06-18 10:11:06 -07:00
end
def self.load_formula(name, path)
mod = Module.new
const_set("FormulaNamespace#{Digest::MD5.hexdigest(path.to_s)}", mod)
2015-06-06 18:10:47 -04:00
contents = path.open("r") { |f| set_encoding(f).read }
mod.module_eval(contents, path)
class_name = class_s(name)
begin
klass = mod.const_get(class_name)
rescue NameError => e
raise FormulaUnavailableError, name, e.backtrace
else
FORMULAE[path] = klass
end
end
2015-06-06 18:10:47 -04:00
if IO.method_defined?(:set_encoding)
def self.set_encoding(io)
io.set_encoding(Encoding::UTF_8)
end
else
def self.set_encoding(io)
io
end
end
def self.class_s(name)
class_name = name.capitalize
class_name.gsub!(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase }
class_name.tr!("+", "x")
class_name
2014-02-21 00:43:58 -05:00
end
2013-06-18 10:11:06 -07:00
# A FormulaLoader returns instances of formulae.
# Subclasses implement loaders for particular sources of formulae.
class FormulaLoader
# The formula's name
attr_reader :name
# The formula's ruby file's path or filename
attr_reader :path
def initialize(name, path)
@name = name
@path = path.resolved_path
end
2013-06-18 10:11:06 -07:00
# Gets the formula instance.
def get_formula(spec)
klass.new(name, path, spec)
end
2013-06-18 10:11:06 -07:00
def klass
load_file unless Formulary.formula_class_defined?(path)
Formulary.formula_class_get(path)
2013-06-18 10:11:06 -07:00
end
2014-12-29 14:53:22 -05:00
private
def load_file
STDERR.puts "#{$0} (#{self.class.name}): loading #{path}" if ARGV.debug?
raise FormulaUnavailableError.new(name) unless path.file?
Formulary.load_formula(name, path)
2014-12-29 14:53:22 -05:00
end
2013-06-18 10:11:06 -07:00
end
2013-06-18 10:11:06 -07:00
# Loads formulae from bottles.
class BottleLoader < FormulaLoader
def initialize(bottle_name)
2013-06-18 10:11:06 -07:00
@bottle_filename = Pathname(bottle_name).realpath
name, full_name = bottle_resolve_formula_names @bottle_filename
super name, Formulary.path(full_name)
2013-06-18 10:11:06 -07:00
end
def get_formula(spec)
formula = super
formula.local_bottle_path = @bottle_filename
formula_version = formula.pkg_version
bottle_version = bottle_resolve_version(@bottle_filename)
unless formula_version == bottle_version
raise BottleVersionMismatchError.new(@bottle_filename, bottle_version, formula, formula_version)
end
formula
2013-06-18 10:11:06 -07:00
end
end
class AliasLoader < FormulaLoader
def initialize(alias_path)
path = alias_path.resolved_path
name = path.basename(".rb").to_s
super name, path
end
end
2013-06-18 10:11:06 -07:00
# Loads formulae from disk using a path
class FromPathLoader < FormulaLoader
def initialize(path)
path = Pathname.new(path).expand_path
super path.basename(".rb").to_s, path
2013-06-18 10:11:06 -07:00
end
end
# Loads formulae from URLs
2013-06-18 10:11:06 -07:00
class FromUrlLoader < FormulaLoader
attr_reader :url
def initialize(url)
2013-06-18 10:11:06 -07:00
@url = url
uri = URI(url)
formula = File.basename(uri.path, ".rb")
super formula, HOMEBREW_CACHE_FORMULA/File.basename(uri.path)
2013-06-18 10:11:06 -07:00
end
def load_file
HOMEBREW_CACHE_FORMULA.mkpath
FileUtils.rm_f(path)
curl url, "-o", path
super
end
2013-06-18 10:11:06 -07:00
end
2013-06-18 10:11:06 -07:00
# Loads tapped formulae.
class TapLoader < FormulaLoader
attr_reader :tap
2014-04-05 22:03:33 -05:00
def initialize(tapped_name)
2014-04-05 22:03:33 -05:00
user, repo, name = tapped_name.split("/", 3).map(&:downcase)
@tap = Tap.new user, repo.sub(/^homebrew-/, "")
name = @tap.formula_renames.fetch(name, name)
path = @tap.formula_files.detect { |file| file.basename(".rb").to_s == name }
path ||= @tap.path/"#{name}.rb"
2014-04-05 22:03:33 -05:00
super name, path
end
def get_formula(spec)
super
rescue FormulaUnavailableError => e
raise TapFormulaUnavailableError.new(tap, name), "", e.backtrace
2013-06-18 10:11:06 -07:00
end
end
class NullLoader < FormulaLoader
def initialize(name)
super name, Formulary.core_path(name)
end
def get_formula(_spec)
raise FormulaUnavailableError.new(name)
end
end
2013-06-18 10:11:06 -07:00
# 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
def self.factory(ref, spec = :stable)
loader_for(ref).get_formula(spec)
end
2015-05-16 11:26:26 +08:00
# Return a Formula instance for the given rack.
2015-07-30 16:33:19 +08:00
# It will auto resolve formula's spec when requested spec is nil
def self.from_rack(rack, spec = nil)
2015-05-16 11:26:26 +08:00
kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : []
keg = kegs.detect(&:linked?) || kegs.detect(&:optlinked?) || kegs.max_by(&:version)
2015-07-30 16:33:19 +08:00
return factory(rack.basename.to_s, spec || :stable) unless keg
2015-05-16 11:26:26 +08:00
2015-07-30 16:33:19 +08:00
tab = Tab.for_keg(keg)
tap = tab.tap
spec ||= tab.spec
2015-05-16 11:26:26 +08:00
if tap.nil? || tap == "Homebrew/homebrew"
2015-05-16 11:26:26 +08:00
factory(rack.basename.to_s, spec)
else
factory("#{tap.sub("homebrew-", "")}/#{rack.basename}", spec)
end
end
def self.to_rack(ref)
# First, check whether the rack with the given name exists.
if (rack = HOMEBREW_CELLAR/File.basename(ref, ".rb")).directory?
2015-08-15 16:12:42 +08:00
return rack.resolved_path
end
# Second, use canonical name to locate rack.
2015-08-15 16:12:42 +08:00
(HOMEBREW_CELLAR/canonical_name(ref)).resolved_path
end
2014-04-05 22:03:34 -05:00
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
2014-04-05 22:03:34 -05:00
end
def self.path(ref)
loader_for(ref).path
end
def self.loader_for(ref)
case ref
when %r{(https?|ftp)://}
return FromUrlLoader.new(ref)
when Pathname::BOTTLE_EXTNAME_RX
return BottleLoader.new(ref)
when HOMEBREW_CORE_FORMULA_REGEX
name = $1
formula_with_that_name = core_path(name)
if (newname = FORMULA_RENAMES[name]) && !formula_with_that_name.file?
return FormulaLoader.new(newname, core_path(newname))
else
return FormulaLoader.new(name, formula_with_that_name)
end
2014-04-05 22:03:33 -05:00
when HOMEBREW_TAP_FORMULA_REGEX
return TapLoader.new(ref)
end
if File.extname(ref) == ".rb"
return FromPathLoader.new(ref)
end
formula_with_that_name = core_path(ref)
if formula_with_that_name.file?
2015-01-01 01:21:59 -05:00
return FormulaLoader.new(ref, formula_with_that_name)
end
possible_alias = Pathname.new("#{HOMEBREW_LIBRARY}/Aliases/#{ref}")
if possible_alias.file?
return AliasLoader.new(possible_alias)
end
2015-08-22 13:15:33 +08:00
possible_tap_formulae = tap_paths(ref)
2015-05-08 19:16:06 +08:00
if possible_tap_formulae.size > 1
raise TapFormulaAmbiguityError.new(ref, possible_tap_formulae)
elsif possible_tap_formulae.size == 1
return FormulaLoader.new(ref, possible_tap_formulae.first)
end
if newref = 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)
elsif !possible_tap_newname_formulae.empty?
return TapLoader.new(possible_tap_newname_formulae.first)
end
possible_cached_formula = Pathname.new("#{HOMEBREW_CACHE_FORMULA}/#{ref}.rb")
if possible_cached_formula.file?
2015-01-01 01:21:59 -05:00
return FormulaLoader.new(ref, possible_cached_formula)
end
NullLoader.new(ref)
end
def self.core_path(name)
Pathname.new("#{HOMEBREW_LIBRARY}/Formula/#{name.downcase}.rb")
end
2015-05-08 19:16:06 +08:00
2015-08-22 13:15:33 +08:00
def self.tap_paths(name, taps = Dir["#{HOMEBREW_LIBRARY}/Taps/*/*/"])
2015-05-08 19:16:06 +08:00
name = name.downcase
2015-08-09 22:43:01 +08:00
taps.map do |tap|
Pathname.glob([
"#{tap}Formula/#{name}.rb",
"#{tap}HomebrewFormula/#{name}.rb",
"#{tap}#{name}.rb"
]).detect(&:file?)
end.compact
2015-05-08 19:16:06 +08:00
end
2015-08-09 22:43:01 +08:00
2015-08-22 13:15:33 +08:00
def self.find_with_priority(ref, spec = :stable)
2015-08-09 22:43:01 +08:00
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)
elsif possible_pinned_tap_formulae.size == 1
selected_formula = factory(possible_pinned_tap_formulae.first, spec)
if core_path(ref).file?
opoo <<-EOS.undent
#{ref} is provided by core, but is now shadowed by #{selected_formula.full_name}.
To refer to the core formula, use Homebrew/homebrew/#{ref} instead.
EOS
end
selected_formula
else
factory(ref, spec)
end
end
end