Check host libstdc++ for brew gcc
dependency
For most formulae, the bottles need a minimum libstdc++ rather than a minimum GCC version. This is particularly important when building on Ubuntu where the default compiler version is older than libstdc++. So, checking the host libstdc++ version is a more accurate way to determine whether brew GCC is needed at runtime. This can be improved in the future to check symbol versions (e.g. GLIBCXX, CXXABI, GLIBC) which can allow some bottles to be installed even with older glibc/libstdc++.
This commit is contained in:
parent
eda9e78529
commit
f5c11fa342
@ -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)]) }
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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|
|
||||
|
@ -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"
|
||||
|
||||
|
47
Library/Homebrew/os/linux/libstdcxx.rb
Normal file
47
Library/Homebrew/os/linux/libstdcxx.rb
Normal file
@ -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
|
58
Library/Homebrew/test/os/linux/libstdcxx_spec.rb
Normal file
58
Library/Homebrew/test/os/linux/libstdcxx_spec.rb
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user