Cleanup and fix homebrew-services migration

This commit is contained in:
Mike McQuaid 2025-03-13 14:50:03 +00:00
parent 3ef52e4844
commit 084ddca27a
No known key found for this signature in database
32 changed files with 311 additions and 368 deletions

View File

@ -12,11 +12,6 @@
"permissions": { "permissions": {
"contents": "write" "contents": "write"
} }
},
"Homebrew/homebrew-services": {
"permissions": {
"contents": "write"
}
} }
} }
}, },

View File

@ -25,7 +25,6 @@ brew cleanup
brew tap --force homebrew/core brew tap --force homebrew/core
# tap some other repos so codespaces can be used for developing multiple taps # tap some other repos so codespaces can be used for developing multiple taps
brew tap homebrew/bundle brew tap homebrew/bundle
brew tap homebrew/services
# install some useful development things # install some useful development things
sudo apt-get update sudo apt-get update

View File

@ -2,7 +2,16 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "services/service" require "services/system"
require "services/cli"
require "services/commands/list"
require "services/commands/cleanup"
require "services/commands/info"
require "services/commands/restart"
require "services/commands/run"
require "services/commands/start"
require "services/commands/stop"
require "services/commands/kill"
module Homebrew module Homebrew
module Cmd module Cmd
@ -64,36 +73,36 @@ module Homebrew
# Keep this after the .parse to keep --help fast. # Keep this after the .parse to keep --help fast.
require "utils" require "utils"
if !::Service::System.launchctl? && !::Service::System.systemctl? if !::Services::System.launchctl? && !::Services::System.systemctl?
raise UsageError, raise UsageError,
"`brew services` is supported only on macOS or Linux (with systemd)!" "`brew services` is supported only on macOS or Linux (with systemd)!"
end end
if (sudo_service_user = args.sudo_service_user) if (sudo_service_user = args.sudo_service_user)
unless ::Service::System.root? unless ::Services::System.root?
raise UsageError, raise UsageError,
"`brew services` is supported only when running as root!" "`brew services` is supported only when running as root!"
end end
unless ::Service::System.launchctl? unless ::Services::System.launchctl?
raise UsageError, raise UsageError,
"`brew services --sudo-service-user` is currently supported only on macOS " \ "`brew services --sudo-service-user` is currently supported only on macOS " \
"(but we'd love a PR to add Linux support)!" "(but we'd love a PR to add Linux support)!"
end end
::Service::ServicesCli.sudo_service_user = sudo_service_user ::Services::Cli.sudo_service_user = sudo_service_user
end end
# Parse arguments. # Parse arguments.
subcommand, formula, = args.named subcommand, formula, = args.named
if [*::Service::Commands::List::TRIGGERS, *::Service::Commands::Cleanup::TRIGGERS].include?(subcommand) if [*::Services::Commands::List::TRIGGERS, *::Services::Commands::Cleanup::TRIGGERS].include?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept a formula argument!" if formula raise UsageError, "The `#{subcommand}` subcommand does not accept a formula argument!" if formula
raise UsageError, "The `#{subcommand}` subcommand does not accept the --all argument!" if args.all? raise UsageError, "The `#{subcommand}` subcommand does not accept the --all argument!" if args.all?
end end
if args.file if args.file
if ::Service::Commands::Start::TRIGGERS.exclude?(subcommand) if ::Services::Commands::Start::TRIGGERS.exclude?(subcommand)
raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!" raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!"
elsif args.all? elsif args.all?
raise UsageError, "The start subcommand does not accept the --all and --file= arguments at the same time!" raise UsageError, "The start subcommand does not accept the --all and --file= arguments at the same time!"
@ -104,14 +113,14 @@ module Homebrew
targets = if args.all? targets = if args.all?
if subcommand == "start" if subcommand == "start"
::Service::Formulae.available_services(loaded: false, skip_root: !::Service::System.root?) ::Services::Formulae.available_services(loaded: false, skip_root: !::Services::System.root?)
elsif subcommand == "stop" elsif subcommand == "stop"
::Service::Formulae.available_services(loaded: true, skip_root: !::Service::System.root?) ::Services::Formulae.available_services(loaded: true, skip_root: !::Services::System.root?)
else else
::Service::Formulae.available_services ::Services::Formulae.available_services
end end
elsif formula elsif formula
[::Service::FormulaWrapper.new(Formulary.factory(formula))] [::Services::FormulaWrapper.new(Formulary.factory(formula))]
else else
[] []
end end
@ -119,30 +128,30 @@ module Homebrew
# Exit successfully if --all was used but there is nothing to do # Exit successfully if --all was used but there is nothing to do
return if args.all? && targets.empty? return if args.all? && targets.empty?
if ::Service::System.systemctl? if ::Services::System.systemctl?
ENV["DBUS_SESSION_BUS_ADDRESS"] = ENV.fetch("HOMEBREW_DBUS_SESSION_BUS_ADDRESS", nil) ENV["DBUS_SESSION_BUS_ADDRESS"] = ENV.fetch("HOMEBREW_DBUS_SESSION_BUS_ADDRESS", nil)
ENV["XDG_RUNTIME_DIR"] = ENV.fetch("HOMEBREW_XDG_RUNTIME_DIR", nil) ENV["XDG_RUNTIME_DIR"] = ENV.fetch("HOMEBREW_XDG_RUNTIME_DIR", nil)
end end
# Dispatch commands and aliases. # Dispatch commands and aliases.
case subcommand.presence case subcommand.presence
when *::Service::Commands::List::TRIGGERS when *::Services::Commands::List::TRIGGERS
::Service::Commands::List.run(json: args.json?) ::Services::Commands::List.run(json: args.json?)
when *::Service::Commands::Cleanup::TRIGGERS when *::Services::Commands::Cleanup::TRIGGERS
::Service::Commands::Cleanup.run ::Services::Commands::Cleanup.run
when *::Service::Commands::Info::TRIGGERS when *::Services::Commands::Info::TRIGGERS
::Service::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?) ::Services::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?)
when *::Service::Commands::Restart::TRIGGERS when *::Services::Commands::Restart::TRIGGERS
::Service::Commands::Restart.run(targets, verbose: args.verbose?) ::Services::Commands::Restart.run(targets, verbose: args.verbose?)
when *::Service::Commands::Run::TRIGGERS when *::Services::Commands::Run::TRIGGERS
::Service::Commands::Run.run(targets, verbose: args.verbose?) ::Services::Commands::Run.run(targets, verbose: args.verbose?)
when *::Service::Commands::Start::TRIGGERS when *::Services::Commands::Start::TRIGGERS
::Service::Commands::Start.run(targets, args.file, verbose: args.verbose?) ::Services::Commands::Start.run(targets, args.file, verbose: args.verbose?)
when *::Service::Commands::Stop::TRIGGERS when *::Services::Commands::Stop::TRIGGERS
max_wait = args.max_wait.to_f max_wait = args.max_wait.to_f
::Service::Commands::Stop.run(targets, verbose: args.verbose?, no_wait: args.no_wait?, max_wait:) ::Services::Commands::Stop.run(targets, verbose: args.verbose?, no_wait: args.no_wait?, max_wait:)
when *::Service::Commands::Kill::TRIGGERS when *::Services::Commands::Kill::TRIGGERS
::Service::Commands::Kill.run(targets, verbose: args.verbose?) ::Services::Commands::Kill.run(targets, verbose: args.verbose?)
else else
raise UsageError, "unknown subcommand: `#{subcommand}`" raise UsageError, "unknown subcommand: `#{subcommand}`"
end end

View File

