Merge pull request #20152 from Homebrew/extend-os-class-methods

Fix handling of class methods in `extend/os`
This commit is contained in:
Rylan Polster 2025-06-22 02:20:18 +00:00 committed by GitHub
commit 44dcb58f9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 305 additions and 248 deletions

View File

@ -57,6 +57,11 @@ class DevelopmentTools
:clang :clang
end end
sig { returns(Version) }
def ld64_version
Version::NULL
end
# Get the Clang version. # Get the Clang version.
# #
# @api public # @api public

View File

@ -5,18 +5,20 @@ module OS
module Linux module Linux
module Cask module Cask
module Quarantine module Quarantine
extend T::Helpers module ClassMethods
extend T::Helpers
requires_ancestor { ::Cask::Quarantine } requires_ancestor { ::Cask::Quarantine }
sig { returns(Symbol) } sig { returns(Symbol) }
def self.check_quarantine_support = :linux def check_quarantine_support = :linux
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def self.available? = false def available? = false
end
end end
end end
end end
end end
Cask::Quarantine.prepend(OS::Linux::Cask::Quarantine) Cask::Quarantine.singleton_class.prepend(OS::Linux::Cask::Quarantine::ClassMethods)

View File

@ -1,9 +1,21 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
class CompilerSelector module OS
sig { returns(String) } module Linux
def self.preferred_gcc module CompilerSelector
OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA module ClassMethods
extend T::Helpers
requires_ancestor { T.class_of(::CompilerSelector) }
sig { returns(String) }
def preferred_gcc
OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA
end
end
end
end end
end end
CompilerSelector.singleton_class.prepend(OS::Linux::CompilerSelector::ClassMethods)

View File

@ -4,69 +4,71 @@
module OS module OS
module Linux module Linux
module DevelopmentTools module DevelopmentTools
extend T::Helpers module ClassMethods
extend T::Helpers
requires_ancestor { ::DevelopmentTools } requires_ancestor { ::DevelopmentTools }
sig { params(tool: T.any(String, Symbol)).returns(T.nilable(Pathname)) } sig { params(tool: T.any(String, Symbol)).returns(T.nilable(Pathname)) }
def locate(tool) def locate(tool)
@locate ||= T.let({}, T.nilable(T::Hash[T.any(String, Symbol), Pathname])) @locate ||= T.let({}, T.nilable(T::Hash[T.any(String, Symbol), Pathname]))
@locate.fetch(tool) do |key| @locate.fetch(tool) do |key|
@locate[key] = if ::DevelopmentTools.needs_build_formulae? && @locate[key] = if ::DevelopmentTools.needs_build_formulae? &&
(binutils_path = HOMEBREW_PREFIX/"opt/binutils/bin/#{tool}").executable? (binutils_path = HOMEBREW_PREFIX/"opt/binutils/bin/#{tool}").executable?
binutils_path binutils_path
elsif ::DevelopmentTools.needs_build_formulae? && elsif ::DevelopmentTools.needs_build_formulae? &&
(glibc_path = HOMEBREW_PREFIX/"opt/glibc/bin/#{tool}").executable? (glibc_path = HOMEBREW_PREFIX/"opt/glibc/bin/#{tool}").executable?
glibc_path glibc_path
elsif (homebrew_path = HOMEBREW_PREFIX/"bin/#{tool}").executable? elsif (homebrew_path = HOMEBREW_PREFIX/"bin/#{tool}").executable?
homebrew_path homebrew_path
elsif File.executable?(system_path = "/usr/bin/#{tool}") elsif File.executable?(system_path = "/usr/bin/#{tool}")
Pathname.new system_path Pathname.new system_path
end
end end
end end
end
sig { returns(Symbol) } sig { returns(Symbol) }
def default_compiler = :gcc def default_compiler = :gcc
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def needs_libc_formula? def needs_libc_formula?
return @needs_libc_formula unless @needs_libc_formula.nil? return @needs_libc_formula unless @needs_libc_formula.nil?
@needs_libc_formula = T.let(OS::Linux::Glibc.below_ci_version?, T.nilable(T::Boolean)) @needs_libc_formula = T.let(OS::Linux::Glibc.below_ci_version?, T.nilable(T::Boolean))
@needs_libc_formula = !!@needs_libc_formula @needs_libc_formula = !!@needs_libc_formula
end
# Keep this method around for now to make it easier to add this functionality later.
# rubocop:disable Lint/UselessMethodDefinition
sig { returns(Pathname) }
def host_gcc_path
# TODO: override this if/when we to pick the GCC based on e.g. the Ubuntu version.
super
end
# rubocop:enable Lint/UselessMethodDefinition
sig { returns(T::Boolean) }
def needs_compiler_formula?
return @needs_compiler_formula unless @needs_compiler_formula.nil?
@needs_compiler_formula = T.let(nil, T.nilable(T::Boolean))
@needs_compiler_formula = if host_gcc_path.exist?
::DevelopmentTools.gcc_version(host_gcc_path.to_s) < OS::LINUX_GCC_CI_VERSION
else
true
end end
end
sig { returns(T::Hash[String, T.nilable(String)]) } # Keep this method around for now to make it easier to add this functionality later.
def build_system_info # rubocop:disable Lint/UselessMethodDefinition
super.merge({ sig { returns(Pathname) }
"glibc_version" => OS::Linux::Glibc.version.to_s.presence, def host_gcc_path
"oldest_cpu_family" => ::Hardware.oldest_cpu.to_s, # TODO: override this if/when we to pick the GCC based on e.g. the Ubuntu version.
}) super
end
# rubocop:enable Lint/UselessMethodDefinition
sig { returns(T::Boolean) }
def needs_compiler_formula?
return @needs_compiler_formula unless @needs_compiler_formula.nil?
@needs_compiler_formula = T.let(nil, T.nilable(T::Boolean))
@needs_compiler_formula = if host_gcc_path.exist?
::DevelopmentTools.gcc_version(host_gcc_path.to_s) < OS::LINUX_GCC_CI_VERSION
else
true
end
end
sig { returns(T::Hash[String, T.nilable(String)]) }
def build_system_info
super.merge({
"glibc_version" => OS::Linux::Glibc.version.to_s.presence,
"oldest_cpu_family" => ::Hardware.oldest_cpu.to_s,
})
end
end end
end end
end end
end end
DevelopmentTools.singleton_class.prepend(OS::Linux::DevelopmentTools) DevelopmentTools.singleton_class.prepend(OS::Linux::DevelopmentTools::ClassMethods)

