Introduce UsesFromMacOSDependency

Add Formula#declared_deps and SoftwareSpec#declared_deps
This commit is contained in:
Bo Anderson 2023-06-19 06:03:31 +01:00
parent 53d513695a
commit d1b923f314
No known key found for this signature in database
GPG Key ID: 3DB94E204E137D65
9 changed files with 149 additions and 27 deletions

View File

@ -33,6 +33,10 @@ class Dependencies < SimpleDelegator
build + required + recommended
end
def dup_without_system_deps
self.class.new(*__getobj__.reject { |dep| dep.uses_from_macos? && dep.use_macos_install? })
end
sig { returns(String) }
def inspect
"#<#{self.class.name}: #{__getobj__}>"

View File

@ -69,6 +69,11 @@ class Dependency
env_proc&.call
end
sig { overridable.returns(T::Boolean) }
def uses_from_macos?
false
end
sig { returns(String) }
def inspect
"#<#{self.class.name}: #{name.inspect} #{tags.inspect}>"
@ -173,7 +178,9 @@ class Dependency
dep = deps.first
tags = merge_tags(deps)
option_names = deps.flat_map(&:option_names).uniq
dep.class.new(name, tags, dep.env_proc, option_names)
kwargs = {}
kwargs[:bounds] = dep.bounds if dep.uses_from_macos?
dep.class.new(name, tags, dep.env_proc, option_names, **kwargs)
end
end
@ -208,3 +215,45 @@ class Dependency
end
end
end
# A dependency that marked as "installed" on macOS
class UsesFromMacOSDependency < Dependency
attr_reader :bounds
def initialize(name, tags = [], env_proc = DEFAULT_ENV_PROC, option_names = [name], bounds:)
super(name, tags, env_proc, option_names)
@bounds = bounds
end
def installed?
use_macos_install? || super
end
sig { returns(T::Boolean) }
def use_macos_install?
# 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
end
end
false
end
sig { override.returns(T::Boolean) }
def uses_from_macos?
true
end
sig { override.params(formula: Formula).returns(T.self_type) }
def dup_with_formula_name(formula)
self.class.new(formula.full_name.to_s, tags, env_proc, option_names, bounds: bounds)
end
end

View File

@ -533,6 +533,9 @@ class Formula
# The {Dependency}s for the currently active {SoftwareSpec}.
delegate deps: :active_spec
# The declared {Dependency}s for the currently active {SoftwareSpec} (i.e. including those provided by macOS)
delegate declared_deps: :active_spec
# Dependencies provided by macOS for the currently active {SoftwareSpec}.
delegate uses_from_macos_elements: :active_spec

View File

@ -254,7 +254,7 @@ module Homebrew
@specs.each do |spec|
# Check for things we don't like to depend on.
# We allow non-Homebrew installs whenever possible.
spec.deps.each do |dep|
spec.declared_deps.each do |dep|
begin
dep_f = dep.to_formula
rescue TapFormulaUnavailableError
@ -323,7 +323,7 @@ module Homebrew
end
# we want to allow uses_from_macos for aliases but not bare dependencies
if self.class.aliases.include?(dep.name) && spec.uses_from_macos_names.exclude?(dep.name)
if self.class.aliases.include?(dep.name) && !dep.uses_from_macos?
problem "Dependency '#{dep.name}' is an alias; use the canonical name '#{dep.to_formula.full_name}'."
end

View File

@ -11,10 +11,13 @@ module Language
module_function
def detected_perl_shebang(formula = self)
perl_path = if formula.deps.map(&:name).include? "perl"
Formula["perl"].opt_bin/"perl"
elsif formula.uses_from_macos_names.include? "perl"
"/usr/bin/perl#{MacOS.preferred_perl_version}"
perl_deps = formula.declared_deps.select { |dep| dep.name == "perl" }
perl_path = if perl_deps.present?
if perl_deps.any? { |dep| !dep.uses_from_macos? || !dep.use_macos_install? }
Formula["perl"].opt_bin/"perl"
else
"/usr/bin/perl#{MacOS.preferred_perl_version}"
end
else
raise ShebangDetectionError.new("Perl", "formula does not depend on Perl")
end

View File

