Module: Cask::CaskLoader Private

Extended by:
Context, Utils::Output::Mixin
Defined in:
cask/cask_loader.rb

Overview

This module is part of a private API. This module may only be used in the Homebrew/brew repository. Third parties should avoid using this module if possible, as it may be removed or changed without warning.

Loads a cask from various sources.

Defined Under Namespace

Modules: ILoader Classes: AbstractContentLoader, FromAPILoader, FromContentLoader, FromInstalledPathLoader, FromInstanceLoader, FromNameLoader, FromPathLoader, FromTapLoader, FromURILoader, NullLoader

Class Method Summary collapse

Methods included from Context

current, current=, debug?, deferred_environment_expansion?, quiet?, verbose?, with_context

Methods included from Utils::Output::Mixin

issue_reporting_message, odebug, odeprecated, odie, odisabled, ofail, oh1, oh1_title, ohai, ohai_title, onoe, opoo, opoo_outside_github_actions, opoo_without_github_actions_annotation, pretty_deprecated, pretty_disabled, pretty_duration, pretty_install_status, pretty_installed, pretty_uninstalled, pretty_unmarked, pretty_upgradable, pretty_warning

Class Method Details

.default_path(token) ⇒ Pathname

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



927
928
929
# File 'cask/cask_loader.rb', line 927

def self.default_path(token)
  find_cask_in_tap(token.to_s.downcase, CoreCaskTap.instance)
end

.find_cask_in_tap(token, tap) ⇒ Pathname

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



932
933
934
935
936
# File 'cask/cask_loader.rb', line 932

def self.find_cask_in_tap(token, tap)
  filename = "#{token}.rb"

  tap.cask_files_by_name.fetch(token, tap.cask_dir/filename)
end

.for(ref, need_path: false, warn: true) ⇒ ILoader

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Note:

Using WithoutRuntime to avoid Sorbet wrapping this method,

which would interfere with RSpec mocking of this class method.

Parameters:

  • ref (String, Pathname, Cask, URI::Generic)
  • need_path (Boolean) (defaults to: false)
  • warn (Boolean) (defaults to: true)

Returns:

Raises:



750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
# File 'cask/cask_loader.rb', line 750

def self.for(ref, need_path: false, warn: true)
  [
    FromInstanceLoader,
    FromContentLoader,
    FromURILoader,
    FromAPILoader,
    FromTapLoader,
    FromNameLoader,
    FromPathLoader,
    FromInstalledPathLoader,
    NullLoader,
  ].each do |loader_class|
    if (loader = loader_class.try_new(ref, warn:))
      $stderr.puts "#{$PROGRAM_NAME} (#{loader.class}): loading #{ref}" if verbose? && debug?
      return loader
    end
  end

  raise CaskError, "No cask loader found for #{ref.inspect}"
end

.installed_json_caskfile?(path) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Legacy .internal.json files contain full API data rather than the compact installed JSON format.

Parameters:

Returns:

  • (Boolean)


807
808
809
# File 'cask/cask_loader.rb', line 807

def self.installed_json_caskfile?(path)
  path.extname == ".json" && !path.basename.to_s.end_with?(".internal.json")
end

.load(ref, config: nil, warn: true) ⇒ Cask

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Note:

Using WithoutRuntime to avoid Sorbet wrapping this method,

which would interfere with RSpec mocking of this class method.

Parameters:

Returns:



693
694
695
696
# File 'cask/cask_loader.rb', line 693

def self.load(ref, config: nil, warn: true)
  normalized_ref = ref.is_a?(Symbol) ? ref.to_s : ref
  self.for(normalized_ref, warn:).load(config:)
end

.load_from_installed_caskfile(path, config: nil, warn: true, api_fallback: true) ⇒ Cask

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • path (Pathname)
  • config (Config, nil) (defaults to: nil)
  • warn (Boolean) (defaults to: true)
  • api_fallback (Boolean) (defaults to: true)

Returns:



793
794
795
796
797
798
# File 'cask/cask_loader.rb', line 793

