From d1b923f314e4a720020646c4e0439c8f7d6df465 Mon Sep 17 00:00:00 2001 From: Bo Anderson Date: Mon, 19 Jun 2023 06:03:31 +0100 Subject: [PATCH] Introduce UsesFromMacOSDependency Add Formula#declared_deps and SoftwareSpec#declared_deps --- Library/Homebrew/dependencies.rb | 4 ++ Library/Homebrew/dependency.rb | 51 +++++++++++++++++- Library/Homebrew/formula.rb | 3 ++ Library/Homebrew/formula_auditor.rb | 4 +- Library/Homebrew/language/perl.rb | 11 ++-- Library/Homebrew/software_spec.rb | 35 +++++++------ Library/Homebrew/test/formulary_spec.rb | 4 ++ Library/Homebrew/test/os/mac/formula_spec.rb | 10 +++- Library/Homebrew/test/software_spec_spec.rb | 54 ++++++++++++++++++-- 9 files changed, 149 insertions(+), 27 deletions(-) diff --git a/Library/Homebrew/dependencies.rb b/Library/Homebrew/dependencies.rb index 018c9f438e..625a874210 100644 --- a/Library/Homebrew/dependencies.rb +++ b/Library/Homebrew/dependencies.rb @@ -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__}>" diff --git a/Library/Homebrew/dependency.rb b/Library/Homebrew/dependency.rb index a371a7d58a..55bd655125 100644 --- a/Library/Homebrew/dependency.rb +++ b/Library/Homebrew/dependency.rb @@ -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 diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index f6f2cf5522..74726fd05b 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -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 diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index 7b58aea88b..9a3fc6a17e 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -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 diff --git a/Library/Homebrew/language/perl.rb b/Library/Homebrew/language/perl.rb index f4822239ad..c9b1775cef 100644 --- a/Library/Homebrew/language/perl.rb +++ b/Library/Homebrew/language/perl.rb @@ -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 diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index b6474b857a..6182838ea4 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -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 diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index 35865147b6..89a627b950 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -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 diff --git a/Library/Homebrew/test/os/mac/formula_spec.rb b/Library/Homebrew/test/os/mac/formula_spec.rb index 8a91e72e4a..a188ee4269 100644 --- a/Library/Homebrew/test/os/mac/formula_spec.rb +++ b/Library/Homebrew/test/os/mac/formula_spec.rb @@ -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 diff --git a/Library/Homebrew/test/software_spec_spec.rb b/Library/Homebrew/test/software_spec_spec.rb index dc93aa2ac6..458248468a 100644 --- a/Library/Homebrew/test/software_spec_spec.rb +++ b/Library/Homebrew/test/software_spec_spec.rb @@ -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