Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Library/Homebrew/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require "api/cask"
require "api/formula"
require "api/internal"
require "api/formula_struct"
require "base64"
require "utils/output"

Expand Down
62 changes: 62 additions & 0 deletions Library/Homebrew/api/formula_struct.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# typed: strict
# frozen_string_literal: true

class FormulaStruct < T::Struct
const :uses_from_macos, T::Array[T.any(String, T::Hash[String, String])], default: []
const :requirements, T::Array[T::Hash[String, T.untyped]], default: []
const :dependencies, T::Array[String], default: []
const :build_dependencies, T::Array[String], default: []
const :test_dependencies, T::Array[String], default: []
const :recommended_dependencies, T::Array[String], default: []
const :optional_dependencies, T::Array[String], default: []
const :stable_dependencies, T::Hash[String, T::Array[String]], default: { dependencies: [], build_dependencies: [], test_dependencies: [], recommended_dependencies: [], optional_dependencies: [], uses_from_macos_bounds: [] }
const :head_dependencies, T::Hash[String, T::Array[String]], default: { dependencies: [], build_dependencies: [], test_dependencies: [], recommended_dependencies: [], optional_dependencies: [], uses_from_macos_bounds: [] }
const :caveats, T.nilable(String)
const :desc, String
const :homepage, String
const :license, String
const :revision, Integer, default: 0
const :version_scheme, Integer, default: 0
const :no_autobump_msg, T.nilable(String)
const :pour_bottle_only_if, T.nilable(String)
const :keg_only_reason, T.nilable(T::Hash[String, String])
const :deprecation_date, T.nilable(String)
const :deprecation_reason, T.nilable(String)
const :deprecation_replacement_formula, T.nilable(String)
const :deprecation_replacement_cask, T.nilable(String)
const :disable_date, T.nilable(String)
const :disable_reason, T.nilable(String)
const :disable_replacement_formula, T.nilable(String)
const :disable_replacement_cask, T.nilable(String)
const :conflicts_with, T::Array[String], default: []
const :conflicts_with_reasons, T::Array[String], default: []
const :link_overwrite, T::Array[String], default: []
const :post_install_defined, T::Boolean, default: false
const :service, T.nilable(T::Hash[String, T.untyped])
const :tap_git_head, String
const :oldnames, T.nilable(T::Array[String])
const :oldname, T.nilable(String)
const :ruby_source_path, String
const :ruby_source_checksum, T::Hash[String, String]
const :urls, T::Hash[String, T::Hash[String, T.nilable(String)]]
const :ruby_source_sha256, T.nilable(String)
const :aliases, T::Array[String], default: []
const :versioned_formulae, T::Array[String], default: []
const :versions, T::Hash[String, String]
const :bottle, T::Hash[String, T.untyped], default: {}
const :uses_from_macos_bounds, T.nilable(T::Array[T::Hash[String, String]])

sig { params(hash: T::Hash[String, T.untyped]).returns(FormulaStruct) }
def self.from_hash(hash)
hash["stable_dependencies"] ||= {
"dependencies" => hash["dependencies"] || [],
"build_dependencies" => hash["build_dependencies"] || [],
"test_dependencies" => hash["test_dependencies"] || [],
"recommended_dependencies" => hash["recommended_dependencies"] || [],
"optional_dependencies" => hash["optional_dependencies"] || [],
"uses_from_macos_bounds" => hash["uses_from_macos_bounds"] || [],
}

super
end
end
104 changes: 56 additions & 48 deletions Library/Homebrew/formulary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,11 @@ def self.load_formula_from_path(name, path, flags:, ignore_errors:)
platform_cache.fetch(:path)[path.to_s] = klass
end

sig { params(name: String, json_formula_with_variations: T::Hash[String, T.untyped], flags: T::Array[String]).returns(T.class_of(Formula)) }
def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
sig {
params(name: String, json_formula_with_variations: T::Hash[String, T.untyped], formula_struct: FormulaStruct,
flags: T::Array[String]).returns(T.class_of(Formula))
}
def self.load_formula_from_json!(name, json_formula_with_variations, formula_struct, flags:)
namespace = :"FormulaNamespaceAPI#{namespace_key(json_formula_with_variations.to_json)}"

mod = Module.new
Expand All @@ -227,18 +230,19 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
mod.const_set(:BUILD_FLAGS, flags)

class_name = class_s(name)
json_formula = Homebrew::API.merge_variations(json_formula_with_variations)

caveats_string = (replace_placeholders(json_formula["caveats"]) if json_formula["caveats"])
caveats_string = if (caveats = formula_struct.caveats)
replace_placeholders(caveats)
end

uses_from_macos_names = json_formula.fetch("uses_from_macos", []).map do |dep|
uses_from_macos_names = formula_struct.uses_from_macos.map do |dep|
next dep unless dep.is_a? Hash

dep.keys.first
end
Comment on lines +238 to 242
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ideally this (and others below) wouldn't need mapped (or filtered/nexted) but are instead already in the format needed for the DSL.