def self.load_from_installed_caskfile(path, config: nil, warn: true, api_fallback: true)
  loader = FromInstalledPathLoader.try_new(path, warn:, api_fallback:)
  loader ||= NullLoader.new(path)

  loader.load(config:)
end

.load_installed_json(path) ⇒ Hash{String => T.untyped}?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



812
813
814
815
816
817
818
819
# File 'cask/cask_loader.rb', line 812

def self.load_installed_json(path)
  return unless installed_json_caskfile?(path)

  json = JSON.parse(path.read)
  json if json.is_a?(Hash)
rescue JSON::ParserError
  nil
end

.load_installed_tab(cask_or_token) ⇒ Tab

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



822
823
824
825
826
827
828
829
830
831
# File 'cask/cask_loader.rb', line 822

def self.load_installed_tab(cask_or_token)
  cask = if cask_or_token.is_a?(Cask)
    cask_or_token
  else
    Cask.new(cask_or_token)
  end
  cask.tab
rescue JSON::ParserError, NoMethodError, TypeError
  Tab.empty
end

.load_prefer_installed(ref, config: nil, warn: true) ⇒ Cask

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • ref (String)
  • config (Config, nil) (defaults to: nil)
  • warn (Boolean) (defaults to: true)

Returns:



772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
# File 'cask/cask_loader.rb', line 772

def self.load_prefer_installed(ref, config: nil, warn: true)
  tap, token = Tap.with_cask_token(ref)
  token ||= ref
  tap ||= Cask.new(ref).tab.tap

  if tap.nil?
    self.load(token, config:, warn:)
  else
    begin
      self.load("#{tap}/#{token}", config:, warn:)
    rescue CaskUnavailableError
      # cask may be migrated to different tap. Try to search in all taps.
      self.load(token, config:, warn:)
    end
  end
end

.path(ref) ⇒ Pathname

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Note:

Using WithoutRuntime to avoid Sorbet wrapping this method,

which would interfere with RSpec mocking of this class method.

Parameters:

Returns:



683
684
685
# File 'cask/cask_loader.rb', line 683

def self.path(ref)
  T.cast(self.for(ref, need_path: true), T.any(FromAPILoader, FromPathLoader)).path
end

.recover_from_installed_caskfile(path, tab: nil, fallback_cask: nil, config: nil) ⇒ Cask?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • path (Pathname)
  • tab (Tab, nil) (defaults to: nil)
  • fallback_cask (Cask, nil) (defaults to: nil)
  • config (Config, nil) (defaults to: nil)

Returns:



883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
# File 'cask/cask_loader.rb', line 883

def self.recover_from_installed_caskfile(path, tab: nil, fallback_cask: nil, config: nil)
  # Only installed metadata has the versioned path layout used to rebuild the cask below.
  return if path.dirname.basename.to_s != "Casks"

  # Read any usable receipt, while retaining the current cask as a fallback for missing receipt data.
  token = token_from_path(path)
  tab ||= load_installed_tab(fallback_cask || token)

  # Ruby uninstall flight blocks cannot be represented by installed JSON and must not be approximated.
  return if tab.uninstall_flight_blocks
  return if fallback_cask&.uninstall_flight_blocks?

  # Prefer exact receipt artifacts, then the current cask and finally the current source definition.
  artifacts = tab.uninstall_artifacts.presence
  artifacts ||= fallback_cask.artifacts_list(uninstall_only: true) if fallback_cask
  artifacts ||= resolve_installed_artifacts(token, nil, tap: tab.tap)

  # Rebuild the installed version from its metadata directory and retain current source path information.
  api_source = {
    "version"   => path.dirname.dirname.dirname.basename.to_s,
    "artifacts" => artifacts,
  }
  api_source["url_specs"] ||= fallback_cask.to_installed_json_hash["url_specs"] if fallback_cask

  # Prefer the installed JSON's source path because it belongs to the installed version.
  if (source_json = load_installed_json(path))
    source_url_specs = source_json["url_specs"]
    api_source["url_specs"] = source_url_specs if source_url_specs.is_a?(Hash)
  end

  # Load through the installed-JSON path so reconstructed artifacts have normal installed paths and behaviour.
  recovered_cask = FromAPILoader.new(
    token,
    from_json:               api_source,
    path:,
    from_installed_caskfile: true,
  ).load(config:)
  recovered_cask unless recovered_cask.uninstall_flight_blocks?
