Merge pull request #19552 from Homebrew/bundle-services

Add `brew bundle exec --services`
This commit is contained in:
Mike McQuaid 2025-03-28 08:43:43 +00:00 committed by GitHub
commit 2603401bcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 293 additions and 48 deletions

View File

@ -113,12 +113,15 @@ module Homebrew
def service_change_state!(verbose:) def service_change_state!(verbose:)
require "bundle/brew_services" require "bundle/brew_services"
file = Bundle::BrewServices.versioned_service_file(@name)
if restart_service_needed? if restart_service_needed?
puts "Restarting #{@name} service." if verbose puts "Restarting #{@name} service." if verbose
BrewServices.restart(@full_name, verbose:) BrewServices.restart(@full_name, file:, verbose:)
elsif start_service_needed? elsif start_service_needed?
puts "Starting #{@name} service." if verbose puts "Starting #{@name} service." if verbose
BrewServices.start(@full_name, verbose:) BrewServices.start(@full_name, file:, verbose:)
else else
true true
end end

View File

@ -1,43 +1,58 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "services/system"
module Homebrew module Homebrew
module Bundle module Bundle
module BrewServices module BrewServices
module_function def self.reset!
def reset!
@started_services = nil @started_services = nil
end end
def stop(name, verbose: false) def self.stop(name, keep: false, verbose: false)
return true unless started?(name) return true unless started?(name)
return unless Bundle.brew("services", "stop", name, verbose:) args = ["services", "stop", name]
args << "--keep" if keep
return unless Bundle.brew(*args, verbose:)
started_services.delete(name) started_services.delete(name)
true true
end end
def start(name, verbose: false) def self.start(name, file: nil, verbose: false)
return unless Bundle.brew("services", "start", name, verbose:) args = ["services", "start", name]
args << "--file=#{file}" if file
return unless Bundle.brew(*args, verbose:)
started_services << name started_services << name
true true
end end
def restart(name, verbose: false) def self.run(name, file: nil, verbose: false)
return unless Bundle.brew("services", "restart", name, verbose:) args = ["services", "run", name]
args << "--file=#{file}" if file
return unless Bundle.brew(*args, verbose:)
started_services << name started_services << name
true true
end end
def started?(name) def self.restart(name, file: nil, verbose: false)
args = ["services", "restart", name]
args << "--file=#{file}" if file
return unless Bundle.brew(*args, verbose:)
started_services << name
true
end
def self.started?(name)
started_services.include? name started_services.include? name
end end
def started_services def self.started_services
@started_services ||= begin @started_services ||= begin
states_to_skip = %w[stopped none] states_to_skip = %w[stopped none]
Utils.safe_popen_read(HOMEBREW_BREW_FILE, "services", "list").lines.filter_map do |line| Utils.safe_popen_read(HOMEBREW_BREW_FILE, "services", "list").lines.filter_map do |line|
@ -48,6 +63,23 @@ module Homebrew
end end
end end
end end
def self.versioned_service_file(name)
env_version = Bundle.formula_versions_from_env[name]
return if env_version.nil?
formula = Formula[name]
prefix = formula.rack/env_version
return unless prefix.directory?
service_file = if Homebrew::Services::System.launchctl?
prefix/"#{formula.plist_name}.plist"
else
prefix/"#{formula.service_name}.service"
end
service_file if service_file.file?
end
end end
end end
end end

View File

