Various sharding fixes

- Load paths with no API when needed (e.g. for `brew edit`)
- Use no API mode for `brew log` as it's needed there
- Define sharding format for homebrew-cask and homebrew-core inside
  `Tap` methods
- Create new formulae/casks in location defined by these `Tap` methods
- Fix a bug in Formulary that made sharded formulae lookup less
  efficient (and possibly broke it for core and some API usage)
- Fix various other hardcoded Formula/Cask directory assumptions

Co-authored-by: Bo Anderson <mail@boanderson.me>
This commit is contained in:
Mike McQuaid 2023-08-04 16:21:31 +01:00
parent 4877de52d3
commit b3c33d34ab
No known key found for this signature in database
GPG Key ID: 3338A31AFDB1D829
18 changed files with 117 additions and 67 deletions

View File

@ -381,6 +381,8 @@ module Cask
sig { void }
def audit_token_conflicts
return unless token_conflicts?
Homebrew.with_no_api_env do
return unless core_formula_names.include?(cask.token)
add_error(
@ -388,6 +390,7 @@ module Cask
strict_only: true,
)
end
end
sig { void }
def audit_token_valid
@ -846,7 +849,10 @@ module Cask
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
formula_path = Formulary.core_path(cask.token)
.to_s
.delete_prefix(core_tap.path.to_s)
"#{core_tap.default_remote}/blob/HEAD/Formula/#{formula_path}"
end
end
end

View File

@ -439,7 +439,7 @@ module Cask
end
def self.default_path(token)
CoreCaskTap.instance.cask_dir/"#{token.to_s.downcase}.rb"
find_cask_in_tap(token.to_s.downcase, CoreCaskTap.instance)
end
def self.tap_paths(token, warn: true)
@ -455,7 +455,8 @@ module Cask
def self.find_cask_in_tap(token, tap)
filename = "#{token}.rb"
Tap.cask_files_by_name(tap).fetch(filename, tap.cask_dir/filename)
Tap.cask_files_by_name(tap)
.fetch(token, tap.cask_dir/filename)
end
end
end

View File

@ -227,7 +227,8 @@ module Homebrew
sig { params(only: T.nilable(Symbol), recurse_tap: T::Boolean).returns(T::Array[Pathname]) }
def to_paths(only: parent&.only_formula_or_cask, recurse_tap: false)
@to_paths ||= {}
@to_paths[only] ||= downcased_unique_named.flat_map do |name|
@to_paths[only] ||= Homebrew.with_no_api_env_if_needed(@without_api) do
downcased_unique_named.flat_map do |name|
path = Pathname(name)
if File.exist?(name)
path
@ -262,6 +263,7 @@ module Homebrew
end
end.uniq.freeze
end
end
sig { returns(T::Array[Keg]) }
def to_default_kegs

View File

@ -29,7 +29,7 @@ module Homebrew
conflicts "-1", "--max-count"
conflicts "--formula", "--cask"
named_args [:formula, :cask], max: 1
named_args [:formula, :cask], max: 1, without_api: true
end
end

View File

@ -96,7 +96,7 @@ module Homebrew
cask_tap = Tap.fetch(args.tap || "homebrew/cask")
raise TapUnavailableError, cask_tap.name unless cask_tap.installed?
cask_path = Cask::CaskLoader.path("#{cask_tap}/#{token}")
cask_path = cask_tap.new_cask_path(token)
cask_path.dirname.mkpath unless cask_path.dirname.exist?
raise Cask::CaskAlreadyCreatedError, token if cask_path.exist?

View File

@ -114,7 +114,7 @@ module Homebrew
repo = source_tap.path
pattern = if source_tap.core_tap?
[repo/"Formula/#{name}.rb"]
[source_tap.new_formula_path(name), repo/"Formula/#{name}.rb"].uniq
else
# A formula can technically live in the root directory of a tap or in any of its subdirectories
[repo/"#{name}.rb", repo/"**/#{name}.rb"]

View File

