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,
}
end
alias generic_build_system_info build_system_info
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
require "os/linux/glibc"
class DependencyCollector
undef gcc_dep_if_needed
undef glibc_dep_if_needed
undef init_global_dep_tree_if_needed!
module OS
module Linux
module DependencyCollector
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)) }
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)
Dependency.new(GCC, [:implicit])
end
Dependency.new(GCC, [:implicit])
end
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
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)) }
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)
Dependency.new(GLIBC, [:implicit])
end
Dependency.new(GLIBC, [:implicit])
end
private
private
GLIBC = "glibc"
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA
GLIBC = "glibc"
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA
sig { void }
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 }
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?
building_global_dep_tree!
global_dep_tree[GLIBC] = Set.new(global_deps_for(GLIBC))
# gcc depends on glibc
global_dep_tree[GCC] = Set.new([*global_deps_for(GCC), GLIBC, *@@global_dep_tree[GLIBC]])
built_global_dep_tree!
end
building_global_dep_tree!
global_dep_tree[GLIBC] = Set.new(global_deps_for(GLIBC))
# gcc depends on glibc
global_dep_tree[GCC] = Set.new([*global_deps_for(GCC), GLIBC, *@@global_dep_tree[GLIBC]])
built_global_dep_tree!
end
sig { params(name: String).returns(T.nilable(Formula)) }
def formula_for(name)
@formula_for ||= T.let({}, T.nilable(T::Hash[String, Formula]))
@formula_for[name] ||= ::Formula[name]
rescue FormulaUnavailableError
nil
end
sig { params(name: String).returns(T.nilable(Formula)) }
def formula_for(name)
@formula_for ||= {}
@formula_for[name] ||= Formula[name]
rescue FormulaUnavailableError
nil
end
sig { params(name: String).returns(T::Array[String]) }
def global_deps_for(name)
@global_deps_for ||= T.let({}, T.nilable(T::Hash[String, T::Array[String]]))
# Always strip out glibc and gcc from all parts of dependency tree when
# we're calculating their dependency trees. Other parts of Homebrew will
# catch any circular dependencies.
@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]) }
def global_deps_for(name)
@global_deps_for ||= {}
# Always strip out glibc and gcc from all parts of dependency tree when
# we're calculating their dependency trees. Other parts of Homebrew will
# catch any circular dependencies.
@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
[]
# Use class variables to avoid this expensive logic needing to be done more
# than once.
# rubocop:disable Style/ClassVars
@@global_dep_tree = T.let({}, T::Hash[String, T::Set[String]])
@@building_global_dep_tree = T.let(false, T::Boolean)
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
# 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
DependencyCollector.prepend(OS::Linux::DependencyCollector)

View File

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

View File

@ -7,168 +7,174 @@ require "hardware"
require "os/linux/glibc"
require "os/linux/kernel"
module Homebrew
module Diagnostic
class Checks
undef fatal_preinstall_checks, supported_configuration_checks
module OS
module Linux
module Diagnostic
module Checks
extend T::Helpers
def fatal_preinstall_checks
%w[
check_access_directories
check_linuxbrew_core
check_linuxbrew_bottle_domain
].freeze
end
requires_ancestor { Homebrew::Diagnostic::Checks }
def supported_configuration_checks
%w[
check_glibc_minimum_version
check_kernel_minimum_version
check_supported_architecture
].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
def fatal_preinstall_checks
%w[
check_access_directories
check_linuxbrew_core
check_linuxbrew_bottle_domain
].freeze
end
return if gcc_dependents.empty?
badly_linked = gcc_dependents.select do |dependent|
keg = Keg.new(dependent.prefix)
keg.binary_executable_or_library_files.any? do |binary|
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$}) }
def supported_configuration_checks
%w[
check_glibc_minimum_version
check_kernel_minimum_version
check_supported_architecture
].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
return if badly_linked.empty?
return if gcc_dependents.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
badly_linked = gcc_dependents.select do |dependent|
keg = Keg.new(dependent.prefix)
keg.binary_executable_or_library_files.any? do |binary|
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
Homebrew::Diagnostic::Checks.prepend(OS::Linux::Diagnostic::Checks)

View File

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

View File

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

View File

@ -8,7 +8,7 @@ module OS
def use_system_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

View File