@ -47,7 +47,7 @@ module Homebrew
PATH_LIKE_ENV_REGEX = /.+#{File::PATH_SEPARATOR}/ PATH_LIKE_ENV_REGEX = /.+#{File::PATH_SEPARATOR}/
def self.run(*args, global: false, file: nil, subcommand: "") def self.run(*args, global: false, file: nil, subcommand: "", services: false)
# Cleanup Homebrew's global environment # Cleanup Homebrew's global environment
HOMEBREW_ENV_CLEANUP.each { |key| ENV.delete(key) } HOMEBREW_ENV_CLEANUP.each { |key| ENV.delete(key) }
@ -157,9 +157,137 @@ module Homebrew
return return
end end
if services
require "bundle/brew_services"
exit_code = 0
run_services(@dsl.entries) do
Kernel.system(*args)
exit_code = $CHILD_STATUS.exitstatus
end
exit!(exit_code)
else
exec(*args) exec(*args)
end end
end end
sig {
params(
entries: T::Array[Homebrew::Bundle::Dsl::Entry],
_block: T.proc.params(
info: T::Hash[String, T.anything],
service_file: Pathname,
conflicting_services: T::Array[T::Hash[String, T.anything]],
).void,
).void
}
private_class_method def self.map_service_info(entries, &_block)
entries_formulae = entries.filter_map do |entry|
next if entry.type != :brew
formula = Formula[entry.name]
next unless formula.any_version_installed?
[entry, formula]
end.to_h
# The formula + everything that could possible conflict with the service
names_to_query = entries_formulae.flat_map do |entry, formula|
[
formula.name,
*formula.versioned_formulae_names,
*formula.conflicts.map(&:name),
*entry.options[:conflicts_with],
]
end
# We parse from a command invocation so that brew wrappers can invoke special actions
# for the elevated nature of `brew services`
services_info = JSON.parse(
Utils.safe_popen_read(HOMEBREW_BREW_FILE, "services", "info", "--json", *names_to_query),
)
entries_formulae.filter_map do |entry, formula|
service_file = Bundle::BrewServices.versioned_service_file(entry.name)
unless service_file&.file?
prefix = formula.any_installed_prefix
next if prefix.nil?
service_file = if Homebrew::Services::System.launchctl?
prefix/"#{formula.plist_name}.plist"
else
prefix/"#{formula.service_name}.service"
end
end
next unless service_file.file?
info = services_info.find { |candidate| candidate["name"] == formula.name }
conflicting_services = services_info.select do |candidate|
next unless candidate["running"]
formula.versioned_formulae_names.include?(candidate["name"])
end
raise "Failed to get service info for #{entry.name}" if info.nil?
yield info, service_file, conflicting_services
end
end
sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry], _block: T.nilable(T.proc.void)).void }
private_class_method def self.run_services(entries, &_block)
services_to_restart = []
map_service_info(entries) do |info, service_file, conflicting_services|
if info["running"] && !Bundle::BrewServices.stop(info["name"], keep: true)
opoo "Failed to stop #{info["name"]} service"
end
conflicting_services.each do |conflict|
if Bundle::BrewServices.stop(conflict["name"], keep: true)
services_to_restart << conflict["name"] if conflict["registered"]
else
opoo "Failed to stop #{conflict["name"]} service"
end
end
unless Bundle::BrewServices.run(info["name"], file: service_file)
opoo "Failed to start #{info["name"]} service"
end
end
return unless block_given?
begin
yield
ensure
# Do a full re-evaluation of services instead state has changed
stop_services(entries)
services_to_restart.each do |service|
next if Bundle::BrewServices.run(service)
opoo "Failed to restart #{service} service"
end
end
end
sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry]).void }
private_class_method def self.stop_services(entries)
map_service_info(entries) do |info, _, _|
next unless info["loaded"]
# Try avoid services not started by `brew bundle services`
next if Homebrew::Services::System.launchctl? && info["registered"]
if info["running"] && !Bundle::BrewServices.stop(info["name"], keep: true)
opoo "Failed to stop #{info["name"]} service"
end
end
end
end
end end
end end
end end

View File

