Fix loading casks/formulae from relative paths.

This commit is contained in:
Markus Reiter 2024-03-01 03:13:56 +01:00
parent 977ac20b2b
commit 3da0f8c4a6
No known key found for this signature in database
GPG Key ID: 245293B51702655B
9 changed files with 108 additions and 24 deletions

View File

@ -207,9 +207,9 @@ module Cask
def self.try_new(ref, warn: false)
ref = ref.to_s
return unless ref.match?(HOMEBREW_TAP_CASK_REGEX)
return unless (token_tap_type = CaskLoader.tap_cask_token_type(ref, warn: warn))
token, tap, = CaskLoader.tap_cask_token_type(ref, warn: warn)
token, tap, = token_tap_type
new("#{tap}/#{token}")
end
@ -529,9 +529,12 @@ module Cask
self.for(ref, warn: warn).load(config: config)
end
sig { params(tapped_token: String, warn: T::Boolean).returns(T.nilable([String, Tap, T.nilable(Symbol)])) }
def self.tap_cask_token_type(tapped_token, warn:)
user, repo, token = tapped_token.split("/", 3).map(&:downcase)
tap = Tap.fetch(user, repo)
return unless (tap_with_token = Tap.with_cask_token(tapped_token))
tap, token = tap_with_token
type = nil
if (new_token = tap.cask_renames[token].presence)
@ -550,7 +553,9 @@ module Cask
opoo "Tap migration for #{tapped_token} points to itself, stopping recursion."
else
old_token = tap.core_cask_tap? ? token : tapped_token
token, tap, = tap_cask_token_type(new_tapped_token, warn: false)
return unless (token_tap_type = tap_cask_token_type(new_tapped_token, warn: false))
token, tap, = token_tap_type
new_token = new_tap.core_cask_tap? ? token : "#{tap}/#{token}"
type = :migration
end

View File

@ -142,8 +142,8 @@ on_request: true)
return unless @cask.conflicts_with
@cask.conflicts_with[:cask].each do |conflicting_cask|
if (match = conflicting_cask.match(HOMEBREW_TAP_CASK_REGEX))
conflicting_cask_tap = Tap.fetch(match[1], match[2])
if (conflicting_cask_tap_with_token = Tap.with_cask_token(conflicting_cask))
conflicting_cask_tap, = conflicting_cask_tap_with_token
next unless conflicting_cask_tap.installed?
end

View File

@ -175,11 +175,13 @@ module Homebrew
end
args.named.each do |name|
next if File.exist?(name)
next unless name =~ HOMEBREW_TAP_FORMULA_REGEX
if (tap_with_name = Tap.with_formula_name(name))
tap, = tap_with_name
elsif (tap_with_token = Tap.with_cask_token(name))
tap, = tap_with_token
end
tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2))
tap.ensure_installed!
tap&.ensure_installed!
end
if args.ignore_dependencies?

View File

@ -19,7 +19,9 @@ class Dependency
@name = name
@tags = tags
@tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2)) if name =~ HOMEBREW_TAP_FORMULA_REGEX
return unless (tap_with_name = Tap.with_formula_name(name))
@tap, = tap_with_name
end
def to_s

View File

@ -34,9 +34,8 @@ module Homebrew
def self.extract
args = extract_args.parse
if (match = args.named.first.match(HOMEBREW_TAP_FORMULA_REGEX))
name = match[3].downcase
source_tap = Tap.fetch(match[1], match[2])
if (tap_with_name = args.named.first&.then { Tap.with_formula_name(_1) })
source_tap, name = tap_with_name
else
name = args.named.first.downcase
source_tap = CoreTap.instance

View File

