Overhaul Formula/Cask JSON generation

- Use constants for placeholders
- Monkeypatch to set `HOMEBREW_PREFIX` consistently to placeholder
- Use environment variable to set `Dir.home` consistently to placeholder
- Use `appdir` short-circuit to set `Cask#appdir` consistently to placeholder
- Use `Cask.generating_hash!` to enable "generating mode" with these patches
- Fix `Formula#caveats` from JSON

Fixes #14505
Fixes #14595
This commit is contained in:
Mike McQuaid 2023-02-14 14:19:40 +00:00
parent 66c9d5f2af
commit 237eec8ef8
No known key found for this signature in database
GPG Key ID: 3338A31AFDB1D829
9 changed files with 97 additions and 68 deletions

View File

@ -19,10 +19,48 @@ module Cask
extend Searchable
include Metadata
attr_reader :token, :sourcefile_path, :source, :config, :default_config, :loaded_from_api
HOME_PLACEHOLDER = "$HOME"
HOMEBREW_PREFIX_PLACEHOLDER = "$HOMEBREW_PREFIX"
APPDIR_PLACEHOLDER = "$APPDIR"
# TODO: can be removed when API JSON is regenerated with HOMEBREW_PREFIX_PLACEHOLDER.
HOMEBREW_OLD_PREFIX_PLACEHOLDER = "$(brew --prefix)"
attr_reader :token, :sourcefile_path, :source, :config, :default_config, :loaded_from_api, :loader
attr_accessor :download, :allow_reassignment
class << self
def generating_hash!
return if generating_hash?
# Apply monkeypatches for API generation
@old_homebrew_prefix = HOMEBREW_PREFIX
@old_home = Dir.home
Object.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, Pathname(HOMEBREW_PREFIX_PLACEHOLDER))
ENV["HOME"] = HOME_PLACEHOLDER
@generating_hash = true
end
def generated_hash!
return unless generating_hash?
# Revert monkeypatches for API generation
Object.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, @old_homebrew_prefix)
ENV["HOME"] = @old_home
@generating_hash = false
end
def generating_hash?
@generating_hash ||= false
@generating_hash == true
end
end
def self.all
# TODO: ideally avoid using ARGV by moving to e.g. CLI::Parser
if ARGV.exclude?("--eval-all") && !Homebrew::EnvConfig.eval_all?
@ -45,13 +83,14 @@ module Cask
end
def initialize(token, sourcefile_path: nil, source: nil, tap: nil, config: nil,
allow_reassignment: false, loaded_from_api: false, &block)
allow_reassignment: false, loaded_from_api: false, loader: nil, &block)
@token = token
@sourcefile_path = sourcefile_path
@source = source
@tap = tap
@allow_reassignment = allow_reassignment
@loaded_from_api = loaded_from_api
@loader = loader
@block = block
@default_config = config || Config.new
@ -251,7 +290,7 @@ module Cask
"outdated" => outdated?,
"sha256" => sha256,
"artifacts" => artifacts_list,
"caveats" => (to_h_string_gsubs(caveats) unless caveats.empty?),
"caveats" => (caveats unless caveats.empty?),
"depends_on" => depends_on,
"conflicts_with" => conflicts_with,
"container" => container&.pairs,
@ -315,52 +354,10 @@ module Cask
when Artifact::AbstractFlightBlock
# Only indicate whether this block is used as we don't load it from the API
{ artifact.summarize => nil }
when Artifact::Relocated
# Don't replace the Homebrew prefix in the source path since the source could include /usr/local
source, *args = artifact.to_args
{ artifact.class.dsl_key => [to_h_string_gsubs(source, replace_prefix: false), *to_h_gsubs(args)] }
else
{ artifact.class.dsl_key => to_h_gsubs(artifact.to_args) }
{ artifact.class.dsl_key => artifact.to_args }
end
end
end
def to_h_string_gsubs(string, replace_prefix: true)
string = string.to_s.gsub(Dir.home, "$HOME")
if replace_prefix
string.gsub(HOMEBREW_PREFIX, "$(brew --prefix)")
else
string.gsub(Caskroom.path, "$(brew --prefix)/Caskroom")
end
end
def to_h_array_gsubs(array)
array.to_a.map do |value|
to_h_gsubs(value)
end
end
def to_h_hash_gsubs(hash)
hash.to_h.transform_values do |value|
to_h_gsubs(value)
end
rescue TypeError
to_h_array_gsubs(hash)
end
def to_h_gsubs(value)
return value if value.blank?
if value.respond_to? :to_h
to_h_hash_gsubs(value)
elsif value.respond_to? :to_a
to_h_array_gsubs(value)
elsif [true, false].include? value
value
else
to_h_string_gsubs(value)
end
end
end
end

View File

