require 'download_strategy'
require 'dependencies'
require 'formula_support'
require 'hardware'
require 'bottles'
require 'extend/fileutils'
require 'patches'
require 'compilers'
class Formula
include FileUtils
attr_reader :name, :path, :url, :version, :homepage, :specs, :downloader
attr_reader :standard, :unstable, :head
attr_reader :bottle_version, :bottle_url, :bottle_sha1
attr_reader :buildpath
def initialize name='__UNKNOWN__', path=nil
set_instance_variable 'homepage'
set_instance_variable 'url'
set_instance_variable 'bottle_version'
set_instance_variable 'bottle_url'
set_instance_variable 'bottle_sha1'
set_instance_variable 'head'
set_instance_variable 'specs'
set_instance_variable 'standard'
set_instance_variable 'unstable'
if @head and (not @url or ARGV.build_head?)
@url = @head
@version = 'HEAD'
@spec_to_use = @unstable
else
if @standard.nil?
@spec_to_use = SoftwareSpecification.new(@url, @specs)
else
@spec_to_use = @standard
end
end
raise "No url provided for formula #{name}" if @url.nil?
@name = name
validate_variable :name
@path = path.nil? ? self.class.path(name) : Pathname.new(path)
set_instance_variable 'version'
@version ||= @spec_to_use.detect_version
validate_variable :version if @version
CHECKSUM_TYPES.each { |type| set_instance_variable type }
@downloader = download_strategy.new @spec_to_use.url, name, version, @spec_to_use.specs
@bottle_url ||= bottle_base_url + bottle_filename(self) if @bottle_sha1
end
def installed?
return installed_prefix.children.length > 0
rescue
return false
end
def explicitly_requested?
return false if ARGV.named.empty?
ARGV.formulae.include? self
end
def linked_keg
HOMEBREW_REPOSITORY/'Library/LinkedKegs'/@name
end
def installed_prefix
head_prefix = HOMEBREW_CELLAR+@name+'HEAD'
if @version == 'HEAD' || head_prefix.directory?
head_prefix
else
prefix
end
end
def prefix
validate_variable :name
validate_variable :version
HOMEBREW_CELLAR+@name+@version
end
def rack; prefix.parent end
def bin; prefix+'bin' end
def doc; prefix+'share/doc'+name end
def include; prefix+'include' end
def info; prefix+'share/info' end
def lib; prefix+'lib' end
def libexec; prefix+'libexec' end
def man; prefix+'share/man' end
def man1; man+'man1' end
def man2; man+'man2' end
def man3; man+'man3' end
def man4; man+'man4' end
def man5; man+'man5' end
def man6; man+'man6' end
def man7; man+'man7' end
def man8; man+'man8' end
def sbin; prefix+'sbin' end
def share; prefix+'share' end
def etc; HOMEBREW_PREFIX+'etc' end
def var; HOMEBREW_PREFIX+'var' end
def plist_name; 'homebrew.mxcl.'+name end
def plist_path; prefix+(plist_name+'.plist') end
def download_strategy
@spec_to_use.download_strategy
end
def cached_download
@downloader.cached_location
end
def caveats; nil end
def options; [] end
def patches; end
def keg_only?
self.class.keg_only_reason || false
end
def fails_with? cc
return false if self.class.cc_failures.nil?
cc = Compiler.new(cc) unless cc.is_a? Compiler
return self.class.cc_failures.find do |failure|
next unless failure.compiler == cc.name
failure.build.zero? or failure.build >= cc.build
end
end
def skip_clean? path
return true if self.class.skip_clean_all?
to_check = path.relative_path_from(prefix).to_s
self.class.skip_clean_paths.include? to_check
end
def brew
validate_variable :name
validate_variable :version
stage do
begin
patch
yield self
rescue Interrupt, RuntimeError, SystemCallError => e
puts if Interrupt === e unless ARGV.debug?
%w(config.log CMakeCache.txt).select{|f| File.exist? f}.each do |f|
HOMEBREW_LOGS.install f
puts "#{f} was copied to #{HOMEBREW_LOGS}"
end
raise
end
onoe e.inspect
puts e.backtrace
ohai "Rescuing build..."
if (e.was_running_configure? rescue false) and File.exist? 'config.log'
puts "It looks like an autotools configure failed."
puts "Gist 'config.log' and any error output when reporting an issue."
puts
end
puts "When you exit this shell Homebrew will attempt to finalise the installation."
puts "If nothing is installed or the shell exits with a non-zero error code,"
puts "Homebrew will abort. The installation prefix is:"
puts prefix
interactive_shell self
end
end
end
def == b
name == b.name
end
def eql? b
self == b and self.class.equal? b.class
end
def hash
name.hash
end
def <=> b
name <=> b.name
end
def to_s
name
end
def std_cmake_args
%W[
-DCMAKE_INSTALL_PREFIX=#{prefix}
-DCMAKE_BUILD_TYPE=None
-DCMAKE_FIND_FRAMEWORK=LAST
-Wno-dev
]
end
def self.class_s name
name.capitalize.gsub(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase } \
.gsub('+', 'x')
end
def self.names
Dir["#{HOMEBREW_REPOSITORY}/Library/Formula/*.rb"].map{ |f| File.basename f, '.rb' }.sort
end
def self.all
map{ |f| f }
end
def self.map
rv = []
each{ |f| rv << yield(f) }
rv
end
def self.each
names.each do |n|
begin
yield Formula.factory(n)
rescue
onoe "Formula #{n} will not import."
end
end
end
def inspect
name
end
def self.aliases
Dir["#{HOMEBREW_REPOSITORY}/Library/Aliases/*"].map{ |f| File.basename f }.sort
end
def self.canonical_name name
name = name.to_s if name.kind_of? Pathname
formula_with_that_name = HOMEBREW_REPOSITORY+"Library/Formula/#{name}.rb"
possible_alias = HOMEBREW_REPOSITORY+"Library/Aliases/#{name}"
possible_cached_formula = HOMEBREW_CACHE_FORMULA+"#{name}.rb"
if name.include? "/"
if name =~ %r{(.+)/(.+)/(.+)}
tapd = HOMEBREW_REPOSITORY/"Library/Taps"/"#$1-#$2".downcase
tapd.find_formula do |relative_pathname|
return "#{tapd}/#{relative_pathname}" if relative_pathname.stem.to_s == $3
end if tapd.directory?
end
name
elsif formula_with_that_name.file? and formula_with_that_name.readable?
name
elsif possible_alias.file?
possible_alias.realpath.basename('.rb').to_s
elsif possible_cached_formula.file?
possible_cached_formula.to_s
else
name
end
end
def self.factory name
return name if name.kind_of? Formula
name = name.to_s
if name =~ %r[(https?|ftp)://]
url = name
name = Pathname.new(name).basename
target_file = HOMEBREW_CACHE_FORMULA+name
name = name.basename(".rb").to_s
HOMEBREW_CACHE_FORMULA.mkpath
FileUtils.rm target_file, :force => true
curl url, '-o', target_file
require target_file
install_type = :from_url
else
name = Formula.canonical_name(name)
if name.include? "/"
require name
name = "#{name}.rb" unless name =~ /\.rb$/
path = Pathname.new(name)
name = path.stem
install_type = :from_path
target_file = path.to_s
else
require Formula.path(name)
install_type = :from_name
end
end
begin
klass_name = self.class_s(name)
klass = Object.const_get klass_name
rescue NameError
onoe "class \"#{klass_name}\" expected but not found in #{name}.rb"
puts "Double-check the name of the class in that formula."
raise LoadError
end
return klass.new(name) if install_type == :from_name
return klass.new(name, target_file)
rescue LoadError
raise FormulaUnavailableError.new(name)
end
def tap
if path.realpath.to_s =~ %r{#{HOMEBREW_REPOSITORY}/Library/Taps/(\w+)-(\w+)}
"#$1/#$2"
else
"mxcl/master"
end
end
def self.path name
HOMEBREW_REPOSITORY+"Library/Formula/#{name.downcase}.rb"
end
def mirrors; self.class.mirrors or []; end
def deps; self.class.dependencies.deps; end
def external_deps; self.class.dependencies.external_deps; end
def recursive_deps
Formula.expand_deps(self).flatten.uniq
end
def self.expand_deps f
f.deps.map do |dep|
f_dep = Formula.factory dep.to_s
expand_deps(f_dep) << f_dep
end
end
protected
def system cmd, *args
pretty_args = args.dup
pretty_args.delete "--disable-dependency-tracking" if cmd == "./configure" and not ARGV.verbose?
ohai "#{cmd} #{pretty_args*' '}".strip
removed_ENV_variables = case if args.empty? then cmd.split(' ').first else cmd end
when "xcodebuild"
ENV.remove_cc_etc
end
if ARGV.verbose?
safe_system cmd, *args
else
rd, wr = IO.pipe
pid = fork do
rd.close
$stdout.reopen wr
$stderr.reopen wr
args.collect!{|arg| arg.to_s}
exec(cmd, *args) rescue nil
exit! 1 end
wr.close
out = ''
out << rd.read until rd.eof?
Process.wait
unless $?.success?
puts out
raise
end
end
removed_ENV_variables.each do |key, value|
ENV[key] = value end if removed_ENV_variables
rescue
raise BuildError.new(self, cmd, args, $?)
end
public
def fetch
if install_bottle? self
downloader = CurlBottleDownloadStrategy.new bottle_url, name, version, nil
mirror_list = []
else
downloader = @downloader
mirror_list = @spec_to_use == @standard ? mirrors : []
end
HOMEBREW_CACHE.mkpath
begin
fetched = downloader.fetch
rescue CurlDownloadStrategyError => e
raise e if mirror_list.empty?
puts "Trying a mirror..."
url, specs = mirror_list.shift.values_at :url, :specs
downloader = download_strategy.new url, name, version, specs
retry
end
return fetched, downloader
end
def checksum_type
CHECKSUM_TYPES.detect { |type| instance_variable_defined?("@#{type}") }
end
def verify_download_integrity fn, *args
require 'digest'
if args.length != 2
type = checksum_type || :md5
supplied = instance_variable_get("@#{type}")
type = type.to_s.upcase
else
supplied, type = args
end
hasher = Digest.const_get(type)
hash = fn.incremental_hash(hasher)
if supplied and not supplied.empty?
message = <<-EOF
#{type} mismatch
Expected: #{supplied}
Got: #{hash}
Archive: #{fn}
(To retry an incomplete download, remove the file above.)
EOF
raise message unless supplied.upcase == hash.upcase
else
opoo "Cannot verify package integrity"
puts "The formula did not provide a download checksum"
puts "For your reference the #{type} is: #{hash}"
end
end
private
CHECKSUM_TYPES=[:md5, :sha1, :sha256].freeze
def stage
fetched, downloader = fetch
verify_download_integrity fetched if fetched.kind_of? Pathname
mktemp do
downloader.stage
@buildpath = Pathname.pwd
yield
@buildpath = nil
end
end
def patch
patch_list = Patches.new(patches)
return if patch_list.empty?
if patch_list.external_patches?
ohai "Downloading patches"
patch_list.download!
end
ohai "Patching"
patch_list.each do |p|
case p.compression
when :gzip then safe_system "/usr/bin/gunzip", p.compressed_filename
when :bzip2 then safe_system "/usr/bin/bunzip2", p.compressed_filename
end
safe_system '/usr/bin/patch', '-f', *(p.patch_args)
end
end
def validate_variable name
v = instance_variable_get("@#{name}")
raise "Invalid @#{name}" if v.to_s.empty? or v =~ /\s/
end
def set_instance_variable(type)
return if instance_variable_defined? "@#{type}"
class_value = self.class.send(type)
instance_variable_set("@#{type}", class_value) if class_value
end
def self.method_added method
raise 'You cannot override Formula.brew' if method == :brew
end
class << self
attr_reader :standard, :unstable
def self.attr_rw(*attrs)
attrs.each do |attr|
class_eval %Q{
def #{attr}(val=nil)
val.nil? ? @#{attr} : @#{attr} = val
end
}
end
end
attr_rw :version, :homepage, :mirrors, :specs
attr_rw :keg_only_reason, :skip_clean_all, :cc_failures
attr_rw :bottle_version, :bottle_url, :bottle_sha1
attr_rw(*CHECKSUM_TYPES)
def head val=nil, specs=nil
return @head if val.nil?
@unstable = SoftwareSpecification.new(val, specs)
@head = val
@specs = specs
end
def url val=nil, specs=nil
return @url if val.nil?
@standard = SoftwareSpecification.new(val, specs)
@url = val
@specs = specs
end
def stable &block
raise "url and md5 must be specified in a block" unless block_given?
instance_eval(&block) unless ARGV.build_devel? or ARGV.build_head?
end
def devel &block
raise "url and md5 must be specified in a block" unless block_given?
if ARGV.build_devel?
@mirrors = nil instance_eval(&block)
end
end
def bottle url=nil, &block
return unless block_given?
bottle_block = Class.new do
def self.version version
@version = version
end
def self.url url
@url = url
end
def self.sha1 sha1
case sha1
when Hash
key, value = sha1.shift
@sha1 = key if value == MacOS.cat
when String
@sha1 = sha1 if MacOS.lion?
end
end
def self.data
@version = 0 unless @version
return @version, @url, @sha1 if @sha1 && @url
return @version, nil, @sha1 if @sha1
end
end
bottle_block.instance_eval(&block)
@bottle_version, @bottle_url, @bottle_sha1 = bottle_block.data
end
def mirror val, specs=nil
@mirrors ||= []
@mirrors << {:url => val, :specs => specs}
@mirrors.uniq!
end
def dependencies
@dependencies ||= DependencyCollector.new
end
def depends_on dep
dependencies.add(dep)
end
def skip_clean paths
if paths == :all
@skip_clean_all = true
return
end
@skip_clean_paths ||= []
[paths].flatten.each do |p|
@skip_clean_paths << p.to_s unless @skip_clean_paths.include? p.to_s
end
end
def skip_clean_all?
@skip_clean_all
end
def skip_clean_paths
@skip_clean_paths or []
end
def keg_only reason, explanation=nil
@keg_only_reason = KegOnlyReason.new(reason, explanation.to_s.chomp)
end
def fails_with compiler, &block
@cc_failures ||= CompilerFailures.new
@cc_failures << if block_given?
CompilerFailure.new(compiler, &block)
else
CompilerFailure.new(compiler)
end
end
end
end
require 'formula_specialties'