Skip to content

Commit eda9e78

Browse files
authored
Merge pull request #20633 from Homebrew/ld-system
os/linux/ld: add support for using system ld.so
2 parents 5f4e42a + 896edb4 commit eda9e78

File tree

3 files changed

+108
-35
lines changed

3 files changed

+108
-35
lines changed

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

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4+
require "os/linux/ld"
45
require "utils/output"
56

67
module OS
78
module Linux
89
module Install
910
module ClassMethods
10-
# This is a list of known paths to the host dynamic linker on Linux if
11-
# the host glibc is new enough. The symlink_ld_so method will fail if
12-
# the host linker cannot be found in this list.
13-
DYNAMIC_LINKERS = %w[
14-
/lib64/ld-linux-x86-64.so.2
15-
/lib64/ld64.so.2
16-
/lib/ld-linux.so.3
17-
/lib/ld-linux.so.2
18-
/lib/ld-linux-aarch64.so.1
19-
/lib/ld-linux-armhf.so.3
20-
/system/bin/linker64
21-
/system/bin/linker
22-
].freeze
23-
2411
# We link GCC runtime libraries that are not specifically used for Fortran,
2512
# which are linked by the GCC formula. We only use the versioned shared libraries
2613
# as the other shared and static libraries are only used at build time where
@@ -67,7 +54,7 @@ def symlink_ld_so
6754

6855
ld_so = HOMEBREW_PREFIX/"opt/glibc/bin/ld.so"
6956
unless ld_so.readable?
70-
ld_so = DYNAMIC_LINKERS.find { |s| File.executable? s }
57+
ld_so = OS::Linux::Ld.system_ld_so
7158
if ld_so.blank?
7259
::Kernel.raise "Unable to locate the system's dynamic linker" unless brew_ld_so.readable?
7360

Library/Homebrew/os/linux/ld.rb

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,69 @@ module OS
55
module Linux
66
# Helper functions for querying `ld` information.
77
module Ld
8-
sig { returns(String) }
9-
def self.brewed_ld_so_diagnostics
10-
@brewed_ld_so_diagnostics ||= T.let({}, T.nilable(T::Hash[Pathname, String]))
8+
# This is a list of known paths to the host dynamic linker on Linux if
9+
# the host glibc is new enough. Brew will fail to create a symlink for
10+
# ld.so if the host linker cannot be found in this list.
11+
DYNAMIC_LINKERS = %w[
12+
/lib64/ld-linux-x86-64.so.2
13+
/lib64/ld64.so.2
14+
/lib/ld-linux.so.3
15+
/lib/ld-linux.so.2
16+
/lib/ld-linux-aarch64.so.1
17+
/lib/ld-linux-armhf.so.3
18+
/system/bin/linker64
19+
/system/bin/linker
20+
].freeze
21+
22+
# The path to the system's dynamic linker or `nil` if not found
23+
sig { returns(T.nilable(Pathname)) }
24+
def self.system_ld_so
25+
@system_ld_so ||= T.let(nil, T.nilable(Pathname))
26+
@system_ld_so ||= begin
27+
linker = DYNAMIC_LINKERS.find { |s| File.executable? s }
28+
Pathname(linker) if linker
29+
end
30+
end
31+
32+
sig { params(brewed: T::Boolean).returns(String) }
33+
def self.ld_so_diagnostics(brewed: true)
34+
@ld_so_diagnostics ||= T.let({}, T.nilable(T::Hash[Pathname, String]))
1135

12-
brewed_ld_so = HOMEBREW_PREFIX/"lib/ld.so"
13-
return "" unless brewed_ld_so.exist?
36+
ld_so_target = if brewed
37+
ld_so = HOMEBREW_PREFIX/"lib/ld.so"
38+
return "" unless ld_so.exist?
39+
40+
ld_so.readlink
41+
else
42+
ld_so = system_ld_so
43+
return "" unless ld_so&.exist?
44+
45+
ld_so
46+
end
1447

15-
brewed_ld_so_target = brewed_ld_so.readlink
16-
@brewed_ld_so_diagnostics[brewed_ld_so_target] ||= begin
17-
ld_so_output = Utils.popen_read(brewed_ld_so, "--list-diagnostics")
48+
@ld_so_diagnostics[ld_so_target] ||= begin
49+
ld_so_output = Utils.popen_read(ld_so, "--list-diagnostics")
1850
ld_so_output if $CHILD_STATUS.success?
1951
end
2052

21-
@brewed_ld_so_diagnostics[brewed_ld_so_target].to_s
53+
@ld_so_diagnostics[ld_so_target].to_s
2254
end
2355

24-
sig { returns(String) }
25-
def self.sysconfdir
56+
sig { params(brewed: T::Boolean).returns(String) }
57+
def self.sysconfdir(brewed: true)
2658
fallback_sysconfdir = "/etc"
2759