@ -1,8 +1,8 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service module Services
module ServicesCli module Cli
extend FileUtils extend FileUtils
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
@ -46,7 +46,7 @@ module Service
end end
# Kill services that don't have a service file # Kill services that don't have a service file
sig { returns(T::Array[Service::FormulaWrapper]) } sig { returns(T::Array[Services::FormulaWrapper]) }
def self.kill_orphaned_services def self.kill_orphaned_services
cleaned_labels = [] cleaned_labels = []
cleaned_services = [] cleaned_services = []
@ -79,7 +79,7 @@ module Service
end end
# Run a service as defined in the formula. This does not clean the service file like `start` does. # Run a service as defined in the formula. This does not clean the service file like `start` does.
sig { params(targets: T::Array[Service::FormulaWrapper], verbose: T.nilable(T::Boolean)).void } sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
def self.run(targets, verbose: false) def self.run(targets, verbose: false)
targets.each do |service| targets.each do |service|
if service.pid? if service.pid?
@ -96,7 +96,7 @@ module Service
# Start a service. # Start a service.
sig { sig {
params(targets: T::Array[Service::FormulaWrapper], service_file: T.nilable(T.any(String, Pathname)), params(targets: T::Array[Services::FormulaWrapper], service_file: T.nilable(T.any(String, Pathname)),
verbose: T.nilable(T::Boolean)).void verbose: T.nilable(T::Boolean)).void
} }
def self.start(targets, service_file = nil, verbose: false) def self.start(targets, service_file = nil, verbose: false)
@ -138,7 +138,7 @@ module Service
# Stop a service and unload it. # Stop a service and unload it.
sig { sig {
params(targets: T::Array[Service::FormulaWrapper], params(targets: T::Array[Services::FormulaWrapper],
verbose: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean),
no_wait: T.nilable(T::Boolean), no_wait: T.nilable(T::Boolean),
max_wait: T.nilable(T.any(Integer, Float))).void max_wait: T.nilable(T.any(Integer, Float))).void
@ -199,7 +199,7 @@ module Service
end end
# Stop a service but keep it registered. # Stop a service but keep it registered.
sig { params(targets: T::Array[Service::FormulaWrapper], verbose: T.nilable(T::Boolean)).void } sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
def self.kill(targets, verbose: false) def self.kill(targets, verbose: false)
targets.each do |service| targets.each do |service|
if !service.pid? if !service.pid?
@ -209,7 +209,7 @@ module Service
else else
puts "Killing `#{service.name}`... (might take a while)" puts "Killing `#{service.name}`... (might take a while)"
if System.systemctl? if System.systemctl?
System::Systemctl.quiet_run("stop", T.must(service.service_name)) System::Systemctl.quiet_run("stop", service.service_name)
elsif System.launchctl? elsif System.launchctl?
quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}"
end end
@ -290,7 +290,7 @@ module Service
end end
sig { sig {
params(service: Service::FormulaWrapper, file: T.nilable(T.any(String, Pathname)), params(service: Services::FormulaWrapper, file: T.nilable(T.any(String, Pathname)),
enable: T.nilable(T::Boolean)).void enable: T.nilable(T::Boolean)).void
} }
def self.launchctl_load(service, file:, enable:) def self.launchctl_load(service, file:, enable:)
@ -298,13 +298,13 @@ module Service
safe_system System.launchctl, "bootstrap", System.domain_target, file safe_system System.launchctl, "bootstrap", System.domain_target, file
end end
sig { params(service: Service::FormulaWrapper, enable: T.nilable(T::Boolean)).void } sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void }
def self.systemd_load(service, enable:) def self.systemd_load(service, enable:)
System::Systemctl.run("start", T.must(service.service_name)) System::Systemctl.run("start", T.must(service.service_name))
System::Systemctl.run("enable", T.must(service.service_name)) if enable System::Systemctl.run("enable", T.must(service.service_name)) if enable
end end
sig { params(service: Service::FormulaWrapper, enable: T.nilable(T::Boolean)).void } sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void }
def self.service_load(service, enable:) def self.service_load(service, enable:)
if System.root? && !service.service_startup? if System.root? && !service.service_startup?
opoo "#{service.name} must be run as non-root to start at user login!" opoo "#{service.name} must be run as non-root to start at user login!"
@ -326,7 +326,7 @@ module Service
ohai("Successfully #{function} `#{service.name}` (label: #{service.service_name})") ohai("Successfully #{function} `#{service.name}` (label: #{service.service_name})")
end end
sig { params(service: Service::FormulaWrapper, file: T.nilable(Pathname)).void } sig { params(service: Services::FormulaWrapper, file: T.nilable(Pathname)).void }
def self.install_service_file(service, file) def self.install_service_file(service, file)
raise UsageError, "Formula `#{service.name}` is not installed" unless service.installed? raise UsageError, "Formula `#{service.name}` is not installed" unless service.installed?
@ -339,9 +339,9 @@ module Service
temp << if T.must(file).blank? temp << if T.must(file).blank?
contents = T.must(service.service_file).read contents = T.must(service.service_file).read
if sudo_service_user && Service::System.launchctl? if sudo_service_user && Services::System.launchctl?
# set the username in the new plist file # set the username in the new plist file
ohai "Setting username in #{service.service_name} to #{Service::System.user}" ohai "Setting username in #{service.service_name} to #{Services::System.user}"
plist_data = Plist.parse_xml(contents, marshal: false) plist_data = Plist.parse_xml(contents, marshal: false)
plist_data["UserName"] = sudo_service_user plist_data["UserName"] = sudo_service_user
plist_data.to_plist plist_data.to_plist
@ -362,7 +362,7 @@ module Service
chmod 0644, service.dest chmod 0644, service.dest
Service::System::Systemctl.run("daemon-reload") if System.systemctl? Services::System::Systemctl.run("daemon-reload") if System.systemctl?
end end
end end
end end

View File

@ -1,7 +1,7 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service module Services
module Commands module Commands
module Cleanup module Cleanup
TRIGGERS = %w[cleanup clean cl rm].freeze TRIGGERS = %w[cleanup clean cl rm].freeze
@ -10,8 +10,8 @@ module Service
def self.run def self.run
cleaned = [] cleaned = []
cleaned += Service::ServicesCli.kill_orphaned_services cleaned += Services::Cli.kill_orphaned_services
cleaned += Service::ServicesCli.remove_unused_service_files cleaned += Services::Cli.remove_unused_service_files
puts "All #{System.root? ? "root" : "user-space"} services OK, nothing cleaned..." if cleaned.empty? puts "All #{System.root? ? "root" : "user-space"} services OK, nothing cleaned..." if cleaned.empty?
end end

View File

@ -1,17 +1,20 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service require "services/formula_wrapper"
require "services/cli"
module Services
module Commands module Commands
module Info module Info
TRIGGERS = %w[info i].freeze TRIGGERS = %w[info i].freeze
sig { sig {
params(targets: T::Array[Service::FormulaWrapper], verbose: T.nilable(T::Boolean), params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean),
json: T.nilable(T::Boolean)).void json: T.nilable(T::Boolean)).void
} }
def self.run(targets, verbose:, json:) def self.run(targets, verbose:, json:)
Service::ServicesCli.check(targets) Services::Cli.check(targets)
output = targets.map(&:to_hash) output = targets.map(&:to_hash)

View File

@ -0,0 +1,16 @@
# typed: strict
# frozen_string_literal: true
module Services
module Commands
module Kill
TRIGGERS = %w[kill k].freeze
sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
def self.run(targets, verbose:)
Services::Cli.check(targets)
Services::Cli.kill(targets, verbose:)
end
end
end
end

View File

@ -1,9 +1,10 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "service/formulae" require "services/formulae"
require "services/cli"
module Service module Services
module Commands module Commands
module List module List
TRIGGERS = [nil, "list", "ls"].freeze TRIGGERS = [nil, "list", "ls"].freeze
@ -12,7 +13,7 @@ module Service
def self.run(json: false) def self.run(json: false)
formulae = Formulae.services_list formulae = Formulae.services_list
if formulae.blank? if formulae.blank?
opoo "No services available to control with `#{Service::ServicesCli.bin}`" if $stderr.tty? opoo "No services available to control with `#{Services::Cli.bin}`" if $stderr.tty?
return return
end end

View File

@ -1,7 +1,7 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service module Services
module Commands module Commands
module Restart module Restart
# NOTE: The restart command is used to update service files # NOTE: The restart command is used to update service files
@ -11,9 +11,9 @@ module Service
TRIGGERS = %w[restart relaunch reload r].freeze TRIGGERS = %w[restart relaunch reload r].freeze
sig { params(targets: T::Array[Service::FormulaWrapper], verbose: T.nilable(T::Boolean)).returns(NilClass) } sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).returns(NilClass) }
def self.run(targets, verbose:) def self.run(targets, verbose:)
Service::ServicesCli.check(targets) Services::Cli.check(targets)
ran = [] ran = []
started = [] started = []
@ -24,11 +24,11 @@ module Service
# group not-started services with started ones for restart # group not-started services with started ones for restart
started << service started << service
end end
Service::ServicesCli.stop([service], verbose:) if service.loaded? Services::Cli.stop([service], verbose:) if service.loaded?
end end
Service::ServicesCli.run(targets, verbose:) if ran.present? Services::Cli.run(targets, verbose:) if ran.present?
Service::ServicesCli.start(started, verbose:) if started.present? Services::Cli.start(started, verbose:) if started.present?
nil nil
end end
end end

View File

@ -0,0 +1,16 @@
# typed: strict
# frozen_string_literal: true
module Services
module Commands
module Run
TRIGGERS = ["run"].freeze
sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
def self.run(targets, verbose:)
Services::Cli.check(targets)
Services::Cli.run(targets, verbose:)
end
end
end
end

