Fold brew bundle services into exec

This commit is contained in:
Bo Anderson 2025-03-28 05:39:46 +00:00
parent c7e8b66da3
commit 650f62bcba
No known key found for this signature in database
3 changed files with 120 additions and 156 deletions

View File

@ -158,10 +158,10 @@ module Homebrew
end end
if services if services
require "bundle/commands/services" require "bundle/brew_services"
exit_code = 0 exit_code = 0
Services.run_services(@dsl.entries) do run_services(@dsl.entries) do
Kernel.system(*args) Kernel.system(*args)
exit_code = $CHILD_STATUS.exitstatus exit_code = $CHILD_STATUS.exitstatus
end end
@ -170,6 +170,123 @@ module Homebrew
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

View File

@ -1,143 +0,0 @@
# typed: strict
# frozen_string_literal: true
require "bundle/brewfile"
require "bundle/brew_services"
require "formula"
module Homebrew
module Bundle
module Commands
module Services
sig { params(args: String, global: T::Boolean, file: T.nilable(String)).void }
def self.run(*args, global:, file:)
raise UsageError, "invalid `brew bundle services` arguments" if args.length != 1
parsed_entries = Brewfile.read(global:, file:).entries
subcommand = args.first
case subcommand
when "run"
run_services(parsed_entries)
when "stop"
stop_services(parsed_entries)
else
raise UsageError, "unknown bundle services subcommand: #{subcommand}"
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_entries(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 }
def self.run_services(entries, &_block)
map_entries(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 conflict["running"] && !Bundle::BrewServices.stop(conflict["name"], keep: true)
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
return unless block_given?
begin
yield
ensure
stop_services(entries)
conflicting_services.each do |conflict|
if conflict["running"] && conflict["registered"] && !Bundle::BrewServices.run(conflict["name"])
opoo "Failed to restart #{conflict["name"]} service"
end
end
end
end
end
sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry]).void }
def self.stop_services(entries)
map_entries(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

View File

@ -61,12 +61,6 @@ module Homebrew
`brew bundle env`: `brew bundle env`:
Print the environment variables that would be set in a `brew bundle exec` environment. Print the environment variables that would be set in a `brew bundle exec` environment.
`brew bundle services run`:
Start services for formulae specified in the `Brewfile`.
`brew bundle services stop`:
Stop services for formulae specified in the `Brewfile`.
EOS EOS
flag "--file=", flag "--file=",
description: "Read from or write to the `Brewfile` from this location. " \ description: "Read from or write to the `Brewfile` from this location. " \
@ -141,7 +135,7 @@ module Homebrew
require "bundle" require "bundle"
subcommand = args.named.first.presence subcommand = args.named.first.presence
if %w[exec add remove services].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
@ -281,10 +275,6 @@ module Homebrew
require "bundle/commands/remove" require "bundle/commands/remove"
Homebrew::Bundle::Commands::Remove.run(*named_args, type: selected_types.first, global:, file:) Homebrew::Bundle::Commands::Remove.run(*named_args, type: selected_types.first, global:, file:)
end end
when "services"
_, *named_args = args.named
require "bundle/commands/services"
Homebrew::Bundle::Commands::Services.run(*named_args, global:, file:)
else else
raise UsageError, "unknown subcommand: #{subcommand}" raise UsageError, "unknown subcommand: #{subcommand}"
end end