From 0f03757e8fa1fc515012c20083f682e7118fbdae Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Mon, 2 Jun 2025 22:15:28 -0400 Subject: [PATCH] Shard JSON API by OS/Arch combination --- Library/Homebrew/api.rb | 11 +-- Library/Homebrew/dev-cmd/generate-cask-api.rb | 19 ++++- .../Homebrew/dev-cmd/generate-formula-api.rb | 19 ++++- Library/Homebrew/test/api_spec.rb | 80 +++++++++++++++++++ 4 files changed, 120 insertions(+), 9 deletions(-) diff --git a/Library/Homebrew/api.rb b/Library/Homebrew/api.rb index 2a4a920c21..abe6ff94c8 100644 --- a/Library/Homebrew/api.rb +++ b/Library/Homebrew/api.rb @@ -122,14 +122,15 @@ module Homebrew end end - sig { params(json: Hash).returns(Hash) } - def self.merge_variations(json) + sig { params(json: Hash, bottle_tag: T.nilable(::Utils::Bottles::Tag)).returns(Hash) } + def self.merge_variations(json, bottle_tag: nil) return json unless json.key?("variations") - bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os, - arch: Homebrew::SimulateSystem.current_arch) + bottle_tag ||= ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os, + arch: Homebrew::SimulateSystem.current_arch) - if (variation = json.dig("variations", bottle_tag.to_s).presence) + if (variation = json.dig("variations", bottle_tag.to_s).presence) || + (variation = json.dig("variations", bottle_tag.to_sym).presence) json = json.merge(variation) end diff --git a/Library/Homebrew/dev-cmd/generate-cask-api.rb b/Library/Homebrew/dev-cmd/generate-cask-api.rb index 40b3a1c7da..d80a9bf9a8 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-api.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-api.rb @@ -33,7 +33,7 @@ module Homebrew raise TapUnavailableError, tap.name unless tap.installed? unless args.dry_run? - directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal/v3"].freeze + directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal"].freeze FileUtils.rm_rf directories FileUtils.mkdir_p directories end @@ -44,12 +44,14 @@ module Homebrew Cask::Cask.generating_hash! + all_casks = {} latest_macos = MacOSVersion.new((HOMEBREW_MACOS_NEWEST_UNSUPPORTED.to_i - 1).to_s).to_sym Homebrew::SimulateSystem.with(os: latest_macos, arch: :arm) do tap.cask_files.each do |path| cask = Cask::CaskLoader.load(path) name = cask.token - json = JSON.pretty_generate(cask.to_hash_with_variations) + all_casks[name] = cask.to_hash_with_variations + json = JSON.pretty_generate(all_casks[name]) cask_source = path.read html_template_name = html_template(name) @@ -67,6 +69,19 @@ module Homebrew canonical_json = JSON.pretty_generate(tap.cask_renames) File.write("_data/cask_canonical.json", "#{canonical_json}\n") unless args.dry_run? + + OnSystem::ALL_OS_ARCH_COMBINATIONS.filter_map do |os, arch| + bottle_tag = Utils::Bottles::Tag.new(system: os, arch:) + next unless bottle_tag.valid_combination? + + variation_casks = all_casks.transform_values do |cask| + Homebrew::API.merge_variations(cask, bottle_tag:) + end + + unless args.dry_run? + File.write("api/internal/cask.#{bottle_tag}.json", JSON.pretty_generate(variation_casks)) + end + end end end diff --git a/Library/Homebrew/dev-cmd/generate-formula-api.rb b/Library/Homebrew/dev-cmd/generate-formula-api.rb index ac8d5dd63b..0a2cf0c61d 100644 --- a/Library/Homebrew/dev-cmd/generate-formula-api.rb +++ b/Library/Homebrew/dev-cmd/generate-formula-api.rb @@ -32,7 +32,7 @@ module Homebrew raise TapUnavailableError, tap.name unless tap.installed? unless args.dry_run? - directories = ["_data/formula", "api/formula", "formula", "api/internal/v3"] + directories = ["_data/formula", "api/formula", "formula", "api/internal"] FileUtils.rm_rf directories + ["_data/formula_canonical.json"] FileUtils.mkdir_p directories end @@ -44,12 +44,14 @@ module Homebrew Formulary.enable_factory_cache! Formula.generating_hash! + all_formulae = {} latest_macos = MacOSVersion.new((HOMEBREW_MACOS_NEWEST_UNSUPPORTED.to_i - 1).to_s).to_sym Homebrew::SimulateSystem.with(os: latest_macos, arch: :arm) do tap.formula_names.each do |name| formula = Formulary.factory(name) name = formula.name - json = JSON.pretty_generate(formula.to_hash_with_variations) + all_formulae[name] = formula.to_hash_with_variations + json = JSON.pretty_generate(all_formulae[name]) html_template_name = html_template(name) unless args.dry_run? @@ -65,6 +67,19 @@ module Homebrew canonical_json = JSON.pretty_generate(tap.formula_renames.merge(tap.alias_table)) File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run? + + OnSystem::ALL_OS_ARCH_COMBINATIONS.filter_map do |os, arch| + bottle_tag = Utils::Bottles::Tag.new(system: os, arch:) + next unless bottle_tag.valid_combination? + + variation_formulae = all_formulae.transform_values do |formula| + Homebrew::API.merge_variations(formula, bottle_tag:) + end + + unless args.dry_run? + File.write("api/internal/formula.#{bottle_tag}.json", JSON.pretty_generate(variation_formulae)) + end + end end end diff --git a/Library/Homebrew/test/api_spec.rb b/Library/Homebrew/test/api_spec.rb index 9186fc625c..dbf590f73d 100644 --- a/Library/Homebrew/test/api_spec.rb +++ b/Library/Homebrew/test/api_spec.rb @@ -90,4 +90,84 @@ RSpec.describe Homebrew::API do end end end + + describe "::merge_variations" do + let(:arm64_sequoia_tag) { Utils::Bottles::Tag.new(system: :sequoia, arch: :arm) } + let(:sonoma_tag) { Utils::Bottles::Tag.new(system: :sonoma, arch: :intel) } + let(:x86_64_linux_tag) { Utils::Bottles::Tag.new(system: :linux, arch: :intel) } + + let(:json) do + { + "name" => "foo", + "foo" => "bar", + "baz" => ["test1", "test2"], + "variations" => { + "arm64_sequoia" => { "foo" => "new" }, + :sonoma => { "baz" => ["new1", "new2", "new3"] }, + }, + } + end + + let(:arm64_sequoia_result) do + { + "name" => "foo", + "foo" => "new", + "baz" => ["test1", "test2"], + } + end + + let(:sonoma_result) do + { + "name" => "foo", + "foo" => "bar", + "baz" => ["new1", "new2", "new3"], + } + end + + it "returns the original JSON if no variations are found" do + result = described_class.merge_variations(arm64_sequoia_result, bottle_tag: arm64_sequoia_tag) + expect(result).to eq arm64_sequoia_result + end + + it "returns the original JSON if no variations are found for the current system" do + result = described_class.merge_variations(arm64_sequoia_result) + expect(result).to eq arm64_sequoia_result + end + + it "returns the original JSON without the variations if no matching variation is found" do + result = described_class.merge_variations(json, bottle_tag: x86_64_linux_tag) + expect(result).to eq json.except("variations") + end + + it "returns the original JSON without the variations if no matching variation is found for the current system" do + Homebrew::SimulateSystem.with(os: :linux, arch: :intel) do + result = described_class.merge_variations(json) + expect(result).to eq json.except("variations") + end + end + + it "returns the JSON with the matching variation applied from a string key" do + result = described_class.merge_variations(json, bottle_tag: arm64_sequoia_tag) + expect(result).to eq arm64_sequoia_result + end + + it "returns the JSON with the matching variation applied from a string key for the current system" do + Homebrew::SimulateSystem.with(os: :sequoia, arch: :arm) do + result = described_class.merge_variations(json) + expect(result).to eq arm64_sequoia_result + end + end + + it "returns the JSON with the matching variation applied from a symbol key" do + result = described_class.merge_variations(json, bottle_tag: sonoma_tag) + expect(result).to eq sonoma_result + end + + it "returns the JSON with the matching variation applied from a symbol key for the current system" do + Homebrew::SimulateSystem.with(os: :sonoma, arch: :intel) do + result = described_class.merge_variations(json) + expect(result).to eq sonoma_result + end + end + end end