Merge pull request #21495 from Homebrew/load-from-internal-api

Optionally load formulae from the internal API
This commit is contained in:
Rylan Polster
2026-02-02 14:15:30 +00:00
committed by GitHub
7 changed files with 137 additions and 120 deletions
+1 -1
View File
@@ -284,7 +284,7 @@ module Homebrew
sig { returns(T::Array[String]) }
def self.formula_names
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.formula_arrays.keys
Homebrew::API::Internal.formula_hashes.keys
else
Homebrew::API::Formula.all_formulae.keys
end
+10
View File
@@ -117,6 +117,16 @@ module Homebrew
const :version_scheme, Integer, default: 0
const :versioned_formulae, T::Array[String], default: []
sig { params(other: T.anything).returns(T::Boolean) }
def ==(other)
case other
when FormulaStruct
serialize == other.serialize
else
false
end
end
sig { params(bottle_tag: ::Utils::Bottles::Tag).returns(T.nilable(T::Hash[String, T.untyped])) }
def serialize_bottle(bottle_tag: ::Utils::Bottles.tag)
bottle_collector = ::Utils::Bottles::Collector.new
+28 -34
View File
@@ -5,7 +5,6 @@ require "cachable"
require "api"
require "api/source_download"
require "download_queue"
require "formula_stub"
module Homebrew
module API
@@ -25,35 +24,19 @@ module Homebrew
"internal/cask.#{SimulateSystem.current_tag}.jws.json"
end
sig { params(name: String).returns(Homebrew::FormulaStub) }
def self.formula_stub(name)
return cache["formula_stubs"][name] if cache.key?("formula_stubs") && cache["formula_stubs"].key?(name)
sig { params(name: String).returns(Homebrew::API::FormulaStruct) }
def self.formula_struct(name)
return cache["formula_structs"][name] if cache.key?("formula_structs") && cache["formula_structs"].key?(name)
stub_array = formula_arrays[name]
raise "No formula stub found for #{name}" unless stub_array
hash = formula_hashes[name]
raise "No formula found for #{name}" unless hash
aliases = formula_aliases.filter_map do |alias_name, original_name|
alias_name if original_name == name
end
struct = Homebrew::API::FormulaStruct.deserialize(hash, bottle_tag: SimulateSystem.current_tag)
oldnames = formula_renames.filter_map do |oldname, newname|
oldname if newname == name
end
cache["formula_structs"] ||= {}
cache["formula_structs"][name] = struct
stub = Homebrew::FormulaStub.new(
name: name,
pkg_version: PkgVersion.parse(stub_array[0]),
version_scheme: stub_array[1],
rebuild: stub_array[2],
sha256: stub_array[3],
aliases:,
oldnames:,
)
cache["formula_stubs"] ||= {}
cache["formula_stubs"][name] = stub
stub
struct
end
sig { returns(Pathname) }
@@ -87,11 +70,12 @@ module Homebrew
sig { returns(T::Boolean) }
def self.download_and_cache_formula_data!
json_contents, updated = fetch_formula_api!
cache["formula_stubs"] = {}
cache["formula_structs"] = {}
cache["formula_aliases"] = json_contents["aliases"]
cache["formula_renames"] = json_contents["renames"]
cache["formula_tap_git_head"] = json_contents["tap_git_head"]
cache["formula_tap_migrations"] = json_contents["tap_migrations"]
cache["formula_arrays"] = json_contents["formulae"]
cache["formula_hashes"] = json_contents["formulae"]
updated
end
@@ -111,9 +95,9 @@ module Homebrew
sig { params(regenerate: T::Boolean).void }
def self.write_formula_names_and_aliases(regenerate: false)
download_and_cache_formula_data! unless cache.key?("formula_arrays")
download_and_cache_formula_data! unless cache.key?("formula_hashes")
Homebrew::API.write_names_file!(formula_arrays.keys, "formula", regenerate:)
Homebrew::API.write_names_file!(formula_hashes.keys, "formula", regenerate:)
Homebrew::API.write_aliases_file!(formula_aliases, "formula", regenerate:)
end
@@ -124,14 +108,14 @@ module Homebrew
Homebrew::API.write_names_file!(cask_hashes.keys, "cask", regenerate:)
end
sig { returns(T::Hash[String, [String, Integer, Integer, T.nilable(String)]]) }
def self.formula_arrays
unless cache.key?("formula_arrays")
sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) }
def self.formula_hashes
unless cache.key?("formula_hashes")
updated = download_and_cache_formula_data!
write_formula_names_and_aliases(regenerate: updated)
end
cache["formula_arrays"]
cache["formula_hashes"]
end
sig { returns(T::Hash[String, String]) }
@@ -164,6 +148,16 @@ module Homebrew
cache["formula_tap_migrations"]
end
sig { returns(String) }
def self.formula_tap_git_head
unless cache.key?("formula_tap_git_head")
updated = download_and_cache_formula_data!
write_formula_names_and_aliases(regenerate: updated)
end
cache["formula_tap_git_head"]
end
sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) }
def self.cask_hashes
unless cache.key?("cask_hashes")
+2
View File
@@ -684,6 +684,8 @@ module Homebrew
sig { returns(T::Boolean) }
def use_internal_api?
return true if ENV["HOMEBREW_REALLY_USE_INTERNAL_API"].present?
# TODO: re-enable this when the internal API is ready again.
false
end
-43
View File
@@ -1,43 +0,0 @@
# 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 :version_scheme, Integer, default: 0
const :rebuild, Integer, default: 0
const :sha256, T.nilable(String)
const :aliases, T::Array[String], default: []
const :oldnames, T::Array[String], default: []
sig { returns(Version) }
def version
pkg_version.version
end
sig { returns(Integer) }
def revision
pkg_version.revision
end
sig { params(other: T.anything).returns(T::Boolean) }
def ==(other)
case other
when FormulaStub
name == other.name &&
pkg_version == other.pkg_version &&
version_scheme == other.version_scheme &&
rebuild == other.rebuild &&
sha256 == other.sha256 &&
aliases == other.aliases &&
oldnames == other.oldnames
else
false
end
end
end
end
+27 -17
View File
@@ -203,14 +203,15 @@ module Formulary
sig {
params(
name: String,
json_formula_with_variations: T::Hash[String, T.untyped],
tap_git_head: String,
flags: T::Array[String],
name: String,
formula_struct: Homebrew::API::FormulaStruct,
api_source: T::Hash[String, T.untyped],
tap_git_head: String,
flags: T::Array[String],
).returns(T.class_of(Formula))
}
def self.load_formula_from_json!(name, json_formula_with_variations, tap_git_head:, flags:)
namespace = :"FormulaNamespaceAPI#{namespace_key(json_formula_with_variations.to_json)}"
def self.load_formula_from_struct!(name, formula_struct, api_source:, tap_git_head:, flags:)
namespace = :"FormulaNamespaceAPI#{namespace_key(api_source.to_json)}"
mod = Module.new
remove_const(namespace) if const_defined?(namespace)
@@ -220,11 +221,10 @@ module Formulary
class_name = class_s(name)
ruby_source_path = "Formula/#{CoreTap.instance.new_formula_subdirectory(name)}/#{name.downcase}.rb"
formula_struct = Homebrew::API::Formula::FormulaStructGenerator.generate_formula_struct_hash(json_formula_with_variations)
klass = Class.new(::Formula) do
@loaded_from_api = T.let(true, T.nilable(T::Boolean))
@api_source = T.let(json_formula_with_variations, T.nilable(T::Hash[String, T.untyped]))
@api_source = T.let(api_source, T.nilable(T::Hash[String, T.untyped]))
desc formula_struct.desc
homepage formula_struct.homepage
@@ -875,16 +875,25 @@ module Formulary
sig { overridable.params(flags: T::Array[String]).void }
def load_from_api(flags:)
json_formula = if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Formula.formula_json(name)
else
Homebrew::API::Formula.all_formulae[name]
end
return load_from_internal_api(flags:) if Homebrew::EnvConfig.use_internal_api?
raise FormulaUnavailableError, name if json_formula.nil?
api_source = Homebrew::API::Formula.all_formulae[name]
raise FormulaUnavailableError, name if api_source.nil?
tap_git_head = json_formula.fetch("tap_git_head", "")
Formulary.load_formula_from_json!(name, json_formula, tap_git_head:, flags:)
tap_git_head = api_source.fetch("tap_git_head", "")
formula_struct = Homebrew::API::Formula::FormulaStructGenerator.generate_formula_struct_hash(api_source)
Formulary.load_formula_from_struct!(name, formula_struct, api_source:, tap_git_head:, flags:)
end
sig { params(flags: T::Array[String]).void }
def load_from_internal_api(flags:)
formula_struct = Homebrew::API::Internal.formula_struct(name)
api_source = Homebrew::API::Internal.formula_hashes[name]
tap_git_head = Homebrew::API::Internal.formula_tap_git_head
raise FormulaUnavailableError, name if api_source.nil?
Formulary.load_formula_from_struct!(name, formula_struct, api_source:, tap_git_head:, flags:)
end
end
@@ -901,7 +910,8 @@ module Formulary
sig { override.params(flags: T::Array[String]).void }
def load_from_api(flags:)
tap_git_head = @contents.fetch("tap_git_head", "")
Formulary.load_formula_from_json!(name, @contents, tap_git_head:, flags:)
formula_struct = Homebrew::API::Formula::FormulaStructGenerator.generate_formula_struct_hash(@contents)
Formulary.load_formula_from_struct!(name, formula_struct, api_source: @contents, tap_git_head:, flags:)
end
end
+69 -25
View File
@@ -24,9 +24,31 @@ RSpec.describe Homebrew::API::Internal do
<<~JSON
{
"formulae": {
"foo": ["1.0.0", 0, 0, "09f88b61e36045188ddb1b1ba8e402b9f3debee1770cc4ca91355eeccb5f4a38"],
"bar": ["0.4.0_5", 1, 0, "bb6e3408f39a404770529cfce548dc2666e861077acd173825cb3138c27c205a"],
"baz": ["10.4.5_2", 0, 2, "404c97537d65ca0b75c389e7d439dcefb9b56f34d3b98017669eda0d0501add7"]
"foo": {
"desc": "Foo formula",
"homepage": "https://example.com/foo",
"license": "MIT",
"ruby_source_checksum": "09f88b61e36045188ddb1b1ba8e402b9f3debee1770cc4ca91355eeccb5f4a38",
"stable_version": "1.0.0"
},
"bar": {
"desc": "Bar formula",
"homepage": "https://example.com/bar",
"license": "Apache-2.0",
"ruby_source_checksum": "bb6e3408f39a404770529cfce548dc2666e861077acd173825cb3138c27c205a",
"stable_version": "0.4.0",
"revision": 5,
"version_scheme": 1
},
"baz": {
"desc": "Baz formula",
"homepage": "https://example.com/baz",
"license": "GPL-3.0-or-later",
"ruby_source_checksum": "404c97537d65ca0b75c389e7d439dcefb9b56f34d3b98017669eda0d0501add7",
"stable_version": "10.4.5",
"revision": 2,
"bottle_rebuild": 2
}
},
"aliases": {
"foo-alias1": "foo",
@@ -38,6 +60,7 @@ RSpec.describe Homebrew::API::Internal do
"bar-old": "bar",
"baz-old": "baz"
},
"tap_git_head": "b871900717ccbb3508ca93fa56e128940b9bd371",
"tap_migrations": {
"abc": "some/tap",
"def": "another/tap"
@@ -45,25 +68,39 @@ RSpec.describe Homebrew::API::Internal do
}
JSON
end
let(:formula_arrays) do
let(:formula_hashes) do
{
"foo" => ["1.0.0", 0, 0, "09f88b61e36045188ddb1b1ba8e402b9f3debee1770cc4ca91355eeccb5f4a38"],
"bar" => ["0.4.0_5", 1, 0, "bb6e3408f39a404770529cfce548dc2666e861077acd173825cb3138c27c205a"],
"baz" => ["10.4.5_2", 0, 2, "404c97537d65ca0b75c389e7d439dcefb9b56f34d3b98017669eda0d0501add7"],
"foo" => {
"desc" => "Foo formula",
"homepage" => "https://example.com/foo",
"license" => "MIT",
"ruby_source_checksum" => "09f88b61e36045188ddb1b1ba8e402b9f3debee1770cc4ca91355eeccb5f4a38",
"stable_version" => "1.0.0",
},
"bar" => {
"desc" => "Bar formula",
"homepage" => "https://example.com/bar",
"license" => "Apache-2.0",
"ruby_source_checksum" => "bb6e3408f39a404770529cfce548dc2666e861077acd173825cb3138c27c205a",
"stable_version" => "0.4.0",
"revision" => 5,
"version_scheme" => 1,
},
"baz" => {
"desc" => "Baz formula",
"homepage" => "https://example.com/baz",
"license" => "GPL-3.0-or-later",
"ruby_source_checksum" => "404c97537d65ca0b75c389e7d439dcefb9b56f34d3b98017669eda0d0501add7",
"stable_version" => "10.4.5",
"revision" => 2,
"bottle_rebuild" => 2,
},
}
end
let(:formula_stubs) do
formula_arrays.to_h do |name, (pkg_version, version_scheme, rebuild, sha256)|
stub = Homebrew::FormulaStub.new(
name: name,
pkg_version: PkgVersion.parse(pkg_version),
version_scheme: version_scheme,
rebuild: rebuild,
sha256: sha256,
aliases: formulae_aliases.select { |_, new_name| new_name == name }.keys,
oldnames: formulae_renames.select { |_, new_name| new_name == name }.keys,
)
[name, stub]
let(:formula_structs) do
formula_hashes.to_h do |name, hash|
struct = Homebrew::API::FormulaStruct.new(**hash.transform_keys(&:to_sym))
[name, struct]
end
end
let(:formulae_aliases) do
@@ -80,6 +117,7 @@ RSpec.describe Homebrew::API::Internal do
"baz-old" => "baz",
}
end
let(:formula_tap_git_head) { "b871900717ccbb3508ca93fa56e128940b9bd371" }
let(:formula_tap_migrations) do
{
"abc" => "some/tap",
@@ -87,17 +125,17 @@ RSpec.describe Homebrew::API::Internal do
}
end
it "returns the expected formula stubs" do
it "returns the expected formula structs" do
mock_curl_download stdout: formula_json
formula_stubs.each do |name, stub|
expect(described_class.formula_stub(name)).to eq stub
formula_structs.each do |name, struct|
expect(described_class.formula_struct(name)).to eq struct
end
end
it "returns the expected formula arrays" do
it "returns the expected formula hashes" do
mock_curl_download stdout: formula_json
formula_arrays_output = described_class.formula_arrays
expect(formula_arrays_output).to eq formula_arrays
formula_hashes_output = described_class.formula_hashes
expect(formula_hashes_output).to eq formula_hashes
end
it "returns the expected formula alias list" do
@@ -112,6 +150,12 @@ RSpec.describe Homebrew::API::Internal do
expect(formula_renames_output).to eq formulae_renames
end
it "returns the expected formula tap git head" do
mock_curl_download stdout: formula_json
formula_tap_git_head_output = described_class.formula_tap_git_head
expect(formula_tap_git_head_output).to eq formula_tap_git_head
end
it "returns the expected formula tap migrations list" do
mock_curl_download stdout: formula_json
formula_tap_migrations_output = described_class.formula_tap_migrations