requirements = {}
json_formula["requirements"]&.map do |req|
formula_struct.requirements.map do |req|
req_name = req["name"].to_sym
next if API_SUPPORTED_REQUIREMENTS.exclude?(req_name)

Expand Down Expand Up @@ -277,11 +281,11 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
add_deps = lambda do |spec|
T.bind(self, SoftwareSpec)

dep_json = json_formula.fetch("#{spec}_dependencies", json_formula)
dep_json = formula_struct.send("#{spec}_dependencies")

dep_json["dependencies"]&.each do |dep|
# Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux
next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) &&
next if formula_struct.uses_from_macos_bounds.nil? && uses_from_macos_names.include?(dep) &&
!Homebrew::SimulateSystem.simulating_or_running_on_macos?

depends_on dep
Expand All @@ -290,7 +294,7 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
[:build, :test, :recommended, :optional].each do |type|
dep_json["#{type}_dependencies"]&.each do |dep|
# Backwards compatibility check - uses_from_macos used to be a part of dependencies on Linux
next if !json_formula.key?("uses_from_macos_bounds") && uses_from_macos_names.include?(dep) &&
next if formula_struct.uses_from_macos_bounds.nil? && uses_from_macos_names.include?(dep) &&
!Homebrew::SimulateSystem.simulating_or_running_on_macos?

depends_on dep => type
Expand All @@ -314,22 +318,24 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
@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]))

desc json_formula["desc"]
homepage json_formula["homepage"]
license SPDX.string_to_license_expression(json_formula["license"])
revision json_formula.fetch("revision", 0)
version_scheme json_formula.fetch("version_scheme", 0)
desc formula_struct.desc
homepage formula_struct.homepage
license SPDX.string_to_license_expression(formula_struct.license)
revision formula_struct.revision
version_scheme formula_struct.version_scheme

if (urls_stable = json_formula["urls"]["stable"].presence)
if (urls_stable = formula_struct.urls["stable"].presence)
stable do
url_spec = {
tag: urls_stable["tag"],
revision: urls_stable["revision"],
using: urls_stable["using"]&.to_sym,
}.compact
url urls_stable["url"], **url_spec
version json_formula["versions"]["stable"]
sha256 urls_stable["checksum"] if urls_stable["checksum"].present?
url T.must(urls_stable.fetch("url")), **url_spec
version formula_struct.versions["stable"]
if (checksum = urls_stable["checksum"])
sha256 checksum
end

instance_exec(:stable, &add_deps)
requirements[:stable]&.each do |req|
Expand All @@ -338,13 +344,13 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
end
end

if (urls_head = json_formula["urls"]["head"].presence)
if (urls_head = formula_struct.urls["head"].presence)
head do
url_spec = {
branch: urls_head["branch"],
using: urls_head["using"]&.to_sym,
}.compact
url urls_head["url"], **url_spec
url T.must(urls_head.fetch("url")), **url_spec

instance_exec(:head, &add_deps)
requirements[:head]&.each do |req|
Expand All @@ -353,12 +359,12 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
end
end

if (because = json_formula["no_autobump_msg"])
if (because = formula_struct.no_autobump_msg)
because = because.to_sym if NO_AUTOBUMP_REASONS_LIST.key?(because.to_sym)
no_autobump!(because:)
end

bottles_stable = json_formula["bottle"]["stable"].presence
bottles_stable = formula_struct.bottle["stable"].presence

if bottles_stable
bottle do
Expand All @@ -375,48 +381,48 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
end
end

if (pour_bottle_only_if = json_formula["pour_bottle_only_if"])
if (pour_bottle_only_if = formula_struct.pour_bottle_only_if)
pour_bottle? only_if: pour_bottle_only_if.to_sym
end

if (keg_only_reason = json_formula["keg_only_reason"].presence)
reason = Formulary.convert_to_string_or_symbol keg_only_reason["reason"]
keg_only reason, keg_only_reason["explanation"]
if (keg_only_reason = formula_struct.keg_only_reason.presence)
reason = Formulary.convert_to_string_or_symbol keg_only_reason.fetch("reason")
keg_only reason, keg_only_reason.fetch("explanation")
end

if (deprecation_date = json_formula["deprecation_date"].presence)
reason = DeprecateDisable.to_reason_string_or_symbol json_formula["deprecation_reason"], type: :formula
replacement_formula = json_formula["deprecation_replacement_formula"]
replacement_cask = json_formula["deprecation_replacement_cask"]
if (deprecation_date = formula_struct.deprecation_date.presence)
reason = DeprecateDisable.to_reason_string_or_symbol formula_struct.deprecation_reason, type: :formula
replacement_formula = formula_struct.deprecation_replacement_formula
replacement_cask = formula_struct.deprecation_replacement_cask
deprecate! date: deprecation_date, because: reason, replacement_formula:, replacement_cask:
end