@ -83,6 +83,8 @@ module Homebrew
"even if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set. " "even if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set. "
switch "--install", switch "--install",
description: "Run `install` before continuing to other operations e.g. `exec`." description: "Run `install` before continuing to other operations e.g. `exec`."
switch "--services",
description: "Temporarily start services while running the `exec` or `sh` command."
switch "-f", "--force", switch "-f", "--force",
description: "`install` runs with `--force`/`--overwrite`. " \ description: "`install` runs with `--force`/`--overwrite`. " \
"`dump` overwrites an existing `Brewfile`. " \ "`dump` overwrites an existing `Brewfile`. " \
@ -133,7 +135,7 @@ module Homebrew
require "bundle" require "bundle"
subcommand = args.named.first.presence subcommand = args.named.first.presence
if ["exec", "add", "remove"].exclude?(subcommand) && args.named.size > 1 if %w[exec add remove].exclude?(subcommand) && args.named.size > 1
raise UsageError, "This command does not take more than 1 subcommand argument." raise UsageError, "This command does not take more than 1 subcommand argument."
end end
@ -232,7 +234,7 @@ module Homebrew
["env"] ["env"]
end end
require "bundle/commands/exec" require "bundle/commands/exec"
Homebrew::Bundle::Commands::Exec.run(*named_args, global:, file:, subcommand:) Homebrew::Bundle::Commands::Exec.run(*named_args, global:, file:, subcommand:, services: args.services?)
when "list" when "list"
require "bundle/commands/list" require "bundle/commands/list"
Homebrew::Bundle::Commands::List.run( Homebrew::Bundle::Commands::List.run(

View File

@ -38,13 +38,14 @@ module Homebrew
[`sudo`] `brew services start` (<formula>|`--all`|`--file=`): [`sudo`] `brew services start` (<formula>|`--all`|`--file=`):
Start the service <formula> immediately and register it to launch at login (or boot). Start the service <formula> immediately and register it to launch at login (or boot).
[`sudo`] `brew services stop` (<formula>|`--all`): [`sudo`] `brew services stop` (`--keep`) (`--no-wait`|`--max-wait=`) (<formula>|`--all`):
Stop the service <formula> immediately and unregister it from launching at login (or boot). Stop the service <formula> immediately and unregister it from launching at login (or boot),
unless `--keep` is specified.
[`sudo`] `brew services kill` (<formula>|`--all`): [`sudo`] `brew services kill` (<formula>|`--all`):
Stop the service <formula> immediately but keep it registered to launch at login (or boot). Stop the service <formula> immediately but keep it registered to launch at login (or boot).
[`sudo`] `brew services restart` (<formula>|`--all`): [`sudo`] `brew services restart` (<formula>|`--all`|`--file=`):
Stop (if necessary) and start the service <formula> immediately and register it to launch at login (or boot). Stop (if necessary) and start the service <formula> immediately and register it to launch at login (or boot).
[`sudo`] `brew services cleanup`: [`sudo`] `brew services cleanup`:
@ -57,6 +58,7 @@ module Homebrew
switch "--all", description: "Run <subcommand> on all services." switch "--all", description: "Run <subcommand> on all services."
switch "--json", description: "Output as JSON." switch "--json", description: "Output as JSON."
switch "--no-wait", description: "Don't wait for `stop` to finish stopping the service." switch "--no-wait", description: "Don't wait for `stop` to finish stopping the service."
switch "--keep", description: "When stopped, don't unregister the service from launching at login (or boot)."
conflicts "--max-wait=", "--no-wait" conflicts "--max-wait=", "--no-wait"
named_args named_args
end end
@ -108,6 +110,7 @@ module Homebrew
file_commands = [ file_commands = [
*Homebrew::Services::Commands::Start::TRIGGERS, *Homebrew::Services::Commands::Start::TRIGGERS,
*Homebrew::Services::Commands::Run::TRIGGERS, *Homebrew::Services::Commands::Run::TRIGGERS,
*Homebrew::Services::Commands::Restart::TRIGGERS,
] ]
if file_commands.exclude?(subcommand) if file_commands.exclude?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!" raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!"
@ -117,6 +120,14 @@ module Homebrew
end end
end end
unless Homebrew::Services::Commands::Stop::TRIGGERS.include?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept the --keep argument!" if args.keep?
raise UsageError, "The `#{subcommand}` subcommand does not accept the --no-wait argument!" if args.no_wait?
if args.max_wait
raise UsageError, "The `#{subcommand}` subcommand does not accept the --max-wait= argument!"
end
end
opoo "The --all argument overrides provided formula argument!" if formulae.present? && args.all? opoo "The --all argument overrides provided formula argument!" if formulae.present? && args.all?
targets = if args.all? targets = if args.all?
@ -156,14 +167,19 @@ module Homebrew
when *Homebrew::Services::Commands::Info::TRIGGERS when *Homebrew::Services::Commands::Info::TRIGGERS
Homebrew::Services::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?) Homebrew::Services::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?)
when *Homebrew::Services::Commands::Restart::TRIGGERS when *Homebrew::Services::Commands::Restart::TRIGGERS
Homebrew::Services::Commands::Restart.run(targets, verbose: args.verbose?) Homebrew::Services::Commands::Restart.run(targets, args.file, verbose: args.verbose?)
when *Homebrew::Services::Commands::Run::TRIGGERS when *Homebrew::Services::Commands::Run::TRIGGERS
Homebrew::Services::Commands::Run.run(targets, args.file, verbose: args.verbose?) Homebrew::Services::Commands::Run.run(targets, args.file, verbose: args.verbose?)
when *Homebrew::Services::Commands::Start::TRIGGERS when *Homebrew::Services::Commands::Start::TRIGGERS
Homebrew::Services::Commands::Start.run(targets, args.file, verbose: args.verbose?) Homebrew::Services::Commands::Start.run(targets, args.file, verbose: args.verbose?)
when *Homebrew::Services::Commands::Stop::TRIGGERS when *Homebrew::Services::Commands::Stop::TRIGGERS
max_wait = args.max_wait.to_f Homebrew::Services::Commands::Stop.run(
Homebrew::Services::Commands::Stop.run(targets, verbose: args.verbose?, no_wait: args.no_wait?, max_wait:) targets,
verbose: args.verbose?,
no_wait: args.no_wait?,
max_wait: args.max_wait.to_f,
keep: args.keep?,
)
when *Homebrew::Services::Commands::Kill::TRIGGERS when *Homebrew::Services::Commands::Kill::TRIGGERS
Homebrew::Services::Commands::Kill.run(targets, verbose: args.verbose?) Homebrew::Services::Commands::Kill.run(targets, verbose: args.verbose?)
else else