@ -1,23 +1,26 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true
class DependencyCollector
undef git_dep_if_needed, subversion_dep_if_needed, cvs_dep_if_needed,
xz_dep_if_needed, unzip_dep_if_needed, bzip2_dep_if_needed
module OS
module Mac
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)
Dependency.new("subversion", [*tags, :implicit])
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
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
DependencyCollector.prepend(OS::Mac::DependencyCollector)

View File

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

View File

@ -1,467 +1,471 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true
module Homebrew
module Diagnostic
class Volumes
def initialize
@volumes = get_mounts
end
module OS
module Mac
module Diagnostic
class Volumes
def initialize
@volumes = get_mounts
end
def which(path)
vols = get_mounts path
def which(path)
vols = get_mounts path
# no volume found
return -1 if vols.empty?
# no volume found
return -1 if vols.empty?
vol_index = @volumes.index(vols[0])
# volume not found in volume list
return -1 if vol_index.nil?
vol_index = @volumes.index(vols[0])
# volume not found in volume list
return -1 if vol_index.nil?
vol_index
end
vol_index
end
def get_mounts(path = nil)
vols = []
# get the volume of path, if path is nil returns all volumes
def get_mounts(path = nil)
vols = []
# get the volume of path, if path is nil returns all volumes
args = %w[/bin/df -P]
args << path if path
args = %w[/bin/df -P]
args << path if path
Utils.popen_read(*args) do |io|
io.each_line do |line|
case line.chomp
# regex matches: /dev/disk0s2 489562928 440803616 48247312 91% /
when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/
vols << Regexp.last_match(1)
Utils.popen_read(*args) do |io|
io.each_line do |line|
case line.chomp
# regex matches: /dev/disk0s2 489562928 440803616 48247312 91% /
when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/
vols << Regexp.last_match(1)
end
end
end
vols
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
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
module Checks
extend T::Helpers
def fatal_setup_build_environment_checks
%w[
check_xcode_minimum_version
check_clt_minimum_version
check_if_supported_sdk_available
].freeze
end
requires_ancestor { Homebrew::Diagnostic::Checks }
def supported_configuration_checks
%w[
check_for_unsupported_macos
].freeze
end
def fatal_preinstall_checks
checks = %w[
check_access_directories
]
def build_from_source_checks
%w[
check_for_installed_developer_tools
check_xcode_up_to_date
check_clt_up_to_date
].freeze
end
# We need the developer tools for `codesign`.
checks << "check_for_installed_developer_tools" if ::Hardware::CPU.arm?
def check_for_non_prefixed_findutils
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"
checks.freeze
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
You are using macOS #{MacOS.version}.
#{who} do not provide support for this #{what}.
#{please_create_pull_requests(what)}
EOS
end
def fatal_setup_build_environment_checks
%w[
check_xcode_minimum_version
check_clt_minimum_version
check_if_supported_sdk_available
].freeze
end
def check_xcode_up_to_date
return unless MacOS::Xcode.outdated?
def supported_configuration_checks
%w[
check_for_unsupported_macos
].freeze
end
# avoid duplicate very similar messages
return if MacOS::Xcode.below_minimum_version?
def build_from_source_checks
%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
# `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?
def check_for_non_prefixed_findutils
findutils = ::Formula["findutils"]
return unless findutils.any_version_installed?
# With fake El Capitan for Portable Ruby, we are intentionally not using Xcode 8.
# This is because we are not using the CLT and Xcode 8 has the 10.12 SDK.
return if ENV["HOMEBREW_FAKE_MACOS"]
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)
message = <<~EOS
Your Xcode (#{MacOS::Xcode.version}) is outdated.
Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it).
#{MacOS::Xcode.update_instructions}
EOS
<<~EOS
Putting non-prefixed findutils in your path can cause python builds to fail.
EOS
rescue FormulaUnavailableError
nil
end
if OS::Mac.version.prerelease?
current_path = Utils.popen_read("/usr/bin/xcode-select", "-p")
message += <<~EOS
If #{MacOS::Xcode.latest_version} is installed, you may need to:
sudo xcode-select --switch /Applications/Xcode.app
Current developer directory is:
#{current_path}
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
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
end
message
end
def check_clt_up_to_date
return unless MacOS::CLT.outdated?
def check_xcode_up_to_date
return unless MacOS::Xcode.outdated?
# avoid duplicate very similar messages
return if MacOS::CLT.below_minimum_version?
# avoid duplicate very similar messages
return if MacOS::Xcode.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?
# 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
end
# With fake El Capitan for Portable Ruby, we are intentionally not using Xcode 8.
# This is because we are not using the CLT and Xcode 8 has the 10.12 SDK.
return if ENV["HOMEBREW_FAKE_MACOS"]
def check_xcode_minimum_version
return unless MacOS::Xcode.below_minimum_version?
message = <<~EOS
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
xcode += " => #{MacOS::Xcode.prefix}" unless MacOS::Xcode.default_prefix?
<<~EOS
Your Xcode (#{xcode}) at #{MacOS::Xcode.bundle_path} is too outdated.
Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it).
#{MacOS::Xcode.update_instructions}
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.
if OS::Mac.version.prerelease?
current_path = Utils.popen_read("/usr/bin/xcode-select", "-p")
message += <<~EOS
If #{MacOS::Xcode.latest_version} is installed, you may need to:
sudo xcode-select --switch /Applications/Xcode.app
Current developer directory is:
#{current_path}
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.
message
end
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
end
end
def check_for_multiple_volumes
return unless HOMEBREW_CELLAR.exist?
def check_xcode_minimum_version
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
real_cellar = HOMEBREW_CELLAR.realpath
where_cellar = volumes.which real_cellar
<<~EOS
Your Xcode (#{xcode}) at #{MacOS::Xcode.bundle_path} is too outdated.
Please update to Xcode #{MacOS::Xcode.latest_version} (or delete it).
#{MacOS::Xcode.update_instructions}
EOS
end
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
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
rescue
return
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
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
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.
# Our gettext formula will be caught by check_linked_keg_only_brews
gettext = begin
Formulary.factory("gettext")
rescue
nil
end
You should set the "HOMEBREW_TEMP" environment variable to a suitable
directory on the same volume as your Cellar.
EOS
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
def check_deprecated_caskroom_taps
tapped_caskroom_taps = Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" }
.map(&:name)
return if tapped_caskroom_taps.empty?
return if @found.all? do |path|
realpath = Pathname.new(path).realpath.to_s
allowlist.any? { |rack| realpath.start_with?(rack) }
end
end
<<~EOS
You have the following deprecated, cask taps tapped:
#{tapped_caskroom_taps.join("\n ")}
Untap them with `brew untap`.
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"
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
<<~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
def check_for_iconv
find_relative_paths("lib/libiconv.dylib", "include/iconv.h")
return if @found.empty?
# 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
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
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|
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
tl;dr: delete these files:
EOS
end
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
def check_for_multiple_volumes
return unless HOMEBREW_CELLAR.exist?
volumes = Volumes.new
# Find the volumes for the TMP folder & HOMEBREW_CELLAR
real_cellar = HOMEBREW_CELLAR.realpath
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
<<~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.
def check_deprecated_caskroom_taps
tapped_caskroom_taps = Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" }
.map(&:name)
return if tapped_caskroom_taps.empty?
Remove the broken installation before reinstalling:
sudo rm -rf #{path_to_remove}
<<~EOS
You have the following deprecated, cask taps tapped:
#{tapped_caskroom_taps.join("\n ")}
Untap them with `brew untap`.
EOS
end
#{installation_instructions}
EOS
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
<<~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
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.
# 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
DevelopmentTools.ld64_version >= 711
OS::Mac::DevelopmentTools.ld64_version >= 711
end
end

View File

@ -1,117 +1,125 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true
module Stdenv
undef homebrew_extra_pkg_config_paths
module OS
module Mac
module Stdenv
extend T::Helpers
sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
requires_ancestor { ::Stdenv }
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:)
sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
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.
delete("LC_ALL")
self["LC_CTYPE"] = "C"
append "LDFLAGS", "-Wl,-headerpad_max_install_names"
# Add `lib` and `include` etc. from the current `macosxsdk` to compiler flags:
macosxsdk(formula: @formula, testing_formula:)
# `sed` is strict and errors out when it encounters files with mixed character sets.
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"
append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
end
return unless MacOS::Xcode.without_clt?
def remove_macosxsdk(version = nil)
# 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"
append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin"
append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
end
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version)
return unless sdk
def remove_macosxsdk(version = nil)
# 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")
remove_from_cflags "-isysroot#{sdk}"
remove "CPPFLAGS", "-isysroot#{sdk}"
remove "LDFLAGS", "-isysroot#{sdk}"
if HOMEBREW_PREFIX.to_s == "/usr/local"
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
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version)
return unless sdk
delete("SDKROOT")
remove_from_cflags "-isysroot#{sdk}"
remove "CPPFLAGS", "-isysroot#{sdk}"
remove "LDFLAGS", "-isysroot#{sdk}"
if HOMEBREW_PREFIX.to_s == "/usr/local"
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
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
Stdenv.prepend(OS::Mac::Stdenv)

