diff --git a/Library/Homebrew/api/formula/formula_struct_generator.rb b/Library/Homebrew/api/formula/formula_struct_generator.rb index 0c0219c301..16c9b6b4d5 100644 --- a/Library/Homebrew/api/formula/formula_struct_generator.rb +++ b/Library/Homebrew/api/formula/formula_struct_generator.rb @@ -247,18 +247,18 @@ module Homebrew sig { params(deps_hash: DependencyHash).returns(T::Array[FormulaStruct::DependsOnArgs]) } def process_dependencies(deps_hash) no_linkage_names = deps_hash.fetch("no_linkage_dependencies", []) - dependencies = deps_hash.fetch("dependencies", []).map do |dep| + dependencies = deps_hash.fetch("dependencies", []).uniq.map do |dep| next dep unless no_linkage_names.include?(dep) { dep => :no_linkage } end - dependencies + [:build, :test, :recommended, :optional].filter_map do |type| - deps_hash["#{type}_dependencies"]&.map do |dep| - next { dep => type } unless no_linkage_names.include?(dep) + types = [:build, :test, :recommended, :optional] + dependencies + types.flat_map { |type| deps_hash.fetch("#{type}_dependencies", []) }.uniq.map do |dep| + tags = types.select { |type| deps_hash.fetch("#{type}_dependencies", []).include?(dep) } + tags << :no_linkage if no_linkage_names.include?(dep) - { dep => [type, :no_linkage] } - end - end.flatten(1) + { dep => (tags.one? ? tags.fetch(0) : tags) } + end end sig { params(requirements_array: RequirementsArray, spec: Symbol).returns(T::Array[FormulaStruct::DependsOnArgs]) } diff --git a/Library/Homebrew/test/api/formula/formula_struct_generator_spec.rb b/Library/Homebrew/test/api/formula/formula_struct_generator_spec.rb index 6dd06fc441..17fffcf397 100644 --- a/Library/Homebrew/test/api/formula/formula_struct_generator_spec.rb +++ b/Library/Homebrew/test/api/formula/formula_struct_generator_spec.rb @@ -236,6 +236,57 @@ RSpec.describe Homebrew::API::Formula::FormulaStructGenerator do expect(output).to eq dependency_args end + describe "::process_dependencies" do + it "combines build and test tags into one dependency" do + expect(described_class.process_dependencies({ + "build_dependencies" => ["foo"], + "test_dependencies" => ["foo"], + })).to eq [{ "foo" => [:build, :test] }] + end + + it "appends no_linkage to combined dependency tags" do + expect(described_class.process_dependencies({ + "build_dependencies" => ["foo"], + "test_dependencies" => ["foo"], + "no_linkage_dependencies" => ["foo"], + })).to eq [{ "foo" => [:build, :test, :no_linkage] }] + end + + it "preserves runtime dependencies alongside combined build and test tags" do + expect(described_class.process_dependencies({ + "dependencies" => ["foo"], + "build_dependencies" => ["foo"], + "test_dependencies" => ["foo"], + })).to eq ["foo", { "foo" => [:build, :test] }] + end + + it "preserves no_linkage on runtime and tagged dependencies" do + expect(described_class.process_dependencies({ + "dependencies" => ["foo"], + "test_dependencies" => ["foo"], + "no_linkage_dependencies" => ["foo"], + })).to eq [{ "foo" => :no_linkage }, { "foo" => [:test, :no_linkage] }] + end + + it "preserves recommended and no_linkage tags" do + expect(described_class.process_dependencies({ + "recommended_dependencies" => ["foo"], + "no_linkage_dependencies" => ["foo"], + })).to eq [{ "foo" => [:recommended, :no_linkage] }] + end + + it "preserves first appearance and uses a fixed tag order" do + expect(described_class.process_dependencies({ + "optional_dependencies" => ["multi", "optional"], + "recommended_dependencies" => ["multi", "recommended"], + "test_dependencies" => ["multi", "test"], + "build_dependencies" => ["build", "multi"], + "dependencies" => ["runtime"], + })).to eq ["runtime", { "build" => :build }, { "multi" => [:build, :test, :recommended, :optional] }, + { "test" => :test }, { "recommended" => :recommended }, { "optional" => :optional }] + end + end + specify "::process_uses_from_macos" do output = described_class.process_uses_from_macos(symbolized_dependency_hash) expect(output).to eq uses_from_macos_args diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index 18e366152c..f283ab22ec 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -185,6 +185,7 @@ RSpec.describe Formulary do license "MIT" url "https://example.com/no-linkage-round-trip-1.0.tar.gz" depends_on "shared" => :no_linkage + depends_on "runtime" on_macos do depends_on "platform" => :no_linkage @@ -192,6 +193,8 @@ RSpec.describe Formulary do on_linux do depends_on "platform" end + depends_on "multi" => [:build, :test] + depends_on "runtime" => [:build, :test] head do url "https://example.com/no-linkage-round-trip.git", branch: "main" @@ -210,7 +213,10 @@ RSpec.describe Formulary do before do stub_const("OnSystem::VALID_OS_ARCH_TAGS", [mac_tag, linux_tag]) - allow(CoreTap.instance).to receive(:git_head).and_return("0" * 40) + allow(Homebrew::API).to receive_messages(formula_names: [formula_name], formula_aliases: {}, + formula_renames: {}) + allow(CoreTap.instance).to receive_messages(git_head: "0" * 40, tap_migrations: {}) + allow(CoreCaskTap.instance).to receive(:tap_migrations).and_return({}) allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false) formula_path.dirname.mkpath formula_path.write formula_content @@ -218,7 +224,7 @@ RSpec.describe Formulary do test_each([:arm64_sequoia, :x86_64_linux].product([:stable, :head], [:public, :internal])) do |(tag_symbol, spec, format)| - it "preserves #{spec} declarations through the #{format} API on #{tag_symbol}" do + it "preserves #{spec} declarations through the #{format} API on #{tag_symbol}", :aggregate_failures do payload = json tag = (tag_symbol == :arm64_sequoia) ? mac_tag : linux_tag Homebrew::SimulateSystem.with_tag(tag) do @@ -238,6 +244,7 @@ RSpec.describe Formulary do expect(restored.declared_deps.map { |dep| [dep.name, dep.tags] }) .to eq(source.declared_deps.map { |dep| [dep.name, dep.tags] }) + expect(restored.to_hash.slice(*source.dependencies_hash.keys)).to eq(source.dependencies_hash) end end end