Module: Homebrew::API Private

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

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.

(HOMEBREW_CACHE/"api").freeze

Class Method Summary collapse

Methods included from Cachable

cache, clear_cache

Class Method Details

.fetch(endpoint) ⇒ Hash

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:

  • (Hash)


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'api.rb', line 21

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)
rescue JSON::ParserError
  raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}"
end

.fetch_homebrew_cask_source(name, git_head: nil, sha256: nil) ⇒ 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:

  • name (String)
  • git_head (String, nil) (defaults to: nil)
  • sha256 (String, nil) (defaults to: nil)

Returns:



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'api.rb', line 120

def self.fetch_homebrew_cask_source(name, git_head: nil, sha256: nil)
  # TODO: unify with formula logic (https://github.com/Homebrew/brew/issues/14746)
  git_head = "master" if git_head.blank?
  raw_endpoint = "#{git_head}/Casks/#{name}.rb"
  return cache[raw_endpoint] if cache.present? && cache.key?(raw_endpoint)

  # This API sometimes returns random 404s so needs a fallback at formulae.brew.sh.
  raw_source_url = "https://raw.githubusercontent.com/Homebrew/homebrew-cask/#{raw_endpoint}"
  api_source_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/cask-source/#{name}.rb"

  url = raw_source_url
  output = Utils::Curl.curl_output("--fail", url)

  if !output.success? || output.blank?
    url = api_source_url
    output = Utils::Curl.curl_output("--fail", url)
    if !output.success? || output.blank?
      raise ArgumentError, <<~EOS
        No valid file found at either of:
        #{Tty.underline}#{raw_source_url}#{Tty.reset}
        #{Tty.underline}#{api_source_url}#{Tty.reset}
      EOS
    end
  end

  cask_source = output.stdout
  actual_sha256 = Digest::SHA256.hexdigest(cask_source)
  if sha256 && actual_sha256 != sha256
    raise ArgumentError, <<~EOS
      SHA256 mismatch
      Expected: #{Formatter.success(sha256.to_s)}
        Actual: #{Formatter.error(actual_sha256.to_s)}
           URL: #{url}
      Check if you can access the URL in your browser.
      Regardless, try again in a few minutes.
    EOS
  end

  cache[raw_endpoint] = cask_source
end

.fetch_json_api_file(endpoint, target:) ⇒ Array([Array, Hash], 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:



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
# File 'api.rb', line 39

def self.fetch_json_api_file(endpoint, target:)
  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

  # TODO: consider using more of Utils::Curl
  curl_args = %W[
    --compressed
    --speed-limit #{ENV.fetch("HOMEBREW_CURL_SPEED_LIMIT")}
    --speed-time #{ENV.fetch("HOMEBREW_CURL_SPEED_TIME")}
  ]
  curl_args << "--progress-bar" unless Context.current.verbose?
  curl_args << "--verbose" if Homebrew::EnvConfig.curl_verbose?
  curl_args << "--silent" if !$stdout.tty? || Context.current.quiet?

  skip_download = target.exist? &&
                  !target.empty? &&
                  (!Homebrew.auto_update_command? ||
                    Homebrew::EnvConfig.no_auto_update? ||
                  ((Time.now - Homebrew::EnvConfig.api_auto_update_secs.to_i) < 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?
      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

    FileUtils.touch(target) unless skip_download
    JSON.parse(target.read)
  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) ⇒ Hash

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:

  • json (Hash)

Returns:

  • (Hash)


162
163
164
165
166
167
168
169
170
171
172
# File 'api.rb', line 162

def self.merge_variations(json)
  bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os,
                                         arch:   Homebrew::SimulateSystem.current_arch)

  if (variations = json["variations"].presence) &&
     (variation = variations[bottle_tag.to_s].presence)
    json = json.merge(variation)
  end

  json.except("variations")
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)


175
176
177
178
179
180
181
182
183
# File 'api.rb', line 175

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