From 8ec57eab2bf75b066f47af37f193879a7656f420 Mon Sep 17 00:00:00 2001 From: Rylan Polster Date: Wed, 4 Jun 2025 22:30:47 -0400 Subject: [PATCH] Load formulae from the internal API with `HOMEBREW_USE_INTERNAL_API` --- Library/Homebrew/api.rb | 1 + Library/Homebrew/api/internal.rb | 72 +++++++++++++++++++++++++++++ Library/Homebrew/bottle.rb | 6 ++- Library/Homebrew/formula_stub.rb | 24 ++++++++++ Library/Homebrew/formulary.rb | 6 ++- Library/Homebrew/github_packages.rb | 4 ++ Library/Homebrew/resource.rb | 11 +++++ 7 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 Library/Homebrew/api/internal.rb create mode 100644 Library/Homebrew/formula_stub.rb diff --git a/Library/Homebrew/api.rb b/Library/Homebrew/api.rb index 3948bcde3d..2053e90c70 100644 --- a/Library/Homebrew/api.rb +++ b/Library/Homebrew/api.rb @@ -4,6 +4,7 @@ require "api/analytics" require "api/cask" require "api/formula" +require "api/internal" require "base64" module Homebrew diff --git a/Library/Homebrew/api/internal.rb b/Library/Homebrew/api/internal.rb new file mode 100644 index 0000000000..0ddfbede9f --- /dev/null +++ b/Library/Homebrew/api/internal.rb @@ -0,0 +1,72 @@ +# typed: strict +# frozen_string_literal: true + +require "extend/cachable" +require "api/download" +require "formula_stub" + +module Homebrew + module API + # Helper functions for using the JSON internal API. + module Internal + extend Cachable + + private_class_method :cache + + sig { returns(String) } + def self.endpoint + "internal/formula.#{SimulateSystem.current_tag}.jws.json" + end + + sig { params(name: String).returns(T::Hash[String, T.untyped]) } + def self.formula(name) + tag = Utils::Bottles.tag + formula_stub = Homebrew::FormulaStub.from_array formula_stub(name) + + bottle_specification = BottleSpecification.new + bottle_specification.tap = Homebrew::DEFAULT_REPOSITORY + bottle_specification.rebuild formula_stub.rebuild + bottle_specification.sha256 tag.to_sym => formula_stub.sha256 + + bottle = Bottle.new(formula_stub, bottle_specification, tag) + bottle_manifest_resource = T.must(bottle.github_packages_manifest_resource) + + begin + bottle_manifest_resource.fetch + bottle_manifest_resource.formula_json + rescue Resource::BottleManifest::Error + opoo "Falling back to API fetch for #{name}" + Homebrew::API.fetch "formula/#{name}.json" + end + end + + sig { returns(Pathname) } + def self.cached_json_file_path + HOMEBREW_CACHE_API/endpoint + end + + sig { returns(T::Boolean) } + def self.download_and_cache_data! + json_formula_stubs, updated = Homebrew::API.fetch_json_api_file endpoint + cache["formula_stubs"] = {} + cache["all_formula_stubs"] = json_formula_stubs + updated + end + private_class_method :download_and_cache_data! + + sig { params(name: String).returns([String, String, Integer, T.nilable(String)]) } + def self.formula_stub(name) + download_and_cache_data! unless cache.key?("all_formula_stubs") + + return cache["formula_stubs"][name] if cache["formula_stubs"].key?(name) + + cache["all_formula_stubs"].find do |stub| + next false if stub["name"] != name + + cache["formula_stubs"][name] = stub + true + end + end + end + end +end diff --git a/Library/Homebrew/bottle.rb b/Library/Homebrew/bottle.rb index 6aa2aa3f26..cd35f52bbb 100644 --- a/Library/Homebrew/bottle.rb +++ b/Library/Homebrew/bottle.rb @@ -7,7 +7,11 @@ class Bottle class Filename attr_reader :name, :version, :tag, :rebuild - sig { params(formula: Formula, tag: Utils::Bottles::Tag, rebuild: Integer).returns(T.attached_class) } + sig { + params(formula: T.any(Formula, Homebrew::FormulaStub), + tag: Utils::Bottles::Tag, + rebuild: Integer).returns(T.attached_class) + } def self.create(formula, tag, rebuild) new(formula.name, formula.pkg_version, tag, rebuild) end diff --git a/Library/Homebrew/formula_stub.rb b/Library/Homebrew/formula_stub.rb new file mode 100644 index 0000000000..15d975ca20 --- /dev/null +++ b/Library/Homebrew/formula_stub.rb @@ -0,0 +1,24 @@ +# typed: strict +# frozen_string_literal: true + +require "pkg_version" + +module Homebrew + # A stub for a formula, with only the information needed to fetch the bottle manifest. + class FormulaStub < T::Struct + const :name, String + const :pkg_version, PkgVersion + const :rebuild, Integer + const :sha256, T.nilable(String) + + sig { params(array: [String, String, Integer, T.nilable(String)]).returns(FormulaStub) } + def self.from_array(array) + new( + name: array[0], + pkg_version: PkgVersion.parse(array[1]), + rebuild: array[2], + sha256: array[3], + ) + end + end +end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 2f191fb787..5c48aa813a 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -165,7 +165,11 @@ module Formulary mod.const_set(:BUILD_FLAGS, flags) class_name = class_s(name) - json_formula = Homebrew::API::Formula.all_formulae[name] + json_formula = if ENV.fetch("HOMEBREW_USE_INTERNAL_API", false).present? + Homebrew::API::Internal.formula(name) + else + Homebrew::API::Formula.all_formulae[name] + end raise FormulaUnavailableError, name if json_formula.nil? json_formula = Homebrew::API.merge_variations(json_formula) diff --git a/Library/Homebrew/github_packages.rb b/Library/Homebrew/github_packages.rb index 8e9c37f5ca..b13659c324 100644 --- a/Library/Homebrew/github_packages.rb +++ b/Library/Homebrew/github_packages.rb @@ -312,6 +312,9 @@ class GitHubPackages SPDX.license_expression_to_string(:cannot_represent) end + formula_path = HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"] + formula = Formulary.factory(formula_path) + formula_annotations_hash = { "com.github.package.type" => GITHUB_PACKAGE_TYPE, "org.opencontainers.image.created" => created_date, @@ -325,6 +328,7 @@ class GitHubPackages "org.opencontainers.image.url" => bottle_hash["formula"]["homepage"], "org.opencontainers.image.vendor" => org, "org.opencontainers.image.version" => version, + "sh.brew.formula.json" => formula.to_hash_with_variations, }.compact_blank manifests = [] end diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index 2b37c2ec43..c259bb7bcd 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -329,6 +329,17 @@ class Resource end end + def formula_json + formula_json = manifest_annotations["sh.brew.formula.json"] + raise Error, "Couldn't find formula JSON from manifest." if formula_json.blank? + + begin + JSON.parse(formula_json) + rescue JSON::ParserError + raise Error, "Couldn't parse formula JSON." + end + end + sig { returns(T.nilable(Integer)) } def bottle_size manifest_annotations["sh.brew.bottle.size"]&.to_i