View File

@ -4,25 +4,27 @@
module OS module OS
module Linux module Linux
module SimulateSystem module SimulateSystem
sig { returns(T.nilable(Symbol)) } module ClassMethods
def os sig { returns(T.nilable(Symbol)) }
@os ||= T.let(nil, T.nilable(Symbol)) def os
return :macos if @os.blank? && Homebrew::EnvConfig.simulate_macos_on_linux? @os ||= T.let(nil, T.nilable(Symbol))
return :macos if @os.blank? && Homebrew::EnvConfig.simulate_macos_on_linux?
@os @os
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def simulating_or_running_on_linux? def simulating_or_running_on_linux?
os.blank? || os == :linux os.blank? || os == :linux
end end
sig { returns(Symbol) } sig { returns(Symbol) }
def current_os def current_os
os || :linux os || :linux
end
end end
end end
end end
end end
Homebrew::SimulateSystem.singleton_class.prepend(OS::Linux::SimulateSystem) Homebrew::SimulateSystem.singleton_class.prepend(OS::Linux::SimulateSystem::ClassMethods)

View File

@ -51,7 +51,7 @@ module OS
out.puts "Host glibc: #{host_glibc_version}" out.puts "Host glibc: #{host_glibc_version}"
out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}" out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}"
out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH
["glibc", CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f| ["glibc", ::CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f|
out.puts "#{f}: #{formula_linked_version(f)}" out.puts "#{f}: #{formula_linked_version(f)}"
end end
end end

View File

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

View File

@ -145,7 +145,7 @@ module OS
return return
end end
oclp_support_tier = Hardware::CPU.features.include?(:pclmulqdq) ? 2 : 3 oclp_support_tier = ::Hardware::CPU.features.include?(:pclmulqdq) ? 2 : 3
<<~EOS <<~EOS
You have booted macOS using OpenCore Legacy Patcher. You have booted macOS using OpenCore Legacy Patcher.

View File

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

View File

@ -149,11 +149,11 @@ module OS
no_fixup_chains no_fixup_chains
# Strip build prefixes from linker where supported, for deterministic builds. # Strip build prefixes from linker where supported, for deterministic builds.
append_to_cccfg "o" if OS::Mac::DevelopmentTools.ld64_version >= 512 append_to_cccfg "o" if ::DevelopmentTools.ld64_version >= 512
# Pass `-ld_classic` whenever the linker is invoked with `-dead_strip_dylibs` # Pass `-ld_classic` whenever the linker is invoked with `-dead_strip_dylibs`
# on `ld` versions that don't properly handle that option. # on `ld` versions that don't properly handle that option.
return unless OS::Mac::DevelopmentTools.ld64_version.between?("1015.7", "1022.1") return unless ::DevelopmentTools.ld64_version.between?("1015.7", "1022.1")
append_to_cccfg "c" append_to_cccfg "c"
end end

View File

@ -7,56 +7,58 @@ module OS
module Mac module Mac
module Hardware module Hardware
module CPU module CPU
extend T::Helpers module ClassMethods
extend T::Helpers
# These methods use info spewed out by sysctl. # These methods use info spewed out by sysctl.
# Look in <mach/machine.h> for decoding info. # Look in <mach/machine.h> for decoding info.
def type def type
case ::Hardware::CPU.sysctl_int("hw.cputype") case ::Hardware::CPU.sysctl_int("hw.cputype")
when MachO::Headers::CPU_TYPE_I386 when MachO::Headers::CPU_TYPE_I386
:intel :intel
when MachO::Headers::CPU_TYPE_ARM64 when MachO::Headers::CPU_TYPE_ARM64
:arm :arm
else else
:dunno :dunno
end
end end
end
def family def family
if ::Hardware::CPU.arm? if ::Hardware::CPU.arm?
::Hardware::CPU.arm_family ::Hardware::CPU.arm_family
elsif ::Hardware::CPU.intel? elsif ::Hardware::CPU.intel?
::Hardware::CPU.intel_family ::Hardware::CPU.intel_family
else else
:dunno :dunno
end
end end
end
# True when running under an Intel-based shell via Rosetta 2 on an # True when running under an Intel-based shell via Rosetta 2 on an
# Apple Silicon Mac. This can be detected via seeing if there's a # Apple Silicon Mac. This can be detected via seeing if there's a
# conflict between what `uname` reports and the underlying `sysctl` flags, # conflict between what `uname` reports and the underlying `sysctl` flags,
# since the `sysctl` flags don't change behaviour under Rosetta 2. # since the `sysctl` flags don't change behaviour under Rosetta 2.
def in_rosetta2? def in_rosetta2?
::Hardware::CPU.sysctl_bool("sysctl.proc_translated") ::Hardware::CPU.sysctl_bool("sysctl.proc_translated")
end end
def self.features def features
@features ||= ::Hardware::CPU.sysctl_n( @features ||= ::Hardware::CPU.sysctl_n(
"machdep.cpu.features", "machdep.cpu.features",
"machdep.cpu.extfeatures", "machdep.cpu.extfeatures",
"machdep.cpu.leaf7_features", "machdep.cpu.leaf7_features",
).split.map { |s| s.downcase.to_sym } ).split.map { |s| s.downcase.to_sym }
end end
def sse4? def sse4?
::Hardware::CPU.sysctl_bool("hw.optional.sse4_1") ::Hardware::CPU.sysctl_bool("hw.optional.sse4_1")
end
end end
end end
end end
end end
end end
Hardware::CPU.singleton_class.prepend(OS::Mac::Hardware::CPU) Hardware::CPU.singleton_class.prepend(OS::Mac::Hardware::CPU::ClassMethods)
module Hardware module Hardware
class CPU class CPU

View File

@ -1,13 +1,26 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Language module OS
module Java module Mac
def self.java_home(version = nil) module Language
openjdk = find_openjdk_formula(version) module Java
return unless openjdk module ClassMethods
extend T::Helpers
openjdk.opt_libexec/"openjdk.jdk/Contents/Home" requires_ancestor { T.class_of(::Language::Java) }
sig { params(version: T.nilable(String)).returns(T.nilable(Pathname)) }
def java_home(version = nil)
openjdk = find_openjdk_formula(version)
return unless openjdk
openjdk.opt_libexec/"openjdk.jdk/Contents/Home"
end
end
end
end end
end end
end end
Language::Java.singleton_class.prepend(OS::Mac::Language::Java::ClassMethods)

View File

@ -4,44 +4,46 @@
module OS module OS
module Mac module Mac
module Readall module Readall
extend T::Helpers module ClassMethods
extend T::Helpers
requires_ancestor { Kernel } requires_ancestor { Kernel }
sig { params(tap: ::Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) } sig { params(tap: ::Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) }
def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type) def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type)
return true if os_name == :linux return true if os_name == :linux
current_macos_version = if os_name.is_a?(Symbol) current_macos_version = if os_name.is_a?(Symbol)
MacOSVersion.from_symbol(os_name) MacOSVersion.from_symbol(os_name)
else else
MacOS.version MacOS.version
end
success = T.let(true, T::Boolean)
tap.cask_files.each do |file|
cask = ::Cask::CaskLoader.load(file)
# Fine to have missing URLs for unsupported macOS
macos_req = cask.depends_on.macos
next if macos_req&.version && Array(macos_req.version).none? do |macos_version|
current_macos_version.compare(macos_req.comparator, macos_version)
end end
raise "Missing URL" if cask.url.nil? success = T.let(true, T::Boolean)
rescue Interrupt tap.cask_files.each do |file|
raise cask = ::Cask::CaskLoader.load(file)
# Handle all possible exceptions reading Casks.
rescue Exception => e # rubocop:disable Lint/RescueException # Fine to have missing URLs for unsupported macOS
os_and_arch = "macOS #{current_macos_version} on #{arch}" macos_req = cask.depends_on.macos
onoe "Invalid cask (#{os_and_arch}): #{file}" next if macos_req&.version && Array(macos_req.version).none? do |macos_version|
$stderr.puts e current_macos_version.compare(macos_req.comparator, macos_version)
success = false end
raise "Missing URL" if cask.url.nil?
rescue Interrupt
raise
# Handle all possible exceptions reading Casks.
rescue Exception => e # rubocop:disable Lint/RescueException
os_and_arch = "macOS #{current_macos_version} on #{arch}"
onoe "Invalid cask (#{os_and_arch}): #{file}"
$stderr.puts e
success = false
end
success
end end
success
end end
end end
end end
end end
Readall.singleton_class.prepend(OS::Mac::Readall) Readall.singleton_class.prepend(OS::Mac::Readall::ClassMethods)

