Add brew bundle services helper
This commit is contained in:
parent
0b58e8fd37
commit
615fb764a1
30
Library/Homebrew/bundle/commands/services.rb
Normal file
30
Library/Homebrew/bundle/commands/services.rb
Normal file
@ -0,0 +1,30 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle/brewfile"
|
||||
require "bundle/services"
|
||||
|
||||
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"
|
||||
Homebrew::Bundle::Services.run(parsed_entries)
|
||||
when "stop"
|
||||
Homebrew::Bundle::Services.stop(parsed_entries)
|
||||
else
|
||||
raise UsageError, "unknown bundle services subcommand: #{subcommand}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
Library/Homebrew/bundle/services.rb
Normal file
112
Library/Homebrew/bundle/services.rb
Normal file
@ -0,0 +1,112 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle/dsl"
|
||||
require "formula"
|
||||
require "services/system"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module Services
|
||||
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)
|
||||
formula_versions = Bundle.formula_versions_from_env
|
||||
|
||||
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|
|
||||
version = formula_versions[entry.name.downcase]
|
||||
prefix = formula.rack/version if version
|
||||
|
||||
service_file = if prefix&.directory?
|
||||
if Homebrew::Services::System.launchctl?
|
||||
prefix/"#{formula.plist_name}.plist"
|
||||
else
|
||||
prefix/"#{formula.service_name}.service"
|
||||
end
|
||||
end
|
||||
|
||||
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]).void }
|
||||
def self.run(entries)
|
||||
map_entries(entries) do |info, service_file, conflicting_services|
|
||||
safe_system HOMEBREW_BREW_FILE, "services", "stop", "--keep", info["name"] if info["running"]
|
||||
conflicting_services.each do |conflicting_service|
|
||||
safe_system HOMEBREW_BREW_FILE, "services", "stop", "--keep", conflicting_service["name"]
|
||||
end
|
||||
|
||||
safe_system HOMEBREW_BREW_FILE, "services", "run", "--file=#{service_file}", info["name"]
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry]).void }
|
||||
def self.stop(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"]
|
||||
|
||||
safe_system HOMEBREW_BREW_FILE, "services", "stop", info["name"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -61,6 +61,12 @@ module Homebrew
|
||||
|
||||
`brew bundle env`:
|
||||
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
|
||||
flag "--file=",
|
||||
description: "Read from or write to the `Brewfile` from this location. " \
|
||||
@ -133,7 +139,7 @@ module Homebrew
|
||||
require "bundle"
|
||||
|
||||
subcommand = args.named.first.presence
|
||||
if ["exec", "add", "remove"].exclude?(subcommand) && args.named.size > 1
|
||||
if %w[exec add remove services].exclude?(subcommand) && args.named.size > 1
|
||||
raise UsageError, "This command does not take more than 1 subcommand argument."
|
||||
end
|
||||
|
||||
@ -273,6 +279,10 @@ module Homebrew
|
||||
require "bundle/commands/remove"
|
||||
Homebrew::Bundle::Commands::Remove.run(*named_args, type: selected_types.first, global:, file:)
|
||||
end
|
||||
when "services"
|
||||
_, *named_args = args.named
|
||||
require "bundle/commands/services"
|
||||
Homebrew::Bundle::Commands::Services.run(*named_args, global:, file:)
|
||||
else
|
||||
raise UsageError, "unknown subcommand: #{subcommand}"
|
||||
end
|
||||
|
||||
@ -53,6 +53,7 @@ module Homebrew
|
||||
return out unless verbose
|
||||
|
||||
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 += "Working directory: #{hash[:working_dir]}\n" unless hash[:working_dir].nil?
|
||||
out += "Root directory: #{hash[:root_dir]}\n" unless hash[:root_dir].nil?
|
||||
|
||||
@ -201,6 +201,7 @@ module Homebrew
|
||||
user: owner,
|
||||
status: status_symbol,
|
||||
file: service_file_present? ? dest : service_file,
|
||||
registered: service_file_present?,
|
||||
}
|
||||
|
||||
return hash unless service?
|
||||
|
||||
@ -89,7 +89,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
|
||||
it "returns verbose output" do
|
||||
out = "service ()\nRunning: true\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 += "Interval: 3600s\nCron: 5 * * * *\n"
|
||||
formula = {
|
||||
@ -97,6 +97,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
|
||||
user: "user",
|
||||
status: :started,
|
||||
file: "/dev/null",
|
||||
registered: true,
|
||||
running: true,
|
||||
loaded: true,
|
||||
schedulable: false,
|
||||
|
||||
@ -371,6 +371,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
|
||||
loaded: false,
|
||||
name: "mysql",
|
||||
pid: nil,
|
||||
registered: false,
|
||||
running: false,
|
||||
schedulable: nil,
|
||||
service_name: "plist-mysql-test",
|
||||
@ -384,13 +385,14 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
|
||||
ENV["HOME"] = "/tmp_home"
|
||||
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_file_present?).and_return(true)
|
||||
expect(service).to receive(:service_file_present?).twice.and_return(true)
|
||||
expected = {
|
||||
exit_code: nil,
|
||||
file: Pathname.new("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist"),
|
||||
loaded: false,
|
||||
name: "mysql",
|
||||
pid: nil,
|
||||
registered: true,
|
||||
running: false,
|
||||
schedulable: nil,
|
||||
service_name: "plist-mysql-test",
|
||||
@ -404,7 +406,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
|
||||
ENV["HOME"] = "/tmp_home"
|
||||
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_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)
|
||||
expected = {
|
||||
command: "/bin/cmd",
|
||||
@ -417,6 +419,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
|
||||
log_path: nil,
|
||||
name: "mysql",
|
||||
pid: nil,
|
||||
registered: true,
|
||||
root_dir: nil,
|
||||
running: false,
|
||||
schedulable: false,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user