Merge pull request #20619 from Homebrew/single-api-fetch-per-run

Download new API files once per Homebrew instance
This commit is contained in:
Mike McQuaid 2025-09-04 07:44:49 +00:00 committed by GitHub
commit c73538ac03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 49 additions and 30 deletions

View File

@ -17,7 +17,7 @@ module Homebrew
HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname) HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
HOMEBREW_CACHE_API_SOURCE = T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname) HOMEBREW_CACHE_API_SOURCE = T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname)
TAP_MIGRATIONS_STALE_SECONDS = T.let(86400, Integer) # 1 day DEFAULT_API_STALE_SECONDS = T.let(86400, Integer) # 1 day
sig { params(endpoint: String).returns(T::Hash[String, T.untyped]) } sig { params(endpoint: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(endpoint) def self.fetch(endpoint)
@ -37,12 +37,25 @@ module Homebrew
raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}" raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}"
end end
sig { params(target: Pathname, stale_seconds: T.nilable(Integer)).returns(T::Boolean) }
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
sig { sig {
params(endpoint: String, target: Pathname, stale_seconds: Integer, download_queue: T.nilable(DownloadQueue)) params(
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) endpoint: String,
target: Pathname,
stale_seconds: T.nilable(Integer),
download_queue: T.nilable(DownloadQueue),
).returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
} }
def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint, def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i, download_queue: nil) stale_seconds: nil, download_queue: nil)
# Lazy-load dependency. # Lazy-load dependency.
require "development_tools" require "development_tools"
@ -63,12 +76,7 @@ module Homebrew
insecure_download = DevelopmentTools.ca_file_substitution_required? || insecure_download = DevelopmentTools.ca_file_substitution_required? ||
DevelopmentTools.curl_substitution_required? DevelopmentTools.curl_substitution_required?
skip_download = target.exist? && skip_download = skip_download?(target:, stale_seconds:)
!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?
if download_queue if download_queue
unless skip_download unless skip_download
@ -161,17 +169,28 @@ module Homebrew
require "download_queue" require "download_queue"
Homebrew::DownloadQueue.new Homebrew::DownloadQueue.new
end end
stale_seconds = 86400 # 1 day
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? if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.fetch_formula_api!(download_queue:, stale_seconds:) Homebrew::API::Internal.fetch_formula_api!(download_queue:, stale_seconds:)
Homebrew::API::Internal.fetch_cask_api!(download_queue:, stale_seconds:) Homebrew::API::Internal.fetch_cask_api!(download_queue:, stale_seconds:)
else else
Homebrew::API::Formula.fetch_api!(download_queue:, stale_seconds:) Homebrew::API::Formula.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds:) Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds: DEFAULT_API_STALE_SECONDS)
Homebrew::API::Cask.fetch_api!(download_queue:, stale_seconds:) Homebrew::API::Cask.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds:) Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds: DEFAULT_API_STALE_SECONDS)
end end
ENV["HOMEBREW_API_UPDATED"] = "1"
return unless download_queue return unless download_queue
begin begin

View File

@ -75,18 +75,18 @@ module Homebrew
end end
sig { sig {
params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: Integer) params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) .returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
} }
def self.fetch_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) def self.fetch_api!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue: Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue:
end end
sig { sig {
params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: Integer) params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) .returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
} }
def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: Homebrew::API::TAP_MIGRATIONS_STALE_SECONDS) def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json", stale_seconds:, download_queue: Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json", stale_seconds:, download_queue:
end end

View File

@ -74,18 +74,18 @@ module Homebrew
end end
sig { sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer) params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) .returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
} }
def self.fetch_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) def self.fetch_api!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue: Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue:
end end
sig { sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer) params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) .returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
} }
def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: Homebrew::API::TAP_MIGRATIONS_STALE_SECONDS) def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file "formula_tap_migrations.jws.json", stale_seconds:, download_queue: Homebrew::API.fetch_json_api_file "formula_tap_migrations.jws.json", stale_seconds:, download_queue:
end end

View File

@ -56,20 +56,20 @@ module Homebrew
end end
sig { sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer) params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T::Hash[String, T.untyped], T::Boolean]) .returns([T::Hash[String, T.untyped], T::Boolean])
} }
def self.fetch_formula_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) def self.fetch_formula_api!(download_queue: nil, stale_seconds: nil)
json_contents, updated = (Homebrew::API.fetch_json_api_file formula_endpoint, stale_seconds:, download_queue:) json_contents, updated = Homebrew::API.fetch_json_api_file(formula_endpoint, stale_seconds:, download_queue:)
[T.cast(json_contents, T::Hash[String, T.untyped]), updated] [T.cast(json_contents, T::Hash[String, T.untyped]), updated]
end end
sig { sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer) params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T::Hash[String, T.untyped], T::Boolean]) .returns([T::Hash[String, T.untyped], T::Boolean])
} }
def self.fetch_cask_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) def self.fetch_cask_api!(download_queue: nil, stale_seconds: nil)
json_contents, updated = (Homebrew::API.fetch_json_api_file cask_endpoint, stale_seconds:, download_queue:) json_contents, updated = Homebrew::API.fetch_json_api_file(cask_endpoint, stale_seconds:, download_queue:)
[T.cast(json_contents, T::Hash[String, T.untyped]), updated] [T.cast(json_contents, T::Hash[String, T.untyped]), updated]
end end

View File

@ -10,13 +10,13 @@ module Homebrew
def initialize(url, name, version, **meta) def initialize(url, name, version, **meta)
super super
@target = T.let(meta.fetch(:target), Pathname) @target = T.let(meta.fetch(:target), Pathname)
@stale_seconds = T.let(meta.fetch(:stale_seconds), Integer) @stale_seconds = T.let(meta[:stale_seconds], T.nilable(Integer))
end end
sig { override.params(timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) } sig { override.params(timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) }
def fetch(timeout: nil) def fetch(timeout: nil)
with_context quiet: quiet? do with_context quiet: quiet? do
Homebrew::API.fetch_json_api_file(url, target: cached_location, stale_seconds: meta.fetch(:stale_seconds)) Homebrew::API.fetch_json_api_file(url, target: cached_location, stale_seconds: meta[:stale_seconds])
end end
cached_location cached_location
end end
@ -30,7 +30,7 @@ module Homebrew
class JSONDownload class JSONDownload
include Downloadable include Downloadable
sig { params(url: String, target: Pathname, stale_seconds: Integer).void } sig { params(url: String, target: Pathname, stale_seconds: T.nilable(Integer)).void }
def initialize(url, target:, stale_seconds:) def initialize(url, target:, stale_seconds:)
super() super()
@url = T.let(URL.new(url, using: API::JSONDownloadStrategy, target:, stale_seconds:), URL) @url = T.let(URL.new(url, using: API::JSONDownloadStrategy, target:, stale_seconds:), URL)