Skip to content

Commit 47b7bf3

Browse files
authored
Merge pull request #20682 from Homebrew/libstdcxx
Check host libstdc++ for brew `gcc` dependency
2 parents d7c0052 + f5c11fa commit 47b7bf3

File tree

7 files changed

+127
-15
lines changed

7 files changed

+127
-15
lines changed

Library/Homebrew/extend/os/linux/development_tools.rb

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ def needs_libc_formula?
4242
@needs_libc_formula ||= OS::Linux::Glibc.below_ci_version?
4343
end
4444

45-
# Keep this method around for now to make it easier to add this functionality later.
46-
# rubocop:disable Lint/UselessMethodDefinition
4745
sig { returns(Pathname) }
4846
def host_gcc_path
49-
# TODO: override this if/when we to pick the GCC based on e.g. the Ubuntu version.
47+
# Prioritise versioned path if installed
48+
path = Pathname.new("/usr/bin/#{OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA.tr("@", "-")}")
49+
return path if path.exist?
50+
5051
super
5152
end
52-
# rubocop:enable Lint/UselessMethodDefinition
5353

5454
sig { returns(T::Boolean) }
5555
def needs_compiler_formula?
@@ -60,12 +60,7 @@ def needs_compiler_formula?
6060
# Undocumented environment variable to make it easier to test compiler
6161
# formula automatic installation.
6262
@needs_compiler_formula = true if ENV["HOMEBREW_FORCE_COMPILER_FORMULA"]
63-
64-
@needs_compiler_formula ||= if host_gcc_path.exist?
65-
::DevelopmentTools.gcc_version(host_gcc_path.to_s) < OS::LINUX_GCC_CI_VERSION
66-
else
67-
true
68-
end
63+
@needs_compiler_formula ||= OS::Linux::Libstdcxx.below_ci_version?
6964
end
7065

7166
sig { returns(T::Hash[String, T.nilable(String)]) }

Library/Homebrew/extend/os/linux/install.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# frozen_string_literal: true
33

44
require "os/linux/ld"
5+
require "os/linux/libstdcxx"
56
require "utils/output"
67

78
module OS
@@ -12,12 +13,12 @@ module ClassMethods
1213
# which are linked by the GCC formula. We only use the versioned shared libraries
1314
# as the other shared and static libraries are only used at build time where
1415
# GCC can find its own libraries.
15-
GCC_RUNTIME_LIBS = %w[
16+
GCC_RUNTIME_LIBS = T.let(%W[
1617
libatomic.so.1
1718
libgcc_s.so.1
1819
libgomp.so.1
19-
libstdc++.so.6
20-
].freeze
20+
#{OS::Linux::Libstdcxx::SONAME}
21+
].freeze, T::Array[String])
2122

2223
sig { params(all_fatal: T::Boolean).void }
2324
def perform_preinstall_checks(all_fatal: false)

Library/Homebrew/extend/os/linux/linkage_checker.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
# frozen_string_literal: true
33

44
require "compilers"
5+
require "os/linux/libstdcxx"
56

