Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Library/Homebrew/caveats.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def completions_and_elisp
sig { params(skip_reason: T::Boolean).returns(T.nilable(String)) }
def keg_only_text(skip_reason: false)
return unless formula.keg_only?
return if formula.linked?

s = if skip_reason
""
Expand Down
29 changes: 17 additions & 12 deletions Library/Homebrew/cmd/link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,20 @@ def run

kegs.freeze.each do |keg|
keg_only = Formulary.keg_only?(keg.rack)
formula = begin
keg.to_formula
rescue FormulaUnavailableError
# Not all kegs may belong to current formulae
nil
end
versioned_keg_only_formula = formula.present? && formula.keg_only_reason&.versioned_formula?

if keg.linked?
opoo "Already linked: #{keg}"
name_and_flag = "#{"--HEAD " if args.HEAD?}#{"--force " if keg_only}#{keg.name}"
name_and_flag = +""
name_and_flag << "--HEAD " if args.HEAD?
name_and_flag << "--force " if keg_only && !versioned_keg_only_formula
name_and_flag << keg.name
puts <<~EOS
To relink, run:
brew unlink #{keg.name} && brew link #{name_and_flag}
Expand All @@ -72,17 +82,10 @@ def run
puts "Would link:"
end
keg.link(**options)
puts_keg_only_path_message(keg) if keg_only
puts_keg_only_path_message(keg) if keg_only && !versioned_keg_only_formula
next
end

formula = begin
keg.to_formula
rescue FormulaUnavailableError
# Not all kegs may belong to formulae
nil
end

if keg_only
if HOMEBREW_PREFIX.to_s == HOMEBREW_DEFAULT_PREFIX && formula.present? &&
formula.keg_only_reason.by_macos?
Expand All @@ -94,14 +97,14 @@ def run
next
end

if !args.force? && (formula.blank? || !formula.keg_only_reason.versioned_formula?)
if !args.force? && (formula.nil? || !formula.keg_only_reason.versioned_formula?)
opoo "#{keg.name} is keg-only and must be linked with `--force`."
puts_keg_only_path_message(keg)
next
end
end

Unlink.unlink_versioned_formulae(formula, verbose: args.verbose?) if formula
Unlink.unlink_link_overwrite_formulae(formula, verbose: args.verbose?) if formula

keg.lock do
print "Linking #{keg}... "
Expand All @@ -116,7 +119,9 @@ def run
puts "#{n} symlinks created."
end

puts_keg_only_path_message(keg) if keg_only && !Homebrew::EnvConfig.developer?
if keg_only && !versioned_keg_only_formula && !Homebrew::EnvConfig.developer?
puts_keg_only_path_message(keg)
end
end
end
end
Expand Down
103 changes: 99 additions & 4 deletions Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -626,11 +626,18 @@ def versioned_formula? = name.include?("@")
# Returns any other `@`-versioned formulae names for any Formula (including versioned formulae).
sig { returns(T::Array[String]) }
def versioned_formulae_names
versioned_names = if tap
name_prefix = name.gsub(/(@[\d.]+)?$/, "")
T.must(tap).prefix_to_versioned_formulae_names.fetch(name_prefix, [])
name_prefix = unversioned_formula_name || name

versioned_names = if (formula_tap = tap)
formula_tap.prefix_to_versioned_formulae_names.fetch(name_prefix, [])
elsif path.exist?
Pathname.glob(path.to_s.gsub(/(@[\d.]+)?\.rb$/, "@*.rb"))
versioned_formula_glob = if name_prefix.end_with?("-full")
"#{name_prefix.delete_suffix("-full")}@*-full.rb"
else
"#{name_prefix}@*.rb"
end

Pathname.glob((path.dirname/versioned_formula_glob).to_s)
.map { |path| path.basename(".rb").to_s }
.sort
Comment thread
MikeMcQuaid marked this conversation as resolved.
else
Expand All @@ -652,6 +659,94 @@ def versioned_formulae
end.sort_by(&:version).reverse
end

