Skip to content

Commit 88d171e

Browse files
authored
Only use MachOShim/ELFShim when required
Not including this globally in `Pathname` speeds up the creation of other `Pathname` objects by ~200x making all of Homebrew faster.
1 parent d6b691d commit 88d171e

File tree

27 files changed

+292
-208
lines changed

27 files changed

+292
-208
lines changed

Library/Homebrew/build.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def install
198198
(formula.logs/"00.options.out").write \
199199
"#{formula.full_name} #{formula.build.used_options.sort.join(" ")}".strip
200200

201-
Pathname.prepend WriteMkpathExtension
201+
Pathname.activate_extensions!
202202
formula.install
203203

204204
stdlibs = detect_stdlibs
Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4-
class Cleaner
5-
private
4+
module OS
5+
module Linux
6+
module Cleaner
7+
private
68

7-
sig { params(path: Pathname).returns(T::Boolean) }
8-
def executable_path?(path)
9-
path.elf? || path.text_executable?
9+
sig { params(path: Pathname).returns(T::Boolean) }
10+
def executable_path?(path)
11+
return true if path.text_executable?
12+
13+
::OSPathname.wrap(path).elf?
14+
end
15+
end
1016
end
1117
end
18+
19+
Cleaner.prepend(OS::Linux::Cleaner)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def check_gcc_dependent_linkage
208208
# There are other checks that test that, we can skip broken kegs.
209209
next if dependent_prefix.nil? || !dependent_prefix.exist? || !dependent_prefix.directory?
210210

211-
keg = Keg.new(dependent_prefix)
211+
keg = ::Keg.new(dependent_prefix)
212212
keg.binary_executable_or_library_files.any? do |binary|
213213
paths = binary.rpaths
214214
versioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/\d+$}) }

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33

44
require "os/linux/elf"
55

6-
class Pathname
7-
prepend ELFShim
6+
module OS
7+
module Linux
8+
module OSPathname
9+
module ClassMethods
10+
extend T::Helpers
11+
12+
requires_ancestor { T.class_of(::OSPathname) }
13+
14+
sig { returns(T::Array[T::Module[T.anything]]) }
15+
def self.os_extension_modules
16+
[ELFShim, *::OSPathname.os_extension_modules]
17+
end
18+
end
19+
end
20+
end
821
end
22+
23+
OSPathname.prepend(ELFShim)
24+
OSPathname.singleton_class.prepend(OS::Linux::OSPathname::ClassMethods)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# typed: strict
22

3-
class Pathname
3+
class OSPathname
44
include ELFShim
55
end
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4-
class Keg
5-
sig { returns(T::Array[Pathname]) }
6-
def binary_executable_or_library_files
7-
elf_files
4+
module OS
5+
module Linux
6+
module Keg
7+
sig { returns(T::Array[::OSPathname]) }
8+
def binary_executable_or_library_files = elf_files
9+
end
810
end
911
end
12+
13+
Keg.prepend(OS::Linux::Keg)

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

Lines changed: 93 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,97 +3,109 @@
33

44
require "compilers"
55

6-
class Keg
7-
sig { params(relocation: Relocation, skip_protodesc_cold: T::Boolean).void }
8-
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
9-
# Patching the dynamic linker of glibc breaks it.
10-
return if name.match? Version.formula_optionally_versioned_regex(:glibc)
6+
module OS
7+
module Linux
8+
module Keg
9+
extend T::Helpers
1110

12-
old_prefix, new_prefix = relocation.replacement_pair_for(:prefix)
11+
requires_ancestor { ::Keg }
1312

14-
elf_files.each do |file|
15-
file.ensure_writable do
16-
change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold:)
17-
end
18-
end
19-
end
13+
sig { params(relocation: ::Keg::Relocation, skip_protodesc_cold: T::Boolean).void }
14+
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
15+
# Patching the dynamic linker of glibc breaks it.
16+
return if name.match? Version.formula_optionally_versioned_regex(:glibc)
17+
18+
old_prefix, new_prefix = relocation.replacement_pair_for(:prefix)
2019