28-
match = brewed_ld_so_diagnostics.match(/path.sysconfdir="(.+)"/)
60+
match = ld_so_diagnostics(brewed:).match(/path.sysconfdir="(.+)"/)
2961
return fallback_sysconfdir unless match
3062

3163
match.captures.compact.first || fallback_sysconfdir
3264
end
3365

34-
sig { returns(T::Array[String]) }
35-
def self.system_dirs
66+
sig { params(brewed: T::Boolean).returns(T::Array[String]) }
67+
def self.system_dirs(brewed: true)
3668
dirs = []
3769

38-
brewed_ld_so_diagnostics.split("\n").each do |line|
70+
ld_so_diagnostics(brewed:).split("\n").each do |line|
3971
match = line.match(/path.system_dirs\[0x.*\]="(.*)"/)
4072
next unless match
4173

@@ -45,9 +77,9 @@ def self.system_dirs
4577
dirs
4678
end
4779

48-
sig { params(conf_path: T.any(Pathname, String)).returns(T::Array[String]) }
49-
def self.library_paths(conf_path = Pathname(sysconfdir)/"ld.so.conf")
50-
conf_file = Pathname(conf_path)
80+
sig { params(conf_path: T.any(Pathname, String), brewed: T::Boolean).returns(T::Array[String]) }
81+
def self.library_paths(conf_path = "ld.so.conf", brewed: true)
82+
conf_file = Pathname(sysconfdir(brewed:))/conf_path
5183
return [] unless conf_file.exist?
5284
return [] unless conf_file.file?
5385
return [] unless conf_file.readable?
@@ -68,8 +100,7 @@ def self.library_paths(conf_path = Pathname(sysconfdir)/"ld.so.conf")
68100
line.sub!(/\s*#.*$/, "")
69101

70102
if line.start_with?(/\s*include\s+/)
71-
include_path = Pathname(line.sub(/^\s*include\s+/, "")).expand_path
72-
wildcard = include_path.absolute? ? include_path : directory/include_path
103+
wildcard = Pathname(line.sub(/^\s*include\s+/, "")).expand_path(directory)
73104

74105
Dir.glob(wildcard.to_s).each do |include_file|
75106
paths += library_paths(include_file)

Library/Homebrew/test/os/linux/ld_spec.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,61 @@
44
require "tmpdir"
55

66
RSpec.describe OS::Linux::Ld do
7+
let(:diagnostics) do
8+
<<~EOS
9+
path.prefix="/usr"
10+
path.sysconfdir="/usr/local/etc"
11+
path.system_dirs[0x0]="/lib64"
12+
path.system_dirs[0x1]="/var/lib"
13+
EOS
14+
end
15+
16+
describe "::system_ld_so" do
17+
let(:ld_so) { "/lib/ld-linux.so.3" }
18+
19+
before do
20+
allow(File).to receive(:executable?).and_return(false)
21+
described_class.instance_variable_set(:@system_ld_so, nil)
22+
end
23+
24+
it "returns the path to a known dynamic linker" do
25+
allow(File).to receive(:executable?).with(ld_so).and_return(true)
26+
expect(described_class.system_ld_so).to eq(Pathname(ld_so))
27+
end
28+
29+
it "returns nil when there is no known dynamic linker" do
30+
expect(described_class.system_ld_so).to be_nil
31+
end
32+
end
33+
34+
describe "::sysconfdir" do
35+
it "returns path.sysconfdir" do
36+
allow(described_class).to receive(:ld_so_diagnostics).and_return(diagnostics)
37+
expect(described_class.sysconfdir).to eq("/usr/local/etc")
38+
expect(described_class.sysconfdir(brewed: false)).to eq("/usr/local/etc")
39+
end
40+
41+
it "returns fallback on blank diagnostics" do
42+
allow(described_class).to receive(:ld_so_diagnostics).and_return("")
43+
expect(described_class.sysconfdir).to eq("/etc")
44+
expect(described_class.sysconfdir(brewed: false)).to eq("/etc")
45+
end
46+
end
47+
48+
describe "::system_dirs" do
49+
it "returns all path.system_dirs" do
50+
allow(described_class).to receive(:ld_so_diagnostics).and_return(diagnostics)
51+
expect(described_class.system_dirs).to eq(["/lib64", "/var/lib"])
52+
expect(described_class.system_dirs(brewed: false)).to eq(["/lib64", "/var/lib"])
53+
end
54+
55+
it "returns an empty array on blank diagnostics" do
56+
allow(described_class).to receive(:ld_so_diagnostics).and_return("")
57+
expect(described_class.system_dirs).to eq([])
58+
expect(described_class.system_dirs(brewed: false)).to eq([])
59+
end
60+
end
61+
762
describe "::library_paths" do
863
ld_etc = Pathname("")
964
before do

0 commit comments

Comments
 (0)