Skip to content

Support 'login-items' as a first-class citizen of the CLI #19744

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

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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 .ruby-version
1 change: 1 addition & 0 deletions Library/Homebrew/ast_constants.rb
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
[{ name: :include, type: :method_call }],
[{ name: :desc, type: :method_call }],
[{ name: :homepage, type: :method_call }],
[{ name: :login_items, type: :method_call }],
[{ name: :url, type: :method_call }],
[{ name: :mirror, type: :method_call }],
[{ name: :version, type: :method_call }],
2 changes: 2 additions & 0 deletions Library/Homebrew/cask/artifact/abstract_uninstall.rb
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ class AbstractUninstall < AbstractArtifact
:launchctl,
:quit,
:signal,
# odeprecated: deprecate when all casks have been migrated to top-level login_items
:login_item,
:kext,
:script,
@@ -295,6 +296,7 @@ def uninstall_signal(*signals, command: nil, **_)
end
end

# TODO: Need to refer to attribute from the cask instead of this uninstall stanza
def uninstall_login_item(*login_items, command: nil, successor: nil, **_)
return if successor

6 changes: 6 additions & 0 deletions Library/Homebrew/cask/cask.rb
Original file line number Diff line number Diff line change
@@ -321,6 +321,10 @@ def languages
@languages ||= @dsl.languages
end

def login_items
@login_items ||= @dsl.login_items
end

def tap_git_head
@tap_git_head ||= tap&.git_head
rescue TapUnavailableError
@@ -331,6 +335,7 @@ def populate_from_api!(json_cask)
raise ArgumentError, "Expected cask to be loaded from the API" unless loaded_from_api?

@languages = json_cask.fetch(:languages, [])
@login_items = json_cask.fetch(:login_items, [])
@tap_git_head = json_cask.fetch(:tap_git_head, "HEAD")

@ruby_source_path = json_cask[:ruby_source_path]
@@ -400,6 +405,7 @@ def to_h
"languages" => languages,
"ruby_source_path" => ruby_source_path,
"ruby_source_checksum" => ruby_source_checksum,
"login_items" => login_items,
}
end

3 changes: 2 additions & 1 deletion Library/Homebrew/cask/cask_loader.rb
Original file line number Diff line number Diff line change
@@ -367,6 +367,7 @@ def load(config:)
end
desc json_cask[:desc]
homepage json_cask[:homepage]
login_items json_cask[:login_items] if json_cask[:login_items].present?

if (deprecation_date = json_cask[:deprecation_date].presence)
reason = DeprecateDisable.to_reason_string_or_symbol json_cask[:deprecation_reason], type: :cask
@@ -378,7 +379,7 @@ def load(config:)
disable! date: disable_date, because: reason
end

auto_updates json_cask[:auto_updates] unless json_cask[:auto_updates].nil?
auto_updates json_cask[:auto_updates] if json_cask[:auto_updates].present?
conflicts_with(**json_cask[:conflicts_with]) if json_cask[:conflicts_with].present?

if json_cask[:depends_on].present?
16 changes: 16 additions & 0 deletions Library/Homebrew/cask/dsl.rb
Original file line number Diff line number Diff line change
@@ -106,6 +106,7 @@ class DSL
:no_autobump!,
:autobump?,
:no_autobump_message,
:login_items,
:on_system_blocks_exist?,
:on_system_block_min_os,
:depends_on_set_in_block?,
@@ -229,6 +230,21 @@ def set_unique_stanza(stanza, should_return)
raise CaskInvalidError.new(cask, "'#{stanza}' stanza failed with: #{e}")
end

# Sets the cask's login items
#
# ### Example
#
# ```ruby
# login_items "Raycast"
# ```
#
# @api public
def login_items(login_items = nil)
return [] if login_items.nil?

set_unique_stanza(:login_items, login_items.nil?) { Array(login_items) }
end

# Sets the cask's homepage.
#
# ### Example
12 changes: 12 additions & 0 deletions Library/Homebrew/cask/info.rb
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ def self.get_info(cask)
language = language_info(cask)
output << language if language
output << "#{artifact_info(cask)}\n"
login_items = login_items_info(cask)
output << login_items if login_items
caveats = Installer.caveats(cask)
output << caveats if caveats
output
@@ -132,5 +134,15 @@ def self.artifact_info(cask)
end
artifact_output.freeze
end

sig { params(cask: Cask).returns(T.nilable(String)) }
def self.login_items_info(cask)
return if cask.login_items.empty?