@ -24,8 +24,7 @@ class SoftwareSpec
}.freeze
attr_reader :name, :full_name, :owner, :build, :resources, :patches, :options, :deprecated_flags,
:deprecated_options, :dependency_collector, :bottle_specification, :compiler_failures,
:uses_from_macos_elements
:deprecated_options, :dependency_collector, :bottle_specification, :compiler_failures
def_delegators :@resource, :stage, :fetch, :verify_download_integrity, :source_modified_time, :download_name,
:cached_download, :clear_cache, :checksum, :mirrors, :specs, :using, :version, :mirror,
@ -195,28 +194,34 @@ class SoftwareSpec
deps = [bounds.shift].to_h
end
spec, tags = deps.is_a?(Hash) ? deps.first : deps
raise TypeError, "Dependency name must be a string!" unless spec.is_a?(String)
@uses_from_macos_elements << deps
# 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 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 if current_os >= since_os
end
end
depends_on deps
depends_on UsesFromMacOSDependency.new(spec, Array(tags), bounds: bounds)
end
# @deprecated
# rubocop:disable Style/TrivialAccessors
def uses_from_macos_elements
# TODO: remove all @uses_from_macos_elements when disabling or removing this method
# odeprecated "#uses_from_macos_elements", "#declared_deps"
@uses_from_macos_elements
end
# rubocop:enable Style/TrivialAccessors
# @deprecated
def uses_from_macos_names
# odeprecated "#uses_from_macos_names", "#declared_deps"
uses_from_macos_elements.flat_map { |e| e.is_a?(Hash) ? e.keys : e }
end
def deps
dependency_collector.deps.dup_without_system_deps
end
def declared_deps
dependency_collector.deps
end

View File

@ -340,6 +340,7 @@ describe Formulary do
expect(formula).to be_a(Formula)
expect(formula.keg_only_reason.reason).to eq :provided_by_macos
expect(formula.declared_deps.count).to eq 6
if OS.mac?
expect(formula.deps.count).to eq 5
else
@ -398,6 +399,7 @@ describe Formulary do
formula = described_class.factory(formula_name)
expect(formula).to be_a(Formula)
expect(formula.declared_deps.count).to eq 7
expect(formula.deps.count).to eq 6
expect(formula.deps.map(&:name).include?("variations_dep")).to be true
expect(formula.deps.map(&:name).include?("uses_from_macos_dep")).to be false
@ -409,6 +411,7 @@ describe Formulary do
formula = described_class.factory(formula_name)
expect(formula).to be_a(Formula)
expect(formula.declared_deps.count).to eq 6
expect(formula.deps.count).to eq 6
expect(formula.deps.map(&:name).include?("uses_from_macos_dep")).to be true
end
@ -419,6 +422,7 @@ describe Formulary do
formula = described_class.factory(formula_name)
expect(formula).to be_a(Formula)
expect(formula.declared_deps.count).to eq 6
expect(formula.deps.count).to eq 5
expect(formula.deps.map(&:name).include?("uses_from_macos_dep")).to be true
end

View File

@ -19,19 +19,27 @@ describe Formula do
expect(f.class.stable.deps).to be_empty
expect(f.class.head.deps).to be_empty
expect(f.class.stable.declared_deps).not_to be_empty
expect(f.class.head.declared_deps).not_to be_empty
expect(f.class.stable.declared_deps.first.name).to eq("foo")
expect(f.class.head.declared_deps.first.name).to eq("foo")
expect(f.class.stable.uses_from_macos_elements.first).to eq("foo")
expect(f.class.head.uses_from_macos_elements.first).to eq("foo")
end
it "doesn't add a macOS dependency to any spec if the OS version doesn't meet requirements" do
it "adds a dependency to any spec if the OS version doesn't meet requirements" do
f = formula "foo" do
url "foo-1.0"
uses_from_macos("foo", since: :high_sierra)
end
expect(f.class.stable.deps).not_to be_empty
expect(f.class.head.deps).not_to be_empty
expect(f.class.stable.deps.first.name).to eq("foo")
expect(f.class.head.deps.first.name).to eq("foo")
expect(f.class.stable.declared_deps).not_to be_empty
expect(f.class.head.declared_deps).not_to be_empty
expect(f.class.stable.uses_from_macos_elements).to eq(["foo"])
expect(f.class.head.uses_from_macos_elements).to eq(["foo"])
end

View File