21-
sig {
22-
params(file: Pathname, old_prefix: T.any(String, Regexp), new_prefix: String,
23-
skip_protodesc_cold: T::Boolean).returns(T::Boolean)
24-
}
25-
def change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold: false)
26-
return false if !file.elf? || !file.dynamic_elf?
27-
28-
# Skip relocation of files with `protodesc_cold` sections because patchelf.rb seems to break them,
29-
# but only when bottling (as we don't want to break existing bottles that require relocation).
30-
# https://github.com/Homebrew/homebrew-core/pull/232490#issuecomment-3161362452
31-
return false if skip_protodesc_cold && file.section_names.include?("protodesc_cold")
32-
33-
updated = {}
34-
old_rpath = file.rpath
35-
new_rpath = if old_rpath
36-
rpath = old_rpath.split(":")
37-
.map { |x| x.sub(old_prefix, new_prefix) }
38-
.select { |x| x.start_with?(new_prefix, "$ORIGIN") }
39-
40-
lib_path = "#{new_prefix}/lib"
41-
rpath << lib_path unless rpath.include? lib_path
42-
43-
# Add GCC's lib directory (as of GCC 12+) to RPATH when there is existing versioned linkage.
44-
# This prevents broken linkage when pouring bottles built with an old GCC formula.
45-
unless name.match?(Version.formula_optionally_versioned_regex(:gcc))
46-
rpath.map! { |rp| rp.sub(%r{lib/gcc/\d+$}, "lib/gcc/current") }
20+
elf_files.each do |file|
21+
file.ensure_writable do
22+
change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold:)
23+
end
24+
end
4725
end
4826

49-
rpath.join(":")
50-
end
51-
updated[:rpath] = new_rpath if old_rpath != new_rpath
52-
53-
old_interpreter = file.interpreter
54-
new_interpreter = if old_interpreter.nil?
55-
nil
56-
elsif File.readable? "#{new_prefix}/lib/ld.so"
57-
"#{new_prefix}/lib/ld.so"
58-
else
59-
old_interpreter.sub old_prefix, new_prefix
60-
end
61-
updated[:interpreter] = new_interpreter if old_interpreter != new_interpreter
27+
sig {
28+
params(file: ::OSPathname, old_prefix: T.any(String, Regexp), new_prefix: String,
29+
skip_protodesc_cold: T::Boolean).returns(T::Boolean)
30+
}
31+
def change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold: false)
32+
return false if !file.elf? || !file.dynamic_elf?
33+
34+
# Skip relocation of files with `protodesc_cold` sections because patchelf.rb seems to break them,
35+
# but only when bottling (as we don't want to break existing bottles that require relocation).
36+
# https://github.com/Homebrew/homebrew-core/pull/232490#issuecomment-3161362452
37+
return false if skip_protodesc_cold && file.section_names.include?("protodesc_cold")
38+
39+
updated = {}
40+
old_rpath = file.rpath
41+
new_rpath = if old_rpath
42+
rpath = old_rpath.split(":")
43+
.map { |x| x.sub(old_prefix, new_prefix) }
44+
.select { |x| x.start_with?(new_prefix, "$ORIGIN") }
45+
46+
lib_path = "#{new_prefix}/lib"
47+
rpath << lib_path unless rpath.include? lib_path
48+
49+
# Add GCC's lib directory (as of GCC 12+) to RPATH when there is existing versioned linkage.
50+
# This prevents broken linkage when pouring bottles built with an old GCC formula.
51+
unless name.match?(Version.formula_optionally_versioned_regex(:gcc))
52+
rpath.map! { |rp| rp.sub(%r{lib/gcc/\d+$}, "lib/gcc/current") }
53+
end
54+
55+
rpath.join(":")
56+
end
57+
updated[:rpath] = new_rpath if old_rpath != new_rpath
58+
59+
old_interpreter = file.interpreter
60+
new_interpreter = if old_interpreter.nil?
61+
nil
62+
elsif File.readable? "#{new_prefix}/lib/ld.so"
63+
"#{new_prefix}/lib/ld.so"
64+
else
65+
old_interpreter.sub old_prefix, new_prefix
66+
end
67+
updated[:interpreter] = new_interpreter if old_interpreter != new_interpreter
68+
69+
file.patch!(interpreter: updated[:interpreter], rpath: updated[:rpath])
70+
true
71+
end
6272

