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:



930
931
932
# File 'cask/cask_loader.rb', line 930

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:



935
936
937
938
939
# File 'cask/cask_loader.rb', line 935

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:



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

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)


810
811
812
# File 'cask/cask_loader.rb', line 810

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:



696
697
698
699
# File 'cask/cask_loader.rb', line 696

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:



796
797
798
799
800
801
# File 'cask/cask_loader.rb', line 796

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:



815
816
817
818
819
820
821
822
# File 'cask/cask_loader.rb', line 815

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:



825
826
827
828
829
830
831
832
833
834
# File 'cask/cask_loader.rb', line 825

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:



775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'cask/cask_loader.rb', line 775

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:



686
687
688
# File 'cask/cask_loader.rb', line 686

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:



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
925
926
927
# File 'cask/cask_loader.rb', line 886

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:



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
874
875
876
# File 'cask/cask_loader.rb', line 844

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:



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
743
744
745
# File 'cask/cask_loader.rb', line 702

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:



804
805
806
# File 'cask/cask_loader.rb', line 804

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