Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement conflicts_with :formula for casks #16374

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions Library/Homebrew/cask/exceptions.rb
Expand Up @@ -88,6 +88,34 @@
end
end

# Error when a cask conflicts with formulae.
#
# @api private
class CaskConflictWithFormulaError < AbstractCaskErrorWithToken
attr_reader :conflicting_formulae

def initialize(token, conflicting_formulae)
super(token)
@conflicting_formulae = conflicting_formulae

Check warning on line 99 in Library/Homebrew/cask/exceptions.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cask/exceptions.rb#L98-L99

Added lines #L98 - L99 were not covered by tests
end

sig { returns(String) }
def to_s
message = []
message << "Cask '#{token}' conflicts with the following formulae that are installed:"
message.concat conflicting_formulae.map(&:conflict_message) << ""
message << <<~EOS

Check warning on line 107 in Library/Homebrew/cask/exceptions.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cask/exceptions.rb#L104-L107

Added lines #L104 - L107 were not covered by tests
Please `brew unlink #{conflicting_formulae.map(&:name) * " "}` before continuing.

Unlinking removes a formula's symlinks from #{HOMEBREW_PREFIX}. You can
link the formula again after the install finishes. You can --force this
install, but the build may fail or cause obscure side effects in the
resulting software.
EOS
message.join("\n")

Check warning on line 115 in Library/Homebrew/cask/exceptions.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cask/exceptions.rb#L115

Added line #L115 was not covered by tests
end
end

# Error when a cask is not available.
#
# @api private
Expand Down
8 changes: 8 additions & 0 deletions Library/Homebrew/cask/installer.rb
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require "formula_installer"
require "formula_conflict"
require "unpack_strategy"
require "utils/topological_hash"

Expand Down Expand Up @@ -138,6 +139,7 @@
end

def check_conflicts
return if force?
return unless @cask.conflicts_with

@cask.conflicts_with[:cask].each do |conflicting_cask|
Expand All @@ -151,6 +153,12 @@
rescue CaskUnavailableError
next # Ignore conflicting Casks that do not exist.
end

formula_conflicts = @cask.conflicts_with[:formula].map do |conflicting_formula|
FormulaConflict.new(conflicting_formula, nil)

Check warning on line 158 in Library/Homebrew/cask/installer.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cask/installer.rb#L157-L158

Added lines #L157 - L158 were not covered by tests
end
formula_conflicts.select! { |c| c.conflicts?(@cask) }

Check warning on line 160 in Library/Homebrew/cask/installer.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cask/installer.rb#L160

Added line #L160 was not covered by tests
raise CaskConflictWithFormulaError.new(@cask, formula_conflicts) unless formula_conflicts.empty?
end

def uninstall_existing_cask
Expand Down
9 changes: 1 addition & 8 deletions Library/Homebrew/exceptions.rb
Expand Up @@ -420,18 +420,11 @@ def initialize(formula, conflicts)
super message
end

def conflict_message(conflict)
message = []
message << " #{conflict.name}"
message << ": because #{conflict.reason}" if conflict.reason
message.join
end

sig { returns(String) }
def message
message = []
message << "Cannot install #{formula.full_name} because conflicting formulae are installed."
message.concat conflicts.map { |c| conflict_message(c) } << ""
message.concat conflicts.map(&:conflict_message) << ""
message << <<~EOS
Please `brew unlink #{conflicts.map(&:name) * " "}` before continuing.

Expand Down
1 change: 1 addition & 0 deletions Library/Homebrew/formula.rb
Expand Up @@ -4,6 +4,7 @@
require "cache_store"
require "did_you_mean"
require "formula_support"
require "formula_conflict"
require "lock_file"
require "formula_pin"
require "hardware"
Expand Down
41 changes: 41 additions & 0 deletions Library/Homebrew/formula_conflict.rb
@@ -0,0 +1,41 @@
# typed: true
# frozen_string_literal: true

# Used to track formulae that cannot be installed at the same time.
FormulaConflict = Struct.new(:name, :reason) do
def conflict_message
message = []
message << " #{name}"

Check warning on line 8 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L7-L8

Added lines #L7 - L8 were not covered by tests
message << ": because #{reason}" if reason
message.join

Check warning on line 10 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L10

Added line #L10 was not covered by tests
end

def conflicts?(formula_or_cask)
f = Formulary.factory(name)

Check warning on line 14 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L14

Added line #L14 was not covered by tests
rescue TapFormulaUnavailableError
# If the formula name is a fully-qualified name let's silently
# ignore it as we don't care about things used in taps that aren't
# currently tapped.
false

Check warning on line 19 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L19

Added line #L19 was not covered by tests
rescue FormulaUnavailableError => e
# If the formula name doesn't exist any more then complain but don't
# stop installation from continuing.
official_tap, filename = if formula_or_cask.is_a?(Formula)