67
module OS
78
module Linux
89
module LinkageChecker
910
# Libraries provided by glibc and gcc.
10-
SYSTEM_LIBRARY_ALLOWLIST = %w[
11+
SYSTEM_LIBRARY_ALLOWLIST = %W[
1112
ld-linux-x86-64.so.2
1213
ld-linux-aarch64.so.1
1314
libanl.so.1
@@ -24,7 +25,7 @@ module LinkageChecker
2425
libutil.so.1
2526
libgcc_s.so.1
2627
libgomp.so.1
27-
libstdc++.so.6
28+
#{OS::Linux::Libstdcxx::SONAME}
2829
libquadmath.so.0
2930
].freeze
3031

Library/Homebrew/extend/os/linux/system_config.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
require "compilers"
55
require "os/linux/glibc"
6+
require "os/linux/libstdcxx"
67
require "system_command"
78

89
module OS
@@ -20,6 +21,13 @@ def host_glibc_version
2021
version
2122
end
2223

24+
def host_libstdcxx_version
25+
version = OS::Linux::Libstdcxx.system_version
26+
return "N/A" if version.null?
27+
28+
version
29+
end
30+
2331
def host_gcc_version
2432
gcc = ::DevelopmentTools.host_gcc_path
2533
return "N/A" unless gcc.executable?
@@ -49,6 +57,7 @@ def dump_verbose_config(out = $stdout)
4957
out.puts "OS: #{OS::Linux.os_version}"
5058
out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl?
5159
out.puts "Host glibc: #{host_glibc_version}"
60+
out.puts "Host libstdc++: #{host_libstdcxx_version}"
5261
out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}"
5362
out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH
5463
["glibc", ::CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f|

Library/Homebrew/os.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def self.kernel_name
5050
LINUX_GLIBC_CI_VERSION = "2.35"
5151
LINUX_GLIBC_NEXT_CI_VERSION = "2.39"
5252
LINUX_GCC_CI_VERSION = "11.0"
53+
LINUX_LIBSTDCXX_CI_VERSION = "6.0.30" # https://packages.ubuntu.com/jammy/libstdc++6
5354
LINUX_PREFERRED_GCC_COMPILER_FORMULA = "gcc@11" # https://packages.ubuntu.com/jammy/gcc
5455
LINUX_PREFERRED_GCC_RUNTIME_FORMULA = "gcc"
5556

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
require "os/linux/ld"
5+
6+
module OS
7+
module Linux
8+
# Helper functions for querying `libstdc++` information.
9+
module Libstdcxx
10+
SOVERSION = 6
11+
SONAME = T.let("libstdc++.so.#{SOVERSION}".freeze, String)
12+
13+
sig { returns(T::Boolean) }
14+
def self.below_ci_version?
15+
system_version < LINUX_LIBSTDCXX_CI_VERSION
16+
end
17+
18+
sig { returns(Version) }
19+
def self.system_version
20+
@system_version ||= T.let(nil, T.nilable(Version))
21+
@system_version ||= if (path = system_path)
22+
Version.new("#{SOVERSION}#{path.realpath.basename.to_s.delete_prefix!(SONAME)}")
23+
else
24+
Version::NULL
25+
end
26+
end
27+
28+
sig { returns(T.nilable(Pathname)) }
29+
def self.system_path
30+
@system_path ||= T.let(nil, T.nilable(Pathname))
31+
@system_path ||= find_library(OS::Linux::Ld.library_paths(brewed: false))
32+
@system_path ||= find_library(OS::Linux::Ld.system_dirs(brewed: false))
33+
end
34+
35+
sig { params(paths: T::Array[String]).returns(T.nilable(Pathname)) }
36+
private_class_method def self.find_library(paths)
37+
paths.each do |path|
38+
next if path.start_with?(HOMEBREW_PREFIX)
39+
40+
candidate = Pathname(path)/SONAME
41+
return candidate if candidate.exist? && candidate.elf?
42+
end
43+
nil
44+
end
45+
end
46+
end
47+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
require "os/linux/libstdcxx"
4+
5+
RSpec.describe OS::Linux::Libstdcxx do
6+
describe "::below_ci_version?" do
7+
it "returns false when system version matches CI version" do
8+
allow(described_class).to receive(:system_version).and_return(Version.new(OS::LINUX_LIBSTDCXX_CI_VERSION))
9+
expect(described_class.below_ci_version?).to be false
10+
end
11+
12+
it "returns true when system version cannot be detected" do
13+
allow(described_class).to receive(:system_version).and_return(Version::NULL)
14+
expect(described_class.below_ci_version?).to be true
15+
end
16+
end
17+
18+
describe "::system_version" do
19+
let(:tmpdir) { mktmpdir }
20+
let(:libstdcxx) { tmpdir/described_class::SONAME }
21+
let(:soversion) { Version.new(described_class::SOVERSION.to_s) }
22+
23+
before do
24+
tmpdir.mkpath
25+
described_class.instance_variable_set(:@system_version, nil)
26+
allow(described_class).to receive(:system_path).and_return(libstdcxx)
27+
end
28+
29+
after do
30+
FileUtils.rm_rf(tmpdir)
31+
end
32+
33+
it "returns NULL when unable to find system path" do
34+
allow(described_class).to receive(:system_path).and_return(nil)
35+
expect(described_class.system_version).to be Version::NULL
36+
end
37+
38+
it "returns full version from filename" do
39+
full_version = Version.new("#{soversion}.0.999")
40+
libstdcxx_real = libstdcxx.sub_ext(".#{full_version}")
41+
FileUtils.touch libstdcxx_real
42+
FileUtils.ln_s libstdcxx_real, libstdcxx
43+
expect(described_class.system_version).to eq full_version
44+
end
45+
46+
it "returns major version when non-standard libstdc++ filename without full version" do
47+
FileUtils.touch libstdcxx
48+
expect(described_class.system_version).to eq soversion
49+
end
50+
51+
it "returns major version when non-standard libstdc++ filename with unexpected realpath" do
52+
libstdcxx_real = tmpdir/"libstdc++.so.real"
53+
FileUtils.touch libstdcxx_real
54+
FileUtils.ln_s libstdcxx_real, libstdcxx
55+
expect(described_class.system_version).to eq soversion
56+
end
57+
end
58+
end

0 commit comments

Comments
 (0)