@ -721,15 +721,15 @@ module Formulary
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
ref = ref.to_s
return unless (name = ref[HOMEBREW_TAP_FORMULA_REGEX, :name])
alias_name = name
return unless (name_tap_type = Formulary.tap_formula_name_type(ref, warn: warn))
name, tap, type = Formulary.tap_formula_name_type(ref, warn: warn)
name, tap, type = name_tap_type
path = Formulary.find_formula_in_tap(name, tap)
options = if type == :alias
{ alias_name: alias_name.downcase }
# TODO: Simplify this by making `tap_formula_name_type` return the alias name.
{ alias_name: T.must(ref[HOMEBREW_TAP_FORMULA_REGEX, :name]).downcase }
else
{}
end
@ -893,7 +893,9 @@ module Formulary
ref = "#{CoreTap.instance}/#{name}"
name, tap, type = Formulary.tap_formula_name_type(ref, warn: warn)
return unless (name_tap_type = Formulary.tap_formula_name_type(ref, warn: warn))
name, tap, type = name_tap_type
options = if type == :alias
{ alias_name: alias_name.downcase }
@ -1127,9 +1129,12 @@ module Formulary
loader_for(ref).path
end
sig { params(tapped_name: String, warn: T::Boolean).returns(T.nilable([String, Tap, T.nilable(Symbol)])) }
def self.tap_formula_name_type(tapped_name, warn:)
user, repo, name = tapped_name.split("/", 3).map(&:downcase)
tap = Tap.fetch(user, repo)
return unless (tap_with_name = Tap.with_formula_name(tapped_name))
tap, name = tap_with_name
type = nil
# FIXME: Remove the need to do this here.
@ -1138,7 +1143,7 @@ module Formulary
if (possible_alias = tap.alias_table[alias_table_key].presence)
# FIXME: Remove the need to split the name and instead make
# the alias table only contain short names.
name = possible_alias.split("/").last
name = T.must(possible_alias.split("/").last)
type = :alias
elsif (new_name = tap.formula_renames[name].presence)
old_name = tap.core_tap? ? name : tapped_name
@ -1156,7 +1161,10 @@ module Formulary
opoo "Tap migration for #{tapped_name} points to itself, stopping recursion."
else
old_name = tap.core_tap? ? name : tapped_name
name, tap, = tap_formula_name_type(new_tapped_name, warn: false)
return unless (name_tap_type = tap_formula_name_type(new_tapped_name, warn: false))
name, tap, = name_tap_type
new_name = new_tap.core_tap? ? name : "#{tap}/#{name}"
type = :migration
end

View File

@ -65,6 +65,38 @@ class Tap
fetch(match[:user], match[:repo])
end
# @private
sig { params(name: String).returns(T.nilable([T.attached_class, String])) }
def self.with_formula_name(name)
return unless (match = name.match(HOMEBREW_TAP_FORMULA_REGEX))
user = T.must(match[:user])
repo = T.must(match[:repo])
name = T.must(match[:name])
# Relative paths are not taps.
return if [user, repo].intersect?([".", ".."])
tap = fetch(user, repo)
[tap, name.downcase]
end
# @private
sig { params(token: String).returns(T.nilable([T.attached_class, String])) }
def self.with_cask_token(token)
return unless (match = token.match(HOMEBREW_TAP_CASK_REGEX))
user = T.must(match[:user])
repo = T.must(match[:repo])
token = T.must(match[:token])
# Relative paths are not taps.
return if [user, repo].intersect?([".", ".."])
tap = fetch(user, repo)
[tap, token.downcase]
end
sig { returns(CoreCaskTap) }
def self.default_cask_tap
odisabled "`Tap.default_cask_tap`", "`CoreCaskTap.instance`"

View File

@ -535,6 +535,22 @@ RSpec.describe Formulary do
end
describe "::loader_for" do
context "when given a relative path with two slashes" do
it "returns a `FromPathLoader`" do
mktmpdir.cd do
FileUtils.mkdir "Formula"
FileUtils.touch "Formula/gcc.rb"
expect(described_class.loader_for("./Formula/gcc.rb")).to be_a Formulary::FromPathLoader
end
end
end
context "when given a tapped name" do
it "returns a `FromTapLoader`" do
expect(described_class.loader_for("homebrew/core/gcc")).to be_a Formulary::FromTapLoader
end
end
context "when not using the API" do
before do
ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1"

View File

@ -599,4 +599,24 @@ RSpec.describe Tap do
expect(described_class.fetch("my", "tap-with-@-symbol").repo_var_suffix).to eq "_MY_HOMEBREW_TAP_WITH___SYMBOL"
end
end
describe "::with_formula_name" do
it "returns the tap and formula name when given a full name" do
expect(described_class.with_formula_name("homebrew/core/gcc")).to eq [CoreTap.instance, "gcc"]
end
it "returns nil when given a relative path" do
expect(described_class.with_formula_name("./Formula/gcc.rb")).to be_nil
end
end
describe "::with_cask_token" do
it "returns the tap and cask token when given a full token" do
expect(described_class.with_cask_token("homebrew/cask/alfred")).to eq [CoreCaskTap.instance, "alfred"]
end
it "returns nil when given a relative path" do
expect(described_class.with_cask_token("./Casks/alfred.rb")).to be_nil
end
end
end