diff --git a/Library/Homebrew/cli/named_args.rb b/Library/Homebrew/cli/named_args.rb index 80116b339ac45..94c032dec0b38 100644 --- a/Library/Homebrew/cli/named_args.rb +++ b/Library/Homebrew/cli/named_args.rb @@ -98,8 +98,13 @@ def load_formula_or_cask(name, only: nil, method: nil) Formulary.factory(name, *spec, force_bottle: @force_bottle, flags: @flags) when :resolve resolve_formula(name) - when :keg - resolve_keg(name) + when :latest_kegs + resolve_latest_keg(name) + when :keg, :default_kegs + # TODO: (3.2) Uncomment the following + # odeprecated "`load_formula_or_cask` with `method: :keg`", + # "`load_formula_or_cask` with `method: :default_kegs`" + resolve_default_keg(name) when :kegs _, kegs = resolve_kegs(name) kegs @@ -208,10 +213,34 @@ def to_paths(only: parent&.only_formula_or_cask, recurse_tap: false) end.uniq.freeze end + sig { returns(T::Array[Keg]) } + def to_default_kegs + @to_default_kegs ||= begin + to_formulae_and_casks(only: :formula, method: :default_kegs).freeze + rescue NoSuchKegError => e + if (reason = MissingFormula.suggest_command(e.name, "uninstall")) + $stderr.puts reason + end + raise e + end + end + + sig { returns(T::Array[Keg]) } + def to_latest_kegs + @to_latest_kegs ||= begin + to_formulae_and_casks(only: :formula, method: :latest_kegs).freeze + rescue NoSuchKegError => e + if (reason = MissingFormula.suggest_command(e.name, "uninstall")) + $stderr.puts reason + end + raise e + end + end + sig { returns(T::Array[Keg]) } def to_kegs @to_kegs ||= begin - to_formulae_and_casks(only: :formula, method: :keg).freeze + to_formulae_and_casks(only: :formula, method: :kegs).freeze rescue NoSuchKegError => e if (reason = MissingFormula.suggest_command(e.name, "uninstall")) $stderr.puts reason @@ -225,7 +254,7 @@ def to_kegs .returns([T::Array[Keg], T::Array[Cask::Cask]]) } def to_kegs_to_casks(only: parent&.only_formula_or_cask, ignore_unavailable: nil, all_kegs: nil) - method = all_kegs ? :kegs : :keg + method = all_kegs ? :kegs : :default_kegs @to_kegs_to_casks ||= {} @to_kegs_to_casks[method] ||= to_formulae_and_casks(only: only, ignore_unavailable: ignore_unavailable, method: method) @@ -282,7 +311,16 @@ def resolve_kegs(name) [rack, kegs] end - def resolve_keg(name) + def resolve_latest_keg(name) + _, kegs = resolve_kegs(name) + + # Return keg if it is the only installed keg + return kegs if kegs.length == 1 + + kegs.reject { |k| k.version.head? }.max_by(&:version) + end + + def resolve_default_keg(name) rack, kegs = resolve_kegs(name) linked_keg_ref = HOMEBREW_LINKED_KEGS/rack.basename diff --git a/Library/Homebrew/cmd/link.rb b/Library/Homebrew/cmd/link.rb index 2952c86e3679f..245afe95000e3 100644 --- a/Library/Homebrew/cmd/link.rb +++ b/Library/Homebrew/cmd/link.rb @@ -26,6 +26,8 @@ def link_args "`brew link --overwrite` without actually linking or deleting any files." switch "-f", "--force", description: "Allow keg-only formulae to be linked." + switch "--HEAD", + description: "Link the HEAD version of the formula if it is installed." named_args :installed_formula, min: 1 end @@ -40,18 +42,29 @@ def link verbose: args.verbose?, } - args.named.to_kegs.each do |keg| + kegs = if args.HEAD? + args.named.to_kegs.group_by(&:name).map do |name, resolved_kegs| + head_keg = resolved_kegs.find { |keg| keg.version.head? } + next head_keg if head_keg.present? + + opoo <<~EOS + No HEAD keg installed for #{name} + To install, run: + brew install --HEAD #{name} + EOS + end.compact + else + args.named.to_latest_kegs + end + + kegs.freeze.each do |keg| keg_only = Formulary.keg_only?(keg.rack) if keg.linked? opoo "Already linked: #{keg}" - name_and_flag = if keg_only - "--force #{keg.name}" - else - keg.name - end + name_and_flag = "#{"--HEAD " if args.HEAD?}#{"--force " if keg_only}#{keg.name}" puts <<~EOS - To relink: + To relink, run: brew unlink #{keg.name} && brew link #{name_and_flag} EOS next diff --git a/Library/Homebrew/cmd/list.rb b/Library/Homebrew/cmd/list.rb index 4132d9bf16ed2..cdfdc9c0e08b9 100644 --- a/Library/Homebrew/cmd/list.rb +++ b/Library/Homebrew/cmd/list.rb @@ -129,9 +129,10 @@ def list safe_system "ls", *ls_args, Cask::Caskroom.path end elsif args.verbose? || !$stdout.tty? - system_command! "find", args: args.named.to_kegs.map(&:to_s) + %w[-not -type d -print], print_stdout: true + system_command! "find", args: args.named.to_default_kegs.map(&:to_s) + %w[-not -type d -print], + print_stdout: true else - args.named.to_kegs.each { |keg| PrettyListing.new keg } + args.named.to_default_kegs.each { |keg| PrettyListing.new keg } end end diff --git a/Library/Homebrew/cmd/unlink.rb b/Library/Homebrew/cmd/unlink.rb index fd8bf4ebf892f..173e0d66fe449 100644 --- a/Library/Homebrew/cmd/unlink.rb +++ b/Library/Homebrew/cmd/unlink.rb @@ -31,7 +31,7 @@ def unlink options = { dry_run: args.dry_run?, verbose: args.verbose? } - args.named.to_kegs.each do |keg| + args.named.to_default_kegs.each do |keg| if args.dry_run? puts "Would remove:" keg.unlink(**options) diff --git a/Library/Homebrew/dev-cmd/linkage.rb b/Library/Homebrew/dev-cmd/linkage.rb index d907aee83f02e..d765f9ac5fbe8 100644 --- a/Library/Homebrew/dev-cmd/linkage.rb +++ b/Library/Homebrew/dev-cmd/linkage.rb @@ -35,10 +35,10 @@ def linkage args = linkage_args.parse CacheStoreDatabase.use(:linkage) do |db| - kegs = if args.named.to_kegs.empty? + kegs = if args.named.to_default_kegs.empty? Formula.installed.map(&:any_installed_keg).reject(&:nil?) else - args.named.to_kegs + args.named.to_default_kegs end kegs.each do |keg| ohai "Checking #{keg.name} linkage" if kegs.size > 1 diff --git a/Library/Homebrew/test/cli/named_args_spec.rb b/Library/Homebrew/test/cli/named_args_spec.rb index 463c93986b495..4f5130a55f5a2 100644 --- a/Library/Homebrew/test/cli/named_args_spec.rb +++ b/Library/Homebrew/test/cli/named_args_spec.rb @@ -179,18 +179,63 @@ def setup_unredable_cask(name) describe "#to_kegs" do before do (HOMEBREW_CELLAR/"foo/1.0").mkpath + (HOMEBREW_CELLAR/"foo/2.0").mkpath (HOMEBREW_CELLAR/"bar/1.0").mkpath end it "resolves kegs with #resolve_kegs" do - expect(described_class.new("foo", "bar").to_kegs.map(&:name)).to eq ["foo", "bar"] + expect(described_class.new("foo", "bar").to_kegs.map(&:name)).to eq ["foo", "foo", "bar"] end - it "when there are no matching kegs returns an array of Kegs" do + it "resolves kegs with multiple versions with #resolve_keg" do + expect(described_class.new("foo").to_kegs.map { |k| k.version.version.to_s }.sort).to eq ["1.0", "2.0"] + end + + it "when there are no matching kegs returns an empty array" do expect(described_class.new.to_kegs).to be_empty end end + describe "#to_default_kegs" do + before do + (HOMEBREW_CELLAR/"foo/1.0").mkpath + (HOMEBREW_CELLAR/"bar/1.0").mkpath + linked_path = (HOMEBREW_CELLAR/"foo/2.0") + linked_path.mkpath + Keg.new(linked_path).link + end + + it "resolves kegs with #resolve_default_keg" do + expect(described_class.new("foo", "bar").to_default_kegs.map(&:name)).to eq ["foo", "bar"] + end + + it "resolves the default keg" do + expect(described_class.new("foo").to_default_kegs.map { |k| k.version.version.to_s }).to eq ["2.0"] + end + + it "when there are no matching kegs returns an empty array" do + expect(described_class.new.to_default_kegs).to be_empty + end + end + + describe "#to_latest_kegs" do + before do + (HOMEBREW_CELLAR/"foo/1.0").mkpath + (HOMEBREW_CELLAR/"foo/2.0").mkpath + (HOMEBREW_CELLAR/"bar/1.0").mkpath + end + + it "resolves the latest kegs with #resolve_latest_keg" do + latest_kegs = described_class.new("foo", "bar").to_latest_kegs + expect(latest_kegs.map(&:name)).to eq ["foo", "bar"] + expect(latest_kegs.map { |k| k.version.version.to_s }).to eq ["2.0", "1.0"] + end + + it "when there are no matching kegs returns an empty array" do + expect(described_class.new.to_latest_kegs).to be_empty + end + end + describe "#to_kegs_to_casks" do before do (HOMEBREW_CELLAR/"foo/1.0").mkpath