View File

@ -1,9 +1,21 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
class Sandbox module OS
sig { returns(T::Boolean) } module Mac
def self.available? module Sandbox
File.executable?(SANDBOX_EXEC) module ClassMethods
extend T::Helpers
requires_ancestor { T.class_of(::Sandbox) }
sig { returns(T::Boolean) }
def available?
File.executable?(::Sandbox::SANDBOX_EXEC)
end
end
end
end end
end end
Sandbox.singleton_class.prepend(OS::Mac::Sandbox::ClassMethods)

View File

@ -4,19 +4,21 @@
module OS module OS
module Mac module Mac
module SimulateSystem module SimulateSystem
sig { returns(T::Boolean) } module ClassMethods
def simulating_or_running_on_macos? sig { returns(T::Boolean) }
return true if Homebrew::SimulateSystem.os.blank? def simulating_or_running_on_macos?
return true if Homebrew::SimulateSystem.os.blank?
[:macos, *MacOSVersion::SYMBOLS.keys].include?(Homebrew::SimulateSystem.os) [:macos, *MacOSVersion::SYMBOLS.keys].include?(Homebrew::SimulateSystem.os)
end end
sig { returns(Symbol) } sig { returns(Symbol) }
def current_os def current_os
::Homebrew::SimulateSystem.os || MacOS.version.to_sym ::Homebrew::SimulateSystem.os || MacOS.version.to_sym
end
end end
end end
end end
end end
Homebrew::SimulateSystem.singleton_class.prepend(OS::Mac::SimulateSystem) Homebrew::SimulateSystem.singleton_class.prepend(OS::Mac::SimulateSystem::ClassMethods)