Check warning on line 23 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L23

Added line #L23 was not covered by tests
["homebrew-core", formula_or_cask.path.basename]
else
["homebrew-cask", formula_or_cask.sourcefile_path.basename]
end
opoo <<~EOS

Check warning on line 28 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L28

Added line #L28 was not covered by tests
#{formula_or_cask}: #{e.message}
'conflicts_with "#{name}"' should be removed from #{filename}.
EOS

raise if Homebrew::EnvConfig.developer?

$stderr.puts "Please report this issue to the #{formula_or_cask.tap} tap " \

Check warning on line 35 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L35

Added line #L35 was not covered by tests
"(not Homebrew/brew or Homebrew/#{official_tap})!"
false

Check warning on line 37 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L37

Added line #L37 was not covered by tests
else
f.linked_keg.exist? && f.opt_prefix.exist?

Check warning on line 39 in Library/Homebrew/formula_conflict.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/formula_conflict.rb#L39

Added line #L39 was not covered by tests
end
end
24 changes: 1 addition & 23 deletions Library/Homebrew/formula_installer.rb
Expand Up @@ -483,29 +483,7 @@ def install
def check_conflicts
return if force?

conflicts = formula.conflicts.select do |c|
f = Formulary.factory(c.name)
rescue TapFormulaUnavailableError
# If the formula name is a fully-qualified name let's silently
# ignore it as we don't care about things used in taps that aren't
# currently tapped.
false
rescue FormulaUnavailableError => e
# If the formula name doesn't exist any more then complain but don't
# stop installation from continuing.
opoo <<~EOS
#{formula}: #{e.message}
'conflicts_with "#{c.name}"' should be removed from #{formula.path.basename}.
EOS

raise if Homebrew::EnvConfig.developer?

$stderr.puts "Please report this issue to the #{formula.tap} tap (not Homebrew/brew or Homebrew/homebrew-core)!"
false
else
f.linked_keg.exist? && f.opt_prefix.exist?
end

conflicts = formula.conflicts.select { |c| c.conflicts?(formula) }
raise FormulaConflictError.new(formula, conflicts) unless conflicts.empty?
end

Expand Down
3 changes: 0 additions & 3 deletions Library/Homebrew/formula_support.rb
@@ -1,9 +1,6 @@
# typed: true
# frozen_string_literal: true

# Used to track formulae that cannot be installed at the same time.
FormulaConflict = Struct.new(:name, :reason)

# Used to annotate formulae that duplicate macOS-provided software
# or cause conflicts when linked in.
class KegOnlyReason
Expand Down
5 changes: 4 additions & 1 deletion Library/Homebrew/test/exceptions_spec.rb
Expand Up @@ -166,7 +166,10 @@ class Baz < Formula; end
subject { described_class.new(formula, [conflict]) }

let(:formula) { instance_double(Formula, full_name: "foo/qux") }
let(:conflict) { instance_double(FormulaConflict, name: "bar", reason: "I decided to") }
let(:conflict) do
instance_double(FormulaConflict, name: "bar", reason: "I decided to", conflicts?: true,
conflict_message: " bar: because I decided to")
end

its(:to_s) { is_expected.to match(/Please `brew unlink bar` before continuing\./) }
end
Expand Down
4 changes: 1 addition & 3 deletions docs/Cask-Cookbook.md
Expand Up @@ -154,7 +154,7 @@ Each cask must declare one or more *artifacts* (i.e. something to install).
| name | multiple occurrences allowed? | value |
| ------------------------------------------ | :---------------------------: | ----- |
| [`uninstall`](#stanza-uninstall) | yes | Procedures to uninstall a cask. Optional unless the `pkg` stanza is used. |
| [`conflicts_with`](#stanza-conflicts_with) | yes | List of conflicts with this cask (*not yet functional*). |
| [`conflicts_with`](#stanza-conflicts_with) | yes | List of conflicts with this cask. |
| [`caveats`](#stanza-caveats) | yes | String or Ruby block providing the user with cask-specific information at install time. |
| [`deprecate!`](#stanza-deprecate--disable) | no | Date as a String in `YYYY-MM-DD` format and a String or Symbol providing a reason. |
| [`disable!`](#stanza-deprecate--disable) | no | Date as a String in `YYYY-MM-DD` format and a String or Symbol providing a reason. |
Expand Down Expand Up @@ -351,8 +351,6 @@ conflicts_with cask: "macfuse-dev"

#### `conflicts_with` *formula*

**Note:** `conflicts_with formula:` is a stub and is not yet functional.

The value should be another formula name.

Example: [MacVim](https://github.com/Homebrew/homebrew-cask/blob/aa461148bbb5119af26b82cccf5003e2b4e50d95/Casks/m/macvim.rb#L16), which conflicts with the `macvim` formula.
Expand Down