diff --git a/Library/Homebrew/cask/tab.rb b/Library/Homebrew/cask/tab.rb index 23c48d65c7..08ceb477b0 100644 --- a/Library/Homebrew/cask/tab.rb +++ b/Library/Homebrew/cask/tab.rb @@ -50,34 +50,33 @@ module Cask end def self.runtime_deps_hash(cask, depends_on) - mappable_types = [:cask, :formula] - depends_on.to_h do |type, deps| - next [type, deps] unless mappable_types.include? type - - deps = deps.map do |dep| - if type == :cask - c = CaskLoader.load(dep) - { - "full_name" => c.full_name, - "version" => c.version.to_s, - "declared_directly" => cask.depends_on.cask.include?(dep), - } - elsif type == :formula - f = Formulary.factory(dep, warn: false) - { - "full_name" => f.full_name, - "version" => f.version.to_s, - "revision" => f.revision, - "pkg_version" => f.pkg_version.to_s, - "declared_directly" => cask.depends_on.formula.include?(dep), - } - else - dep - end - end - - [type, deps] + cask_and_formula_dep_graph = ::Utils::TopologicalHash.graph_package_dependencies(cask) + cask_deps, formula_deps = cask_and_formula_dep_graph.values.flatten.uniq.partition do |dep| + dep.is_a?(Cask) end + + runtime_deps = {} + + if cask_deps.any? + runtime_deps[:cask] = cask_deps.map do |dep| + { + "full_name" => dep.full_name, + "version" => dep.version.to_s, + "declared_directly" => cask.depends_on.cask.include?(dep.full_name), + } + end + end + + if formula_deps.any? + runtime_deps[:formula] = formula_deps.map do |dep| + formula_to_dep_hash(dep, cask.depends_on.formula) + end + end + + runtime_deps[:macos] = depends_on.macos if depends_on.macos + runtime_deps[:arch] = depends_on.arch if depends_on.arch + + runtime_deps end def version diff --git a/Library/Homebrew/tab.rb b/Library/Homebrew/tab.rb index 267cd6bfc8..85afd06e63 100644 --- a/Library/Homebrew/tab.rb +++ b/Library/Homebrew/tab.rb @@ -93,6 +93,17 @@ class AbstractTab new(attributes) end + def self.formula_to_dep_hash(formula, declared_deps) + { + "full_name" => formula.full_name, + "version" => formula.version.to_s, + "revision" => formula.revision, + "pkg_version" => formula.pkg_version.to_s, + "declared_directly" => declared_deps.include?(formula.full_name), + } + end + private_class_method :formula_to_dep_hash + def initialize(attributes = {}) attributes.each { |key, value| instance_variable_set(:"@#{key}", value) } end @@ -290,14 +301,7 @@ class Tab < AbstractTab def self.runtime_deps_hash(formula, deps) deps.map do |dep| - f = dep.to_formula - { - "full_name" => f.full_name, - "version" => f.version.to_s, - "revision" => f.revision, - "pkg_version" => f.pkg_version.to_s, - "declared_directly" => formula.deps.include?(dep), - } + formula_to_dep_hash(dep.to_formula, formula.deps.map(&:name)) end end diff --git a/Library/Homebrew/test/cask/tab_spec.rb b/Library/Homebrew/test/cask/tab_spec.rb index 7df32cef23..9d8818f28b 100644 --- a/Library/Homebrew/test/cask/tab_spec.rb +++ b/Library/Homebrew/test/cask/tab_spec.rb @@ -85,28 +85,79 @@ RSpec.describe Cask::Tab, :cask do expect(tab.runtime_dependencies).not_to be_nil end - specify "::runtime_deps_hash" do - cask = Cask::CaskLoader.load("with-depends-on-everything") + describe "::runtime_deps_hash" do + specify "with no dependencies" do + cask = Cask::CaskLoader.load("local-transmission") - unar = instance_double(Formula, full_name: "unar", version: "1.2", revision: 0, pkg_version: "1.2") - expect(Formulary).to receive(:factory).with("unar", { warn: false }).and_return(unar) + expect(described_class.runtime_deps_hash(cask, cask.depends_on)).to eq({}) + end - expected_hash = { - arch: [{ type: :intel, bits: 64 }, { type: :arm, bits: 64 }], - cask: [ - { "full_name"=>"local-caffeine", "version"=>"1.2.3", "declared_directly"=>true }, - { "full_name"=>"local-transmission", "version"=>"2.61", "declared_directly"=>true }, - ], - formula: [ - { "full_name"=>"unar", "version"=>"1.2", "revision"=>0, "pkg_version"=>"1.2", "declared_directly"=>true }, - ], - macos: MacOSRequirement.new([:el_capitan], comparator: ">="), - } + specify "with cask dependencies" do + cask = Cask::CaskLoader.load("with-depends-on-cask") - runtime_deps_hash = described_class.runtime_deps_hash(cask, cask.depends_on) - tab = described_class.new - tab.runtime_dependencies = runtime_deps_hash - expect(tab.runtime_dependencies).to eql(expected_hash) + expected_hash = { + cask: [ + { "full_name"=>"local-transmission", "version"=>"2.61", "declared_directly"=>true }, + ], + } + expect(described_class.runtime_deps_hash(cask, cask.depends_on)).to eq(expected_hash) + end + + specify "with macos symbol dependencies" do + cask = Cask::CaskLoader.load("with-depends-on-macos-symbol") + + expected_hash = { + macos: MacOSRequirement.new([MacOS.version.to_sym], comparator: "=="), + } + expect(described_class.runtime_deps_hash(cask, cask.depends_on)).to eq(expected_hash) + end + + specify "with macos array dependencies" do + cask = Cask::CaskLoader.load("with-depends-on-macos-array") + + expected_hash = { + macos: MacOSRequirement.new([[:catalina, MacOS.version.to_sym]], comparator: "=="), + } + expect(described_class.runtime_deps_hash(cask, cask.depends_on)).to eq(expected_hash) + end + + specify "with arch dependencies" do + cask = Cask::CaskLoader.load("with-depends-on-arch") + + expected_hash = { + arch: [ + { type: :intel, bits: 64 }, + { type: :arm, bits: 64 }, + ], + } + expect(described_class.runtime_deps_hash(cask, cask.depends_on)).to eq(expected_hash) + end + + specify "with all types of dependencies" do + cask = Cask::CaskLoader.load("with-depends-on-everything") + + unar = instance_double(Formula, full_name: "unar", version: "1.2", revision: 0, pkg_version: "1.2", + deps: [], requirements: []) + expect(Formulary).to receive(:factory).with("unar").and_return(unar) + + expected_hash = { + arch: [{ type: :intel, bits: 64 }, { type: :arm, bits: 64 }], + cask: [ + { "full_name"=>"local-caffeine", "version"=>"1.2.3", "declared_directly"=>true }, + { "full_name"=>"with-depends-on-cask", "version"=>"1.2.3", "declared_directly"=>true }, + { "full_name"=>"local-transmission", "version"=>"2.61", "declared_directly"=>false }, + ], + formula: [ + { "full_name"=>"unar", "version"=>"1.2", "revision"=>0, "pkg_version"=>"1.2", "declared_directly"=>true }, + ], + macos: MacOSRequirement.new([:el_capitan], comparator: ">="), + } + + runtime_deps_hash = described_class.runtime_deps_hash(cask, cask.depends_on) + tab = described_class.new + tab.runtime_dependencies = runtime_deps_hash + expect(tab.runtime_dependencies).to eql(expected_hash) + end end specify "other attributes" do diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-depends-on-everything.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-depends-on-everything.rb index 85570a7b00..fef3374ef7 100644 --- a/Library/Homebrew/test/support/fixtures/cask/Casks/with-depends-on-everything.rb +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-depends-on-everything.rb @@ -7,7 +7,7 @@ cask "with-depends-on-everything" do depends_on arch: [:intel, :arm64] depends_on cask: "local-caffeine" - depends_on cask: "local-transmission" + depends_on cask: "with-depends-on-cask" depends_on formula: "unar" depends_on macos: ">= :el_capitan" diff --git a/Library/Homebrew/test/tab_spec.rb b/Library/Homebrew/test/tab_spec.rb index 6c8414ae44..d3041239c3 100644 --- a/Library/Homebrew/test/tab_spec.rb +++ b/Library/Homebrew/test/tab_spec.rb @@ -155,18 +155,69 @@ RSpec.describe Tab do expect(tab.runtime_dependencies).not_to be_nil end - specify "::runtime_deps_hash" do - runtime_deps = [Dependency.new("foo")] - foo = formula("foo") { url "foo-1.0" } - stub_formula_loader foo - runtime_deps_hash = described_class.runtime_deps_hash(foo, runtime_deps) - tab = described_class.new - tab.homebrew_version = "1.1.6" - tab.runtime_dependencies = runtime_deps_hash - expect(tab.runtime_dependencies).to eql( - [{ "full_name" => "foo", "version" => "1.0", "revision" => 0, "pkg_version" => "1.0", -"declared_directly" => false }], - ) + describe "::runtime_deps_hash" do + it "handles older Homebrew versions correctly" do + runtime_deps = [Dependency.new("foo")] + foo = formula("foo") { url "foo-1.0" } + stub_formula_loader foo + runtime_deps_hash = described_class.runtime_deps_hash(foo, runtime_deps) + tab = described_class.new + tab.homebrew_version = "1.1.6" + tab.runtime_dependencies = runtime_deps_hash + expect(tab.runtime_dependencies).to eql( + [{ "full_name" => "foo", "version" => "1.0", "revision" => 0, "pkg_version" => "1.0", + "declared_directly" => false }], + ) + end + + it "include declared dependencies" do + foo = formula("foo") { url "foo-1.0" } + stub_formula_loader foo + + runtime_deps = [Dependency.new("foo")] + formula = instance_double(Formula, deps: runtime_deps) + + expected_output = [ + { + "full_name" => "foo", + "version" => "1.0", + "revision" => 0, + "pkg_version" => "1.0", + "declared_directly" => true, + }, + ] + expect(described_class.runtime_deps_hash(formula, runtime_deps)).to eq(expected_output) + end + + it "includes recursive dependencies" do + foo = formula("foo") { url "foo-1.0" } + stub_formula_loader foo + bar = formula("bar") { url "bar-2.0" } + stub_formula_loader bar + + # Simulating dependencies formula => foo => bar + formula_declared_deps = [Dependency.new("foo")] + formula_recursive_deps = [Dependency.new("foo"), Dependency.new("bar")] + formula = instance_double(Formula, deps: formula_declared_deps) + + expected_output = [ + { + "full_name" => "foo", + "version" => "1.0", + "revision" => 0, + "pkg_version" => "1.0", + "declared_directly" => true, + }, + { + "full_name" => "bar", + "version" => "2.0", + "revision" => 0, + "pkg_version" => "2.0", + "declared_directly" => false, + }, + ] + expect(described_class.runtime_deps_hash(formula, formula_recursive_deps)).to eq(expected_output) + end end specify "#cxxstdlib" do