rescue CaskInvalidError, CaskUnavailableError, MethodDeprecatedError, JSON::ParserError
  # Recovery is best effort; callers treat nil as an unavailable installed cask and use their existing fallback.
  nil
end

.resolve_installed_artifacts(token, artifacts, tap: nil, api_fallback: true) ⇒ Array<Hash{String, Symbol => T.anything}>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
# File 'cask/cask_loader.rb', line 841

def self.resolve_installed_artifacts(token, artifacts, tap: nil, api_fallback: true)
  artifacts = artifacts.presence
  return artifacts if artifacts
  return [] unless api_fallback

  artifacts ||= begin
    tap_loader = (FromNameLoader.try_new(token, warn: false) if tap.nil? && FromAPILoader.try_new(token).nil?)

    if tap && !tap.core_cask_tap?
      load("#{tap}/#{token}", warn: false).artifacts_list(uninstall_only: true)
    elsif tap_loader
      tap_loader.load(config: nil).artifacts_list(uninstall_only: true)
    end
  rescue CaskError, MethodDeprecatedError, JSON::ParserError, ErrorDuringExecution, SystemExit
    nil
  end

  # API fetch failures must not abort best-effort installed metadata recovery. Skip the
  # per-cask endpoint only when the token is definitively absent from the current API;
  # a membership-check failure is treated as unknown so recovery still tries the endpoint.
  artifacts ||= begin
    definitely_absent = begin
      !Homebrew::API.cask_token?(token)
    rescue ErrorDuringExecution, SystemExit
      false
    end
    Homebrew::API::Cask.cask_json(token)["artifacts"] unless definitely_absent
  rescue ErrorDuringExecution, SystemExit
    nil
  end
  artifacts ||= []
  artifacts
end

.tap_cask_token_type(tapped_token, warn:) ⇒ Array<(String, Tap, [Symbol, nil])>?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • tapped_token (String)
  • warn (Boolean)

Returns:



699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
# File 'cask/cask_loader.rb', line 699

def self.tap_cask_token_type(tapped_token, warn:)
  return unless (tap_with_token = Tap.with_cask_token(tapped_token))

  tap, token = tap_with_token

  type = nil

  if (new_token = tap.cask_renames[token].presence)
    old_token = tap.core_cask_tap? ? token : tapped_token
    token = new_token
    new_token = tap.core_cask_tap? ? token : "#{tap}/#{token}"
    type = :rename
  elsif (new_tap_name = tap.tap_migrations[token].presence)
    new_tap, new_token = Tap.with_cask_token(new_tap_name)
    unless new_tap
      if new_tap_name.include?("/")
        new_tap = Tap.fetch(new_tap_name)
        new_token = token
      else
        new_tap = tap
        new_token = new_tap_name
      end
    end
    new_tapped_token = "#{new_tap}/#{new_token}"

    if tapped_token != new_tapped_token
      old_token = tap.core_cask_tap? ? token : tapped_token
      return unless (token_tap_type = tap_cask_token_type(new_tapped_token, warn: false))

      token, tap, = token_tap_type
      new_token = new_tap.core_cask_tap? ? token : "#{tap}/#{token}"
      type = :migration
    end
  end

  if warn && old_token && new_token
    destination_exists = find_cask_in_tap(token, tap).exist? ||
                         (tap.core_cask_tap? && !Homebrew::EnvConfig.no_install_from_api? &&
                          Homebrew::API.cask_token?(token))
    opoo "Cask #{old_token} was renamed to #{new_token}." if destination_exists
  end

  [token, tap, type]
end

.token_from_path(path) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



801
802
803
# File 'cask/cask_loader.rb', line 801

def self.token_from_path(path)
  path.basename(path.extname).basename(".internal").to_s
end