diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index c30dfa7c22..955cad92df 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -219,7 +219,7 @@ module Cask end alias == eql? - def to_hash + def to_h { "token" => token, "full_token" => full_name, @@ -243,8 +243,8 @@ module Cask } end - def to_h - hash = to_hash + def to_hash_with_variations + hash = to_h variations = {} hash_keys_to_skip = %w[outdated installed versions] @@ -252,21 +252,20 @@ module Cask if @dsl.on_system_blocks_exist? [:arm, :intel].each do |arch| MacOSVersions::SYMBOLS.each_key do |os_name| - # Big Sur is the first version of macOS that supports arm - next if arch == :arm && MacOS::Version.from_symbol(os_name) < MacOS::Version.from_symbol(:big_sur) + bottle_tag = ::Utils::Bottles::Tag.new(system: os_name, arch: arch) + next unless bottle_tag.valid_combination? Homebrew::SimulateSystem.os = os_name Homebrew::SimulateSystem.arch = arch refresh - bottle_tag = ::Utils::Bottles::Tag.new(system: os_name, arch: arch).to_sym - to_hash.each do |key, value| + to_h.each do |key, value| next if hash_keys_to_skip.include? key next if value.to_s == hash[key].to_s - variations[bottle_tag] ||= {} - variations[bottle_tag][key] = value + variations[bottle_tag.to_sym] ||= {} + variations[bottle_tag.to_sym][key] = value end end end diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 0fe8851a56..4f6d706567 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -61,6 +61,9 @@ module Homebrew switch "--all", depends_on: "--json", description: "Print JSON of all available formulae." + switch "--variations", + depends_on: "--json", + description: "Include the variations hash in each formula's JSON output." switch "-v", "--verbose", description: "Show more verbose analytics data for ." switch "--formula", "--formulae", @@ -202,6 +205,8 @@ module Homebrew if args.bottle? formulae.map(&:to_recursive_bottle_hash) + elsif args.variations? + formulae.map(&:to_hash_with_variations) else formulae.map(&:to_hash) end @@ -216,6 +221,11 @@ module Homebrew if args.bottle? { "formulae" => formulae.map(&:to_recursive_bottle_hash) } + elsif args.variations? + { + "formulae" => formulae.map(&:to_hash_with_variations), + "casks" => casks.map(&:to_hash_with_variations), + } else { "formulae" => formulae.map(&:to_hash), diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 17488e0ed1..1e8086d8e0 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2005,6 +2005,45 @@ class Formula hsh end + # @private + def to_hash_with_variations + hash = to_hash + variations = {} + + os_versions = [*MacOSVersions::SYMBOLS.keys, :linux] + + if path.exist? && self.class.on_system_blocks_exist? + formula_contents = path.read + [:arm, :intel].each do |arch| + os_versions.each do |os_name| + bottle_tag = Utils::Bottles::Tag.new(system: os_name, arch: arch) + next unless bottle_tag.valid_combination? + + Homebrew::SimulateSystem.os = os_name + Homebrew::SimulateSystem.arch = arch + + variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}") + variations_formula_class = Formulary.load_formula(name, path, formula_contents, variations_namespace, + flags: self.class.build_flags, ignore_errors: true) + variations_formula = variations_formula_class.new(name, path, :stable, + alias_path: alias_path, force_bottle: force_bottle) + + variations_formula.to_hash.each do |key, value| + next if value.to_s == hash[key].to_s + + variations[bottle_tag.to_sym] ||= {} + variations[bottle_tag.to_sym][key] = value + end + end + end + end + + Homebrew::SimulateSystem.clear + + hash["variations"] = variations + hash + end + # @api private # Generate a hash to be used to install a formula from a JSON file def to_recursive_bottle_hash(top_level: true) @@ -2469,6 +2508,7 @@ class Formula # The methods below define the formula DSL. class << self + extend Predicable include BuildEnvironment::DSL include OnSystem::MacOSAndLinux @@ -2483,6 +2523,11 @@ class Formula end end + # Whether this formula contains OS/arch-specific blocks + # (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`). + # @private + attr_predicate :on_system_blocks_exist? + # The reason for why this software is not linked (by default) to # {::HOMEBREW_PREFIX}. # @private diff --git a/Library/Homebrew/formula.rbi b/Library/Homebrew/formula.rbi index f77f4c0c50..fc8d6c255c 100644 --- a/Library/Homebrew/formula.rbi +++ b/Library/Homebrew/formula.rbi @@ -49,6 +49,7 @@ class Formula def env; end def conflicts; end + def self.on_system_blocks_exist?; end # This method is included by `OnSystem` def self.on_macos(&block); end end diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb index be59b6df8e..f52ba3cb66 100644 --- a/Library/Homebrew/test/cask/cask_spec.rb +++ b/Library/Homebrew/test/cask/cask_spec.rb @@ -211,4 +211,64 @@ describe Cask::Cask, :cask do end end end + + describe "#to_hash_with_variations" do + let!(:original_macos_version) { MacOS.full_version.to_s } + let(:expected_variations) { + <<~JSON + { + "arm64_big_sur": { + "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/arm.zip", + "version": "1.2.0", + "sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b" + }, + "monterey": { + "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.3/intel.zip" + }, + "big_sur": { + "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/intel.zip", + "version": "1.2.0", + "sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b" + }, + "catalina": { + "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip", + "version": "1.0.0", + "sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216" + }, + "mojave": { + "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip", + "version": "1.0.0", + "sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216" + } + } + JSON + } + + before do + # Use a more limited symbols list to shorten the variations hash + symbols = { + monterey: "12", + big_sur: "11", + catalina: "10.15", + mojave: "10.14", + } + stub_const("MacOSVersions::SYMBOLS", symbols) + + # For consistency, always run on Monterey and ARM + MacOS.full_version = "12" + allow(Hardware::CPU).to receive(:type).and_return(:arm) + end + + after do + MacOS.full_version = original_macos_version + end + + it "returns the correct variations hash" do + c = Cask::CaskLoader.load("multiple-versions") + h = c.to_hash_with_variations + + expect(h).to be_a(Hash) + expect(JSON.pretty_generate(h["variations"])).to eq expected_variations.strip + end + end end diff --git a/Library/Homebrew/test/cask/cmd/list_spec.rb b/Library/Homebrew/test/cask/cmd/list_spec.rb index b09c8ae9ae..86704bc041 100644 --- a/Library/Homebrew/test/cask/cmd/list_spec.rb +++ b/Library/Homebrew/test/cask/cmd/list_spec.rb @@ -120,9 +120,7 @@ describe Cask::Cmd::List, :cask do }, "conflicts_with": null, "container": null, - "auto_updates": null, - "variations": { - } + "auto_updates": null }, { "token": "local-transmission", @@ -151,9 +149,7 @@ describe Cask::Cmd::List, :cask do }, "conflicts_with": null, "container": null, - "auto_updates": null, - "variations": { - } + "auto_updates": null }, { "token": "multiple-versions", @@ -185,32 +181,7 @@ describe Cask::Cmd::List, :cask do }, "conflicts_with": null, "container": null, - "auto_updates": null, - "variations": { - "arm_big_sur": { - "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/arm.zip", - "version": "1.2.0", - "sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b" - }, - "intel_monterey": { - "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.3/intel.zip" - }, - "intel_big_sur": { - "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.2.0/intel.zip", - "version": "1.2.0", - "sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b" - }, - "intel_catalina": { - "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip", - "version": "1.0.0", - "sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216" - }, - "intel_mojave": { - "url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/1.0.0/intel.zip", - "version": "1.0.0", - "sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216" - } - } + "auto_updates": null }, { "token": "third-party-cask", @@ -239,9 +210,7 @@ describe Cask::Cmd::List, :cask do }, "conflicts_with": null, "container": null, - "auto_updates": null, - "variations": { - } + "auto_updates": null } ] EOS diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index e47a197361..d4680abd7f 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -905,6 +905,99 @@ describe Formula do expect(h["versions"]["bottle"]).to be_truthy end + describe "#to_hash_with_variations", :needs_macos do + let(:formula_path) { CoreTap.new.formula_dir/"foo-variations.rb" } + let(:formula_content) do + <<~RUBY + class FooVariations < Formula + url "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" + sha256 TESTBALL_SHA256 + + on_intel do + depends_on "intel-formula" + end + + on_big_sur do + depends_on "big-sur-formula" + end + + on_catalina :or_older do + depends_on "catalina-or-older-formula" + end + + on_linux do + depends_on "linux-formula" + end + end + RUBY + end + let(:expected_variations) { + <<~JSON + { + "arm64_big_sur": { + "dependencies": [ + "big-sur-formula" + ] + }, + "monterey": { + "dependencies": [ + "intel-formula" + ] + }, + "big_sur": { + "dependencies": [ + "intel-formula", + "big-sur-formula" + ] + }, + "catalina": { + "dependencies": [ + "intel-formula", + "catalina-or-older-formula" + ] + }, + "mojave": { + "dependencies": [ + "intel-formula", + "catalina-or-older-formula" + ] + }, + "x86_64_linux": { + "dependencies": [ + "intel-formula", + "linux-formula" + ] + } + } + JSON + } + + before do + # Use a more limited symbols list to shorten the variations hash + symbols = { + monterey: "12", + big_sur: "11", + catalina: "10.15", + mojave: "10.14", + } + stub_const("MacOSVersions::SYMBOLS", symbols) + + # For consistency, always run on Monterey and ARM + allow(MacOS).to receive(:version).and_return(MacOS::Version.new("12")) + allow(Hardware::CPU).to receive(:type).and_return(:arm) + + formula_path.dirname.mkpath + formula_path.write formula_content + end + + it "returns the correct variations hash" do + h = Formulary.factory("foo-variations").to_hash_with_variations + + expect(h).to be_a(Hash) + expect(JSON.pretty_generate(h["variations"])).to eq expected_variations.strip + end + end + specify "#to_recursive_bottle_hash" do f1 = formula "foo" do url "foo-1.0" diff --git a/Library/Homebrew/test/utils/bottles/tag_spec.rb b/Library/Homebrew/test/utils/bottles/tag_spec.rb index 1f2d5d4163..256c592635 100644 --- a/Library/Homebrew/test/utils/bottles/tag_spec.rb +++ b/Library/Homebrew/test/utils/bottles/tag_spec.rb @@ -36,4 +36,37 @@ describe Utils::Bottles::Tag do expect(tag.linux?).to be true expect(tag.to_sym).to eq(symbol) end + + describe "#standardized_arch" do + it "returns :x86_64 for :intel" do + expect(described_class.new(system: :all, arch: :intel).standardized_arch).to eq(:x86_64) + end + + it "returns :arm64 for :arm" do + expect(described_class.new(system: :all, arch: :arm).standardized_arch).to eq(:arm64) + end + end + + describe "#valid_combination?" do + it "returns true for intel archs" do + tag = described_class.new(system: :big_sur, arch: :intel) + expect(tag.valid_combination?).to be true + tag = described_class.new(system: :linux, arch: :x86_64) + expect(tag.valid_combination?).to be true + end + + it "returns false for arm archs and macos versions older than big_sur" do + tag = described_class.new(system: :catalina, arch: :arm64) + expect(tag.valid_combination?).to be false + tag = described_class.new(system: :mojave, arch: :arm) + expect(tag.valid_combination?).to be false + end + + it "returns false for arm archs and linux" do + tag = described_class.new(system: :linux, arch: :arm64) + expect(tag.valid_combination?).to be false + tag = described_class.new(system: :linux, arch: :arm) + expect(tag.valid_combination?).to be false + end + end end diff --git a/Library/Homebrew/utils/bottles.rb b/Library/Homebrew/utils/bottles.rb index 72767645cd..6ffcb815b9 100644 --- a/Library/Homebrew/utils/bottles.rb +++ b/Library/Homebrew/utils/bottles.rb @@ -169,14 +169,22 @@ module Utils [system, arch].hash end + sig { returns(Symbol) } + def standardized_arch + return :x86_64 if [:x86_64, :intel].include? arch + return :arm64 if [:arm64, :arm].include? arch + + arch + end + sig { returns(Symbol) } def to_sym if system == :all && arch == :all :all - elsif macos? && arch == :x86_64 + elsif macos? && [:x86_64, :intel].include?(arch) system else - "#{arch}_#{system}".to_sym + "#{standardized_arch}_#{system}".to_sym end end @@ -203,6 +211,15 @@ module Utils false end + sig { returns(T::Boolean) } + def valid_combination? + return true unless [:arm64, :arm].include? arch + return false if linux? + + # Big Sur is the first version of macOS that runs on ARM + to_macos_version >= :big_sur + end + sig { returns(String) } def default_prefix if linux? diff --git a/completions/bash/brew b/completions/bash/brew index 52f38dc610..86abb433c2 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -312,6 +312,7 @@ _brew_abv() { --installed --json --quiet + --variations --verbose " return @@ -1044,6 +1045,7 @@ _brew_info() { --installed --json --quiet + --variations --verbose " return diff --git a/completions/fish/brew.fish b/completions/fish/brew.fish index a2388a139b..d1ba1881af 100644 --- a/completions/fish/brew.fish +++ b/completions/fish/brew.fish @@ -307,6 +307,7 @@ __fish_brew_complete_arg 'abv' -l help -d 'Show this message' __fish_brew_complete_arg 'abv' -l installed -d 'Print JSON of formulae that are currently installed' __fish_brew_complete_arg 'abv' -l json -d 'Print a JSON representation. Currently the default value for version is `v1` for formula. For formula and cask use `v2`. See the docs for examples of using the JSON output: https://docs.brew.sh/Querying-Brew' __fish_brew_complete_arg 'abv' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'abv' -l variations -d 'Include the variations hash in each formula\'s JSON output' __fish_brew_complete_arg 'abv' -l verbose -d 'Show more verbose analytics data for formula' __fish_brew_complete_arg 'abv; and not __fish_seen_argument -l cask -l casks' -a '(__fish_brew_suggest_formulae_all)' __fish_brew_complete_arg 'abv; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_all)' @@ -757,6 +758,7 @@ __fish_brew_complete_arg 'info' -l help -d 'Show this message' __fish_brew_complete_arg 'info' -l installed -d 'Print JSON of formulae that are currently installed' __fish_brew_complete_arg 'info' -l json -d 'Print a JSON representation. Currently the default value for version is `v1` for formula. For formula and cask use `v2`. See the docs for examples of using the JSON output: https://docs.brew.sh/Querying-Brew' __fish_brew_complete_arg 'info' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'info' -l variations -d 'Include the variations hash in each formula\'s JSON output' __fish_brew_complete_arg 'info' -l verbose -d 'Show more verbose analytics data for formula' __fish_brew_complete_arg 'info; and not __fish_seen_argument -l cask -l casks' -a '(__fish_brew_suggest_formulae_all)' __fish_brew_complete_arg 'info; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_all)' diff --git a/completions/zsh/_brew b/completions/zsh/_brew index aa175d3830..7c33b10012 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -382,6 +382,7 @@ _brew_abv() { '(--all)--installed[Print JSON of formulae that are currently installed]' \ '--json[Print a JSON representation. Currently the default value for version is `v1` for formula. For formula and cask use `v2`. See the docs for examples of using the JSON output: https://docs.brew.sh/Querying-Brew]' \ '--quiet[Make some output more quiet]' \ + '--variations[Include the variations hash in each formula'\''s JSON output]' \ '--verbose[Show more verbose analytics data for formula]' \ - formula \ '(--cask)--formula[Treat all named arguments as formulae]' \ @@ -931,6 +932,7 @@ _brew_info() { '(--all)--installed[Print JSON of formulae that are currently installed]' \ '--json[Print a JSON representation. Currently the default value for version is `v1` for formula. For formula and cask use `v2`. See the docs for examples of using the JSON output: https://docs.brew.sh/Querying-Brew]' \ '--quiet[Make some output more quiet]' \ + '--variations[Include the variations hash in each formula'\''s JSON output]' \ '--verbose[Show more verbose analytics data for formula]' \ - formula \ '(--cask)--formula[Treat all named arguments as formulae]' \ diff --git a/docs/Manpage.md b/docs/Manpage.md index f74003edb1..57e1e3b4ef 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -295,6 +295,8 @@ If a *`formula`* or *`cask`* is provided, show summary of information about it. Print JSON of formulae that are currently installed. * `--all`: Print JSON of all available formulae. +* `--variations`: + Include the variations hash in each formula's JSON output. * `-v`, `--verbose`: Show more verbose analytics data for *`formula`*. * `--formula`: diff --git a/manpages/brew.1 b/manpages/brew.1 index 87c2999563..760aa819c6 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -385,6 +385,10 @@ Print JSON of formulae that are currently installed\. Print JSON of all available formulae\. . .TP +\fB\-\-variations\fR +Include the variations hash in each formula\'s JSON output\. +. +.TP \fB\-v\fR, \fB\-\-verbose\fR Show more verbose analytics data for \fIformula\fR\. .