Support offline usage under HOMEBREW_INSTALL_FROM_API

This commit is contained in:
Bo Anderson 2021-12-07 00:13:56 +00:00 committed by Rylan Polster
parent b49949dc01
commit 1d36c42fb7
No known key found for this signature in database
GPG Key ID: 46A744940CFF4D64
9 changed files with 141 additions and 34 deletions

View File

@ -21,6 +21,7 @@ module Homebrew
module_function
API_DOMAIN = "https://formulae.brew.sh/api"
HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze
sig { params(endpoint: String, json: T::Boolean).returns(T.any(String, Hash)) }
def fetch(endpoint, json: true)

View File

@ -20,6 +20,17 @@ module Homebrew
def fetch(name)
Homebrew::API.fetch "#{formula_api_path}/#{name}.json"
end
sig { returns(Array) }
def all_formulae
@all_formulae ||= begin
json_formulae = JSON.parse((HOMEBREW_CACHE_API/"#{formula_api_path}.json").read)
json_formulae.to_h do |json_formula|
[json_formula["name"], json_formula.except("name")]
end
end
end
end
end
end

View File

@ -764,7 +764,7 @@ then
export HOMEBREW_DEVELOPER_MODE="1"
fi
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" && -n "${HOMEBREW_DEVELOPER_COMMAND}" ]]
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" && -n "${HOMEBREW_DEVELOPER_COMMAND}" && "${HOMEBREW_COMMAND}" != "irb" ]]
then
odie "Developer commands cannot be run while HOMEBREW_INSTALL_FROM_API is set!"
elif [[ -n "${HOMEBREW_INSTALL_FROM_API}" && -n "${HOMEBREW_DEVELOPER_MODE}" ]]

View File

@ -94,11 +94,6 @@ module Homebrew
unreadable_error = nil
if only != :cask
if prefer_loading_from_api && Homebrew::EnvConfig.install_from_api? &&
Homebrew::API::Bottle.available?(name)
Homebrew::API::Bottle.fetch_bottles(name)
end
begin
formula = case method
when nil, :factory

View File

@ -252,16 +252,7 @@ module Homebrew
def info_formula(f, args:)
specs = []
if Homebrew::EnvConfig.install_from_api? && Homebrew::API::Bottle.available?(f.name)
info = Homebrew::API::Bottle.fetch(f.name)
latest_version = info["pkg_version"].split("_").first
bottle_exists = info["bottles"].key?(Utils::Bottles.tag.to_s) || info["bottles"].key?("all")
s = "stable #{latest_version}"
s += " (bottled)" if bottle_exists
specs << s
elsif (stable = f.stable)
if (stable = f.stable)
s = "stable #{stable.version}"
s += " (bottled)" if stable.bottled? && f.pour_bottle?
specs << s

View File

@ -745,6 +745,20 @@ EOS
fi
done
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" ]]
then
mkdir -p "${HOMEBREW_CACHE}/api"
# TODO: etags?
curl \
"${CURL_DISABLE_CURLRC_ARGS[@]}" \
--fail --compressed --silent --max-time 5 \
--location --output "${HOMEBREW_CACHE}/api/formula.json" \
--user-agent "${HOMEBREW_USER_AGENT_CURL}" \
"https://formulae.brew.sh/api/formula.json"
# TODO: we probably want to print an error if this fails.
# TODO: set HOMEBREW_UPDATED or HOMEBREW_UPDATE_FAILED
fi
safe_cd "${HOMEBREW_REPOSITORY}"
# HOMEBREW_UPDATE_AUTO wasn't modified in subshell.

View File

@ -161,19 +161,6 @@ module Homebrew
puts pinned.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", "
end
if Homebrew::EnvConfig.install_from_api?
formulae_to_install.map! do |formula|
next formula if formula.head?
next formula if formula.tap.present? && !formula.core_formula?
next formula unless Homebrew::API::Bottle.available?(formula.name)
Homebrew::API::Bottle.fetch_bottles(formula.name)
Formulary.factory(formula.name)
rescue FormulaUnavailableError
formula
end
end
if formulae_to_install.empty?
oh1 "No packages to upgrade"
else

View File