if (disable_date = json_formula["disable_date"].presence)
reason = DeprecateDisable.to_reason_string_or_symbol json_formula["disable_reason"], type: :formula
replacement_formula = json_formula["disable_replacement_formula"]
replacement_cask = json_formula["disable_replacement_cask"]
if (disable_date = formula_struct.disable_date.presence)
reason = DeprecateDisable.to_reason_string_or_symbol formula_struct.disable_reason, type: :formula
replacement_formula = formula_struct.disable_replacement_formula
replacement_cask = formula_struct.disable_replacement_cask
disable! date: disable_date, because: reason, replacement_formula:, replacement_cask:
end
Comment on lines +400 to 405
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly this looks perhaps like a formula_struct.disable or something should contain all relevant detail but also be nilable.


json_formula["conflicts_with"]&.each_with_index do |conflict, index|
conflicts_with conflict, because: json_formula.dig("conflicts_with_reasons", index)
formula_struct.conflicts_with.zip(formula_struct.conflicts_with_reasons) do |conflict, reason|
conflicts_with conflict, because: reason
end

json_formula["link_overwrite"]&.each do |overwrite_path|
formula_struct.link_overwrite.each do |overwrite_path|
link_overwrite overwrite_path
end

define_method(:install) do
raise NotImplementedError, "Cannot build from source from abstract formula."
end

@post_install_defined_boolean = T.let(json_formula["post_install_defined"], T.nilable(T::Boolean))
@post_install_defined_boolean = T.let(formula_struct.post_install_defined, T.nilable(T::Boolean))
@post_install_defined_boolean = true if @post_install_defined_boolean.nil? # Backwards compatibility
define_method(:post_install_defined?) do
self.class.instance_variable_get(:@post_install_defined_boolean)
end

if (service_hash = json_formula["service"].presence)
if (service_hash = formula_struct.service.presence)
service_hash = Homebrew::Service.from_hash(service_hash)
service do
T.bind(self, Homebrew::Service)
Expand Down Expand Up @@ -445,33 +451,33 @@ def self.load_formula_from_json!(name, json_formula_with_variations, flags:)
self.class.instance_variable_get(:@caveats_string)
end

@tap_git_head_string = T.let(json_formula["tap_git_head"], T.nilable(String))
@tap_git_head_string = T.let(formula_struct.tap_git_head, T.nilable(String))
define_method(:tap_git_head) do
self.class.instance_variable_get(:@tap_git_head_string)
end

@oldnames_array = T.let(json_formula["oldnames"] || [json_formula["oldname"]].compact, T.nilable(T::Array[String]))
@oldnames_array = T.let(formula_struct.oldnames || [formula_struct.oldname].compact, T.nilable(T::Array[String]))
define_method(:oldnames) do
self.class.instance_variable_get(:@oldnames_array)
end

@aliases_array = T.let(json_formula.fetch("aliases", []), T.nilable(T::Array[String]))
@aliases_array = T.let(formula_struct.aliases, T.nilable(T::Array[String]))
define_method(:aliases) do
self.class.instance_variable_get(:@aliases_array)
end

@versioned_formulae_array = T.let(json_formula.fetch("versioned_formulae", []), T.nilable(T::Array[String]))
@versioned_formulae_array = T.let(formula_struct.versioned_formulae, T.nilable(T::Array[String]))
define_method(:versioned_formulae_names) do
self.class.instance_variable_get(:@versioned_formulae_array)
end

@ruby_source_path_string = T.let(json_formula["ruby_source_path"], T.nilable(String))
@ruby_source_path_string = T.let(formula_struct.ruby_source_path, T.nilable(String))
define_method(:ruby_source_path) do
self.class.instance_variable_get(:@ruby_source_path_string)
end

@ruby_source_checksum_string = T.let(json_formula.dig("ruby_source_checksum", "sha256"), T.nilable(String))
@ruby_source_checksum_string ||= json_formula["ruby_source_sha256"]
@ruby_source_checksum_string = T.let(formula_struct.ruby_source_checksum.fetch("sha256"), T.nilable(String))
@ruby_source_checksum_string ||= formula_struct.ruby_source_sha256
define_method(:ruby_source_checksum) do
checksum = self.class.instance_variable_get(:@ruby_source_checksum_string)
Checksum.new(checksum) if checksum
Expand Down Expand Up @@ -1079,7 +1085,8 @@ def load_from_api(flags:)

raise FormulaUnavailableError, name if json_formula.nil?

Formulary.load_formula_from_json!(name, json_formula, flags:)
formula_struct = FormulaStruct.from_hash(json_formula)
Formulary.load_formula_from_json!(name, json_formula, formula_struct, flags:)
end
end

Expand All @@ -1095,7 +1102,8 @@ def initialize(name, contents, tap: nil, alias_name: nil)

sig { override.params(flags: T::Array[String]).void }
def load_from_api(flags:)
Formulary.load_formula_from_json!(name, @contents, flags:)
formula_struct = FormulaStruct.from_hash(@contents)
Formulary.load_formula_from_json!(name, @contents, formula_struct, flags:)
end
end

Expand Down
Loading