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:
Jack Nagel 2012-03-18 13:58:13 -05:00
parent bfbfdf03eb
commit de444ead0b
8 changed files with 370 additions and 73 deletions

View File

@ -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"

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 :)