diff --git a/Library/Homebrew/extend/os/linux/development_tools.rb b/Library/Homebrew/extend/os/linux/development_tools.rb index 04827adee3..2ab9898841 100644 --- a/Library/Homebrew/extend/os/linux/development_tools.rb +++ b/Library/Homebrew/extend/os/linux/development_tools.rb @@ -42,14 +42,14 @@ module OS @needs_libc_formula ||= OS::Linux::Glibc.below_ci_version? end - # Keep this method around for now to make it easier to add this functionality later. - # rubocop:disable Lint/UselessMethodDefinition sig { returns(Pathname) } def host_gcc_path - # TODO: override this if/when we to pick the GCC based on e.g. the Ubuntu version. + # Prioritise versioned path if installed + path = Pathname.new("/usr/bin/#{OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA.tr("@", "-")}") + return path if path.exist? + super end - # rubocop:enable Lint/UselessMethodDefinition sig { returns(T::Boolean) } def needs_compiler_formula? @@ -60,12 +60,7 @@ module OS # Undocumented environment variable to make it easier to test compiler # formula automatic installation. @needs_compiler_formula = true if ENV["HOMEBREW_FORCE_COMPILER_FORMULA"] - - @needs_compiler_formula ||= if host_gcc_path.exist? - ::DevelopmentTools.gcc_version(host_gcc_path.to_s) < OS::LINUX_GCC_CI_VERSION - else - true - end + @needs_compiler_formula ||= OS::Linux::Libstdcxx.below_ci_version? end sig { returns(T::Hash[String, T.nilable(String)]) } diff --git a/Library/Homebrew/extend/os/linux/install.rb b/Library/Homebrew/extend/os/linux/install.rb index 9098dce7ce..6287faa84f 100644 --- a/Library/Homebrew/extend/os/linux/install.rb +++ b/Library/Homebrew/extend/os/linux/install.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "os/linux/ld" +require "os/linux/libstdcxx" require "utils/output" module OS @@ -12,12 +13,12 @@ module OS # which are linked by the GCC formula. We only use the versioned shared libraries # as the other shared and static libraries are only used at build time where # GCC can find its own libraries. - GCC_RUNTIME_LIBS = %w[ + GCC_RUNTIME_LIBS = T.let(%W[ libatomic.so.1 libgcc_s.so.1 libgomp.so.1 - libstdc++.so.6 - ].freeze + #{OS::Linux::Libstdcxx::SONAME} + ].freeze, T::Array[String]) sig { params(all_fatal: T::Boolean).void } def perform_preinstall_checks(all_fatal: false) diff --git a/Library/Homebrew/extend/os/linux/linkage_checker.rb b/Library/Homebrew/extend/os/linux/linkage_checker.rb index 634bd5b71f..82a3d46d27 100644 --- a/Library/Homebrew/extend/os/linux/linkage_checker.rb +++ b/Library/Homebrew/extend/os/linux/linkage_checker.rb @@ -2,12 +2,13 @@ # frozen_string_literal: true require "compilers" +require "os/linux/libstdcxx" module OS module Linux module LinkageChecker # Libraries provided by glibc and gcc. - SYSTEM_LIBRARY_ALLOWLIST = %w[ + SYSTEM_LIBRARY_ALLOWLIST = %W[ ld-linux-x86-64.so.2 ld-linux-aarch64.so.1 libanl.so.1 @@ -24,7 +25,7 @@ module OS libutil.so.1 libgcc_s.so.1 libgomp.so.1 - libstdc++.so.6 + #{OS::Linux::Libstdcxx::SONAME} libquadmath.so.0 ].freeze diff --git a/Library/Homebrew/extend/os/linux/system_config.rb b/Library/Homebrew/extend/os/linux/system_config.rb index 6a967b3a2d..77343a36ff 100644 --- a/Library/Homebrew/extend/os/linux/system_config.rb +++ b/Library/Homebrew/extend/os/linux/system_config.rb @@ -3,6 +3,7 @@ require "compilers" require "os/linux/glibc" +require "os/linux/libstdcxx" require "system_command" module OS @@ -20,6 +21,13 @@ module OS version end + def host_libstdcxx_version + version = OS::Linux::Libstdcxx.system_version + return "N/A" if version.null? + + version + end + def host_gcc_version gcc = ::DevelopmentTools.host_gcc_path return "N/A" unless gcc.executable? @@ -49,6 +57,7 @@ module OS out.puts "OS: #{OS::Linux.os_version}" out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl? out.puts "Host glibc: #{host_glibc_version}" + out.puts "Host libstdc++: #{host_libstdcxx_version}" out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}" out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH ["glibc", ::CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f| diff --git a/Library/Homebrew/os.rb b/Library/Homebrew/os.rb index 3cd2b7f6f1..ffa463e864 100644 --- a/Library/Homebrew/os.rb +++ b/Library/Homebrew/os.rb @@ -50,6 +50,7 @@ module OS LINUX_GLIBC_CI_VERSION = "2.35" LINUX_GLIBC_NEXT_CI_VERSION = "2.39" LINUX_GCC_CI_VERSION = "11.0" + LINUX_LIBSTDCXX_CI_VERSION = "6.0.30" # https://packages.ubuntu.com/jammy/libstdc++6 LINUX_PREFERRED_GCC_COMPILER_FORMULA = "gcc@11" # https://packages.ubuntu.com/jammy/gcc LINUX_PREFERRED_GCC_RUNTIME_FORMULA = "gcc" diff --git a/Library/Homebrew/os/linux/libstdcxx.rb b/Library/Homebrew/os/linux/libstdcxx.rb new file mode 100644 index 0000000000..86efb98830 --- /dev/null +++ b/Library/Homebrew/os/linux/libstdcxx.rb @@ -0,0 +1,47 @@ +# typed: strict +# frozen_string_literal: true + +require "os/linux/ld" + +module OS + module Linux + # Helper functions for querying `libstdc++` information. + module Libstdcxx + SOVERSION = 6 + SONAME = T.let("libstdc++.so.#{SOVERSION}".freeze, String) + + sig { returns(T::Boolean) } + def self.below_ci_version? + system_version < LINUX_LIBSTDCXX_CI_VERSION + end + + sig { returns(Version) } + def self.system_version + @system_version ||= T.let(nil, T.nilable(Version)) + @system_version ||= if (path = system_path) + Version.new("#{SOVERSION}#{path.realpath.basename.to_s.delete_prefix!(SONAME)}") + else + Version::NULL + end + end + + sig { returns(T.nilable(Pathname)) } + def self.system_path + @system_path ||= T.let(nil, T.nilable(Pathname)) + @system_path ||= find_library(OS::Linux::Ld.library_paths(brewed: false)) + @system_path ||= find_library(OS::Linux::Ld.system_dirs(brewed: false)) + end + + sig { params(paths: T::Array[String]).returns(T.nilable(Pathname)) } + private_class_method def self.find_library(paths) + paths.each do |path| + next if path.start_with?(HOMEBREW_PREFIX) + + candidate = Pathname(path)/SONAME + return candidate if candidate.exist? && candidate.elf? + end + nil + end + end + end +end diff --git a/Library/Homebrew/test/os/linux/libstdcxx_spec.rb b/Library/Homebrew/test/os/linux/libstdcxx_spec.rb new file mode 100644 index 0000000000..5f6200064e --- /dev/null +++ b/Library/Homebrew/test/os/linux/libstdcxx_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "os/linux/libstdcxx" + +RSpec.describe OS::Linux::Libstdcxx do + describe "::below_ci_version?" do + it "returns false when system version matches CI version" do + allow(described_class).to receive(:system_version).and_return(Version.new(OS::LINUX_LIBSTDCXX_CI_VERSION)) + expect(described_class.below_ci_version?).to be false + end + + it "returns true when system version cannot be detected" do + allow(described_class).to receive(:system_version).and_return(Version::NULL) + expect(described_class.below_ci_version?).to be true + end + end + + describe "::system_version" do + let(:tmpdir) { mktmpdir } + let(:libstdcxx) { tmpdir/described_class::SONAME } + let(:soversion) { Version.new(described_class::SOVERSION.to_s) } + + before do + tmpdir.mkpath + described_class.instance_variable_set(:@system_version, nil) + allow(described_class).to receive(:system_path).and_return(libstdcxx) + end + + after do + FileUtils.rm_rf(tmpdir) + end + + it "returns NULL when unable to find system path" do + allow(described_class).to receive(:system_path).and_return(nil) + expect(described_class.system_version).to be Version::NULL + end + + it "returns full version from filename" do + full_version = Version.new("#{soversion}.0.999") + libstdcxx_real = libstdcxx.sub_ext(".#{full_version}") + FileUtils.touch libstdcxx_real + FileUtils.ln_s libstdcxx_real, libstdcxx + expect(described_class.system_version).to eq full_version + end + + it "returns major version when non-standard libstdc++ filename without full version" do + FileUtils.touch libstdcxx + expect(described_class.system_version).to eq soversion + end + + it "returns major version when non-standard libstdc++ filename with unexpected realpath" do + libstdcxx_real = tmpdir/"libstdc++.so.real" + FileUtils.touch libstdcxx_real + FileUtils.ln_s libstdcxx_real, libstdcxx + expect(described_class.system_version).to eq soversion + end + end +end