From c1d85bf598d80d80f11c93e7f5e31960a35a072c Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sat, 27 Jan 2024 12:33:17 -0800 Subject: [PATCH 1/7] formula: add #to_api_hash This will be used internally to generate a slimmer api hash representation of formulas that will require less space and be faster to load. Changes: - Added #to_api_hash - Modified #to_hash_with_variations to work with both #to_hash and #to_api_hash - Modified #bottle_hash to have compact representation for the api hash - Added #urls_hash to share url hash generation logic between the hash methods --- Library/Homebrew/formula.rb | 161 +++++++++++++++++++++++++++--------- 1 file changed, 122 insertions(+), 39 deletions(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 4ae1d95ec1..d8932d54b7 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2239,7 +2239,7 @@ class Formula "head" => head&.version&.to_s, "bottle" => bottle_defined?, }, - "urls" => {}, + "urls" => urls_hash, "revision" => revision, "version_scheme" => version_scheme, "bottle" => {}, @@ -2277,26 +2277,7 @@ class Formula "ruby_source_checksum" => {}, } - if stable - stable_spec = T.must(stable) - hsh["urls"]["stable"] = { - "url" => stable_spec.url, - "tag" => stable_spec.specs[:tag], - "revision" => stable_spec.specs[:revision], - "using" => (stable_spec.using if stable_spec.using.is_a?(Symbol)), - "checksum" => stable_spec.checksum&.to_s, - } - - hsh["bottle"]["stable"] = bottle_hash if bottle_defined? - end - - if head - hsh["urls"]["head"] = { - "url" => T.must(head).url, - "branch" => T.must(head).specs[:branch], - "using" => (T.must(head).using if T.must(head).using.is_a?(Symbol)), - } - end + hsh["bottle"]["stable"] = bottle_hash if stable && bottle_defined? hsh["options"] = options.map do |opt| { "option" => opt.flag, "description" => opt.description } @@ -2393,8 +2374,82 @@ class Formula end # @private - def to_hash_with_variations - hash = to_hash + def to_api_hash + api_hash = { + "desc" => desc, + "license" => SPDX.license_expression_to_string(license), + "homepage" => homepage, + "urls" => urls_hash.transform_values(&:compact), + "build_dependencies" => [], + "dependencies" => [], + "test_dependencies" => [], + "uses_from_macos" => [], + "uses_from_macos_bounds" => [], + "requirements" => [], + "post_install_defined" => post_install_defined?, + "ruby_source_path" => ruby_source_path, + "ruby_source_checksum" => { "sha256" => ruby_source_checksum&.hexdigest }, + } + + # Exclude default values. + api_hash["revision"] = revision unless revision.zero? + api_hash["version_scheme"] = version_scheme unless version_scheme.zero? + + api_hash["key_only_reason"] = keg_only_reason.to_hash if keg_only_reason + api_hash["pour_bottle_only_if"] = self.class.pour_bottle_only_if.to_s if self.class.pour_bottle_only_if + api_hash["link_overwrite"] = self.class.link_overwrite_paths.to_a if self.class.link_overwrite_paths.present? + + if stable + api_hash["versions"] = { "stable" => stable&.version&.to_s } + api_hash["bottle"] = { "stable" => bottle_hash(compact_for_api: true) } if bottle_defined? + end + + if (versioned_formulae_list = versioned_formulae.presence) + # Could we just use `versioned_formulae_names` here instead? + api_hash["versioned_formulae"] = versioned_formulae_list.map(&:name) + end + + if conflicts.present? + api_hash["conflicts_with"] = conflicts.map(&:name) + api_hash["conflicts_with_reasons"] = conflicts.map(&:reason) + end + + if deprecation_date + api_hash["deprecation_date"] = deprecation_date + api_hash["deprecation_reason"] = deprecation_reason # this might need to be checked too + end + + if disable_date + api_hash["disable_date"] = disable_date + api_hash["disable_reason"] = disable_reason # this might need to be checked too + end + + if (caveats_string = caveats) + api_hash["caveats"] = caveats_string.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER) + .gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER) + end + + api_hash["service"] = service.serialize if service? + + api_hash + end + + # @private + def to_hash_with_variations(hash_method: :to_hash) + if loaded_from_api? && hash_method == :to_api_hash + raise ArgumentError, "API Hash must be generated from Ruby source files" + end + + namespace_prefix = case hash_method + when :to_hash + "Variations" + when :to_api_hash + "APIVariations" + else + raise ArgumentError, "Unknown hash method #{hash_method.inspect}" + end + + hash = public_send(hash_method) # Take from API, merging in local install status. if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? @@ -2413,13 +2468,13 @@ class Formula next unless bottle_tag.valid_combination? Homebrew::SimulateSystem.with os: os, arch: arch do - variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}") + variations_namespace = Formulary.class_s("#{namespace_prefix}#{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| + variations_formula.public_send(hash_method).each do |key, value| next if value.to_s == hash[key].to_s variations[bottle_tag.to_sym] ||= {} @@ -2434,29 +2489,57 @@ class Formula end # Returns the bottle information for a formula. - def bottle_hash + def bottle_hash(compact_for_api: false) bottle_spec = T.must(stable).bottle_specification - hash = { - "rebuild" => bottle_spec.rebuild, - "root_url" => bottle_spec.root_url, - "files" => {}, - } + + hash = {} + hash["rebuild"] = bottle_spec.rebuild if !compact_for_api || !bottle_spec.rebuild.zero? + hash["root_url"] = bottle_spec.root_url unless compact_for_api + hash["files"] = {} + bottle_spec.collector.each_tag do |tag| tag_spec = bottle_spec.collector.specification_for(tag, no_older_versions: true) os_cellar = tag_spec.cellar os_cellar = os_cellar.inspect if os_cellar.is_a?(Symbol) - checksum = tag_spec.checksum.hexdigest - filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild) - path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename) - url = "#{bottle_spec.root_url}/#{path}" - hash["files"][tag.to_sym] = { - "cellar" => os_cellar, - "url" => url, - "sha256" => checksum, + file_hash = {} + file_hash["cellar"] = os_cellar + unless compact_for_api + filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild) + path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename) + file_hash["url"] = "#{bottle_spec.root_url}/#{path}" + end + file_hash["sha256"] = checksum + + hash["files"][tag.to_sym] = file_hash + end + hash + end + + # @private + def urls_hash + hash = {} + + if stable + stable_spec = T.must(stable) + hash["stable"] = { + "url" => stable_spec.url, + "tag" => stable_spec.specs[:tag], + "revision" => stable_spec.specs[:revision], + "using" => (stable_spec.using if stable_spec.using.is_a?(Symbol)), + "checksum" => stable_spec.checksum&.to_s, } end + + if head + hash["head"] = { + "url" => T.must(head).url, + "branch" => T.must(head).specs[:branch], + "using" => (T.must(head).using if T.must(head).using.is_a?(Symbol)), + } + end + hash end From d2dd80b0d69083a32660819e968b0eccf7e6634c Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sat, 27 Jan 2024 13:01:14 -0800 Subject: [PATCH 2/7] formula: share requirements serialization logic --- Library/Homebrew/formula.rb | 55 +++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index d8932d54b7..670a482e02 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2219,9 +2219,6 @@ class Formula [sym, send(sym)&.declared_deps] end dependencies.transform_values! { |deps| deps&.reject(&:implicit?) } # Remove all implicit deps from all lists - requirements = self.class.spec_syms.to_h do |sym| - [sym, send(sym)&.requirements] - end hsh = { "name" => name, @@ -2254,7 +2251,7 @@ class Formula "optional_dependencies" => [], "uses_from_macos" => [], "uses_from_macos_bounds" => [], - "requirements" => [], + "requirements" => serialized_requirements, "conflicts_with" => conflicts.map(&:name), "conflicts_with_reasons" => conflicts.map(&:reason), "link_overwrite" => self.class.link_overwrite_paths.to_a, @@ -2331,25 +2328,6 @@ class Formula dep_hash["uses_from_macos_bounds"] = uses_from_macos_deps.map(&:bounds) end - hsh["requirements"] = merge_spec_dependables(requirements).map do |data| - req = data[:dependable] - req_name = req.name.dup - req_name.prepend("maximum_") if req.respond_to?(:comparator) && req.comparator == "<=" - req_version = if req.respond_to?(:version) - req.version - elsif req.respond_to?(:arch) - req.arch - end - { - "name" => req_name, - "cask" => req.cask, - "download" => req.download, - "version" => req_version, - "contexts" => req.tags, - "specs" => data[:specs], - } - end - hsh["installed"] = installed_kegs.sort_by(&:version).map do |keg| tab = Tab.for_keg keg { @@ -2385,7 +2363,6 @@ class Formula "test_dependencies" => [], "uses_from_macos" => [], "uses_from_macos_bounds" => [], - "requirements" => [], "post_install_defined" => post_install_defined?, "ruby_source_path" => ruby_source_path, "ruby_source_checksum" => { "sha256" => ruby_source_checksum&.hexdigest }, @@ -2409,6 +2386,10 @@ class Formula api_hash["versioned_formulae"] = versioned_formulae_list.map(&:name) end + if (requirements_array = serialized_requirements.presence) + api_hash["requirements"] = requirements_array + end + if conflicts.present? api_hash["conflicts_with"] = conflicts.map(&:name) api_hash["conflicts_with_reasons"] = conflicts.map(&:reason) @@ -2543,6 +2524,32 @@ class Formula hash end + # @private + def serialized_requirements + requirements = self.class.spec_syms.to_h do |sym| + [sym, send(sym)&.requirements] + end + + merge_spec_dependables(requirements).map do |data| + req = data[:dependable] + req_name = req.name.dup + req_name.prepend("maximum_") if req.respond_to?(:comparator) && req.comparator == "<=" + req_version = if req.respond_to?(:version) + req.version + elsif req.respond_to?(:arch) + req.arch + end + { + "name" => req_name, + "cask" => req.cask, + "download" => req.download, + "version" => req_version, + "contexts" => req.tags, + "specs" => data[:specs], + } + end + end + # @private def on_system_blocks_exist? self.class.on_system_blocks_exist? || @on_system_blocks_exist From 69609731d9f42fc9c1ffe50bc40e7cc3b1831bb3 Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sat, 27 Jan 2024 13:27:07 -0800 Subject: [PATCH 3/7] formula: share dependencies serialization logic Note: This changes where the "head_dependencies" key in the hash shows up but not the hash's contents. "head_dependencies" now shows up directly after all of the other dependency keys. Before it was always at the end of the hash after variations. --- Library/Homebrew/formula.rb | 229 ++++++++++++++++++------------------ 1 file changed, 116 insertions(+), 113 deletions(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 670a482e02..bdf8175738 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2214,64 +2214,52 @@ class Formula # @private def to_hash - # Create a hash of spec names (stable/head) to the list of dependencies under each - dependencies = self.class.spec_syms.to_h do |sym| - [sym, send(sym)&.declared_deps] - end - dependencies.transform_values! { |deps| deps&.reject(&:implicit?) } # Remove all implicit deps from all lists - hsh = { - "name" => name, - "full_name" => full_name, - "tap" => tap&.name, - "oldname" => oldnames.first, # deprecated - "oldnames" => oldnames, - "aliases" => aliases.sort, - "versioned_formulae" => versioned_formulae.map(&:name), - "desc" => desc, - "license" => SPDX.license_expression_to_string(license), - "homepage" => homepage, - "versions" => { + "name" => name, + "full_name" => full_name, + "tap" => tap&.name, + "oldname" => oldnames.first, # deprecated + "oldnames" => oldnames, + "aliases" => aliases.sort, + "versioned_formulae" => versioned_formulae.map(&:name), + "desc" => desc, + "license" => SPDX.license_expression_to_string(license), + "homepage" => homepage, + "versions" => { "stable" => stable&.version&.to_s, "head" => head&.version&.to_s, "bottle" => bottle_defined?, }, - "urls" => urls_hash, - "revision" => revision, - "version_scheme" => version_scheme, - "bottle" => {}, - "pour_bottle_only_if" => self.class.pour_bottle_only_if&.to_s, - "keg_only" => keg_only?, - "keg_only_reason" => keg_only_reason&.to_hash, - "options" => [], - "build_dependencies" => [], - "dependencies" => [], - "test_dependencies" => [], - "recommended_dependencies" => [], - "optional_dependencies" => [], - "uses_from_macos" => [], - "uses_from_macos_bounds" => [], - "requirements" => serialized_requirements, - "conflicts_with" => conflicts.map(&:name), - "conflicts_with_reasons" => conflicts.map(&:reason), - "link_overwrite" => self.class.link_overwrite_paths.to_a, - "caveats" => caveats&.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER) - &.gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER), - "installed" => [], - "linked_keg" => linked_version&.to_s, - "pinned" => pinned?, - "outdated" => outdated?, - "deprecated" => deprecated?, - "deprecation_date" => deprecation_date, - "deprecation_reason" => deprecation_reason, - "disabled" => disabled?, - "disable_date" => disable_date, - "disable_reason" => disable_reason, - "post_install_defined" => post_install_defined?, - "service" => (service.serialize if service?), - "tap_git_head" => tap_git_head, - "ruby_source_path" => ruby_source_path, - "ruby_source_checksum" => {}, + "urls" => urls_hash, + "revision" => revision, + "version_scheme" => version_scheme, + "bottle" => {}, + "pour_bottle_only_if" => self.class.pour_bottle_only_if&.to_s, + "keg_only" => keg_only?, + "keg_only_reason" => keg_only_reason&.to_hash, + "options" => [], + **dependencies_hash, + "requirements" => serialized_requirements, + "conflicts_with" => conflicts.map(&:name), + "conflicts_with_reasons" => conflicts.map(&:reason), + "link_overwrite" => self.class.link_overwrite_paths.to_a, + "caveats" => caveats&.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER) + &.gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER), + "installed" => [], + "linked_keg" => linked_version&.to_s, + "pinned" => pinned?, + "outdated" => outdated?, + "deprecated" => deprecated?, + "deprecation_date" => deprecation_date, + "deprecation_reason" => deprecation_reason, + "disabled" => disabled?, + "disable_date" => disable_date, + "disable_reason" => disable_reason, + "post_install_defined" => post_install_defined?, + "service" => (service.serialize if service?), + "tap_git_head" => tap_git_head, + "ruby_source_path" => ruby_source_path, + "ruby_source_checksum" => {}, } hsh["bottle"]["stable"] = bottle_hash if stable && bottle_defined? @@ -2280,54 +2268,6 @@ class Formula { "option" => opt.flag, "description" => opt.description } end - dependencies.each do |spec_sym, spec_deps| - next if spec_deps.nil? - - dep_hash = if spec_sym == :stable - hsh - else - next if spec_deps == dependencies[:stable] - - hsh["#{spec_sym}_dependencies"] ||= {} - end - - dep_hash["build_dependencies"] = spec_deps.select(&:build?) - .reject(&:uses_from_macos?) - .map(&:name) - .uniq - dep_hash["dependencies"] = spec_deps.reject(&:optional?) - .reject(&:recommended?) - .reject(&:build?) - .reject(&:test?) - .reject(&:uses_from_macos?) - .map(&:name) - .uniq - dep_hash["test_dependencies"] = spec_deps.select(&:test?) - .reject(&:uses_from_macos?) - .map(&:name) - .uniq - dep_hash["recommended_dependencies"] = spec_deps.select(&:recommended?) - .reject(&:uses_from_macos?) - .map(&:name) - .uniq - dep_hash["optional_dependencies"] = spec_deps.select(&:optional?) - .reject(&:uses_from_macos?) - .map(&:name) - .uniq - - uses_from_macos_deps = spec_deps.select(&:uses_from_macos?).uniq - dep_hash["uses_from_macos"] = uses_from_macos_deps.map do |dep| - if dep.tags.length >= 2 - { dep.name => dep.tags } - elsif dep.tags.present? - { dep.name => dep.tags.first } - else - dep.name - end - end - dep_hash["uses_from_macos_bounds"] = uses_from_macos_deps.map(&:bounds) - end - hsh["installed"] = installed_kegs.sort_by(&:version).map do |keg| tab = Tab.for_keg keg { @@ -2354,20 +2294,22 @@ class Formula # @private def to_api_hash api_hash = { - "desc" => desc, - "license" => SPDX.license_expression_to_string(license), - "homepage" => homepage, - "urls" => urls_hash.transform_values(&:compact), - "build_dependencies" => [], - "dependencies" => [], - "test_dependencies" => [], - "uses_from_macos" => [], - "uses_from_macos_bounds" => [], - "post_install_defined" => post_install_defined?, - "ruby_source_path" => ruby_source_path, - "ruby_source_checksum" => { "sha256" => ruby_source_checksum&.hexdigest }, + "desc" => desc, + "license" => SPDX.license_expression_to_string(license), + "homepage" => homepage, + "urls" => urls_hash.transform_values(&:compact), + "post_install_defined" => post_install_defined?, + "ruby_source_path" => ruby_source_path, + "ruby_source_checksum" => { "sha256" => ruby_source_checksum&.hexdigest }, } + dep_hash = dependencies_hash + .except("recommended_dependencies", "optional_dependencies") + .transform_values(&:presence) + .compact + + api_hash.merge(dep_hash) + # Exclude default values. api_hash["revision"] = revision unless revision.zero? api_hash["version_scheme"] = version_scheme unless version_scheme.zero? @@ -2550,6 +2492,67 @@ class Formula end end + # @private + def dependencies_hash + # Create a hash of spec names (stable/head) to the list of dependencies under each + dependencies = self.class.spec_syms.to_h do |sym| + [sym, send(sym)&.declared_deps] + end + dependencies.transform_values! { |deps| deps&.reject(&:implicit?) } # Remove all implicit deps from all lists + + hash = {} + + dependencies.each do |spec_sym, spec_deps| + next if spec_deps.nil? + + dep_hash = if spec_sym == :stable + hash + else + next if spec_deps == dependencies[:stable] + + hash["#{spec_sym}_dependencies"] ||= {} + end + + dep_hash["build_dependencies"] = spec_deps.select(&:build?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["dependencies"] = spec_deps.reject(&:optional?) + .reject(&:recommended?) + .reject(&:build?) + .reject(&:test?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["test_dependencies"] = spec_deps.select(&:test?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["recommended_dependencies"] = spec_deps.select(&:recommended?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + dep_hash["optional_dependencies"] = spec_deps.select(&:optional?) + .reject(&:uses_from_macos?) + .map(&:name) + .uniq + + uses_from_macos_deps = spec_deps.select(&:uses_from_macos?).uniq + dep_hash["uses_from_macos"] = uses_from_macos_deps.map do |dep| + if dep.tags.length >= 2 + { dep.name => dep.tags } + elsif dep.tags.present? + { dep.name => dep.tags.first } + else + dep.name + end + end + dep_hash["uses_from_macos_bounds"] = uses_from_macos_deps.map(&:bounds) + end + + hash + end + # @private def on_system_blocks_exist? self.class.on_system_blocks_exist? || @on_system_blocks_exist From ba3a0f8c330357ceca0eb5064611896fdd851410 Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sat, 27 Jan 2024 23:16:56 -0800 Subject: [PATCH 4/7] dev-cmd/generate-formula-api: generate homebrew-core.json This adds the code to generate the homebrew-core.json file which represents the entire tap instead of just the previous array of formula hashes. Any shared logic has been moved into the top-level hash scope including aliases, renames, tap_git_head and tap_migrations. I also added a check to skip adding the variations hash to the api hash if it is empty. Now we're down to 10MB from 24MB!!! --- Library/Homebrew/dev-cmd/generate-formula-api.rb | 15 ++++++++++++++- Library/Homebrew/formula.rb | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/dev-cmd/generate-formula-api.rb b/Library/Homebrew/dev-cmd/generate-formula-api.rb index 6bc958082e..bf149c8c31 100644 --- a/Library/Homebrew/dev-cmd/generate-formula-api.rb +++ b/Library/Homebrew/dev-cmd/generate-formula-api.rb @@ -46,7 +46,7 @@ module Homebrew raise TapUnavailableError, tap.name unless tap.installed? unless args.dry_run? - directories = ["_data/formula", "api/formula", "formula"] + directories = ["_data/formula", "api/formula", "formula", "api/internal/v3"] FileUtils.rm_rf directories + ["_data/formula_canonical.json"] FileUtils.mkdir_p directories end @@ -58,6 +58,14 @@ module Homebrew Formulary.enable_factory_cache! Formula.generating_hash! + homebrew_core_tap_hash = { + "tap_git_head" => tap.git_head, + "aliases" => tap.alias_table, + "renames" => tap.formula_renames, + "tap_migrations" => tap.tap_migrations, + "formulae" => {}, + } + tap.formula_names.each do |name| formula = Formulary.factory(name) name = formula.name @@ -69,11 +77,16 @@ module Homebrew File.write("api/formula/#{name}.json", FORMULA_JSON_TEMPLATE) File.write("formula/#{name}.html", html_template_name) end + + homebrew_core_tap_hash["formulae"][formula.name] = + formula.to_hash_with_variations(hash_method: :to_api_hash) rescue onoe "Error while generating data for formula '#{name}'." raise end + homebrew_core_tap_json = JSON.generate(homebrew_core_tap_hash) + File.write("api/internal/v3/homebrew-core.json", homebrew_core_tap_json) unless args.dry_run? 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? end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index bdf8175738..05161759cd 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2407,7 +2407,7 @@ class Formula end end - hash["variations"] = variations + hash["variations"] = variations if hash_method != :to_api_hash || variations.present? hash end From d9a98ac4ecfc3dee5bb4c562e8398b23f3a1b42c Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Tue, 30 Jan 2024 23:00:29 -0800 Subject: [PATCH 5/7] Address review feedback - move caveats serialization to a method - remove unnecessary nesting where the hash would only have one key Also, removed comment about checking disable and deprecation reasons. --- Library/Homebrew/formula.rb | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 05161759cd..6e5faeec91 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2243,8 +2243,7 @@ class Formula "conflicts_with" => conflicts.map(&:name), "conflicts_with_reasons" => conflicts.map(&:reason), "link_overwrite" => self.class.link_overwrite_paths.to_a, - "caveats" => caveats&.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER) - &.gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER), + "caveats" => caveats_with_placeholders, "installed" => [], "linked_keg" => linked_version&.to_s, "pinned" => pinned?, @@ -2300,7 +2299,7 @@ class Formula "urls" => urls_hash.transform_values(&:compact), "post_install_defined" => post_install_defined?, "ruby_source_path" => ruby_source_path, - "ruby_source_checksum" => { "sha256" => ruby_source_checksum&.hexdigest }, + "ruby_source_sha256" => ruby_source_checksum&.hexdigest, } dep_hash = dependencies_hash @@ -2314,13 +2313,16 @@ class Formula api_hash["revision"] = revision unless revision.zero? api_hash["version_scheme"] = version_scheme unless version_scheme.zero? - api_hash["key_only_reason"] = keg_only_reason.to_hash if keg_only_reason + # Optional values. + api_hash["keg_only_reason"] = keg_only_reason.to_hash if keg_only_reason api_hash["pour_bottle_only_if"] = self.class.pour_bottle_only_if.to_s if self.class.pour_bottle_only_if api_hash["link_overwrite"] = self.class.link_overwrite_paths.to_a if self.class.link_overwrite_paths.present? + api_hash["caveats"] = caveats_with_placeholders if caveats + api_hash["service"] = service.serialize if service? if stable - api_hash["versions"] = { "stable" => stable&.version&.to_s } - api_hash["bottle"] = { "stable" => bottle_hash(compact_for_api: true) } if bottle_defined? + api_hash["version"] = stable&.version&.to_s + api_hash["bottle"] = bottle_hash(compact_for_api: true) if bottle_defined? end if (versioned_formulae_list = versioned_formulae.presence) @@ -2339,21 +2341,14 @@ class Formula if deprecation_date api_hash["deprecation_date"] = deprecation_date - api_hash["deprecation_reason"] = deprecation_reason # this might need to be checked too + api_hash["deprecation_reason"] = deprecation_reason end if disable_date api_hash["disable_date"] = disable_date - api_hash["disable_reason"] = disable_reason # this might need to be checked too + api_hash["disable_reason"] = disable_reason end - if (caveats_string = caveats) - api_hash["caveats"] = caveats_string.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER) - .gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER) - end - - api_hash["service"] = service.serialize if service? - api_hash end @@ -2492,6 +2487,12 @@ class Formula end end + # @private + def caveats_with_placeholders + caveats&.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER) + &.gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER) + end + # @private def dependencies_hash # Create a hash of spec names (stable/head) to the list of dependencies under each From 3c503cdf563d93de6940878d4915270cf93d308d Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Tue, 30 Jan 2024 23:07:19 -0800 Subject: [PATCH 6/7] Normalize service serialization method names --- Library/Homebrew/formula.rb | 4 ++-- Library/Homebrew/formulary.rb | 2 +- Library/Homebrew/service.rb | 4 ++-- Library/Homebrew/test/formula_spec.rb | 8 ++++---- Library/Homebrew/test/service_spec.rb | 14 +++++++------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 6e5faeec91..6fb60ee81d 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2255,7 +2255,7 @@ class Formula "disable_date" => disable_date, "disable_reason" => disable_reason, "post_install_defined" => post_install_defined?, - "service" => (service.serialize if service?), + "service" => (service.to_hash if service?), "tap_git_head" => tap_git_head, "ruby_source_path" => ruby_source_path, "ruby_source_checksum" => {}, @@ -2318,7 +2318,7 @@ class Formula api_hash["pour_bottle_only_if"] = self.class.pour_bottle_only_if.to_s if self.class.pour_bottle_only_if api_hash["link_overwrite"] = self.class.link_overwrite_paths.to_a if self.class.link_overwrite_paths.present? api_hash["caveats"] = caveats_with_placeholders if caveats - api_hash["service"] = service.serialize if service? + api_hash["service"] = service.to_hash if service? if stable api_hash["version"] = stable&.version&.to_s diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 5063333823..23a214902e 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -341,7 +341,7 @@ module Formulary end if (service_hash = json_formula["service"].presence) - service_hash = Homebrew::Service.deserialize(service_hash) + service_hash = Homebrew::Service.from_hash(service_hash) service do T.bind(self, Homebrew::Service) diff --git a/Library/Homebrew/service.rb b/Library/Homebrew/service.rb index 1fe6e86e6b..ae9cf8a9d9 100644 --- a/Library/Homebrew/service.rb +++ b/Library/Homebrew/service.rb @@ -515,7 +515,7 @@ module Homebrew # Prepare the service hash for inclusion in the formula API JSON. sig { returns(Hash) } - def serialize + def to_hash name_params = { macos: (plist_name if plist_name != default_plist_name), linux: (service_name if service_name != default_service_name), @@ -568,7 +568,7 @@ module Homebrew # Turn the service API hash values back into what is expected by the formula DSL. sig { params(api_hash: Hash).returns(Hash) } - def self.deserialize(api_hash) + def self.from_hash(api_hash) hash = {} hash[:name] = api_hash["name"].transform_keys(&:to_sym) if api_hash.key?("name") diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 94454a9780..414b6004dd 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -738,7 +738,7 @@ describe Formula do url "https://brew.sh/test-1.0.tbz" end - expect(f.service.serialize).to eq({}) + expect(f.service.to_hash).to eq({}) end specify "service complicated" do @@ -754,7 +754,7 @@ describe Formula do keep_alive true end end - expect(f.service.serialize.keys) + expect(f.service.to_hash.keys) .to contain_exactly(:run, :run_type, :error_log_path, :log_path, :working_dir, :keep_alive) end @@ -766,7 +766,7 @@ describe Formula do end end - expect(f.service.serialize.keys).to contain_exactly(:run, :run_type) + expect(f.service.to_hash.keys).to contain_exactly(:run, :run_type) end specify "service with only custom names" do @@ -779,7 +779,7 @@ describe Formula do expect(f.plist_name).to eq("custom.macos.beanstalkd") expect(f.service_name).to eq("custom.linux.beanstalkd") - expect(f.service.serialize.keys).to contain_exactly(:name) + expect(f.service.to_hash.keys).to contain_exactly(:name) end specify "service helpers return data" do diff --git a/Library/Homebrew/test/service_spec.rb b/Library/Homebrew/test/service_spec.rb index 85f1e8be51..b5c56fe40b 100644 --- a/Library/Homebrew/test/service_spec.rb +++ b/Library/Homebrew/test/service_spec.rb @@ -1044,7 +1044,7 @@ describe Homebrew::Service do end end - describe "#serialize" do + describe "#to_hash" do let(:serialized_hash) do { environment_variables: { @@ -1072,12 +1072,12 @@ describe Homebrew::Service do end Formula.generating_hash! - expect(f.service.serialize).to eq(serialized_hash) + expect(f.service.to_hash).to eq(serialized_hash) Formula.generated_hash! end end - describe ".deserialize" do + describe ".from_hash" do let(:serialized_hash) do { "name" => { @@ -1111,12 +1111,12 @@ describe Homebrew::Service do end it "replaces placeholders with local paths" do - expect(described_class.deserialize(serialized_hash)).to eq(deserialized_hash) + expect(described_class.from_hash(serialized_hash)).to eq(deserialized_hash) end describe "run command" do it "handles String argument correctly" do - expect(described_class.deserialize({ + expect(described_class.from_hash({ "run" => "$HOMEBREW_PREFIX/opt/formula_name/bin/beanstalkd", })).to eq({ run: "#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd", @@ -1124,7 +1124,7 @@ describe Homebrew::Service do end it "handles Array argument correctly" do - expect(described_class.deserialize({ + expect(described_class.from_hash({ "run" => ["$HOMEBREW_PREFIX/opt/formula_name/bin/beanstalkd", "--option"], })).to eq({ run: ["#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd", "--option"], @@ -1132,7 +1132,7 @@ describe Homebrew::Service do end it "handles Hash argument correctly" do - expect(described_class.deserialize({ + expect(described_class.from_hash({ "run" => { "linux" => "$HOMEBREW_PREFIX/opt/formula_name/bin/beanstalkd", "macos" => ["$HOMEBREW_PREFIX/opt/formula_name/bin/beanstalkd", "--option"], From 54b54b7e93375d7db139d42d1c5f7042592bcf6f Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sat, 3 Feb 2024 13:21:32 -0800 Subject: [PATCH 7/7] formula: change how `#to_hash` is defined for backwards compatibility Now the output of commands like `brew info --json=` and `brew generate-formula-api` should be the same as before along with the additional files for the internal API. Before this commit the hash key order had changed. --- Library/Homebrew/formula.rb | 90 ++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 6fb60ee81d..9ced439387 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2215,50 +2215,56 @@ class Formula # @private def to_hash hsh = { - "name" => name, - "full_name" => full_name, - "tap" => tap&.name, - "oldname" => oldnames.first, # deprecated - "oldnames" => oldnames, - "aliases" => aliases.sort, - "versioned_formulae" => versioned_formulae.map(&:name), - "desc" => desc, - "license" => SPDX.license_expression_to_string(license), - "homepage" => homepage, - "versions" => { + "name" => name, + "full_name" => full_name, + "tap" => tap&.name, + "oldname" => oldnames.first, # deprecated + "oldnames" => oldnames, + "aliases" => aliases.sort, + "versioned_formulae" => versioned_formulae.map(&:name), + "desc" => desc, + "license" => SPDX.license_expression_to_string(license), + "homepage" => homepage, + "versions" => { "stable" => stable&.version&.to_s, "head" => head&.version&.to_s, "bottle" => bottle_defined?, }, - "urls" => urls_hash, - "revision" => revision, - "version_scheme" => version_scheme, - "bottle" => {}, - "pour_bottle_only_if" => self.class.pour_bottle_only_if&.to_s, - "keg_only" => keg_only?, - "keg_only_reason" => keg_only_reason&.to_hash, - "options" => [], - **dependencies_hash, - "requirements" => serialized_requirements, - "conflicts_with" => conflicts.map(&:name), - "conflicts_with_reasons" => conflicts.map(&:reason), - "link_overwrite" => self.class.link_overwrite_paths.to_a, - "caveats" => caveats_with_placeholders, - "installed" => [], - "linked_keg" => linked_version&.to_s, - "pinned" => pinned?, - "outdated" => outdated?, - "deprecated" => deprecated?, - "deprecation_date" => deprecation_date, - "deprecation_reason" => deprecation_reason, - "disabled" => disabled?, - "disable_date" => disable_date, - "disable_reason" => disable_reason, - "post_install_defined" => post_install_defined?, - "service" => (service.to_hash if service?), - "tap_git_head" => tap_git_head, - "ruby_source_path" => ruby_source_path, - "ruby_source_checksum" => {}, + "urls" => urls_hash, + "revision" => revision, + "version_scheme" => version_scheme, + "bottle" => {}, + "pour_bottle_only_if" => self.class.pour_bottle_only_if&.to_s, + "keg_only" => keg_only?, + "keg_only_reason" => keg_only_reason&.to_hash, + "options" => [], + "build_dependencies" => [], + "dependencies" => [], + "test_dependencies" => [], + "recommended_dependencies" => [], + "optional_dependencies" => [], + "uses_from_macos" => [], + "uses_from_macos_bounds" => [], + "requirements" => serialized_requirements, + "conflicts_with" => conflicts.map(&:name), + "conflicts_with_reasons" => conflicts.map(&:reason), + "link_overwrite" => self.class.link_overwrite_paths.to_a, + "caveats" => caveats_with_placeholders, + "installed" => [], + "linked_keg" => linked_version&.to_s, + "pinned" => pinned?, + "outdated" => outdated?, + "deprecated" => deprecated?, + "deprecation_date" => deprecation_date, + "deprecation_reason" => deprecation_reason, + "disabled" => disabled?, + "disable_date" => disable_date, + "disable_reason" => disable_reason, + "post_install_defined" => post_install_defined?, + "service" => (service.to_hash if service?), + "tap_git_head" => tap_git_head, + "ruby_source_path" => ruby_source_path, + "ruby_source_checksum" => {}, } hsh["bottle"]["stable"] = bottle_hash if stable && bottle_defined? @@ -2267,6 +2273,8 @@ class Formula { "option" => opt.flag, "description" => opt.description } end + hsh.merge!(dependencies_hash) + hsh["installed"] = installed_kegs.sort_by(&:version).map do |keg| tab = Tab.for_keg keg { @@ -2307,7 +2315,7 @@ class Formula .transform_values(&:presence) .compact - api_hash.merge(dep_hash) + api_hash.merge!(dep_hash) # Exclude default values. api_hash["revision"] = revision unless revision.zero?