bundle/commands/cleanup: correctly handle .keepme references.

Extract the relevant logic from `formula.rb`, moving to `keg.rb` and
then use this logic in `bundle/commands/cleanup.rb` to ensure that we
don't say we need to uninstall formulae that should be still kept.
This commit is contained in:
Mike McQuaid 2025-04-01 15:12:12 +01:00
parent f9baac24a2
commit c72386e3c3
No known key found for this signature in database
4 changed files with 43 additions and 3 deletions

View File

@ -106,6 +106,13 @@ module Homebrew
current_formulae.reject! do |f|
Homebrew::Bundle::BrewInstaller.formula_in_array?(f[:full_name], kept_formulae)
end
# Don't try to uninstall formulae with keepme references
current_formulae.reject! do |f|
Formula[f[:full_name]].installed_kegs.any? do |keg|
keg.keepme_refs.present?
end
end
current_formulae.map { |f| f[:full_name] }
end

View File

@ -3146,8 +3146,7 @@ class Formula
opoo "Skipping (old) #{keg} due to it being linked" unless quiet
elsif pinned? && keg == Keg.new(@pin.path.resolved_path)
opoo "Skipping (old) #{keg} due to it being pinned" unless quiet
elsif (keepme = keg/".keepme") && keepme.exist? && keepme.readable? &&
(keepme_refs = keepme.readlines.map(&:strip).select { |ref| Pathname(ref).exist? }.presence)
elsif (keepme_refs = keg.keepme_refs.presence)
opoo "Skipping #{keg} as it is needed by #{keepme_refs.join(", ")}" unless quiet
else
eligible_for_cleanup << keg

View File

@ -95,6 +95,8 @@ class Keg
PYC_EXTENSIONS = %w[.pyc .pyo].freeze
LIBTOOL_EXTENSIONS = %w[.la .lai].freeze
KEEPME_FILE = ".keepme"
# @param path if this is a file in a keg, returns the containing {Keg} object.
def self.for(path)
original_path = path
@ -592,6 +594,14 @@ class Keg
end
end
sig { returns(T::Array[String]) }
def keepme_refs
keepme = path/KEEPME_FILE
return [] if !keepme.exist? || !keepme.readable?
keepme.readlines.select { |ref| File.exist?(ref.strip) }
end
def binary_executable_or_library_files
[]
end

View File

@ -44,7 +44,7 @@ RSpec.describe Homebrew::Bundle::Commands::Cleanup do
it "computes which formulae to uninstall" do
dependencies_arrays_hash = { dependencies: [], build_dependencies: [] }
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return [
formulae_hash = [
{ name: "a2", full_name: "a2", aliases: ["a"], dependencies: ["d"] },
{ name: "c", full_name: "c" },
{ name: "d", full_name: "homebrew/tap/d", aliases: ["d2"] },
@ -70,6 +70,17 @@ RSpec.describe Homebrew::Bundle::Commands::Cleanup do
{ name: "builddependency2", full_name: "builddependency2" },
{ name: "caskdependency", full_name: "homebrew/tap/caskdependency" },
].map { |formula| dependencies_arrays_hash.merge(formula) }
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return(formulae_hash)
formulae_hash.each do |hash_formula|
name = hash_formula[:name]
full_name = hash_formula[:full_name]
tap_name = full_name.rpartition("/").first.presence || "homebrew/core"
tap = Tap.fetch(tap_name)
f = formula(name, tap:) { url "#{name}-1.0" }
stub_formula_loader f, full_name
end
allow(Homebrew::Bundle::CaskDumper).to receive(:formula_dependencies).and_return(%w[caskdependency])
expect(described_class.formulae_to_uninstall).to eql %w[
c
@ -93,6 +104,19 @@ RSpec.describe Homebrew::Bundle::Commands::Cleanup do
expect(described_class.taps_to_untap).to eql(%w[z homebrew/tap])
end
it "ignores formulae with .keepme references when computing which formulae to uninstall" do
name = full_name ="c"
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return([{ name:, full_name: }])
f = formula(name) { url "#{name}-1.0" }
stub_formula_loader f, name
keg = instance_double(Keg)
allow(keg).to receive(:keepme_refs).and_return(["/some/file"])
allow(f).to receive(:installed_kegs).and_return([keg])
expect(described_class.formulae_to_uninstall).to be_empty
end
it "computes which VSCode extensions to uninstall" do
allow(Homebrew::Bundle::VscodeExtensionDumper).to receive(:extensions).and_return(%w[z])
expect(described_class.vscode_extensions_to_uninstall).to eql(%w[z])