Skip to content

Commit

Permalink
cmd/mark: new command
Browse files Browse the repository at this point in the history
Add `brew mark`, a new command to mark or unmark formulae as installed
on request, or installed as dependency, as previously discussed in
#17125 (comment).

Sample usage:

    $ brew mark --installed-on-request --no-installed-as-dependency curl
    ==> curl is now marked as installed on request and not installed as dependency.
    $ brew mark --installed-on-request --no-installed-as-dependency curl
    ==> curl is already marked as installed on request and not installed as dependency.
    $ brew autoremove --dry-run
    [no output]
    $ brew mark --no-installed-on-request --installed-as-dependency curl
    ==> curl is now marked as not installed on request and installed as dependency.
    $ brew autoremove --dry-run
    ==> Would autoremove 2 unneeded formulae:
    curl
    rtmpdump
  • Loading branch information
ZhongRuoyu committed Jun 9, 2024
1 parent 633b46a commit 75a5230
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 1 deletion.
101 changes: 101 additions & 0 deletions Library/Homebrew/cmd/mark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# typed: strict
# frozen_string_literal: true

require "abstract_command"
require "formula"
require "tab"

module Homebrew
module Cmd
class Mark < AbstractCommand
cmd_args do
description <<~EOS
Mark or unmark <formula> as installed on request, or installed as
dependency. This can be useful if you want to control whether an
installed formula should be removed by `brew autoremove`.
EOS
switch "--installed-on-request",
description: "Mark <formula> as installed on request."
switch "--no-installed-on-request",
description: "Mark <formula> as not installed on request."
switch "--installed-as-dependency",
description: "Mark <formula> as installed as dependency."
switch "--no-installed-as-dependency",
description: "Mark <formula> as not installed as dependency."

conflicts "--installed-on-request", "--no-installed-on-request"
conflicts "--installed-as-dependency", "--no-installed-as-dependency"

named_args :formula, min: 1
end

sig { override.void }
def run
installed_on_request = if args.installed_on_request?
true
elsif args.no_installed_on_request?
false
end
installed_as_dependency = if args.installed_as_dependency?
true
elsif args.no_installed_as_dependency?
false
end
raise UsageError, "No marking option specified." if installed_on_request.nil? && installed_as_dependency.nil?

formulae = args.named.to_formulae
if (formula_not_installed = formulae.find { |f| !f.any_version_installed? })
odie "#{formula_not_installed.name} is not installed."
end

formulae.each do |formula|
mark_formula formula, installed_on_request:, installed_as_dependency:
end
end

private

sig {
params(
formula: Formula,
installed_on_request: T.nilable(T::Boolean),
installed_as_dependency: T.nilable(T::Boolean),
).void
}
def mark_formula(formula, installed_on_request:, installed_as_dependency:)
tab = Tab.for_formula(formula)
raise ArgumentError, "Tab file for #{formula.name} does not exist." unless tab.tabfile.exist?

unchanged_statuses = []
changed_statuses = []
unless installed_on_request.nil?
status = "#{installed_on_request ? "" : "not "}installed on request"
if tab.installed_on_request ^ installed_on_request
changed_statuses << status
else
unchanged_statuses << status
end
end
unless installed_as_dependency.nil?
status = "#{installed_as_dependency ? "" : "not "}installed as dependency"
if tab.installed_as_dependency ^ installed_as_dependency
changed_statuses << status
else
unchanged_statuses << status
end
end

unless unchanged_statuses.empty?
ohai "#{formula.name} is already marked as #{unchanged_statuses.to_sentence}."
end

return if changed_statuses.empty?

tab.installed_on_request = installed_on_request unless installed_on_request.nil?
tab.installed_as_dependency = installed_as_dependency unless installed_as_dependency.nil?
tab.write
ohai "#{formula.name} is now marked as #{changed_statuses.to_sentence}."
end
end
end
end
25 changes: 25 additions & 0 deletions Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/mark.rbi

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions Library/Homebrew/test/cmd/mark_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

require "cmd/mark"
require "cmd/shared_examples/args_parse"
require "tab"

RSpec.describe Homebrew::Cmd::Mark do
it_behaves_like "parseable arguments"

describe "integration tests", :integration_test do
def installed_on_request?(formula)
# `brew` subprocesses can change the tab, invalidating the cached values.
Tab.clear_cache
Tab.for_formula(formula).installed_on_request
end

