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_cannot_install, 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:



922
923
924
# File 'cask/cask_loader.rb', line 922

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:



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

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:



762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
# File 'cask/cask_loader.rb', line 762

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)


819
820
821
# File 'cask/cask_loader.rb', line 819

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:



705
706
707
708
# File 'cask/cask_loader.rb', line 705

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:



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

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:



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

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:



834
835
836
837
838
839
840
841
842
843
# File 'cask/cask_loader.rb', line 834

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:



784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
# File 'cask/cask_loader.rb', line 784

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:



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

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:



878
879
880
881
882
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
# File 'cask/cask_loader.rb', line 878

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:



853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
# File 'cask/cask_loader.rb', line 853

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

  begin
    cask = if tap && !tap.core_cask_tap?
      load("#{tap}/#{token}", warn: false)
    elsif (loader = FromAPILoader.try_new(token) || FromNameLoader.try_new(token, warn: false))
      loader.load(config: nil)
    end
    cask&.artifacts_list(uninstall_only: true) || []
  rescue CaskError, MethodDeprecatedError, JSON::ParserError, KeyError, ErrorDuringExecution, SystemExit
    []
  end
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:



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
746
747
748
749
750
751
752
753
754
# File 'cask/cask_loader.rb', line 711

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:



813
814
815
# File 'cask/cask_loader.rb', line 813

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