Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions Library/Homebrew/dependency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ def to_formula(prefer_stub: false)
end

sig {
params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer),
minimum_compatibility_version: T.nilable(Integer)).returns(T::Boolean)
params(
minimum_version: T.nilable(Version),
minimum_revision: T.nilable(Integer),
minimum_compatibility_version: T.nilable(Integer),
bottle_os_version: T.nilable(String),
).returns(T::Boolean)
}
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil)
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil,
bottle_os_version: nil)
formula = begin
to_formula(prefer_stub: true)
rescue FormulaUnavailableError
Expand Down Expand Up @@ -95,8 +100,8 @@ def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibilit
end

def satisfied?(inherited_options = [], minimum_version: nil, minimum_revision: nil,
minimum_compatibility_version: nil)
installed?(minimum_version:, minimum_revision:, minimum_compatibility_version:) &&
minimum_compatibility_version: nil, bottle_os_version: nil)
installed?(minimum_version:, minimum_revision:, minimum_compatibility_version:, bottle_os_version:) &&
missing_options(inherited_options).empty?
end

Expand Down Expand Up @@ -277,25 +282,39 @@ def hash
end

sig {
params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer),
minimum_compatibility_version: T.nilable(Integer)).returns(T::Boolean)
params(
minimum_version: T.nilable(Version),
minimum_revision: T.nilable(Integer),
minimum_compatibility_version: T.nilable(Integer),
bottle_os_version: T.nilable(String),
).returns(T::Boolean)
}
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil)
use_macos_install? || super
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil,
bottle_os_version: nil)
use_macos_install?(bottle_os_version:) || super
end

sig { returns(T::Boolean) }
def use_macos_install?
sig { params(bottle_os_version: T.nilable(String)).returns(T::Boolean) }
def use_macos_install?(bottle_os_version: nil)
# Check whether macOS is new enough for dependency to not be required.
if Homebrew::SimulateSystem.simulating_or_running_on_macos?
# Assume the oldest macOS version when simulating a generic macOS version
return true if Homebrew::SimulateSystem.current_os == :macos && !bounds.key?(:since)

if Homebrew::SimulateSystem.current_os != :macos
current_os = MacOSVersion.from_symbol(Homebrew::SimulateSystem.current_os)
since_os = MacOSVersion.from_symbol(bounds[:since]) if bounds.key?(:since)
return true if current_os >= since_os
# If there's no since bound, the dependency is always available from macOS
since_os_bounds = bounds[:since]
return true if since_os_bounds.blank?

# When installing a bottle built on an older macOS version, use that version
# to determine if the dependency should come from macOS or Homebrew
effective_os = if bottle_os_version.present? &&
bottle_os_version.start_with?("macOS ")
# bottle_os_version is a string like "14" for Sonoma, "15" for Sequoia
# Convert it to a MacOS version symbol for comparison
MacOSVersion.new(bottle_os_version.delete_prefix("macOS "))
else
MacOSVersion.from_symbol(Homebrew::SimulateSystem.current_os)
end

since_os = MacOSVersion.from_symbol(since_os_bounds)
return true if effective_os >= since_os
end

