diff --git a/Library/Homebrew/cask/artifact.rb b/Library/Homebrew/cask/artifact.rb index e8605c3135431..2fb6ea116f569 100644 --- a/Library/Homebrew/cask/artifact.rb +++ b/Library/Homebrew/cask/artifact.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "cask/artifact/app" +require "cask/artifact/appimage" require "cask/artifact/artifact" # generic 'artifact' stanza require "cask/artifact/audio_unit_plugin" require "cask/artifact/binary" @@ -53,5 +54,9 @@ module Artifact ::Cask::Artifact::VstPlugin, ::Cask::Artifact::Vst3Plugin, ].freeze + + LINUX_ONLY_ARTIFACTS = [ + ::Cask::Artifact::AppImage, + ].freeze end end diff --git a/Library/Homebrew/cask/artifact/appimage.rb b/Library/Homebrew/cask/artifact/appimage.rb new file mode 100644 index 0000000000000..cb934e7b295bf --- /dev/null +++ b/Library/Homebrew/cask/artifact/appimage.rb @@ -0,0 +1,15 @@ +# typed: strict +# frozen_string_literal: true + +require "cask/artifact/symlinked" + +module Cask + module Artifact + class AppImage < Symlinked + sig { params(target: T.any(String, Pathname)).returns(Pathname) } + def resolve_target(target) + config.appimagedir/target + end + end + end +end diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index 2370186f62b02..7566cf8a46206 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -164,17 +164,67 @@ def font? end sig { returns(T::Boolean) } - def supports_macos? = true + def supports_macos? + return true if font? + + if @dsl.on_system_blocks_exist? + any_loaded = false + begin + OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag| + next unless bottle_tag.macos? + + Homebrew::SimulateSystem.with_tag(bottle_tag) do + refresh + + any_loaded = true + return false if artifacts.any? do |artifact| + ::Cask::Artifact::LINUX_ONLY_ARTIFACTS.include?(artifact.class) + end + end + end + rescue CaskInvalidError => e + # Invalid cask for macOS + odebug e.message + ensure + refresh + end + + return false unless any_loaded + end + + true + end sig { returns(T::Boolean) } def supports_linux? return true if font? - return false if artifacts.any? do |artifact| - ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class) + if @dsl.on_system_blocks_exist? + any_loaded = false + begin + OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag| + next unless bottle_tag.linux? + + Homebrew::SimulateSystem.with_tag(bottle_tag) do + refresh + + any_loaded = true + return false if artifacts.any? do |artifact| + ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class) + end + end + end + rescue CaskInvalidError + # Invalid cask for Linux + ensure + refresh + end + + return false unless any_loaded end - @dsl.os.present? + # Only assume Linux support if there is an explicit `on_system` stanza + @dsl.on_system_blocks_exist? end # The caskfile is needed during installation when there are diff --git a/Library/Homebrew/cask/config.rb b/Library/Homebrew/cask/config.rb index fbf5a426d12ee..9b462d794cd63 100644 --- a/Library/Homebrew/cask/config.rb +++ b/Library/Homebrew/cask/config.rb @@ -16,6 +16,7 @@ class Config DEFAULT_DIRS = T.let( { appdir: "/Applications", + appimagedir: "~/Applications", keyboard_layoutdir: "/Library/Keyboard Layouts", colorpickerdir: "~/Library/ColorPickers", prefpanedir: "~/Library/PreferencePanes", @@ -49,6 +50,7 @@ def self.from_args(args) args = T.unsafe(args) new(explicit: { appdir: args.appdir, + appimagedir: args.appimagedir, keyboard_layoutdir: args.keyboard_layoutdir, colorpickerdir: args.colorpickerdir, prefpanedir: args.prefpanedir, diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 6dac4e258543e..f269ec7a3dcfe 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -38,6 +38,7 @@ class DSL ORDINARY_ARTIFACT_CLASSES = [ Artifact::Installer, Artifact::App, + Artifact::AppImage, Artifact::Artifact, Artifact::AudioUnitPlugin, Artifact::Binary, diff --git a/Library/Homebrew/cli/parser.rb b/Library/Homebrew/cli/parser.rb index 4bdf353adb2f7..69edb02b82f08 100644 --- a/Library/Homebrew/cli/parser.rb +++ b/Library/Homebrew/cli/parser.rb @@ -66,6 +66,10 @@ def self.global_cask_options description: "Target location for Applications " \ "(default: `#{Cask::Config::DEFAULT_DIRS[:appdir]}`).", }], + [:flag, "--appimagedir=", { + description: "Target location for AppImages " \ + "(default: `#{Cask::Config::DEFAULT_DIRS[:appimagedir]}`).", + }], [:flag, "--keyboard-layoutdir=", { description: "Target location for Keyboard Layouts " \ "(default: `#{Cask::Config::DEFAULT_DIRS[:keyboard_layoutdir]}`).", diff --git a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb index aa257c4d41be8..fbc160a52b65d 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb @@ -127,33 +127,61 @@ def run sig { params(cask: Cask::Cask).returns(T::Hash[T::Hash[Symbol, T.any(Symbol, String)], Float]) } def filter_runners(cask) - filtered_macos_runners = RUNNERS.select do |runner, _| - runner[:symbol] != :linux && + filtered_runners = if cask.supports_macos? + filtered_macos_runners = MACOS_RUNNERS.select do |runner, _| cask.depends_on.macos.present? && - cask.depends_on.macos.allows?(MacOSVersion.from_symbol(T.must(runner[:symbol]).to_sym)) - end + cask.depends_on.macos.allows?(MacOSVersion.from_symbol(T.must(runner[:symbol]).to_sym)) + end + + return MACOS_RUNNERS.dup if filtered_macos_runners.none? - filtered_runners = if filtered_macos_runners.any? filtered_macos_runners - else - RUNNERS.dup end - filtered_runners = filtered_runners.merge(LINUX_RUNNERS) if cask.supports_linux? - archs = architectures(cask:) + odebug "Architectures macOS: #{archs}" if archs.any? filtered_runners.select! do |runner, _| archs.include?(runner.fetch(:arch)) end + odebug "Filtered runners: #{filtered_runners.keys.map { |r| r[:name] }}" if filtered_runners.any? + if cask.supports_linux? + filtered_linux_runners = LINUX_RUNNERS.dup + + archs = architectures(cask:, os: :linux, arch: :intel) + filtered_linux_runners.select! do |runner, _| + archs.include?(runner.fetch(:arch)) + end + + filtered_runners.merge!(filtered_linux_runners) + end + + odebug "Architectures linux: #{archs}" if archs.any? + odebug "Filtered runners: #{filtered_runners.keys.map { |r| r[:name] }}" if filtered_runners.any? + filtered_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, arch: Symbol).returns(T::Array[Symbol]) } + def architectures(cask:, os: :macos, arch: :arm) + architectures = [] + begin + 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 { @@ -255,7 +283,7 @@ def generate_matrix(tap, labels: [], cask_names: [], skip_install: false, new_ca native_runner_arch = arch == runner.fetch(:arch) # we don't need to run simulated archs on Linux next if runner.fetch(:symbol) == :linux && !native_runner_arch - # we don't need to run simulated archs on macOS + # we don't need to run simulated archs on macOS Sequoia 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/extend/os/cask/installer.rb b/Library/Homebrew/extend/os/cask/installer.rb index a7a6e0c277d9e..f435378b2ae74 100644 --- a/Library/Homebrew/extend/os/cask/installer.rb +++ b/Library/Homebrew/extend/os/cask/installer.rb @@ -2,3 +2,4 @@ # frozen_string_literal: true require "extend/os/linux/cask/installer" if OS.linux? +require "extend/os/mac/cask/installer" if OS.mac? diff --git a/Library/Homebrew/extend/os/linux/cask/installer.rb b/Library/Homebrew/extend/os/linux/cask/installer.rb index 7388daeac4939..e8c9063ec1182 100644 --- a/Library/Homebrew/extend/os/linux/cask/installer.rb +++ b/Library/Homebrew/extend/os/linux/cask/installer.rb @@ -11,9 +11,7 @@ module Installer sig { void } def check_stanza_os_requirements - return unless artifacts.any? do |artifact| - ::Cask::Artifact::MACOS_ONLY_ARTIFACTS.include?(artifact.class) - end + return unless @cask.supports_linux? raise ::Cask::CaskError, "macOS is required for this software." end diff --git a/Library/Homebrew/extend/os/mac/cask/installer.rb b/Library/Homebrew/extend/os/mac/cask/installer.rb new file mode 100644 index 0000000000000..b7a33e4084dcb --- /dev/null +++ b/Library/Homebrew/extend/os/mac/cask/installer.rb @@ -0,0 +1,23 @@ +# typed: strict +# frozen_string_literal: true + +module OS + module Mac + module Cask + module Installer + extend T::Helpers + + requires_ancestor { ::Cask::Installer } + + sig { void } + def check_stanza_os_requirements + return unless @cask.supports_macos? + + raise ::Cask::CaskError, "Linux is required for this software." + end + end + end + end +end + +Cask::Installer.prepend(OS::Mac::Cask::Installer)