mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
Merge pull request #23503 from Homebrew/cask-api-platform-support
Add supported_platforms to cask API data
This commit is contained in:
@@ -344,10 +344,19 @@ module Cask
|
||||
add_error "a #{sym} stanza is required" unless cask.public_send(sym)
|
||||
end
|
||||
add_error "at least one name stanza is required" if cask.name.empty?
|
||||
# TODO: specific DSL knowledge should not be spread around in various files like this
|
||||
rejected_artifacts = [:uninstall, :zap]
|
||||
installable_artifacts = cask.artifacts.reject { |k| rejected_artifacts.include?(k) }
|
||||
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
|
||||
|
||||
installable_artifact = if cask.on_system_blocks_exist?
|
||||
begin
|
||||
OnSystem::VALID_OS_ARCH_TAGS.any? do |tag|
|
||||
cask.refresh_for_tag(tag) { cask.installable_artifact? }
|
||||
end
|
||||
ensure
|
||||
cask.refresh
|
||||
end
|
||||
else
|
||||
cask.installable_artifact?
|
||||
end
|
||||
add_error "at least one installable artifact stanza is required" unless installable_artifact
|
||||
end
|
||||
|
||||
sig { void }
|
||||
|
||||
@@ -256,6 +256,31 @@ module Cask
|
||||
artifacts.all?(Artifact::Font)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def installable_artifact?
|
||||
artifacts.any? do |artifact|
|
||||
artifact.respond_to?(:install_phase) || artifact.is_a?(Artifact::StageOnly)
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(os: Symbol).returns(T::Boolean) }
|
||||
def artifacts_supported_on_os?(os)
|
||||
case os
|
||||
when :linux
|
||||
artifacts.all? do |artifact|
|
||||
if artifact.is_a?(Artifact::Installer)
|
||||
!artifact.manual_install
|
||||
else
|
||||
Artifact::MACOS_ONLY_ARTIFACTS.exclude?(artifact.class)
|
||||
end
|
||||
end
|
||||
when :macos
|
||||
artifacts.none? { |artifact| Artifact::LINUX_ONLY_ARTIFACTS.include?(artifact.class) }
|
||||
else
|
||||
raise ArgumentError, "Unsupported operating system: #{os.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def supports_linux?
|
||||
return true if depends_on.requires_linux?
|
||||
@@ -612,17 +637,24 @@ module Cask
|
||||
|
||||
hash = to_h_with_language_variations
|
||||
variations = {}
|
||||
supported_platforms = []
|
||||
on_system_blocks_exist = dsl!.on_system_blocks_exist?
|
||||
|
||||
if dsl!.on_system_blocks_exist?
|
||||
if on_system_blocks_exist
|
||||
begin
|
||||
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
|
||||
macos_requirements = [depends_on.macos, depends_on.maximum_macos].compact
|
||||
next if bottle_tag.macos? &&
|
||||
macos_requirements.present? &&
|
||||
!dsl!.depends_on_set_in_block? &&
|
||||
macos_requirements.any? { |requirement| !requirement.allows?(bottle_tag.to_macos_version) }
|
||||
macos_requirements.any? do |requirement|
|
||||
# Avoid recursive equality between cached version-comparison keys across casks.
|
||||
!requirement.allows?(MacOSVersion.from_symbol(bottle_tag.system))
|
||||
end
|
||||
|
||||
refresh_for_tag(bottle_tag) do
|
||||
supported_platforms << bottle_tag.to_sym if platform_supported?(bottle_tag)
|
||||
|
||||
to_h_with_language_variations.each do |key, value|
|
||||
next if HASH_KEYS_TO_SKIP.include? key
|
||||
next if value.to_s == hash[key].to_s
|
||||
@@ -635,9 +667,14 @@ module Cask
|
||||
ensure
|
||||
refresh
|
||||
end
|
||||
else
|
||||
supported_platforms = OnSystem::VALID_OS_ARCH_TAGS.filter_map do |bottle_tag|
|
||||
bottle_tag.to_sym if platform_supported?(bottle_tag)
|
||||
end
|
||||
end
|
||||
|
||||
hash["variations"] = variations
|
||||
hash["supported_platforms"] = supported_platforms
|
||||
hash
|
||||
end
|
||||
|
||||
@@ -720,6 +757,28 @@ module Cask
|
||||
|
||||
private
|
||||
|
||||
sig { params(bottle_tag: ::Utils::Bottles::Tag).returns(T::Boolean) }
|
||||
def platform_supported?(bottle_tag)
|
||||
return false if bottle_tag.linux? && !supports_linux?
|
||||
return false if bottle_tag.macos? && !supports_macos?
|
||||
return false if version.blank? || sha256.blank? || url.blank?
|
||||
return false unless installable_artifact?
|
||||
return false if bottle_tag.linux? && !artifacts_supported_on_os?(:linux)
|
||||
return false if bottle_tag.macos? && !artifacts_supported_on_os?(:macos)
|
||||
|
||||
arch_supported = depends_on.arch&.any? do |arch|
|
||||
required_arch = ::Utils::Bottles::Tag.new(system: bottle_tag.system, arch: arch[:type]).standardized_arch
|
||||
required_arch == bottle_tag.standardized_arch
|
||||
end
|
||||
return false if arch_supported == false
|
||||
|
||||
return true unless bottle_tag.macos?
|
||||
|
||||
[depends_on.macos, depends_on.maximum_macos].compact.all? do |requirement|
|
||||
requirement.allows?(bottle_tag.to_macos_version)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns caveats text for API serialization, excluding conditional
|
||||
# built-in caveats that depend on the current machine's state.
|
||||
# These are stored as separate boolean fields (e.g. caveats_rosetta)
|
||||
|
||||
@@ -372,13 +372,10 @@ on_request: true)
|
||||
|
||||
sig { void }
|
||||
def check_supported_system
|
||||
# Audited casks always have an activatable artifact for the systems they
|
||||
# support, so API data without one means this system is unsupported.
|
||||
# API data without an installable artifact means this system is unsupported.
|
||||
# Source loads keep working for unaudited casks, e.g. naked containers.
|
||||
return unless @cask.loaded_from_api?
|
||||
return if @cask.artifacts.any? do |artifact|
|
||||
artifact.respond_to?(:install_phase) || artifact.is_a?(Artifact::StageOnly)
|
||||
end
|
||||
return if @cask.installable_artifact?
|
||||
|
||||
os_name = Homebrew::SimulateSystem.simulating_or_running_on_macos? ? "macOS" : "Linux"
|
||||
raise CaskError, "#{@cask}: This cask is not available on #{os_name}."
|
||||
|
||||
@@ -11,19 +11,10 @@ module OS
|
||||
|
||||
sig { void }
|
||||
def check_stanza_os_requirements
|
||||
return if !cask.depends_on.requires_macos? && artifacts.all? { |artifact| supported_artifact?(artifact) }
|
||||
return if !cask.depends_on.requires_macos? && cask.artifacts_supported_on_os?(:linux)
|
||||
|
||||
raise ::Cask::CaskError, "#{cask}: This cask requires macOS."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { params(artifact: ::Cask::Artifact::AbstractArtifact).returns(T::Boolean) }
|
||||
def supported_artifact?(artifact)
|
||||
return !artifact.manual_install if artifact.is_a?(::Cask::Artifact::Installer)
|
||||
|
||||
::Cask::Artifact::MACOS_ONLY_ARTIFACTS.exclude?(artifact.class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,17 +11,10 @@ module OS
|
||||
|
||||
sig { void }
|
||||
def check_stanza_os_requirements
|
||||
return if !cask.depends_on.requires_linux? && artifacts.all? { |artifact| supported_artifact?(artifact) }
|
||||
return if !cask.depends_on.requires_linux? && cask.artifacts_supported_on_os?(:macos)
|
||||
|
||||
raise ::Cask::CaskError, "#{cask}: This cask requires Linux."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { params(artifact: ::Cask::Artifact::AbstractArtifact).returns(T::Boolean) }
|
||||
def supported_artifact?(artifact)
|
||||
::Cask::Artifact::LINUX_ONLY_ARTIFACTS.exclude?(artifact.class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -170,6 +170,42 @@ RSpec.describe Cask::Audit, :cask do
|
||||
it { is_expected.to error_with(/#{stanza} stanza is required/) }
|
||||
end
|
||||
end
|
||||
|
||||
context "when a cask has only uninstall artifacts" do
|
||||
let(:cask) do
|
||||
Cask::Cask.new("uninstall-only") do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/foo.pkg"
|
||||
name "Uninstall Only"
|
||||
homepage "https://brew.sh/"
|
||||
uninstall pkgutil: "org.example.foo"
|
||||
zap trash: "~/Library/Caches/org.example.foo"
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to error_with("at least one installable artifact stanza is required") }
|
||||
end
|
||||
|
||||
context "when a cask has an installable artifact only on Linux" do
|
||||
let(:cask) do
|
||||
Homebrew::SimulateSystem.with(os: :sequoia, arch: :arm) do
|
||||
Cask::Cask.new("linux-only-artifact") do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/foo"
|
||||
name "Linux Only Artifact"
|
||||
homepage "https://brew.sh/"
|
||||
|
||||
on_linux do
|
||||
binary "foo"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.not_to error_with("at least one installable artifact stanza is required") }
|
||||
end
|
||||
end
|
||||
|
||||
describe "checking homepage availability" do
|
||||
|
||||
@@ -646,6 +646,13 @@ RSpec.describe Cask::Cask, :cask do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#artifacts_supported_on_os?" do
|
||||
it "rejects unknown operating systems" do
|
||||
expect { cask.artifacts_supported_on_os?(:windows) }
|
||||
.to raise_error(ArgumentError, "Unsupported operating system: :windows")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#outdated_info" do
|
||||
it "includes pinned cask details" do
|
||||
cask = Cask::CaskLoader.load("local-caffeine")
|
||||
@@ -979,6 +986,196 @@ RSpec.describe Cask::Cask, :cask do
|
||||
)
|
||||
end
|
||||
|
||||
context "when recording supported platforms" do
|
||||
let(:platform_tags) do
|
||||
[
|
||||
Utils::Bottles::Tag.new(system: :sonoma, arch: :intel),
|
||||
Utils::Bottles::Tag.new(system: :sonoma, arch: :arm),
|
||||
Utils::Bottles::Tag.new(system: :monterey, arch: :intel),
|
||||
Utils::Bottles::Tag.new(system: :monterey, arch: :arm),
|
||||
Utils::Bottles::Tag.new(system: :catalina, arch: :intel),
|
||||
Utils::Bottles::Tag.new(system: :linux, arch: :intel),
|
||||
Utils::Bottles::Tag.new(system: :linux, arch: :arm),
|
||||
]
|
||||
end
|
||||
let(:macos_platforms) { [:sonoma, :arm64_sonoma, :monterey, :arm64_monterey, :catalina] }
|
||||
|
||||
before do
|
||||
stub_const("OnSystem::VALID_OS_ARCH_TAGS", platform_tags)
|
||||
end
|
||||
|
||||
it "records platforms allowed by scoped macOS requirements" do
|
||||
c = Cask::CaskLoader.load("with-depends-on-macos-in-on-macos")
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(
|
||||
[:sonoma, :arm64_sonoma, :monterey, :arm64_monterey, :x86_64_linux, :arm64_linux],
|
||||
)
|
||||
end
|
||||
|
||||
it "excludes platforms without complete download data" do
|
||||
c = Cask::CaskLoader.load("multiple-versions")
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(macos_platforms)
|
||||
end
|
||||
|
||||
it "excludes platforms rejected by architecture requirements" do
|
||||
c = described_class.new("architecture-restricted") do
|
||||
version :latest
|
||||
arch arm: "arm64", intel: "x86_64"
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
on_linux do
|
||||
depends_on arch: :x86_64
|
||||
end
|
||||
binary "foo"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(
|
||||
[*macos_platforms, :x86_64_linux],
|
||||
)
|
||||
end
|
||||
|
||||
it "excludes Linux for macOS-only artifacts" do
|
||||
c = described_class.new("macos-artifact") do
|
||||
version :latest
|
||||
arch arm: "arm64", intel: "x86_64"
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
app "Foo.app"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(macos_platforms)
|
||||
end
|
||||
|
||||
it "excludes Linux for manual installers" do
|
||||
c = described_class.new("manual-installer") do
|
||||
version :latest
|
||||
arch arm: "arm64", intel: "x86_64"
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
installer manual: "Foo.app"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(macos_platforms)
|
||||
end
|
||||
|
||||
it "excludes macOS for Linux-only artifacts" do
|
||||
c = described_class.new("linux-artifact") do
|
||||
version :latest
|
||||
arch arm: "arm64", intel: "x86_64"
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
app_image "Foo.AppImage"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq([:x86_64_linux, :arm64_linux])
|
||||
end
|
||||
|
||||
it "includes stage-only casks" do
|
||||
c = described_class.new("stage-only") do
|
||||
version :latest
|
||||
arch arm: "arm64", intel: "x86_64"
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
stage_only true
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(platform_tags.map(&:to_sym))
|
||||
end
|
||||
|
||||
it "records no supported platforms for a cask without an installable artifact" do
|
||||
c = described_class.new("zap-only") do
|
||||
version :latest
|
||||
arch arm: "arm64", intel: "x86_64"
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
zap trash: "~/Library/Caches/brew-test"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq([])
|
||||
end
|
||||
|
||||
it "records every platform when a cask has no platform variations" do
|
||||
c = described_class.new("no-platform-variations") do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/foo.zip"
|
||||
binary "foo"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq(platform_tags.map(&:to_sym))
|
||||
end
|
||||
|
||||
it "records top-level platform requirements without variations" do
|
||||
c = described_class.new("top-level-platform-requirements") do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/foo.zip"
|
||||
depends_on macos: :monterey
|
||||
depends_on arch: :x86_64
|
||||
binary "foo"
|
||||
end
|
||||
|
||||
expect(c.to_hash_with_variations["supported_platforms"]).to eq([:sonoma, :monterey])
|
||||
end
|
||||
|
||||
it "serializes architecture-varying and universal casks with the same macOS requirement" do
|
||||
tags = OnSystem::ALL_OS_ARCH_COMBINATIONS.filter_map do |os, arch|
|
||||
tag = Utils::Bottles::Tag.new(system: os, arch:)
|
||||
tag if tag.valid_combination?
|
||||
end
|
||||
stub_const("OnSystem::VALID_OS_ARCH_TAGS", tags)
|
||||
|
||||
architecture_varying = described_class.new("architecture-varying") do
|
||||
arch arm: "ARM64", intel: "64"
|
||||
version "1.2.3"
|
||||
sha256 arm: "a" * 64,
|
||||
intel: "b" * 64
|
||||
url "https://brew.sh/#{arch}.zip"
|
||||
depends_on macos: :big_sur
|
||||
app "Foo.app"
|
||||
end
|
||||
universal = described_class.new("universal") do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/foo.zip"
|
||||
depends_on macos: :big_sur
|
||||
app "Foo.app"
|
||||
end
|
||||
|
||||
architecture_varying.to_hash_with_variations
|
||||
supported_platforms = Timeout.timeout(5) do
|
||||
universal.to_hash_with_variations["supported_platforms"]
|
||||
end
|
||||
|
||||
expected_platforms = tags.filter_map do |tag|
|
||||
tag.to_sym if tag.macos? && tag.system != :catalina
|
||||
end
|
||||
expect(supported_platforms).to eq(expected_platforms)
|
||||
end
|
||||
|
||||
it "isolates macOS requirement comparisons between casks" do
|
||||
supported_platforms = [:monterey, :sonoma].map do |minimum_macos|
|
||||
c = described_class.new("requires-#{minimum_macos}") do
|
||||
version :latest
|
||||
sha256 :no_check
|
||||
url "https://brew.sh/foo.zip"
|
||||
depends_on macos: minimum_macos
|
||||
binary "foo"
|
||||
end
|
||||
|
||||
c.to_hash_with_variations["supported_platforms"]
|
||||
end
|
||||
|
||||
expect(supported_platforms).to eq(
|
||||
[
|
||||
[:sonoma, :arm64_sonoma, :monterey, :arm64_monterey],
|
||||
[:sonoma, :arm64_sonoma],
|
||||
],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: The calls to `Cask.generating_hash!` and `Cask.generated_hash!`
|
||||
# are not idempotent so they can only be used in one test.
|
||||
it "returns the correct hash placeholders" do
|
||||
|
||||
Reference in New Issue
Block a user