Module: Homebrew::API Private

Extended by:
Cachable, T::Generic, Utils::Output::Mixin
Defined in:
api.rb,
api/cask.rb,
api/formula.rb,
api/internal.rb,
api/analytics.rb,
api/cask_struct.rb,
api/json_download.rb,
api/formula_struct.rb,
api/source_download.rb,
api/cask/cask_struct_generator.rb,
api/formula/formula_struct_generator.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, Internal Classes: CaskStruct, FormulaStruct, JSONDownload, JSONDownloadStrategy, SourceDownload, SourceDownloadStrategy

Constant Summary collapse

Cache =

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.

type_template { { fixed: T::Hash[String, T.untyped] } }
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)
DEFAULT_API_STALE_SECONDS =

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.

7 days

T.let(7 * 24 * 60 * 60, Integer)

Class Method Summary collapse

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_outdated, pretty_uninstalled, pretty_upgradable

Methods included from Cachable

cache, clear_cache

Class Method Details

.cached_cask_json_file_pathPathname

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.

Returns:



447
448
449
450
451
452
453
# File 'api.rb', line 447

def self.cached_cask_json_file_path
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.cached_packages_json_file_path
  else
    Homebrew::API::Cask.cached_json_file_path
  end
end

.cached_formula_json_file_pathPathname

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.

Returns:



411
412
413
414
415
416
417
# File 'api.rb', line 411

def self.cached_formula_json_file_path
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.cached_packages_json_file_path
  else
    Homebrew::API::Formula.cached_json_file_path
  end
end

.cask_renamesHash{String => 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.

Returns:



429
430
431
432
433
434
435
# File 'api.rb', line 429

def self.cask_renames
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.cask_renames
  else
    Homebrew::API::Cask.all_renames
  end
end

.cask_tap_migrationsHash{String => 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.

Returns:



438
439
440
441
442
443
444
# File 'api.rb', line 438

def self.cask_tap_migrations
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.cask_tap_migrations
  else
    Homebrew::API::Cask.tap_migrations
  end
end

.cask_tokensArray<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.

Returns:



420
421
422
423
424
425
426
# File 'api.rb', line 420

def self.cask_tokens
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.cask_hashes.keys
  else
    Homebrew::API::Cask.all_casks.keys
  end
end

.download_executables_file_from_github_packages!(target) ⇒ 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)


294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'api.rb', line 294

def self.download_executables_file_from_github_packages!(target)
  github_packages_url = "https://ghcr.io/v2/homebrew/command-not-found/executables"
  manifest_args = [
    "--fail", "--location",
    "--header", "Accept: application/vnd.oci.image.manifest.v1+json",
    "#{github_packages_url}/manifests/latest"
  ]
  if HOMEBREW_GITHUB_PACKAGES_AUTH.present?
    manifest_args.insert(-2, "--header", "Authorization: #{HOMEBREW_GITHUB_PACKAGES_AUTH}")
  end

  manifest_output = Utils::Curl.curl_output(*manifest_args, show_error: false)
  return false unless manifest_output.success?

  manifest = JSON.parse(manifest_output.stdout)
  layers = T.cast(manifest.fetch("layers"), T::Array[T::Hash[String, T.untyped]])
  layer = layers.find do |candidate|
    candidate.dig("annotations", "org.opencontainers.image.title") == target.basename.to_s
  end
  return false if layer.nil?

  digest = T.cast(layer["digest"], T.nilable(String))
  return false if digest.blank?

  download_args = ["--fail"]
  if HOMEBREW_GITHUB_PACKAGES_AUTH.present?
    download_args += ["--header", "Authorization: #{HOMEBREW_GITHUB_PACKAGES_AUTH}"]
  end
  download_args << "#{github_packages_url}/blobs/#{digest}"
  target.dirname.mkpath
  Utils::Curl.curl_download(*download_args, to: target, show_error: false)
  FileUtils.touch(target)
  true
rescue ErrorDuringExecution, JSON::ParserError, KeyError, TypeError
  target.unlink if target.exist? && target.empty?

  false
end

.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:



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'api.rb', line 32

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_api_files!void

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.

This method returns an undefined value.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'api.rb', line 185

def self.fetch_api_files!
  download_queue = Homebrew::DownloadQueue.new

  stale_seconds = if ENV["HOMEBREW_API_UPDATED"].present? ||
                     (Homebrew::EnvConfig.no_auto_update? && !Homebrew::EnvConfig.force_api_auto_update?)
    nil
  elsif Homebrew.auto_update_command?
    Homebrew::EnvConfig.api_auto_update_secs.to_i
  else
    DEFAULT_API_STALE_SECONDS
  end

  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.fetch_packages_api!(download_queue:, stale_seconds:, enqueue: true)
  else
    Homebrew::API::Formula.fetch_api!(download_queue:, stale_seconds:, enqueue: true)
    Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds: DEFAULT_API_STALE_SECONDS,
                                                 enqueue: true)
    Homebrew::API::Cask.fetch_api!(download_queue:, stale_seconds:, enqueue: true)
    Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds: DEFAULT_API_STALE_SECONDS,
                                              enqueue: true)
  end

  ENV["HOMEBREW_API_UPDATED"] = "1"

  begin
    download_queue.fetch
  ensure
    download_queue.shutdown
  end
