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_once, opoo_outside_github_actions, opoo_without_github_actions_annotation, pretty_cannot_install, pretty_deprecated, pretty_disabled, pretty_duration, pretty_install_status, pretty_installed, pretty_removed, 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:



865
866
867
# File 'cask/cask_loader.rb', line 865

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:



870
871
872
873
874
# File 'cask/cask_loader.rb', line 870

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) ⇒ FromAPILoader, ...

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)
  • need_path (Boolean) (defaults to: false)
  • warn (Boolean) (defaults to: true)

Returns:



588
589
590
591
# File 'cask/cask_loader.rb', line 588

def self.for(ref, need_path: false, warn: true)
  odeprecated "Cask::CaskLoader.for", "Cask::CaskLoader.loader_for"
  loader_for(ref, warn:)
end

.from_content(content, config: nil, tap: 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.

Evaluate explicitly supplied source. Homebrew/brew callers only.

Parameters:

  • content (String)
  • config (Config, nil) (defaults to: nil)
  • tap (Tap, nil) (defaults to: nil)

Returns:



632
633
634
# File 'cask/cask_loader.rb', line 632

def self.from_content(content, config: nil, tap: nil)
  FromContentLoader.new(content, tap:).load(config:)
end

.from_path(path, 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.

Load a parent-selected source file. Homebrew/brew callers only.

Parameters:

Returns:

Raises:



612
613
614
615
616
617
# File 'cask/cask_loader.rb', line 612

def self.from_path(path, config: nil)
  path = Pathname(path)
  raise CaskUnavailableError.new(path.to_s, "Invalid cask source path.") if FromPathLoader.invalid_path?(path)

  FromPathLoader.new(path).load(config:)
end

.from_tap_path(path) ⇒ 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.

Load a registered tap source file for auditing or CI.

Parameters:

Returns:

Raises:



622
623
624
625
626
627
# File 'cask/cask_loader.rb', line 622

def self.from_tap_path(path)
  tap_path = ::Utils::Path.tap_package_path(Pathname(path), :cask)
  raise CaskUnavailableError.new(path.to_s, "Not a registered tap source file.") unless tap_path

  from_path(tap_path)
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)


762
763
764
# File 'cask/cask_loader.rb', line 762

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.

Look up a cask by name, without interpreting paths or Ruby source.

Parameters:

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

Returns:



605
606
607
# File 'cask/cask_loader.rb', line 605

def self.load(ref, config: nil, warn: true)
  loader_for(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.

Load only the recorded installed source file, with optional API artifact recovery.

Parameters:

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

Returns:



745
746
747
748
749
750
751
752
753
# File 'cask/cask_loader.rb', line 745

def self.load_from_installed_caskfile(path, config: nil, warn: true, api_fallback: true)
  token = token_from_path(path)
  installed_path = Cask.new(token).installed_caskfile
  if installed_path.nil? || path.expand_path != installed_path.expand_path
    raise CaskUnavailableError.new(token, "Not the installed cask metadata file.")
  end

  FromInstalledPathLoader.new(path, api_fallback:).load(config:)
end

.load_from_name(name, 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.

Loads a bare cask token without interpreting it as source, a path or a URI.

Parameters:

  • name (String)
  • warn (Boolean) (defaults to: true)

Returns:



638
639
640
641
642
643
644
# File 'cask/cask_loader.rb', line 638

def self.load_from_name(name, warn: true)
  if !name.match?(/\A#{HOMEBREW_TAP_CASK_TOKEN_REGEX}\z/o) || ::Utils::INVALID_PATH_COMPONENTS.include?(name)
    raise CaskUnavailableError.new(name, "Invalid cask token.")
  end

  load(name, warn:)
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:



767
768
769
770
771
772
773
774
# File 'cask/cask_loader.rb', line 767

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:



777
778
779
780
781
782
783
784
785
786
# File 'cask/cask_loader.rb', line 777

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.

Resolve a name using its receipt's tap, with an installed-source fallback.

Parameters:

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

Returns:



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
# File 'cask/cask_loader.rb', line 713

def self.load_prefer_installed(ref, config: nil, warn: true)
  unless ::Utils::Path.valid_package_name?(ref)
    raise CaskUnavailableError.new(ref, "Invalid cask name.")
  end

  tap, token = Tap.with_cask_token(ref)
  token ||= ref
  installed_cask = Cask.new(token)
  installed_tap = installed_cask.tab.tap
  tap ||= installed_tap

  begin
    name = tap ? "#{tap}/#{token}" : token
    self.load(name, config:, warn:)
  rescue CaskUnreadableError
    raise
  rescue CaskUnavailableError
    raise if tap && tap != installed_tap

    installed_path = installed_cask.installed_caskfile
    raise unless installed_path

    load_from_installed_caskfile(installed_path, config:, warn:)
  end
end

.loader_for(ref, warn: true) ⇒ FromAPILoader, ...

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.

Select only name-based sources: API, qualified tap then unqualified tap lookup.

Parameters:

  • ref (String)
  • warn (Boolean) (defaults to: true)

Returns:



696
697
698
699
700
701
702
703
704
705
706
707
708
# File 'cask/cask_loader.rb', line 696

def self.loader_for(ref, warn: true)
  unless ::Utils::Path.valid_package_name?(ref)
    opoo "Loading casks from paths, URLs or Ruby source is not supported; use a cask name." if warn
    return NullLoader.new(ref.to_s)
  end

  loader = FromAPILoader.try_new(ref, warn:) ||
           FromTapLoader.try_new(ref, warn:) ||
           FromNameLoader.try_new(ref, warn:) ||
           NullLoader.new(ref)
  $stderr.puts "#{$PROGRAM_NAME} (#{loader.class}): loading #{ref}" if verbose? && debug?
  loader
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:



596
597
598
# File 'cask/cask_loader.rb', line 596

def self.path(ref)
  loader_for(ref).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:



821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
# File 'cask/cask_loader.rb', line 821

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:



796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
# File 'cask/cask_loader.rb', line 796

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:



647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
# File 'cask/cask_loader.rb', line 647

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:



756
757
758
# File 'cask/cask_loader.rb', line 756

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