mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Refactor API methods
This commit is contained in:
parent
6bb369916c
commit
737dd1654b
39
Library/Homebrew/api.rb
Normal file
39
Library/Homebrew/api.rb
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "api/analytics"
|
||||||
|
require "api/bottle"
|
||||||
|
require "api/cask"
|
||||||
|
require "api/formula"
|
||||||
|
require "api/versions"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
# Helper functions for using Homebrew's formulae.brew.sh API.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module API
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
API_DOMAIN = "https://formulae.brew.sh/api"
|
||||||
|
|
||||||
|
sig { params(endpoint: String, json: T::Boolean).returns(T.any(String, Hash)) }
|
||||||
|
def fetch(endpoint, json: false)
|
||||||
|
return @cache[endpoint] if @cache.present? && @cache.key?(endpoint)
|
||||||
|
|
||||||
|
api_url = "#{API_DOMAIN}/#{endpoint}"
|
||||||
|
output = Utils::Curl.curl_output("--fail", "--max-time", "5", api_url)
|
||||||
|
raise ArgumentError, "No file found at #{Tty.underline}#{api_url}#{Tty.reset}" unless output.success?
|
||||||
|
|
||||||
|
@cache ||= {}
|
||||||
|
@cache[endpoint] = if json
|
||||||
|
JSON.parse(output.stdout)
|
||||||
|
else
|
||||||
|
output.stdout
|
||||||
|
end
|
||||||
|
rescue JSON::ParserError
|
||||||
|
raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
Library/Homebrew/api/analytics.rb
Normal file
28
Library/Homebrew/api/analytics.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
# Helper functions for using the analytics JSON API.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module Analytics
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def analytics_api_path
|
||||||
|
"analytics"
|
||||||
|
end
|
||||||
|
alias generic_analytics_api_path analytics_api_path
|
||||||
|
|
||||||
|
sig { params(category: String, days: T.any(Integer, String)).returns(Hash) }
|
||||||
|
def fetch(category, days)
|
||||||
|
Homebrew::API.fetch "#{analytics_api_path}/#{category}/#{days}d.json", json: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "extend/os/api/analytics"
|
95
Library/Homebrew/api/bottle.rb
Normal file
95
Library/Homebrew/api/bottle.rb
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "github_packages"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
# Helper functions for using the Bottle JSON API.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module Bottle
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def bottle_api_path
|
||||||
|
"bottle"
|
||||||
|
end
|
||||||
|
alias generic_bottle_api_path bottle_api_path
|
||||||
|
|
||||||
|
GITHUB_PACKAGES_SHA256_REGEX = %r{#{GitHubPackages::URL_REGEX}.*/blobs/sha256:(?<sha256>\h{64})$}.freeze
|
||||||
|
|
||||||
|
sig { params(name: String).returns(Hash) }
|
||||||
|
def fetch(name)
|
||||||
|
Homebrew::API.fetch "#{bottle_api_path}/#{name}.json", json: true
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(name: String).returns(T::Boolean) }
|
||||||
|
def available?(name)
|
||||||
|
fetch name
|
||||||
|
true
|
||||||
|
rescue ArgumentError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(name: String).void }
|
||||||
|
def fetch_bottles(name)
|
||||||
|
hash = fetch(name)
|
||||||
|
bottle_tag = Utils::Bottles.tag.to_s
|
||||||
|
|
||||||
|
if !hash["bottles"].key?(bottle_tag) && !hash["bottles"].key?("all")
|
||||||
|
odie "No bottle available for #{name} on the current OS"
|
||||||
|
end
|
||||||
|
|
||||||
|
download_bottle(hash, bottle_tag)
|
||||||
|
|
||||||
|
hash["dependencies"].each do |dep_hash|
|
||||||
|
existing_formula = begin
|
||||||
|
Formulary.factory dep_hash["name"]
|
||||||
|
rescue FormulaUnavailableError
|
||||||
|
# The formula might not exist if it's not installed and homebrew/core isn't tapped
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
next if existing_formula.present? && existing_formula.latest_version_installed?
|
||||||
|
|
||||||
|
download_bottle(dep_hash, bottle_tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(url: String).returns(T.nilable(String)) }
|
||||||
|
def checksum_from_url(url)
|
||||||
|
match = url.match GITHUB_PACKAGES_SHA256_REGEX
|
||||||
|
return if match.blank?
|
||||||
|
|
||||||
|
match[:sha256]
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(hash: Hash, tag: Symbol).void }
|
||||||
|
def download_bottle(hash, tag)
|
||||||
|
bottle = hash["bottles"][tag]
|
||||||
|
bottle ||= hash["bottles"]["all"]
|
||||||
|
return if bottle.blank?
|
||||||
|
|
||||||
|
sha256 = bottle["sha256"] || checksum_from_url(bottle["url"])
|
||||||
|
bottle_filename = ::Bottle::Filename.new(hash["name"], hash["pkg_version"], tag, hash["rebuild"])
|
||||||
|
|
||||||
|
resource = Resource.new hash["name"]
|
||||||
|
resource.url bottle["url"]
|
||||||
|
resource.sha256 sha256
|
||||||
|
resource.version hash["pkg_version"]
|
||||||
|
resource.downloader.resolved_basename = bottle_filename
|
||||||
|
|
||||||
|
resource.fetch
|
||||||
|
|
||||||
|
# Map the name of this formula to the local bottle path to allow the
|
||||||
|
# formula to be loaded by passing just the name to `Formulary::factory`.
|
||||||
|
Formulary.map_formula_name_to_local_bottle_path hash["name"], resource.downloader.cached_location
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "extend/os/api/bottle"
|
20
Library/Homebrew/api/cask.rb
Normal file
20
Library/Homebrew/api/cask.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
# Helper functions for using the cask JSON API.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module Cask
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
sig { params(name: String).returns(Hash) }
|
||||||
|
def fetch(name)
|
||||||
|
Homebrew::API.fetch "cask/#{name}.json", json: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
Library/Homebrew/api/formula.rb
Normal file
28
Library/Homebrew/api/formula.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
# Helper functions for using the formula JSON API.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module Formula
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
def formula_api_path
|
||||||
|
"formula"
|
||||||
|
end
|
||||||
|
alias generic_formula_api_path formula_api_path
|
||||||
|
|
||||||
|
sig { params(name: String).returns(Hash) }
|
||||||
|
def fetch(name)
|
||||||
|
Homebrew::API.fetch "#{formula_api_path}/#{name}.json", json: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "extend/os/api/formula"
|
52
Library/Homebrew/api/versions.rb
Normal file
52
Library/Homebrew/api/versions.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
# Helper functions for using the versions JSON API.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module Versions
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def formulae
|
||||||
|
# The result is cached by Homebrew::API.fetch
|
||||||
|
Homebrew::API.fetch "versions-formulae.json", json: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def linux
|
||||||
|
# The result is cached by Homebrew::API.fetch
|
||||||
|
Homebrew::API.fetch "versions-linux.json", json: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def casks
|
||||||
|
# The result is cached by Homebrew::API.fetch
|
||||||
|
Homebrew::API.fetch "versions-casks.json", json: true
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(name: String).returns(T.nilable(PkgVersion)) }
|
||||||
|
def latest_formula_version(name)
|
||||||
|
versions = if OS.mac? || Homebrew::EnvConfig.force_homebrew_on_linux?
|
||||||
|
formulae
|
||||||
|
else
|
||||||
|
linux
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless versions.key? name
|
||||||
|
|
||||||
|
version = Version.new(versions[name]["version"])
|
||||||
|
revision = versions[name]["revision"]
|
||||||
|
PkgVersion.new(version, revision)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(token: String).returns(T.nilable(Version)) }
|
||||||
|
def latest_cask_version(token)
|
||||||
|
return unless casks.key? token
|
||||||
|
|
||||||
|
Version.new(casks[token]["version"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,118 +0,0 @@
|
|||||||
# typed: true
|
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "github_packages"
|
|
||||||
|
|
||||||
# Helper functions for using the Bottle JSON API.
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
module BottleAPI
|
|
||||||
extend T::Sig
|
|
||||||
|
|
||||||
module_function
|
|
||||||
|
|
||||||
FORMULAE_BREW_SH_BOTTLE_API_DOMAIN = if OS.mac?
|
|
||||||
"https://formulae.brew.sh/api/bottle"
|
|
||||||
else
|
|
||||||
"https://formulae.brew.sh/api/bottle-linux"
|
|
||||||
end.freeze
|
|
||||||
|
|
||||||
FORMULAE_BREW_SH_VERSIONS_API_URL = if OS.mac?
|
|
||||||
"https://formulae.brew.sh/api/versions-formulae.json"
|
|
||||||
else
|
|
||||||
"https://formulae.brew.sh/api/versions-linux.json"
|
|
||||||
end.freeze
|
|
||||||
|
|
||||||
GITHUB_PACKAGES_SHA256_REGEX = %r{#{GitHubPackages::URL_REGEX}.*/blobs/sha256:(?<sha256>\h{64})$}.freeze
|
|
||||||
|
|
||||||
sig { params(name: String).returns(Hash) }
|
|
||||||
def fetch(name)
|
|
||||||
return @cache[name] if @cache.present? && @cache.key?(name)
|
|
||||||
|
|
||||||
api_url = "#{FORMULAE_BREW_SH_BOTTLE_API_DOMAIN}/#{name}.json"
|
|
||||||
output = Utils::Curl.curl_output("--fail", api_url)
|
|
||||||
raise ArgumentError, "No JSON file found at #{Tty.underline}#{api_url}#{Tty.reset}" unless output.success?
|
|
||||||
|
|
||||||
@cache ||= {}
|
|
||||||
@cache[name] = JSON.parse(output.stdout)
|
|
||||||
rescue JSON::ParserError
|
|
||||||
raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}"
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(name: String).returns(T.nilable(PkgVersion)) }
|
|
||||||
def latest_pkg_version(name)
|
|
||||||
@formula_versions ||= begin
|
|
||||||
output = Utils::Curl.curl_output("--fail", FORMULAE_BREW_SH_VERSIONS_API_URL)
|
|
||||||
JSON.parse(output.stdout)
|
|
||||||
end
|
|
||||||
|
|
||||||
return unless @formula_versions.key? name
|
|
||||||
|
|
||||||
version = Version.new(@formula_versions[name]["version"])
|
|
||||||
revision = @formula_versions[name]["revision"]
|
|
||||||
PkgVersion.new(version, revision)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(name: String).returns(T::Boolean) }
|
|
||||||
def bottle_available?(name)
|
|
||||||
fetch name
|
|
||||||
true
|
|
||||||
rescue ArgumentError
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(name: String).void }
|
|
||||||
def fetch_bottles(name)
|
|
||||||
hash = fetch(name)
|
|
||||||
bottle_tag = Utils::Bottles.tag.to_s
|
|
||||||
|
|
||||||
if !hash["bottles"].key?(bottle_tag) && !hash["bottles"].key?("all")
|
|
||||||
odie "No bottle available for #{name} on the current OS"
|
|
||||||
end
|
|
||||||
|
|
||||||
download_bottle(hash, bottle_tag)
|
|
||||||
|
|
||||||
hash["dependencies"].each do |dep_hash|
|
|
||||||
existing_formula = begin
|
|
||||||
Formulary.factory dep_hash["name"]
|
|
||||||
rescue FormulaUnavailableError
|
|
||||||
# The formula might not exist if it's not installed and homebrew/core isn't tapped
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
next if existing_formula.present? && existing_formula.latest_version_installed?
|
|
||||||
|
|
||||||
download_bottle(dep_hash, bottle_tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(url: String).returns(T.nilable(String)) }
|
|
||||||
def checksum_from_url(url)
|
|
||||||
match = url.match GITHUB_PACKAGES_SHA256_REGEX
|
|
||||||
return if match.blank?
|
|
||||||
|
|
||||||
match[:sha256]
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(hash: Hash, tag: Symbol).void }
|
|
||||||
def download_bottle(hash, tag)
|
|
||||||
bottle = hash["bottles"][tag]
|
|
||||||
bottle ||= hash["bottles"]["all"]
|
|
||||||
return if bottle.blank?
|
|
||||||
|
|
||||||
sha256 = bottle["sha256"] || checksum_from_url(bottle["url"])
|
|
||||||
bottle_filename = Bottle::Filename.new(hash["name"], hash["pkg_version"], tag, hash["rebuild"])
|
|
||||||
|
|
||||||
resource = Resource.new hash["name"]
|
|
||||||
resource.url bottle["url"]
|
|
||||||
resource.sha256 sha256
|
|
||||||
resource.version hash["pkg_version"]
|
|
||||||
resource.downloader.resolved_basename = bottle_filename
|
|
||||||
|
|
||||||
resource.fetch
|
|
||||||
|
|
||||||
# Map the name of this formula to the local bottle path to allow the
|
|
||||||
# formula to be loaded by passing just the name to `Formulary::factory`.
|
|
||||||
Formulary.map_formula_name_to_local_bottle_path hash["name"], resource.downloader.cached_location
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,5 +0,0 @@
|
|||||||
# typed: strict
|
|
||||||
|
|
||||||
module BottleAPI
|
|
||||||
include Kernel
|
|
||||||
end
|
|
@ -2,7 +2,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "delegate"
|
require "delegate"
|
||||||
require "bottle_api"
|
require "api"
|
||||||
require "cli/args"
|
require "cli/args"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
@ -94,8 +94,9 @@ module Homebrew
|
|||||||
unreadable_error = nil
|
unreadable_error = nil
|
||||||
|
|
||||||
if only != :cask
|
if only != :cask
|
||||||
if prefer_loading_from_json && ENV["HOMEBREW_JSON_CORE"].present? && BottleAPI.bottle_available?(name)
|
if prefer_loading_from_json && ENV["HOMEBREW_JSON_CORE"].present? &&
|
||||||
BottleAPI.fetch_bottles(name)
|
Homebrew::API::Bottle.available?(name)
|
||||||
|
Homebrew::API::Bottle.fetch_bottles(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
@ -11,6 +11,7 @@ require "tab"
|
|||||||
require "json"
|
require "json"
|
||||||
require "utils/spdx"
|
require "utils/spdx"
|
||||||
require "deprecate_disable"
|
require "deprecate_disable"
|
||||||
|
require "api"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -243,8 +244,8 @@ module Homebrew
|
|||||||
def info_formula(f, args:)
|
def info_formula(f, args:)
|
||||||
specs = []
|
specs = []
|
||||||
|
|
||||||
if ENV["HOMEBREW_JSON_CORE"].present? && BottleAPI.bottle_available?(f.name)
|
if ENV["HOMEBREW_JSON_CORE"].present? && Homebrew::API::Bottle.available?(f.name)
|
||||||
info = BottleAPI.fetch(f.name)
|
info = Homebrew::API::Bottle.fetch(f.name)
|
||||||
|
|
||||||
latest_version = info["pkg_version"].split("_").first
|
latest_version = info["pkg_version"].split("_").first
|
||||||
bottle_exists = info["bottles"].key?(Utils::Bottles.tag.to_s) || info["bottles"].key?("all")
|
bottle_exists = info["bottles"].key?(Utils::Bottles.tag.to_s) || info["bottles"].key?("all")
|
||||||
|
@ -6,7 +6,7 @@ require "keg"
|
|||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "cask/cmd"
|
require "cask/cmd"
|
||||||
require "cask/caskroom"
|
require "cask/caskroom"
|
||||||
require "bottle_api"
|
require "api"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -99,7 +99,7 @@ module Homebrew
|
|||||||
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
|
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
|
||||||
|
|
||||||
current_version = if ENV["HOMEBREW_JSON_CORE"].present? && (f.core_formula? || f.tap.blank?)
|
current_version = if ENV["HOMEBREW_JSON_CORE"].present? && (f.core_formula? || f.tap.blank?)
|
||||||
BottleAPI.latest_pkg_version(f.name)&.to_s || f.pkg_version.to_s
|
Homebrew::API::Versions.latest_formula_version(f.name)&.to_s || f.pkg_version.to_s
|
||||||
elsif f.alias_changed? && !f.latest_formula.latest_version_installed?
|
elsif f.alias_changed? && !f.latest_formula.latest_version_installed?
|
||||||
latest = f.latest_formula
|
latest = f.latest_formula
|
||||||
"#{latest.name} (#{latest.pkg_version})"
|
"#{latest.name} (#{latest.pkg_version})"
|
||||||
|
@ -12,7 +12,7 @@ require "cask/cmd"
|
|||||||
require "cask/utils"
|
require "cask/utils"
|
||||||
require "cask/macos"
|
require "cask/macos"
|
||||||
require "upgrade"
|
require "upgrade"
|
||||||
require "bottle_api"
|
require "api"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -90,9 +90,9 @@ module Homebrew
|
|||||||
formula = Formulary.factory(name)
|
formula = Formulary.factory(name)
|
||||||
next unless formula.any_version_installed?
|
next unless formula.any_version_installed?
|
||||||
next if formula.tap.present? && !formula.core_formula?
|
next if formula.tap.present? && !formula.core_formula?
|
||||||
next unless BottleAPI.bottle_available?(name)
|
next unless Homebrew::API::Bottle.available?(name)
|
||||||
|
|
||||||
BottleAPI.fetch_bottles(name)
|
Homebrew::API::Bottle.fetch_bottles(name)
|
||||||
rescue FormulaUnavailableError
|
rescue FormulaUnavailableError
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
@ -8,7 +8,7 @@ require "upgrade"
|
|||||||
require "cask/cmd"
|
require "cask/cmd"
|
||||||
require "cask/utils"
|
require "cask/utils"
|
||||||
require "cask/macos"
|
require "cask/macos"
|
||||||
require "bottle_api"
|
require "api"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -163,9 +163,9 @@ module Homebrew
|
|||||||
if ENV["HOMEBREW_JSON_CORE"].present?
|
if ENV["HOMEBREW_JSON_CORE"].present?
|
||||||
formulae_to_install.map! do |formula|
|
formulae_to_install.map! do |formula|
|
||||||
next formula if formula.tap.present? && !formula.core_formula?
|
next formula if formula.tap.present? && !formula.core_formula?
|
||||||
next formula unless BottleAPI.bottle_available?(formula.name)
|
next formula unless Homebrew::API::Bottle.available?(formula.name)
|
||||||
|
|
||||||
BottleAPI.fetch_bottles(formula.name)
|
Homebrew::API::Bottle.fetch_bottles(formula.name)
|
||||||
Formulary.factory(formula.name)
|
Formulary.factory(formula.name)
|
||||||
rescue FormulaUnavailableError
|
rescue FormulaUnavailableError
|
||||||
formula
|
formula
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "formula"
|
require "formula"
|
||||||
|
require "api"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -87,7 +88,7 @@ module Homebrew
|
|||||||
formula_installs = {}
|
formula_installs = {}
|
||||||
|
|
||||||
ohai "Getting analytics data..."
|
ohai "Getting analytics data..."
|
||||||
analytics = Utils::Analytics.formulae_brew_sh_json("analytics/install/90d.json")
|
analytics = Homebrew::API::Analytics.fetch "install", 90
|
||||||
|
|
||||||
if analytics.blank?
|
if analytics.blank?
|
||||||
raise UsageError,
|
raise UsageError,
|
||||||
|
4
Library/Homebrew/extend/os/api/analytics.rb
Normal file
4
Library/Homebrew/extend/os/api/analytics.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "extend/os/linux/api/analytics" if OS.linux?
|
4
Library/Homebrew/extend/os/api/bottle.rb
Normal file
4
Library/Homebrew/extend/os/api/bottle.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "extend/os/linux/api/bottle" if OS.linux?
|
4
Library/Homebrew/extend/os/api/formula.rb
Normal file
4
Library/Homebrew/extend/os/api/formula.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "extend/os/linux/api/formula" if OS.linux?
|
17
Library/Homebrew/extend/os/linux/api/analytics.rb
Normal file
17
Library/Homebrew/extend/os/linux/api/analytics.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
module Analytics
|
||||||
|
class << self
|
||||||
|
sig { returns(String) }
|
||||||
|
def analytics_api_path
|
||||||
|
return generic_analytics_api_path if Homebrew::EnvConfig.force_homebrew_on_linux?
|
||||||
|
|
||||||
|
"analytics-linux"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
Library/Homebrew/extend/os/linux/api/bottle.rb
Normal file
17
Library/Homebrew/extend/os/linux/api/bottle.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
module Bottle
|
||||||
|
class << self
|
||||||
|
sig { returns(String) }
|
||||||
|
def bottle_api_path
|
||||||
|
return generic_bottle_api_path if Homebrew::EnvConfig.force_homebrew_on_linux?
|
||||||
|
|
||||||
|
"bottle-linux"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
Library/Homebrew/extend/os/linux/api/formula.rb
Normal file
17
Library/Homebrew/extend/os/linux/api/formula.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module API
|
||||||
|
module Formula
|
||||||
|
class << self
|
||||||
|
sig { returns(String) }
|
||||||
|
def formula_api_path
|
||||||
|
return generic_formula_api_path if Homebrew::EnvConfig.force_homebrew_on_linux?
|
||||||
|
|
||||||
|
"formula-linux"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,23 +0,0 @@
|
|||||||
# typed: strict
|
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Utils
|
|
||||||
module Analytics
|
|
||||||
class << self
|
|
||||||
extend T::Sig
|
|
||||||
sig { returns(String) }
|
|
||||||
def formula_path
|
|
||||||
return generic_formula_path if Homebrew::EnvConfig.force_homebrew_on_linux?
|
|
||||||
|
|
||||||
"formula-linux"
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def analytics_path
|
|
||||||
return generic_analytics_path if Homebrew::EnvConfig.force_homebrew_on_linux?
|
|
||||||
|
|
||||||
"analytics-linux"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,5 +1,4 @@
|
|||||||
# typed: strict
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "extend/os/linux/utils/analytics" if OS.linux?
|
|
||||||
require "extend/os/mac/utils/analytics" if OS.mac?
|
require "extend/os/mac/utils/analytics" if OS.mac?
|
||||||
|
@ -29,7 +29,7 @@ require "mktemp"
|
|||||||
require "find"
|
require "find"
|
||||||
require "utils/spdx"
|
require "utils/spdx"
|
||||||
require "extend/on_os"
|
require "extend/on_os"
|
||||||
require "bottle_api"
|
require "api"
|
||||||
|
|
||||||
# A formula provides instructions and metadata for Homebrew to install a piece
|
# A formula provides instructions and metadata for Homebrew to install a piece
|
||||||
# of software. Every Homebrew formula is a {Formula}.
|
# of software. Every Homebrew formula is a {Formula}.
|
||||||
@ -520,7 +520,8 @@ class Formula
|
|||||||
# exists and is not empty.
|
# exists and is not empty.
|
||||||
# @private
|
# @private
|
||||||
def latest_version_installed?
|
def latest_version_installed?
|
||||||
latest_prefix = if ENV["HOMEBREW_JSON_CORE"].present? && (latest_pkg_version = BottleAPI.latest_pkg_version(name))
|
latest_prefix = if ENV["HOMEBREW_JSON_CORE"].present? &&
|
||||||
|
(latest_pkg_version = Homebrew::API::Versions.latest_formula_version(name))
|
||||||
prefix latest_pkg_version
|
prefix latest_pkg_version
|
||||||
else
|
else
|
||||||
latest_installed_prefix
|
latest_installed_prefix
|
||||||
@ -1340,7 +1341,7 @@ class Formula
|
|||||||
all_kegs = []
|
all_kegs = []
|
||||||
current_version = T.let(false, T::Boolean)
|
current_version = T.let(false, T::Boolean)
|
||||||
latest_version = if ENV["HOMEBREW_JSON_CORE"].present? && (core_formula? || tap.blank?)
|
latest_version = if ENV["HOMEBREW_JSON_CORE"].present? && (core_formula? || tap.blank?)
|
||||||
BottleAPI.latest_pkg_version(name) || pkg_version
|
Homebrew::API::Versions.latest_formula_version(name) || pkg_version
|
||||||
else
|
else
|
||||||
pkg_version
|
pkg_version
|
||||||
end
|
end
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
# typed: false
|
# typed: false
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
describe BottleAPI do
|
require "api"
|
||||||
before do
|
|
||||||
ENV["HOMEBREW_JSON_CORE"] = "1"
|
describe Homebrew::API::Bottle do
|
||||||
end
|
# before do
|
||||||
|
# ENV["HOMEBREW_JSON_CORE"] = "1"
|
||||||
|
# end
|
||||||
|
|
||||||
let(:bottle_json) {
|
let(:bottle_json) {
|
||||||
<<~EOS
|
<<~EOS
|
||||||
@ -28,14 +30,6 @@ describe BottleAPI do
|
|||||||
EOS
|
EOS
|
||||||
}
|
}
|
||||||
let(:bottle_hash) { JSON.parse(bottle_json) }
|
let(:bottle_hash) { JSON.parse(bottle_json) }
|
||||||
let(:versions_json) {
|
|
||||||
<<~EOS
|
|
||||||
{
|
|
||||||
"foo":{"version":"1.2.3","revision":0},
|
|
||||||
"bar":{"version":"1.2","revision":4}
|
|
||||||
}
|
|
||||||
EOS
|
|
||||||
}
|
|
||||||
|
|
||||||
def mock_curl_output(stdout: "", success: true)
|
def mock_curl_output(stdout: "", success: true)
|
||||||
curl_output = OpenStruct.new(stdout: stdout, success?: success)
|
curl_output = OpenStruct.new(stdout: stdout, success?: success)
|
||||||
@ -51,7 +45,7 @@ describe BottleAPI do
|
|||||||
|
|
||||||
it "raises an error if the formula does not exist" do
|
it "raises an error if the formula does not exist" do
|
||||||
mock_curl_output success: false
|
mock_curl_output success: false
|
||||||
expect { described_class.fetch("bar") }.to raise_error(ArgumentError, /No JSON file found/)
|
expect { described_class.fetch("bar") }.to raise_error(ArgumentError, /No file found/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises an error if the bottle JSON is invalid" do
|
it "raises an error if the bottle JSON is invalid" do
|
||||||
@ -60,35 +54,15 @@ describe BottleAPI do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "::latest_pkg_version" do
|
describe "::available?" do
|
||||||
it "returns the expected `PkgVersion` when the revision is 0" do
|
|
||||||
mock_curl_output stdout: versions_json
|
|
||||||
pkg_version = described_class.latest_pkg_version("foo")
|
|
||||||
expect(pkg_version.to_s).to eq "1.2.3"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the expected `PkgVersion` when the revision is not 0" do
|
|
||||||
mock_curl_output stdout: versions_json
|
|
||||||
pkg_version = described_class.latest_pkg_version("bar")
|
|
||||||
expect(pkg_version.to_s).to eq "1.2_4"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns `nil` when the formula is not in the JSON file" do
|
|
||||||
mock_curl_output stdout: versions_json
|
|
||||||
pkg_version = described_class.latest_pkg_version("baz")
|
|
||||||
expect(pkg_version).to be_nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "::bottle_available?" do
|
|
||||||
it "returns `true` if `fetch` succeeds" do
|
it "returns `true` if `fetch` succeeds" do
|
||||||
allow(described_class).to receive(:fetch)
|
allow(described_class).to receive(:fetch)
|
||||||
expect(described_class.bottle_available?("foo")).to eq true
|
expect(described_class.available?("foo")).to eq true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns `false` if `fetch` fails" do
|
it "returns `false` if `fetch` fails" do
|
||||||
allow(described_class).to receive(:fetch).and_raise ArgumentError
|
allow(described_class).to receive(:fetch).and_raise ArgumentError
|
||||||
expect(described_class.bottle_available?("foo")).to eq false
|
expect(described_class.available?("foo")).to eq false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
55
Library/Homebrew/test/api/versions_spec.rb
Normal file
55
Library/Homebrew/test/api/versions_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "api"
|
||||||
|
|
||||||
|
describe Homebrew::API::Versions do
|
||||||
|
let(:versions_formulae_json) {
|
||||||
|
<<~EOS
|
||||||
|
{
|
||||||
|
"foo":{"version":"1.2.3","revision":0},
|
||||||
|
"bar":{"version":"1.2","revision":4}
|
||||||
|
}
|
||||||
|
EOS
|
||||||
|
}
|
||||||
|
let(:versions_casks_json) { '{"foo":{"version":"1.2.3"}}' }
|
||||||
|
|
||||||
|
def mock_curl_output(stdout: "", success: true)
|
||||||
|
curl_output = OpenStruct.new(stdout: stdout, success?: success)
|
||||||
|
allow(Utils::Curl).to receive(:curl_output).and_return curl_output
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "::latest_formula_version" do
|
||||||
|
it "returns the expected `PkgVersion` when the revision is 0" do
|
||||||
|
mock_curl_output stdout: versions_formulae_json
|
||||||
|
pkg_version = described_class.latest_formula_version("foo")
|
||||||
|
expect(pkg_version.to_s).to eq "1.2.3"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the expected `PkgVersion` when the revision is not 0" do
|
||||||
|
mock_curl_output stdout: versions_formulae_json
|
||||||
|
pkg_version = described_class.latest_formula_version("bar")
|
||||||
|
expect(pkg_version.to_s).to eq "1.2_4"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns `nil` when the formula is not in the JSON file" do
|
||||||
|
mock_curl_output stdout: versions_formulae_json
|
||||||
|
pkg_version = described_class.latest_formula_version("baz")
|
||||||
|
expect(pkg_version).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "::latest_cask_version" do
|
||||||
|
it "returns the expected `Version`" do
|
||||||
|
mock_curl_output stdout: versions_casks_json
|
||||||
|
version = described_class.latest_cask_version("foo")
|
||||||
|
expect(version.to_s).to eq "1.2.3"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns `nil` when the cask is not in the JSON file" do
|
||||||
|
mock_curl_output stdout: versions_casks_json
|
||||||
|
version = described_class.latest_cask_version("bar")
|
||||||
|
expect(version).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
39
Library/Homebrew/test/api_spec.rb
Normal file
39
Library/Homebrew/test/api_spec.rb
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "api"
|
||||||
|
|
||||||
|
describe Homebrew::API do
|
||||||
|
let(:text) { "foo" }
|
||||||
|
let(:json) { '{"foo":"bar"}' }
|
||||||
|
let(:json_hash) { JSON.parse(json) }
|
||||||
|
|
||||||
|
def mock_curl_output(stdout: "", success: true)
|
||||||
|
curl_output = OpenStruct.new(stdout: stdout, success?: success)
|
||||||
|
allow(Utils::Curl).to receive(:curl_output).and_return curl_output
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "::fetch" do
|
||||||
|
it "fetches a text file" do
|
||||||
|
mock_curl_output stdout: text
|
||||||
|
fetched_text = described_class.fetch("foo.txt")
|
||||||
|
expect(fetched_text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fetches a JSON file" do
|
||||||
|
mock_curl_output stdout: json
|
||||||
|
fetched_json = described_class.fetch("foo.json", json: true)
|
||||||
|
expect(fetched_json).to eq json_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if the file does not exist" do
|
||||||
|
mock_curl_output success: false
|
||||||
|
expect { described_class.fetch("bar.txt") }.to raise_error(ArgumentError, /No file found/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if the JSON file is invalid" do
|
||||||
|
mock_curl_output stdout: text
|
||||||
|
expect { described_class.fetch("baz.txt", json: true) }.to raise_error(ArgumentError, /Invalid JSON file/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -4,6 +4,7 @@
|
|||||||
require "context"
|
require "context"
|
||||||
require "erb"
|
require "erb"
|
||||||
require "settings"
|
require "settings"
|
||||||
|
require "api"
|
||||||
|
|
||||||
module Utils
|
module Utils
|
||||||
# Helper module for fetching and reporting analytics data.
|
# Helper module for fetching and reporting analytics data.
|
||||||
@ -129,7 +130,12 @@ module Utils
|
|||||||
def output(args:, filter: nil)
|
def output(args:, filter: nil)
|
||||||
days = args.days || "30"
|
days = args.days || "30"
|
||||||
category = args.category || "install"
|
category = args.category || "install"
|
||||||
json = formulae_brew_sh_json("analytics/#{category}/#{days}d.json")
|
begin
|
||||||
|
json = Homebrew::API::Analytics.fetch category, days
|
||||||
|
rescue ArgumentError
|
||||||
|
# Ignore failed API requests
|
||||||
|
return
|
||||||
|
end
|
||||||
return if json.blank? || json["items"].blank?
|
return if json.blank? || json["items"].blank?
|
||||||
|
|
||||||
os_version = category == "os-version"
|
os_version = category == "os-version"
|
||||||
@ -182,17 +188,27 @@ module Utils
|
|||||||
end
|
end
|
||||||
|
|
||||||
def formula_output(f, args:)
|
def formula_output(f, args:)
|
||||||
json = formulae_brew_sh_json("#{formula_path}/#{f}.json")
|
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
|
||||||
|
|
||||||
|
json = Homebrew::API::Formula.fetch f
|
||||||
return if json.blank? || json["analytics"].blank?
|
return if json.blank? || json["analytics"].blank?
|
||||||
|
|
||||||
get_analytics(json, args: args)
|
get_analytics(json, args: args)
|
||||||
|
rescue ArgumentError
|
||||||
|
# Ignore failed API requests
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def cask_output(cask, args:)
|
def cask_output(cask, args:)
|
||||||
json = formulae_brew_sh_json("#{cask_path}/#{cask}.json")
|
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
|
||||||
|
|
||||||
|
json = Homebrew::API::Cask.fetch cask
|
||||||
return if json.blank? || json["analytics"].blank?
|
return if json.blank? || json["analytics"].blank?
|
||||||
|
|
||||||
get_analytics(json, args: args)
|
get_analytics(json, args: args)
|
||||||
|
rescue ArgumentError
|
||||||
|
# Ignore failed API requests
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(String) }
|
sig { returns(String) }
|
||||||
@ -317,18 +333,6 @@ module Utils
|
|||||||
Homebrew::Settings.read(key) == "true"
|
Homebrew::Settings.read(key) == "true"
|
||||||
end
|
end
|
||||||
|
|
||||||
def formulae_brew_sh_json(endpoint)
|
|
||||||
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
|
|
||||||
|
|
||||||
output, = curl_output("--max-time", "5",
|
|
||||||
"https://formulae.brew.sh/api/#{endpoint}")
|
|
||||||
return if output.blank?
|
|
||||||
|
|
||||||
JSON.parse(output)
|
|
||||||
rescue JSON::ParserError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_count(count)
|
def format_count(count)
|
||||||
count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
||||||
end
|
end
|
||||||
@ -336,23 +340,6 @@ module Utils
|
|||||||
def format_percent(percent)
|
def format_percent(percent)
|
||||||
format("%<percent>.2f", percent: percent)
|
format("%<percent>.2f", percent: percent)
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def formula_path
|
|
||||||
"formula"
|
|
||||||
end
|
|
||||||
alias generic_formula_path formula_path
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def analytics_path
|
|
||||||
"analytics"
|
|
||||||
end
|
|
||||||
alias generic_analytics_path analytics_path
|
|
||||||
|
|
||||||
sig { returns(String) }
|
|
||||||
def cask_path
|
|
||||||
"cask"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user