View File

@ -1,169 +1,175 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true
module Superenv
class << self
# The location of Homebrew's shims on macOS.
def shims_path
HOMEBREW_SHIMS_PATH/"mac/super"
end
module OS
module Mac
module Superenv
extend T::Helpers
undef bin
requires_ancestor { ::Superenv }
def bin
return unless DevelopmentTools.installed?
module ClassMethods
# The location of Homebrew's shims on macOS.
def shims_path
HOMEBREW_SHIMS_PATH/"mac/super"
end
shims_path.realpath
end
end
def bin
return unless ::DevelopmentTools.installed?
undef homebrew_extra_pkg_config_paths,
homebrew_extra_isystem_paths, homebrew_extra_library_paths,
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"
shims_path.realpath
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
ENV["ac_have_clock_syscall"] = "no"
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
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
# 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
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 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|
next true unless file.dylib?

View File

@ -10,7 +10,7 @@ module OS
sig { params(formula: Formula).returns(T.nilable(T::Boolean)) }
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?)
end
end

View File

@ -3,175 +3,179 @@
require "macho"
module Hardware
class CPU
class << self
undef type, family, features, sse4?
module OS
module Mac
module Hardware
module CPU
extend T::Helpers
# These methods use info spewed out by sysctl.
# Look in <mach/machine.h> for decoding info.
def type
case sysctl_int("hw.cputype")
when MachO::Headers::CPU_TYPE_I386
:intel
when MachO::Headers::CPU_TYPE_ARM64
:arm
else
:dunno
# These methods use info spewed out by sysctl.
# Look in <mach/machine.h> for decoding info.
def type
case sysctl_int("hw.cputype")
when MachO::Headers::CPU_TYPE_I386
:intel
when MachO::Headers::CPU_TYPE_ARM64
:arm
else
:dunno
end
end
end
def family
if arm?
arm_family
elsif intel?
intel_family
else
:dunno
def family
if ::Hardware::CPU.arm?
arm_family
elsif ::Hardware::CPU.intel?
intel_family
else
:dunno
end
end
end
# 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
# conflict between what `uname` reports and the underlying `sysctl` flags,
# since the `sysctl` flags don't change behaviour under Rosetta 2.
def in_rosetta2?
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
# 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
# conflict between what `uname` reports and the underlying `sysctl` flags,
# since the `sysctl` flags don't change behaviour under Rosetta 2.
def in_rosetta2?
sysctl_bool("sysctl.proc_translated")
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
def features
@features ||= sysctl_n(
"machdep.cpu.features",
"machdep.cpu.extfeatures",
"machdep.cpu.leaf7_features",
).split.map { |s| s.downcase.to_sym }
end
end
def sysctl_bool(key)
sysctl_int(key) == 1
end
def sse4?
sysctl_bool("hw.optional.sse4_1")
end
def sysctl_int(key)
sysctl_n(key).to_i & 0xffffffff
end
def extmodel
sysctl_int("machdep.cpu.extmodel")
end
def sysctl_n(*keys)
(@properties ||= {}).fetch(keys) do
@properties[keys] = Utils.popen_read("/usr/sbin/sysctl", "-n", *keys)
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
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
Hardware::CPU.singleton_class.prepend(OS::Mac::Hardware::CPU)

