| 
									
										
										
										
											2024-06-02 15:14:25 +01:00
										 |  |  | # typed: strict | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-28 10:39:15 -05:00
										 |  |  | require "cache_store" | 
					
						
							| 
									
										
										
										
											2018-02-28 09:13:17 -08:00
										 |  |  | require "linkage_checker" | 
					
						
							| 
									
										
										
										
											2016-07-14 13:14:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  | module OS | 
					
						
							|  |  |  |   module Mac | 
					
						
							|  |  |  |     module FormulaCellarChecks | 
					
						
							|  |  |  |       extend T::Helpers | 
					
						
							| 
									
										
										
										
											2016-07-09 13:51:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       requires_ancestor { Homebrew::FormulaAuditor } | 
					
						
							|  |  |  |       requires_ancestor { ::FormulaCellarChecks } | 
					
						
							| 
									
										
										
										
											2016-07-09 13:51:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |       def check_shadowed_headers | 
					
						
							|  |  |  |         return if ["libtool", "subversion", "berkeley-db"].any? do |formula_name| | 
					
						
							|  |  |  |           formula.name.start_with?(formula_name) | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2016-07-09 13:51:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |         return if formula.name.match?(Version.formula_optionally_versioned_regex(:php)) | 
					
						
							|  |  |  |         return if formula.keg_only? || !formula.include.directory? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |         files  = relative_glob(formula.include, "**/*.h") | 
					
						
							|  |  |  |         files &= relative_glob("#{MacOS.sdk_path}/usr/include", "**/*.h") | 
					
						
							|  |  |  |         files.map! { |p| File.join(formula.include, p) } | 
					
						
							| 
									
										
										
										
											2016-07-09 13:51:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |         return if files.empty? | 
					
						
							| 
									
										
										
										
											2016-07-09 13:51:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |         <<~EOS | 
					
						
							|  |  |  |           Header files that shadow system header files were installed to "#{formula.include}" | 
					
						
							|  |  |  |           The offending files are: | 
					
						
							|  |  |  |             #{files * "\n  "} | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |       def check_openssl_links | 
					
						
							|  |  |  |         return unless formula.prefix.directory? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         keg = ::Keg.new(formula.prefix) | 
					
						
							|  |  |  |         system_openssl = keg.mach_o_files.select do |obj| | 
					
						
							|  |  |  |           dlls = obj.dynamically_linked_libraries | 
					
						
							|  |  |  |           dlls.any? { |dll| %r{/usr/lib/lib(crypto|ssl|tls)\..*dylib}.match? dll } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         return if system_openssl.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         <<~EOS | 
					
						
							|  |  |  |           object files were linked against system openssl | 
					
						
							|  |  |  |           These object files were linked against the deprecated system OpenSSL or | 
					
						
							|  |  |  |           the system's private LibreSSL. | 
					
						
							|  |  |  |           Adding `depends_on "openssl"` to the formula may help. | 
					
						
							|  |  |  |             #{system_openssl * "\n  "} | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2018-02-24 17:32:29 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { params(lib: Pathname).returns(T.nilable(String)) } | 
					
						
							|  |  |  |       def check_python_framework_links(lib) | 
					
						
							|  |  |  |         python_modules = Pathname.glob lib/"python*/site-packages/**/*.so" | 
					
						
							|  |  |  |         framework_links = python_modules.select do |obj| | 
					
						
							|  |  |  |           dlls = obj.dynamically_linked_libraries | 
					
						
							|  |  |  |           dlls.any? { |dll| dll.include?("Python.framework") } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         return if framework_links.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         <<~EOS | 
					
						
							|  |  |  |           python modules have explicit framework links | 
					
						
							|  |  |  |           These python extension modules were linked directly to a Python | 
					
						
							|  |  |  |           framework binary. They should be linked with -undefined dynamic_lookup | 
					
						
							|  |  |  |           instead of -lpython or -framework Python. | 
					
						
							|  |  |  |             #{framework_links * "\n  "} | 
					
						
							|  |  |  |         EOS | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2018-05-22 14:46:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { void } | 
					
						
							|  |  |  |       def check_linkage | 
					
						
							|  |  |  |         return unless formula.prefix.directory? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         keg = ::Keg.new(formula.prefix) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         CacheStoreDatabase.use(:linkage) do |db| | 
					
						
							|  |  |  |           checker = ::LinkageChecker.new(keg, formula, cache_db: db) | 
					
						
							|  |  |  |           next unless checker.broken_library_linkage? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           output = <<~EOS | 
					
						
							|  |  |  |             #{formula} has broken dynamic library links: | 
					
						
							|  |  |  |               #{checker.display_test_output} | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           tab = keg.tab | 
					
						
							|  |  |  |           if tab.poured_from_bottle | 
					
						
							|  |  |  |             output += <<~EOS | 
					
						
							|  |  |  |               Rebuild this from source with: | 
					
						
							|  |  |  |                 brew reinstall --build-from-source #{formula} | 
					
						
							|  |  |  |               If that's successful, file an issue#{formula.tap ? " here:\n  #{formula.tap.issues_url}" : "."} | 
					
						
							|  |  |  |             EOS | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           problem_if_output output | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2018-04-09 14:19:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { params(formula: ::Formula).returns(T.nilable(String)) } | 
					
						
							|  |  |  |       def check_flat_namespace(formula) | 
					
						
							|  |  |  |         return unless formula.prefix.directory? | 
					
						
							|  |  |  |         return if formula.tap&.audit_exception(:flat_namespace_allowlist, formula.name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         keg = ::Keg.new(formula.prefix) | 
					
						
							|  |  |  |         flat_namespace_files = keg.mach_o_files.reject do |file| | 
					
						
							|  |  |  |           next true unless file.dylib? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           macho = MachO.open(file) | 
					
						
							|  |  |  |           if MachO::Utils.fat_magic?(macho.magic) | 
					
						
							|  |  |  |             macho.machos.map(&:header).all? { |h| h.flag? :MH_TWOLEVEL } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             macho.header.flag? :MH_TWOLEVEL | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         return if flat_namespace_files.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         <<~EOS | 
					
						
							|  |  |  |           Libraries were compiled with a flat namespace. | 
					
						
							|  |  |  |           This can cause linker errors due to name collisions and | 
					
						
							|  |  |  |           is often due to a bug in detecting the macOS version. | 
					
						
							|  |  |  |             #{flat_namespace_files * "\n  "} | 
					
						
							| 
									
										
										
										
											2018-04-09 14:19:07 -04:00
										 |  |  |         EOS | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-07-14 13:14:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { void } | 
					
						
							|  |  |  |       def audit_installed | 
					
						
							|  |  |  |         super | 
					
						
							|  |  |  |         problem_if_output(check_shadowed_headers) | 
					
						
							|  |  |  |         problem_if_output(check_openssl_links) | 
					
						
							|  |  |  |         problem_if_output(check_python_framework_links(formula.lib)) | 
					
						
							|  |  |  |         check_linkage | 
					
						
							|  |  |  |         problem_if_output(check_flat_namespace(formula)) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2021-10-12 13:11:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       MACOS_LIB_EXTENSIONS = %w[.dylib .framework].freeze | 
					
						
							| 
									
										
										
										
											2021-10-12 13:11:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  |       sig { params(filename: Pathname).returns(T::Boolean) } | 
					
						
							|  |  |  |       def valid_library_extension?(filename) | 
					
						
							|  |  |  |         super || MACOS_LIB_EXTENSIONS.include?(filename.extname) | 
					
						
							| 
									
										
										
										
											2021-10-12 13:49:53 +08:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2021-10-12 13:11:23 +08:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-08-22 21:25:00 -05:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2016-07-09 13:51:53 +01:00
										 |  |  | end | 
					
						
							| 
									
										
										
										
											2025-06-13 16:59:10 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | FormulaCellarChecks.prepend(OS::Mac::FormulaCellarChecks) |