| 
									
										
										
										
											2024-08-12 10:30:59 +01:00
										 |  |  | # typed: true # rubocop:todo Sorbet/StrictSigil | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-13 15:24:18 +01:00
										 |  |  | require "macho" | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:28:26 +02:00
										 |  |  | # {Pathname} extension for dealing with Mach-O files. | 
					
						
							| 
									
										
										
										
											2016-11-08 16:16:34 -05:00
										 |  |  | module MachOShim | 
					
						
							| 
									
										
										
										
											2019-03-10 21:05:51 -04:00
										 |  |  |   extend Forwardable | 
					
						
							| 
									
										
										
										
											2024-10-06 09:25:57 -07:00
										 |  |  |   extend T::Helpers | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   requires_ancestor { Pathname } | 
					
						
							| 
									
										
										
										
											2019-03-10 21:05:51 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 16:42:13 -03:00
										 |  |  |   delegate [:dylib_id] => :macho | 
					
						
							| 
									
										
										
										
											2019-03-10 21:05:51 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |   def macho | 
					
						
							| 
									
										
										
										
											2021-03-26 14:11:03 +00:00
										 |  |  |     @macho ||= MachO.open(to_s) | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-08-25 00:28:26 +02:00
										 |  |  |   private :macho | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def mach_data | 
					
						
							|  |  |  |     @mach_data ||= begin | 
					
						
							|  |  |  |       machos = [] | 
					
						
							|  |  |  |       mach_data = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-07 13:37:23 -04:00
										 |  |  |       if MachO::Utils.fat_magic?(macho.magic) | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |         machos = macho.machos | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         machos << macho | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       machos.each do |m| | 
					
						
							|  |  |  |         arch = case m.cputype | 
					
						
							| 
									
										
										
										
											2021-07-18 10:48:03 +08:00
										 |  |  |         when :x86_64, :i386, :ppc64, :arm64, :arm then m.cputype | 
					
						
							| 
									
										
										
										
											2016-06-17 20:41:08 -04:00
										 |  |  |         when :ppc then :ppc7400 | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |         else :dunno | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         type = case m.filetype | 
					
						
							| 
									
										
										
										
											2016-08-07 13:37:23 -04:00
										 |  |  |         when :dylib, :bundle then m.filetype | 
					
						
							|  |  |  |         when :execute then :executable | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |         else :dunno | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |         mach_data << { arch:, type: } | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       mach_data | 
					
						
							| 
									
										
										
										
											2016-02-15 17:56:47 +01:00
										 |  |  |     rescue MachO::NotAMachOError | 
					
						
							|  |  |  |       # Silently ignore errors that indicate the file is not a Mach-O binary ... | 
					
						
							|  |  |  |       [] | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |     rescue | 
					
						
							| 
									
										
										
										
											2016-02-15 17:56:47 +01:00
										 |  |  |       # ... but complain about other (parse) errors for further investigation. | 
					
						
							| 
									
										
										
										
											2018-06-06 11:22:21 -04:00
										 |  |  |       onoe "Failed to read Mach-O binary: #{self}" | 
					
						
							| 
									
										
										
										
											2020-04-05 15:44:50 +01:00
										 |  |  |       raise if Homebrew::EnvConfig.developer? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |       [] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-08-25 00:28:26 +02:00
										 |  |  |   private :mach_data | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-19 00:34:39 +08:00
										 |  |  |   # TODO: See if the `#write!` call can be delayed until | 
					
						
							|  |  |  |   # we know we're not making any changes to the rpaths. | 
					
						
							|  |  |  |   def delete_rpath(rpath, **options) | 
					
						
							| 
									
										
										
										
											2023-08-14 14:29:05 +08:00
										 |  |  |     candidates = rpaths(resolve_variable_references: false).select do |r| | 
					
						
							|  |  |  |       resolve_variable_name(r) == resolve_variable_name(rpath) | 
					
						
							| 
									
										
										
										
											2023-07-23 00:22:05 +08:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-26 11:15:19 +08:00
										 |  |  |     # Delete the last instance to avoid changing the order in which rpaths are searched. | 
					
						
							| 
									
										
										
										
											2023-08-14 14:29:05 +08:00
										 |  |  |     rpath_to_delete = candidates.last | 
					
						
							| 
									
										
										
										
											2023-07-26 11:15:19 +08:00
										 |  |  |     options[:last] = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     macho.delete_rpath(rpath_to_delete, options) | 
					
						
							| 
									
										
										
										
											2022-01-19 00:34:39 +08:00
										 |  |  |     macho.write! | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-11 23:05:02 +08:00
										 |  |  |   def change_rpath(old, new, **options) | 
					
						
							|  |  |  |     macho.change_rpath(old, new, options) | 
					
						
							|  |  |  |     macho.write! | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def change_dylib_id(id, **options) | 
					
						
							|  |  |  |     macho.change_dylib_id(id, options) | 
					
						
							|  |  |  |     macho.write! | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def change_install_name(old, new, **options) | 
					
						
							|  |  |  |     macho.change_install_name(old, new, options) | 
					
						
							|  |  |  |     macho.write! | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 16:42:13 -03:00
										 |  |  |   def dynamically_linked_libraries(except: :none, resolve_variable_references: true) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:56:50 +01:00
										 |  |  |     lcs = macho.dylib_load_commands | 
					
						
							|  |  |  |     lcs.reject! { |lc| lc.flag?(except) } if except != :none | 
					
						
							| 
									
										
										
										
											2023-12-14 02:52:30 +00:00
										 |  |  |     names = lcs.map { |lc| lc.name.to_s }.uniq | 
					
						
							| 
									
										
										
										
											2024-04-08 09:47:06 -07:00
										 |  |  |     names.map! { resolve_variable_name(_1) } if resolve_variable_references | 
					
						
							| 
									
										
										
										
											2023-06-20 16:42:13 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     names | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def rpaths(resolve_variable_references: true) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:11:12 +08:00
										 |  |  |     names = macho.rpaths | 
					
						
							| 
									
										
										
										
											2023-08-05 23:40:22 +08:00
										 |  |  |     # Don't recursively resolve rpaths to avoid infinite loops. | 
					
						
							|  |  |  |     names.map! { |name| resolve_variable_name(name, resolve_rpaths: false) } if resolve_variable_references | 
					
						
							| 
									
										
										
										
											2023-06-20 16:42:13 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     names | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 23:40:22 +08:00
										 |  |  |   def resolve_variable_name(name, resolve_rpaths: true) | 
					
						
							| 
									
										
										
										
											2023-06-20 16:42:13 -03:00
										 |  |  |     if name.start_with? "@loader_path" | 
					
						
							|  |  |  |       Pathname(name.sub("@loader_path", dirname)).cleanpath.to_s | 
					
						
							|  |  |  |     elsif name.start_with?("@executable_path") && binary_executable? | 
					
						
							|  |  |  |       Pathname(name.sub("@executable_path", dirname)).cleanpath.to_s | 
					
						
							| 
									
										
										
										
											2023-08-05 23:40:22 +08:00
										 |  |  |     elsif resolve_rpaths && name.start_with?("@rpath") && (target = resolve_rpath(name)).present? | 
					
						
							| 
									
										
										
										
											2023-07-27 11:53:46 +08:00
										 |  |  |       target | 
					
						
							| 
									
										
										
										
											2023-06-20 16:42:13 -03:00
										 |  |  |     else | 
					
						
							|  |  |  |       name | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-27 11:53:46 +08:00
										 |  |  |   def resolve_rpath(name) | 
					
						
							|  |  |  |     target = T.let(nil, T.nilable(String)) | 
					
						
							|  |  |  |     return unless rpaths(resolve_variable_references: true).find do |rpath| | 
					
						
							|  |  |  |       File.exist?(target = File.join(rpath, name.delete_prefix("@rpath"))) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     target | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-20 17:37:08 -04:00
										 |  |  |   def archs | 
					
						
							| 
									
										
										
										
											2021-06-17 11:34:31 +01:00
										 |  |  |     mach_data.map { |m| m.fetch :arch } | 
					
						
							| 
									
										
										
										
											2016-09-20 17:37:08 -04:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def arch | 
					
						
							|  |  |  |     case archs.length | 
					
						
							|  |  |  |     when 0 then :dunno | 
					
						
							|  |  |  |     when 1 then archs.first | 
					
						
							|  |  |  |     else :universal | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def universal? | 
					
						
							|  |  |  |     arch == :universal | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def i386? | 
					
						
							|  |  |  |     arch == :i386 | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def x86_64? | 
					
						
							|  |  |  |     arch == :x86_64 | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def ppc7400? | 
					
						
							|  |  |  |     arch == :ppc7400 | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def ppc64? | 
					
						
							|  |  |  |     arch == :ppc64 | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def dylib? | 
					
						
							|  |  |  |     mach_data.any? { |m| m.fetch(:type) == :dylib } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def mach_o_executable? | 
					
						
							|  |  |  |     mach_data.any? { |m| m.fetch(:type) == :executable } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 16:43:00 -08:00
										 |  |  |   alias binary_executable? mach_o_executable? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-20 17:37:08 -04:00
										 |  |  |   def mach_o_bundle? | 
					
						
							|  |  |  |     mach_data.any? { |m| m.fetch(:type) == :bundle } | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2016-02-01 14:19:29 -05:00
										 |  |  | end |