mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Revert "Revert "os/linux/elf: avoid using ldd for listing dynamic dependencies""
This commit is contained in:
parent
88e283cf8f
commit
916b37388d
@ -1,6 +1,8 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "os/linux/ld"
|
||||
|
||||
# {Pathname} extension for dealing with ELF files.
|
||||
# @see https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
|
||||
#
|
||||
@ -130,19 +132,7 @@ module ELFShim
|
||||
@dylib_id, needed = needed_libraries path
|
||||
return if needed.empty?
|
||||
|
||||
ldd = DevelopmentTools.locate "ldd"
|
||||
ldd_output = Utils.popen_read(ldd, path.expand_path.to_s).split("\n")
|
||||
return unless $CHILD_STATUS.success?
|
||||
|
||||
ldd_paths = ldd_output.filter_map do |line|
|
||||
match = line.match(/\t.+ => (.+) \(.+\)|\t(.+) => not found/)
|
||||
next unless match
|
||||
|
||||
match.captures.compact.first
|
||||
end
|
||||
@dylibs = ldd_paths.select do |ldd_path|
|
||||
needed.include? File.basename(ldd_path)
|
||||
end
|
||||
@dylibs = needed.map { |lib| find_full_lib_path(lib).to_s }
|
||||
end
|
||||
|
||||
private
|
||||
@ -157,6 +147,52 @@ module ELFShim
|
||||
patcher = path.patchelf_patcher
|
||||
[patcher.soname, patcher.needed]
|
||||
end
|
||||
|
||||
def find_full_lib_path(basename)
|
||||
local_paths = (path.patchelf_patcher.runpath || path.patchelf_patcher.rpath)&.split(":")
|
||||
|
||||
# Search for dependencies in the runpath/rpath first
|
||||
local_paths&.each do |local_path|
|
||||
candidate = Pathname(local_path)/basename
|
||||
return candidate if candidate.exist? && candidate.elf?
|
||||
end
|
||||
|
||||
# Check if DF_1_NODEFLIB is set
|
||||
dt_flags_1 = path.patchelf_patcher.elf.segment_by_type(:dynamic)&.tag_by_type(:flags_1)
|
||||
nodeflib_flag = if dt_flags_1.nil?
|
||||
false
|
||||
else
|
||||
dt_flags_1.value & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
|
||||
end
|
||||
|
||||
linker_library_paths = OS::Linux::Ld.library_paths
|
||||
linker_system_dirs = OS::Linux::Ld.system_dirs
|
||||
|
||||
# If DF_1_NODEFLIB is set, exclude any library paths that are subdirectories
|
||||
# of the system dirs
|
||||
if nodeflib_flag
|
||||
linker_library_paths = linker_library_paths.reject do |lib_path|
|
||||
linker_system_dirs.any? { |system_dir| Utils::Path.child_of? system_dir, lib_path }
|
||||
end
|
||||
end
|
||||
|
||||
# If not found, search recursively in the paths listed in ld.so.conf (skipping
|
||||
# paths that are subdirectories of the system dirs if DF_1_NODEFLIB is set)
|
||||
linker_library_paths.each do |linker_library_path|
|
||||
candidate = Pathname(linker_library_path)/basename
|
||||
return candidate if candidate.exist? && candidate.elf?
|
||||
end
|
||||
|
||||
# If not found, search in the system dirs, unless DF_1_NODEFLIB is set
|
||||
unless nodeflib_flag
|
||||
linker_system_dirs.each do |linker_system_dir|
|
||||
candidate = Pathname(linker_system_dir)/basename
|
||||
return candidate if candidate.exist? && candidate.elf?
|
||||
end
|
||||
end
|
||||
|
||||
basename
|
||||
end
|
||||
end
|
||||
private_constant :Metadata
|
||||
|
||||
|
74
Library/Homebrew/os/linux/ld.rb
Normal file
74
Library/Homebrew/os/linux/ld.rb
Normal file
@ -0,0 +1,74 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module OS
|
||||
module Linux
|
||||
# Helper functions for querying `ld` information.
|
||||
#
|
||||
# @api private
|
||||
module Ld
|
||||
sig { returns(String) }
|
||||
def self.brewed_ld_so_diagnostics
|
||||
brewed_ld_so = HOMEBREW_PREFIX/"lib/ld.so"
|
||||
return "" unless brewed_ld_so.exist?
|
||||
|
||||
ld_so_output = Utils.popen_read(brewed_ld_so, "--list-diagnostics")
|
||||
return "" unless $CHILD_STATUS.success?
|
||||
|
||||
ld_so_output
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def self.sysconfdir
|
||||
fallback_sysconfdir = "/etc"
|
||||
|
||||
match = brewed_ld_so_diagnostics.match(/path.sysconfdir="(.+)"/)
|
||||
return fallback_sysconfdir unless match
|
||||
|
||||
match.captures.compact.first || fallback_sysconfdir
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.system_dirs
|
||||
dirs = []
|
||||
|
||||
brewed_ld_so_diagnostics.split("\n").each do |line|
|
||||
match = line.match(/path.system_dirs\[0x.*\]="(.*)"/)
|
||||
next unless match
|
||||
|
||||
dirs << match.captures.compact.first
|
||||
end
|
||||
|
||||
dirs
|
||||
end
|
||||
|
||||
sig { params(conf_path: T.any(Pathname, String)).returns(T::Array[String]) }
|
||||
def self.library_paths(conf_path = Pathname(sysconfdir)/"ld.so.conf")
|
||||
conf_file = Pathname(conf_path)
|
||||
paths = Set.new
|
||||
directory = conf_file.realpath.dirname
|
||||
|
||||
conf_file.readlines.each do |line|
|
||||
# Remove comments and leading/trailing whitespace
|
||||
line.strip!
|
||||
line.sub!(/\s*#.*$/, "")
|
||||
|
||||
if line.start_with?(/\s*include\s+/)
|
||||
include_path = Pathname(line.sub(/^\s*include\s+/, "")).expand_path
|
||||
wildcard = include_path.absolute? ? include_path : directory/include_path
|
||||
|
||||
Dir.glob(wildcard.to_s).each do |include_file|
|
||||
paths += library_paths(include_file)
|
||||
end
|
||||
elsif line.empty?
|
||||
next
|
||||
else
|
||||
paths << line
|
||||
end
|
||||
end
|
||||
|
||||
paths.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
Library/Homebrew/test/os/linux/ld_spec.rb
Normal file
47
Library/Homebrew/test/os/linux/ld_spec.rb
Normal file
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "os/linux/ld"
|
||||
require "tmpdir"
|
||||
|
||||
RSpec.describe OS::Linux::Ld do
|
||||
describe "::library_paths" do
|
||||
ld_etc = Pathname("")
|
||||
before do
|
||||
ld_etc = Pathname(Dir.mktmpdir("homebrew-tests-ld-etc-", Dir.tmpdir))
|
||||
FileUtils.mkdir [ld_etc/"subdir1", ld_etc/"subdir2"]
|
||||
(ld_etc/"ld.so.conf").write <<~EOS
|
||||
# This line is a comment
|
||||
|
||||
include #{ld_etc}/subdir1/*.conf # This is an end-of-line comment, should be ignored
|
||||
|
||||
# subdir2 is an empty directory
|
||||
include #{ld_etc}/subdir2/*.conf
|
||||
|
||||
/a/b/c
|
||||
/d/e/f # Indentation on this line should be ignored
|
||||
/a/b/c # Duplicate entry should be ignored
|
||||
EOS
|
||||
|
||||
(ld_etc/"subdir1/1-1.conf").write <<~EOS
|
||||
/foo/bar
|
||||
/baz/qux
|
||||
EOS
|
||||
|
||||
(ld_etc/"subdir1/1-2.conf").write <<~EOS
|
||||
/g/h/i
|
||||
EOS
|
||||
|
||||
# Empty files (or files containing only whitespace) should be ignored
|
||||
(ld_etc/"subdir1/1-3.conf").write "\n\t\n\t\n"
|
||||
(ld_etc/"subdir1/1-4.conf").write ""
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf ld_etc
|
||||
end
|
||||
|
||||
it "parses library paths successfully" do
|
||||
expect(described_class.library_paths(ld_etc/"ld.so.conf")).to eq(%w[/foo/bar /baz/qux /g/h/i /a/b/c /d/e/f])
|
||||
end
|
||||
end
|
||||
end
|
33
Library/Homebrew/test/utils/path_spec.rb
Normal file
33
Library/Homebrew/test/utils/path_spec.rb
Normal file
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "utils/path"
|
||||
|
||||
RSpec.describe Utils::Path do
|
||||
describe "::child_of?" do
|
||||
it "recognizes a path as its own child" do
|
||||
expect(described_class.child_of?("/foo/bar", "/foo/bar")).to be(true)
|
||||
end
|
||||
|
||||
it "recognizes a path that is a child of the parent" do
|
||||
expect(described_class.child_of?("/foo", "/foo/bar")).to be(true)
|
||||
end
|
||||
|
||||
it "recognizes a path that is a grandchild of the parent" do
|
||||
expect(described_class.child_of?("/foo", "/foo/bar/baz")).to be(true)
|
||||
end
|
||||
|
||||
it "does not recognize a path that is not a child" do
|
||||
expect(described_class.child_of?("/foo", "/bar/baz")).to be(false)
|
||||
end
|
||||
|
||||
it "handles . and .. in paths correctly" do
|
||||
expect(described_class.child_of?("/foo", "/foo/./bar")).to be(true)
|
||||
expect(described_class.child_of?("/foo/bar", "/foo/../foo/bar/baz")).to be(true)
|
||||
end
|
||||
|
||||
it "handles relative paths correctly" do
|
||||
expect(described_class.child_of?("foo", "./bar/baz")).to be(false)
|
||||
expect(described_class.child_of?("../foo", "./bar/baz/../../../foo/bar/baz")).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
14
Library/Homebrew/utils/path.rb
Normal file
14
Library/Homebrew/utils/path.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Utils
|
||||
module Path
|
||||
sig { params(parent: T.any(Pathname, String), child: T.any(Pathname, String)).returns(T::Boolean) }
|
||||
def self.child_of?(parent, child)
|
||||
parent_pathname = Pathname(parent).expand_path
|
||||
child_pathname = Pathname(child).expand_path
|
||||
child_pathname.ascend { |p| return true if p == parent_pathname }
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user