New fails_with infrastructure
- Formulae can now declare failures on any compiler. - FailsWithLLVM and associated formula elements have been moved to compat. Signed-off-by: Jack Nagel <jacknagel@gmail.com>
This commit is contained in:
parent
bfbfdf03eb
commit
de444ead0b
@ -58,6 +58,12 @@ def install f
|
||||
end
|
||||
end
|
||||
|
||||
if f.fails_with? ENV.compiler
|
||||
cs = CompilerSelector.new f
|
||||
cs.select_compiler
|
||||
cs.advise
|
||||
end
|
||||
|
||||
f.brew do
|
||||
if ARGV.flag? '--interactive'
|
||||
ohai "Entering interactive mode"
|
||||
|
||||
@ -76,6 +76,16 @@ class Formula
|
||||
def fails_with_llvm msg=nil, data=nil
|
||||
FailsWithLLVM.new(msg, data).handle_failure
|
||||
end
|
||||
|
||||
def fails_with_llvm?
|
||||
fails_with? :llvm
|
||||
end
|
||||
|
||||
def self.fails_with_llvm msg=nil, data=nil
|
||||
fails_with_llvm_reason = FailsWithLLVM.new(msg, data)
|
||||
@cc_failures ||= CompilerFailures.new
|
||||
@cc_failures << fails_with_llvm_reason
|
||||
end
|
||||
end
|
||||
|
||||
class UnidentifiedFormula < Formula
|
||||
@ -94,3 +104,52 @@ module HomebrewEnvExtension extend self
|
||||
compiler == :llvm
|
||||
end
|
||||
end
|
||||
|
||||
class FailsWithLLVM
|
||||
attr_reader :compiler, :build, :cause
|
||||
|
||||
def initialize msg=nil, data=nil
|
||||
if msg.nil? or msg.kind_of? Hash
|
||||
@cause = "(No specific reason was given)"
|
||||
data = msg
|
||||
else
|
||||
@cause = msg
|
||||
end
|
||||
@build = (data.delete :build rescue nil).to_i
|
||||
@compiler = :llvm
|
||||
end
|
||||
|
||||
def handle_failure
|
||||
return unless ENV.compiler == :llvm
|
||||
|
||||
# version 2336 is the latest version as of Xcode 4.2, so it is the
|
||||
# latest version we have tested against so we will switch to GCC and
|
||||
# bump this integer when Xcode 4.3 is released. TODO do that!
|
||||
if build.to_i >= 2336
|
||||
if MacOS.xcode_version < "4.2"
|
||||
opoo "Formula will not build with LLVM, using GCC"
|
||||
ENV.gcc
|
||||
else
|
||||
opoo "Formula will not build with LLVM, trying Clang"
|
||||
ENV.clang
|
||||
end
|
||||
return
|
||||
end
|
||||
opoo "Building with LLVM, but this formula is reported to not work with LLVM:"
|
||||
puts
|
||||
puts cause
|
||||
puts
|
||||
puts <<-EOS.undent
|
||||
We are continuing anyway so if the build succeeds, please open a ticket with
|
||||
the following information: #{MacOS.llvm_build_version}-#{MACOS_VERSION}. So
|
||||
that we can update the formula accordingly. Thanks!
|
||||
EOS
|
||||
puts
|
||||
if MacOS.xcode_version < "4.2"
|
||||
puts "If it doesn't work you can: brew install --use-gcc"
|
||||
else
|
||||
puts "If it doesn't work you can try: brew install --use-clang"
|
||||
end
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
138
Library/Homebrew/compilers.rb
Normal file
138
Library/Homebrew/compilers.rb
Normal file
@ -0,0 +1,138 @@
|
||||
class Compilers < Array
|
||||
def include? cc
|
||||
cc = cc.name if cc.is_a? Compiler
|
||||
self.any? { |c| c.name == cc }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CompilerFailures < Array
|
||||
def include? cc
|
||||
cc = Compiler.new(cc) unless cc.is_a? Compiler
|
||||
self.any? { |failure| failure.compiler == cc.name }
|
||||
end
|
||||
|
||||
def <<(failure)
|
||||
super(failure) unless self.include? failure.compiler
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Compiler
|
||||
attr_reader :name, :build
|
||||
|
||||
def initialize name
|
||||
@name = name
|
||||
@build = case name
|
||||
when :clang then MacOS.clang_build_version.to_i
|
||||
when :llvm then MacOS.llvm_build_version.to_i
|
||||
when :gcc then MacOS.gcc_42_build_version.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@name.to_sym == other.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CompilerFailure
|
||||
attr_reader :compiler
|
||||
|
||||
def initialize compiler, &block
|
||||
@compiler = compiler
|
||||
instance_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
def build val=nil
|
||||
val.nil? ? @build.to_i : @build = val.to_i
|
||||
end
|
||||
|
||||
def cause val=nil
|
||||
val.nil? ? @cause : @cause = val
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# CompilerSelector is used to process a formula's CompilerFailures.
|
||||
# If no viable compilers are available, ENV.compiler is left as-is.
|
||||
class CompilerSelector
|
||||
NAMES = { :clang => "Clang", :gcc => "GCC", :llvm => "LLVM" }
|
||||
|
||||
def initialize f
|
||||
@f = f
|
||||
@old_compiler = ENV.compiler
|
||||
@compilers = Compilers.new
|
||||
@compilers << Compiler.new(:clang) if MacOS.clang_build_version
|
||||
@compilers << Compiler.new(:llvm) if MacOS.llvm_build_version
|
||||
@compilers << Compiler.new(:gcc) if MacOS.gcc_42_build_version
|
||||
end
|
||||
|
||||
def select_compiler
|
||||
# @compilers is our list of available compilers. If @f declares a
|
||||
# failure with compiler foo, then we remove foo from the list if
|
||||
# the failing build is >= the currently installed version of foo.
|
||||
@compilers.reject! do |cc|
|
||||
failure = @f.fails_with? cc
|
||||
next unless failure
|
||||
failure.build >= cc.build
|
||||
end
|
||||
|
||||
return if @compilers.empty? or @compilers.include? ENV.compiler
|
||||
|
||||
ENV.send case ENV.compiler
|
||||
when :clang
|
||||
if @compilers.include? :llvm then :llvm
|
||||
elsif @compilers.include? :gcc then :gcc
|
||||
else ENV.compiler
|
||||
end
|
||||
when :llvm
|
||||
if @compilers.include? :clang and MacOS.clang_build_version >= 211 then :clang
|
||||
elsif @compilers.include? :gcc then :gcc
|
||||
elsif @compilers.include? :clang then :clang
|
||||
else ENV.compiler
|
||||
end
|
||||
when :gcc
|
||||
if @compilers.include? :clang and MacOS.clang_build_version >= 211 then :clang
|
||||
elsif @compilers.include? :llvm then :llvm
|
||||
elsif @compilers.include? :clang then :clang
|
||||
else ENV.compiler
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def advise
|
||||
failure = @f.fails_with? @old_compiler
|
||||
return unless failure
|
||||
|
||||
# If we're still using the original ENV.compiler, then the formula did not
|
||||
# declare a specific failing build, so we continue and print some advice.
|
||||
# Otherwise, tell the user that we're switching compilers.
|
||||
if @old_compiler == ENV.compiler
|
||||
cc = Compiler.new(ENV.compiler)
|
||||
subject = "#{@f.name}-#{@f.version}: builds with #{NAMES[cc.name]}-#{cc.build}-#{MACOS_VERSION}"
|
||||
warning = "Using #{NAMES[cc.name]}, but this formula is reported to fail with #{NAMES[cc.name]}."
|
||||
warning += "\n\n#{failure.cause.strip}\n" unless failure.cause.nil?
|
||||
warning += <<-EOS.undent
|
||||
|
||||
We are continuing anyway so if the build succeeds, please open a ticket with
|
||||
the subject
|
||||
|
||||
#{subject}
|
||||
|
||||
so that we can update the formula accordingly. Thanks!
|
||||
EOS
|
||||
|
||||
viable = @compilers.reject { |cc| @f.fails_with? cc }
|
||||
unless viable.empty?
|
||||
warning += "\nIf it fails you can use "
|
||||
options = viable.map { |cc| "--use-#{cc.name}" }
|
||||
warning += "#{options*' or '} to try a different compiler."
|
||||
end
|
||||
|
||||
opoo warning
|
||||
else
|
||||
opoo "Formula will not build with #{NAMES[@old_compiler]}, trying #{NAMES[ENV.compiler]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -5,6 +5,7 @@ require 'hardware'
|
||||
require 'bottles'
|
||||
require 'extend/fileutils'
|
||||
require 'patches'
|
||||
require 'compilers'
|
||||
|
||||
# Derive and define at least @url, see Library/Formula for examples
|
||||
class Formula
|
||||
@ -156,14 +157,12 @@ class Formula
|
||||
self.class.keg_only_reason || false
|
||||
end
|
||||
|
||||
def fails_with_llvm?
|
||||
llvm = self.class.fails_with_llvm_reason
|
||||
if llvm
|
||||
if llvm.build and MacOS.llvm_build_version.to_i > llvm.build.to_i
|
||||
false
|
||||
else
|
||||
llvm
|
||||
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
|
||||
|
||||
@ -182,8 +181,6 @@ class Formula
|
||||
validate_variable :name
|
||||
validate_variable :version
|
||||
|
||||
fails_with_llvm?.handle_failure if fails_with_llvm?
|
||||
|
||||
stage do
|
||||
begin
|
||||
patch
|
||||
@ -571,8 +568,8 @@ private
|
||||
end
|
||||
|
||||
attr_rw :version, :homepage, :mirrors, :specs
|
||||
attr_rw :keg_only_reason, :fails_with_llvm_reason, :skip_clean_all
|
||||
attr_rw :bottle_url, :bottle_sha1
|
||||
attr_rw :keg_only_reason, :skip_clean_all, :bottle_url, :bottle_sha1
|
||||
attr_rw :cc_failures
|
||||
attr_rw(*CHECKSUM_TYPES)
|
||||
|
||||
def head val=nil, specs=nil
|
||||
@ -675,8 +672,13 @@ private
|
||||
@keg_only_reason = KegOnlyReason.new(reason, explanation.to_s.chomp)
|
||||
end
|
||||
|
||||
def fails_with_llvm msg=nil, data=nil
|
||||
@fails_with_llvm_reason = FailsWithLLVM.new(msg, data)
|
||||
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
|
||||
|
||||
@ -69,61 +69,3 @@ EOS
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Used to annotate formulae that won't build correctly with LLVM.
|
||||
class FailsWithLLVM
|
||||
attr_reader :msg, :data, :build
|
||||
|
||||
def initialize msg=nil, data=nil
|
||||
if msg.nil? or msg.kind_of? Hash
|
||||
@msg = "(No specific reason was given)"
|
||||
data = msg
|
||||
else
|
||||
@msg = msg
|
||||
end
|
||||
@data = data
|
||||
@build = data.delete :build rescue nil
|
||||
end
|
||||
|
||||
def reason
|
||||
s = @msg
|
||||
s += "Tested with LLVM build #{@build}" unless @build == nil
|
||||
s += "\n"
|
||||
return s
|
||||
end
|
||||
|
||||
def handle_failure
|
||||
return unless ENV.compiler == :llvm
|
||||
|
||||
# version 2336 is the latest version as of Xcode 4.2, so it is the
|
||||
# latest version we have tested against so we will switch to GCC and
|
||||
# bump this integer when Xcode 4.3 is released. TODO do that!
|
||||
if build.to_i >= 2336
|
||||
if MacOS.xcode_version < "4.2"
|
||||
opoo "Formula will not build with LLVM, using GCC"
|
||||
ENV.gcc
|
||||
else
|
||||
opoo "Formula will not build with LLVM, trying Clang"
|
||||
ENV.clang
|
||||
end
|
||||
return
|
||||
end
|
||||
opoo "Building with LLVM, but this formula is reported to not work with LLVM:"
|
||||
puts
|
||||
puts reason
|
||||
puts
|
||||
puts <<-EOS.undent
|
||||
We are continuing anyway so if the build succeeds, please open a ticket with
|
||||
the following information: #{MacOS.llvm_build_version}-#{MACOS_VERSION}. So
|
||||
that we can update the formula accordingly. Thanks!
|
||||
EOS
|
||||
puts
|
||||
if MacOS.xcode_version < "4.2"
|
||||
puts "If it doesn't work you can: brew install --use-gcc"
|
||||
else
|
||||
puts "If it doesn't work you can try: brew install --use-clang"
|
||||
end
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
@ -3,8 +3,12 @@ require 'testing_env'
|
||||
require 'extend/ARGV' # needs to be after test/unit to avoid conflict with OptionsParser
|
||||
ARGV.extend(HomebrewArgvExtension)
|
||||
|
||||
require 'extend/ENV'
|
||||
ENV.extend(HomebrewEnvExtension)
|
||||
|
||||
require 'test/testball'
|
||||
require 'utils'
|
||||
|
||||
require 'hardware'
|
||||
|
||||
class AbstractDownloadStrategy
|
||||
attr_reader :url
|
||||
@ -62,4 +66,67 @@ class FormulaTests < Test::Unit::TestCase
|
||||
assert_equal f.url, "file:///#{TEST_FOLDER}/bad_url/testball-0.1.tbz"
|
||||
assert_equal downloader.url, "file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
end
|
||||
|
||||
def test_compiler_selection
|
||||
%W{HOMEBREW_USE_CLANG HOMEBEW_USE_LLVM HOMEBREW_USE_GCC}.each { |e| ENV.delete(e) }
|
||||
|
||||
f = TestAllCompilerFailures.new
|
||||
assert f.fails_with? :clang
|
||||
assert f.fails_with? :llvm
|
||||
assert f.fails_with? :gcc
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert_equal MacOS.default_compiler, ENV.compiler
|
||||
|
||||
f = TestNoCompilerFailures.new
|
||||
assert !(f.fails_with? :clang)
|
||||
assert !(f.fails_with? :llvm)
|
||||
assert !(f.fails_with? :gcc)
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert_equal MacOS.default_compiler, ENV.compiler
|
||||
|
||||
f = TestLLVMFailure.new
|
||||
assert !(f.fails_with? :clang)
|
||||
assert f.fails_with? :llvm
|
||||
assert !(f.fails_with? :gcc)
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert ENV.compiler, case MacOS.clang_build_version
|
||||
when 0..210 then :gcc
|
||||
else :clang
|
||||
end
|
||||
|
||||
f = TestMixedCompilerFailures.new
|
||||
assert f.fails_with? :clang
|
||||
assert !(f.fails_with? :llvm)
|
||||
assert f.fails_with? :gcc
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert_equal :llvm, ENV.compiler
|
||||
|
||||
f = TestMoreMixedCompilerFailures.new
|
||||
assert !(f.fails_with? :clang)
|
||||
assert f.fails_with? :llvm
|
||||
assert f.fails_with? :gcc
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert_equal :clang, ENV.compiler
|
||||
|
||||
f = TestEvenMoreMixedCompilerFailures.new
|
||||
assert f.fails_with? :clang
|
||||
assert f.fails_with? :llvm
|
||||
assert !(f.fails_with? :gcc)
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert_equal :clang, ENV.compiler
|
||||
|
||||
f = TestBlockWithoutBuildCompilerFailure.new
|
||||
assert f.fails_with? :clang
|
||||
assert !(f.fails_with? :llvm)
|
||||
assert !(f.fails_with? :gcc)
|
||||
cs = CompilerSelector.new(f)
|
||||
cs.select_compiler
|
||||
assert_equal MacOS.default_compiler, ENV.compiler
|
||||
end
|
||||
end
|
||||
|
||||
@ -39,3 +39,85 @@ class ConfigureFails <Formula
|
||||
system "./configure"
|
||||
end
|
||||
end
|
||||
|
||||
class TestAllCompilerFailures < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "compilerfailures"
|
||||
end
|
||||
|
||||
fails_with :clang
|
||||
fails_with :llvm
|
||||
fails_with :gcc
|
||||
end
|
||||
|
||||
class TestNoCompilerFailures < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "nocompilerfailures"
|
||||
end
|
||||
|
||||
fails_with(:clang) { build 42 }
|
||||
fails_with(:llvm) { build 42 }
|
||||
fails_with(:gcc) { build 42 }
|
||||
end
|
||||
|
||||
class TestLLVMFailure < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "llvmfailure"
|
||||
end
|
||||
|
||||
fails_with :llvm
|
||||
end
|
||||
|
||||
class TestMixedCompilerFailures < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "mixedcompilerfailures"
|
||||
end
|
||||
|
||||
fails_with(:clang) { build MacOS.clang_build_version }
|
||||
fails_with(:llvm) { build 42 }
|
||||
fails_with(:gcc) { build 5666 }
|
||||
end
|
||||
|
||||
class TestMoreMixedCompilerFailures < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "moremixedcompilerfailures"
|
||||
end
|
||||
|
||||
fails_with(:clang) { build 42 }
|
||||
fails_with(:llvm) { build 2336 }
|
||||
fails_with(:gcc) { build 5666 }
|
||||
end
|
||||
|
||||
class TestEvenMoreMixedCompilerFailures < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "evenmoremixedcompilerfailures"
|
||||
end
|
||||
|
||||
fails_with :clang
|
||||
fails_with(:llvm) { build 2336 }
|
||||
fails_with(:gcc) { build 5648 }
|
||||
end
|
||||
|
||||
class TestBlockWithoutBuildCompilerFailure < Formula
|
||||
def initialize name=nil
|
||||
@url="file:///#{TEST_FOLDER}/tarballs/testball-0.1.tbz"
|
||||
@homepage = 'http://example.com/'
|
||||
super "blockwithoutbuildcompilerfailure"
|
||||
end
|
||||
|
||||
fails_with :clang do
|
||||
cause "failure"
|
||||
end
|
||||
end
|
||||
|
||||
@ -9,6 +9,7 @@ ABS__FILE__=File.expand_path(__FILE__)
|
||||
$:.push(File.expand_path(__FILE__+'/../..'))
|
||||
require 'extend/pathname'
|
||||
require 'exceptions'
|
||||
require 'utils'
|
||||
|
||||
# these are defined in global.rb, but we don't want to break our actual
|
||||
# homebrew tree, and we do want to test everything :)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user