false
Expand Down
31 changes: 21 additions & 10 deletions Library/Homebrew/formula_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def initialize(
@poured_bottle = T.let(false, T::Boolean)
@start_time = T.let(nil, T.nilable(Time))
@bottle_tab_runtime_dependencies = T.let({}.freeze, T::Hash[String, T::Hash[String, String]])
@bottle_built_os_version = T.let(nil, T.nilable(String))
@hold_locks = T.let(false, T::Boolean)
@show_summary_heading = T.let(false, T::Boolean)
@etc_var_preinstall = T.let([], T::Array[Pathname])
Expand Down Expand Up @@ -338,12 +339,22 @@ def prelude

Tab.clear_cache

# Setup bottle_tab_runtime_dependencies for compute_dependencies
# Setup bottle_tab_runtime_dependencies for compute_dependencies and
# bottle_built_os_version for dependency resolution.
begin
@bottle_tab_runtime_dependencies = formula.bottle_tab_attributes
.fetch("runtime_dependencies", []).then { |deps| deps || [] }
.each_with_object({}) { |dep, h| h[dep["full_name"]] = dep }
.freeze
bottle_tab_attributes = formula.bottle_tab_attributes
@bottle_tab_runtime_dependencies = bottle_tab_attributes
.fetch("runtime_dependencies", []).then { |deps| deps || [] }
.each_with_object({}) { |dep, h| h[dep["full_name"]] = dep }
.freeze

if (bottle_tag = formula.bottle_for_tag(Utils::Bottles.tag)&.tag) &&
bottle_tag.system != :all
# Extract the OS version the bottle was built on.
# This ensures that when installing older bottles (e.g. Sonoma bottle on Sequoia),
# we resolve dependencies according to the bottle's built OS, not the current OS.
@bottle_built_os_version = bottle_tab_attributes.dig("built_on", "os_version")
end
rescue Resource::BottleManifest::Error
# If we can't get the bottle manifest, assume a full dependencies install.
end
Expand Down Expand Up @@ -772,14 +783,14 @@ def expand_dependencies_for_formula(formula, inherited_options)
keep_build_test ||= dep.build? && !install_bottle_for?(dependent, build) &&
(formula.head? || !dependent.latest_version_installed?)

bottle_runtime_version = @bottle_tab_runtime_dependencies.dig(dep.name, "version").presence
bottle_runtime_version = Version.new(bottle_runtime_version) if bottle_runtime_version
bottle_runtime_revision = @bottle_tab_runtime_dependencies.dig(dep.name, "revision")
minimum_version = @bottle_tab_runtime_dependencies.dig(dep.name, "version").presence
minimum_version = Version.new(minimum_version) if minimum_version
minimum_revision = @bottle_tab_runtime_dependencies.dig(dep.name, "revision")
bottle_os_version = @bottle_built_os_version

if dep.prune_from_option?(build) || ((dep.build? || dep.test?) && !keep_build_test)
Dependency.prune
elsif dep.satisfied?(inherited_options[dep.name], minimum_version: bottle_runtime_version,
minimum_revision: bottle_runtime_revision)
elsif dep.satisfied?(inherited_options[dep.name], minimum_version:, minimum_revision:, bottle_os_version:)
Dependency.skip
end
end
Expand Down
36 changes: 36 additions & 0 deletions Library/Homebrew/test/dependency_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,40 @@
expect(dep).not_to be_test
end
end

describe "Dependency#installed? with bottle_os_version" do
subject(:dependency) { described_class.new("foo") }

it "accepts macOS bottle_os_version parameter" do
expect { dependency.installed?(bottle_os_version: "macOS 14") }.not_to raise_error
end

it "accepts Ubuntu bottle_os_version parameter" do
expect { dependency.installed?(bottle_os_version: "Ubuntu 22.04") }.not_to raise_error
end
end

describe "Dependency#satisfied? with bottle_os_version" do
subject(:dependency) { described_class.new("foo") }

it "accepts bottle_os_version parameter" do
expect { dependency.satisfied?([], bottle_os_version: "macOS 14") }.not_to raise_error
end

it "accepts Ubuntu bottle_os_version parameter" do
expect { dependency.installed?(bottle_os_version: "Ubuntu 22.04") }.not_to raise_error
end
end

describe "UsesFromMacOSDependency#installed? with bottle_os_version" do
subject(:uses_from_macos) { described_class.new("foo", bounds: { since: :sonoma }) }

it "accepts macOS bottle_os_version parameter" do
expect { uses_from_macos.installed?(bottle_os_version: "macOS 14") }.not_to raise_error
end

it "accepts Ubuntu bottle_os_version parameter" do
expect { uses_from_macos.installed?(bottle_os_version: "Ubuntu 22.04") }.not_to raise_error
end
end
end
Loading