View File

@ -1,18 +1,18 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service module Services
module Commands module Commands
module Start module Start
TRIGGERS = %w[start launch load s l].freeze TRIGGERS = %w[start launch load s l].freeze
sig { sig {
params(targets: T::Array[Service::FormulaWrapper], custom_plist: T.nilable(String), params(targets: T::Array[Services::FormulaWrapper], custom_plist: T.nilable(String),
verbose: T.nilable(T::Boolean)).void verbose: T.nilable(T::Boolean)).void
} }
def self.run(targets, custom_plist, verbose:) def self.run(targets, custom_plist, verbose:)
Service::ServicesCli.check(targets) Services::Cli.check(targets)
Service::ServicesCli.start(targets, custom_plist, verbose:) Services::Cli.start(targets, custom_plist, verbose:)
end end
end end
end end

View File

@ -1,20 +1,20 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service module Services
module Commands module Commands
module Stop module Stop
TRIGGERS = %w[stop unload terminate term t u].freeze TRIGGERS = %w[stop unload terminate term t u].freeze
sig { sig {
params(targets: T::Array[Service::FormulaWrapper], params(targets: T::Array[Services::FormulaWrapper],
verbose: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean),
no_wait: T.nilable(T::Boolean), no_wait: T.nilable(T::Boolean),
max_wait: T.nilable(Float)).void max_wait: T.nilable(Float)).void
} }
def self.run(targets, verbose:, no_wait:, max_wait:) def self.run(targets, verbose:, no_wait:, max_wait:)
Service::ServicesCli.check(targets) Services::Cli.check(targets)
Service::ServicesCli.stop(targets, verbose:, no_wait:, max_wait:) Services::Cli.stop(targets, verbose:, no_wait:, max_wait:)
end end
end end
end end

View File

@ -3,7 +3,7 @@
# Wrapper for a formula to handle service-related stuff like parsing and # Wrapper for a formula to handle service-related stuff like parsing and
# generating the service/plist files. # generating the service/plist files.
module Service module Services
class FormulaWrapper class FormulaWrapper
# Access the `Formula` instance. # Access the `Formula` instance.
sig { returns(Formula) } sig { returns(Formula) }

View File

@ -1,11 +1,13 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service require "services/formula_wrapper"
module Services
module Formulae module Formulae
# All available services, with optional filters applied # All available services, with optional filters applied
# @private # @private
sig { params(loaded: T.nilable(T::Boolean), skip_root: T::Boolean).returns(T::Array[Service::FormulaWrapper]) } sig { params(loaded: T.nilable(T::Boolean), skip_root: T::Boolean).returns(T::Array[Services::FormulaWrapper]) }
def self.available_services(loaded: nil, skip_root: false) def self.available_services(loaded: nil, skip_root: false)
require "formula" require "formula"

View File

@ -1,17 +0,0 @@
# typed: strict
# frozen_string_literal: true
# fix loadppath
$LOAD_PATH.unshift(File.expand_path(__dir__))
require "service/formula_wrapper"
require "service/services_cli"
require "service/system"
require "service/commands/cleanup"
require "service/commands/info"
require "service/commands/list"
require "service/commands/restart"
require "service/commands/run"
require "service/commands/start"
require "service/commands/stop"
require "service/commands/kill"

View File

