Module: Homebrew::API Private

Extended by:
Cachable
Defined in:
api.rb,
api/cask.rb,
api/formula.rb,
api/download.rb,
api/analytics.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.

Helper functions for using Homebrew's formulae.brew.sh API.

Defined Under Namespace

Modules: Analytics, Cask, Formula Classes: Download, DownloadStrategy

Constant Summary collapse

HOMEBREW_CACHE_API =

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

T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
HOMEBREW_CACHE_API_SOURCE =

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

T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname)

Class Method Summary collapse

Methods included from Cachable

cache, clear_cache

Class Method Details

.fetch(endpoint) ⇒ 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:



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'api.rb', line 18

def self.fetch(endpoint)
  return cache[endpoint] if cache.present? && cache.key?(endpoint)

  api_url = "#{Homebrew::EnvConfig.api_domain}/#{endpoint}"
  output = Utils::Curl.curl_output("--fail", api_url)
  if !output.success? && Homebrew::EnvConfig.api_domain != HOMEBREW_API_DEFAULT_DOMAIN
    # Fall back to the default API domain and try again
    api_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/#{endpoint}"
    output = Utils::Curl.curl_output("--fail", api_url)
  end
  raise ArgumentError, "No file found at #{Tty.underline}#{api_url}#{Tty.reset}" unless output.success?

  cache[endpoint] = JSON.parse(output.stdout, freeze: true)
rescue JSON::ParserError
  raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}"
end

.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) ⇒ Array<([Array<T.untyped>, Hash{String => T.untyped}], 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.

Parameters:

  • endpoint (String)
  • target (Pathname) (defaults to: HOMEBREW_CACHE_API/endpoint)
  • stale_seconds (Integer) (defaults to: Homebrew::EnvConfig.api_auto_update_secs.to_i)

Returns:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'api.rb', line 39

def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
                             stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
  # Lazy-load dependency.
  require "development_tools"

  retry_count = 0
  url = "#{Homebrew::EnvConfig.api_domain}/#{endpoint}"
  default_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/#{endpoint}"

  if Homebrew.running_as_root_but_not_owned_by_root? &&
     (!target.exist? || target.empty?)
    odie "Need to download #{url} but cannot as root! Run `brew update` without `sudo` first then try again."
  end

  curl_args = Utils::Curl.curl_args(retries: 0) + %W[
    --compressed
    --speed-limit #{ENV.fetch("HOMEBREW_CURL_SPEED_LIMIT")}
    --speed-time #{ENV.fetch("HOMEBREW_CURL_SPEED_TIME")}
  ]

  insecure_download = DevelopmentTools.ca_file_substitution_required? ||
                      DevelopmentTools.curl_substitution_required?
  skip_download = target.exist? &&
                  !target.empty? &&
                  (!Homebrew.auto_update_command? ||
                    (Homebrew::EnvConfig.no_auto_update? && !Homebrew::EnvConfig.force_api_auto_update?) ||
                  ((Time.now - stale_seconds) < target.mtime))
  skip_download ||= Homebrew.running_as_root_but_not_owned_by_root?

  json_data = begin
    begin
      args = curl_args.dup
      args.prepend("--time-cond", target.to_s) if target.exist? && !target.empty?
      if insecure_download
        opoo DevelopmentTools.insecure_download_warning(endpoint)
        args.append("--insecure")
      end
      unless skip_download
        ohai "Downloading #{url}" if $stdout.tty? && !Context.current.quiet?
        # Disable retries here, we handle them ourselves below.
        Utils::Curl.curl_download(*args, url, to: target, retries: 0, show_error: false)
      end
    rescue ErrorDuringExecution
      if url == default_url
        raise unless target.exist?
        raise if target.empty?
      elsif retry_count.zero? || !target.exist? || target.empty?
        # Fall back to the default API domain and try again
        # This block will be executed only once, because we set `url` to `default_url`
        url = default_url
        target.unlink if target.exist? && target.empty?
        skip_download = false

        retry
      end

      opoo "#{target.basename}: update failed, falling back to cached version."
    end

    mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now
    FileUtils.touch(target, mtime:) unless skip_download
    # Can use `target.read` again when/if https://github.com/sorbet/sorbet/pull/8999 is merged/released.
    JSON.parse(File.read(target, encoding: Encoding::UTF_8), freeze: true)
  rescue JSON::ParserError
    target.unlink
    retry_count += 1
    skip_download = false
    odie "Cannot download non-corrupt #{url}!" if retry_count > Homebrew::EnvConfig.curl_retries.to_i

    retry
  end

  if endpoint.end_with?(".jws.json")
    success, data = verify_and_parse_jws(json_data)
    unless success
      target.unlink
      odie <<~EOS
        Failed to verify integrity (#{data}) of:
          #{url}
        Potential MITM attempt detected. Please run `brew update` and try again.
      EOS
    end
    [data, !skip_download]
  else
    [json_data, !skip_download]
  end
end

.merge_variations(json, bottle_tag: T.unsafe(nil)) ⇒ 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:



131
132
133
134
135
136
137
138
139
140
141
142
# File 'api.rb', line 131

def self.merge_variations(json, bottle_tag: T.unsafe(nil))
  return json unless json.key?("variations")

  bottle_tag ||= Homebrew::SimulateSystem.current_tag

  if (variation = json.dig("variations", bottle_tag.to_s).presence) ||
     (variation = json.dig("variations", bottle_tag.to_sym).presence)
    json = json.merge(variation)
  end

  json.except("variations")
end

.tap_from_source_download(path) ⇒ Tap?

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:



185
186
187
188
189
190
191
192
193
194
# File 'api.rb', line 185

def self.tap_from_source_download(path)
  path = path.expand_path
  source_relative_path = path.relative_path_from(Homebrew::API::HOMEBREW_CACHE_API_SOURCE)
  return if source_relative_path.to_s.start_with?("../")

  org, repo = source_relative_path.each_filename.first(2)
  return if org.blank? || repo.blank?

  Tap.fetch(org, repo)
end

.write_names_file(names, type, regenerate:) ⇒ 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.

Parameters:

Returns:

  • (Boolean)


145
146
147
148
149
150
151
152
153
# File 'api.rb', line 145

def self.write_names_file(names, type, regenerate:)
  names_path = HOMEBREW_CACHE_API/"#{type}_names.txt"
  if !names_path.exist? || regenerate
    names_path.write(names.join("\n"))
    return true
  end

  false
end