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`:
|
`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. " \
|
||||||
@ -133,7 +139,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 services].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
|
||||||
|
|
||||||
@ -273,6 +279,10 @@ 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
|
||||||
|
|||||||
@ -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?
|
||||||
|
|||||||
@ -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?
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user