diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index e82def925e..81345e90e9 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -277,7 +277,7 @@ module Cask def ruby_source_checksum @ruby_source_checksum ||= { - "sha256" => Digest::SHA256.file(sourcefile_path).hexdigest, + sha256: Digest::SHA256.file(sourcefile_path).hexdigest, }.freeze end @@ -296,7 +296,11 @@ module Cask @tap_git_head = json_cask.fetch(:tap_git_head, "HEAD") @ruby_source_path = json_cask[:ruby_source_path] - @ruby_source_checksum = json_cask[:ruby_source_checksum].freeze + + # TODO: Clean this up when we deprecate the current JSON API and move to the internal JSON v3. + ruby_source_sha256 = json_cask.dig(:ruby_source_checksum, :sha256) + ruby_source_sha256 ||= json_cask[:ruby_source_sha256] + @ruby_source_checksum = { "sha256" => ruby_source_sha256 } end def to_s @@ -317,14 +321,6 @@ module Cask alias == eql? def to_h - url_specs = url&.specs.dup - case url_specs&.dig(:user_agent) - when :default - url_specs.delete(:user_agent) - when Symbol - url_specs[:user_agent] = ":#{url_specs[:user_agent]}" - end - { "token" => token, "full_token" => full_name, @@ -362,15 +358,64 @@ module Cask } end - def to_hash_with_variations - if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? - return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup) + # @private + def to_api_hash + api_hash = { + "token" => token, + "name" => name, + "desc" => desc, + "homepage" => homepage, + "url" => url, + "version" => version, + "sha256" => sha256, + "artifacts" => artifacts_list(compact: true), + "ruby_source_path" => ruby_source_path, + "ruby_source_sha256" => ruby_source_checksum.fetch(:sha256), + } + + if deprecation_date + api_hash["deprecation_date"] = deprecation_date + api_hash["deprecation_reason"] = deprecation_reason end - hash = to_h - variations = {} + if disable_date + api_hash["disable_date"] = disable_date + api_hash["disable_reason"] = disable_reason + end - hash_keys_to_skip = %w[outdated installed versions] + if (url_specs_hash = url_specs).present? + api_hash["url_specs"] = url_specs_hash + end + + api_hash["caskfile_only"] = true if caskfile_only? + api_hash["conflicts_with"] = conflicts_with if conflicts_with.present? + api_hash["depends_on"] = depends_on if depends_on.present? + api_hash["container"] = container.pairs if container + api_hash["caveats"] = caveats if caveats.present? + api_hash["auto_updates"] = auto_updates if auto_updates + api_hash["languages"] = languages if languages.present? + + api_hash + end + + HASH_KEYS_TO_SKIP = %w[outdated installed versions].freeze + private_constant :HASH_KEYS_TO_SKIP + + # @private + def to_hash_with_variations(hash_method: :to_h) + case hash_method + when :to_h + if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? + return api_to_local_hash(Homebrew::API::Cask.all_casks[token].dup) + end + when :to_api_hash + raise ArgumentError, "API Hash must be generated from Ruby source files" if loaded_from_api? + else + raise ArgumentError, "Unknown hash method #{hash_method.inspect}" + end + + hash = public_send(hash_method) + variations = {} if @dsl.on_system_blocks_exist? begin @@ -381,8 +426,8 @@ module Cask Homebrew::SimulateSystem.with os: os, arch: arch do refresh - to_h.each do |key, value| - next if hash_keys_to_skip.include? key + public_send(hash_method).each do |key, value| + next if HASH_KEYS_TO_SKIP.include? key next if value.to_s == hash[key].to_s variations[bottle_tag.to_sym] ||= {} @@ -395,7 +440,7 @@ module Cask end end - hash["variations"] = variations + hash["variations"] = variations if hash_method != :to_api_hash || variations.present? hash end @@ -416,16 +461,28 @@ module Cask hash end - def artifacts_list - artifacts.map do |artifact| + def artifacts_list(compact: false) + artifacts.filter_map do |artifact| case artifact when Artifact::AbstractFlightBlock # Only indicate whether this block is used as we don't load it from the API - { artifact.summarize => nil } + # We can skip this entirely once we move to internal JSON v3. + { artifact.summarize => nil } unless compact else { artifact.class.dsl_key => artifact.to_args } end end end + + def url_specs + url&.specs.dup.tap do |url_specs| + case url_specs&.dig(:user_agent) + when :default + url_specs.delete(:user_agent) + when Symbol + url_specs[:user_agent] = ":#{url_specs[:user_agent]}" + end + end + end end end diff --git a/Library/Homebrew/dev-cmd/generate-cask-api.rb b/Library/Homebrew/dev-cmd/generate-cask-api.rb index 3cf93c86af..b61e871d22 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-api.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-api.rb @@ -46,7 +46,7 @@ module Homebrew raise TapUnavailableError, tap.name unless tap.installed? unless args.dry_run? - directories = ["_data/cask", "api/cask", "api/cask-source", "cask"].freeze + directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal/v3"].freeze FileUtils.rm_rf directories FileUtils.mkdir_p directories end @@ -74,6 +74,9 @@ module Homebrew onoe "Error while generating data for cask '#{path.stem}'." raise end + + homebrew_cask_tap_json = JSON.generate(tap.to_api_hash) + File.write("api/internal/v3/homebrew-cask.json", homebrew_cask_tap_json) unless args.dry_run? end end end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 24119765e6..0147b40dcd 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -1370,6 +1370,22 @@ class CoreCaskTap < AbstractCoreTap migrations end end + + sig { returns(T::Hash[String, T.untyped]) } + def to_api_hash + casks_api_hash = cask_tokens.to_h do |token| + cask = Cask::CaskLoader.load(token) + cask_hash = cask.to_hash_with_variations(hash_method: :to_api_hash) + [token, cask_hash] + end + + { + "tap_git_head" => git_head, + "renames" => cask_renames, + "tap_migrations" => tap_migrations, + "casks" => casks_api_hash, + } + end end # Permanent configuration per {Tap} using `git-config(1)`.