Skip to content

Commit 1359c65

Browse files
authored
Merge pull request #20802 from Homebrew/copilot/fix-cb24e1d7-252f-4cc2-ba24-b077f3679160
Resolve dependencies according to bottle/tab when installing older bottles
2 parents 61d4238 + a24ff24 commit 1359c65

File tree

3 files changed

+94
-28
lines changed

3 files changed

+94
-28
lines changed

Library/Homebrew/dependency.rb

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,15 @@ def to_formula
4848
end
4949

5050
sig {
51-
params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer),
52-
minimum_compatibility_version: T.nilable(Integer)).returns(T::Boolean)
51+
params(
52+
minimum_version: T.nilable(Version),
53+
minimum_revision: T.nilable(Integer),
54+
minimum_compatibility_version: T.nilable(Integer),
55+
bottle_os_version: T.nilable(String),
56+
).returns(T::Boolean)
5357
}
54-
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil)
58+
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil,
59+
bottle_os_version: nil)
5560
formula = begin
5661
to_installed_formula
5762
rescue FormulaUnavailableError
@@ -100,8 +105,8 @@ def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibilit
100105
end
101106

102107
def satisfied?(inherited_options = [], minimum_version: nil, minimum_revision: nil,
103-
minimum_compatibility_version: nil)
104-
installed?(minimum_version:, minimum_revision:, minimum_compatibility_version:) &&
108+
minimum_compatibility_version: nil, bottle_os_version: nil)
109+
installed?(minimum_version:, minimum_revision:, minimum_compatibility_version:, bottle_os_version:) &&
105110
missing_options(inherited_options).empty?
106111
end
107112

@@ -312,25 +317,39 @@ def hash
312317
end
313318

314319
sig {
315-
params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer),
316-
minimum_compatibility_version: T.nilable(Integer)).returns(T::Boolean)
320+
params(
321+
minimum_version: T.nilable(Version),
322+
minimum_revision: T.nilable(Integer),
323+
minimum_compatibility_version: T.nilable(Integer),
324+
bottle_os_version: T.nilable(String),
325+
).returns(T::Boolean)
317326
}
318-
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil)
319-
use_macos_install? || super
327+
def installed?(minimum_version: nil, minimum_revision: nil, minimum_compatibility_version: nil,
328+
bottle_os_version: nil)
329+
use_macos_install?(bottle_os_version:) || super
320330
end
321331

322-
sig { returns(T::Boolean) }
323-
def use_macos_install?
332+
sig { params(bottle_os_version: T.nilable(String)).returns(T::Boolean) }
333+
def use_macos_install?(bottle_os_version: nil)
324334
# Check whether macOS is new enough for dependency to not be required.
325335
if Homebrew::SimulateSystem.simulating_or_running_on_macos?
326-
# Assume the oldest macOS version when simulating a generic macOS version
327-
return true if Homebrew::SimulateSystem.current_os == :macos && !bounds.key?(:since)
328-
329-
if Homebrew::SimulateSystem.current_os != :macos
330-
current_os = MacOSVersion.from_symbol(Homebrew::SimulateSystem.current_os)
331-
since_os = MacOSVersion.from_symbol(bounds[:since]) if bounds.key?(:since)
332-
return true if current_os >= since_os
336+
# If there's no since bound, the dependency is always available from macOS
337+
since_os_bounds = bounds[:since]
338+
return true if since_os_bounds.blank?
339+
340+
# When installing a bottle built on an older macOS version, use that version
341+
# to determine if the dependency should come from macOS or Homebrew
342+
effective_os = if bottle_os_version.present? &&
343+
bottle_os_version.start_with?("macOS ")
344+
# bottle_os_version is a string like "14" for Sonoma, "15" for Sequoia
345+
# Convert it to a MacOS version symbol for comparison
346+
MacOSVersion.new(bottle_os_version.delete_prefix("macOS "))
347+
else
348+
MacOSVersion.from_symbol(Homebrew::SimulateSystem.current_os)
333349
end
350+
351+
since_os = MacOSVersion.from_symbol(since_os_bounds)
352+
return true if effective_os >= since_os
334353
end
335354

336355
false