@ -110,7 +110,7 @@ module Homebrew
end
def self.get_package(tap, subject_name, subject_path, content)
if subject_path.dirname == tap.cask_dir
if subject_path.to_s.start_with?("#{tap.cask_dir}/")
cask = begin
Cask::CaskLoader.load(content.dup)
rescue Cask::CaskUnavailableError
@ -130,7 +130,7 @@ module Homebrew
subject_path = Pathname(subject_path)
tap = Tap.from_path(subject_path)
subject_name = subject_path.basename.to_s.chomp(".rb")
is_cask = subject_path.dirname == tap.cask_dir
is_cask = subject_path.to_s.start_with?("#{tap.cask_dir}/")
name = is_cask ? "cask" : "formula"
new_package = get_package(tap, subject_name, subject_path, new_contents)
@ -241,8 +241,8 @@ module Homebrew
files.each do |file|
files_to_commits[file] ||= []
files_to_commits[file] << commit
tap_file = tap.path/file
if (tap_file.dirname == tap.formula_dir || tap_file.dirname == tap.cask_dir) &&
tap_file = (tap.path/file).to_s
if (tap_file.start_with?("#{tap.formula_dir}/") || tap_file.start_with?("#{tap.cask_dir}/")) &&
File.extname(file) == ".rb"
next
end

View File

@ -45,7 +45,7 @@ module Homebrew
def update_path
return if @name.nil? || @tap.nil?
@path = Formulary.path "#{@tap}/#{@name}"
@path = @tap.new_formula_path(@name)
end
def fetch?

View File