View File

@ -159,12 +159,13 @@ module Homebrew
verbose: T::Boolean, verbose: T::Boolean,
no_wait: T::Boolean, no_wait: T::Boolean,
max_wait: T.nilable(T.any(Integer, Float)), max_wait: T.nilable(T.any(Integer, Float)),
keep: T::Boolean,
).void ).void
} }
def self.stop(targets, verbose: false, no_wait: false, max_wait: 0) def self.stop(targets, verbose: false, no_wait: false, max_wait: 0, keep: false)
targets.each do |service| targets.each do |service|
unless service.loaded? unless service.loaded?
rm service.dest if service.dest.exist? # get rid of installed service file anyway, dude rm service.dest if !keep && service.dest.exist? # get rid of installed service file anyway, dude
if service.service_file_present? if service.service_file_present?
odie <<~EOS odie <<~EOS
Service `#{service.name}` is started as `#{service.owner}`. Try: Service `#{service.name}` is started as `#{service.owner}`. Try:
@ -188,7 +189,11 @@ module Homebrew
end end
if System.systemctl? if System.systemctl?
if keep
System::Systemctl.quiet_run(*systemctl_args, "stop", service.service_name)
else
System::Systemctl.quiet_run(*systemctl_args, "disable", "--now", service.service_name) System::Systemctl.quiet_run(*systemctl_args, "disable", "--now", service.service_name)
end
elsif System.launchctl? elsif System.launchctl?
quiet_system System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}" quiet_system System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}"
unless no_wait unless no_wait
@ -204,9 +209,11 @@ module Homebrew
quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" if service.pid? quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" if service.pid?
end end
unless keep
rm service.dest if service.dest.exist? rm service.dest if service.dest.exist?
# Run daemon-reload on systemctl to finish unloading stopped and deleted service. # Run daemon-reload on systemctl to finish unloading stopped and deleted service.
System::Systemctl.run(*systemctl_args, "daemon-reload") if System.systemctl? System::Systemctl.run(*systemctl_args, "daemon-reload") if System.systemctl?
end
if service.pid? || service.loaded? if service.pid? || service.loaded?
opoo "Unable to stop `#{service.name}` (label: #{service.service_name})" opoo "Unable to stop `#{service.name}` (label: #{service.service_name})"

View File