sig { returns(T.nilable(String)) }
def unversioned_formula_name
return unless versioned_formula?

name.sub(/@[\d.]+(?=-full$|$)/, "")
end

# Returns the sibling `-full` or non-`-full` formula names for any Formula.
sig { returns(T::Array[String]) }
def full_formulae_names
[
if name.end_with?("-full")
name.delete_suffix("-full")
else
"#{name}-full"
end,
]
end

# Returns sibling `-full` or non-`-full` Formula objects for any Formula.
sig { returns(T::Array[Formula]) }
def full_formulae
full_formulae_names.filter_map do |formula_name|
Formula[formula_name]
rescue FormulaUnavailableError
nil
end.sort_by(&:version).reverse
end

sig { returns(T.nilable(String)) }
def link_overwrite_reason
installed_overwrite_formulae = link_overwrite_formulae.select(&:any_version_installed?)
return if installed_overwrite_formulae.empty?

reason_formulae = installed_overwrite_formulae.select(&:linked?)
status = if reason_formulae.empty?
reason_formulae = installed_overwrite_formulae
"installed"
else
"linked"
end

"#{reason_formulae.map(&:full_name).to_sentence} #{reason_formulae.one? ? "is" : "are"} already #{status}"
end

sig { returns(T::Array[String]) }
def link_overwrite_related_formula_names
[*versioned_formulae_names, *full_formulae_names, unversioned_formula_name].compact
end

# Returns sibling Formula names whose prefix links should be replaced when this Formula is linked.
sig { returns(T::Array[String]) }
def link_overwrite_formulae_names
formula_names = T.let(Set.new, T::Set[String])
pending_formula_names = T.let([name], T::Array[String])

pending_formula_names.each do |current_name|
current_formula = begin
if current_name == name
self
else
Formula[current_name]
end
rescue FormulaUnavailableError
next
end

current_formula.link_overwrite_related_formula_names.each do |related_formula_name|
next if related_formula_name == name
next unless formula_names.add?(related_formula_name)

pending_formula_names << related_formula_name
end
end

formula_names.to_a.sort
end

# Returns sibling Formulae whose prefix links should be replaced when this Formula is linked.
sig { returns(T::Array[Formula]) }
def link_overwrite_formulae
link_overwrite_formulae_names.filter_map do |formula_name|
Formula[formula_name]
rescue FormulaUnavailableError
nil
end
end

