aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/compilers.rb
blob: eaaf041b564d1a97810e0736aeca9310d4f527d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
class Compilers
  include Enumerable

  def initialize(*args)
    @compilers = Array.new(*args)
  end

  def each(*args, &block)
    @compilers.each(*args, &block)
  end

  def include?(cc)
    cc = cc.name if cc.is_a? Compiler
    @compilers.any? { |c| c.name == cc }
  end

  def <<(o)
    @compilers << o
    self
  end
end


class CompilerFailures
  include Enumerable

  def initialize(*args)
    @failures = Array.new(*args)
  end

  def each(*args, &block)
    @failures.each(*args, &block)
  end

  def include?(cc)
    cc = Compiler.new(cc) unless cc.is_a? Compiler
    @failures.any? { |failure| failure.compiler == cc.name }
  end

  def <<(o)
    @failures << o unless include? o.compiler
    self
  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 = @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
    end
  end
end