@ -53,6 +53,7 @@ module Homebrew
return out unless verbose return out unless verbose
out += "File: #{hash[:file]} #{pretty_bool(hash[:file].present?)}\n" out += "File: #{hash[:file]} #{pretty_bool(hash[:file].present?)}\n"
out += "Registered at login: #{pretty_bool(hash[:registered])}\n"
out += "Command: #{hash[:command]}\n" unless hash[:command].nil? out += "Command: #{hash[:command]}\n" unless hash[:command].nil?
out += "Working directory: #{hash[:working_dir]}\n" unless hash[:working_dir].nil? out += "Working directory: #{hash[:working_dir]}\n" unless hash[:working_dir].nil?
out += "Root directory: #{hash[:root_dir]}\n" unless hash[:root_dir].nil? out += "Root directory: #{hash[:root_dir]}\n" unless hash[:root_dir].nil?

View File

@ -14,8 +14,14 @@ module Homebrew
TRIGGERS = %w[restart relaunch reload r].freeze TRIGGERS = %w[restart relaunch reload r].freeze
sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T::Boolean).void } sig {
def self.run(targets, verbose:) params(
targets: T::Array[Services::FormulaWrapper],
custom_plist: T.nilable(String),
verbose: T::Boolean,
).void
}
def self.run(targets, custom_plist, verbose:)
Services::Cli.check(targets) Services::Cli.check(targets)
ran = [] ran = []
@ -30,8 +36,8 @@ module Homebrew
Services::Cli.stop([service], verbose:) if service.loaded? Services::Cli.stop([service], verbose:) if service.loaded?
end end
Services::Cli.run(targets, verbose:) if ran.present? Services::Cli.run(targets, custom_plist, verbose:) if ran.present?
Services::Cli.start(started, verbose:) if started.present? Services::Cli.start(started, custom_plist, verbose:) if started.present?
end end
end end
end end

View File

@ -15,11 +15,12 @@ module Homebrew
verbose: T::Boolean, verbose: T::Boolean,
no_wait: T::Boolean, no_wait: T::Boolean,
max_wait: T.nilable(Float), max_wait: T.nilable(Float),
keep: T::Boolean,
).void ).void
} }
def self.run(targets, verbose:, no_wait:, max_wait:) def self.run(targets, verbose:, no_wait:, max_wait:, keep:)
Services::Cli.check(targets) Services::Cli.check(targets)
Services::Cli.stop(targets, verbose:, no_wait:, max_wait:) Services::Cli.stop(targets, verbose:, no_wait:, max_wait:, keep:)
end end
end end
end end

View File

@ -201,6 +201,7 @@ module Homebrew
user: owner, user: owner,
status: status_symbol, status: status_symbol,
file: service_file_present? ? dest : service_file, file: service_file_present? ? dest : service_file,
registered: service_file_present?,
} }
return hash unless service? return hash unless service?

View File

@ -59,6 +59,9 @@ class Homebrew::Cmd::Bundle::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def no_vscode?; end def no_vscode?; end
sig { returns(T::Boolean) }
def services?; end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def tap?; end def tap?; end

View File

@ -20,6 +20,9 @@ class Homebrew::Cmd::Services::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def json?; end def json?; end
sig { returns(T::Boolean) }
def keep?; end
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def max_wait; end def max_wait; end

View File

