Move remaining OS extensions to prepend

This commit is contained in:
Douglas Eichelberger 2024-09-21 12:24:21 -07:00
parent 7c4f2c19fe
commit eed660e784
28 changed files with 1603 additions and 1526 deletions

View File

@ -177,7 +177,6 @@ class DevelopmentTools
"cpu_family" => Hardware::CPU.family.to_s, "cpu_family" => Hardware::CPU.family.to_s,
} }
end end
alias generic_build_system_info build_system_info
end end
end end

View File

@ -0,0 +1,5 @@
# typed: strict
# module OS::Linux::Cleanup
# include Kernel
# end

View File

@ -1,101 +1,103 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "os/linux/glibc" require "os/linux/glibc"
class DependencyCollector module OS
undef gcc_dep_if_needed module Linux
undef glibc_dep_if_needed module DependencyCollector
undef init_global_dep_tree_if_needed! sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def gcc_dep_if_needed(related_formula_names)
# gcc is required for libgcc_s.so.1 if glibc or gcc are too old
return unless ::DevelopmentTools.needs_build_formulae?
return if building_global_dep_tree?
return if related_formula_names.include?(GCC)
return if global_dep_tree[GCC]&.intersect?(related_formula_names)
return unless formula_for(GCC)
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) } Dependency.new(GCC, [:implicit])
def gcc_dep_if_needed(related_formula_names) end
# gcc is required for libgcc_s.so.1 if glibc or gcc are too old
return unless DevelopmentTools.needs_build_formulae?
return if building_global_dep_tree?
return if related_formula_names.include?(GCC)
return if global_dep_tree[GCC]&.intersect?(related_formula_names)
return unless formula_for(GCC)
Dependency.new(GCC, [:implicit]) sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
end def glibc_dep_if_needed(related_formula_names)
return unless ::DevelopmentTools.needs_libc_formula?
return if building_global_dep_tree?
return if related_formula_names.include?(GLIBC)
return if global_dep_tree[GLIBC]&.intersect?(related_formula_names)
return unless formula_for(GLIBC)
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) } Dependency.new(GLIBC, [:implicit])
def glibc_dep_if_needed(related_formula_names) end
return unless DevelopmentTools.needs_libc_formula?
return if building_global_dep_tree?
return if related_formula_names.include?(GLIBC)
return if global_dep_tree[GLIBC]&.intersect?(related_formula_names)
return unless formula_for(GLIBC)
Dependency.new(GLIBC, [:implicit]) private
end
private GLIBC = "glibc"
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA
GLIBC = "glibc" sig { void }
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA def init_global_dep_tree_if_needed!
return unless ::DevelopmentTools.needs_build_formulae?
return if building_global_dep_tree?
return unless global_dep_tree.empty?
sig { void } building_global_dep_tree!
def init_global_dep_tree_if_needed! global_dep_tree[GLIBC] = Set.new(global_deps_for(GLIBC))
return unless DevelopmentTools.needs_build_formulae? # gcc depends on glibc
return if building_global_dep_tree? global_dep_tree[GCC] = Set.new([*global_deps_for(GCC), GLIBC, *@@global_dep_tree[GLIBC]])
return unless global_dep_tree.empty? built_global_dep_tree!
end
building_global_dep_tree! sig { params(name: String).returns(T.nilable(Formula)) }
global_dep_tree[GLIBC] = Set.new(global_deps_for(GLIBC)) def formula_for(name)
# gcc depends on glibc @formula_for ||= T.let({}, T.nilable(T::Hash[String, Formula]))
global_dep_tree[GCC] = Set.new([*global_deps_for(GCC), GLIBC, *@@global_dep_tree[GLIBC]]) @formula_for[name] ||= ::Formula[name]
built_global_dep_tree! rescue FormulaUnavailableError
end nil
end
sig { params(name: String).returns(T.nilable(Formula)) } sig { params(name: String).returns(T::Array[String]) }
def formula_for(name) def global_deps_for(name)
@formula_for ||= {} @global_deps_for ||= T.let({}, T.nilable(T::Hash[String, T::Array[String]]))
@formula_for[name] ||= Formula[name] # Always strip out glibc and gcc from all parts of dependency tree when
rescue FormulaUnavailableError # we're calculating their dependency trees. Other parts of Homebrew will
nil # catch any circular dependencies.
end @global_deps_for[name] ||= if (formula = formula_for(name))
formula.deps.map(&:name).flat_map do |dep|
[dep, *global_deps_for(dep)].compact
end.uniq
else
[]
end
end
sig { params(name: String).returns(T::Array[String]) } # Use class variables to avoid this expensive logic needing to be done more
def global_deps_for(name) # than once.
@global_deps_for ||= {} # rubocop:disable Style/ClassVars
# Always strip out glibc and gcc from all parts of dependency tree when @@global_dep_tree = T.let({}, T::Hash[String, T::Set[String]])
# we're calculating their dependency trees. Other parts of Homebrew will @@building_global_dep_tree = T.let(false, T::Boolean)
# catch any circular dependencies.
@global_deps_for[name] ||= if (formula = formula_for(name)) sig { returns(T::Hash[String, T::Set[String]]) }
formula.deps.map(&:name).flat_map do |dep| def global_dep_tree
[dep, *global_deps_for(dep)].compact @@global_dep_tree
end.uniq end
else
[] sig { void }
def building_global_dep_tree!
@@building_global_dep_tree = true
end
sig { void }
def built_global_dep_tree!
@@building_global_dep_tree = false
end
sig { returns(T::Boolean) }
def building_global_dep_tree?
@@building_global_dep_tree.present?
end
# rubocop:enable Style/ClassVars
end end
end end
# Use class variables to avoid this expensive logic needing to be done more
# than once.
# rubocop:disable Style/ClassVars
@@global_dep_tree = {}
@@building_global_dep_tree = false
sig { returns(T::Hash[String, T::Set[String]]) }
def global_dep_tree
@@global_dep_tree
end
sig { void }
def building_global_dep_tree!
@@building_global_dep_tree = true
end
sig { void }
def built_global_dep_tree!
@@building_global_dep_tree = false
end
sig { returns(T::Boolean) }
def building_global_dep_tree?
@@building_global_dep_tree.present?
end
# rubocop:enable Style/ClassVars
end end
DependencyCollector.prepend(OS::Linux::DependencyCollector)

View File

@ -1,54 +1,64 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
class DevelopmentTools module OS
class << self module Linux
sig { params(tool: T.any(String, Symbol)).returns(T.nilable(Pathname)) } module DevelopmentTools
def locate(tool) extend T::Helpers
(@locate ||= {}).fetch(tool) do |key|
@locate[key] = if needs_build_formulae? && requires_ancestor { ::DevelopmentTools }
(binutils_path = HOMEBREW_PREFIX/"opt/binutils/bin/#{tool}").executable?
binutils_path sig { params(tool: T.any(String, Symbol)).returns(T.nilable(Pathname)) }
elsif needs_build_formulae? && (glibc_path = HOMEBREW_PREFIX/"opt/glibc/bin/#{tool}").executable? def locate(tool)
glibc_path @locate ||= T.let({}, T.nilable(T::Hash[T.any(String, Symbol), Pathname]))
elsif (homebrew_path = HOMEBREW_PREFIX/"bin/#{tool}").executable? @locate.fetch(tool) do |key|
homebrew_path @locate[key] = if ::DevelopmentTools.needs_build_formulae? &&
elsif File.executable?((system_path = "/usr/bin/#{tool}")) (binutils_path = HOMEBREW_PREFIX/"opt/binutils/bin/#{tool}").executable?
Pathname.new system_path binutils_path
elsif ::DevelopmentTools.needs_build_formulae? &&
(glibc_path = HOMEBREW_PREFIX/"opt/glibc/bin/#{tool}").executable?
glibc_path
elsif (homebrew_path = HOMEBREW_PREFIX/"bin/#{tool}").executable?
homebrew_path
elsif File.executable?((system_path = "/usr/bin/#{tool}"))
Pathname.new system_path
end
end end
end end
end
sig { returns(Symbol) } sig { returns(Symbol) }
def default_compiler def default_compiler = :gcc
:gcc
end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def needs_libc_formula? def needs_libc_formula?
return @needs_libc_formula if defined? @needs_libc_formula return @needs_libc_formula unless @needs_libc_formula.nil?
@needs_libc_formula = OS::Linux::Glibc.below_ci_version? @needs_libc_formula = T.let(OS::Linux::Glibc.below_ci_version?, T.nilable(T::Boolean))
end @needs_libc_formula = !!@needs_libc_formula
sig { returns(T::Boolean) }
def needs_compiler_formula?
return @needs_compiler_formula if defined? @needs_compiler_formula
gcc = "/usr/bin/gcc"
@needs_compiler_formula = if File.exist?(gcc)
gcc_version(gcc) < OS::LINUX_GCC_CI_VERSION
else
true
end end
end
sig { returns(T::Hash[String, T.nilable(String)]) } sig { returns(T::Boolean) }
def build_system_info def needs_compiler_formula?
generic_build_system_info.merge({ return @needs_compiler_formula unless @needs_compiler_formula.nil?
"glibc_version" => OS::Linux::Glibc.version.to_s.presence,
"oldest_cpu_family" => Hardware.oldest_cpu.to_s, gcc = "/usr/bin/gcc"
}) @needs_compiler_formula = T.let(if File.exist?(gcc)
::DevelopmentTools.gcc_version(gcc) < OS::LINUX_GCC_CI_VERSION
else
true
end, T.nilable(T::Boolean))
!!@needs_compiler_formula
end
sig { returns(T::Hash[String, T.nilable(String)]) }
def build_system_info
super.merge({
"glibc_version" => OS::Linux::Glibc.version.to_s.presence,
"oldest_cpu_family" => Hardware.oldest_cpu.to_s,
})
end
end end
end end
end end
DevelopmentTools.singleton_class.prepend(OS::Linux::DevelopmentTools)

View File