# Whether this {Formula} is version-synced with other formulae.
sig { returns(T::Boolean) }
def synced_with_other_formulae?
Expand Down
40 changes: 36 additions & 4 deletions Library/Homebrew/formula_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ def initialize(
@overwrite = overwrite
@keep_tmp = keep_tmp
@debug_symbols = debug_symbols
@link_keg = T.let(!formula.keg_only? || link_keg, T::Boolean)
@installed_as_dependency = installed_as_dependency
@installed_on_request = installed_on_request
link_keg ||= !formula.keg_only? || auto_link_versioned_keg_only?
@link_keg = T.let(link_keg, T::Boolean)
Comment thread
MikeMcQuaid marked this conversation as resolved.
@show_header = show_header
@ignore_deps = ignore_deps
@only_deps = only_deps
Expand All @@ -131,8 +134,6 @@ def initialize(
@verbose = verbose
@quiet = quiet
@debug = debug
@installed_as_dependency = installed_as_dependency
@installed_on_request = installed_on_request
@options = options
@requirement_messages = T.let([], T::Array[String])
@poured_bottle = T.let(false, T::Boolean)
Expand Down Expand Up @@ -937,6 +938,24 @@ def caveats
Homebrew.messages.record_caveats(formula.name, caveats)
end

sig { returns(T.nilable(String)) }
def link_manual_command_warning
return if installed_as_dependency?
return unless formula.keg_only?
return unless formula.keg_only_reason.versioned_formula?
return if link_keg
return if formula.linked?

reason = formula.link_overwrite_reason
return if reason.blank?

<<~EOS
#{formula.full_name} was installed but not linked because #{reason}.
To link this version, run:
brew link #{formula.full_name}
EOS
end

sig { void }
def finish
return if only_deps?
Expand All @@ -952,6 +971,8 @@ def finish
end
else
link(keg)
warning = link_manual_command_warning
opoo warning if !quiet? && warning.present?
end

install_service
Expand Down Expand Up @@ -1162,7 +1183,7 @@ def link(keg)
keg.remove_linked_keg_record
end

Homebrew::Unlink.unlink_versioned_formulae(formula, verbose: verbose?)
Homebrew::Unlink.unlink_link_overwrite_formulae(formula, verbose: verbose?)

link_overwrite_backup = {} # Hash: conflict file -> backup file
backup_dir = HOMEBREW_CACHE/"Backup"
Expand Down Expand Up @@ -1701,6 +1722,17 @@ def forbidden_formula_check

private

sig { returns(T::Boolean) }
def auto_link_versioned_keg_only?
return false if installed_as_dependency?
return false unless formula.keg_only?
return false unless formula.keg_only_reason.versioned_formula?
return false if formula.any_version_installed?
return false if formula.link_overwrite_formulae.any?(&:any_version_installed?)

true
end

sig { void }
def lock
return unless self.class.locked.empty?
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def install_formula?(
msg = <<~EOS
#{msg}, it's just not linked.
To link this version, run:
brew link #{formula}
brew link #{formula.full_name}
EOS
else
msg = if quiet
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/tap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,7 @@ def formula_names
def prefix_to_versioned_formulae_names
@prefix_to_versioned_formulae_names ||= T.let(formula_names
.select { |name| name.include?("@") }
.group_by { |name| name.gsub(/(@[\d.]+)?$/, "") }
.group_by { |name| name.sub(/@[\d.]+(?=-full$|$)/, "") }
.transform_values(&:sort)
.freeze, T.nilable(T::Hash[String, T::Array[String]]))
end
Expand Down
6 changes: 6 additions & 0 deletions Library/Homebrew/test/caveats_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ def caveats
expect(caveats).to include("keg-only")
end

it "omits keg-only caveats when the formula is linked" do
allow(f).to receive(:linked?).and_return(true)

expect(caveats).to be_empty
end

it "gives command to be run when f.bin is a directory" do
Pathname.new(f.bin).mkpath
expect(caveats).to include(f.opt_bin.to_s)
Expand Down
32 changes: 32 additions & 0 deletions Library/Homebrew/test/cmd/link_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,36 @@
.and be_a_success
expect(HOMEBREW_PREFIX/"bin/testfile").to be_a_file
end

{
"@-versioned" => "testball-link-output@1.0",
"-full" => "testball-link-output-full",
}.each do |formula_type, formula_name|
it "does not print keg-only output when linking a #{formula_type} formula", :integration_test do
formula_content = <<~RUBY
keg_only :versioned_formula

def caveats
"unexpected caveat output"
end

def post_install
puts "unexpected post_install output"
end
RUBY

setup_test_formula formula_name, formula_content, tab_attributes: { installed_on_request: true }
Formula[formula_name].bin.mkpath
FileUtils.touch Formula[formula_name].bin/"link-output-test"
Formula[formula_name].any_installed_keg.unlink
unexpected_output = /unexpected caveat output|unexpected post_install output|
If you need to have this software first in your PATH|keg-only/x

expect { brew "link", formula_name }
.to not_to_output(unexpected_output).to_stdout
.and not_to_output.to_stderr
.and be_a_success
expect(HOMEBREW_PREFIX/"bin/link-output-test").to be_a_file
end
end
end
Loading
Loading