Tab: track C++ stdlib in use

There are subtle incompatibilities between Apple's libstdc++ and the
libstdc++ used by the various GNU GCC formulae. In addition, we'll
likely also be supporting libc++ in the future, and that's also
incompatible with the other stdlibs.

Tracking it in the tab lets us make sure that dependencies are all
built against the same stdlib to avoid subtle breakage.
This commit is contained in:
Misty De Meo 2013-07-27 00:11:45 -07:00
parent 3ac74331a8
commit b71682bdc7
4 changed files with 126 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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