@ -6,6 +6,8 @@ require "extend/cachable"
require "tab"
require "utils/bottles"
require "active_support/core_ext/hash/deep_transform_values"
# The {Formulary} is responsible for creating instances of {Formula}.
# It is not meant to be used directly from formulae.
#
@ -44,6 +46,8 @@ module Formulary
remove_const(namespace.demodulize)
end
remove_const("FormulaNamespaceAPI")
super
end
@ -392,6 +396,102 @@ module Formulary
end
end
# Load formulae from the API.
class FormulaAPILoader < FormulaLoader
def initialize(name)
super name, Formulary.core_path(name)
end
def klass(flags:, ignore_errors:)
namespace = "FormulaNamespaceAPI"
mod = if Formulary.const_defined?(namespace)
Formulary.const_get(namespace)
else
Formulary.const_set(namespace, Module.new)
end
mod.send(:remove_const, :BUILD_FLAGS) if mod.const_defined?(:BUILD_FLAGS)
mod.const_set(:BUILD_FLAGS, flags)
class_s = Formulary.class_s(name)
if mod.const_defined?(class_s)
mod.const_get(class_s)
else
json_formula = Homebrew::API::Formula.all_formulae[name]
klass = Class.new(::Formula) do
desc json_formula["desc"]
homepage json_formula["homepage"]
license json_formula["license"]
revision json_formula["revision"]
version_scheme json_formula["version_scheme"]
if (urls_stable = json_formula["urls"]["stable"]).present?
stable do
url urls_stable["url"]
version json_formula["versions"]["stable"]
end
end
if (bottles_stable = json_formula["bottle"]["stable"]).present?
bottle do
root_url bottles_stable["root_url"]
bottles_stable["files"].each do |tag, bottle_spec|
cellar = bottle_spec["cellar"]
cellar = cellar[1..].to_sym if cellar.start_with?(":")
sha256 cellar: cellar, tag.to_sym => bottle_spec["sha256"]
end
end
end
if (keg_only_reason = json_formula["keg_only_reason"]).present?
reason = keg_only_reason["reason"]
reason = reason[1..].to_sym if reason.start_with?(":")
keg_only reason, keg_only_reason["explanation"]
end
if (deprecation_date = json_formula["deprecation_date"]).present?
deprecate! date: deprecation_date, because: json_formula["deprecation_reason"]
end
if (disable_date = json_formula["disable_date"]).present?
disable! date: disable_date, because: json_formula["disable_reason"]
end
json_formula["build_dependencies"].each do |dep|
depends_on dep => :build
end
json_formula["dependencies"].each do |dep|
depends_on dep
end
json_formula["recommended_dependencies"].each do |dep|
depends_on dep => :recommended
end
json_formula["optional_dependencies"].each do |dep|
depends_on dep => :optional
end
json_formula["uses_from_macos"].each do |dep|
dep = dep.deep_transform_values(&:to_sym) if dep.is_a?(Hash)
uses_from_macos dep
end
def install
raise "Cannot build from source from abstract formula."
end
@caveats_string = json_formula["caveats"]
def caveats
@caveats_string
end
end
mod.const_set(class_s, klass)
end
end
end
# Return a {Formula} instance for the given reference.
# `ref` is a string containing:
#
@ -539,11 +639,9 @@ module Formulary
when URL_START_REGEX
return FromUrlLoader.new(ref)
when HOMEBREW_TAP_FORMULA_REGEX
# If `homebrew/core` is specified and not installed, check whether the formula is already installed.
if ref.start_with?("homebrew/core/") && !CoreTap.instance.installed? && Homebrew::EnvConfig.install_from_api?
name = ref.split("/", 3).last
possible_keg_formula = Pathname.new("#{HOMEBREW_PREFIX}/opt/#{name}/.brew/#{name}.rb")
return FormulaLoader.new(name, possible_keg_formula) if possible_keg_formula.file?
return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
end
return TapLoader.new(ref, from: from)
@ -557,6 +655,12 @@ module Formulary
possible_alias = CoreTap.instance.alias_dir/ref
return AliasLoader.new(possible_alias) if possible_alias.file?
if !CoreTap.instance.installed? &&
Homebrew::EnvConfig.install_from_api? &&
Homebrew::API::Formula.all_formulae.key?(ref)
return FormulaAPILoader.new(ref)
end
possible_tap_formulae = tap_paths(ref)
raise TapFormulaAmbiguityError.new(ref, possible_tap_formulae) if possible_tap_formulae.size > 1

View File

@ -142,8 +142,6 @@ class Tap
# The remote repository name of this {Tap}.
# e.g. `user/homebrew-repo`
def remote_repo
raise TapUnavailableError, name unless installed?
return unless remote
@remote_repo ||= remote.delete_prefix("https://github.com/")
@ -795,6 +793,12 @@ class CoreTap < Tap
safe_system HOMEBREW_BREW_FILE, "tap", instance.name
end
def remote
super if installed? || !Homebrew::EnvConfig.install_from_api?
Homebrew::EnvConfig.core_git_remote
end
# CoreTap never allows shallow clones (on request from GitHub).
def install(quiet: false, clone_target: nil, force_auto_update: nil, custom_remote: false)
remote = Homebrew::EnvConfig.core_git_remote # set by HOMEBREW_CORE_GIT_REMOTE