@ -58,7 +58,7 @@ RSpec.describe Homebrew::Bundle::BrewInstaller do
context "with a successful installation" do context "with a successful installation" do
it "start service" do it "start service" do
expect(Homebrew::Bundle::BrewServices).to \ expect(Homebrew::Bundle::BrewServices).to \
receive(:start).with(formula_name, verbose: false).and_return(true) receive(:start).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.preinstall(formula_name, start_service: true) described_class.preinstall(formula_name, start_service: true)
described_class.install(formula_name, start_service: true) described_class.install(formula_name, start_service: true)
end end
@ -67,7 +67,7 @@ RSpec.describe Homebrew::Bundle::BrewInstaller do
context "with a skipped installation" do context "with a skipped installation" do
it "start service" do it "start service" do
expect(Homebrew::Bundle::BrewServices).to \ expect(Homebrew::Bundle::BrewServices).to \
receive(:start).with(formula_name, verbose: false).and_return(true) receive(:start).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.install(formula_name, preinstall: false, start_service: true) described_class.install(formula_name, preinstall: false, start_service: true)
end end
end end
@ -83,7 +83,7 @@ RSpec.describe Homebrew::Bundle::BrewInstaller do
context "with a successful installation" do context "with a successful installation" do
it "restart service" do it "restart service" do
expect(Homebrew::Bundle::BrewServices).to \ expect(Homebrew::Bundle::BrewServices).to \
receive(:restart).with(formula_name, verbose: false).and_return(true) receive(:restart).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.preinstall(formula_name, restart_service: :always) described_class.preinstall(formula_name, restart_service: :always)
described_class.install(formula_name, restart_service: :always) described_class.install(formula_name, restart_service: :always)
end end
@ -92,7 +92,7 @@ RSpec.describe Homebrew::Bundle::BrewInstaller do
context "with a skipped installation" do context "with a skipped installation" do
it "restart service" do it "restart service" do
expect(Homebrew::Bundle::BrewServices).to \ expect(Homebrew::Bundle::BrewServices).to \
receive(:restart).with(formula_name, verbose: false).and_return(true) receive(:restart).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.install(formula_name, preinstall: false, restart_service: :always) described_class.install(formula_name, preinstall: false, restart_service: :always)
end end
end end
@ -201,7 +201,8 @@ RSpec.describe Homebrew::Bundle::BrewInstaller do
verbose:).and_return(true) verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true) expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true) expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, verbose:).and_return(true) expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, file: nil,
verbose:).and_return(true)
described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"]) described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"])
described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"]) described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"])
end end
@ -216,7 +217,8 @@ RSpec.describe Homebrew::Bundle::BrewInstaller do
verbose:).and_return(true) verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true) expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true) expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, verbose:).and_return(true) expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, file: nil,
verbose:).and_return(true)
described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true) described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true)
described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true) described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true)
end end

View File

@ -46,6 +46,14 @@ RSpec.describe Homebrew::Bundle::BrewServices do
expect(described_class.started_services).to include("nginx") expect(described_class.started_services).to include("nginx")
end end
it "runs the service" do
allow(described_class).to receive(:started_services).and_return([])
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "services", "run", "nginx",
verbose: false).and_return(true)
expect(described_class.run("nginx")).to be(true)
expect(described_class.started_services).to include("nginx")
end
it "restarts the service" do it "restarts the service" do
allow(described_class).to receive(:started_services).and_return([]) allow(described_class).to receive(:started_services).and_return([])
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "services", "restart", "nginx", expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "services", "restart", "nginx",
@ -54,4 +62,32 @@ RSpec.describe Homebrew::Bundle::BrewServices do
expect(described_class.started_services).to include("nginx") expect(described_class.started_services).to include("nginx")
end end
end end
describe ".versioned_service_file" do
let(:foo) do
instance_double(
Formula,
name: "fooformula",
version: "1.0",
rack: HOMEBREW_CELLAR/"fooformula",
plist_name: "homebrew.mxcl.fooformula",
)
end
it "returns the versioned service file" do
expect(Formula).to receive(:[]).with(foo.name).and_return(foo)
expect(Homebrew::Bundle).to receive(:formula_versions_from_env).and_return(foo.name => foo.version)
prefix = foo.rack/"1.0"
allow(FileTest).to receive(:directory?).and_call_original
expect(FileTest).to receive(:directory?).with(prefix.to_s).and_return(true)
service_file = prefix/"#{foo.plist_name}.plist"
expect(Homebrew::Services::System).to receive(:launchctl?).and_return(true)
allow(FileTest).to receive(:file?).and_call_original
expect(FileTest).to receive(:file?).with(service_file.to_s).and_return(true)
expect(described_class.versioned_service_file(foo.name)).to eq(service_file)
end
end
end end

View File