def installed_as_dependency?(formula)
# `brew` subprocesses can change the tab, invalidating the cached values.
Tab.clear_cache
Tab.for_formula(formula).installed_as_dependency
end

let(:testball) { Formula["testball"] }
let(:baz) { Formula["baz"] }

before do
install_test_formula "testball", tab_attributes: {
"installed_on_request" => false,
"installed_as_dependency" => true,
}
setup_test_formula "baz"
end

it "marks or unmarks a formula as installed on request" do
expect(installed_on_request?(testball)).to be false

expect { brew "mark", "--installed-on-request", "testball" }
.to be_a_success
.and output(/testball is now marked as installed on request/).to_stdout
.and not_to_output.to_stderr
expect(installed_on_request?(testball)).to be true

expect { brew "mark", "--installed-on-request", "testball" }
.to be_a_success
.and output(/testball is already marked as installed on request/).to_stdout
.and not_to_output.to_stderr
expect(installed_on_request?(testball)).to be true

expect { brew "mark", "--no-installed-on-request", "testball" }
.to be_a_success
.and output(/testball is now marked as not installed on request/).to_stdout
.and not_to_output.to_stderr
expect(installed_on_request?(testball)).to be false
end

it "marks or unmarks a formula as installed as dependency" do
expect(installed_as_dependency?(testball)).to be true

expect { brew "mark", "--no-installed-as-dependency", "testball" }
.to be_a_success
.and output(/testball is now marked as not installed as dependency/).to_stdout
.and not_to_output.to_stderr
expect(installed_as_dependency?(testball)).to be false

expect { brew "mark", "--no-installed-as-dependency", "testball" }
.to be_a_success
.and output(/testball is already marked as not installed as dependency/).to_stdout
.and not_to_output.to_stderr
expect(installed_as_dependency?(testball)).to be false

expect { brew "mark", "--installed-as-dependency", "testball" }
.to be_a_success
.and output(/testball is now marked as installed as dependency/).to_stdout
.and not_to_output.to_stderr
expect(installed_as_dependency?(testball)).to be true
end

it "raises an error when a formula is not installed" do
expect { brew "mark", "--installed-on-request", "testball", "baz" }
.to be_a_failure
.and not_to_output.to_stdout
.and output(/baz is not installed/).to_stderr
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,23 @@ class #{Formulary.class_s(name)} < Formula
end
end

def install_test_formula(name, content = nil, build_bottle: false)
def install_test_formula(name, content = nil, build_bottle: false,
tab_attributes: nil)
setup_test_formula(name, content)
fi = FormulaInstaller.new(Formula[name], build_bottle:)
fi.prelude
fi.fetch
fi.install
fi.finish

return if tab_attributes.nil?

tab = Tab.for_name(name)
tab_attributes.each do |key, value|
tab.instance_variable_set(:"@#{key}", value)
end
tab.write
nil
end

def setup_test_tap
Expand Down
22 changes: 22 additions & 0 deletions completions/bash/brew
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,27 @@ _brew_ls() {
__brew_complete_installed_casks
}

_brew_mark() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "${cur}" in
-*)
__brewcomp "
--debug
--help
--installed-as-dependency
--installed-on-request
--no-installed-as-dependency
--no-installed-on-request
--quiet
--verbose
"
return
;;
*) ;;
esac
__brew_complete_formulae
}

_brew_migrate() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "${cur}" in
Expand Down Expand Up @@ -2740,6 +2761,7 @@ _brew() {
ln) _brew_ln ;;
log) _brew_log ;;
ls) _brew_ls ;;
mark) _brew_mark ;;
migrate) _brew_migrate ;;
missing) _brew_missing ;;
nodenv-sync) _brew_nodenv_sync ;;
Expand Down
12 changes: 12 additions & 0 deletions completions/fish/brew.fish
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,18 @@ __fish_brew_complete_arg 'ls; and not __fish_seen_argument -l cask -l casks' -a
__fish_brew_complete_arg 'ls; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_installed)'


__fish_brew_complete_cmd 'mark' 'Mark or unmark formula as installed on request, or installed as a dependency'
__fish_brew_complete_arg 'mark' -l debug -d 'Display any debugging information'
__fish_brew_complete_arg 'mark' -l help -d 'Show this message'
__fish_brew_complete_arg 'mark' -l installed-as-dependency -d 'Mark formula as installed as dependency'
__fish_brew_complete_arg 'mark' -l installed-on-request -d 'Mark formula as installed on request'
__fish_brew_complete_arg 'mark' -l no-installed-as-dependency -d 'Mark formula as not installed as dependency'
__fish_brew_complete_arg 'mark' -l no-installed-on-request -d 'Mark formula as not installed on request'
__fish_brew_complete_arg 'mark' -l quiet -d 'Make some output more quiet'
__fish_brew_complete_arg 'mark' -l verbose -d 'Make some output more verbose'
__fish_brew_complete_arg 'mark' -a '(__fish_brew_suggest_formulae_all)'