@ -49,7 +49,7 @@ homebrew-formula-path() {
local formula_path
formula_path="$(
shopt -s nullglob
echo "${HOMEBREW_REPOSITORY}/Library/Taps"/*/*/{Formula/,HomebrewFormula/,}"${formula}.rb"
echo "${HOMEBREW_REPOSITORY}/Library/Taps"/*/*/{Formula/,HomebrewFormula/,Formula/*/,}"${formula}.rb"
)"
[[ -n "${formula_path}" ]] && formula_exists="1"
fi

View File

@ -1003,10 +1003,15 @@ module Formulary
end.select(&:file?)
end
sig { params(name: String, tap: Tap).returns(Pathname) }
def self.find_formula_in_tap(name, tap)
filename = name.dup
filename << ".rb" unless filename.end_with?(".rb")
filename = if name.end_with?(".rb")
name
else
"#{name}.rb"
end
Tap.formula_files_by_name(tap).fetch(filename, tap.formula_dir/filename)
Tap.formula_files_by_name(tap)
.fetch(name, tap.formula_dir/filename)
end
end

View File

@ -31,7 +31,7 @@ module Readall
failed = true
end
if (formula_dir/"#{f.basename}.rb").exist?
if formula_dir.glob("**/#{f.basename}.rb").any?(&:exist?)
onoe "Formula duplicating alias: #{f}"
failed = true
end

View File

@ -500,12 +500,22 @@ class Tap
@potential_formula_dirs ||= [path/"Formula", path/"HomebrewFormula", path].freeze
end
sig { params(name: String).returns(Pathname) }
def new_formula_path(name)
formula_dir/"#{name.downcase}.rb"
end
# Path to the directory of all {Cask} files for this {Tap}.
sig { returns(Pathname) }
def cask_dir
@cask_dir ||= path/"Casks"
end
sig { params(token: String).returns(Pathname) }
def new_cask_path(token)
cask_dir/"#{token.downcase}.rb"
end
def contents
contents = []
@ -555,7 +565,7 @@ class Tap
formula_files.each_with_object({}) do |file, hash|
# If there's more than one file with the same basename: intentionally
# ignore the later ones here.
hash[file.basename.to_s] ||= file
hash[file.basename(".rb").to_s] ||= file
end
end
@ -584,7 +594,7 @@ class Tap
cask_files.each_with_object({}) do |file, hash|
# If there's more than one file with the same basename: intentionally
# ignore the later ones here.
hash[file.basename.to_s] ||= file
hash[file.basename(".rb").to_s] ||= file
end
end
@ -984,6 +994,19 @@ class CoreTap < AbstractCoreTap
end
end
sig { params(name: String).returns(Pathname) }
def new_formula_path(name)
formula_subdir = if name.start_with?("lib")
"lib"
else
name[0].to_s
end
return super unless (formula_dir/formula_subdir).directory?
formula_dir/formula_subdir/"#{name.downcase}.rb"
end
# @private
sig { returns(Pathname) }
def alias_dir
@ -1108,6 +1131,15 @@ class CoreCaskTap < AbstractCoreTap
true
end
sig { params(token: String).returns(Pathname) }
def new_cask_path(token)
cask_subdir = token[0].to_s
return super unless (cask_dir/cask_subdir).directory?
cask_dir/cask_subdir/"#{token.downcase}.rb"
end
sig { override.returns(T::Array[Pathname]) }
def cask_files
return super if Homebrew::EnvConfig.no_install_from_api? || installed?

View File

@ -333,7 +333,7 @@ describe Homebrew::Cleanup do
FileUtils.touch testball
FileUtils.touch testball_resource
(HOMEBREW_CELLAR/"testball"/"0.0.1").mkpath
FileUtils.touch(CoreTap.instance.formula_dir/"testball.rb")
FileUtils.touch(CoreTap.instance.new_formula_path("testball"))
end
it "cleans up file if outdated" do

View File

@ -4,7 +4,7 @@ require "cmd/shared_examples/args_parse"
describe "brew create" do
let(:url) { "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" }
let(:formula_file) { CoreTap.new.formula_dir/"testball.rb" }
let(:formula_file) { CoreTap.new.new_formula_path("testball") }
it_behaves_like "parseable arguments"

View File

@ -82,7 +82,7 @@ describe FormulaInstaller do
ENV["HOMEBREW_DEVELOPER"] = "1"
dep_name = "homebrew-test-cyclic"
dep_path = CoreTap.new.formula_dir/"#{dep_name}.rb"
dep_path = CoreTap.new.new_formula_path(dep_name)
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"
@ -105,7 +105,7 @@ describe FormulaInstaller do
formula1_name = "homebrew-test-formula1"
formula2_name = "homebrew-test-formula2"
formula1_path = CoreTap.new.formula_dir/"#{formula1_name}.rb"
formula1_path = CoreTap.new.new_formula_path(formula1_name)
formula1_path.write <<~RUBY
class #{Formulary.class_s(formula1_name)} < Formula
url "foo"
@ -116,7 +116,7 @@ describe FormulaInstaller do
Formulary.cache.delete(formula1_path)
formula1 = Formulary.factory(formula1_name)
formula2_path = CoreTap.new.formula_dir/"#{formula2_name}.rb"
formula2_path = CoreTap.new.new_formula_path(formula2_name)
formula2_path.write <<~RUBY
class #{Formulary.class_s(formula2_name)} < Formula
url "foo"
@ -135,7 +135,7 @@ describe FormulaInstaller do
it "raises on pinned dependency" do
dep_name = "homebrew-test-dependency"
dep_path = CoreTap.new.formula_dir/"#{dep_name}.rb"
dep_path = CoreTap.new.new_formula_path(dep_name)
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"

View File

@ -429,7 +429,7 @@ describe Formula do
example "alias paths with tab with non alias source path" do
alias_path = (CoreTap.instance.alias_dir/"another_name")
source_path = (CoreTap.instance.formula_dir/"another_other_name")
source_path = CoreTap.instance.new_formula_path("another_other_name")
f = formula alias_path: alias_path do
url "foo-1.0"
@ -940,7 +940,7 @@ describe Formula do
end
describe "#to_hash_with_variations", :needs_macos do
let(:formula_path) { CoreTap.new.formula_dir/"foo-variations.rb" }
let(:formula_path) { CoreTap.new.new_formula_path("foo-variations") }
let(:formula_content) do
<<~RUBY
class FooVariations < Formula

View File

@ -6,7 +6,7 @@ require "utils/bottles"
describe Formulary do
let(:formula_name) { "testball_bottle" }
let(:formula_path) { CoreTap.new.formula_dir/"#{formula_name}.rb" }
let(:formula_path) { CoreTap.new.new_formula_path(formula_name) }
let(:formula_content) do
<<~RUBY
class #{described_class.class_s(formula_name)} < Formula
@ -84,7 +84,11 @@ describe Formulary do
before { CoreTap.instance.clear_cache }
let(:formula_name) { "testball_sharded" }
let(:formula_path) { CoreTap.new.formula_dir/formula_name[0]/"#{formula_name}.rb" }
let(:formula_path) do
core_tap = CoreTap.new
(core_tap.formula_dir/formula_name[0]).mkpath
core_tap.new_formula_path(formula_name)
end
it "returns a Formula" do
expect(described_class.factory(formula_name)).to be_a(Formula)

View File

@ -4,9 +4,9 @@ shared_examples "formulae exist" do |array|
array.each do |f|
it "#{f} formula exists" do
core_tap = Pathname("#{HOMEBREW_LIBRARY_PATH}/../Taps/homebrew/homebrew-core")
formula_path = core_tap/"Formula/#{f}.rb"
formula_paths = core_tap.glob("Formula/**/#{f}.rb")
alias_path = core_tap/"Aliases/#{f}"
expect(formula_path.exist? || alias_path.exist?).to be true
expect(formula_paths.any?(&:exist?) || alias_path.exist?).to be true
end
end
end