@ -1,16 +0,0 @@
# typed: strict
# frozen_string_literal: true
module Service
module Commands
module Kill
TRIGGERS = %w[kill k].freeze
sig { params(targets: T::Array[Service::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
def self.run(targets, verbose:)
Service::ServicesCli.check(targets)
Service::ServicesCli.kill(targets, verbose:)
end
end
end
end

View File

@ -1,16 +0,0 @@
# typed: strict
# frozen_string_literal: true
module Service
module Commands
module Run
TRIGGERS = ["run"].freeze
sig { params(targets: T::Array[Service::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
def self.run(targets, verbose:)
Service::ServicesCli.check(targets)
Service::ServicesCli.run(targets, verbose:)
end
end
end
end

View File

@ -3,7 +3,7 @@
require_relative "system/systemctl" require_relative "system/systemctl"
module Service module Services
module System module System
extend FileUtils extend FileUtils

View File

@ -1,7 +1,7 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Service module Services
module System module System
module Systemctl module Systemctl
sig { returns(T.nilable(Pathname)) } sig { returns(T.nilable(Pathname)) }

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/cli"
require "services/system"
require "services/formula_wrapper"
RSpec.describe Service::ServicesCli do RSpec.describe Services::Cli do
subject(:services_cli) { described_class } subject(:services_cli) { described_class }
let(:service_string) { "service" } let(:service_string) { "service" }
@ -15,7 +17,7 @@ RSpec.describe Service::ServicesCli do
describe "#running" do describe "#running" do
it "macOS - returns the currently running services" do it "macOS - returns the currently running services" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
allow(Utils).to receive(:popen_read).and_return <<~EOS allow(Utils).to receive(:popen_read).and_return <<~EOS
77513 50 homebrew.mxcl.php 77513 50 homebrew.mxcl.php
495 0 homebrew.mxcl.node_exporter 495 0 homebrew.mxcl.node_exporter
@ -29,8 +31,8 @@ RSpec.describe Service::ServicesCli do
end end
it "systemD - returns the currently running services" do it "systemD - returns the currently running services" do
allow(Service::System).to receive(:launchctl?).and_return(false) allow(Services::System).to receive(:launchctl?).and_return(false)
allow(Service::System::Systemctl).to receive(:popen_read).and_return <<~EOS allow(Services::System::Systemctl).to receive(:popen_read).and_return <<~EOS
homebrew.php.service loaded active running Homebrew PHP service homebrew.php.service loaded active running Homebrew PHP service
systemd-udevd.service loaded active running Rule-based Manager for Device Events and Files systemd-udevd.service loaded active running Rule-based Manager for Device Events and Files
udisks2.service loaded active running Disk Manager udisks2.service loaded active running Disk Manager
@ -44,26 +46,23 @@ RSpec.describe Service::ServicesCli do
it "checks the input does not exist" do it "checks the input does not exist" do
expect do expect do
services_cli.check([]) services_cli.check([])
end.to raise_error(UsageError, end.to raise_error(UsageError, "Invalid usage: Formula(e) missing, please provide a formula name or use --all")
a_string_including("Formula(e) missing, please provide a formula name or use --all"))
end end
it "checks the input exists" do it "checks the input exists" do
expect do expect do
services_cli.check("hello") services_cli.check("hello")
end.not_to raise_error(UsageError, end.not_to raise_error(UsageError,
a_string_including("Formula(e) missing, please provide a formula name or use --all")) "Invalid usage: Formula(e) missing, please provide a formula name or use --all")
end end
end end
describe "#kill_orphaned_services" do describe "#kill_orphaned_services" do
it "skips unmanaged services" do it "skips unmanaged services" do
service = instance_double(service_string, name: "example_service")
allow(services_cli).to receive(:running).and_return(["example_service"]) allow(services_cli).to receive(:running).and_return(["example_service"])
allow(Service::FormulaWrapper).to receive(:from).and_return(service)
expect do expect do
services_cli.kill_orphaned_services services_cli.kill_orphaned_services
end.to output("Service example_service not managed by `brew services` => skipping\n").to_stdout end.to output("Warning: Service example_service not managed by `brew services` => skipping\n").to_stderr
end end
it "tries but is unable to kill a non existing service" do it "tries but is unable to kill a non existing service" do
@ -75,18 +74,17 @@ RSpec.describe Service::ServicesCli do
keep_alive?: false, keep_alive?: false,
) )
allow(service).to receive(:service_name) allow(service).to receive(:service_name)
allow(Service::FormulaWrapper).to receive(:from).and_return(service) allow(Services::FormulaWrapper).to receive(:from).and_return(service)
allow(services_cli).to receive(:running).and_return(["example_service"]) allow(services_cli).to receive(:running).and_return(["example_service"])
expect do expect do
services_cli.kill_orphaned_services services_cli.kill_orphaned_services
end.to output(a_string_including("Killing `example_service`... (might take a while)")).to_stdout.and end.to output("Killing `example_service`... (might take a while)\n").to_stdout
output(a_string_including("Unable to kill `example_service` (label: )")).to_stderr
end end
end end
describe "#run" do describe "#run" do
it "checks empty targets cause no error" do it "checks empty targets cause no error" do
expect(Service::System).not_to receive(:root?) expect(Services::System).not_to receive(:root?)
services_cli.run([]) services_cli.run([])
end end
@ -102,14 +100,14 @@ RSpec.describe Service::ServicesCli do
describe "#start" do describe "#start" do
it "checks missing file causes error" do it "checks missing file causes error" do
expect(Service::System).not_to receive(:root?) expect(Services::System).not_to receive(:root?)
expect do expect do
services_cli.start(["service_name"], "/hfdkjshksdjhfkjsdhf/fdsjghsdkjhb") services_cli.start(["service_name"], "/hfdkjshksdjhfkjsdhf/fdsjghsdkjhb")
end.to raise_error(UsageError, a_string_including("Provided service file does not exist")) end.to raise_error(UsageError, "Invalid usage: Provided service file does not exist")
end end
it "checks empty targets cause no error" do it "checks empty targets cause no error" do
expect(Service::System).not_to receive(:root?) expect(Services::System).not_to receive(:root?)
services_cli.start([]) services_cli.start([])
end end
@ -125,14 +123,14 @@ RSpec.describe Service::ServicesCli do
describe "#stop" do describe "#stop" do
it "checks empty targets cause no error" do it "checks empty targets cause no error" do
expect(Service::System).not_to receive(:root?) expect(Services::System).not_to receive(:root?)
services_cli.stop([]) services_cli.stop([])
end end
end end
describe "#kill" do describe "#kill" do
it "checks empty targets cause no error" do it "checks empty targets cause no error" do
expect(Service::System).not_to receive(:root?) expect(Services::System).not_to receive(:root?)
services_cli.kill([]) services_cli.kill([])
end end
@ -155,15 +153,15 @@ RSpec.describe Service::ServicesCli do
describe "#install_service_file" do describe "#install_service_file" do
it "checks service is installed" do it "checks service is installed" do
service = instance_double(Service::FormulaWrapper, name: "name", installed?: false) service = instance_double(Services::FormulaWrapper, name: "name", installed?: false)
expect do expect do
services_cli.install_service_file(service, nil) services_cli.install_service_file(service, nil)
end.to raise_error(UsageError, a_string_including("Formula `name` is not installed")) end.to raise_error(UsageError, "Invalid usage: Formula `name` is not installed")
end end
it "checks service file exists" do it "checks service file exists" do
service = instance_double( service = instance_double(
Service::FormulaWrapper, Services::FormulaWrapper,
name: "name", name: "name",
installed?: true, installed?: true,
service_file: instance_double(Pathname, exist?: false), service_file: instance_double(Pathname, exist?: false),
@ -172,28 +170,26 @@ RSpec.describe Service::ServicesCli do
services_cli.install_service_file(service, nil) services_cli.install_service_file(service, nil)
end.to raise_error( end.to raise_error(
UsageError, UsageError,
a_string_including( "Invalid usage: Formula `name` has not implemented #plist, #service or installed a locatable service file",
"Formula `name` has not implemented #plist, #service or installed a locatable service file",
),
) )
end end
end end
describe "#systemd_load", :needs_linux do describe "#systemd_load", :needs_linux do
it "checks non-enabling run" do it "checks non-enabling run" do
expect(Service::System::Systemctl).to receive(:executable).once.and_return("/bin/systemctl") expect(Services::System::Systemctl).to receive(:executable).once.and_return("/bin/systemctl")
expect(Service::System::Systemctl).to receive(:scope).once.and_return("--user") expect(Services::System::Systemctl).to receive(:scope).once.and_return("--user")
services_cli.systemd_load( services_cli.systemd_load(
instance_double(Service::FormulaWrapper, service_name: "name"), instance_double(Services::FormulaWrapper, service_name: "name"),
enable: false, enable: false,
) )
end end
it "checks enabling run" do it "checks enabling run" do
expect(Service::System::Systemctl).to receive(:executable).twice.and_return("/bin/systemctl") expect(Services::System::Systemctl).to receive(:executable).twice.and_return("/bin/systemctl")
expect(Service::System::Systemctl).to receive(:scope).twice.and_return("--user") expect(Services::System::Systemctl).to receive(:scope).twice.and_return("--user")
services_cli.systemd_load( services_cli.systemd_load(
instance_double(Service::FormulaWrapper, service_name: "name"), instance_double(Services::FormulaWrapper, service_name: "name"),
enable: true, enable: true,
) )
end end
@ -201,70 +197,76 @@ RSpec.describe Service::ServicesCli do
describe "#launchctl_load", :needs_macos do describe "#launchctl_load", :needs_macos do
it "checks non-enabling run" do it "checks non-enabling run" do
expect(Service::System).to receive(:domain_target).once.and_return("target") expect(Services::System).to receive(:domain_target).once.and_return("target")
expect(Service::System).to receive(:launchctl).once.and_return("/bin/launchctl") expect(Services::System).to receive(:launchctl).once.and_return("/bin/launchctl")
services_cli.launchctl_load(instance_double(Service::FormulaWrapper), file: "a", enable: false) expect(described_class).to receive(:safe_system).once.and_return(true)
services_cli.launchctl_load(instance_double(Services::FormulaWrapper), file: "a", enable: false)
end end
it "checks enabling run" do it "checks enabling run" do
expect(Service::System).to receive(:domain_target).twice.and_return("target") expect(Services::System).to receive(:domain_target).twice.and_return("target")
expect(Service::System).to receive(:launchctl).twice.and_return("/bin/launchctl") expect(Services::System).to receive(:launchctl).twice.and_return("/bin/launchctl")
services_cli.launchctl_load(instance_double(Service::FormulaWrapper, service_name: "name"), file: "a", expect(described_class).to receive(:safe_system).twice.and_return(true)
services_cli.launchctl_load(instance_double(Services::FormulaWrapper, service_name: "name"), file: "a",
enable: true) enable: true)
end end
end end
describe "#service_load" do describe "#service_load" do
it "checks non-root for login" do it "checks non-root for login" do
expect(Service::System).to receive(:launchctl?).once.and_return(false) expect(Services::System).to receive(:launchctl?).once.and_return(false)
expect(Service::System).to receive(:systemctl?).once.and_return(false) expect(Services::System).to receive(:systemctl?).once.and_return(false)
expect(Service::System).to receive(:root?).once.and_return(true) expect(Services::System).to receive(:root?).once.and_return(true)
expect do expect do
services_cli.service_load( services_cli.service_load(
instance_double(Service::FormulaWrapper, name: "name", service_name: "service.name", instance_double(Services::FormulaWrapper, name: "name", service_name: "service.name",
service_startup?: false), enable: false service_startup?: false), enable: false
) )
end.to output(a_string_including("Successfully ran `name` (label: service.name)")).to_stdout.and end.to output("==> Successfully ran `name` (label: service.name)\n").to_stdout
output(a_string_including("name must be run as non-root to start at user login!")).to_stderr
end end
it "checks root for startup" do it "checks root for startup" do
expect(Service::System).to receive(:launchctl?).once.and_return(false) expect(Services::System).to receive(:launchctl?).once.and_return(false)
expect(Service::System).to receive(:systemctl?).once.and_return(false) expect(Services::System).to receive(:systemctl?).once.and_return(false)
expect(Service::System).to receive(:root?).twice.and_return(false) expect(Services::System).to receive(:root?).twice.and_return(false)
expect do expect do
services_cli.service_load( services_cli.service_load(
instance_double(Service::FormulaWrapper, name: "name", service_name: "service.name", instance_double(Services::FormulaWrapper, name: "name", service_name: "service.name",
service_startup?: true), service_startup?: true),
enable: false, enable: false,
) )
end.to output(a_string_including("Successfully ran `name` (label: service.name)")).to_stdout.and end.to output("==> Successfully ran `name` (label: service.name)\n").to_stdout
output(a_string_including("name must be run as root to start at system startup!")).to_stderr
end end
it "triggers launchctl" do it "triggers launchctl" do
expect(Service::System).to receive(:domain_target).once.and_return("target") expect(Services::System).to receive(:launchctl?).once.and_return(true)
expect(Service::System).to receive(:launchctl?).once.and_return(true) expect(Services::System).not_to receive(:systemctl?)
expect(Service::System).to receive(:launchctl).once expect(Services::System).to receive(:root?).twice.and_return(false)
expect(Service::System).not_to receive(:systemctl?) expect(described_class).to receive(:launchctl_load).once.and_return(true)
expect(Service::System).to receive(:root?).twice.and_return(false)
expect do
services_cli.service_load(
instance_double(Service::FormulaWrapper, name: "name", service_name: "service.name",
service_startup?: false), enable: false
)
end.to output("Successfully ran `name` (label: service.name)\n").to_stdout
end
it "triggers systemctl" do
expect(Service::System).to receive(:launchctl?).once.and_return(false)
expect(Service::System).to receive(:systemctl?).once.and_return(true)
expect(Service::System).to receive(:root?).thrice.and_return(false)
expect do expect do
services_cli.service_load( services_cli.service_load(
instance_double( instance_double(
Service::FormulaWrapper, Services::FormulaWrapper,
name: "name",
service_name: "service.name",
service_startup?: false,
service_file: instance_double(Pathname, exist?: false),
),
enable: false,
)
end.to output("==> Successfully ran `name` (label: service.name)\n").to_stdout
end
it "triggers systemctl" do
expect(Services::System).to receive(:launchctl?).once.and_return(false)
expect(Services::System).to receive(:systemctl?).once.and_return(true)
expect(Services::System).to receive(:root?).twice.and_return(false)
expect(Services::System::Systemctl).to receive(:run).once.and_return(true)
expect do
services_cli.service_load(
instance_double(
Services::FormulaWrapper,
name: "name", name: "name",
service_name: "service.name", service_name: "service.name",
service_startup?: false, service_startup?: false,
@ -272,17 +274,18 @@ service_startup?: false), enable: false
), ),
enable: false, enable: false,
) )
end.to output("Successfully ran `name` (label: service.name)\n").to_stdout end.to output("==> Successfully ran `name` (label: service.name)\n").to_stdout
end end
it "represents correct action" do it "represents correct action" do
expect(Service::System).to receive(:launchctl?).once.and_return(false) expect(Services::System).to receive(:launchctl?).once.and_return(false)
expect(Service::System).to receive(:systemctl?).once.and_return(true) expect(Services::System).to receive(:systemctl?).once.and_return(true)
expect(Service::System).to receive(:root?).exactly(4).times.and_return(false) expect(Services::System).to receive(:root?).twice.and_return(false)
expect(Services::System::Systemctl).to receive(:run).twice.and_return(true)
expect do expect do
services_cli.service_load( services_cli.service_load(
instance_double( instance_double(
Service::FormulaWrapper, Services::FormulaWrapper,
name: "name", name: "name",
service_name: "service.name", service_name: "service.name",
service_startup?: false, service_startup?: false,
@ -290,7 +293,7 @@ service_startup?: false), enable: false
), ),
enable: true, enable: true,
) )
end.to output("Successfully started `name` (label: service.name)\n").to_stdout end.to output("==> Successfully started `name` (label: service.name)\n").to_stdout
end end
end end
end end

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/commands/cleanup"
require "services/system"
require "services/cli"
RSpec.describe Service::Commands::Cleanup do RSpec.describe Services::Commands::Cleanup do
describe "#TRIGGERS" do describe "#TRIGGERS" do
it "contains all restart triggers" do it "contains all restart triggers" do
expect(described_class::TRIGGERS).to eq(%w[cleanup clean cl rm]) expect(described_class::TRIGGERS).to eq(%w[cleanup clean cl rm])
@ -11,9 +13,9 @@ RSpec.describe Service::Commands::Cleanup do
describe "#run" do describe "#run" do
it "root - prints on empty cleanup" do it "root - prints on empty cleanup" do
expect(Service::System).to receive(:root?).once.and_return(true) expect(Services::System).to receive(:root?).once.and_return(true)
expect(Service::ServicesCli).to receive(:kill_orphaned_services).once.and_return([]) expect(Services::Cli).to receive(:kill_orphaned_services).once.and_return([])
expect(Service::ServicesCli).to receive(:remove_unused_service_files).once.and_return([]) expect(Services::Cli).to receive(:remove_unused_service_files).once.and_return([])
expect do expect do
described_class.run described_class.run
@ -21,9 +23,9 @@ RSpec.describe Service::Commands::Cleanup do
end end
it "user - prints on empty cleanup" do it "user - prints on empty cleanup" do
expect(Service::System).to receive(:root?).once.and_return(false) expect(Services::System).to receive(:root?).once.and_return(false)
expect(Service::ServicesCli).to receive(:kill_orphaned_services).once.and_return([]) expect(Services::Cli).to receive(:kill_orphaned_services).once.and_return([])
expect(Service::ServicesCli).to receive(:remove_unused_service_files).once.and_return([]) expect(Services::Cli).to receive(:remove_unused_service_files).once.and_return([])
expect do expect do
described_class.run described_class.run
@ -31,9 +33,9 @@ RSpec.describe Service::Commands::Cleanup do
end end
it "prints nothing on cleanup" do it "prints nothing on cleanup" do
expect(Service::System).not_to receive(:root?) expect(Services::System).not_to receive(:root?)
expect(Service::ServicesCli).to receive(:kill_orphaned_services).once.and_return(["a"]) expect(Services::Cli).to receive(:kill_orphaned_services).once.and_return(["a"])
expect(Service::ServicesCli).to receive(:remove_unused_service_files).once.and_return(["b"]) expect(Services::Cli).to receive(:remove_unused_service_files).once.and_return(["b"])
expect do expect do
described_class.run described_class.run

View File

@ -1,37 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/commands/info"
# needs for tty color tests RSpec.describe Services::Commands::Info do
module Tty
def self.green
"<GREEN>"
end
def self.yellow
"<YELLOW>"
end
def self.red
"<RED>"
end
def self.default
"<DEFAULT>"
end
def self.bold
"<BOLD>"
end
def self.reset
"<RESET>"
end
end
RSpec.describe Service::Commands::Info do
before do before do
allow_any_instance_of(IO).to receive(:tty?).and_return(true) allow_any_instance_of(IO).to receive(:tty?).and_return(false)
end end
describe "#TRIGGERS" do describe "#TRIGGERS" do
@ -44,12 +17,11 @@ RSpec.describe Service::Commands::Info do
it "fails with empty list" do it "fails with empty list" do
expect do expect do
described_class.run([], verbose: false, json: false) described_class.run([], verbose: false, json: false)
end.to raise_error UsageError, end.to raise_error UsageError, "Invalid usage: Formula(e) missing, please provide a formula name or use --all"
a_string_including("Formula(e) missing, please provide a formula name or use --all")
end end
it "succeeds with items" do it "succeeds with items" do
out = "<BOLD>service<RESET> ()\nRunning: true\nLoaded: true\nSchedulable: false\n" out = "service ()\nRunning: true\nLoaded: true\nSchedulable: false\n"
formula = { formula = {
name: "service", name: "service",
user: "user", user: "user",
@ -83,8 +55,8 @@ RSpec.describe Service::Commands::Info do
describe "#output" do describe "#output" do
it "returns minimal output" do it "returns minimal output" do
out = "<BOLD>service<RESET> ()\nRunning: <BOLD><GREEN>✔<RESET><RESET>\n" out = "service ()\nRunning: true\n"
out += "Loaded: <BOLD><GREEN>✔<RESET><RESET>\nSchedulable: <BOLD><RED>✘<RESET><RESET>\n" out += "Loaded: true\nSchedulable: false\n"
formula = { formula = {
name: "service", name: "service",
user: "user", user: "user",
@ -98,8 +70,8 @@ RSpec.describe Service::Commands::Info do
end end
it "returns normal output" do it "returns normal output" do
out = "<BOLD>service<RESET> ()\nRunning: <BOLD><GREEN>✔<RESET><RESET>\n" out = "service ()\nRunning: true\n"
out += "Loaded: <BOLD><GREEN>✔<RESET><RESET>\nSchedulable: <BOLD><RED>✘<RESET><RESET>\n" out += "Loaded: true\nSchedulable: false\n"
out += "User: user\nPID: 42\n" out += "User: user\nPID: 42\n"
formula = { formula = {
name: "service", name: "service",
@ -115,9 +87,9 @@ RSpec.describe Service::Commands::Info do
end end
it "returns verbose output" do it "returns verbose output" do
out = "<BOLD>service<RESET> ()\nRunning: <BOLD><GREEN>✔<RESET><RESET>\n" out = "service ()\nRunning: true\n"
out += "Loaded: <BOLD><GREEN>✔<RESET><RESET>\nSchedulable: <BOLD><RED>✘<RESET><RESET>\n" out += "Loaded: true\nSchedulable: false\n"
out += "User: user\nPID: 42\nFile: /dev/null <BOLD><GREEN>✔<RESET><RESET>\nCommand: /bin/command\n" out += "User: user\nPID: 42\nFile: /dev/null 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 = {

View File

@ -1,35 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/commands/list"
# needs for tty color tests RSpec.describe Services::Commands::List do
module Tty
def self.green
"<GREEN>"
end
def self.yellow
"<YELLOW>"
end
def self.red
"<RED>"
end
def self.default
"<DEFAULT>"
end
def self.bold
"<BOLD>"
end
def self.reset
"<RESET>"
end
end
RSpec.describe Service::Commands::List do
describe "#TRIGGERS" do describe "#TRIGGERS" do
it "contains all restart triggers" do it "contains all restart triggers" do
expect(described_class::TRIGGERS).to eq([nil, "list", "ls"]) expect(described_class::TRIGGERS).to eq([nil, "list", "ls"])
@ -38,24 +11,23 @@ RSpec.describe Service::Commands::List do
describe "#run" do describe "#run" do
it "fails with empty list" do it "fails with empty list" do
allow_any_instance_of(IO).to receive(:tty?).and_return(true) expect(Services::Formulae).to receive(:services_list).and_return([])
expect(Service::Formulae).to receive(:services_list).and_return([])
expect do expect do
allow($stderr).to receive(:tty?).and_return(true)
described_class.run described_class.run
end.to output(a_string_including("No services available to control with `brew services`")).to_stderr end.to output(a_string_including("No services available to control with `brew services`")).to_stderr
end end
it "succeeds with list" do it "succeeds with list" do
out = "<BOLD>Name Status User File<RESET>\nservice <GREEN>started<RESET> user /dev/null\n" out = "Name Status User File\nservice started user /dev/null\n"
formula = instance_double( formula = {
Service::FormulaWrapper,
name: "service", name: "service",
owner: "user", user: "user",
status_symbol: :started, status: :started,
service_file: +File::NULL, file: "/dev/null",
loaded?: true, loaded: true,
) }
expect(Service::Formulae).to receive(:services_list).and_return([formula]) expect(Services::Formulae).to receive(:services_list).and_return([formula])
expect do expect do
described_class.run described_class.run
end.to output(out).to_stdout end.to output(out).to_stdout
@ -75,7 +47,7 @@ RSpec.describe Service::Commands::List do
filtered_formula = formula.slice(*described_class::JSON_FIELDS) filtered_formula = formula.slice(*described_class::JSON_FIELDS)
expected_output = "#{JSON.pretty_generate([filtered_formula])}\n" expected_output = "#{JSON.pretty_generate([filtered_formula])}\n"
expect(Service::Formulae).to receive(:services_list).and_return([formula]) expect(Services::Formulae).to receive(:services_list).and_return([formula])
expect do expect do
described_class.run(json: true) described_class.run(json: true)
end.to output(expected_output).to_stdout end.to output(expected_output).to_stdout
@ -87,20 +59,20 @@ RSpec.describe Service::Commands::List do
formula = { name: "a", user: "u", file: Pathname.new("/tmp/file.file"), status: :stopped } formula = { name: "a", user: "u", file: Pathname.new("/tmp/file.file"), status: :stopped }
expect do expect do
described_class.print_table([formula]) described_class.print_table([formula])
end.to output("<BOLD>Name Status User File<RESET>\na <DEFAULT>stopped<RESET> u \n").to_stdout end.to output("Name Status User File\na stopped u \n").to_stdout
end end
it "prints without user or file data" do it "prints without user or file data" do
formula = { name: "a", user: nil, file: nil, status: :started, loaded: true } formula = { name: "a", user: nil, file: nil, status: :started, loaded: true }
expect do expect do
described_class.print_table([formula]) described_class.print_table([formula])
end.to output("<BOLD>Name Status User File<RESET>\na <GREEN>started<RESET> \n").to_stdout end.to output("Name Status User File\na started \n").to_stdout
end end
it "prints shortened home directory" do it "prints shortened home directory" do
ENV["HOME"] = "/tmp" ENV["HOME"] = "/tmp"
formula = { name: "a", user: "u", file: Pathname.new("/tmp/file.file"), status: :started, loaded: true } formula = { name: "a", user: "u", file: Pathname.new("/tmp/file.file"), status: :started, loaded: true }
expected_output = "<BOLD>Name Status User File<RESET>\na <GREEN>started<RESET> u ~/file.file\n" expected_output = "Name Status User File\na started u ~/file.file\n"
expect do expect do
described_class.print_table([formula]) described_class.print_table([formula])
end.to output(expected_output).to_stdout end.to output(expected_output).to_stdout
@ -109,7 +81,7 @@ RSpec.describe Service::Commands::List do
it "prints an error code" do it "prints an error code" do
file = Pathname.new("/tmp/file.file") file = Pathname.new("/tmp/file.file")
formula = { name: "a", user: "u", file:, status: :error, exit_code: 256, loaded: true } formula = { name: "a", user: "u", file:, status: :error, exit_code: 256, loaded: true }
expected_output = "<BOLD>Name Status User File<RESET>\na <RED>error <RESET>256 u /tmp/file.file\n" expected_output = "Name Status User File\na error 256 u /tmp/file.file\n"
expect do expect do
described_class.print_table([formula]) described_class.print_table([formula])
end.to output(expected_output).to_stdout end.to output(expected_output).to_stdout
@ -147,23 +119,23 @@ RSpec.describe Service::Commands::List do
describe "#get_status_string" do describe "#get_status_string" do
it "returns started" do it "returns started" do
expect(described_class.get_status_string(:started)).to eq("<GREEN>started<RESET>") expect(described_class.get_status_string(:started)).to eq("started")
end end
it "returns stopped" do it "returns stopped" do
expect(described_class.get_status_string(:stopped)).to eq("<DEFAULT>stopped<RESET>") expect(described_class.get_status_string(:stopped)).to eq("stopped")
end end
it "returns error" do it "returns error" do
expect(described_class.get_status_string(:error)).to eq("<RED>error <RESET>") expect(described_class.get_status_string(:error)).to eq("error ")
end end
it "returns unknown" do it "returns unknown" do
expect(described_class.get_status_string(:unknown)).to eq("<YELLOW>unknown<RESET>") expect(described_class.get_status_string(:unknown)).to eq("unknown")
end end
it "returns other" do it "returns other" do
expect(described_class.get_status_string(:other)).to eq("<YELLOW>other<RESET>") expect(described_class.get_status_string(:other)).to eq("other")
end end
end end
end end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/commands/restart"
require "services/cli"
RSpec.describe Service::Commands::Restart do require "services/formula_wrapper"
RSpec.describe Services::Commands::Restart do
describe "#TRIGGERS" do describe "#TRIGGERS" do
it "contains all restart triggers" do it "contains all restart triggers" do
expect(described_class::TRIGGERS).to eq(%w[restart relaunch reload r]) expect(described_class::TRIGGERS).to eq(%w[restart relaunch reload r])
@ -13,32 +14,31 @@ RSpec.describe Service::Commands::Restart 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([], verbose: false)
end.to raise_error UsageError, end.to raise_error UsageError, "Invalid usage: Formula(e) missing, please provide a formula name or use --all"
a_string_including("Formula(e) missing, please provide a formula name or use --all")
end end
it "starts if services are not loaded" do it "starts if services are not loaded" do
expect(Service::ServicesCli).not_to receive(:run) expect(Services::Cli).not_to receive(:run)
expect(Service::ServicesCli).not_to receive(:stop) expect(Services::Cli).not_to receive(:stop)
expect(Service::ServicesCli).to receive(:start).once expect(Services::Cli).to receive(:start).once
service = instance_double(Service::FormulaWrapper, service_name: "name", loaded?: false) service = instance_double(Services::FormulaWrapper, service_name: "name", loaded?: false)
expect(described_class.run([service], verbose: false)).to be_nil expect(described_class.run([service], verbose: false)).to be_nil
end end
it "starts if services are loaded with file" do it "starts if services are loaded with file" do
expect(Service::ServicesCli).not_to receive(:run) expect(Services::Cli).not_to receive(:run)
expect(Service::ServicesCli).to receive(:start).once expect(Services::Cli).to receive(:start).once
expect(Service::ServicesCli).to receive(:stop).once expect(Services::Cli).to receive(:stop).once
service = instance_double(Service::FormulaWrapper, service_name: "name", loaded?: true, service = instance_double(Services::FormulaWrapper, service_name: "name", loaded?: true,
service_file_present?: true) service_file_present?: true)
expect(described_class.run([service], verbose: false)).to be_nil expect(described_class.run([service], verbose: false)).to be_nil
end end
it "runs if services are loaded without file" do it "runs if services are loaded without file" do
expect(Service::ServicesCli).not_to receive(:start) expect(Services::Cli).not_to receive(:start)
expect(Service::ServicesCli).to receive(:run).once expect(Services::Cli).to receive(:run).once
expect(Service::ServicesCli).to receive(:stop).once expect(Services::Cli).to receive(:stop).once
service = instance_double(Service::FormulaWrapper, service_name: "name", loaded?: true, service = instance_double(Services::FormulaWrapper, service_name: "name", loaded?: true,
service_file_present?: false) service_file_present?: false)
expect(described_class.run([service], verbose: false)).to be_nil expect(described_class.run([service], verbose: false)).to be_nil
end end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/formulae"
RSpec.describe Service::Formulae do RSpec.describe Services::Formulae do
describe "#services_list" do describe "#services_list" do
it "empty list without available formulae" do it "empty list without available formulae" do
allow(described_class).to receive(:available_services).and_return({}) allow(described_class).to receive(:available_services).and_return({})
@ -10,7 +10,7 @@ RSpec.describe Service::Formulae do
end end
it "list with available formulae" do it "list with available formulae" do
formula = instance_double(Service::FormulaWrapper) formula = instance_double(Services::FormulaWrapper)
expected = [ expected = [
{ {
file: Pathname.new("/Library/LaunchDaemons/file.plist"), file: Pathname.new("/Library/LaunchDaemons/file.plist"),

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/system"
require "services/formula_wrapper"
require "tempfile" require "tempfile"
RSpec.describe Service::FormulaWrapper do RSpec.describe Services::FormulaWrapper do
subject(:service) { described_class.new(formula) } subject(:service) { described_class.new(formula) }
let(:formula) do let(:formula) do
@ -40,17 +41,17 @@ RSpec.describe Service::FormulaWrapper do
describe "#service_file" do describe "#service_file" do
it "macOS - outputs the full service file path" do it "macOS - outputs the full service file path" do
allow(Service::System).to receive(:launchctl?).and_return(true) allow(Services::System).to receive(:launchctl?).and_return(true)
expect(service.service_file.to_s).to eq("/usr/local/opt/mysql/homebrew.mysql.plist") expect(service.service_file.to_s).to eq("/usr/local/opt/mysql/homebrew.mysql.plist")
end end
it "systemD - outputs the full service file path" do it "systemD - outputs the full service file path" do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: true) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: true)
expect(service.service_file.to_s).to eq("/usr/local/opt/mysql/homebrew.mysql.service") expect(service.service_file.to_s).to eq("/usr/local/opt/mysql/homebrew.mysql.service")
end end
it "Other - outputs no service file" do it "Other - outputs no service file" do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false)
expect(service.service_file).to be_nil expect(service.service_file).to be_nil
end end
end end
@ -63,45 +64,45 @@ RSpec.describe Service::FormulaWrapper do
describe "#service_name" do describe "#service_name" do
it "macOS - outputs the service name" do it "macOS - outputs the service name" do
allow(Service::System).to receive(:launchctl?).and_return(true) allow(Services::System).to receive(:launchctl?).and_return(true)
expect(service.service_name).to eq("plist-mysql-test") expect(service.service_name).to eq("plist-mysql-test")
end end
it "systemD - outputs the service name" do it "systemD - outputs the service name" do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: true) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: true)
expect(service.service_name).to eq("plist-mysql-test") expect(service.service_name).to eq("plist-mysql-test")
end end
it "Other - outputs no service name" do it "Other - outputs no service name" do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false)
expect(service.service_name).to be_nil expect(service.service_name).to be_nil
end end
end end
describe "#dest_dir" do describe "#dest_dir" do
before do before do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false)
end end
it "macOS - user - outputs the destination directory for the service file" do it "macOS - user - outputs the destination directory for the service file" do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Service::System).to receive_messages(root?: false, launchctl?: true) allow(Services::System).to receive_messages(root?: false, launchctl?: true)
expect(service.dest_dir.to_s).to eq("/tmp_home/Library/LaunchAgents") expect(service.dest_dir.to_s).to eq("/tmp_home/Library/LaunchAgents")
end end
it "macOS - root - outputs the destination directory for the service file" do it "macOS - root - outputs the destination directory for the service file" do
allow(Service::System).to receive_messages(launchctl?: true, root?: true) allow(Services::System).to receive_messages(launchctl?: true, root?: true)
expect(service.dest_dir.to_s).to eq("/Library/LaunchDaemons") expect(service.dest_dir.to_s).to eq("/Library/LaunchDaemons")
end end
it "systemD - user - outputs the destination directory for the service file" do it "systemD - user - outputs the destination directory for the service file" do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Service::System).to receive_messages(root?: false, launchctl?: false, systemctl?: true) allow(Services::System).to receive_messages(root?: false, launchctl?: false, systemctl?: true)
expect(service.dest_dir.to_s).to eq("/tmp_home/.config/systemd/user") expect(service.dest_dir.to_s).to eq("/tmp_home/.config/systemd/user")
end end
it "systemD - root - outputs the destination directory for the service file" do it "systemD - root - outputs the destination directory for the service file" do
allow(Service::System).to receive_messages(root?: true, launchctl?: false, systemctl?: true) allow(Services::System).to receive_messages(root?: true, launchctl?: false, systemctl?: true)
expect(service.dest_dir.to_s).to eq("/usr/lib/systemd/system") expect(service.dest_dir.to_s).to eq("/usr/lib/systemd/system")
end end
end end
@ -109,16 +110,16 @@ RSpec.describe Service::FormulaWrapper do
describe "#dest" do describe "#dest" do
before do before do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false)
end end
it "macOS - outputs the destination for the service file" do it "macOS - outputs the destination for the service file" do
allow(Service::System).to receive(:launchctl?).and_return(true) allow(Services::System).to receive(:launchctl?).and_return(true)
expect(service.dest.to_s).to eq("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist") expect(service.dest.to_s).to eq("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist")
end end
it "systemD - outputs the destination for the service file" do it "systemD - outputs the destination for the service file" do
allow(Service::System).to receive(:systemctl?).and_return(true) allow(Services::System).to receive(:systemctl?).and_return(true)
expect(service.dest.to_s).to eq("/tmp_home/.config/systemd/user/homebrew.mysql.service") expect(service.dest.to_s).to eq("/tmp_home/.config/systemd/user/homebrew.mysql.service")
end end
end end
@ -131,20 +132,20 @@ RSpec.describe Service::FormulaWrapper do
describe "#loaded?" do describe "#loaded?" do
it "macOS - outputs if the service is loaded" do it "macOS - outputs if the service is loaded" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
allow(Utils).to receive(:safe_popen_read) allow(Utils).to receive(:safe_popen_read)
expect(service.loaded?).to be(false) expect(service.loaded?).to be(false)
end end
it "systemD - outputs if the service is loaded" do it "systemD - outputs if the service is loaded" do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: true) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: true)
allow(Service::System::Systemctl).to receive(:quiet_run).and_return(false) allow(Services::System::Systemctl).to receive(:quiet_run).and_return(false)
allow(Utils).to receive(:safe_popen_read) allow(Utils).to receive(:safe_popen_read)
expect(service.loaded?).to be(false) expect(service.loaded?).to be(false)
end end
it "Other - outputs no status" do it "Other - outputs no status" do
allow(Service::System).to receive_messages(launchctl?: false, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false)
expect(service.loaded?).to be_nil expect(service.loaded?).to be_nil
end end
end end
@ -181,7 +182,7 @@ RSpec.describe Service::FormulaWrapper do
it "user if file present" do it "user if file present" do
allow(service).to receive_messages(boot_path_service_file_present?: false, allow(service).to receive_messages(boot_path_service_file_present?: false,
user_path_service_file_present?: true) user_path_service_file_present?: true)
allow(Service::System).to receive(:user).and_return("user") allow(Services::System).to receive(:user).and_return("user")
expect(service.owner).to eq("user") expect(service.owner).to eq("user")
end end
@ -194,24 +195,24 @@ RSpec.describe Service::FormulaWrapper do
describe "#service_file_present?" do describe "#service_file_present?" do
it "macOS - outputs if the service file is present" do it "macOS - outputs if the service file is present" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
expect(service.service_file_present?).to be(false) expect(service.service_file_present?).to be(false)
end end
it "macOS - outputs if the service file is present for root" do it "macOS - outputs if the service file is present for root" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
expect(service.service_file_present?(for: :root)).to be(false) expect(service.service_file_present?(for: :root)).to be(false)
end end
it "macOS - outputs if the service file is present for user" do it "macOS - outputs if the service file is present for user" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
expect(service.service_file_present?(for: :user)).to be(false) expect(service.service_file_present?(for: :user)).to be(false)
end end
end end
describe "#owner?" do describe "#owner?" do
it "macOS - outputs the service file owner" do it "macOS - outputs the service file owner" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
expect(service.owner).to be_nil expect(service.owner).to be_nil
end end
end end
@ -320,7 +321,7 @@ RSpec.describe Service::FormulaWrapper do
describe "#to_hash" do describe "#to_hash" do
it "represents non-service values" do it "represents non-service values" do
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false)
allow_any_instance_of(described_class).to receive_messages(service?: false, service_file_present?: false) allow_any_instance_of(described_class).to receive_messages(service?: false, service_file_present?: false)
expected = { expected = {
exit_code: nil, exit_code: nil,
@ -339,7 +340,7 @@ RSpec.describe Service::FormulaWrapper do
it "represents running non-service values" do it "represents running non-service values" do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(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?).and_return(true)
expected = { expected = {
@ -359,7 +360,7 @@ RSpec.describe Service::FormulaWrapper do
it "represents service values" do it "represents service values" do
ENV["HOME"] = "/tmp_home" ENV["HOME"] = "/tmp_home"
allow(Service::System).to receive_messages(launchctl?: true, systemctl?: false) allow(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?).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)

View File

@ -1,16 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/system"
require "services/system/systemctl"
RSpec.describe Service::System::Systemctl do RSpec.describe Services::System::Systemctl do
describe ".scope" do describe ".scope" do
it "outputs systemctl scope for user" do it "outputs systemctl scope for user" do
allow(Service::System).to receive(:root?).and_return(false) allow(Services::System).to receive(:root?).and_return(false)
expect(described_class.scope).to eq("--user") expect(described_class.scope).to eq("--user")
end end
it "outputs systemctl scope for root" do it "outputs systemctl scope for root" do
allow(Service::System).to receive(:root?).and_return(true) allow(Services::System).to receive(:root?).and_return(true)
expect(described_class.scope).to eq("--system") expect(described_class.scope).to eq("--system")
end end
end end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
require "services/service" require "services/system"
RSpec.describe Service::System do RSpec.describe Services::System do
describe "#launchctl" do describe "#launchctl" do
it "macOS - outputs launchctl command location", :needs_macos do it "macOS - outputs launchctl command location", :needs_macos do
expect(described_class.launchctl).to eq(Pathname.new("/bin/launchctl")) expect(described_class.launchctl).to eq(Pathname.new("/bin/launchctl"))

View File

@ -286,7 +286,7 @@ RSpec.configure do |config|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bar", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bar",
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bundle", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bundle",
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-foo", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-foo",
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-services", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-test-bot",
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-shallow", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-shallow",
HOMEBREW_LIBRARY/"PinnedTaps", HOMEBREW_LIBRARY/"PinnedTaps",
HOMEBREW_REPOSITORY/".git", HOMEBREW_REPOSITORY/".git",

View File

@ -214,11 +214,11 @@ RSpec.describe Tap do
expect(homebrew_foo_tap.remote).to eq("https://github.com/Homebrew/homebrew-foo") expect(homebrew_foo_tap.remote).to eq("https://github.com/Homebrew/homebrew-foo")
expect(homebrew_foo_tap).not_to have_custom_remote expect(homebrew_foo_tap).not_to have_custom_remote
services_tap = described_class.fetch("Homebrew", "services") services_tap = described_class.fetch("Homebrew", "test-bot")
services_tap.path.mkpath services_tap.path.mkpath
services_tap.path.cd do services_tap.path.cd do
system "git", "init" system "git", "init"
system "git", "remote", "add", "origin", "https://github.com/Homebrew/homebrew-services" system "git", "remote", "add", "origin", "https://github.com/Homebrew/homebrew-test-bot"
end end
expect(services_tap).not_to be_private expect(services_tap).not_to be_private
end end
@ -240,7 +240,7 @@ RSpec.describe Tap do
expect(homebrew_foo_tap.remote_repository).to eq("Homebrew/homebrew-foo") expect(homebrew_foo_tap.remote_repository).to eq("Homebrew/homebrew-foo")
services_tap = described_class.fetch("Homebrew", "services") services_tap = described_class.fetch("Homebrew", "test-bot")
services_tap.path.mkpath services_tap.path.mkpath
services_tap.path.cd do services_tap.path.cd do
system "git", "init" system "git", "init"
@ -254,7 +254,7 @@ RSpec.describe Tap do
expect(homebrew_foo_tap.remote_repository).to eq("Homebrew/homebrew-foo") expect(homebrew_foo_tap.remote_repository).to eq("Homebrew/homebrew-foo")
services_tap = described_class.fetch("Homebrew", "services") services_tap = described_class.fetch("Homebrew", "test-bot")
services_tap.path.mkpath services_tap.path.mkpath
services_tap.path.cd do services_tap.path.cd do
system "git", "init" system "git", "init"
@ -275,7 +275,7 @@ RSpec.describe Tap do
end end
describe "#custom_remote?" do describe "#custom_remote?" do
subject(:tap) { described_class.fetch("Homebrew", "services") } subject(:tap) { described_class.fetch("Homebrew", "test-bot") }
let(:remote) { nil } let(:remote) { nil }
@ -293,13 +293,13 @@ RSpec.describe Tap do
end end
context "when using the default remote" do context "when using the default remote" do
let(:remote) { "https://github.com/Homebrew/homebrew-services" } let(:remote) { "https://github.com/Homebrew/homebrew-test-bot" }
it(:custom_remote?) { expect(tap.custom_remote?).to be false } it(:custom_remote?) { expect(tap.custom_remote?).to be false }
end end
context "when using a non-default remote" do context "when using a non-default remote" do
let(:remote) { "git@github.com:Homebrew/homebrew-services" } let(:remote) { "git@github.com:Homebrew/homebrew-test-bot" }
it(:custom_remote?) { expect(tap.custom_remote?).to be true } it(:custom_remote?) { expect(tap.custom_remote?).to be true }
end end

View File

@ -40,7 +40,7 @@ An executable script for a command named `extcmd` should be named `brew-extcmd`.
## Providing `--help` ## Providing `--help`
All internal and external Homebrew commands can provide styled `--help` output by using Homebrews [argument parser](https://rubydoc.brew.sh/Homebrew/CLI/Parser), as seen in the [`brew services` command](https://github.com/Homebrew/homebrew-services/blob/HEAD/cmd/services.rb); or by including lines starting with `#:` (a comment then `:` character in both Bash and Ruby), as seen in the [header of `update.sh`](https://github.com/Homebrew/brew/blob/cf7def0c68903814c6b4e04a55fe8f3cb3f5605e/Library/Homebrew/cmd/update.sh#L1-L10), which is printed with `brew update --help`. All internal and external Homebrew commands can provide styled `--help` output by using Homebrews [argument parser](https://rubydoc.brew.sh/Homebrew/CLI/Parser), as seen in the [`brew test-bot` command](https://github.com/Homebrew/homebrew-test-bot/blob/HEAD/cmd/test-bot.rb); or by including lines starting with `#:` (a comment then `:` character in both Bash and Ruby), as seen in the [header of `update.sh`](https://github.com/Homebrew/brew/blob/cf7def0c68903814c6b4e04a55fe8f3cb3f5605e/Library/Homebrew/cmd/update.sh#L1-L10), which is printed with `brew update --help`.
## Unofficial external commands ## Unofficial external commands

View File

@ -27,7 +27,7 @@ A *formula* is a package definition written in Ruby. It can be created with `bre
| **bottle** | pre-built **keg** poured into a **rack** of the **Cellar** instead of building from upstream sources | `qt--6.5.1.ventura.bottle.tar.gz` | | **bottle** | pre-built **keg** poured into a **rack** of the **Cellar** instead of building from upstream sources | `qt--6.5.1.ventura.bottle.tar.gz` |
| **tab** | information about a **keg**, e.g. whether it was poured from a **bottle** or built from source | `/opt/homebrew/Cellar/foo/0.1/INSTALL_RECEIPT.json` | | **tab** | information about a **keg**, e.g. whether it was poured from a **bottle** or built from source | `/opt/homebrew/Cellar/foo/0.1/INSTALL_RECEIPT.json` |
| **Brew Bundle** | an [extension of Homebrew](https://github.com/Homebrew/homebrew-bundle) to describe dependencies | `brew 'myservice', restart_service: true` | | **Brew Bundle** | an [extension of Homebrew](https://github.com/Homebrew/homebrew-bundle) to describe dependencies | `brew 'myservice', restart_service: true` |
| **Brew Services** | an [extension of Homebrew](https://github.com/Homebrew/homebrew-services) to manage services | `brew services start myservice` | | **Brew Services** | the Homebrew command to manage background services | `brew services start myservice` |
## An introduction ## An introduction
@ -1042,7 +1042,7 @@ Another example would be configuration files that should not be overwritten on p
### Service files ### Service files
There are two ways to add `launchd` plists and `systemd` services to a formula, so that [`brew services`](https://github.com/Homebrew/homebrew-services) can pick them up: There are two ways to add `launchd` plists and `systemd` services to a formula, so that `brew services` can pick them up:
1. If the package already provides a service file the formula can reference it by name: 1. If the package already provides a service file the formula can reference it by name: