From f624b6d25b59aa52aae4f1b60ca1c17ddcbf3fa4 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Wed, 6 May 2026 17:32:09 +0100 Subject: [PATCH] Filter Linux cask loads by support - Keep `brew audit --tap` and `brew readall` loading Linux-supported casks so Linux syntax errors still surface. - Skip macOS-only casks before Linux reloads because their macOS `sha256 arm:/intel:` shorthand is invalid in a Linux context. - Fixes #22148. --- Library/Homebrew/dev-cmd/audit.rb | 32 +++++++++++-- Library/Homebrew/extend/os/mac/readall.rb | 2 +- Library/Homebrew/readall.rb | 45 ++++++++++++++++-- Library/Homebrew/test/cmd/readall_spec.rb | 44 ++++++++++++++++++ Library/Homebrew/test/dev-cmd/audit_spec.rb | 51 ++++++++++++++++++++- 5 files changed, 166 insertions(+), 8 deletions(-) diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index a922e219b5edf..729710d9ba981 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -96,6 +96,8 @@ def run Formulary.enable_factory_cache! os_arch_combinations = args.os_arch_combinations + cask_audit_os, cask_audit_arch = + os_arch_combinations.find { |os, _arch| os != :linux } || os_arch_combinations.fetch(0) Homebrew.auditing = true Homebrew.inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug? @@ -132,8 +134,8 @@ def run if tap.formula_file?(file) audit_formulae << Formulary.factory(absolute_file) - elsif tap.cask_file?(file) - audit_casks << Cask::CaskLoader.load(absolute_file) + elsif tap.cask_file?(file) && (cask = cask_for_audit(absolute_file, cask_audit_os, cask_audit_arch)) + audit_casks << cask end end @@ -142,7 +144,7 @@ def run Tap.fetch(args.tap).then do |tap| [ tap.formula_files.map { |path| Formulary.factory(path) }, - tap.cask_files.map { |path| Cask::CaskLoader.load(path) }, + tap.cask_files.filter_map { |path| cask_for_audit(path, cask_audit_os, cask_audit_arch) }, ] end elsif args.installed? @@ -342,6 +344,30 @@ def run private + sig { + params( + path: T.any(String, Pathname), + cask_audit_os: Symbol, + cask_audit_arch: Symbol, + ).returns(T.nilable(Cask::Cask)) + } + def cask_for_audit(path, cask_audit_os, cask_audit_arch) + if cask_audit_os == :linux + return if Pathname(path).read.match?(/^\s*depends_on(?:\s*\(\s*|\s+)(?::macos\b|macos:)/) + + cask = SimulateSystem.with(os: :macos, arch: cask_audit_arch) do + loaded_cask = Cask::CaskLoader.load(path) + loaded_cask if loaded_cask.supports_linux? + end + return unless cask + + SimulateSystem.with(os: :linux, arch: cask_audit_arch) { cask.refresh } + return cask + end + + SimulateSystem.with(os: cask_audit_os, arch: cask_audit_arch) { Cask::CaskLoader.load(path) } + end + sig { params(results: T::Hash[[Symbol, Pathname], T::Array[T::Hash[Symbol, T.untyped]]]).void } def print_problems(results) results.each do |(name, path), problems| diff --git a/Library/Homebrew/extend/os/mac/readall.rb b/Library/Homebrew/extend/os/mac/readall.rb index 9fef3d1f542d8..3c632f70f96f2 100644 --- a/Library/Homebrew/extend/os/mac/readall.rb +++ b/Library/Homebrew/extend/os/mac/readall.rb @@ -14,7 +14,7 @@ module ClassMethods sig { params(tap: ::Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) } def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type) - return true if os_name == :linux + return super if os_name == :linux current_macos_version = if os_name.is_a?(Symbol) MacOSVersion.from_symbol(os_name) diff --git a/Library/Homebrew/readall.rb b/Library/Homebrew/readall.rb index f6d7932c8df09..9a76264342224 100644 --- a/Library/Homebrew/readall.rb +++ b/Library/Homebrew/readall.rb @@ -90,9 +90,48 @@ def self.valid_formulae?(tap, bottle_tag: nil) success end - sig { params(_tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) } - def self.valid_casks?(_tap, os_name: nil, arch: nil) - true + sig { params(tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) } + def self.valid_casks?(tap, os_name: nil, arch: nil) + validating_linux = if os_name.nil? + Homebrew::SimulateSystem.current_os == :linux + else + os_name == :linux + end + return true unless validating_linux + + success = T.let(true, T::Boolean) + tap.cask_files.each do |file| + next if file.read.match?(/^\s*depends_on(?:\s*\(\s*|\s+)(?::macos\b|macos:)/) + + cask = if arch + Homebrew::SimulateSystem.with(os: :macos, arch:) do + loaded_cask = Cask::CaskLoader.load(file) + loaded_cask if loaded_cask.supports_linux? + end + else + Homebrew::SimulateSystem.with(os: :macos) do + loaded_cask = Cask::CaskLoader.load(file) + loaded_cask if loaded_cask.supports_linux? + end + end + next unless cask + + if arch + Homebrew::SimulateSystem.with(os: :linux, arch:) { cask.refresh } + else + Homebrew::SimulateSystem.with(os: :linux) { cask.refresh } + end + rescue Interrupt + raise + # Handle all possible exceptions reading casks. + rescue Exception => e # rubocop:disable Lint/RescueException + os_and_arch = "Linux" + os_and_arch += " on #{arch}" if arch + onoe "Invalid cask (#{os_and_arch}): #{file}" + $stderr.puts e + success = false + end + success end sig { diff --git a/Library/Homebrew/test/cmd/readall_spec.rb b/Library/Homebrew/test/cmd/readall_spec.rb index b0898a3b5854a..170600feed59b 100644 --- a/Library/Homebrew/test/cmd/readall_spec.rb +++ b/Library/Homebrew/test/cmd/readall_spec.rb @@ -20,4 +20,48 @@ .and not_to_output.to_stdout .and not_to_output.to_stderr end + + it "skips macOS-only casks when loading tap casks on Linux" do + tap_path = mktmpdir + macos_only_cask_file = tap_path/"Casks/macos-only-example.rb" + linux_cask_file = tap_path/"Casks/linux-example.rb" + macos_only_cask_file.dirname.mkpath + macos_only_cask_file.write <<~RUBY + cask "macos-only-example" do + version "1.0" + sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000", + intel: "1111111111111111111111111111111111111111111111111111111111111111" + url "https://example.invalid/x.pkg" + name "Example" + desc "macOS-only cask" + homepage "https://example.invalid/" + depends_on macos: ">= :ventura" + binary "x" + end + RUBY + linux_cask_file.write <<~RUBY + cask "linux-example" do + version "1.0" + sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000", + intel: "1111111111111111111111111111111111111111111111111111111111111111" + url "https://example.invalid/x.tar.gz" + name "Example" + desc "Linux-supported cask" + homepage "https://example.invalid/" + binary "x" + end + RUBY + + success = nil + expect do + success = Homebrew::SimulateSystem.with(os: :linux) do + Readall.valid_tap?( + instance_double(Tap, formula_files: [], cask_files: [macos_only_cask_file, linux_cask_file]), + os_arch_combinations: [[:linux, :arm]], + ) + end + end.to output(a_string_matching(/\A(?=.*linux-example)(?!.*macos-only-example).*\z/m)).to_stderr + + expect(success).to be false + end end diff --git a/Library/Homebrew/test/dev-cmd/audit_spec.rb b/Library/Homebrew/test/dev-cmd/audit_spec.rb index 36b7557c27027..190cae7ebddf6 100644 --- a/Library/Homebrew/test/dev-cmd/audit_spec.rb +++ b/Library/Homebrew/test/dev-cmd/audit_spec.rb @@ -1,4 +1,4 @@ -# typed: strict +# typed: false # frozen_string_literal: true require "dev-cmd/audit" @@ -6,4 +6,53 @@ RSpec.describe Homebrew::DevCmd::Audit do it_behaves_like "parseable arguments" + + describe "#run" do + subject(:audit) { described_class.new(["--tap=homebrew/test"]) } + + let(:tap_path) { mktmpdir } + let(:macos_only_cask_file) { tap_path/"Casks/macos-only-example.rb" } + let(:linux_cask_file) { tap_path/"Casks/linux-example.rb" } + let(:tap) { instance_double(Tap, formula_files: [], cask_files: [macos_only_cask_file, linux_cask_file]) } + + before do + macos_only_cask_file.dirname.mkpath + macos_only_cask_file.write <<~RUBY + cask "macos-only-example" do + version "1.0" + sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000", + intel: "1111111111111111111111111111111111111111111111111111111111111111" + url "https://example.invalid/x.pkg" + name "Example" + desc "macOS-only cask" + homepage "https://example.invalid/" + depends_on macos: ">= :ventura" + binary "x" + end + RUBY + linux_cask_file.write <<~RUBY + cask "linux-example" do + version "1.0" + sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000", + intel: "1111111111111111111111111111111111111111111111111111111111111111" + url "https://example.invalid/x.tar.gz" + name "Example" + desc "Linux-supported cask" + homepage "https://example.invalid/" + binary "x" + end + RUBY + + allow(Homebrew).to receive(:install_bundler_gems!) + allow(Tap).to receive(:fetch).and_call_original + allow(Tap).to receive(:fetch).with("homebrew/test").and_return(tap) + allow(Tap).to receive(:installed).and_return([]) + end + + it "skips macOS-only casks when loading tap casks on Linux" do + Homebrew::SimulateSystem.with(os: :linux) do + expect { audit.run }.to raise_error(Cask::CaskInvalidError, /linux-example.*invalid 'sha256'/) + end + end + end end