@ -7,168 +7,174 @@ require "hardware"
require "os/linux/glibc" require "os/linux/glibc"
require "os/linux/kernel" require "os/linux/kernel"
module Homebrew module OS
module Diagnostic module Linux
class Checks module Diagnostic
undef fatal_preinstall_checks, supported_configuration_checks module Checks
extend T::Helpers
def fatal_preinstall_checks requires_ancestor { Homebrew::Diagnostic::Checks }
%w[
check_access_directories
check_linuxbrew_core
check_linuxbrew_bottle_domain
].freeze
end
def supported_configuration_checks def fatal_preinstall_checks
%w[ %w[
check_glibc_minimum_version check_access_directories
check_kernel_minimum_version check_linuxbrew_core
check_supported_architecture check_linuxbrew_bottle_domain
].freeze ].freeze
end
def check_tmpdir_sticky_bit
message = generic_check_tmpdir_sticky_bit
return if message.nil?
message + <<~EOS
If you don't have administrative privileges on this machine,
create a directory and set the HOMEBREW_TEMP environment variable,
for example:
install -d -m 1755 ~/tmp
#{Utils::Shell.set_variable_in_profile("HOMEBREW_TEMP", "~/tmp")}
EOS
end
def check_tmpdir_executable
f = Tempfile.new(%w[homebrew_check_tmpdir_executable .sh], HOMEBREW_TEMP)
f.write "#!/bin/sh\n"
f.chmod 0700
f.close
return if system T.must(f.path)
<<~EOS
The directory #{HOMEBREW_TEMP} does not permit executing
programs. It is likely mounted as "noexec". Please set HOMEBREW_TEMP
in your #{Utils::Shell.profile} to a different directory, for example:
export HOMEBREW_TEMP=~/tmp
echo 'export HOMEBREW_TEMP=~/tmp' >> #{Utils::Shell.profile}
EOS
ensure
f&.unlink
end
def check_xdg_data_dirs
xdg_data_dirs = ENV.fetch("HOMEBREW_XDG_DATA_DIRS", nil)
return if xdg_data_dirs.blank?
return if xdg_data_dirs.split(":").include?("#{HOMEBREW_PREFIX}/share")
<<~EOS
Homebrew's share was not found in your XDG_DATA_DIRS but you have
this variable set to include other locations.
Some programs like `vapigen` may not work correctly.
Consider adding Homebrew's share directory to XDG_DATA_DIRS like so:
echo 'export XDG_DATA_DIRS="#{HOMEBREW_PREFIX}/share:$XDG_DATA_DIRS"' >> #{Utils::Shell.profile}
EOS
end
def check_umask_not_zero
return unless File.umask.zero?
<<~EOS
umask is currently set to 000. Directories created by Homebrew cannot
be world-writable. This issue can be resolved by adding "umask 002" to
your #{Utils::Shell.profile}:
echo 'umask 002' >> #{Utils::Shell.profile}
EOS
end
def check_supported_architecture
return if Hardware::CPU.arch == :x86_64
<<~EOS
Your CPU architecture (#{Hardware::CPU.arch}) is not supported. We only support
x86_64 CPU architectures. You will be unable to use binary packages (bottles).
#{please_create_pull_requests}
EOS
end
def check_glibc_minimum_version
return unless OS::Linux::Glibc.below_minimum_version?
<<~EOS
Your system glibc #{OS::Linux::Glibc.system_version} is too old.
We only support glibc #{OS::Linux::Glibc.minimum_version} or later.
#{please_create_pull_requests}
We recommend updating to a newer version via your distribution's
package manager, upgrading your distribution to the latest version,
or changing distributions.
EOS
end
def check_kernel_minimum_version
return unless OS::Linux::Kernel.below_minimum_version?
<<~EOS
Your Linux kernel #{OS.kernel_version} is too old.
We only support kernel #{OS::Linux::Kernel.minimum_version} or later.
You will be unable to use binary packages (bottles).
#{please_create_pull_requests}
We recommend updating to a newer version via your distribution's
package manager, upgrading your distribution to the latest version,
or changing distributions.
EOS
end
def check_linuxbrew_core
return unless Homebrew::EnvConfig.no_install_from_api?
return unless CoreTap.instance.linuxbrew_core?
<<~EOS
Your Linux core repository is still linuxbrew-core.
You must `brew update` to update to homebrew-core.
EOS
end
def check_linuxbrew_bottle_domain
return unless Homebrew::EnvConfig.bottle_domain.include?("linuxbrew")
<<~EOS
Your HOMEBREW_BOTTLE_DOMAIN still contains "linuxbrew".
You must unset it (or adjust it to not contain linuxbrew
e.g. by using homebrew instead).
EOS
end
def check_gcc_dependent_linkage
gcc_dependents = Formula.installed.select do |formula|
next false unless formula.tap&.core_tap?
# FIXME: This includes formulae that have no runtime dependency on GCC.
formula.recursive_dependencies.map(&:name).include? "gcc"
rescue TapFormulaUnavailableError
false
end end
return if gcc_dependents.empty?
badly_linked = gcc_dependents.select do |dependent| def supported_configuration_checks
keg = Keg.new(dependent.prefix) %w[
keg.binary_executable_or_library_files.any? do |binary| check_glibc_minimum_version
paths = binary.rpaths check_kernel_minimum_version
versioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/\d+$}) } check_supported_architecture
unversioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/current$}) } ].freeze
end
versioned_linkage && !unversioned_linkage def check_tmpdir_sticky_bit
message = generic_check_tmpdir_sticky_bit
return if message.nil?
message + <<~EOS
If you don't have administrative privileges on this machine,
create a directory and set the HOMEBREW_TEMP environment variable,
for example:
install -d -m 1755 ~/tmp
#{Utils::Shell.set_variable_in_profile("HOMEBREW_TEMP", "~/tmp")}
EOS
end
def check_tmpdir_executable
f = Tempfile.new(%w[homebrew_check_tmpdir_executable .sh], HOMEBREW_TEMP)
f.write "#!/bin/sh\n"
f.chmod 0700
f.close
return if system T.must(f.path)
<<~EOS
The directory #{HOMEBREW_TEMP} does not permit executing
programs. It is likely mounted as "noexec". Please set HOMEBREW_TEMP
in your #{Utils::Shell.profile} to a different directory, for example:
export HOMEBREW_TEMP=~/tmp
echo 'export HOMEBREW_TEMP=~/tmp' >> #{Utils::Shell.profile}
EOS
ensure
f&.unlink
end
def check_xdg_data_dirs
xdg_data_dirs = ENV.fetch("HOMEBREW_XDG_DATA_DIRS", nil)
return if xdg_data_dirs.blank?
return if xdg_data_dirs.split(":").include?("#{HOMEBREW_PREFIX}/share")
<<~EOS
Homebrew's share was not found in your XDG_DATA_DIRS but you have
this variable set to include other locations.
Some programs like `vapigen` may not work correctly.
Consider adding Homebrew's share directory to XDG_DATA_DIRS like so:
echo 'export XDG_DATA_DIRS="#{HOMEBREW_PREFIX}/share:$XDG_DATA_DIRS"' >> #{Utils::Shell.profile}
EOS
end
def check_umask_not_zero
return unless File.umask.zero?
<<~EOS
umask is currently set to 000. Directories created by Homebrew cannot
be world-writable. This issue can be resolved by adding "umask 002" to
your #{Utils::Shell.profile}:
echo 'umask 002' >> #{Utils::Shell.profile}
EOS
end
def check_supported_architecture
return if Hardware::CPU.arch == :x86_64
<<~EOS
Your CPU architecture (#{Hardware::CPU.arch}) is not supported. We only support
x86_64 CPU architectures. You will be unable to use binary packages (bottles).
#{please_create_pull_requests}
EOS
end
def check_glibc_minimum_version
return unless OS::Linux::Glibc.below_minimum_version?
<<~EOS
Your system glibc #{OS::Linux::Glibc.system_version} is too old.
We only support glibc #{OS::Linux::Glibc.minimum_version} or later.
#{please_create_pull_requests}
We recommend updating to a newer version via your distribution's
package manager, upgrading your distribution to the latest version,
or changing distributions.
EOS
end
def check_kernel_minimum_version
return unless OS::Linux::Kernel.below_minimum_version?
<<~EOS
Your Linux kernel #{OS.kernel_version} is too old.
We only support kernel #{OS::Linux::Kernel.minimum_version} or later.
You will be unable to use binary packages (bottles).
#{please_create_pull_requests}
We recommend updating to a newer version via your distribution's
package manager, upgrading your distribution to the latest version,
or changing distributions.
EOS
end
def check_linuxbrew_core
return unless Homebrew::EnvConfig.no_install_from_api?
return unless CoreTap.instance.linuxbrew_core?
<<~EOS
Your Linux core repository is still linuxbrew-core.
You must `brew update` to update to homebrew-core.
EOS
end
def check_linuxbrew_bottle_domain
return unless Homebrew::EnvConfig.bottle_domain.include?("linuxbrew")
<<~EOS
Your HOMEBREW_BOTTLE_DOMAIN still contains "linuxbrew".
You must unset it (or adjust it to not contain linuxbrew
e.g. by using homebrew instead).
EOS
end
def check_gcc_dependent_linkage
gcc_dependents = ::Formula.installed.select do |formula|
next false unless formula.tap&.core_tap?
# FIXME: This includes formulae that have no runtime dependency on GCC.
formula.recursive_dependencies.map(&:name).include? "gcc"
rescue TapFormulaUnavailableError
false
end end
end return if gcc_dependents.empty?
return if badly_linked.empty?
inject_file_list badly_linked, <<~EOS badly_linked = gcc_dependents.select do |dependent|
Formulae which link to GCC through a versioned path were found. These formulae keg = Keg.new(dependent.prefix)
are prone to breaking when GCC is updated. You should `brew reinstall` these formulae: keg.binary_executable_or_library_files.any? do |binary|
EOS paths = binary.rpaths
versioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/\d+$}) }
unversioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/current$}) }
versioned_linkage && !unversioned_linkage
end
end
return if badly_linked.empty?
inject_file_list badly_linked, <<~EOS
Formulae which link to GCC through a versioned path were found. These formulae
are prone to breaking when GCC is updated. You should `brew reinstall` these formulae:
EOS
end
end end
end end
end end
end end
Homebrew::Diagnostic::Checks.prepend(OS::Linux::Diagnostic::Checks)

View File

@ -28,7 +28,7 @@ module OS
sig { params(spec: SoftwareSpec).void } sig { params(spec: SoftwareSpec).void }
def add_global_deps_to_spec(spec) def add_global_deps_to_spec(spec)
return unless DevelopmentTools.needs_build_formulae? return unless ::DevelopmentTools.needs_build_formulae?
@global_deps ||= begin @global_deps ||= begin
dependency_collector = spec.dependency_collector dependency_collector = spec.dependency_collector

View File

@ -1,15 +1,12 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Homebrew module OS
class SimulateSystem module Linux
class << self module SimulateSystem
undef os
undef simulating_or_running_on_linux?
undef current_os
sig { returns(T.nilable(Symbol)) } sig { returns(T.nilable(Symbol)) }
def os def os
@os ||= T.let(nil, T.nilable(Symbol))
return :macos if @os.blank? && Homebrew::EnvConfig.simulate_macos_on_linux? return :macos if @os.blank? && Homebrew::EnvConfig.simulate_macos_on_linux?
@os @os
@ -27,3 +24,5 @@ module Homebrew
end end
end end
end end
Homebrew::SimulateSystem.singleton_class.prepend(OS::Linux::SimulateSystem)

View File

@ -8,7 +8,7 @@ module OS
def use_system_ruby? def use_system_ruby?
return false if Homebrew::EnvConfig.force_vendor_ruby? return false if Homebrew::EnvConfig.force_vendor_ruby?
Homebrew::EnvConfig.developer? && ENV["HOMEBREW_USE_RUBY_FROM_PATH"].present? ::Homebrew::EnvConfig.developer? && ENV["HOMEBREW_USE_RUBY_FROM_PATH"].present?
end end
end end
end end

View File

@ -1,23 +1,26 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
class DependencyCollector module OS
undef git_dep_if_needed, subversion_dep_if_needed, cvs_dep_if_needed, module Mac
xz_dep_if_needed, unzip_dep_if_needed, bzip2_dep_if_needed module DependencyCollector
def git_dep_if_needed(tags); end
def git_dep_if_needed(tags); end def subversion_dep_if_needed(tags)
Dependency.new("subversion", [*tags, :implicit])
end
def subversion_dep_if_needed(tags) def cvs_dep_if_needed(tags)
Dependency.new("subversion", [*tags, :implicit]) Dependency.new("cvs", [*tags, :implicit])
end
def xz_dep_if_needed(tags); end
def unzip_dep_if_needed(tags); end
def bzip2_dep_if_needed(tags); end
end
end end
def cvs_dep_if_needed(tags)
Dependency.new("cvs", [*tags, :implicit])
end
def xz_dep_if_needed(tags); end
def unzip_dep_if_needed(tags); end
def bzip2_dep_if_needed(tags); end
end end
DependencyCollector.prepend(OS::Mac::DependencyCollector)

View File

@ -1,86 +1,91 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "os/mac/xcode" require "os/mac/xcode"
class DevelopmentTools module OS
class << self module Mac
alias generic_locate locate module DevelopmentTools
undef installed?, default_compiler, curl_handles_most_https_certificates?, extend T::Helpers
subversion_handles_most_https_certificates?
sig { params(tool: T.any(String, Symbol)).returns(T.nilable(Pathname)) } requires_ancestor { ::DevelopmentTools }
def locate(tool)
(@locate ||= {}).fetch(tool) do |key| sig { params(tool: T.any(String, Symbol)).returns(T.nilable(Pathname)) }
@locate[key] = if (located_tool = generic_locate(tool)) def locate(tool)
located_tool @locate ||= T.let({}, T.nilable(T::Hash[T.any(String, Symbol), Pathname]))
else @locate.fetch(tool) do |key|
path = Utils.popen_read("/usr/bin/xcrun", "-no-cache", "-find", tool, err: :close).chomp @locate[key] = if (located_tool = super(tool))
Pathname.new(path) if File.executable?(path) located_tool
else
path = Utils.popen_read("/usr/bin/xcrun", "-no-cache", "-find", tool, err: :close).chomp
Pathname.new(path) if File.executable?(path)
end
end end
end end
end
# Checks if the user has any developer tools installed, either via Xcode # Checks if the user has any developer tools installed, either via Xcode
# or the CLT. Convenient for guarding against formula builds when building # or the CLT. Convenient for guarding against formula builds when building
# is impossible. # is impossible.
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def installed? def installed?
MacOS::Xcode.installed? || MacOS::CLT.installed? MacOS::Xcode.installed? || MacOS::CLT.installed?
end
sig { returns(Symbol) }
def default_compiler
:clang
end
sig { returns(Version) }
def ld64_version
@ld64_version ||= begin
json = Utils.popen_read("/usr/bin/ld", "-version_details")
if $CHILD_STATUS.success?
Version.parse(JSON.parse(json)["version"])
else
Version::NULL
end
end end
end
sig { returns(T::Boolean) } sig { returns(Symbol) }
def curl_handles_most_https_certificates? def default_compiler
# The system Curl is too old for some modern HTTPS certificates on :clang
# older macOS versions. end
ENV["HOMEBREW_SYSTEM_CURL_TOO_OLD"].nil?
end
sig { returns(T::Boolean) } sig { returns(Version) }
def subversion_handles_most_https_certificates? def self.ld64_version
# The system Subversion is too old for some HTTPS certificates on @ld64_version ||= T.let(begin
# older macOS versions. json = Utils.popen_read("/usr/bin/ld", "-version_details")
MacOS.version >= :sierra if $CHILD_STATUS.success?
end Version.parse(JSON.parse(json)["version"])
else
Version::NULL
end
end, T.nilable(Version))
end
sig { returns(String) } sig { returns(T::Boolean) }
def installation_instructions def curl_handles_most_https_certificates?
MacOS::CLT.installation_instructions # The system Curl is too old for some modern HTTPS certificates on
end # older macOS versions.
ENV["HOMEBREW_SYSTEM_CURL_TOO_OLD"].nil?
end
sig { returns(String) } sig { returns(T::Boolean) }
def custom_installation_instructions def subversion_handles_most_https_certificates?
<<~EOS # The system Subversion is too old for some HTTPS certificates on
Install GNU's GCC: # older macOS versions.
brew install gcc MacOS.version >= :sierra
EOS end
end
sig { returns(T::Hash[String, T.nilable(String)]) } sig { returns(String) }
def build_system_info def installation_instructions
build_info = { MacOS::CLT.installation_instructions
"xcode" => MacOS::Xcode.version.to_s.presence, end
"clt" => MacOS::CLT.version.to_s.presence,
"preferred_perl" => MacOS.preferred_perl_version, sig { returns(String) }
} def custom_installation_instructions
generic_build_system_info.merge build_info <<~EOS
Install GNU's GCC:
brew install gcc
EOS
end
sig { returns(T::Hash[String, T.nilable(String)]) }
def build_system_info
build_info = {
"xcode" => MacOS::Xcode.version.to_s.presence,
"clt" => MacOS::CLT.version.to_s.presence,
"preferred_perl" => MacOS.preferred_perl_version,
}
super.merge build_info
end
end end
end end
end end
DevelopmentTools.singleton_class.prepend(OS::Mac::DevelopmentTools)

View File

@ -1,467 +1,471 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Homebrew module OS
module Diagnostic module Mac
class Volumes module Diagnostic
def initialize class Volumes
@volumes = get_mounts def initialize
end @volumes = get_mounts
end
def which(path) def which(path)
vols = get_mounts path vols = get_mounts path
# no volume found # no volume found
return -1 if vols.empty? return -1 if vols.empty?
vol_index = @volumes.index(vols[0]) vol_index = @volumes.index(vols[0])
# volume not found in volume list # volume not found in volume list
return -1 if vol_index.nil? return -1 if vol_index.nil?
vol_index vol_index
end end
def get_mounts(path = nil) def get_mounts(path = nil)
vols = [] vols = []
# get the volume of path, if path is nil returns all volumes # get the volume of path, if path is nil returns all volumes
args = %w[/bin/df -P] args = %w[/bin/df -P]
args << path if path args << path if path
Utils.popen_read(*args) do |io| Utils.popen_read(*args) do |io|
io.each_line do |line| io.each_line do |line|
case line.chomp case line.chomp
# regex matches: /dev/disk0s2 489562928 440803616 48247312 91% / # regex matches: /dev/disk0s2 489562928 440803616 48247312 91% /
when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/ when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/
vols << Regexp.last_match(1) vols << Regexp.last_match(1)
end
end end
end end
vols
end end
vols
end
end
class Checks
undef fatal_preinstall_checks, fatal_build_from_source_checks,
fatal_setup_build_environment_checks, supported_configuration_checks,
build_from_source_checks
def fatal_preinstall_checks
checks = %w[
check_access_directories
]
# We need the developer tools for `codesign`.
checks << "check_for_installed_developer_tools" if Hardware::CPU.arm?
checks.freeze
end end
def fatal_build_from_source_checks module Checks
%w[ extend T::Helpers
check_xcode_license_approved
check_xcode_minimum_version
check_clt_minimum_version
check_if_xcode_needs_clt_installed
check_if_supported_sdk_available
check_broken_sdks
].freeze
end
def fatal_setup_build_environment_checks requires_ancestor { Homebrew::Diagnostic::Checks }
%w[
check_xcode_minimum_version
check_clt_minimum_version
check_if_supported_sdk_available
].freeze
end
def supported_configuration_checks def fatal_preinstall_checks
%w[ checks = %w[
check_for_unsupported_macos check_access_directories
].freeze ]
end
def build_from_source_checks # We need the developer tools for `codesign`.
%w[ checks << "check_for_installed_developer_tools" if ::Hardware::CPU.arm?
check_for_installed_developer_tools
check_xcode_up_to_date
check_clt_up_to_date
].freeze
end
def check_for_non_prefixed_findutils checks.freeze
findutils = Formula["findutils"]
return unless findutils.any_version_installed?
gnubin = %W[#{findutils.opt_libexec}/gnubin #{findutils.libexec}/gnubin]
default_names = Tab.for_name("findutils").with? "default-names"
return if !default_names && !paths.intersect?(gnubin)
<<~EOS
Putting non-prefixed findutils in your path can cause python builds to fail.
EOS
rescue FormulaUnavailableError
nil
end
def check_for_unsupported_macos
return if Homebrew::EnvConfig.developer?
return if ENV["HOMEBREW_INTEGRATION_TEST"]
who = +"We"
what = if OS::Mac.version.prerelease?
"pre-release version"
elsif OS::Mac.version.outdated_release?
who << " (and Apple)"
"old version"
end end
return if what.blank?
who.freeze def fatal_build_from_source_checks
%w[
check_xcode_license_approved
check_xcode_minimum_version
check_clt_minimum_version
check_if_xcode_needs_clt_installed
check_if_supported_sdk_available
check_broken_sdks
].freeze
end
<<~EOS def fatal_setup_build_environment_checks
You are using macOS #{MacOS.version}. %w[
#{who} do not provide support for this #{what}. check_xcode_minimum_version
#{please_create_pull_requests(what)} check_clt_minimum_version
EOS check_if_supported_sdk_available
end ].freeze
end
def check_xcode_up_to_date def supported_configuration_checks
return unless MacOS::Xcode.outdated? %w[
check_for_unsupported_macos
].freeze
end
# avoid duplicate very similar messages def build_from_source_checks
return if MacOS::Xcode.below_minimum_version? %w[
check_for_installed_developer_tools
check_xcode_up_to_date
check_clt_up_to_date
].freeze
end
# CI images are going to end up outdated so don't complain when def check_for_non_prefixed_findutils
# `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew findutils = ::Formula["findutils"]
# repository. This only needs to support whatever CI providers return unless findutils.any_version_installed?
# Homebrew/brew is currently using.
return if GitHub::Actions.env_set?
# With fake El Capitan for Portable Ruby, we are intentionally not using Xcode 8. gnubin = %W[#{findutils.opt_libexec}/gnubin #{findutils.libexec}/gnubin]
# This is because we are not using the CLT and Xcode 8 has the 10.12 SDK. default_names = Tab.for_name("findutils").with? "default-names"
return if ENV["HOMEBREW_FAKE_MACOS"] return if !default_names && !paths.intersect?(gnubin)
message = <<~EOS <<~EOS
Your Xcode (#{MacOS::Xcode.version}) is outdated. Putting non-prefixed findutils in your path can cause python builds to fail.
Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it). EOS
#{MacOS::Xcode.update_instructions} rescue FormulaUnavailableError
EOS nil
end
if OS::Mac.version.prerelease? def check_for_unsupported_macos
current_path = Utils.popen_read("/usr/bin/xcode-select", "-p") return if Homebrew::EnvConfig.developer?
message += <<~EOS return if ENV["HOMEBREW_INTEGRATION_TEST"]
If #{MacOS::Xcode.latest_version} is installed, you may need to:
sudo xcode-select --switch /Applications/Xcode.app who = +"We"
Current developer directory is: what = if OS::Mac.version.prerelease?
#{current_path} "pre-release version"
elsif OS::Mac.version.outdated_release?
who << " (and Apple)"
"old version"
end
return if what.blank?
who.freeze
<<~EOS
You are using macOS #{MacOS.version}.
#{who} do not provide support for this #{what}.
#{please_create_pull_requests(what)}
EOS EOS
end end
message
end
def check_clt_up_to_date def check_xcode_up_to_date
return unless MacOS::CLT.outdated? return unless MacOS::Xcode.outdated?
# avoid duplicate very similar messages # avoid duplicate very similar messages
return if MacOS::CLT.below_minimum_version? return if MacOS::Xcode.below_minimum_version?
# CI images are going to end up outdated so don't complain when # CI images are going to end up outdated so don't complain when
# `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew # `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew
# repository. This only needs to support whatever CI providers # repository. This only needs to support whatever CI providers
# Homebrew/brew is currently using. # Homebrew/brew is currently using.
return if GitHub::Actions.env_set? return if GitHub::Actions.env_set?
<<~EOS # With fake El Capitan for Portable Ruby, we are intentionally not using Xcode 8.
A newer Command Line Tools release is available. # This is because we are not using the CLT and Xcode 8 has the 10.12 SDK.
#{MacOS::CLT.update_instructions} return if ENV["HOMEBREW_FAKE_MACOS"]
EOS
end
def check_xcode_minimum_version message = <<~EOS
return unless MacOS::Xcode.below_minimum_version? Your Xcode (#{MacOS::Xcode.version}) is outdated.
Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it).
#{MacOS::Xcode.update_instructions}
EOS
xcode = MacOS::Xcode.version.to_s if OS::Mac.version.prerelease?
xcode += " => #{MacOS::Xcode.prefix}" unless MacOS::Xcode.default_prefix? current_path = Utils.popen_read("/usr/bin/xcode-select", "-p")
message += <<~EOS
<<~EOS If #{MacOS::Xcode.latest_version} is installed, you may need to:
Your Xcode (#{xcode}) at #{MacOS::Xcode.bundle_path} is too outdated. sudo xcode-select --switch /Applications/Xcode.app
Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it). Current developer directory is:
#{MacOS::Xcode.update_instructions} #{current_path}
EOS
end
def check_clt_minimum_version
return unless MacOS::CLT.below_minimum_version?
<<~EOS
Your Command Line Tools are too outdated.
#{MacOS::CLT.update_instructions}
EOS
end
def check_if_xcode_needs_clt_installed
return unless MacOS::Xcode.needs_clt_installed?
<<~EOS
Xcode alone is not sufficient on #{MacOS.version.pretty_name}.
#{DevelopmentTools.installation_instructions}
EOS
end
def check_xcode_prefix
prefix = MacOS::Xcode.prefix
return if prefix.nil?
return unless prefix.to_s.include?(" ")
<<~EOS
Xcode is installed to a directory with a space in the name.
This will cause some formulae to fail to build.
EOS
end
def check_xcode_prefix_exists
prefix = MacOS::Xcode.prefix
return if prefix.nil? || prefix.exist?
<<~EOS
The directory Xcode is reportedly installed to doesn't exist:
#{prefix}
You may need to `xcode-select` the proper path if you have moved Xcode.
EOS
end
def check_xcode_select_path
return if MacOS::CLT.installed?
return unless MacOS::Xcode.installed?
return if File.file?("#{MacOS.active_developer_dir}/usr/bin/xcodebuild")
path = MacOS::Xcode.bundle_path
path = "/Developer" if path.nil? || !path.directory?
<<~EOS
Your Xcode is configured with an invalid path.
You should change it to the correct path:
sudo xcode-select --switch #{path}
EOS
end
def check_xcode_license_approved
# If the user installs Xcode-only, they have to approve the
# license or no "xc*" tool will work.
return unless `/usr/bin/xcrun clang 2>&1`.include?("license")
return if $CHILD_STATUS.success?
<<~EOS
You have not agreed to the Xcode license.
Agree to the license by opening Xcode.app or running:
sudo xcodebuild -license
EOS
end
def check_filesystem_case_sensitive
dirs_to_check = [
HOMEBREW_PREFIX,
HOMEBREW_REPOSITORY,
HOMEBREW_CELLAR,
HOMEBREW_TEMP,
]
case_sensitive_dirs = dirs_to_check.select do |dir|
# We select the dir as being case-sensitive if either the UPCASED or the
# downcased variant is missing.
# Of course, on a case-insensitive fs, both exist because the os reports so.
# In the rare situation when the user has indeed a downcased and an upcased
# dir (e.g. /TMP and /tmp) this check falsely thinks it is case-insensitive
# but we don't care because: 1. there is more than one dir checked, 2. the
# check is not vital and 3. we would have to touch files otherwise.
upcased = Pathname.new(dir.to_s.upcase)
downcased = Pathname.new(dir.to_s.downcase)
dir.exist? && !(upcased.exist? && downcased.exist?)
end
return if case_sensitive_dirs.empty?
volumes = Volumes.new
case_sensitive_vols = case_sensitive_dirs.map do |case_sensitive_dir|
volumes.get_mounts(case_sensitive_dir)
end
case_sensitive_vols.uniq!
<<~EOS
The filesystem on #{case_sensitive_vols.join(",")} appears to be case-sensitive.
The default macOS filesystem is case-insensitive. Please report any apparent problems.
EOS
end
def check_for_gettext
find_relative_paths("lib/libgettextlib.dylib",
"lib/libintl.dylib",
"include/libintl.h")
return if @found.empty?
# Our gettext formula will be caught by check_linked_keg_only_brews
gettext = begin
Formulary.factory("gettext")
rescue
nil
end
if gettext&.linked_keg&.directory?
allowlist = ["#{HOMEBREW_CELLAR}/gettext"]
if Hardware::CPU.physical_cpu_arm64?
allowlist += %W[
#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}/Cellar/gettext
#{HOMEBREW_DEFAULT_PREFIX}/Cellar/gettext
]
end
return if @found.all? do |path|
realpath = Pathname.new(path).realpath.to_s
allowlist.any? { |rack| realpath.start_with?(rack) }
end
end
inject_file_list @found, <<~EOS
gettext files detected at a system prefix.
These files can cause compilation and link failures, especially if they
are compiled with improper architectures. Consider removing these files:
EOS
end
def check_for_iconv
find_relative_paths("lib/libiconv.dylib", "include/iconv.h")
return if @found.empty?
libiconv = begin
Formulary.factory("libiconv")
rescue
nil
end
if libiconv&.linked_keg&.directory?
unless libiconv&.keg_only?
<<~EOS
A libiconv formula is installed and linked.
This will break stuff. For serious. Unlink it.
EOS EOS
end end
else message
inject_file_list @found, <<~EOS end
libiconv files detected at a system prefix other than /usr.
Homebrew doesn't provide a libiconv formula and expects to link against
the system version in /usr. libiconv in other prefixes can cause
compile or link failure, especially if compiled with improper
architectures. macOS itself never installs anything to /usr/local so
it was either installed by a user or some other third party software.
tl;dr: delete these files: def check_clt_up_to_date
return unless MacOS::CLT.outdated?
# avoid duplicate very similar messages
return if MacOS::CLT.below_minimum_version?
# CI images are going to end up outdated so don't complain when
# `brew test-bot` runs `brew doctor` in the CI for the Homebrew/brew
# repository. This only needs to support whatever CI providers
# Homebrew/brew is currently using.
return if GitHub::Actions.env_set?
<<~EOS
A newer Command Line Tools release is available.
#{MacOS::CLT.update_instructions}
EOS EOS
end end
end
def check_for_multiple_volumes def check_xcode_minimum_version
return unless HOMEBREW_CELLAR.exist? return unless MacOS::Xcode.below_minimum_version?
volumes = Volumes.new xcode = MacOS::Xcode.version.to_s
xcode += " => #{MacOS::Xcode.prefix}" unless MacOS::Xcode.default_prefix?
# Find the volumes for the TMP folder & HOMEBREW_CELLAR <<~EOS
real_cellar = HOMEBREW_CELLAR.realpath Your Xcode (#{xcode}) at #{MacOS::Xcode.bundle_path} is too outdated.
where_cellar = volumes.which real_cellar Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it).
#{MacOS::Xcode.update_instructions}
EOS
end
begin def check_clt_minimum_version
tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP)) return unless MacOS::CLT.below_minimum_version?
begin
real_tmp = tmp.realpath.parent <<~EOS
where_tmp = volumes.which real_tmp Your Command Line Tools are too outdated.
ensure #{MacOS::CLT.update_instructions}
Dir.delete tmp.to_s EOS
end
def check_if_xcode_needs_clt_installed
return unless MacOS::Xcode.needs_clt_installed?
<<~EOS
Xcode alone is not sufficient on #{MacOS.version.pretty_name}.
#{::DevelopmentTools.installation_instructions}
EOS
end
def check_xcode_prefix
prefix = MacOS::Xcode.prefix
return if prefix.nil?
return unless prefix.to_s.include?(" ")
<<~EOS
Xcode is installed to a directory with a space in the name.
This will cause some formulae to fail to build.
EOS
end
def check_xcode_prefix_exists
prefix = MacOS::Xcode.prefix
return if prefix.nil? || prefix.exist?
<<~EOS
The directory Xcode is reportedly installed to doesn't exist:
#{prefix}
You may need to `xcode-select` the proper path if you have moved Xcode.
EOS
end
def check_xcode_select_path
return if MacOS::CLT.installed?
return unless MacOS::Xcode.installed?
return if File.file?("#{MacOS.active_developer_dir}/usr/bin/xcodebuild")
path = MacOS::Xcode.bundle_path
path = "/Developer" if path.nil? || !path.directory?
<<~EOS
Your Xcode is configured with an invalid path.
You should change it to the correct path:
sudo xcode-select --switch #{path}
EOS
end
def check_xcode_license_approved
# If the user installs Xcode-only, they have to approve the
# license or no "xc*" tool will work.
return unless `/usr/bin/xcrun clang 2>&1`.include?("license")
return if $CHILD_STATUS.success?
<<~EOS
You have not agreed to the Xcode license.
Agree to the license by opening Xcode.app or running:
sudo xcodebuild -license
EOS
end
def check_filesystem_case_sensitive
dirs_to_check = [
HOMEBREW_PREFIX,
HOMEBREW_REPOSITORY,
HOMEBREW_CELLAR,
HOMEBREW_TEMP,
]
case_sensitive_dirs = dirs_to_check.select do |dir|
# We select the dir as being case-sensitive if either the UPCASED or the
# downcased variant is missing.
# Of course, on a case-insensitive fs, both exist because the os reports so.
# In the rare situation when the user has indeed a downcased and an upcased
# dir (e.g. /TMP and /tmp) this check falsely thinks it is case-insensitive
# but we don't care because: 1. there is more than one dir checked, 2. the
# check is not vital and 3. we would have to touch files otherwise.
upcased = Pathname.new(dir.to_s.upcase)
downcased = Pathname.new(dir.to_s.downcase)
dir.exist? && !(upcased.exist? && downcased.exist?)
end end
rescue return if case_sensitive_dirs.empty?
return
volumes = Volumes.new
case_sensitive_vols = case_sensitive_dirs.map do |case_sensitive_dir|
volumes.get_mounts(case_sensitive_dir)
end
case_sensitive_vols.uniq!
<<~EOS
The filesystem on #{case_sensitive_vols.join(",")} appears to be case-sensitive.
The default macOS filesystem is case-insensitive. Please report any apparent problems.
EOS
end end
return if where_cellar == where_tmp def check_for_gettext
find_relative_paths("lib/libgettextlib.dylib",
"lib/libintl.dylib",
"include/libintl.h")
return if @found.empty?
<<~EOS # Our gettext formula will be caught by check_linked_keg_only_brews
Your Cellar and TEMP directories are on different volumes. gettext = begin
macOS won't move relative symlinks across volumes unless the target file already Formulary.factory("gettext")
exists. Brews known to be affected by this are Git and Narwhal. rescue
nil
end
You should set the "HOMEBREW_TEMP" environment variable to a suitable if gettext&.linked_keg&.directory?
directory on the same volume as your Cellar. allowlist = ["#{HOMEBREW_CELLAR}/gettext"]
EOS if ::Hardware::CPU.physical_cpu_arm64?
end allowlist += %W[
#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}/Cellar/gettext
#{HOMEBREW_DEFAULT_PREFIX}/Cellar/gettext
]
end
def check_deprecated_caskroom_taps return if @found.all? do |path|
tapped_caskroom_taps = Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" } realpath = Pathname.new(path).realpath.to_s
.map(&:name) allowlist.any? { |rack| realpath.start_with?(rack) }
return if tapped_caskroom_taps.empty? end
end
<<~EOS inject_file_list @found, <<~EOS
You have the following deprecated, cask taps tapped: gettext files detected at a system prefix.
#{tapped_caskroom_taps.join("\n ")} These files can cause compilation and link failures, especially if they
Untap them with `brew untap`. are compiled with improper architectures. Consider removing these files:
EOS EOS
end
def check_if_supported_sdk_available
return unless DevelopmentTools.installed?
return unless MacOS.sdk_root_needed?
return if MacOS.sdk
locator = MacOS.sdk_locator
source = if locator.source == :clt
return if MacOS::CLT.below_minimum_version? # Handled by other diagnostics.
update_instructions = MacOS::CLT.update_instructions
"Command Line Tools (CLT)"
else
return if MacOS::Xcode.below_minimum_version? # Handled by other diagnostics.
update_instructions = MacOS::Xcode.update_instructions
"Xcode"
end end
<<~EOS def check_for_iconv
Your #{source} does not support macOS #{MacOS.version}. find_relative_paths("lib/libiconv.dylib", "include/iconv.h")
It is either outdated or was modified. return if @found.empty?
Please update your #{source} or delete it if no updates are available.
#{update_instructions}
EOS
end
# The CLT 10.x -> 11.x upgrade process on 10.14 contained a bug which broke the SDKs. libiconv = begin
# Notably, MacOSX10.14.sdk would indirectly symlink to MacOSX10.15.sdk. Formulary.factory("libiconv")
# This diagnostic was introduced to check for this and recommend a full reinstall. rescue
def check_broken_sdks nil
locator = MacOS.sdk_locator end
if libiconv&.linked_keg&.directory?
unless libiconv&.keg_only?
<<~EOS
A libiconv formula is installed and linked.
This will break stuff. For serious. Unlink it.
EOS
end
else
inject_file_list @found, <<~EOS
libiconv files detected at a system prefix other than /usr.
Homebrew doesn't provide a libiconv formula and expects to link against
the system version in /usr. libiconv in other prefixes can cause
compile or link failure, especially if compiled with improper
architectures. macOS itself never installs anything to /usr/local so
it was either installed by a user or some other third party software.
return if locator.all_sdks.all? do |sdk| tl;dr: delete these files:
path_version = sdk.path.basename.to_s[MacOS::SDK::VERSIONED_SDK_REGEX, 1] EOS
next true if path_version.blank? end
sdk.version == MacOSVersion.new(path_version).strip_patch
end end
if locator.source == :clt def check_for_multiple_volumes
source = "Command Line Tools (CLT)" return unless HOMEBREW_CELLAR.exist?
path_to_remove = MacOS::CLT::PKG_PATH
installation_instructions = MacOS::CLT.installation_instructions volumes = Volumes.new
else
source = "Xcode" # Find the volumes for the TMP folder & HOMEBREW_CELLAR
path_to_remove = MacOS::Xcode.bundle_path real_cellar = HOMEBREW_CELLAR.realpath
installation_instructions = MacOS::Xcode.installation_instructions where_cellar = volumes.which real_cellar
begin
tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP))
begin
real_tmp = tmp.realpath.parent
where_tmp = volumes.which real_tmp
ensure
Dir.delete tmp.to_s
end
rescue
return
end
return if where_cellar == where_tmp
<<~EOS
Your Cellar and TEMP directories are on different volumes.
macOS won't move relative symlinks across volumes unless the target file already
exists. Brews known to be affected by this are Git and Narwhal.
You should set the "HOMEBREW_TEMP" environment variable to a suitable
directory on the same volume as your Cellar.
EOS
end end
<<~EOS def check_deprecated_caskroom_taps
The contents of the SDKs in your #{source} installation do not match the SDK folder names. tapped_caskroom_taps = Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" }
A clean reinstall of #{source} should fix this. .map(&:name)
return if tapped_caskroom_taps.empty?
Remove the broken installation before reinstalling: <<~EOS
sudo rm -rf #{path_to_remove} You have the following deprecated, cask taps tapped:
#{tapped_caskroom_taps.join("\n ")}
Untap them with `brew untap`.
EOS
end
#{installation_instructions} def check_if_supported_sdk_available
EOS return unless ::DevelopmentTools.installed?
return unless MacOS.sdk_root_needed?
return if MacOS.sdk
locator = MacOS.sdk_locator
source = if locator.source == :clt
return if MacOS::CLT.below_minimum_version? # Handled by other diagnostics.
update_instructions = MacOS::CLT.update_instructions
"Command Line Tools (CLT)"
else
return if MacOS::Xcode.below_minimum_version? # Handled by other diagnostics.
update_instructions = MacOS::Xcode.update_instructions
"Xcode"
end
<<~EOS
Your #{source} does not support macOS #{MacOS.version}.
It is either outdated or was modified.
Please update your #{source} or delete it if no updates are available.
#{update_instructions}
EOS
end
# The CLT 10.x -> 11.x upgrade process on 10.14 contained a bug which broke the SDKs.
# Notably, MacOSX10.14.sdk would indirectly symlink to MacOSX10.15.sdk.
# This diagnostic was introduced to check for this and recommend a full reinstall.
def check_broken_sdks
locator = MacOS.sdk_locator
return if locator.all_sdks.all? do |sdk|
path_version = sdk.path.basename.to_s[MacOS::SDK::VERSIONED_SDK_REGEX, 1]
next true if path_version.blank?
sdk.version == MacOSVersion.new(path_version).strip_patch
end
if locator.source == :clt
source = "Command Line Tools (CLT)"
path_to_remove = MacOS::CLT::PKG_PATH
installation_instructions = MacOS::CLT.installation_instructions
else
source = "Xcode"
path_to_remove = MacOS::Xcode.bundle_path
installation_instructions = MacOS::Xcode.installation_instructions
end
<<~EOS
The contents of the SDKs in your #{source} installation do not match the SDK folder names.
A clean reinstall of #{source} should fix this.
Remove the broken installation before reinstalling:
sudo rm -rf #{path_to_remove}
#{installation_instructions}
EOS
end
end end
end end
end end
end end
Homebrew::Diagnostic::Checks.prepend(OS::Mac::Diagnostic::Checks)

View File

@ -37,6 +37,6 @@ module SharedEnvExtension
# This is supported starting Xcode 13, which ships ld64-711. # This is supported starting Xcode 13, which ships ld64-711.
# https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes # https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes
# https://en.wikipedia.org/wiki/Xcode#Xcode_11.0_-_14.x_(since_SwiftUI_framework)_2 # https://en.wikipedia.org/wiki/Xcode#Xcode_11.0_-_14.x_(since_SwiftUI_framework)_2
DevelopmentTools.ld64_version >= 711 OS::Mac::DevelopmentTools.ld64_version >= 711
end end
end end

View File

@ -1,117 +1,125 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Stdenv module OS
undef homebrew_extra_pkg_config_paths module Mac
module Stdenv
extend T::Helpers
sig { returns(T::Array[Pathname]) } requires_ancestor { ::Stdenv }
def homebrew_extra_pkg_config_paths
[Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
sig { sig { returns(T::Array[Pathname]) }
params( def homebrew_extra_pkg_config_paths
formula: T.nilable(Formula), [Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
cc: T.nilable(String), end
build_bottle: T.nilable(T::Boolean), private :homebrew_extra_pkg_config_paths
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
append "LDFLAGS", "-Wl,-headerpad_max_install_names" sig {
params(
formula: T.nilable(::Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
# `sed` is strict and errors out when it encounters files with mixed character sets. append "LDFLAGS", "-Wl,-headerpad_max_install_names"
delete("LC_ALL")
self["LC_CTYPE"] = "C"
# Add `lib` and `include` etc. from the current `macosxsdk` to compiler flags: # `sed` is strict and errors out when it encounters files with mixed character sets.
macosxsdk(formula: @formula, testing_formula:) delete("LC_ALL")
self["LC_CTYPE"] = "C"
return unless MacOS::Xcode.without_clt? # Add `lib` and `include` etc. from the current `macosxsdk` to compiler flags:
macosxsdk(formula: @formula, testing_formula:)
append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin" return unless MacOS::Xcode.without_clt?
append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
end
def remove_macosxsdk(version = nil) append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin"
# Clear all `lib` and `include` dirs from `CFLAGS`, `CPPFLAGS`, `LDFLAGS` that were append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
# previously added by `macosxsdk`. end
remove_from_cflags(/ ?-mmacosx-version-min=\d+\.\d+/)
delete("CPATH")
remove "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version) def remove_macosxsdk(version = nil)
return unless sdk # Clear all `lib` and `include` dirs from `CFLAGS`, `CPPFLAGS`, `LDFLAGS` that were
# previously added by `macosxsdk`.
remove_from_cflags(/ ?-mmacosx-version-min=\d+\.\d+/)
delete("CPATH")
remove "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
delete("SDKROOT") sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version)
remove_from_cflags "-isysroot#{sdk}" return unless sdk
remove "CPPFLAGS", "-isysroot#{sdk}"
remove "LDFLAGS", "-isysroot#{sdk}" delete("SDKROOT")
if HOMEBREW_PREFIX.to_s == "/usr/local" remove_from_cflags "-isysroot#{sdk}"
delete("CMAKE_PREFIX_PATH") remove "CPPFLAGS", "-isysroot#{sdk}"
else remove "LDFLAGS", "-isysroot#{sdk}"
# It was set in `setup_build_environment`, so we have to restore it here. if HOMEBREW_PREFIX.to_s == "/usr/local"
self["CMAKE_PREFIX_PATH"] = HOMEBREW_PREFIX.to_s delete("CMAKE_PREFIX_PATH")
else
# It was set in `setup_build_environment`, so we have to restore it here.
self["CMAKE_PREFIX_PATH"] = HOMEBREW_PREFIX.to_s
end
remove "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
def macosxsdk(version = nil, formula: nil, testing_formula: false)
# Sets all needed `lib` and `include` dirs to `CFLAGS`, `CPPFLAGS`, `LDFLAGS`.
remove_macosxsdk
min_version = version || MacOS.version
append_to_cflags("-mmacosx-version-min=#{min_version}")
self["CPATH"] = "#{HOMEBREW_PREFIX}/include"
prepend "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
sdk = if formula
MacOS.sdk_for_formula(formula, version, check_only_runtime_requirements: testing_formula)
else
MacOS.sdk(version)
end
return if !MacOS.sdk_root_needed? && sdk&.source != :xcode
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
sdk = sdk.path
# Extra setup to support Xcode 4.3+ without CLT.
self["SDKROOT"] = sdk
# Tell clang/gcc where system include's are:
append_path "CPATH", "#{sdk}/usr/include"
# The -isysroot is needed, too, because of the Frameworks
append_to_cflags "-isysroot#{sdk}"
append "CPPFLAGS", "-isysroot#{sdk}"
# And the linker needs to find sdk/usr/lib
append "LDFLAGS", "-isysroot#{sdk}"
# Needed to build cmake itself and perhaps some cmake projects:
append_path "CMAKE_PREFIX_PATH", "#{sdk}/usr"
append_path "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
# Some configure scripts won't find libxml2 without help.
# This is a no-op with macOS SDK 10.15.4 and later.
def libxml2
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed
if !sdk
append "CPPFLAGS", "-I/usr/include/libxml2"
elsif !Pathname("#{sdk}/usr/include/libxml").directory?
# Use the includes form the sdk
append "CPPFLAGS", "-I#{sdk}/usr/include/libxml2"
end
end
def no_weak_imports
append "LDFLAGS", "-Wl,-no_weak_imports" if no_weak_imports_support?
end
def no_fixup_chains
append "LDFLAGS", "-Wl,-no_fixup_chains" if no_fixup_chains_support?
end
end end
remove "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
def macosxsdk(version = nil, formula: nil, testing_formula: false)
# Sets all needed `lib` and `include` dirs to `CFLAGS`, `CPPFLAGS`, `LDFLAGS`.
remove_macosxsdk
min_version = version || MacOS.version
append_to_cflags("-mmacosx-version-min=#{min_version}")
self["CPATH"] = "#{HOMEBREW_PREFIX}/include"
prepend "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
sdk = if formula
MacOS.sdk_for_formula(formula, version, check_only_runtime_requirements: testing_formula)
else
MacOS.sdk(version)
end
return if !MacOS.sdk_root_needed? && sdk&.source != :xcode
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
sdk = sdk.path
# Extra setup to support Xcode 4.3+ without CLT.
self["SDKROOT"] = sdk
# Tell clang/gcc where system include's are:
append_path "CPATH", "#{sdk}/usr/include"
# The -isysroot is needed, too, because of the Frameworks
append_to_cflags "-isysroot#{sdk}"
append "CPPFLAGS", "-isysroot#{sdk}"
# And the linker needs to find sdk/usr/lib
append "LDFLAGS", "-isysroot#{sdk}"
# Needed to build cmake itself and perhaps some cmake projects:
append_path "CMAKE_PREFIX_PATH", "#{sdk}/usr"
append_path "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
# Some configure scripts won't find libxml2 without help.
# This is a no-op with macOS SDK 10.15.4 and later.
def libxml2
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed
if !sdk
append "CPPFLAGS", "-I/usr/include/libxml2"
elsif !Pathname("#{sdk}/usr/include/libxml").directory?
# Use the includes form the sdk
append "CPPFLAGS", "-I#{sdk}/usr/include/libxml2"
end
end
def no_weak_imports
append "LDFLAGS", "-Wl,-no_weak_imports" if no_weak_imports_support?
end
def no_fixup_chains
append "LDFLAGS", "-Wl,-no_fixup_chains" if no_fixup_chains_support?
end end
end end
Stdenv.prepend(OS::Mac::Stdenv)

View File

@ -1,169 +1,175 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Superenv module OS
class << self module Mac
# The location of Homebrew's shims on macOS. module Superenv
def shims_path extend T::Helpers
HOMEBREW_SHIMS_PATH/"mac/super"
end
undef bin requires_ancestor { ::Superenv }
def bin module ClassMethods
return unless DevelopmentTools.installed? # The location of Homebrew's shims on macOS.
def shims_path
HOMEBREW_SHIMS_PATH/"mac/super"
end
shims_path.realpath def bin
end return unless ::DevelopmentTools.installed?
end
undef homebrew_extra_pkg_config_paths, shims_path.realpath
homebrew_extra_isystem_paths, homebrew_extra_library_paths, end
homebrew_extra_cmake_include_paths,
homebrew_extra_cmake_library_paths,
homebrew_extra_cmake_frameworks_paths,
determine_cccfg
sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[Pathname("/usr/lib/pkgconfig"), Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
sig { returns(T::Boolean) }
def libxml2_include_needed?
return false if deps.any? { |d| d.name == "libxml2" }
return false if Pathname("#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml").directory?
true
end
private :libxml2_include_needed?
def homebrew_extra_isystem_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_library_paths
paths = []
if compiler == :llvm_clang
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/lib"
paths << Formula["llvm"].opt_lib.to_s
end
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries"
paths
end
def homebrew_extra_cmake_include_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_cmake_library_paths
[Pathname("#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries")]
end
def homebrew_extra_cmake_frameworks_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks" if MacOS::Xcode.without_clt?
paths
end
def determine_cccfg
s = +""
# Fix issue with >= Mountain Lion apr-1-config having broken paths
s << "a"
s.freeze
end
# @private
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
sdk = formula ? MacOS.sdk_for_formula(formula) : MacOS.sdk
is_xcode_sdk = sdk&.source == :xcode
if is_xcode_sdk || MacOS.sdk_root_needed?
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
self["HOMEBREW_SDKROOT"] = sdk.path if sdk
end
self["HOMEBREW_DEVELOPER_DIR"] = if is_xcode_sdk
MacOS::Xcode.prefix.to_s
else
MacOS::CLT::PKG_PATH
end
# This is a workaround for the missing `m4` in Xcode CLT 15.3, which was
# reported in FB13679972. Apple has fixed this in Xcode CLT 16.0.
# See https://github.com/Homebrew/homebrew-core/issues/165388
if deps.none? { |d| d.name == "m4" } &&
MacOS.active_developer_dir == MacOS::CLT::PKG_PATH &&
!File.exist?("#{MacOS::CLT::PKG_PATH}/usr/bin/m4") &&
(gm4 = DevelopmentTools.locate("gm4").to_s).present?
self["M4"] = gm4
end
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
# Filter out symbols known not to be defined since GNU Autotools can't
# reliably figure this out with Xcode 8 and above.
if MacOS.version == "10.12" && MacOS::Xcode.version >= "9.0"
%w[fmemopen futimens open_memstream utimensat].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
elsif MacOS.version == "10.11" && MacOS::Xcode.version >= "8.0"
%w[basename_r clock_getres clock_gettime clock_settime dirname_r
getentropy mkostemp mkostemps timingsafe_bcmp].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end end
ENV["ac_cv_search_clock_gettime"] = "no" sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[Pathname("/usr/lib/pkgconfig"), Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
# works around libev.m4 unsetting ac_cv_func_clock_gettime sig { returns(T::Boolean) }
ENV["ac_have_clock_syscall"] = "no" def libxml2_include_needed?
return false if deps.any? { |d| d.name == "libxml2" }
return false if Pathname("#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml").directory?
true
end
private :libxml2_include_needed?
def homebrew_extra_isystem_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_library_paths
paths = []
if compiler == :llvm_clang
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/lib"
paths << ::Formula["llvm"].opt_lib.to_s
end
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries"
paths
end
def homebrew_extra_cmake_include_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_cmake_library_paths
[Pathname(
"#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries",
)]
end
def homebrew_extra_cmake_frameworks_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks" if MacOS::Xcode.without_clt?
paths
end
def determine_cccfg
s = +""
# Fix issue with >= Mountain Lion apr-1-config having broken paths
s << "a"
s.freeze
end
# @private
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
sdk = formula ? MacOS.sdk_for_formula(formula) : MacOS.sdk
is_xcode_sdk = sdk&.source == :xcode
if is_xcode_sdk || MacOS.sdk_root_needed?
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
self["HOMEBREW_SDKROOT"] = sdk.path if sdk
end
self["HOMEBREW_DEVELOPER_DIR"] = if is_xcode_sdk
MacOS::Xcode.prefix.to_s
else
MacOS::CLT::PKG_PATH
end
# This is a workaround for the missing `m4` in Xcode CLT 15.3, which was
# reported in FB13679972. Apple has fixed this in Xcode CLT 16.0.
# See https://github.com/Homebrew/homebrew-core/issues/165388
if deps.none? { |d| d.name == "m4" } &&
MacOS.active_developer_dir == MacOS::CLT::PKG_PATH &&
!File.exist?("#{MacOS::CLT::PKG_PATH}/usr/bin/m4") &&
(gm4 = ::DevelopmentTools.locate("gm4").to_s).present?
self["M4"] = gm4
end
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
# Filter out symbols known not to be defined since GNU Autotools can't
# reliably figure this out with Xcode 8 and above.
if MacOS.version == "10.12" && MacOS::Xcode.version >= "9.0"
%w[fmemopen futimens open_memstream utimensat].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
elsif MacOS.version == "10.11" && MacOS::Xcode.version >= "8.0"
%w[basename_r clock_getres clock_gettime clock_settime dirname_r
getentropy mkostemp mkostemps timingsafe_bcmp].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
ENV["ac_cv_search_clock_gettime"] = "no"
# works around libev.m4 unsetting ac_cv_func_clock_gettime
ENV["ac_have_clock_syscall"] = "no"
end
# On macOS Sonoma (at least release candidate), iconv() is generally
# present and working, but has a minor regression that defeats the
# test implemented in gettext's configure script (and used by many
# gettext dependents).
ENV["am_cv_func_iconv_works"] = "yes" if MacOS.version == "14"
# The tools in /usr/bin proxy to the active developer directory.
# This means we can use them for any combination of CLT and Xcode.
self["HOMEBREW_PREFER_CLT_PROXIES"] = "1"
# Deterministic timestamping.
# This can work on older Xcode versions, but they contain some bugs.
# Notably, Xcode 10.2 fixes issues where ZERO_AR_DATE affected file mtimes.
# Xcode 11.0 contains fixes for lldb reading things built with ZERO_AR_DATE.
self["ZERO_AR_DATE"] = "1" if MacOS::Xcode.version >= "11.0" || MacOS::CLT.version >= "11.0"
# Pass `-no_fixup_chains` whenever the linker is invoked with `-undefined dynamic_lookup`.
# See: https://github.com/python/cpython/issues/97524
# https://github.com/pybind/pybind11/pull/4301
no_fixup_chains
# Strip build prefixes from linker where supported, for deterministic builds.
append_to_cccfg "o" if OS::Mac::DevelopmentTools.ld64_version >= 512
# Pass `-ld_classic` whenever the linker is invoked with `-dead_strip_dylibs`
# on `ld` versions that don't properly handle that option.
if OS::Mac::DevelopmentTools.ld64_version >= "1015.7" && OS::Mac::DevelopmentTools.ld64_version <= "1022.1"
append_to_cccfg "c"
end
end
def no_weak_imports
append_to_cccfg "w" if no_weak_imports_support?
end
def no_fixup_chains
append_to_cccfg "f" if no_fixup_chains_support?
end
end end
# On macOS Sonoma (at least release candidate), iconv() is generally
# present and working, but has a minor regression that defeats the
# test implemented in gettext's configure script (and used by many
# gettext dependents).
ENV["am_cv_func_iconv_works"] = "yes" if MacOS.version == "14"
# The tools in /usr/bin proxy to the active developer directory.
# This means we can use them for any combination of CLT and Xcode.
self["HOMEBREW_PREFER_CLT_PROXIES"] = "1"
# Deterministic timestamping.
# This can work on older Xcode versions, but they contain some bugs.
# Notably, Xcode 10.2 fixes issues where ZERO_AR_DATE affected file mtimes.
# Xcode 11.0 contains fixes for lldb reading things built with ZERO_AR_DATE.
self["ZERO_AR_DATE"] = "1" if MacOS::Xcode.version >= "11.0" || MacOS::CLT.version >= "11.0"
# Pass `-no_fixup_chains` whenever the linker is invoked with `-undefined dynamic_lookup`.
# See: https://github.com/python/cpython/issues/97524
# https://github.com/pybind/pybind11/pull/4301
no_fixup_chains
# Strip build prefixes from linker where supported, for deterministic builds.
append_to_cccfg "o" if DevelopmentTools.ld64_version >= 512
# Pass `-ld_classic` whenever the linker is invoked with `-dead_strip_dylibs`
# on `ld` versions that don't properly handle that option.
append_to_cccfg "c" if DevelopmentTools.ld64_version >= "1015.7" && DevelopmentTools.ld64_version <= "1022.1"
end
def no_weak_imports
append_to_cccfg "w" if no_weak_imports_support?
end
def no_fixup_chains
append_to_cccfg "f" if no_fixup_chains_support?
end end
end end
Superenv.singleton_class.prepend(OS::Mac::Superenv::ClassMethods)
Superenv.prepend(OS::Mac::Superenv)

View File

@ -97,7 +97,7 @@ module FormulaCellarChecks
return unless formula.prefix.directory? return unless formula.prefix.directory?
return if formula.tap&.audit_exception(:flat_namespace_allowlist, formula.name) return if formula.tap&.audit_exception(:flat_namespace_allowlist, formula.name)
keg = Keg.new(formula.prefix) keg = ::Keg.new(formula.prefix)
flat_namespace_files = keg.mach_o_files.reject do |file| flat_namespace_files = keg.mach_o_files.reject do |file|
next true unless file.dylib? next true unless file.dylib?

View File

@ -10,7 +10,7 @@ module OS
sig { params(formula: Formula).returns(T.nilable(T::Boolean)) } sig { params(formula: Formula).returns(T.nilable(T::Boolean)) }
def fresh_install?(formula) def fresh_install?(formula)
!Homebrew::EnvConfig.developer? && !OS::Mac.version.outdated_release? && !::Homebrew::EnvConfig.developer? && !OS::Mac.version.outdated_release? &&
(!installed_as_dependency? || !formula.any_version_installed?) (!installed_as_dependency? || !formula.any_version_installed?)
end end
end end

View File

@ -3,175 +3,179 @@
require "macho" require "macho"
module Hardware module OS
class CPU module Mac
class << self module Hardware
undef type, family, features, sse4? module CPU
extend T::Helpers
# These methods use info spewed out by sysctl. # These methods use info spewed out by sysctl.
# Look in <mach/machine.h> for decoding info. # Look in <mach/machine.h> for decoding info.
def type def type
case sysctl_int("hw.cputype") case sysctl_int("hw.cputype")
when MachO::Headers::CPU_TYPE_I386 when MachO::Headers::CPU_TYPE_I386
:intel :intel
when MachO::Headers::CPU_TYPE_ARM64 when MachO::Headers::CPU_TYPE_ARM64
:arm :arm
else else
:dunno :dunno
end
end end
end
def family def family
if arm? if ::Hardware::CPU.arm?
arm_family arm_family
elsif intel? elsif ::Hardware::CPU.intel?
intel_family intel_family
else else
:dunno :dunno
end
end end
end
# True when running under an Intel-based shell via Rosetta 2 on an # True when running under an Intel-based shell via Rosetta 2 on an
# Apple Silicon Mac. This can be detected via seeing if there's a # Apple Silicon Mac. This can be detected via seeing if there's a
# conflict between what `uname` reports and the underlying `sysctl` flags, # conflict between what `uname` reports and the underlying `sysctl` flags,
# since the `sysctl` flags don't change behaviour under Rosetta 2. # since the `sysctl` flags don't change behaviour under Rosetta 2.
def in_rosetta2? def in_rosetta2?
sysctl_bool("sysctl.proc_translated") sysctl_bool("sysctl.proc_translated")
end
def features
@features ||= sysctl_n(
"machdep.cpu.features",
"machdep.cpu.extfeatures",
"machdep.cpu.leaf7_features",
).split.map { |s| s.downcase.to_sym }
end
def sse4?
sysctl_bool("hw.optional.sse4_1")
end
def extmodel
sysctl_int("machdep.cpu.extmodel")
end
def aes?
sysctl_bool("hw.optional.aes")
end
def altivec?
sysctl_bool("hw.optional.altivec")
end
def avx?
sysctl_bool("hw.optional.avx1_0")
end
def avx2?
sysctl_bool("hw.optional.avx2_0")
end
def sse3?
sysctl_bool("hw.optional.sse3")
end
def ssse3?
sysctl_bool("hw.optional.supplementalsse3")
end
def sse4_2?
sysctl_bool("hw.optional.sse4_2")
end
# NOTE: This is more reliable than checking `uname`. `sysctl` returns
# the right answer even when running in Rosetta 2.
def physical_cpu_arm64?
sysctl_bool("hw.optional.arm64")
end
def virtualized?
sysctl_bool("kern.hv_vmm_present")
end
private
def arm_family
case sysctl_int("hw.cpufamily")
when 0x2c91a47e # ARMv8.0-A (Typhoon)
:arm_typhoon
when 0x92fb37c8 # ARMv8.0-A (Twister)
:arm_twister
when 0x67ceee93 # ARMv8.1-A (Hurricane, Zephyr)
:arm_hurricane_zephyr
when 0xe81e7ef6 # ARMv8.2-A (Monsoon, Mistral)
:arm_monsoon_mistral
when 0x07d34b9f # ARMv8.3-A (Vortex, Tempest)
:arm_vortex_tempest
when 0x462504d2 # ARMv8.4-A (Lightning, Thunder)
:arm_lightning_thunder
when 0x573b5eec, 0x1b588bb3 # ARMv8.4-A (Firestorm, Icestorm)
:arm_firestorm_icestorm
when 0xda33d83d # ARMv8.5-A (Blizzard, Avalanche)
:arm_blizzard_avalanche
when 0xfa33415e # ARMv8.6-A (M3, Ibiza)
:arm_ibiza
when 0x5f4dea93 # ARMv8.6-A (M3 Pro, Lobos)
:arm_lobos
when 0x72015832 # ARMv8.6-A (M3 Max, Palma)
:arm_palma
else
# When adding new ARM CPU families, please also update
# test/hardware/cpu_spec.rb to include the new families.
:dunno
end end
end
def intel_family(_family = nil, _cpu_model = nil) def features
case sysctl_int("hw.cpufamily") @features ||= sysctl_n(
when 0x73d67300 # Yonah: Core Solo/Duo "machdep.cpu.features",
:core "machdep.cpu.extfeatures",
when 0x426f69ef # Merom: Core 2 Duo "machdep.cpu.leaf7_features",
:core2 ).split.map { |s| s.downcase.to_sym }
when 0x78ea4fbc # Penryn
:penryn
when 0x6b5a4cd2 # Nehalem
:nehalem
when 0x573b5eec # Westmere
:westmere
when 0x5490b78c # Sandy Bridge
:sandybridge
when 0x1f65e835 # Ivy Bridge
:ivybridge
when 0x10b282dc # Haswell
:haswell
when 0x582ed09c # Broadwell
:broadwell
when 0x37fc219f # Skylake
:skylake
when 0x0f817246 # Kaby Lake
:kabylake
when 0x38435547 # Ice Lake
:icelake
when 0x1cf8a03e # Comet Lake
:cometlake
else
:dunno
end end
end
def sysctl_bool(key) def sse4?
sysctl_int(key) == 1 sysctl_bool("hw.optional.sse4_1")
end end
def sysctl_int(key) def extmodel
sysctl_n(key).to_i & 0xffffffff sysctl_int("machdep.cpu.extmodel")
end end
def sysctl_n(*keys) def aes?
(@properties ||= {}).fetch(keys) do sysctl_bool("hw.optional.aes")
@properties[keys] = Utils.popen_read("/usr/sbin/sysctl", "-n", *keys) end
def altivec?
sysctl_bool("hw.optional.altivec")
end
def avx?
sysctl_bool("hw.optional.avx1_0")
end
def avx2?
sysctl_bool("hw.optional.avx2_0")
end
def sse3?
sysctl_bool("hw.optional.sse3")
end
def ssse3?
sysctl_bool("hw.optional.supplementalsse3")
end
def sse4_2?
sysctl_bool("hw.optional.sse4_2")
end
# NOTE: This is more reliable than checking `uname`. `sysctl` returns
# the right answer even when running in Rosetta 2.
def physical_cpu_arm64?
sysctl_bool("hw.optional.arm64")
end
def virtualized?
sysctl_bool("kern.hv_vmm_present")
end
private
def arm_family
case sysctl_int("hw.cpufamily")
when 0x2c91a47e # ARMv8.0-A (Typhoon)
:arm_typhoon
when 0x92fb37c8 # ARMv8.0-A (Twister)
:arm_twister
when 0x67ceee93 # ARMv8.1-A (Hurricane, Zephyr)
:arm_hurricane_zephyr
when 0xe81e7ef6 # ARMv8.2-A (Monsoon, Mistral)
:arm_monsoon_mistral
when 0x07d34b9f # ARMv8.3-A (Vortex, Tempest)
:arm_vortex_tempest
when 0x462504d2 # ARMv8.4-A (Lightning, Thunder)
:arm_lightning_thunder
when 0x573b5eec, 0x1b588bb3 # ARMv8.4-A (Firestorm, Icestorm)
:arm_firestorm_icestorm
when 0xda33d83d # ARMv8.5-A (Blizzard, Avalanche)
:arm_blizzard_avalanche
when 0xfa33415e # ARMv8.6-A (M3, Ibiza)
:arm_ibiza
when 0x5f4dea93 # ARMv8.6-A (M3 Pro, Lobos)
:arm_lobos
when 0x72015832 # ARMv8.6-A (M3 Max, Palma)
:arm_palma
else
# When adding new ARM CPU families, please also update
# test/hardware/cpu_spec.rb to include the new families.
:dunno
end
end
def intel_family(_family = nil, _cpu_model = nil)
case sysctl_int("hw.cpufamily")
when 0x73d67300 # Yonah: Core Solo/Duo
:core
when 0x426f69ef # Merom: Core 2 Duo
:core2
when 0x78ea4fbc # Penryn
:penryn
when 0x6b5a4cd2 # Nehalem
:nehalem
when 0x573b5eec # Westmere
:westmere
when 0x5490b78c # Sandy Bridge
:sandybridge
when 0x1f65e835 # Ivy Bridge
:ivybridge
when 0x10b282dc # Haswell
:haswell
when 0x582ed09c # Broadwell
:broadwell
when 0x37fc219f # Skylake
:skylake
when 0x0f817246 # Kaby Lake
:kabylake
when 0x38435547 # Ice Lake
:icelake
when 0x1cf8a03e # Comet Lake
:cometlake
else
:dunno
end
end
def sysctl_bool(key)
sysctl_int(key) == 1
end
def sysctl_int(key)
sysctl_n(key).to_i & 0xffffffff
end
def sysctl_n(*keys)
(@properties ||= {}).fetch(keys) do
@properties[keys] = Utils.popen_read("/usr/sbin/sysctl", "-n", *keys)
end
end end
end end
end end
end end
end end
Hardware::CPU.singleton_class.prepend(OS::Mac::Hardware::CPU)

View File

@ -23,101 +23,101 @@ class Keg
GENERIC_MUST_BE_WRITABLE_DIRECTORIES + GENERIC_MUST_BE_WRITABLE_DIRECTORIES +
[HOMEBREW_PREFIX/"Frameworks"] [HOMEBREW_PREFIX/"Frameworks"]
).sort.uniq.freeze ).sort.uniq.freeze
end
undef binary_executable_or_library_files module OS
module Mac
module Keg
def binary_executable_or_library_files = mach_o_files
def binary_executable_or_library_files def codesign_patched_binary(file)
mach_o_files return if MacOS.version < :big_sur
end
def codesign_patched_binary(file) unless ::Hardware::CPU.arm?
return if MacOS.version < :big_sur result = system_command("codesign", args: ["--verify", file], print_stderr: false)
return unless result.stderr.match?(/invalid signature/i)
end
unless Hardware::CPU.arm? odebug "Codesigning #{file}"
result = system_command("codesign", args: ["--verify", file], print_stderr: false) prepare_codesign_writable_files(file) do
return unless result.stderr.match?(/invalid signature/i) # Use quiet_system to squash notifications about resigning binaries
end # which already have valid signatures.
return if quiet_system("codesign", "--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file)
odebug "Codesigning #{file}" # If the codesigning fails, it may be a bug in Apple's codesign utility
prepare_codesign_writable_files(file) do # A known workaround is to copy the file to another inode, then move it back
# Use quiet_system to squash notifications about resigning binaries # erasing the previous file. Then sign again.
# which already have valid signatures. #
return if quiet_system("codesign", "--sign", "-", "--force", # TODO: remove this once the bug in Apple's codesign utility is fixed
"--preserve-metadata=entitlements,requirements,flags,runtime", Dir::Tmpname.create("workaround") do |tmppath|
file) FileUtils.cp file, tmppath
FileUtils.mv tmppath, file, force: true
end
# If the codesigning fails, it may be a bug in Apple's codesign utility # Try signing again
# A known workaround is to copy the file to another inode, then move it back odebug "Codesigning (2nd try) #{file}"
# erasing the previous file. Then sign again. result = system_command("codesign", args: [
# "--sign", "-", "--force",
# TODO: remove this once the bug in Apple's codesign utility is fixed "--preserve-metadata=entitlements,requirements,flags,runtime",
Dir::Tmpname.create("workaround") do |tmppath| file
FileUtils.cp file, tmppath ], print_stderr: false)
FileUtils.mv tmppath, file, force: true return if result.success?
# If it fails again, error out
onoe <<~EOS
Failed applying an ad-hoc signature to #{file}:
#{result.stderr}
EOS
end
end end
# Try signing again def prepare_codesign_writable_files(file)
odebug "Codesigning (2nd try) #{file}" result = system_command("codesign", args: [
result = system_command("codesign", args: [ "--display", "--file-list", "-", file
"--sign", "-", "--force", ], print_stderr: false)
"--preserve-metadata=entitlements,requirements,flags,runtime", return unless result.success?
file
], print_stderr: false)
return if result.success?
# If it fails again, error out files = result.stdout.lines.map { |f| Pathname(f.chomp) }
onoe <<~EOS saved_perms = {}
Failed applying an ad-hoc signature to #{file}: files.each do |f|
#{result.stderr} unless f.writable?
EOS saved_perms[f] = f.stat.mode
end FileUtils.chmod "u+rw", f.to_path
end end
end
def prepare_codesign_writable_files(file) yield
result = system_command("codesign", args: [ ensure
"--display", "--file-list", "-", file saved_perms&.each do |f, p|
], print_stderr: false) f.chmod p if p
return unless result.success? end
files = result.stdout.lines.map { |f| Pathname(f.chomp) }
saved_perms = {}
files.each do |f|
unless f.writable?
saved_perms[f] = f.stat.mode
FileUtils.chmod "u+rw", f.to_path
end end
end
yield
ensure
saved_perms&.each do |f, p|
f.chmod p if p
end
end
undef prepare_debug_symbols def prepare_debug_symbols
binary_executable_or_library_files.each do |file|
odebug "Extracting symbols #{file}"
def prepare_debug_symbols result = system_command("dsymutil", args: [file], print_stderr: false)
binary_executable_or_library_files.each do |file| next if result.success?
odebug "Extracting symbols #{file}"
result = system_command("dsymutil", args: [file], print_stderr: false) # If it fails again, error out
next if result.success? ofail <<~EOS
Failed to extract symbols from #{file}:
#{result.stderr}
EOS
end
end
# If it fails again, error out # Needed to make symlink permissions consistent on macOS and Linux for
ofail <<~EOS # reproducible bottles.
Failed to extract symbols from #{file}: def consistent_reproducible_symlink_permissions!
#{result.stderr} path.find do |file|
EOS File.lchmod 0777, file if file.symlink?
end end
end end
undef consistent_reproducible_symlink_permissions!
# Needed to make symlink permissions consistent on macOS and Linux for
# reproducible bottles.
def consistent_reproducible_symlink_permissions!
path.find do |file|
File.lchmod 0777, file if file.symlink?
end end
end end
end end
Keg.prepend(OS::Mac::Keg)

View File

@ -1,255 +1,262 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
class Keg module OS
class << self module Mac
undef file_linked_libraries module Keg
extend T::Helpers
def file_linked_libraries(file, string) requires_ancestor { ::Keg }
# Check dynamic library linkage. Importantly, do not perform for static
# libraries, which will falsely report "linkage" to themselves.
if file.mach_o_executable? || file.dylib? || file.mach_o_bundle?
file.dynamically_linked_libraries.select { |lib| lib.include? string }
else
[]
end
end
end
undef relocate_dynamic_linkage module ClassMethods
def file_linked_libraries(file, string)
def relocate_dynamic_linkage(relocation) # Check dynamic library linkage. Importantly, do not perform for static
mach_o_files.each do |file| # libraries, which will falsely report "linkage" to themselves.
file.ensure_writable do if file.mach_o_executable? || file.dylib? || file.mach_o_bundle?
modified = T.let(false, T::Boolean) file.dynamically_linked_libraries.select { |lib| lib.include? string }
needs_codesigning = T.let(false, T::Boolean)
if file.dylib?
id = relocated_name_for(file.dylib_id, relocation)
modified = change_dylib_id(id, file)
needs_codesigning ||= modified
end
each_linkage_for(file, :dynamically_linked_libraries) do |old_name|
new_name = relocated_name_for(old_name, relocation)
modified = change_install_name(old_name, new_name, file) if new_name
needs_codesigning ||= modified
end
each_linkage_for(file, :rpaths) do |old_name|
new_name = relocated_name_for(old_name, relocation)
modified = change_rpath(old_name, new_name, file) if new_name
needs_codesigning ||= modified
end
# codesign the file if needed
codesign_patched_binary(file) if needs_codesigning
end
end
end
def fix_dynamic_linkage
mach_o_files.each do |file|
file.ensure_writable do
modified = T.let(false, T::Boolean)
needs_codesigning = T.let(false, T::Boolean)
modified = change_dylib_id(dylib_id_for(file), file) if file.dylib?
needs_codesigning ||= modified
each_linkage_for(file, :dynamically_linked_libraries) do |bad_name|
# Don't fix absolute paths unless they are rooted in the build directory.
new_name = if bad_name.start_with?("/") && !rooted_in_build_directory?(bad_name)
bad_name
else else
fixed_name(file, bad_name) []
end
end
end
def relocate_dynamic_linkage(relocation)
mach_o_files.each do |file|
file.ensure_writable do
modified = T.let(false, T::Boolean)
needs_codesigning = T.let(false, T::Boolean)
if file.dylib?
id = relocated_name_for(file.dylib_id, relocation)
modified = change_dylib_id(id, file)
needs_codesigning ||= modified
end
each_linkage_for(file, :dynamically_linked_libraries) do |old_name|
new_name = relocated_name_for(old_name, relocation)
modified = change_install_name(old_name, new_name, file) if new_name
needs_codesigning ||= modified
end
each_linkage_for(file, :rpaths) do |old_name|
new_name = relocated_name_for(old_name, relocation)
modified = change_rpath(old_name, new_name, file) if new_name
needs_codesigning ||= modified
end
# codesign the file if needed
codesign_patched_binary(file) if needs_codesigning
end
end
end
def fix_dynamic_linkage
mach_o_files.each do |file|
file.ensure_writable do
modified = T.let(false, T::Boolean)
needs_codesigning = T.let(false, T::Boolean)
modified = change_dylib_id(dylib_id_for(file), file) if file.dylib?
needs_codesigning ||= modified
each_linkage_for(file, :dynamically_linked_libraries) do |bad_name|
# Don't fix absolute paths unless they are rooted in the build directory.
new_name = if bad_name.start_with?("/") && !rooted_in_build_directory?(bad_name)
bad_name
else
fixed_name(file, bad_name)
end
loader_name = loader_name_for(file, new_name)
modified = change_install_name(bad_name, loader_name, file) if loader_name != bad_name
needs_codesigning ||= modified
end
each_linkage_for(file, :rpaths) do |bad_name|
new_name = opt_name_for(bad_name)
loader_name = loader_name_for(file, new_name)
next if loader_name == bad_name
modified = change_rpath(bad_name, loader_name, file)
needs_codesigning ||= modified
end
# Strip duplicate rpaths and rpaths rooted in the build directory.
# We do this separately from the rpath relocation above to avoid
# failing to relocate an rpath whose variable duplicate we deleted.
each_linkage_for(file, :rpaths, resolve_variable_references: true) do |bad_name|
next if !rooted_in_build_directory?(bad_name) && file.rpaths.count(bad_name) == 1
modified = delete_rpath(bad_name, file)
needs_codesigning ||= modified
end
# codesign the file if needed
codesign_patched_binary(file) if needs_codesigning
end end
loader_name = loader_name_for(file, new_name)
modified = change_install_name(bad_name, loader_name, file) if loader_name != bad_name
needs_codesigning ||= modified
end end
each_linkage_for(file, :rpaths) do |bad_name| generic_fix_dynamic_linkage
new_name = opt_name_for(bad_name) end
loader_name = loader_name_for(file, new_name)
next if loader_name == bad_name
modified = change_rpath(bad_name, loader_name, file) def loader_name_for(file, target)
needs_codesigning ||= modified # Use @loader_path-relative install names for other Homebrew-installed binaries.
if ENV["HOMEBREW_RELOCATABLE_INSTALL_NAMES"] && target.start_with?(HOMEBREW_PREFIX)
dylib_suffix = find_dylib_suffix_from(target)
target_dir = Pathname.new(target.delete_suffix(dylib_suffix)).cleanpath
"@loader_path/#{target_dir.relative_path_from(file.dirname)/dylib_suffix}"
else
target
end
end
# If file is a dylib or bundle itself, look for the dylib named by
# bad_name relative to the lib directory, so that we can skip the more
# expensive recursive search if possible.
def fixed_name(file, bad_name)
if bad_name.start_with? ::Keg::PREFIX_PLACEHOLDER
bad_name.sub(::Keg::PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
elsif bad_name.start_with? ::Keg::CELLAR_PLACEHOLDER
bad_name.sub(::Keg::CELLAR_PLACEHOLDER, HOMEBREW_CELLAR)
elsif (file.dylib? || file.mach_o_bundle?) && (file.dirname/bad_name).exist?
"@loader_path/#{bad_name}"
elsif file.mach_o_executable? && (lib/bad_name).exist?
"#{lib}/#{bad_name}"
elsif file.mach_o_executable? && (libexec/"lib"/bad_name).exist?
"#{libexec}/lib/#{bad_name}"
elsif (abs_name = find_dylib(bad_name)) && abs_name.exist?
abs_name.to_s
else
opoo "Could not fix #{bad_name} in #{file}"
bad_name
end
end
VARIABLE_REFERENCE_RX = /^@(loader_|executable_|r)path/
def each_linkage_for(file, linkage_type, resolve_variable_references: false, &block)
file.public_send(linkage_type, resolve_variable_references:)
.grep_v(VARIABLE_REFERENCE_RX)
.each(&block)
end
def dylib_id_for(file)
# The new dylib ID should have the same basename as the old dylib ID, not
# the basename of the file itself.
basename = File.basename(file.dylib_id)
relative_dirname = file.dirname.relative_path_from(path)
(opt_record/relative_dirname/basename).to_s
end
def relocated_name_for(old_name, relocation)
old_prefix, new_prefix = relocation.replacement_pair_for(:prefix)
old_cellar, new_cellar = relocation.replacement_pair_for(:cellar)
if old_name.start_with? old_cellar
old_name.sub(old_cellar, new_cellar)
elsif old_name.start_with? old_prefix
old_name.sub(old_prefix, new_prefix)
end
end
# Matches framework references like `XXX.framework/Versions/YYY/XXX` and
# `XXX.framework/XXX`, both with or without a slash-delimited prefix.
FRAMEWORK_RX = %r{(?:^|/)(([^/]+)\.framework/(?:Versions/[^/]+/)?\2)$}
def find_dylib_suffix_from(bad_name)
if (framework = bad_name.match(FRAMEWORK_RX))
framework[1]
else
File.basename(bad_name)
end
end
def find_dylib(bad_name)
return unless lib.directory?
suffix = "/#{find_dylib_suffix_from(bad_name)}"
lib.find { |pn| break pn if pn.to_s.end_with?(suffix) }
end
def mach_o_files
hardlinks = Set.new
mach_o_files = []
path.find do |pn|
next if pn.symlink? || pn.directory?
next if !pn.dylib? && !pn.mach_o_bundle? && !pn.mach_o_executable?
# if we've already processed a file, ignore its hardlinks (which have the same dev ID and inode)
# this prevents relocations from being performed on a binary more than once
next unless hardlinks.add? [pn.stat.dev, pn.stat.ino]
mach_o_files << pn
end end
# Strip duplicate rpaths and rpaths rooted in the build directory. mach_o_files
# We do this separately from the rpath relocation above to avoid end
# failing to relocate an rpath whose variable duplicate we deleted.
each_linkage_for(file, :rpaths, resolve_variable_references: true) do |bad_name|
next if !rooted_in_build_directory?(bad_name) && file.rpaths.count(bad_name) == 1
modified = delete_rpath(bad_name, file) def prepare_relocation_to_locations
needs_codesigning ||= modified relocation = generic_prepare_relocation_to_locations
brewed_perl = runtime_dependencies&.any? { |dep| dep["full_name"] == "perl" && dep["declared_directly"] }
perl_path = if brewed_perl || name == "perl"
"#{HOMEBREW_PREFIX}/opt/perl/bin/perl"
elsif tab.built_on.present?
perl_path = "/usr/bin/perl#{tab.built_on["preferred_perl"]}"
# For `:all` bottles, we could have built this bottle with a Perl we don't have.
# Such bottles typically don't have strict version requirements.
perl_path = "/usr/bin/perl#{MacOS.preferred_perl_version}" unless File.exist?(perl_path)
perl_path
else
"/usr/bin/perl#{MacOS.preferred_perl_version}"
end
relocation.add_replacement_pair(:perl, ::Keg::PERL_PLACEHOLDER, perl_path)
if (openjdk = openjdk_dep_name_if_applicable)
openjdk_path = HOMEBREW_PREFIX/"opt"/openjdk/"libexec/openjdk.jdk/Contents/Home"
relocation.add_replacement_pair(:java, ::Keg::JAVA_PLACEHOLDER, openjdk_path.to_s)
end end
# codesign the file if needed relocation
codesign_patched_binary(file) if needs_codesigning end
def recursive_fgrep_args
# Don't recurse into symlinks; the man page says this is the default, but
# it's wrong. -O is a BSD-grep-only option.
"-lrO"
end
def egrep_args
grep_bin = "egrep"
grep_args = "--files-with-matches"
[grep_bin, grep_args]
end
private
CELLAR_RX = %r{\A#{HOMEBREW_CELLAR}/(?<formula_name>[^/]+)/[^/]+}
# Replace HOMEBREW_CELLAR references with HOMEBREW_PREFIX/opt references
# if the Cellar reference is to a different keg.
def opt_name_for(filename)
return filename unless filename.start_with?(HOMEBREW_PREFIX.to_s)
return filename if filename.start_with?(path.to_s)
return filename if (matches = CELLAR_RX.match(filename)).blank?
filename.sub(CELLAR_RX, "#{HOMEBREW_PREFIX}/opt/#{matches[:formula_name]}")
end
def rooted_in_build_directory?(filename)
# CMake normalises `/private/tmp` to `/tmp`.
# https://gitlab.kitware.com/cmake/cmake/-/issues/23251
return true if HOMEBREW_TEMP.to_s == "/private/tmp" && filename.start_with?("/tmp/")
filename.start_with?(HOMEBREW_TEMP.to_s) || filename.start_with?(HOMEBREW_TEMP.realpath.to_s)
end end
end end
generic_fix_dynamic_linkage
end
def loader_name_for(file, target)
# Use @loader_path-relative install names for other Homebrew-installed binaries.
if ENV["HOMEBREW_RELOCATABLE_INSTALL_NAMES"] && target.start_with?(HOMEBREW_PREFIX)
dylib_suffix = find_dylib_suffix_from(target)
target_dir = Pathname.new(target.delete_suffix(dylib_suffix)).cleanpath
"@loader_path/#{target_dir.relative_path_from(file.dirname)/dylib_suffix}"
else
target
end
end
# If file is a dylib or bundle itself, look for the dylib named by
# bad_name relative to the lib directory, so that we can skip the more
# expensive recursive search if possible.
def fixed_name(file, bad_name)
if bad_name.start_with? PREFIX_PLACEHOLDER
bad_name.sub(PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
elsif bad_name.start_with? CELLAR_PLACEHOLDER
bad_name.sub(CELLAR_PLACEHOLDER, HOMEBREW_CELLAR)
elsif (file.dylib? || file.mach_o_bundle?) && (file.dirname/bad_name).exist?
"@loader_path/#{bad_name}"
elsif file.mach_o_executable? && (lib/bad_name).exist?
"#{lib}/#{bad_name}"
elsif file.mach_o_executable? && (libexec/"lib"/bad_name).exist?
"#{libexec}/lib/#{bad_name}"
elsif (abs_name = find_dylib(bad_name)) && abs_name.exist?
abs_name.to_s
else
opoo "Could not fix #{bad_name} in #{file}"
bad_name
end
end
VARIABLE_REFERENCE_RX = /^@(loader_|executable_|r)path/
def each_linkage_for(file, linkage_type, resolve_variable_references: false, &block)
file.public_send(linkage_type, resolve_variable_references:)
.grep_v(VARIABLE_REFERENCE_RX)
.each(&block)
end
def dylib_id_for(file)
# The new dylib ID should have the same basename as the old dylib ID, not
# the basename of the file itself.
basename = File.basename(file.dylib_id)
relative_dirname = file.dirname.relative_path_from(path)
(opt_record/relative_dirname/basename).to_s
end
def relocated_name_for(old_name, relocation)
old_prefix, new_prefix = relocation.replacement_pair_for(:prefix)
old_cellar, new_cellar = relocation.replacement_pair_for(:cellar)
if old_name.start_with? old_cellar
old_name.sub(old_cellar, new_cellar)
elsif old_name.start_with? old_prefix
old_name.sub(old_prefix, new_prefix)
end
end
# Matches framework references like `XXX.framework/Versions/YYY/XXX` and
# `XXX.framework/XXX`, both with or without a slash-delimited prefix.
FRAMEWORK_RX = %r{(?:^|/)(([^/]+)\.framework/(?:Versions/[^/]+/)?\2)$}
def find_dylib_suffix_from(bad_name)
if (framework = bad_name.match(FRAMEWORK_RX))
framework[1]
else
File.basename(bad_name)
end
end
def find_dylib(bad_name)
return unless lib.directory?
suffix = "/#{find_dylib_suffix_from(bad_name)}"
lib.find { |pn| break pn if pn.to_s.end_with?(suffix) }
end
def mach_o_files
hardlinks = Set.new
mach_o_files = []
path.find do |pn|
next if pn.symlink? || pn.directory?
next if !pn.dylib? && !pn.mach_o_bundle? && !pn.mach_o_executable?
# if we've already processed a file, ignore its hardlinks (which have the same dev ID and inode)
# this prevents relocations from being performed on a binary more than once
next unless hardlinks.add? [pn.stat.dev, pn.stat.ino]
mach_o_files << pn
end
mach_o_files
end
def prepare_relocation_to_locations
relocation = generic_prepare_relocation_to_locations
brewed_perl = runtime_dependencies&.any? { |dep| dep["full_name"] == "perl" && dep["declared_directly"] }
perl_path = if brewed_perl || name == "perl"
"#{HOMEBREW_PREFIX}/opt/perl/bin/perl"
elsif tab.built_on.present?
perl_path = "/usr/bin/perl#{tab.built_on["preferred_perl"]}"
# For `:all` bottles, we could have built this bottle with a Perl we don't have.
# Such bottles typically don't have strict version requirements.
perl_path = "/usr/bin/perl#{MacOS.preferred_perl_version}" unless File.exist?(perl_path)
perl_path
else
"/usr/bin/perl#{MacOS.preferred_perl_version}"
end
relocation.add_replacement_pair(:perl, PERL_PLACEHOLDER, perl_path)
if (openjdk = openjdk_dep_name_if_applicable)
openjdk_path = HOMEBREW_PREFIX/"opt"/openjdk/"libexec/openjdk.jdk/Contents/Home"
relocation.add_replacement_pair(:java, JAVA_PLACEHOLDER, openjdk_path.to_s)
end
relocation
end
def recursive_fgrep_args
# Don't recurse into symlinks; the man page says this is the default, but
# it's wrong. -O is a BSD-grep-only option.
"-lrO"
end
def egrep_args
grep_bin = "egrep"
grep_args = "--files-with-matches"
[grep_bin, grep_args]
end
private
CELLAR_RX = %r{\A#{HOMEBREW_CELLAR}/(?<formula_name>[^/]+)/[^/]+}
# Replace HOMEBREW_CELLAR references with HOMEBREW_PREFIX/opt references
# if the Cellar reference is to a different keg.
def opt_name_for(filename)
return filename unless filename.start_with?(HOMEBREW_PREFIX.to_s)
return filename if filename.start_with?(path.to_s)
return filename if (matches = CELLAR_RX.match(filename)).blank?
filename.sub(CELLAR_RX, "#{HOMEBREW_PREFIX}/opt/#{matches[:formula_name]}")
end
def rooted_in_build_directory?(filename)
# CMake normalises `/private/tmp` to `/tmp`.
# https://gitlab.kitware.com/cmake/cmake/-/issues/23251
return true if HOMEBREW_TEMP.to_s == "/private/tmp" && filename.start_with?("/tmp/")
filename.start_with?(HOMEBREW_TEMP.to_s) || filename.start_with?(HOMEBREW_TEMP.realpath.to_s)
end end
end end
Keg.singleton_class.prepend(OS::Mac::Keg::ClassMethods)
Keg.prepend(OS::Mac::Keg)

View File

@ -9,7 +9,7 @@ module OS
requires_ancestor { Kernel } requires_ancestor { Kernel }
sig { params(tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) } sig { params(tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) }
def valid_casks?(tap, os_name: nil, arch: Hardware::CPU.type) def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type)
return true if os_name == :linux return true if os_name == :linux
current_macos_version = if os_name.is_a?(Symbol) current_macos_version = if os_name.is_a?(Symbol)

View File

@ -13,7 +13,7 @@ module OS
sig { returns(Symbol) } sig { returns(Symbol) }
def current_os def current_os
Homebrew::SimulateSystem.os || MacOS.version.to_sym ::Homebrew::SimulateSystem.os || MacOS.version.to_sym
end end
end end
end end

View File

@ -3,20 +3,26 @@
require "system_command" require "system_command"
module OS
module Mac
module SystemConfig
sig { returns(String) }
def describe_clang
return "N/A" if ::SystemConfig.clang.null?
clang_build_info = ::SystemConfig.clang_build.null? ? "(parse error)" : ::SystemConfig.clang_build
"#{::SystemConfig.clang} build #{clang_build_info}"
end
end
end
end
SystemConfig.prepend(OS::Mac::SystemConfig)
module SystemConfig module SystemConfig
class << self class << self
include SystemCommand::Mixin include SystemCommand::Mixin
undef describe_clang
sig { returns(String) }
def describe_clang
return "N/A" if clang.null?
clang_build_info = clang_build.null? ? "(parse error)" : clang_build
"#{clang} build #{clang_build_info}"
end
def xcode def xcode
@xcode ||= if MacOS::Xcode.installed? @xcode ||= if MacOS::Xcode.installed?
xcode = MacOS::Xcode.version.to_s xcode = MacOS::Xcode.version.to_s

View File

@ -0,0 +1,9 @@
# typed: strict
module Hardware
class CPU
class << self
include OS::Mac::Hardware::CPU
end
end
end

5
Library/Homebrew/keg.rbi Normal file
View File

@ -0,0 +1,5 @@
# typed: strict
class Keg
include OS::Mac::Keg
end

View File

@ -210,7 +210,6 @@ class Keg
# for GNU grep; overridden for BSD grep on OS X # for GNU grep; overridden for BSD grep on OS X
"-lr" "-lr"
end end
alias generic_recursive_fgrep_args recursive_fgrep_args
def egrep_args def egrep_args
grep_bin = "grep" grep_bin = "grep"

View File

@ -151,7 +151,7 @@ module OS
# Xcode.prefix is pretty smart, so let's look inside to find the sdk # Xcode.prefix is pretty smart, so let's look inside to find the sdk
sdk_prefix = "#{Xcode.prefix}/Platforms/MacOSX.platform/Developer/SDKs" sdk_prefix = "#{Xcode.prefix}/Platforms/MacOSX.platform/Developer/SDKs"
# Finally query Xcode itself (this is slow, so check it last) # Finally query Xcode itself (this is slow, so check it last)
sdk_platform_path = Utils.popen_read(DevelopmentTools.locate("xcrun"), "--show-sdk-platform-path").chomp sdk_platform_path = Utils.popen_read(::DevelopmentTools.locate("xcrun"), "--show-sdk-platform-path").chomp
sdk_prefix = File.join(sdk_platform_path, "Developer", "SDKs") unless File.directory? sdk_prefix sdk_prefix = File.join(sdk_platform_path, "Developer", "SDKs") unless File.directory? sdk_prefix
sdk_prefix sdk_prefix

View File

@ -227,7 +227,7 @@ module OS
sig { returns(String) } sig { returns(String) }
def self.detect_version_from_clang_version def self.detect_version_from_clang_version
version = DevelopmentTools.clang_version version = ::DevelopmentTools.clang_version
return "dunno" if version.null? return "dunno" if version.null?

View File

@ -87,7 +87,7 @@ class AbstractTab
"tap" => nil, "tap" => nil,
"tap_git_head" => nil, "tap_git_head" => nil,
}, },
"built_on" => DevelopmentTools.generic_build_system_info, "built_on" => DevelopmentTools.build_system_info,
} }
new(attributes) new(attributes)