diff --git a/Library/Homebrew/compilers.rb b/Library/Homebrew/compilers.rb index 47baccc603..26b2bc1b6d 100644 --- a/Library/Homebrew/compilers.rb +++ b/Library/Homebrew/compilers.rb @@ -19,7 +19,7 @@ end # # @api private class CompilerFailure - attr_reader :name + attr_reader :type def version(val = nil) @version = Version.parse(val.to_s) if val @@ -42,29 +42,51 @@ class CompilerFailure def self.create(spec, &block) # Non-Apple compilers are in the format fails_with compiler => version if spec.is_a?(Hash) - _, major_version = spec.first - name = "gcc-#{major_version}" + compiler, major_version = spec.first + raise ArgumentError, "The hash `fails_with` syntax only supports GCC" if compiler != :gcc + + type = compiler # so fails_with :gcc => '7' simply marks all 7 releases incompatible version = "#{major_version}.999" + exact_major_match = true else - name = spec + type = spec version = 9999 + exact_major_match = false end - new(name, version, &block) - end - - def initialize(name, version, &block) - @name = name - @version = Version.parse(version.to_s) - instance_eval(&block) if block + new(type, version, exact_major_match: exact_major_match, &block) end def fails_with?(compiler) - name == compiler.name && version >= compiler.version + version_matched = if type != :gcc + version >= compiler.version + elsif @exact_major_match + gcc_major(version) == gcc_major(compiler.version) && version >= compiler.version + else + gcc_major(version) >= gcc_major(compiler.version) + end + type == compiler.type && version_matched end def inspect - "#<#{self.class.name}: #{name} #{version}>" + "#<#{self.class.name}: #{type} #{version}>" + end + + private + + def initialize(type, version, exact_major_match:, &block) + @type = type + @version = Version.parse(version.to_s) + @exact_major_match = exact_major_match + instance_eval(&block) if block + end + + def gcc_major(version) + if version.major >= 5 + Version.new(version.major.to_s) + else + version.major_minor + end end COLLECTIONS = { @@ -81,7 +103,7 @@ class CompilerSelector extend T::Sig include CompilerConstants - Compiler = Struct.new(:name, :version) + Compiler = Struct.new(:type, :name, :version) COMPILER_PRIORITY = { clang: [:clang, :gnu, :llvm_clang], @@ -130,15 +152,15 @@ class CompilerSelector case compiler when :gnu gnu_gcc_versions.reverse_each do |v| - name = "gcc-#{v}" - version = compiler_version(name) - yield Compiler.new(name, version) unless version.null? + executable = "gcc-#{v}" + version = compiler_version(executable) + yield Compiler.new(:gcc, executable, version) unless version.null? end when :llvm next # no-op. DSL supported, compiler is not. else version = compiler_version(compiler) - yield Compiler.new(compiler, version) unless version.null? + yield Compiler.new(compiler, compiler, version) unless version.null? end end end diff --git a/Library/Homebrew/test/compiler_failure_spec.rb b/Library/Homebrew/test/compiler_failure_spec.rb index 4ec13cc57b..2be605f4b9 100644 --- a/Library/Homebrew/test/compiler_failure_spec.rb +++ b/Library/Homebrew/test/compiler_failure_spec.rb @@ -9,31 +9,31 @@ describe CompilerFailure do describe "::create" do it "creates a failure when given a symbol" do failure = described_class.create(:clang) - expect(failure).to fail_with(double("Compiler", name: :clang, version: 600)) + expect(failure).to fail_with(double("Compiler", type: :clang, name: :clang, version: 600)) end it "can be given a build number in a block" do failure = described_class.create(:clang) { build 700 } - expect(failure).to fail_with(double("Compiler", name: :clang, version: 700)) + expect(failure).to fail_with(double("Compiler", type: :clang, name: :clang, version: 700)) end it "can be given an empty block" do failure = described_class.create(:clang) {} - expect(failure).to fail_with(double("Compiler", name: :clang, version: 600)) + expect(failure).to fail_with(double("Compiler", type: :clang, name: :clang, version: 600)) end it "creates a failure when given a hash" do failure = described_class.create(gcc: "7") - expect(failure).to fail_with(double("Compiler", name: "gcc-7", version: "7")) - expect(failure).to fail_with(double("Compiler", name: "gcc-7", version: "7.1")) - expect(failure).not_to fail_with(double("Compiler", name: "gcc-6", version: "6.0")) + expect(failure).to fail_with(double("Compiler", type: :gcc, name: "gcc-7", version: Version.new("7"))) + expect(failure).to fail_with(double("Compiler", type: :gcc, name: "gcc-7", version: Version.new("7.1"))) + expect(failure).not_to fail_with(double("Compiler", type: :gcc, name: "gcc-6", version: Version.new("6.0"))) end it "creates a failure when given a hash and a block with aversion" do failure = described_class.create(gcc: "7") { version "7.1" } - expect(failure).to fail_with(double("Compiler", name: "gcc-7", version: "7")) - expect(failure).to fail_with(double("Compiler", name: "gcc-7", version: "7.1")) - expect(failure).not_to fail_with(double("Compiler", name: "gcc-7", version: "7.2")) + expect(failure).to fail_with(double("Compiler", type: :gcc, name: "gcc-7", version: Version.new("7"))) + expect(failure).to fail_with(double("Compiler", type: :gcc, name: "gcc-7", version: Version.new("7.1"))) + expect(failure).not_to fail_with(double("Compiler", type: :gcc, name: "gcc-7", version: Version.new("7.2"))) end end end diff --git a/Library/Homebrew/test/compiler_selector_spec.rb b/Library/Homebrew/test/compiler_selector_spec.rb index 46c62fe8a3..2f421a20bd 100644 --- a/Library/Homebrew/test/compiler_selector_spec.rb +++ b/Library/Homebrew/test/compiler_selector_spec.rb @@ -23,6 +23,7 @@ describe CompilerSelector do when "gcc-7" then Version.create("7.1") when "gcc-6" then Version.create("6.1") when "gcc-5" then Version.create("5.1") + when "gcc-4.9" then Version.create("4.9.1") else Version::NULL end end @@ -45,29 +46,56 @@ describe CompilerSelector do it "returns gcc-6 if gcc formula offers gcc-6 on mac", :needs_macos do software_spec.fails_with(:clang) - allow(Formulary).to receive(:factory).with("gcc").and_return(double(version: "6.0")) + allow(Formulary).to receive(:factory).with("gcc").and_return(double(version: Version.new("6.0"))) expect(selector.compiler).to eq("gcc-6") end it "returns gcc-5 if gcc formula offers gcc-5 on linux", :needs_linux do software_spec.fails_with(:clang) - allow(Formulary).to receive(:factory).with("gcc@5").and_return(double(version: "5.0")) + allow(Formulary).to receive(:factory).with("gcc@5").and_return(double(version: Version.new("5.0"))) expect(selector.compiler).to eq("gcc-5") end - it "returns gcc-6 if gcc formula offers gcc-6 and fails with gcc-5 and gcc-7 on linux", :needs_linux do + it "returns gcc-6 if gcc formula offers gcc-5 and fails with gcc-5 and gcc-7 on linux", :needs_linux do software_spec.fails_with(:clang) software_spec.fails_with(gcc: "5") software_spec.fails_with(gcc: "7") - allow(Formulary).to receive(:factory).with("gcc@5").and_return(double(version: "5.0")) + allow(Formulary).to receive(:factory).with("gcc@5").and_return(double(version: Version.new("5.0"))) expect(selector.compiler).to eq("gcc-6") end - it "raises an error when gcc or llvm is missing" do + it "returns gcc-7 if gcc formula offers gcc-5 and fails with gcc <= 6 on linux", :needs_linux do + software_spec.fails_with(:clang) + software_spec.fails_with(:gcc) { version "6" } + allow(Formulary).to receive(:factory).with("gcc@5").and_return(double(version: Version.new("5.0"))) + expect(selector.compiler).to eq("gcc-7") + end + + it "returns gcc-7 if gcc-7 is version 7.1 but spec fails with gcc-7 <= 7.0" do + software_spec.fails_with(:clang) + software_spec.fails_with(gcc: "7") { version "7.0" } + expect(selector.compiler).to eq("gcc-7") + end + + it "returns gcc-6 if gcc-7 is version 7.1 but spec fails with gcc-7 <= 7.1" do + software_spec.fails_with(:clang) + software_spec.fails_with(gcc: "7") { version "7.1" } + expect(selector.compiler).to eq("gcc-6") + end + + it "raises an error when gcc or llvm is missing (hash syntax)" do software_spec.fails_with(:clang) software_spec.fails_with(gcc: "7") software_spec.fails_with(gcc: "6") software_spec.fails_with(gcc: "5") + software_spec.fails_with(gcc: "4.9") + + expect { selector.compiler }.to raise_error(CompilerSelectionError) + end + + it "raises an error when gcc or llvm is missing (symbol syntax)" do + software_spec.fails_with(:clang) + software_spec.fails_with(:gcc) expect { selector.compiler }.to raise_error(CompilerSelectionError) end