From b1cd9f5f7cc75970ffc2e26591903cd1069e2f64 Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Fri, 9 Jan 2026 22:39:52 +0100 Subject: [PATCH 1/4] Make Cask CI generate correctly for partial arch dependencies --- Library/Homebrew/cask/artifact.rb | 2 + Library/Homebrew/cask/cask.rb | 30 ++- .../dev-cmd/generate-cask-ci-matrix.rb | 42 +++- .../dev-cmd/generate-cask-ci-matrix_spec.rb | 230 ++++++++++++++++++ 4 files changed, 290 insertions(+), 14 deletions(-) diff --git a/Library/Homebrew/cask/artifact.rb b/Library/Homebrew/cask/artifact.rb index 7f031467f8531..1ad24d77552fa 100644 --- a/Library/Homebrew/cask/artifact.rb +++ b/Library/Homebrew/cask/artifact.rb @@ -52,5 +52,7 @@ module Artifact ::Cask::Artifact::VstPlugin, ::Cask::Artifact::Vst3Plugin, ].freeze + + LINUX_ONLY_ARTIFACTS = [].freeze end end diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index c7db1770618a0..54d046c541c2a 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -167,12 +167,36 @@ def supports_macos? = true sig { returns(T::Boolean) } def supports_linux? return true if font? + return false if contains_os_specific_artifacts? - return false if artifacts.any? do |artifact| - ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class) + @dsl.os.present? + end + + sig { returns(T::Boolean) } + def contains_os_specific_artifacts? + return false unless @dsl.on_system_blocks_exist? + + any_loaded = false + @contains_os_specific_artifacts ||= begin + OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag| + Homebrew::SimulateSystem.with_tag(bottle_tag) do + refresh + + any_loaded = true if artifacts.any? do |artifact| + (bottle_tag.linux? && ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class)) || + (bottle_tag.macos? && ::Cask::Artifact::LINUX_ONLY_ARTIFACTS.include?(artifact.class)) + end + end + end + + any_loaded + rescue CaskInvalidError + # Invalid cask + ensure + refresh end - @dsl.os.present? + @contains_os_specific_artifacts end # The caskfile is needed during installation when there are diff --git a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb index aa257c4d41be8..112a962fdeb33 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb @@ -139,21 +139,41 @@ def filter_runners(cask) RUNNERS.dup end - filtered_runners = filtered_runners.merge(LINUX_RUNNERS) if cask.supports_linux? - - archs = architectures(cask:) + macos_archs = architectures(cask:, os: :macos) filtered_runners.select! do |runner, _| - archs.include?(runner.fetch(:arch)) + macos_archs.include?(runner.fetch(:arch)) + end + + return filtered_runners unless cask.supports_linux? + + linux_archs = architectures(cask:, os: :linux) + linux_runners = LINUX_RUNNERS.select do |runner, _| + linux_archs.include?(runner.fetch(:arch)) end - filtered_runners + filtered_runners.merge(linux_runners) end - sig { params(cask: Cask::Cask).returns(T::Array[Symbol]) } - def architectures(cask:) - return RUNNERS.keys.map { |r| r.fetch(:arch).to_sym }.uniq.sort if cask.depends_on.arch.blank? + sig { params(cask: Cask::Cask, os: Symbol).returns(T::Array[Symbol]) } + def architectures(cask:, os: :macos) + architectures = [] + [:arm, :intel].each do |arch| + tag = Utils::Bottles::Tag.new(system: os, arch: arch) + Homebrew::SimulateSystem.with_tag(tag) do + cask.refresh + + if cask.depends_on.arch.blank? + architectures = RUNNERS.keys.map { |r| r.fetch(:arch).to_sym }.uniq.sort + next + end + + architectures = cask.depends_on.arch.map { |arch| arch[:type] } + end + rescue ::Cask::CaskInvalidError + # Can't read cask for this system-arch combination. + end - cask.depends_on.arch.map { |arch| arch[:type] }.uniq.sort + architectures end sig { @@ -253,9 +273,9 @@ def generate_matrix(tap, labels: [], cask_names: [], skip_install: false, new_ca runners, multi_os = runners(cask:) runners.product(architectures(cask:)).filter_map do |runner, arch| native_runner_arch = arch == runner.fetch(:arch) - # we don't need to run simulated archs on Linux + # we don't need to run simulated archs on Linux or macOS Sequoia + # because it has a GitHub hosted x86_64 runner next if runner.fetch(:symbol) == :linux && !native_runner_arch - # we don't need to run simulated archs on macOS next if runner.fetch(:symbol) == :sequoia && !native_runner_arch # If it's just a single OS test then we can just use the two real arch runners. diff --git a/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb b/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb index 0299fbfecef36..46cc728507482 100644 --- a/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb +++ b/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb @@ -4,5 +4,235 @@ require "dev-cmd/generate-cask-ci-matrix" RSpec.describe Homebrew::DevCmd::GenerateCaskCiMatrix do + subject(:generate_matrix) { described_class.new(["test"]) } + + let(:c_on_system_depends_on_mixed) do + Cask::Cask.new("test-on-system-depends-on-mixed") do + os macos: "darwin", linux: "linux" + + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + on_macos do + depends_on arch: :x86_64 + end + + on_linux do + depends_on arch: :arm64 + end + end + end + let(:c_on_macos_depends_on_intel) do + Cask::Cask.new("test-on-macos-depends-on-intel") do + os macos: "darwin", linux: "linux" + + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + on_macos do + depends_on arch: :x86_64 + end + end + end + let(:c_on_linux_depends_on_intel) do + Cask::Cask.new("test-on-linux-depends-on-intel") do + os macos: "darwin", linux: "linux" + + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + on_linux do + depends_on arch: :x86_64 + end + end + end + let(:c_on_system_depends_on_intel) do + Cask::Cask.new("test-on-system-depends-on-intel") do + os macos: "darwin", linux: "linux" + + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + depends_on arch: :x86_64 + end + end + let(:c_on_system) do + Cask::Cask.new("test-on-system") do + os macos: "darwin", linux: "linux" + + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + end + end + let(:c_depends_macos_on_intel) do + Cask::Cask.new("test-depends-on-intel") do + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + depends_on arch: :x86_64 + + app "Test.app" + end + end + let(:c_app) do + Cask::Cask.new("test-app") do + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + app "Test.app" + end + end + let(:c_app_only_macos) do + Cask::Cask.new("test-on-macos-guarded-stanza") do + os macos: "darwin", linux: "linux" + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + on_macos do + app "Test.app" + end + end + end + let(:c) do + Cask::Cask.new("test-font") do + version "0.0.1,2" + + url "https://brew.sh/test-0.0.1.dmg" + name "Test" + desc "Test cask" + homepage "https://brew.sh" + + font "Test.ttf" + end + end + let(:newest_macos) { MacOSVersion.new(HOMEBREW_MACOS_NEWEST_SUPPORTED).to_sym } + it_behaves_like "parseable arguments" + + describe "::filter_runners" do + # We simulate a macOS version older than the newest, as the method will use + # the host macOS version instead of the default (the newest macOS version). + let(:older_macos) { :big_sur } + + context "when cask does not have on_system blocks/calls or `depends_on arch`" do + it "returns an array including everything" do + expect(generate_matrix.send(:filter_runners, c)) + .to eq({ + { arch: :arm, name: "macos-14", symbol: :sonoma } => 0.0, + { arch: :arm, name: "macos-15", symbol: :sequoia } => 0.0, + { arch: :arm, name: "macos-26", symbol: :tahoe } => 1.0, + { arch: :arm, name: "ubuntu-22.04-arm", symbol: :linux } => 1.0, + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + { arch: :intel, name: "ubuntu-22.04", symbol: :linux } => 1.0, + }) + + expect(generate_matrix.send(:filter_runners, c_app_only_macos)) + .to eq({ + { arch: :arm, name: "macos-14", symbol: :sonoma } => 0.0, + { arch: :arm, name: "macos-15", symbol: :sequoia } => 0.0, + { arch: :arm, name: "macos-26", symbol: :tahoe } => 1.0, + { arch: :arm, name: "ubuntu-22.04-arm", symbol: :linux } => 1.0, + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + { arch: :intel, name: "ubuntu-22.04", symbol: :linux } => 1.0, + }) + end + end + + context "when cask does not have on_system blocks/calls but has macOS specific stanza" do + it "returns an array including all macOS" do + expect(generate_matrix.send(:filter_runners, c_app)) + .to eq({ + { arch: :arm, name: "macos-14", symbol: :sonoma } => 0.0, + { arch: :arm, name: "macos-15", symbol: :sequoia } => 0.0, + { arch: :arm, name: "macos-26", symbol: :tahoe } => 1.0, + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + }) + end + end + + context "when cask does not have on_system blocks/calls but has `depends_on arch`" do + it "returns an array only including macOS/`depends_on arch` value" do + expect(generate_matrix.send(:filter_runners, c_depends_macos_on_intel)) + .to eq({ { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0 }) + end + end + + context "when cask has on_system blocks/calls but does not have `depends_on arch`" do + it "returns an array with combinations of OS and architectures" do + expect(generate_matrix.send(:filter_runners, c_on_system)) + .to eq({ + { arch: :arm, name: "macos-14", symbol: :sonoma } => 0.0, + { arch: :arm, name: "macos-15", symbol: :sequoia } => 0.0, + { arch: :arm, name: "macos-26", symbol: :tahoe } => 1.0, + { arch: :arm, name: "ubuntu-22.04-arm", symbol: :linux } => 1.0, + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + { arch: :intel, name: "ubuntu-22.04", symbol: :linux } => 1.0, + }) + end + end + + context "when cask has on_system blocks/calls and `depends_on arch`" do + it "returns an array with combinations of OS and `depends_on arch` value" do + expect(generate_matrix.send(:filter_runners, c_on_system_depends_on_intel)) + .to eq({ + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + { arch: :intel, name: "ubuntu-22.04", symbol: :linux } => 1.0, + }) + + expect(generate_matrix.send(:filter_runners, c_on_linux_depends_on_intel)) + .to eq({ + { arch: :arm, name: "macos-14", symbol: :sonoma } => 0.0, + { arch: :arm, name: "macos-15", symbol: :sequoia } => 0.0, + { arch: :arm, name: "macos-26", symbol: :tahoe } => 1.0, + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + { arch: :intel, name: "ubuntu-22.04", symbol: :linux } => 1.0, + }) + + expect(generate_matrix.send(:filter_runners, c_on_macos_depends_on_intel)) + .to eq({ + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + { arch: :intel, name: "ubuntu-22.04", symbol: :linux } => 1.0, + { arch: :arm, name: "ubuntu-22.04-arm", symbol: :linux } => 1.0, + }) + + expect(generate_matrix.send(:filter_runners, c_on_system_depends_on_mixed)) + .to eq({ + { arch: :arm, name: "ubuntu-22.04-arm", symbol: :linux } => 1.0, + { arch: :intel, name: "macos-15-intel", symbol: :sequoia } => 1.0, + }) + end + end + end end From 30ca11f72b24b58be68a7de41761379e1b0106ed Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Fri, 9 Jan 2026 22:51:19 +0100 Subject: [PATCH 2/4] Update Library/Homebrew/cask/cask.rb Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Library/Homebrew/cask/cask.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index 54d046c541c2a..4daa1c89aa5df 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -179,21 +179,24 @@ def contains_os_specific_artifacts? any_loaded = false @contains_os_specific_artifacts ||= begin OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag| - Homebrew::SimulateSystem.with_tag(bottle_tag) do - refresh + begin + Homebrew::SimulateSystem.with_tag(bottle_tag) do + refresh - any_loaded = true if artifacts.any? do |artifact| - (bottle_tag.linux? && ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class)) || - (bottle_tag.macos? && ::Cask::Artifact::LINUX_ONLY_ARTIFACTS.include?(artifact.class)) + any_loaded = true if artifacts.any? do |artifact| + (bottle_tag.linux? && ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class)) || + (bottle_tag.macos? && ::Cask::Artifact::LINUX_ONLY_ARTIFACTS.include?(artifact.class)) + end end + rescue CaskInvalidError + # Invalid for this OS/arch tag; treat as having no OS-specific artifacts. + next + ensure + refresh end end any_loaded - rescue CaskInvalidError - # Invalid cask - ensure - refresh end @contains_os_specific_artifacts From a7f52e714025e4ef3cee8263d9db7f35f0fecc7a Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Fri, 9 Jan 2026 22:51:59 +0100 Subject: [PATCH 3/4] Update Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../dev-cmd/generate-cask-ci-matrix.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb index 112a962fdeb33..4737e2dd23431 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb @@ -158,17 +158,17 @@ def filter_runners(cask) def architectures(cask:, os: :macos) architectures = [] [:arm, :intel].each do |arch| - tag = Utils::Bottles::Tag.new(system: os, arch: arch) - Homebrew::SimulateSystem.with_tag(tag) do - cask.refresh + tag = Utils::Bottles::Tag.new(system: os, arch: arch) + Homebrew::SimulateSystem.with_tag(tag) do + cask.refresh - if cask.depends_on.arch.blank? - architectures = RUNNERS.keys.map { |r| r.fetch(:arch).to_sym }.uniq.sort - next - end - - architectures = cask.depends_on.arch.map { |arch| arch[:type] } + if cask.depends_on.arch.blank? + architectures = RUNNERS.keys.map { |r| r.fetch(:arch).to_sym }.uniq.sort + next end + + architectures = cask.depends_on.arch.map { |arch| arch[:type] } + end rescue ::Cask::CaskInvalidError # Can't read cask for this system-arch combination. end From bbe62a6161ab64f5ba90fd6d26a65996225fd294 Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Tue, 20 Jan 2026 19:00:14 +0100 Subject: [PATCH 4/4] Apply suggestion from @SMillerDev --- Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb index 4737e2dd23431..be4b9ff878494 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb @@ -274,7 +274,7 @@ def generate_matrix(tap, labels: [], cask_names: [], skip_install: false, new_ca runners.product(architectures(cask:)).filter_map do |runner, arch| native_runner_arch = arch == runner.fetch(:arch) # we don't need to run simulated archs on Linux or macOS Sequoia - # because it has a GitHub hosted x86_64 runner + # because they exist as real GitHub hosted runner next if runner.fetch(:symbol) == :linux && !native_runner_arch next if runner.fetch(:symbol) == :sequoia && !native_runner_arch