@ -139,37 +139,59 @@ describe SoftwareSpec do
it "allows specifying dependencies" do
spec.uses_from_macos("foo")
expect(spec.declared_deps).not_to be_empty
expect(spec.deps).not_to be_empty
expect(spec.deps.first.name).to eq("foo")
expect(spec.deps.first).to be_uses_from_macos
expect(spec.deps.first).not_to be_use_macos_install
end
it "works with tags" do
spec.uses_from_macos("foo" => :build)
expect(spec.declared_deps).not_to be_empty
expect(spec.deps).not_to be_empty
expect(spec.deps.first.name).to eq("foo")
expect(spec.deps.first.tags).to include(:build)
expect(spec.deps.first).to be_uses_from_macos
expect(spec.deps.first).not_to be_use_macos_install
end
it "ignores dependencies with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do
it "handles dependencies with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do
ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1"
spec.uses_from_macos("foo")
expect(spec.deps).to be_empty
expect(spec.declared_deps.first.name).to eq("foo")
expect(spec.declared_deps.first.tags).to be_empty
expect(spec.declared_deps.first).to be_uses_from_macos
expect(spec.declared_deps.first).to be_use_macos_install
end
it "ignores dependencies with tags with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do
it "handles dependencies with tags with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do
ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1"
spec.uses_from_macos("foo" => :build)
expect(spec.deps).to be_empty
expect(spec.declared_deps.first.name).to eq("foo")
expect(spec.declared_deps.first.tags).to include(:build)
expect(spec.declared_deps.first).to be_uses_from_macos
expect(spec.declared_deps.first).to be_use_macos_install
end
it "ignores OS version specifications" do
spec.uses_from_macos("foo", since: :mojave)
spec.uses_from_macos("bar" => :build, :since => :mojave)
expect(spec.deps.count).to eq 2
expect(spec.deps.first.name).to eq("foo")
expect(spec.deps.first).to be_uses_from_macos
expect(spec.deps.first).not_to be_use_macos_install
expect(spec.deps.last.name).to eq("bar")
expect(spec.deps.last.tags).to include(:build)
expect(spec.deps.last).to be_uses_from_macos
expect(spec.deps.last).not_to be_use_macos_install
expect(spec.declared_deps.count).to eq 2
end
end
@ -183,30 +205,54 @@ describe SoftwareSpec do
spec.uses_from_macos("foo", since: :el_capitan)
expect(spec.deps).to be_empty
expect(spec.declared_deps).not_to be_empty
expect(spec.declared_deps.first).to be_uses_from_macos
expect(spec.declared_deps.first).to be_use_macos_install
expect(spec.uses_from_macos_elements.first).to eq("foo")
end
it "doesn't add a macOS dependency if the OS version doesn't meet requirements" do
it "add a macOS dependency if the OS version doesn't meet requirements" do
spec.uses_from_macos("foo", since: :high_sierra)
expect(spec.declared_deps).not_to be_empty
expect(spec.deps).not_to be_empty
expect(spec.deps.first.name).to eq("foo")
expect(spec.deps.first).to be_uses_from_macos
expect(spec.deps.first).not_to be_use_macos_install
expect(spec.uses_from_macos_elements).to eq(["foo"])
end
it "works with tags" do
spec.uses_from_macos("foo" => :build, :since => :high_sierra)
expect(spec.declared_deps).not_to be_empty
expect(spec.deps).not_to be_empty
dep = spec.deps.first
expect(dep.name).to eq("foo")
expect(dep.tags).to include(:build)
expect(dep.first).to be_uses_from_macos
expect(dep.first).not_to be_use_macos_install
end
it "doesn't add a dependency if no OS version is specified" do
it "doesn't add an effective dependency if no OS version is specified" do
spec.uses_from_macos("foo")
spec.uses_from_macos("bar" => :build)
expect(spec.deps).to be_empty
expect(spec.declared_deps).not_to be_empty
dep = spec.declared_deps.first
expect(dep.name).to eq("foo")
expect(dep.first).to be_uses_from_macos
expect(dep.first).to be_use_macos_install
dep = spec.declared_deps.last
expect(dep.name).to eq("bar")
expect(dep.tags).to include(:build)
expect(dep.first).to be_uses_from_macos
expect(dep.first).to be_use_macos_install
end
it "raises an error if passing invalid OS versions" do