Library/Homebrew/formula_installer.rb

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def initialize(
138138
@poured_bottle = T.let(false, T::Boolean)
139139
@start_time = T.let(nil, T.nilable(Time))
140140
@bottle_tab_runtime_dependencies = T.let({}.freeze, T::Hash[String, T::Hash[String, String]])
141+
@bottle_built_os_version = T.let(nil, T.nilable(String))
141142
@hold_locks = T.let(false, T::Boolean)
142143
@show_summary_heading = T.let(false, T::Boolean)
143144
@etc_var_preinstall = T.let([], T::Array[Pathname])
@@ -338,12 +339,22 @@ def prelude
338339

339340
Tab.clear_cache
340341

341-
# Setup bottle_tab_runtime_dependencies for compute_dependencies
342+
# Setup bottle_tab_runtime_dependencies for compute_dependencies and
343+
# bottle_built_os_version for dependency resolution.
342344
begin
343-
@bottle_tab_runtime_dependencies = formula.bottle_tab_attributes
344-
.fetch("runtime_dependencies", []).then { |deps| deps || [] }
345-
.each_with_object({}) { |dep, h| h[dep["full_name"]] = dep }
346-
.freeze
345+
bottle_tab_attributes = formula.bottle_tab_attributes
346+
@bottle_tab_runtime_dependencies = bottle_tab_attributes
347+
.fetch("runtime_dependencies", []).then { |deps| deps || [] }
348+
.each_with_object({}) { |dep, h| h[dep["full_name"]] = dep }
349+
.freeze
350+
351+
if (bottle_tag = formula.bottle_for_tag(Utils::Bottles.tag)&.tag) &&
352+
bottle_tag.system != :all
353+
# Extract the OS version the bottle was built on.
354+
# This ensures that when installing older bottles (e.g. Sonoma bottle on Sequoia),
355+
# we resolve dependencies according to the bottle's built OS, not the current OS.
356+
@bottle_built_os_version = bottle_tab_attributes.dig("built_on", "os_version")
357+
end
347358
rescue Resource::BottleManifest::Error
348359
# If we can't get the bottle manifest, assume a full dependencies install.
349360
end
@@ -772,14 +783,14 @@ def expand_dependencies_for_formula(formula, inherited_options)
772783
keep_build_test ||= dep.build? && !install_bottle_for?(dependent, build) &&
773784
(formula.head? || !dependent.latest_version_installed?)
774785

775-
bottle_runtime_version = @bottle_tab_runtime_dependencies.dig(dep.name, "version").presence
776-
bottle_runtime_version = Version.new(bottle_runtime_version) if bottle_runtime_version
777-
bottle_runtime_revision = @bottle_tab_runtime_dependencies.dig(dep.name, "revision")
786+
minimum_version = @bottle_tab_runtime_dependencies.dig(dep.name, "version").presence
787+
minimum_version = Version.new(minimum_version) if minimum_version
788+
minimum_revision = @bottle_tab_runtime_dependencies.dig(dep.name, "revision")
789+
bottle_os_version = @bottle_built_os_version
778790

779791
if dep.prune_from_option?(build) || ((dep.build? || dep.test?) && !keep_build_test)
780792
Dependency.prune
781-
elsif dep.satisfied?(inherited_options[dep.name], minimum_version: bottle_runtime_version,
782-
minimum_revision: bottle_runtime_revision)
793+
elsif dep.satisfied?(inherited_options[dep.name], minimum_version:, minimum_revision:, bottle_os_version:)
783794
Dependency.skip
784795
end
785796
end

Library/Homebrew/test/dependency_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,40 @@
122122
expect(dep).not_to be_test
123123
end
124124
end
125+
126+
describe "Dependency#installed? with bottle_os_version" do
127+
subject(:dependency) { described_class.new("foo") }
128+
129+
it "accepts macOS bottle_os_version parameter" do
130+
expect { dependency.installed?(bottle_os_version: "macOS 14") }.not_to raise_error
131+
end
132+
133+
it "accepts Ubuntu bottle_os_version parameter" do
134+
expect { dependency.installed?(bottle_os_version: "Ubuntu 22.04") }.not_to raise_error
135+
end
136+
end
137+
138+
describe "Dependency#satisfied? with bottle_os_version" do
139+
subject(:dependency) { described_class.new("foo") }
140+
141+
it "accepts bottle_os_version parameter" do
142+
expect { dependency.satisfied?([], bottle_os_version: "macOS 14") }.not_to raise_error
143+
end
144+
145+
it "accepts Ubuntu bottle_os_version parameter" do
146+
expect { dependency.installed?(bottle_os_version: "Ubuntu 22.04") }.not_to raise_error
147+
end
148+
end
149+
150+
describe "UsesFromMacOSDependency#installed? with bottle_os_version" do
151+
subject(:uses_from_macos) { described_class.new("foo", bounds: { since: :sonoma }) }
152+
153+
it "accepts macOS bottle_os_version parameter" do
154+
expect { uses_from_macos.installed?(bottle_os_version: "macOS 14") }.not_to raise_error
155+
end
156+
157+
it "accepts Ubuntu bottle_os_version parameter" do
158+
expect { uses_from_macos.installed?(bottle_os_version: "Ubuntu 22.04") }.not_to raise_error
159+
end
160+
end
125161
end

0 commit comments

Comments
 (0)