View File

@ -7,24 +7,26 @@ module OS
module Mac module Mac
# Wrapper around UNIXSocket to allow > 104 characters on macOS. # Wrapper around UNIXSocket to allow > 104 characters on macOS.
module UNIXSocketExt module UNIXSocketExt
extend T::Helpers module ClassMethods
extend T::Helpers
requires_ancestor { Kernel } requires_ancestor { Kernel }
sig { params(path: String).returns(String) } sig { params(path: String).returns(String) }
def sockaddr_un(path) def sockaddr_un(path)
if path.bytesize > 252 # largest size that can fit into a single-byte length if path.bytesize > 252 # largest size that can fit into a single-byte length
raise ArgumentError, "too long unix socket path (#{path.bytesize} bytes given but 252 bytes max)" raise ArgumentError, "too long unix socket path (#{path.bytesize} bytes given but 252 bytes max)"
end
[
path.bytesize + 3, # = length (1 byte) + family (1 byte) + path (variable) + null terminator (1 byte)
1, # AF_UNIX
path,
].pack("CCZ*")
end end
[
path.bytesize + 3, # = length (1 byte) + family (1 byte) + path (variable) + null terminator (1 byte)
1, # AF_UNIX
path,
].pack("CCZ*")
end end
end end
end end
end end
Utils::UNIXSocketExt.singleton_class.prepend(OS::Mac::UNIXSocketExt) Utils::UNIXSocketExt.singleton_class.prepend(OS::Mac::UNIXSocketExt::ClassMethods)

View File

@ -10,7 +10,6 @@ require "utils/fork"
# Helper class for running a sub-process inside of a sandboxed environment. # Helper class for running a sub-process inside of a sandboxed environment.
class Sandbox class Sandbox
SANDBOX_EXEC = "/usr/bin/sandbox-exec" SANDBOX_EXEC = "/usr/bin/sandbox-exec"
private_constant :SANDBOX_EXEC
# This is defined in the macOS SDK but Ruby unfortunately does not expose it. # This is defined in the macOS SDK but Ruby unfortunately does not expose it.
# This value can be found by compiling a C program that prints TIOCSCTTY. # This value can be found by compiling a C program that prints TIOCSCTTY.