View File

@ -23,101 +23,101 @@ class Keg
GENERIC_MUST_BE_WRITABLE_DIRECTORIES +
[HOMEBREW_PREFIX/"Frameworks"]
).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
mach_o_files
end
def codesign_patched_binary(file)
return if MacOS.version < :big_sur
def codesign_patched_binary(file)
return if MacOS.version < :big_sur
unless ::Hardware::CPU.arm?
result = system_command("codesign", args: ["--verify", file], print_stderr: false)
return unless result.stderr.match?(/invalid signature/i)
end
unless Hardware::CPU.arm?
result = system_command("codesign", args: ["--verify", file], print_stderr: false)
return unless result.stderr.match?(/invalid signature/i)
end
odebug "Codesigning #{file}"
prepare_codesign_writable_files(file) do
# Use quiet_system to squash notifications about resigning binaries
# which already have valid signatures.
return if quiet_system("codesign", "--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file)
odebug "Codesigning #{file}"
prepare_codesign_writable_files(file) do
# Use quiet_system to squash notifications about resigning binaries
# which already have valid signatures.
return if quiet_system("codesign", "--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file)
# If the codesigning fails, it may be a bug in Apple's codesign utility
# A known workaround is to copy the file to another inode, then move it back
# erasing the previous file. Then sign again.
#
# TODO: remove this once the bug in Apple's codesign utility is fixed
Dir::Tmpname.create("workaround") do |tmppath|
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
# A known workaround is to copy the file to another inode, then move it back
# erasing the previous file. Then sign again.
#
# TODO: remove this once the bug in Apple's codesign utility is fixed
Dir::Tmpname.create("workaround") do |tmppath|
FileUtils.cp file, tmppath
FileUtils.mv tmppath, file, force: true
# Try signing again
odebug "Codesigning (2nd try) #{file}"
result = system_command("codesign", args: [
"--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file
], print_stderr: false)
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
# Try signing again
odebug "Codesigning (2nd try) #{file}"
result = system_command("codesign", args: [
"--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file
], print_stderr: false)
return if result.success?
def prepare_codesign_writable_files(file)
result = system_command("codesign", args: [
"--display", "--file-list", "-", file
], print_stderr: false)
return unless result.success?
# If it fails again, error out
onoe <<~EOS
Failed applying an ad-hoc signature to #{file}:
#{result.stderr}
EOS
end
end
def prepare_codesign_writable_files(file)
result = system_command("codesign", args: [
"--display", "--file-list", "-", file
], print_stderr: false)
return unless result.success?
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
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
yield
ensure
saved_perms&.each do |f, p|
f.chmod p if p
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
binary_executable_or_library_files.each do |file|
odebug "Extracting symbols #{file}"
result = system_command("dsymutil", args: [file], print_stderr: false)
next if result.success?
result = system_command("dsymutil", args: [file], print_stderr: false)
next if result.success?
# If it fails again, error out
ofail <<~EOS
Failed to extract symbols from #{file}:
#{result.stderr}
EOS
end
end
# If it fails again, error out
ofail <<~EOS
Failed to extract symbols from #{file}:
#{result.stderr}
EOS
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?
# 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
Keg.prepend(OS::Mac::Keg)

View File

@ -1,255 +1,262 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true
class Keg
class << self
undef file_linked_libraries
module OS
module Mac
module Keg
extend T::Helpers
def file_linked_libraries(file, string)
# 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
requires_ancestor { ::Keg }
undef relocate_dynamic_linkage
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
module ClassMethods
def file_linked_libraries(file, string)
# 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
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
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
generic_fix_dynamic_linkage
end
modified = change_rpath(bad_name, loader_name, file)
needs_codesigning ||= modified
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? ::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
# 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
mach_o_files
end
modified = delete_rpath(bad_name, file)
needs_codesigning ||= modified
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, ::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
# codesign the file if needed
codesign_patched_binary(file) if needs_codesigning
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
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
Keg.singleton_class.prepend(OS::Mac::Keg::ClassMethods)
Keg.prepend(OS::Mac::Keg)

View File

@ -9,7 +9,7 @@ module OS
requires_ancestor { Kernel }
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
current_macos_version = if os_name.is_a?(Symbol)

View File

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

View File

@ -3,20 +3,26 @@
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
class << self
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
@xcode ||= if MacOS::Xcode.installed?
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
"-lr"
end
alias generic_recursive_fgrep_args recursive_fgrep_args
def egrep_args
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
sdk_prefix = "#{Xcode.prefix}/Platforms/MacOSX.platform/Developer/SDKs"
# 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

View File

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

View File

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