@ -89,7 +89,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
it "returns verbose output" do it "returns verbose output" do
out = "service ()\nRunning: true\n" out = "service ()\nRunning: true\n"
out += "Loaded: true\nSchedulable: false\n" out += "Loaded: true\nSchedulable: false\n"
out += "User: user\nPID: 42\nFile: /dev/null true\nCommand: /bin/command\n" out += "User: user\nPID: 42\nFile: /dev/null true\nRegistered at login: true\nCommand: /bin/command\n"
out += "Working directory: /working/dir\nRoot directory: /root/dir\nLog: /log/dir\nError log: /log/dir/error\n" out += "Working directory: /working/dir\nRoot directory: /root/dir\nLog: /log/dir\nError log: /log/dir/error\n"
out += "Interval: 3600s\nCron: 5 * * * *\n" out += "Interval: 3600s\nCron: 5 * * * *\n"
formula = { formula = {
@ -97,6 +97,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
user: "user", user: "user",
status: :started, status: :started,
file: "/dev/null", file: "/dev/null",
registered: true,
running: true, running: true,
loaded: true, loaded: true,
schedulable: false, schedulable: false,

View File

@ -12,7 +12,7 @@ RSpec.describe Homebrew::Services::Commands::Restart do
describe "#run" do describe "#run" do
it "fails with empty list" do it "fails with empty list" do
expect do expect do
described_class.run([], verbose: false) described_class.run([], nil, verbose: false)
end.to raise_error UsageError, "Invalid usage: Formula(e) missing, please provide a formula name or use --all" end.to raise_error UsageError, "Invalid usage: Formula(e) missing, please provide a formula name or use --all"
end end
@ -21,7 +21,7 @@ RSpec.describe Homebrew::Services::Commands::Restart do
expect(Homebrew::Services::Cli).not_to receive(:stop) expect(Homebrew::Services::Cli).not_to receive(:stop)
expect(Homebrew::Services::Cli).to receive(:start).once expect(Homebrew::Services::Cli).to receive(:start).once
service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: false) service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: false)
expect { described_class.run([service], verbose: false) }.not_to raise_error expect { described_class.run([service], nil, verbose: false) }.not_to raise_error
end end
it "starts if services are loaded with file" do it "starts if services are loaded with file" do
@ -30,7 +30,7 @@ RSpec.describe Homebrew::Services::Commands::Restart do
expect(Homebrew::Services::Cli).to receive(:stop).once expect(Homebrew::Services::Cli).to receive(:stop).once
service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: true, service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: true,
service_file_present?: true) service_file_present?: true)
expect { described_class.run([service], verbose: false) }.not_to raise_error expect { described_class.run([service], nil, verbose: false) }.not_to raise_error
end end
it "runs if services are loaded without file" do it "runs if services are loaded without file" do
@ -39,7 +39,7 @@ service_file_present?: true)
expect(Homebrew::Services::Cli).to receive(:stop).once expect(Homebrew::Services::Cli).to receive(:stop).once
service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: true, service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: true,
service_file_present?: false) service_file_present?: false)
expect { described_class.run([service], verbose: false) }.not_to raise_error expect { described_class.run([service], nil, verbose: false) }.not_to raise_error
end end
end end
end end

View File

@ -371,6 +371,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
loaded: false, loaded: false,
name: "mysql", name: "mysql",
pid: nil, pid: nil,
registered: false,
running: false, running: false,
schedulable: nil, schedulable: nil,
service_name: "plist-mysql-test", service_name: "plist-mysql-test",
@ -384,13 +385,14 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false)
expect(service).to receive(:service?).twice.and_return(false) expect(service).to receive(:service?).twice.and_return(false)
expect(service).to receive(:service_file_present?).and_return(true) expect(service).to receive(:service_file_present?).twice.and_return(true)
expected = { expected = {
exit_code: nil, exit_code: nil,
file: Pathname.new("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist"), file: Pathname.new("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist"),
loaded: false, loaded: false,
name: "mysql", name: "mysql",
pid: nil, pid: nil,
registered: true,
running: false, running: false,
schedulable: nil, schedulable: nil,
service_name: "plist-mysql-test", service_name: "plist-mysql-test",
@ -404,7 +406,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false)
expect(service).to receive(:service?).twice.and_return(true) expect(service).to receive(:service?).twice.and_return(true)
expect(service).to receive(:service_file_present?).and_return(true) expect(service).to receive(:service_file_present?).twice.and_return(true)
expect(service).to receive(:load_service).twice.and_return(service_object) expect(service).to receive(:load_service).twice.and_return(service_object)
expected = { expected = {
command: "/bin/cmd", command: "/bin/cmd",
@ -417,6 +419,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
log_path: nil, log_path: nil,
name: "mysql", name: "mysql",
pid: nil, pid: nil,
registered: true,
root_dir: nil, root_dir: nil,
running: false, running: false,
schedulable: false, schedulable: false,