diff options
| author | Jack Nagel | 2012-03-18 13:58:13 -0500 |
|---|---|---|
| committer | Jack Nagel | 2012-04-01 12:39:59 -0500 |
| commit | de444ead0b8898ea2989d6a8d5984c4c31318a64 (patch) | |
| tree | d2cad5d7b20789f8a990b4293a58da7b36acbe5f | |
| parent | bfbfdf03eb2bcbb73082af0325f85761ea87719e (diff) | |
| download | brew-de444ead0b8898ea2989d6a8d5984c4c31318a64.tar.bz2 | |
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>
| -rwxr-xr-x | Library/Homebrew/build.rb | 6 | ||||
| -rw-r--r-- | Library/Homebrew/compat/compatibility.rb | 59 | ||||
| -rw-r--r-- | Library/Homebrew/compilers.rb | 138 | ||||
| -rw-r--r-- | Library/Homebrew/formula.rb | 30 | ||||
| -rw-r--r-- | Library/Homebrew/formula_support.rb | 58 | ||||
| -rw-r--r-- | Library/Homebrew/test/test_formula.rb | 69 | ||||
| -rw-r--r-- | Library/Homebrew/test/testball.rb | 82 | ||||
| -rw-r--r-- | Library/Homebrew/test/testing_env.rb | 1 |
8 files changed, 370 insertions, 73 deletions
diff --git a/Library/Homebrew/build.rb b/Library/Homebrew/build.rb index 8f1fe3924..319f58df9 100755 --- a/Library/Homebrew/build.rb +++ b/Library/Homebrew/build.rb @@ -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" diff --git a/Library/Homebrew/compat/compatibility.rb b/Library/Homebrew/compat/compatibility.rb index 3d67de077..acb03a75f 100644 --- a/Library/Homebrew/compat/compatibility.rb +++ b/Library/Homebrew/compat/compatibility.rb @@ -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 diff --git a/Library/Homebrew/compilers.rb b/Library/Homebrew/compilers.rb new file mode 100644 index 000000000..ff56d34a0 --- /dev/null +++ b/Library/Homebrew/compilers.rb @@ -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 diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index c8ef58e50..eb1d7dcbe 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -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 diff --git a/Library/Homebrew/formula_support.rb b/Library/Homebrew/formula_support.rb index df98f3cb4..3bda65dcd 100644 --- a/Library/Homebrew/formula_support.rb +++ b/Library/Homebrew/formula_support.rb @@ -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 diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb index a819e5751..0735d55d6 100644 --- a/Library/Homebrew/test/test_formula.rb +++ b/Library/Homebrew/test/test_formula.rb @@ -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 diff --git a/Library/Homebrew/test/testball.rb b/Library/Homebrew/test/testball.rb index aae18115c..a77d7709c 100644 --- a/Library/Homebrew/test/testball.rb +++ b/Library/Homebrew/test/testball.rb @@ -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 diff --git a/Library/Homebrew/test/testing_env.rb b/Library/Homebrew/test/testing_env.rb index c0556651c..1e183287a 100644 --- a/Library/Homebrew/test/testing_env.rb +++ b/Library/Homebrew/test/testing_env.rb @@ -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 :) |
