brew/Library/Homebrew/formula.rb

831 lines
22 KiB
Ruby
Raw Normal View History

require 'formula_support'
2013-02-09 18:19:50 -06:00
require 'formula_lock'
require 'formula_pin'
require 'hardware'
require 'bottles'
2012-03-09 10:56:45 -08:00
require 'patches'
require 'compilers'
require 'build_environment'
2013-01-23 00:26:23 -06:00
require 'build_options'
require 'formulary'
2013-09-14 10:16:52 -05:00
require 'software_spec'
require 'install_renamed'
2012-04-05 21:09:24 -05:00
class Formula
include FileUtils
include Utils::Inreplace
extend BuildEnvironmentDSL
2013-09-21 19:27:24 -05:00
attr_reader :name, :path, :homepage, :downloader, :build
2012-04-05 21:09:24 -05:00
attr_reader :stable, :bottle, :devel, :head, :active_spec
# The current working directory during builds and tests.
# Will only be non-nil inside #stage and #test.
attr_reader :buildpath, :testpath
2012-02-24 12:50:21 -08:00
attr_accessor :local_bottle_path
# Flag for marking whether this formula needs C++ standard library
# compatibility check
attr_reader :cxxstdlib
# Homebrew determines the name
def initialize name='__UNKNOWN__', path=nil
@name = name
# If we got an explicit path, use that, else determine from the name
@path = path.nil? ? self.class.path(name) : Pathname.new(path).expand_path
@homepage = self.class.homepage
set_spec :stable
set_spec :devel
set_spec :head
set_spec :bottle do |bottle|
# Ensure the bottle URL is set. If it does not have a checksum,
# then a bottle is not available for the current platform.
# TODO: push this down into Bottle; we can pass the formula instance
# into a validation method on the bottle instance.
unless bottle.checksum.nil? || bottle.checksum.empty?
@bottle = bottle
bottle.url ||= bottle_url(self)
end
2012-04-05 21:09:24 -05:00
end
@active_spec = determine_active_spec
validate_attributes :url, :name, :version
@downloader = active_spec.downloader
2013-09-21 19:27:24 -05:00
@build = determine_build_options
@pin = FormulaPin.new(self)
@cxxstdlib ||= Set.new
2012-04-05 21:09:24 -05:00
end
def set_spec(name)
spec = self.class.send(name)
2013-10-22 13:07:09 -05:00
if block_given? && yield(spec) || spec.url
spec.owner = self
instance_variable_set("@#{name}", spec)
end
end
def determine_active_spec
case
when @head && ARGV.build_head? then @head # --HEAD
when @devel && ARGV.build_devel? then @devel # --devel
when @bottle && install_bottle?(self) then @bottle # bottle available
when @stable then @stable
when @devel && @stable.nil? then @devel # devel-only
when @head && @stable.nil? then @head # head-only
else
raise FormulaSpecificationError, "formulae require at least a URL"
end
end
def validate_attributes(*attrs)
attrs.each do |attr|
if (value = send(attr).to_s).empty? || value =~ /\s/
raise FormulaValidationError.new(attr, value)
end
end
end
def default_build?
2013-09-21 19:27:24 -05:00
self.class.build.used_options.empty?
end
def determine_build_options
build = active_spec.build
options.each { |opt, desc| build.add(opt, desc) }
build
end
def url; active_spec.url; end
def version; active_spec.version; end
def mirrors; active_spec.mirrors; end
2012-04-05 21:09:24 -05:00
def resource(name)
2013-09-17 21:25:39 -05:00
active_spec.resource(name)
end
def resources
2013-09-17 21:25:39 -05:00
active_spec.resources.values
end
2013-09-21 19:27:24 -05:00
def deps
active_spec.deps
end
def requirements
active_spec.requirements
end
# if the dir is there, but it's empty we consider it not installed
def installed?
(dir = installed_prefix).directory? && dir.children.length > 0
end
def linked_keg
2013-04-15 15:00:57 -05:00
Pathname.new("#{HOMEBREW_LIBRARY}/LinkedKegs/#{name}")
end
def installed_prefix
if head && (head_prefix = prefix(head.version)).directory?
head_prefix
elsif devel && (devel_prefix = prefix(devel.version)).directory?
2012-04-05 21:09:24 -05:00
devel_prefix
else
prefix
end
end
def installed_version
require 'keg'
Keg.new(installed_prefix).version
end
def prefix(v=version)
Pathname.new("#{HOMEBREW_CELLAR}/#{name}/#{v}")
end
def rack; prefix.parent end
2012-10-28 08:54:54 -07:00
def bin; prefix+'bin' end
def doc; share+'doc'+name end
def include; prefix+'include' end
def info; share+'info' end
def lib; prefix+'lib' end
def libexec; prefix+'libexec' end
def man; 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
2013-05-03 09:28:45 -07:00
def frameworks; prefix+'Frameworks' end
def kext_prefix; prefix+'Library/Extensions' end
# configuration needs to be preserved past upgrades
def etc; (HOMEBREW_PREFIX+'etc').extend(InstallRenamed) end
# generally we don't want var stuff inside the keg
def var; HOMEBREW_PREFIX+'var' end
def bash_completion; prefix+'etc/bash_completion.d' end
def zsh_completion; share+'zsh/site-functions' end
2013-10-05 20:29:19 +01:00
# for storing etc, var files for later copying from bottles
def bottle_prefix; prefix+'.bottle' end
2012-09-08 12:18:52 -07:00
# override this to provide a plist
def plist; nil; end
alias :startup_plist :plist
# plist name, i.e. the name of the launchd service
def plist_name; 'homebrew.mxcl.'+name end
def plist_path; prefix+(plist_name+'.plist') end
def plist_manual; self.class.plist_manual end
def plist_startup; self.class.plist_startup end
2013-04-15 15:00:57 -05:00
def opt_prefix
Pathname.new("#{HOMEBREW_PREFIX}/opt/#{name}")
end
def cached_download
downloader.cached_location
end
def clear_cache
downloader.clear_cache
end
# Can be overridden to selectively disable bottles from formulae.
# Defaults to true so overridden version does not have to check if bottles
# are supported.
2013-02-11 20:24:06 -08:00
def pour_bottle?; true end
# Can be overridden to run commands on both source and bottle installation.
def post_install; end
# tell the user about any caveats regarding this package, return a string
def caveats; nil end
2011-06-21 22:28:29 +01:00
# any e.g. configure options for this package
2011-06-22 17:47:30 +01:00
def options; [] end
2011-06-21 22:28:29 +01:00
# patches are automatically applied after extracting the tarball
# return an array of strings, or if you need a patch level other than -p1
# return a Hash eg.
# {
# :p0 => ['http://foo.com/patch1', 'http://foo.com/patch2'],
2013-02-11 20:24:06 -08:00
# :p1 => 'http://bar.com/patch2'
# }
# The final option is to return DATA, then put a diff after __END__. You
# can still return a Hash with DATA as the value for a patch level key.
2009-09-21 23:51:31 +01:00
def patches; end
# rarely, you don't want your library symlinked into the main prefix
# see gettext.rb for an example
2010-07-18 10:38:45 -07:00
def keg_only?
kor = self.class.keg_only_reason
not kor.nil? and kor.valid?
end
def keg_only_reason
self.class.keg_only_reason
2010-07-18 10:38:45 -07:00
end
def fails_with? cc
cc = Compiler.new(cc) unless cc.is_a? Compiler
(self.class.cc_failures || []).any? do |failure|
# Major version check distinguishes between, e.g.,
# GCC 4.7.1 and GCC 4.8.2, where a comparison is meaningless
failure.compiler == cc.name && failure.major_version == cc.major_version && \
failure.version >= cc.version
end
end
# sometimes the clean process breaks things
# skip cleaning paths in a formula with a class method like this:
# skip_clean [bin+"foo", lib+"bar"]
2011-12-17 17:12:19 -08:00
# redefining skip_clean? now deprecated
def skip_clean? path
return true if self.class.skip_clean_all?
2012-09-09 10:01:59 -07:00
return true if path.extname == '.la' and self.class.skip_clean_paths.include? :la
to_check = path.relative_path_from(prefix).to_s
2009-11-23 10:07:23 -08:00
self.class.skip_clean_paths.include? to_check
end
# yields self with current working directory set to the uncompressed tarball
def brew
validate_attributes :name, :version
stage do
begin
patch
# we allow formulae to do anything they want to the Ruby process
# so load any deps before this point! And exit asap afterwards
yield self
2013-02-17 22:54:27 -06:00
rescue RuntimeError, SystemCallError
%w(config.log CMakeCache.txt).each do |fn|
(HOMEBREW_LOGS/name).install(fn) if File.file?(fn)
end
raise
end
end
end
def lock
2013-02-09 18:19:50 -06:00
@lock = FormulaLock.new(name)
@lock.lock
end
def unlock
2013-02-09 18:19:50 -06:00
@lock.unlock unless @lock.nil?
end
def pinnable?
@pin.pinnable?
end
def pinned?
@pin.pinned?
end
def pin
@pin.pin
end
def unpin
@pin.unpin
end
def == other
instance_of?(other.class) && name == other.name
end
alias_method :eql?, :==
def hash
name.hash
end
def <=> b
name <=> b.name
end
def to_s
name
end
def inspect
name
end
2010-09-22 08:05:58 -07:00
# Standard parameters for CMake builds.
# Using Build Type "None" tells cmake to use our CFLAGS,etc. settings.
# Setting it to Release would ignore our flags.
# Setting CMAKE_FIND_FRAMEWORK to "LAST" tells CMake to search for our
# libraries before trying to utilize Frameworks, many of which will be from
# 3rd party installs.
2010-09-22 08:05:58 -07:00
# Note: there isn't a std_autotools variant because autotools is a lot
# less consistent and the standard parameters are more memorable.
def std_cmake_args
%W[
-DCMAKE_INSTALL_PREFIX=#{prefix}
-DCMAKE_BUILD_TYPE=None
-DCMAKE_FIND_FRAMEWORK=LAST
-DCMAKE_VERBOSE_MAKEFILE=ON
-Wno-dev
]
end
# Install python bindings inside of a block given to this method and/or
# call python so: `system python, "setup.py", "install", "--prefix=#{prefix}"
# Note that there are no quotation marks around python!
# <https://github.com/mxcl/homebrew/wiki/Homebrew-and-Python>
def python(options={:allowed_major_versions => [2, 3]}, &block)
require 'python_helper'
2013-06-06 13:18:32 +02:00
python_helper(options, &block)
end
# Explicitly only execute the block for 2.x (if a python 2.x is available)
def python2 &block
python(:allowed_major_versions => [2], &block)
end
# Explicitly only execute the block for 3.x (if a python 3.x is available)
def python3 &block
python(:allowed_major_versions => [3], &block)
end
# Generates a formula's ruby class name from a formula's name
def self.class_s name
2013-02-11 20:24:06 -08:00
# remove invalid characters and then camelcase it
name.capitalize.gsub(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase } \
.gsub('+', 'x')
end
# an array of all Formula names
def self.names
2013-10-30 13:20:48 -07:00
Dir["#{HOMEBREW_LIBRARY}/Formula/*.rb"].map{ |f| File.basename f, '.rb' }.sort
end
def self.each
names.each do |name|
2013-04-14 23:29:15 -05:00
begin
yield Formula.factory(name)
rescue StandardError => e
# Don't let one broken formula break commands. But do complain.
onoe "Failed to import: #{name}"
puts e
next
2010-07-18 14:07:40 -07:00
end
end
end
class << self
include Enumerable
end
2012-08-10 16:05:30 -04:00
def self.installed
return [] unless HOMEBREW_CELLAR.directory?
HOMEBREW_CELLAR.subdirs.map do |rack|
2013-06-29 21:35:57 -05:00
begin
factory(rack.basename.to_s)
rescue FormulaUnavailableError
end
end.compact
2012-08-10 16:05:30 -04:00
end
def self.aliases
2013-10-30 13:20:48 -07:00
Dir["#{HOMEBREW_LIBRARY}/Aliases/*"].map{ |f| File.basename f }.sort
end
2013-06-27 11:12:02 -07:00
# TODO - document what this returns and why
def self.canonical_name name
# if name includes a '/', it may be a tap reference, path, or URL
if name.include? "/"
2012-03-04 14:17:54 +00:00
if name =~ %r{(.+)/(.+)/(.+)}
2013-04-15 15:00:57 -05:00
tap_name = "#$1-#$2".downcase
2013-10-30 13:20:48 -07:00
tapd = Pathname.new("#{HOMEBREW_LIBRARY}/Taps/#{tap_name}")
2012-03-04 14:17:54 +00:00
tapd.find_formula do |relative_pathname|
return "#{tapd}/#{relative_pathname}" if relative_pathname.stem.to_s == $3
end if tapd.directory?
end
# Otherwise don't resolve paths or URLs
return name
end
# test if the name is a core formula
2013-10-30 13:20:48 -07:00
formula_with_that_name = Pathname.new("#{HOMEBREW_LIBRARY}/Formula/#{name}.rb")
if formula_with_that_name.file? and formula_with_that_name.readable?
return name
end
# test if the name is a formula alias
2013-10-30 13:20:48 -07:00
possible_alias = Pathname.new("#{HOMEBREW_LIBRARY}/Aliases/#{name}")
if possible_alias.file?
return possible_alias.realpath.basename('.rb').to_s
end
# test if the name is a cached downloaded formula
possible_cached_formula = Pathname.new("#{HOMEBREW_CACHE_FORMULA}/#{name}.rb")
if possible_cached_formula.file?
return possible_cached_formula.to_s
end
# dunno, pass through the name
return name
end
def self.factory name
Formulary.factory name
end
2013-10-29 15:46:10 -04:00
def tap?
!!path.realpath.to_s.match(HOMEBREW_TAP_DIR_REGEX)
end
def tap
2013-10-30 11:19:28 -07:00
if path.realpath.to_s =~ HOMEBREW_TAP_DIR_REGEX
"#$1/#$2"
elsif core_formula?
"mxcl/master"
else
"path or URL"
end
end
# True if this formula is provided by Homebrew itself
def core_formula?
path.realpath.to_s == Formula.path(name).to_s
end
def self.path name
2013-10-30 13:20:48 -07:00
Pathname.new("#{HOMEBREW_LIBRARY}/Formula/#{name.downcase}.rb")
end
def env
@env ||= self.class.env
end
def conflicts
self.class.conflicts
end
# Returns a list of Dependency objects in an installable order, which
# means if a depends on b then b will be ordered before a in this list
def recursive_dependencies(&block)
Dependency.expand(self, &block)
end
# The full set of Requirements for this formula's dependency tree.
def recursive_requirements(&block)
Requirement.expand(self, &block)
end
def to_hash
hsh = {
"name" => name,
"homepage" => homepage,
"versions" => {
"stable" => (stable.version.to_s if stable),
"bottle" => bottle ? true : false,
"devel" => (devel.version.to_s if devel),
"head" => (head.version.to_s if head)
},
"installed" => [],
"linked_keg" => (linked_keg.realpath.basename.to_s if linked_keg.exist?),
"keg_only" => keg_only?,
"dependencies" => deps.map {|dep| dep.to_s},
"conflicts_with" => conflicts.map(&:name),
"options" => [],
"caveats" => caveats
}
build.each do |opt|
hsh["options"] << {
"option" => "--"+opt.name,
"description" => opt.description
}
end
if rack.directory?
rack.subdirs.each do |keg|
tab = Tab.for_keg keg
hsh["installed"] << {
"version" => keg.basename.to_s,
2013-02-16 21:19:35 -06:00
"used_options" => tab.used_options.map(&:flag),
"built_as_bottle" => tab.built_bottle,
"poured_from_bottle" => tab.poured_from_bottle
}
end
end
hsh
end
# For brew-fetch and others.
def fetch
active_spec.fetch
end
# For FormulaInstaller.
def verify_download_integrity fn
active_spec.verify_download_integrity(fn)
end
def test
require 'test/unit/assertions'
extend(Test::Unit::Assertions)
# Adding the used options allows us to use `build.with?` inside of tests
tab = Tab.for_name(name)
tab.used_options.each { |opt| build.args << opt unless build.has_opposite_of? opt }
ret = nil
mktemp do
@testpath = Pathname.pwd
ret = instance_eval(&self.class.test)
@testpath = nil
end
ret
end
def test_defined?
not self.class.instance_variable_get(:@test_defined).nil?
end
protected
# Pretty titles the command and buffers stdout/stderr
# Throws if there's an error
def system cmd, *args
# remove "boring" arguments so that the important ones are more likely to
# be shown considering that we trim long ohai lines to the terminal width
pretty_args = args.dup
if cmd == "./configure" and not ARGV.verbose?
pretty_args.delete "--disable-dependency-tracking"
pretty_args.delete "--disable-debug"
end
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
@exec_count ||= 0
@exec_count += 1
logd = HOMEBREW_LOGS/name
logfn = "#{logd}/%02d.%s" % [@exec_count, File.basename(cmd.to_s).split(' ').first]
mkdir_p(logd)
rd, wr = IO.pipe
fork do
ENV['HOMEBREW_CC_LOG_PATH'] = logfn
rd.close
$stdout.reopen wr
$stderr.reopen wr
args.collect!{|arg| arg.to_s}
exec(cmd.to_s, *args) rescue nil
puts "Failed to execute: #{cmd}"
exit! 1 # never gets here unless exec threw or failed
end
wr.close
File.open(logfn, 'w') do |f|
while buf = rd.gets
f.puts buf
puts buf if ARGV.verbose?
end
Process.wait
$stdout.flush
unless $?.success?
f.flush
Kernel.system "/usr/bin/tail", "-n", "5", logfn unless ARGV.verbose?
f.puts
require 'cmd/--config'
Homebrew.write_build_config(f)
raise BuildError.new(self, cmd, args, $?)
end
end
ensure
rd.close if rd and not rd.closed?
ENV.update(removed_ENV_variables) if removed_ENV_variables
end
private
def stage
active_spec.stage do
2012-02-24 12:50:21 -08:00
@buildpath = Pathname.pwd
yield
2012-02-24 12:50:21 -08:00
@buildpath = nil
end
end
def patch
2012-03-09 10:56:45 -08:00
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|
2012-03-09 10:56:45 -08:00
case p.compression
when :gzip then with_system_path { safe_system "gunzip", p.compressed_filename }
when :bzip2 then with_system_path { safe_system "bunzip2", p.compressed_filename }
end
2012-03-09 10:56:45 -08:00
# -f means don't prompt the user if there are errors; just exit with non-zero status
safe_system '/usr/bin/patch', '-f', *(p.patch_args)
end
end
# Explicitly request changing C++ standard library compatibility check
# settings. Use with caution!
def cxxstdlib_check check_type
@cxxstdlib << check_type
end
def self.method_added method
2013-01-07 17:34:56 -06:00
case method
when :brew
2013-06-06 16:36:12 -07:00
raise "You cannot override Formula#brew in class #{name}"
2013-01-07 17:34:56 -06:00
when :test
@test_defined = true
2013-01-07 17:34:56 -06:00
end
2009-07-31 14:17:56 +01:00
end
# The methods below define the formula DSL.
class << self
2013-06-20 16:36:01 -05:00
attr_rw :homepage, :keg_only_reason, :cc_failures
attr_rw :plist_startup, :plist_manual
2013-09-17 21:25:39 -05:00
def specs
@specs ||= [stable, devel, head, bottle].freeze
2013-09-17 21:25:39 -05:00
end
def url val, specs={}
stable.url(val, specs)
end
def version val=nil
stable.version(val)
end
def mirror val
stable.mirror(val)
end
Checksum::TYPES.each do |cksum|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{cksum}(val)
stable.#{cksum}(val)
end
EOS
end
def build
stable.build
end
def stable &block
@stable ||= SoftwareSpec.new
2012-04-05 21:09:24 -05:00
return @stable unless block_given?
@stable.instance_eval(&block)
end
def bottle *, &block
@bottle ||= Bottle.new
2012-04-05 21:09:24 -05:00
return @bottle unless block_given?
@bottle.instance_eval(&block)
2013-09-27 09:22:15 +01:00
@bottle.version = @stable.version
2012-04-05 21:09:24 -05:00
end
2012-04-05 21:09:24 -05:00
def devel &block
@devel ||= SoftwareSpec.new
2012-04-05 21:09:24 -05:00
return @devel unless block_given?
@devel.instance_eval(&block)
end
2013-09-13 11:13:12 -05:00
def head val=nil, specs={}, &block
@head ||= HeadSoftwareSpec.new
2013-09-13 11:13:12 -05:00
if block_given?
@head.instance_eval(&block)
elsif val
@head.url(val, specs)
else
@head
end
2012-04-05 21:09:24 -05:00
end
# Define a named resource using a SoftwareSpec style block
2013-09-17 21:25:38 -05:00
def resource name, &block
specs.each do |spec|
spec.resource(name, &block) unless spec.resource?(name)
end
end
2012-02-28 19:56:35 -08:00
def depends_on dep
2013-09-21 19:27:24 -05:00
specs.each { |spec| spec.depends_on(dep) }
end
def option name, description=nil
2013-09-21 19:27:24 -05:00
specs.each { |spec| spec.option(name, description) }
end
def plist_options options
@plist_startup = options[:startup]
@plist_manual = options[:manual]
end
def conflicts
@conflicts ||= []
end
def conflicts_with *names
opts = Hash === names.last ? names.pop : {}
names.each { |name| conflicts << FormulaConflict.new(name, opts[:because]) }
end
2012-10-21 13:03:35 -07:00
def skip_clean *paths
2013-06-20 16:18:01 -05:00
paths.flatten!
2012-10-21 13:03:35 -07:00
# :all is deprecated though
if paths.include? :all
@skip_clean_all = true
2010-07-25 15:53:39 -07:00
return
end
2012-10-21 13:03:35 -07:00
paths.each do |p|
p = p.to_s unless p == :la # Keep :la in paths as a symbol
2013-06-20 16:25:58 -05:00
skip_clean_paths << p
end
end
def skip_clean_all?
@skip_clean_all
end
def skip_clean_paths
2013-06-20 16:25:58 -05:00
@skip_clean_paths ||= Set.new
end
def keg_only reason, explanation=nil
2011-03-15 22:46:10 -07:00
@keg_only_reason = KegOnlyReason.new(reason, explanation.to_s.chomp)
2010-07-18 10:38:45 -07:00
end
# For Apple compilers, this should be in the format:
# fails_with compiler do
# cause "An explanation for why the build doesn't work."
# build "The Apple build number for the newest incompatible release."
# end
#
# The block may be omitted, and if present the build may be omitted;
# if so, then the compiler will be blacklisted for *all* versions.
#
# For GNU GCC compilers, this should be in the format:
# fails_with compiler => major_version do
# cause
# version "The official release number for the latest incompatible
# version, for instance 4.8.1"
# end
#
# `major_version` should be the major release number only, for instance
# '4.8' for the GCC 4.8 series (4.8.0, 4.8.1, etc.).
# If `version` or the block is omitted, then the compiler will be
# blacklisted for all compilers in that series.
#
# For example, if a bug is only triggered on GCC 4.8.1 but is not
# encountered on 4.8.2:
#
# fails_with :gcc => '4.8' do
# version '4.8.1'
# end
def fails_with compiler, &block
@cc_failures ||= Set.new
@cc_failures << CompilerFailure.new(compiler, &block)
end
def require_universal_deps
specs.each { |spec| spec.build.universal = true }
end
def test &block
return @test unless block_given?
@test_defined = true
@test = block
end
end
end
require 'formula_specialties'