end

.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint, stale_seconds: nil, download_queue: Homebrew.default_download_queue, enqueue: false) ⇒ 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, nil) (defaults to: nil)
  • download_queue (DownloadQueue) (defaults to: Homebrew.default_download_queue)
  • enqueue (Boolean) (defaults to: false)

Returns:



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
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
160
161
162
163
164
165
# File 'api.rb', line 67

def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
                             stale_seconds: nil, download_queue: Homebrew.default_download_queue,
                             enqueue: false)
  # 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 = skip_download?(target:, stale_seconds:)

  if enqueue
    unless skip_download
      require "api/json_download"
      download = Homebrew::API::JSONDownload.new(endpoint, target:, stale_seconds:)
      download_queue.enqueue(download)
    end
    return [{}, false]
  end

  json_data = begin
    download_succeeded = T.let(false, T::Boolean)
    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)
        download_succeeded = true
      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

    # Only refresh the cache mtime after a successful curl revalidation/download.
    # Touching after a failed download would mark a stale cache as fresh and
    # cause `skip_download?` to short-circuit subsequent retries until cleanup.
    if download_succeeded
      mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now
      FileUtils.touch(target, mtime:)
    end
    # 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

.formula_aliasesHash{String => 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.

Returns:



384
385
386
387
388
389
390
# File 'api.rb', line 384

def self.formula_aliases
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.formula_aliases
  else
    Homebrew::API::Formula.all_aliases
  end
end

.formula_namesArray<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.

Returns:



375
376
377
378
379
380
381
# File 'api.rb', line 375

def self.formula_names
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.formula_hashes.keys
  else
    Homebrew::API::Formula.all_formulae.keys
  end
end

.formula_renamesHash{String => 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.

Returns:



393
394
395
396
397
398
399
# File 'api.rb', line 393

def self.formula_renames
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.formula_renames
  else
    Homebrew::API::Formula.all_renames
  end
end

.formula_tap_migrationsHash{String => 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.

Returns:



402
403
404
405
406
407
408
# File 'api.rb', line 402

def self.formula_tap_migrations
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.formula_tap_migrations
  else
    Homebrew::API::Formula.tap_migrations
  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:



171
172
173
174
175
176
177
178
179
180
181
182
# File 'api.rb', line 171

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

.skip_download?(target:, stale_seconds:) ⇒ 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)


50
51
52
53
54
55
56
# File 'api.rb', line 50

def self.skip_download?(target:, stale_seconds:)
  return true if Homebrew.running_as_root_but_not_owned_by_root?
  return false if !target.exist? || target.empty?
  return true unless stale_seconds

  (Time.now - stale_seconds) < target.mtime
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:



363
364
365
366
367
368
369
370
371
372
# File 'api.rb', line 363

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_aliases_file!(aliases, 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)


241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'api.rb', line 241

def self.write_aliases_file!(aliases, type, regenerate:)
  aliases_path = HOMEBREW_CACHE_API/"#{type}_aliases.txt"
  if !aliases_path.exist? || regenerate
    aliases_text = aliases.map do |alias_name, real_name|
      "#{alias_name}|#{real_name}"
    end
    aliases_path.unlink if aliases_path.exist?
    aliases_path.write(aliases_text.sort.join("\n"))
    return true
  end

  false
end

.write_executables_file!(formulae, 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)


261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'api.rb', line 261

def self.write_executables_file!(formulae, regenerate:)
  executables_path = HOMEBREW_CACHE_API/"internal/executables.txt"
  executables_lines = formulae.filter_map do |name, hash|
    executables = T.cast(hash["executables"], T.nilable(T::Array[String]))
    next if executables.blank?

    "#{name}:#{executables.join(" ")}"
  end
  if executables_lines.empty?
    begin
      executables_path.unlink
      return true
    rescue Errno::ENOENT
      return false
    end
  end

  contents = "#{executables_lines.sort.join("\n")}\n"
  cached_contents = begin
    executables_path.read unless regenerate
  rescue Errno::ENOENT
    nil
  end
  if regenerate || cached_contents != contents
    executables_path.dirname.mkpath
    executables_path.write(contents)
    return true
  end

  false
end

.write_names_and_aliasesvoid

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.

This method returns an undefined value.



218
219
220
221
222
223
224
225
226
# File 'api.rb', line 218

def self.write_names_and_aliases
  if Homebrew::EnvConfig.use_internal_api?
    Homebrew::API::Internal.write_formula_names_and_aliases
    Homebrew::API::Internal.write_cask_names
  else
    Homebrew::API::Formula.write_names_and_aliases
    Homebrew::API::Cask.write_names
  end
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)


229
230
231
232
233
234
235
236
237
238
# File 'api.rb', line 229

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

  false
end