<<~EOS
#{ohai_title("Login Items")}
#{cask.login_items.join(", ")}
EOS
end
end
end
29 changes: 27 additions & 2 deletions Library/Homebrew/cask/installer.rb
Original file line number Diff line number Diff line change
@@ -20,14 +20,15 @@ class Installer
skip_cask_deps: T::Boolean, binaries: T::Boolean, verbose: T::Boolean, zap: T::Boolean,
require_sha: T::Boolean, upgrade: T::Boolean, reinstall: T::Boolean, installed_as_dependency: T::Boolean,
installed_on_request: T::Boolean, quarantine: T::Boolean, verify_download_integrity: T::Boolean,
quiet: T::Boolean
quiet: T::Boolean, login_items: T::Boolean
).void
}
def initialize(cask, command: SystemCommand, force: false, adopt: false,
skip_cask_deps: false, binaries: true, verbose: false,
zap: false, require_sha: false, upgrade: false, reinstall: false,
installed_as_dependency: false, installed_on_request: true,
quarantine: true, verify_download_integrity: true, quiet: false)
quarantine: true, verify_download_integrity: true, quiet: false,
login_items: false)
@cask = cask
@command = command
@force = force
@@ -44,6 +45,7 @@ def initialize(cask, command: SystemCommand, force: false, adopt: false,
@quarantine = quarantine
@verify_download_integrity = verify_download_integrity
@quiet = quiet
@login_items = login_items
end

sig { returns(T::Boolean) }
@@ -61,6 +63,9 @@ def installed_as_dependency? = @installed_as_dependency
sig { returns(T::Boolean) }
def installed_on_request? = @installed_on_request

sig { returns(T::Boolean) }
def login_items? = @login_items

sig { returns(T::Boolean) }
def quarantine? = @quarantine

@@ -310,6 +315,17 @@ def install_artifacts(predecessor: nil)
already_installed_artifacts.unshift(artifact)
end

unless @cask.login_items.empty?
if login_items?
@cask.login_items.each do |lgi|
# TODO: register the login_items here using osascript
ohai "***** Will REGISTER login_item: #{lgi}"
end
else
ohai "Skipping processing of login_items"
end
end

save_config_file
save_download_sha if @cask.version.latest?
rescue => e
@@ -546,6 +562,15 @@ def uninstall_artifacts(clear: false, successor: nil)
odebug "Uninstalling artifacts"
odebug "#{::Utils.pluralize("artifact", artifacts.length, include_count: true)} defined", artifacts

if login_items?
@cask.login_items.each do |lgi|
# TODO: unregister the login_items here using osascript
ohai "***** Will UNREGISTER login_item: #{lgi}"
end
else
ohai "Skipping processing of login_items"
end

artifacts.each do |artifact|
if artifact.respond_to?(:uninstall_phase)
odebug "Uninstalling artifact of class #{artifact.class}"
8 changes: 5 additions & 3 deletions Library/Homebrew/cask/reinstall.rb
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ class Reinstall
sig {
params(
casks: ::Cask::Cask, verbose: T::Boolean, force: T::Boolean, skip_cask_deps: T::Boolean, binaries: T::Boolean,
require_sha: T::Boolean, quarantine: T::Boolean, zap: T::Boolean
require_sha: T::Boolean, quarantine: T::Boolean, zap: T::Boolean, login_items: T::Boolean
).void
}
def self.reinstall_casks(
@@ -17,15 +17,17 @@ def self.reinstall_casks(
binaries: false,
require_sha: false,
quarantine: false,
zap: false
zap: false,
login_items: true
)
require "cask/installer"

quarantine = true if quarantine.nil?

casks.each do |cask|
Installer
.new(cask, binaries:, verbose:, force:, skip_cask_deps:, require_sha:, reinstall: true, quarantine:, zap:)
.new(cask, binaries:, verbose:, force:, skip_cask_deps:, require_sha:, reinstall: true, quarantine:, zap:,
login_items:)
.install
end
end
10 changes: 7 additions & 3 deletions Library/Homebrew/cask/upgrade.rb
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ class Upgrade
binaries: T.nilable(T::Boolean),
quarantine: T.nilable(T::Boolean),
require_sha: T.nilable(T::Boolean),
login_items: T.nilable(T::Boolean),
).returns(T::Boolean)
}
def self.upgrade_casks(
@@ -36,7 +37,8 @@ def self.upgrade_casks(
quiet: false,
binaries: nil,
quarantine: nil,
require_sha: nil
require_sha: nil,
login_items: nil
)
quarantine = true if quarantine.nil?

@@ -123,7 +125,7 @@ def self.upgrade_casks(
upgrade_cask(
old_cask, new_cask,
binaries:, force:, skip_cask_deps:, verbose:,
quarantine:, require_sha:
quarantine:, require_sha:, login_items:
)
rescue => e
new_exception = e.exception("#{new_cask.full_name}: #{e}")
@@ -147,13 +149,14 @@ def self.upgrade_casks(
force: T.nilable(T::Boolean),
quarantine: T.nilable(T::Boolean),
require_sha: T.nilable(T::Boolean),
login_items: T.nilable(T::Boolean),
skip_cask_deps: T.nilable(T::Boolean),
verbose: T.nilable(T::Boolean),
).void
}
def self.upgrade_cask(
old_cask, new_cask,
binaries:, force:, quarantine:, require_sha:, skip_cask_deps:, verbose:
binaries:, force:, quarantine:, require_sha:, login_items:, skip_cask_deps:, verbose:
)
require "cask/installer"

@@ -181,6 +184,7 @@ def self.upgrade_cask(
require_sha:,
upgrade: true,
quarantine:,
login_items:,
}.compact

new_cask_installer =
6 changes: 6 additions & 0 deletions Library/Homebrew/cmd/install.rb
Original file line number Diff line number Diff line change
@@ -147,6 +147,10 @@ class InstallCmd < AbstractCommand
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine,
}],
[:switch, "--[no-]login-items", {
description: "Disable/enable registering of login item(s) (default: disabled).",
env: :cask_opts_login_items,
}],
[:switch, "--adopt", {
description: "Adopt existing artifacts in the destination that are identical to those being installed. " \
"Cannot be combined with `--force`.",
@@ -249,6 +253,7 @@ def run
binaries: args.binaries?,
force: args.force?,
quarantine: args.quarantine?,
login_items: args.login_items?,
quiet: args.quiet?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
@@ -265,6 +270,7 @@ def run
dry_run: args.dry_run?,
binaries: args.binaries?,
quarantine: args.quarantine?,
login_items: args.login_items?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
verbose: args.verbose?,
5 changes: 5 additions & 0 deletions Library/Homebrew/cmd/reinstall.rb
Original file line number Diff line number Diff line change
@@ -87,6 +87,10 @@ class Reinstall < AbstractCommand
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine,
}],
[:switch, "--[no-]login-items", {
description: "Disable/enable registering of login item(s) (default: disabled).",
env: :cask_opts_login_items,
}],
[:switch, "--adopt", {
description: "Adopt existing artifacts in the destination that are identical to those being installed. " \
"Cannot be combined with `--force`.",
@@ -181,6 +185,7 @@ def run
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
quarantine: args.quarantine?,
login_items: args.login_items?,
zap: args.zap?,
)
end
2 changes: 1 addition & 1 deletion Library/Homebrew/cmd/uninstall.rb
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ def run

raise Cask::CaskNotInstalledError, cask if !cask.installed? && !args.force?

Cask::Installer.new(cask, verbose: args.verbose?, force: args.force?).zap
Cask::Installer.new(cask, verbose: args.verbose?, force: args.force?, login_items: true).zap
end
else
Cask::Uninstall.uninstall_casks(
5 changes: 5 additions & 0 deletions Library/Homebrew/cmd/upgrade.rb
Original file line number Diff line number Diff line change
@@ -110,6 +110,10 @@ class UpgradeCmd < AbstractCommand
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine,
}],
[:switch, "--[no-]login-items", {
description: "Disable/enable registering of login item(s) (default: disabled).",
env: :cask_opts_login_items,
}],
].each do |args|
options = args.pop
send(*args, **options)
@@ -272,6 +276,7 @@ def upgrade_outdated_casks(casks)
dry_run: args.dry_run?,
binaries: args.binaries?,
quarantine: args.quarantine?,
login_items: args.login_items?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
verbose: args.verbose?,
13 changes: 12 additions & 1 deletion Library/Homebrew/env_config.rb
Original file line number Diff line number Diff line change
@@ -105,7 +105,8 @@ module EnvConfig
},
HOMEBREW_CASK_OPTS: {
description: "Append these options to all `cask` commands. All `--*dir` options, " \
"`--language`, `--require-sha`, `--no-quarantine` and `--no-binaries` are supported. " \
"`--language`, `--require-sha`, `--no-quarantine`, `--no-login-items` " \
"and `--no-binaries` are supported. " \
"For example, you might add something like the following to your " \
"`~/.profile`, `~/.bash_profile`, or `~/.zshenv`:" \
"\n\n `export HOMEBREW_CASK_OPTS=\"--appdir=${HOME}/Applications --fontdir=/Library/Fonts\"`",
@@ -616,6 +617,16 @@ def cask_opts_quarantine?
true
end

sig { returns(T::Boolean) }
def cask_opts_login_items?
cask_opts.reverse_each do |opt|
return true if opt == "--login-items"
return false if opt == "--no-login-items"
end

false
end

sig { returns(T::Boolean) }
def cask_opts_require_sha?
cask_opts.include?("--require-sha")
2 changes: 2 additions & 0 deletions Library/Homebrew/rubocops/cask/constants/stanza.rb
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ module Constants
:app,
:pkg,
:installer,
:login_items,
:binary,
:manpage,
:bash_completion,
@@ -85,6 +86,7 @@ module Constants
:launchctl,
:quit,
:signal,
# odeprecated: deprecate when all casks have been migrated to top-level login_items
:login_item,
:kext,
:script,
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.