63-
file.patch!(interpreter: updated[:interpreter], rpath: updated[:rpath])
64-
true
65-
end
73+
sig { params(options: T::Hash[Symbol, T::Boolean]).returns(T::Array[Symbol]) }
74+
def detect_cxx_stdlibs(options = {})
75+
skip_executables = options.fetch(:skip_executables, false)
76+
results = Set.new
77+
elf_files.each do |file|
78+
next unless file.dynamic_elf?
79+
next if file.binary_executable? && skip_executables
80+
81+
dylibs = file.dynamically_linked_libraries
82+
results << :libcxx if dylibs.any? { |s| s.include? "libc++.so" }
83+
results << :libstdcxx if dylibs.any? { |s| s.include? "libstdc++.so" }
84+
end
85+
results.to_a
86+
end
6687

67-
sig { params(options: T::Hash[Symbol, T::Boolean]).returns(T::Array[Symbol]) }
68-
def detect_cxx_stdlibs(options = {})
69-
skip_executables = options.fetch(:skip_executables, false)
70-
results = Set.new
71-
elf_files.each do |file|
72-
next unless file.dynamic_elf?
73-
next if file.binary_executable? && skip_executables
74-
75-
dylibs = file.dynamically_linked_libraries
76-
results << :libcxx if dylibs.any? { |s| s.include? "libc++.so" }
77-
results << :libstdcxx if dylibs.any? { |s| s.include? "libstdc++.so" }
78-
end
79-
results.to_a
80-
end
88+
sig { returns(T::Array[::OSPathname]) }
89+
def elf_files
90+
hardlinks = Set.new
91+
elf_files = []
92+
path.find do |pn|
93+
next if pn.symlink? || pn.directory?
8194

82-
sig { returns(T::Array[Pathname]) }
83-
def elf_files
84-
hardlinks = Set.new
85-
elf_files = []
86-
path.find do |pn|
87-
next if pn.symlink? || pn.directory?
88-
next if !pn.dylib? && !pn.binary_executable?
95+
pn = ::OSPathname.wrap(pn)
96+
next if !pn.dylib? && !pn.binary_executable?
8997

90-
# If we've already processed a file, ignore its hardlinks (which have the
91-
# same dev ID and inode). This prevents relocations from being performed
92-
# on a binary more than once.
93-
next unless hardlinks.add? [pn.stat.dev, pn.stat.ino]
98+
# If we've already processed a file, ignore its hardlinks (which have the
99+
# same dev ID and inode). This prevents relocations from being performed
100+
# on a binary more than once.
101+
next unless hardlinks.add? [pn.stat.dev, pn.stat.ino]
94102

95-
elf_files << pn
103+
elf_files << pn
104+
end
105+
elf_files
106+
end
96107
end
97-
elf_files
98108
end
99109
end
110+
111+
Keg.prepend(OS::Linux::Keg)

Library/Homebrew/extend/os/mac/cleaner.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ module Cleaner
88

99
sig { params(path: Pathname).returns(T::Boolean) }
1010
def executable_path?(path)
11-
path.mach_o_executable? || path.text_executable?
11+
return true if path.text_executable?
12+
13+
::OSPathname.wrap(path).mach_o_executable?
1214
end
1315
end
1416
end

Library/Homebrew/extend/os/mac/extend/pathname.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33

44
require "os/mac/mach"
55

6-
class Pathname
7-
prepend MachOShim
6+
module OS
7+
module Mac
8+
module OSPathname
9+
module ClassMethods
10+
extend T::Helpers
11+
12+
requires_ancestor { T.class_of(::OSPathname) }
13+
14+
sig { returns(T::Array[T::Module[T.anything]]) }
15+
def self.os_extension_modules
16+
[MachOShim, *::OSPathname.os_extension_modules]
17+
end
18+
end
19+
end
20+
end
821
end
22+
23+
OSPathname.prepend(MachOShim)
24+
OSPathname.singleton_class.prepend(OS::Mac::OSPathname::ClassMethods)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# typed: strict
22

3-
class Pathname
3+
class OSPathname
44
include MachOShim
55
end

0 commit comments

Comments
 (0)