__fish_brew_complete_cmd 'migrate' 'Migrate renamed packages to new names, where formula are old names of packages'
__fish_brew_complete_arg 'migrate' -l cask -d 'Only migrate casks'
__fish_brew_complete_arg 'migrate' -l debug -d 'Display any debugging information'
Expand Down
1 change: 1 addition & 0 deletions completions/internal_commands_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ livecheck
ln
log
ls
mark
migrate
missing
nodenv-sync
Expand Down
16 changes: 16 additions & 0 deletions completions/zsh/_brew
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ __brew_internal_commands() {
'list:List all installed formulae and casks'
'livecheck:Check for newer versions of formulae and/or casks from upstream'
'log:Show the `git log` for formula or cask, or show the log for the Homebrew repository if no formula or cask is provided'
'mark:Mark or unmark formula as installed on request, or installed as a dependency'
'migrate:Migrate renamed packages to new names, where formula are old names of packages'
'missing:Check the given formula kegs for missing dependencies'
'nodenv-sync:Create symlinks for Homebrew'\''s installed NodeJS versions in `~/.nodenv/versions`'
Expand Down Expand Up @@ -1338,6 +1339,21 @@ _brew_ls() {
'*::installed_cask:__brew_installed_casks'
}

# brew mark
_brew_mark() {
_arguments \
'--debug[Display any debugging information]' \
'--help[Show this message]' \
'(--no-installed-as-dependency)--installed-as-dependency[Mark formula as installed as dependency]' \
'(--no-installed-on-request)--installed-on-request[Mark formula as installed on request]' \
'(--installed-as-dependency)--no-installed-as-dependency[Mark formula as not installed as dependency]' \
'(--installed-on-request)--no-installed-on-request[Mark formula as not installed on request]' \
'--quiet[Make some output more quiet]' \
'--verbose[Make some output more verbose]' \
- formula \
'*::formula:__brew_formulae'
}

# brew migrate
_brew_migrate() {
_arguments \
Expand Down
21 changes: 21 additions & 0 deletions docs/Manpage.md
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,27 @@ repository if no formula or cask is provided.

: Treat all named arguments as casks.

### `mark` \[*`options`*\] *`formula`* \[...\]

Mark or unmark *`formula`* as installed on request, or installed as a
dependency.

`--installed-on-request`

: Mark *`formula`* as installed on request.

`--no-installed-on-request`

: Mark *`formula`* as not installed on request.

`--installed-as-dependency`

: Mark *`formula`* as installed as dependency.

`--no-installed-as-dependency`

: Mark *`formula`* as not installed as dependency.

### `migrate` \[*`options`*\] *`installed_formula`*\|*`installed_cask`* \[...\]

Migrate renamed packages to new names, where *`formula`* are old names of
Expand Down
14 changes: 14 additions & 0 deletions manpages/brew.1
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,20 @@ Treat all named arguments as formulae\.
.TP
\fB\-\-cask\fP
Treat all named arguments as casks\.
.SS "\fBmark\fP \fR[\fIoptions\fP] \fIformula\fP \fR[\.\.\.]"
Mark or unmark \fIformula\fP as installed on request, or installed as a dependency\.
.TP
\fB\-\-installed\-on\-request\fP
Mark \fIformula\fP as installed on request\.
.TP
\fB\-\-no\-installed\-on\-request\fP
Mark \fIformula\fP as not installed on request\.
.TP
\fB\-\-installed\-as\-dependency\fP
Mark \fIformula\fP as installed as dependency\.
.TP
\fB\-\-no\-installed\-as\-dependency\fP
Mark \fIformula\fP as not installed as dependency\.
.SS "\fBmigrate\fP \fR[\fIoptions\fP] \fIinstalled_formula\fP|\fIinstalled_cask\fP \fR[\.\.\.]"
Migrate renamed packages to new names, where \fIformula\fP are old names of packages\.
.TP
Expand Down

0 comments on commit 75a5230

Please sign in to comment.