@ -237,13 +237,9 @@ module Cask
return FromContentLoader.new(cask_source).load(config: config)
end
# convert generic string replacements into actual ones
json_cask[:artifacts] = json_cask[:artifacts].map(&method(:from_h_hash_gsubs))
json_cask[:caveats] = from_h_string_gsubs(json_cask[:caveats])
tap = Tap.fetch(json_cask[:tap]) if json_cask[:tap].to_s.include?("/")
Cask.new(token, tap: tap, source: cask_source, config: config, loaded_from_api: true) do
Cask.new(token, tap: tap, source: cask_source, config: config, loaded_from_api: true, loader: self) do
version json_cask[:version]
if json_cask[:sha256] == "no_check"
@ -300,45 +296,53 @@ module Cask
end
json_cask[:artifacts].each do |artifact|
# convert generic string replacements into actual ones
artifact = cask.loader.from_h_hash_gsubs(artifact, appdir)
key = artifact.keys.first
send(key, *artifact[key])
end
caveats json_cask[:caveats] if json_cask[:caveats].present?
if json_cask[:caveats].present?
# convert generic string replacements into actual ones
json_cask[:caveats] = cask.loader.from_h_string_gsubs(json_cask[:caveats], appdir)
caveats json_cask[:caveats]
end
end
end
private
def from_h_string_gsubs(string)
def from_h_string_gsubs(string, appdir)
# TODO: HOMEBREW_OLD_PREFIX_PLACEHOLDER can be removed when API JSON is
# regenerated with HOMEBREW_PREFIX_PLACEHOLDER.
string.to_s
.gsub("$HOME", Dir.home)
.gsub("$(brew --prefix)", HOMEBREW_PREFIX)
.gsub(Cask::HOME_PLACEHOLDER, Dir.home)
.gsub(Cask::HOMEBREW_PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
.gsub(Cask::APPDIR_PLACEHOLDER, appdir)
.gsub(Cask::HOMEBREW_OLD_PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
end
def from_h_array_gsubs(array)
def from_h_array_gsubs(array, appdir)
array.to_a.map do |value|
from_h_gsubs(value)
from_h_gsubs(value, appdir)
end
end
def from_h_hash_gsubs(hash)
def from_h_hash_gsubs(hash, appdir)
hash.to_h.transform_values do |value|
from_h_gsubs(value)
from_h_gsubs(value, appdir)
end
rescue TypeError
from_h_array_gsubs(hash)
from_h_array_gsubs(hash, appdir)
end
def from_h_gsubs(value)
def from_h_gsubs(value, appdir)
return value if value.blank?
if value.respond_to? :to_h
from_h_hash_gsubs(value)
from_h_hash_gsubs(value, appdir)
elsif value.respond_to? :to_a
from_h_array_gsubs(value)
from_h_array_gsubs(value, appdir)
elsif value.is_a? String
from_h_string_gsubs(value)
from_h_string_gsubs(value, appdir)
else
value
end

View File

@ -376,6 +376,8 @@ module Cask
# @api public
def appdir
return Cask::APPDIR_PLACEHOLDER if Cask.generating_hash?
cask.config.appdir
end
end

View File

@ -2119,7 +2119,7 @@ class Formula
"uses_from_macos" => uses_from_macos_elements.uniq,
"requirements" => [],
"conflicts_with" => conflicts.map(&:name),
"caveats" => caveats&.gsub(HOMEBREW_PREFIX, "$(brew --prefix)"),
"caveats" => caveats&.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER),
"installed" => [],
"linked_keg" => linked_version&.to_s,
"pinned" => pinned?,

View File

@ -251,6 +251,7 @@ module Formulary
@caveats_string = json_formula["caveats"]
def caveats
self.class.instance_variable_get(:@caveats_string)
&.gsub(HOMEBREW_PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
end
@tap_git_head_string = json_formula["tap_git_head"]

View File

@ -64,6 +64,7 @@ HOMEBREW_MACOS_ARM_DEFAULT_PREFIX = "/opt/homebrew"
HOMEBREW_MACOS_ARM_DEFAULT_REPOSITORY = HOMEBREW_MACOS_ARM_DEFAULT_PREFIX
HOMEBREW_LINUX_DEFAULT_PREFIX = "/home/linuxbrew/.linuxbrew"
HOMEBREW_LINUX_DEFAULT_REPOSITORY = "#{HOMEBREW_LINUX_DEFAULT_PREFIX}/Homebrew"
HOMEBREW_PREFIX_PLACEHOLDER = "$HOMEBREW_PREFIX"
HOMEBREW_PULL_API_REGEX =
%r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze

View File

@ -300,5 +300,18 @@ describe Cask::Cask, :cask do
expect(h).to be_a(Hash)
expect(JSON.pretty_generate(h["variations"])).to eq expected_sha256_variations.strip
end
it "returns the correct hash placeholders" do
described_class.generating_hash!
expect(described_class).to be_generating_hash
c = Cask::CaskLoader.load("placeholders")
h = c.to_hash_with_variations
described_class.generated_hash!
expect(described_class).not_to be_generating_hash
expect(h).to be_a(Hash)
expect(h["artifacts"].first[:binary].first).to eq "$APPDIR/some/path"
expect(h["caveats"]).to eq "$HOMEBREW_PREFIX and $HOME\n"
end
end
end

View File

@ -117,7 +117,7 @@ describe Cask::Cmd::List, :cask do
{
"zap": [
{
"trash": "$HOME/support/fixtures/cask/caffeine/org.example.caffeine.plist"
"trash": "#{TEST_FIXTURE_DIR}/cask/caffeine/org.example.caffeine.plist"
}
]
}

View File

@ -0,0 +1,11 @@
cask "placeholders" do
version "2.61"
sha256 "e44ffa103fbf83f55c8d0b1bea309a43b2880798dae8620b1ee8da5e1095ec68"
url "file://#{TEST_FIXTURE_DIR}/cask/transmission-2.61.dmg"
homepage "https://brew.sh/placeholders"
binary "#{appdir}/some/path"
caveats "#{HOMEBREW_PREFIX} and #{Dir.home}"
end