diff --git a/Library/Homebrew/build.rb b/Library/Homebrew/build.rb index ac60221d94..9d25c9a865 100755 --- a/Library/Homebrew/build.rb +++ b/Library/Homebrew/build.rb @@ -170,7 +170,8 @@ class Build begin f.install - Tab.create(f, Options.coerce(ARGV.options_only)).write + Tab.create(f, ENV.compiler, + Options.coerce(ARGV.options_only)).write rescue Exception => e if ARGV.debug? debrew e, f diff --git a/Library/Homebrew/cxxstdlib.rb b/Library/Homebrew/cxxstdlib.rb new file mode 100644 index 0000000000..4e3bed22e8 --- /dev/null +++ b/Library/Homebrew/cxxstdlib.rb @@ -0,0 +1,44 @@ +class CxxStdlib + attr_accessor :type, :compiler + + def initialize(type, compiler) + if ![:libstdcxx, :libcxx].include? type + raise ArgumentError, "Invalid C++ stdlib type: #{type}" + end + + @type = type.to_sym + @compiler = compiler.to_sym + end + + def apple_compiler? + not compiler.to_s =~ SharedEnvExtension::GNU_GCC_REGEXP + end + + def compatible_with?(other) + # libstdc++ and libc++ aren't ever intercompatible + return false unless type == other.type + + # libstdc++ is compatible across Apple compilers, but + # not between Apple and GNU compilers, or between GNU compiler versions + return false if apple_compiler? && !other.apple_compiler? + if compiler.to_s =~ SharedEnvExtension::GNU_GCC_REGEXP + return false unless other.compiler.to_s =~ SharedEnvExtension::GNU_GCC_REGEXP + return false unless compiler.to_s[4..6] == other.compiler.to_s[4..6] + end + + true + end + + def check_dependencies(formula, deps) + deps.each do |dep| + dep_stdlib = Tab.for_formula(dep.to_formula).cxxstdlib + if !compatible_with? dep_stdlib + raise IncompatibleCxxStdlibs.new(formula, dep, dep_stdlib, self) + end + end + end + + def type_string + type.to_s.gsub(/cxx$/, 'c++') + end +end diff --git a/Library/Homebrew/tab.rb b/Library/Homebrew/tab.rb index a55df156d5..9b19e95102 100644 --- a/Library/Homebrew/tab.rb +++ b/Library/Homebrew/tab.rb @@ -1,3 +1,4 @@ +require 'cxxstdlib' require 'ostruct' require 'options' require 'utils/json' @@ -9,7 +10,7 @@ require 'utils/json' class Tab < OpenStruct FILENAME = 'INSTALL_RECEIPT.json' - def self.create f, args + def self.create f, stdlib, compiler, args f.build.args = args sha = HOMEBREW_REPOSITORY.cd do @@ -23,7 +24,9 @@ class Tab < OpenStruct :poured_from_bottle => false, :tapped_from => f.tap, :time => Time.now.to_i, # to_s would be better but Ruby has no from_s function :P - :HEAD => sha + :HEAD => sha, + :compiler => compiler, + :stdlib => stdlib end def self.from_file path @@ -59,7 +62,9 @@ class Tab < OpenStruct :poured_from_bottle => false, :tapped_from => "", :time => nil, - :HEAD => nil + :HEAD => nil, + :stdlib => :libstdcxx, + :compiler => :clang end def with? name @@ -92,6 +97,13 @@ class Tab < OpenStruct used_options + unused_options end + def cxxstdlib + # Older tabs won't have these values, so provide sensible defaults + lib = stdlib || :libstdcxx + cc = compiler || MacOS.default_compiler + CxxStdlib.new(lib.to_sym, cc.to_sym) + end + def to_json Utils::JSON.dump({ :used_options => used_options.map(&:to_s), @@ -100,7 +112,9 @@ class Tab < OpenStruct :poured_from_bottle => poured_from_bottle, :tapped_from => tapped_from, :time => time, - :HEAD => send("HEAD")}) + :HEAD => send("HEAD"), + :stdlib => (stdlib.to_s if stdlib), + :compiler => (compiler.to_s if compiler)}) end def write diff --git a/Library/Homebrew/test/test_stdlib.rb b/Library/Homebrew/test/test_stdlib.rb new file mode 100644 index 0000000000..a5e276746c --- /dev/null +++ b/Library/Homebrew/test/test_stdlib.rb @@ -0,0 +1,62 @@ +require 'testing_env' +require 'test/testball' +require 'formula' +require 'cxxstdlib' +require 'tab' + +class CxxStdlibTests < Test::Unit::TestCase + def setup + @clang = CxxStdlib.new(:libstdcxx, :clang) + @gcc = CxxStdlib.new(:libstdcxx, :gcc) + @llvm = CxxStdlib.new(:libstdcxx, :llvm) + @gcc4 = CxxStdlib.new(:libstdcxx, :gcc_4_0) + @gcc48 = CxxStdlib.new(:libstdcxx, 'gcc-4.8') + @gcc49 = CxxStdlib.new(:libstdcxx, 'gcc-4.9') + @lcxx = CxxStdlib.new(:libcxx, :clang) + end + + def test_apple_libstdcxx_intercompatibility + assert @clang.compatible_with?(@gcc) + assert @clang.compatible_with?(@llvm) + assert @clang.compatible_with?(@gcc4) + end + + def test_compatibility_same_compilers_and_type + assert @gcc48.compatible_with?(@gcc48) + assert @clang.compatible_with?(@clang) + end + + def test_apple_gnu_libstdcxx_incompatibility + assert !@clang.compatible_with?(@gcc48) + assert !@gcc48.compatible_with?(@clang) + end + + def test_gnu_cross_version_incompatibility + assert !@clang.compatible_with?(@gcc48) + assert !@gcc48.compatible_with?(@clang) + end + + def test_libstdcxx_libcxx_incompatibility + assert !@clang.compatible_with?(@lcxx) + assert !@lcxx.compatible_with?(@clang) + end + + def test_apple_compiler_reporting + assert @clang.apple_compiler? + assert @gcc.apple_compiler? + assert @llvm.apple_compiler? + assert @gcc4.apple_compiler? + assert !@gcc48.apple_compiler? + end + + def test_type_string_formatting + assert_equal @clang.type_string, 'libstdc++' + assert_equal @lcxx.type_string, 'libc++' + end + + def test_constructing_from_tab + stdlib = Tab.dummy_tab.cxxstdlib + assert_equal stdlib.compiler, :clang + assert_equal stdlib.type, :libstdcxx + end +end