Merge pull request #15396 from apainintheneck/custom-service-name
Custom service name
This commit is contained in:
commit
946478aed4
@ -2,6 +2,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "language/python"
|
||||
require "utils/service"
|
||||
|
||||
# A formula's caveats.
|
||||
#
|
||||
@ -153,33 +154,32 @@ class Caveats
|
||||
end
|
||||
|
||||
def service_caveats
|
||||
return if !formula.plist && !formula.service? && !keg&.plist_installed?
|
||||
return if formula.service? && formula.service.command.blank?
|
||||
return if !formula.plist && !formula.service? && !Utils::Service.installed?(formula) && !keg&.plist_installed?
|
||||
return if formula.service? && !formula.service.command? && !Utils::Service.installed?(formula)
|
||||
|
||||
s = []
|
||||
|
||||
command = if formula.service?
|
||||
command = if formula.service.command?
|
||||
formula.service.manual_command
|
||||
else
|
||||
formula.plist_manual
|
||||
end
|
||||
|
||||
return <<~EOS if !which("launchctl") && formula.plist
|
||||
return <<~EOS if !Utils::Service.launchctl? && formula.plist
|
||||
#{Formatter.warning("Warning:")} #{formula.name} provides a launchd plist which can only be used on macOS!
|
||||
You can manually execute the service instead with:
|
||||
#{command}
|
||||
EOS
|
||||
|
||||
# Brew services only works with these two tools
|
||||
return <<~EOS if !which("systemctl") && !which("launchctl") && formula.service?
|
||||
return <<~EOS if !Utils::Service.systemctl? && !Utils::Service.launchctl? && formula.service.command?
|
||||
#{Formatter.warning("Warning:")} #{formula.name} provides a service which can only be used on macOS or systemd!
|
||||
You can manually execute the service instead with:
|
||||
#{command}
|
||||
EOS
|
||||
|
||||
is_running_service = formula.service? && quiet_system("ps aux | grep #{formula.service.command&.first}")
|
||||
startup = formula.service&.requires_root? || formula.plist_startup
|
||||
if is_running_service || (formula.plist && quiet_system("/bin/launchctl list #{formula.plist_name} &>/dev/null"))
|
||||
startup = formula.service.requires_root? || formula.plist_startup
|
||||
if Utils::Service.running?(formula)
|
||||
s << "To restart #{formula.full_name} after an upgrade:"
|
||||
s << " #{startup ? "sudo " : ""}brew services restart #{formula.full_name}"
|
||||
elsif startup
|
||||
@ -190,7 +190,7 @@ class Caveats
|
||||
s << " brew services start #{formula.full_name}"
|
||||
end
|
||||
|
||||
if formula.plist_manual || formula.service?
|
||||
if formula.plist_manual || formula.service.command?
|
||||
s << "Or, if you don't want/need a background service you can just run:"
|
||||
s << " #{command}"
|
||||
end
|
||||
|
@ -1022,13 +1022,13 @@ class Formula
|
||||
# The generated launchd {.plist} service name.
|
||||
sig { returns(String) }
|
||||
def plist_name
|
||||
"homebrew.mxcl.#{name}"
|
||||
service.plist_name
|
||||
end
|
||||
|
||||
# The generated service name.
|
||||
sig { returns(String) }
|
||||
def service_name
|
||||
"homebrew.#{name}"
|
||||
service.service_name
|
||||
end
|
||||
|
||||
# The generated launchd {.plist} file path.
|
||||
@ -1058,8 +1058,6 @@ class Formula
|
||||
|
||||
# The service specification of the software.
|
||||
def service
|
||||
return unless service?
|
||||
|
||||
@service ||= Homebrew::Service.new(self, &self.class.service)
|
||||
end
|
||||
|
||||
@ -2176,7 +2174,7 @@ class Formula
|
||||
"disabled" => disabled?,
|
||||
"disable_date" => disable_date,
|
||||
"disable_reason" => disable_reason,
|
||||
"service" => service&.serialize,
|
||||
"service" => (service.serialize if service?),
|
||||
"tap_git_head" => tap_git_head,
|
||||
"ruby_source_path" => ruby_source_path,
|
||||
"ruby_source_checksum" => {},
|
||||
|
@ -288,7 +288,7 @@ module FormulaCellarChecks
|
||||
def check_service_command(formula)
|
||||
return unless formula.prefix.directory?
|
||||
return unless formula.service?
|
||||
return if formula.service.command.blank?
|
||||
return unless formula.service.command?
|
||||
|
||||
"Service command does not exist" unless File.exist?(formula.service.command.first)
|
||||
end
|
||||
|
@ -1040,7 +1040,7 @@ on_request: installed_on_request?, options: options)
|
||||
return
|
||||
end
|
||||
|
||||
if formula.service? && formula.service.command.present?
|
||||
if formula.service? && formula.service.command?
|
||||
service_path = formula.systemd_service_path
|
||||
service_path.atomic_write(formula.service.to_systemd_unit)
|
||||
service_path.chmod 0644
|
||||
@ -1052,7 +1052,7 @@ on_request: installed_on_request?, options: options)
|
||||
end
|
||||
end
|
||||
|
||||
service = if formula.service? && formula.service.command.present?
|
||||
service = if formula.service? && formula.service.command?
|
||||
formula.service.to_plist
|
||||
elsif formula.plist
|
||||
formula.plist
|
||||
|
@ -260,14 +260,22 @@ module Formulary
|
||||
|
||||
if (service_hash = json_formula["service"])
|
||||
service_hash = Homebrew::Service.deserialize(service_hash)
|
||||
run_params = service_hash.delete(:run)
|
||||
service do
|
||||
T.bind(self, Homebrew::Service)
|
||||
if run_params.is_a?(Hash)
|
||||
|
||||
if (run_params = service_hash.delete(:run))
|
||||
case run_params
|
||||
when Hash
|
||||
run(**run_params)
|
||||
else
|
||||
when Array, String
|
||||
run run_params
|
||||
end
|
||||
end
|
||||
|
||||
if (name_params = service_hash.delete(:name))
|
||||
name(**name_params)
|
||||
end
|
||||
|
||||
service_hash.each do |key, arg|
|
||||
public_send(key, arg)
|
||||
end
|
||||
|
@ -21,15 +21,35 @@ module RuboCop
|
||||
share: :opt_share,
|
||||
}.freeze
|
||||
|
||||
# At least one of these methods must be defined in a service block.
|
||||
REQUIRED_METHOD_CALLS = [:run, :name].freeze
|
||||
|
||||
def audit_formula(_node, _class_node, _parent_class_node, body_node)
|
||||
service_node = find_block(body_node, :service)
|
||||
return if service_node.blank?
|
||||
|
||||
method_calls = service_node.each_descendant(:send).group_by(&:method_name)
|
||||
method_calls.delete(:service)
|
||||
|
||||
# NOTE: Solving the first problem here might solve the second one too
|
||||
# so we don't show both of them at the same time.
|
||||
if (method_calls.keys & REQUIRED_METHOD_CALLS).empty?
|
||||
offending_node(service_node)
|
||||
problem "Service blocks require `run` or `name` to be defined."
|
||||
elsif !method_calls.key?(:run)
|
||||
other_method_calls = method_calls.keys - [:name]
|
||||
if other_method_calls.any?
|
||||
offending_node(service_node)
|
||||
problem "`run` must be defined to use methods other than `name` like #{other_method_calls}."
|
||||
end
|
||||
end
|
||||
|
||||
# This check ensures that cellar paths like `bin` are not referenced
|
||||
# because their `opt_` variants are more portable and work with the
|
||||
# API.
|
||||
# because their `opt_` variants are more portable and work with the API.
|
||||
CELLAR_PATH_AUDIT_CORRECTIONS.each do |path, opt_path|
|
||||
find_every_method_call_by_name(service_node, path).each do |node|
|
||||
next unless method_calls.key?(path)
|
||||
|
||||
method_calls.fetch(path).each do |node|
|
||||
offending_node(node)
|
||||
problem "Use `#{opt_path}` instead of `#{path}` in service blocks." do |corrector|
|
||||
corrector.replace(node.source_range, opt_path)
|
||||
|
@ -28,7 +28,7 @@ module Homebrew
|
||||
@run_type = RUN_TYPE_IMMEDIATE
|
||||
@run_at_load = true
|
||||
@environment_variables = {}
|
||||
@service_block = block
|
||||
instance_eval(&block) if block
|
||||
end
|
||||
|
||||
sig { returns(Formula) }
|
||||
@ -36,6 +36,34 @@ module Homebrew
|
||||
@formula
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def default_plist_name
|
||||
"homebrew.mxcl.#{@formula.name}"
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def plist_name
|
||||
@plist_name ||= default_plist_name
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def default_service_name
|
||||
"homebrew.#{@formula.name}"
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def service_name
|
||||
@service_name ||= default_service_name
|
||||
end
|
||||
|
||||
sig { params(macos: T.nilable(String), linux: T.nilable(String)).void }
|
||||
def name(macos: nil, linux: nil)
|
||||
raise TypeError, "Service#name expects at least one String" if [macos, linux].none?(String)
|
||||
|
||||
@plist_name = macos if macos
|
||||
@service_name = linux if linux
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
command: T.nilable(T.any(T::Array[String], String, Pathname)),
|
||||
@ -162,7 +190,6 @@ module Homebrew
|
||||
# @return [Boolean]
|
||||
sig { returns(T::Boolean) }
|
||||
def requires_root?
|
||||
eval_service_block
|
||||
@require_root.present? && @require_root == true
|
||||
end
|
||||
|
||||
@ -198,7 +225,6 @@ module Homebrew
|
||||
# @return [Boolean]
|
||||
sig { returns(T::Boolean) }
|
||||
def keep_alive?
|
||||
eval_service_block
|
||||
@keep_alive.present? && @keep_alive[:always] != false
|
||||
end
|
||||
|
||||
@ -357,20 +383,22 @@ module Homebrew
|
||||
|
||||
sig { returns(T.nilable(T::Array[String])) }
|
||||
def command
|
||||
eval_service_block
|
||||
@run&.map(&:to_s)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def command?
|
||||
@run.present?
|
||||
end
|
||||
|
||||
# Returns the `String` command to run manually instead of the service.
|
||||
# @return [String]
|
||||
sig { returns(String) }
|
||||
def manual_command
|
||||
eval_service_block
|
||||
vars = @environment_variables.except(:PATH)
|
||||
.map { |k, v| "#{k}=\"#{v}\"" }
|
||||
|
||||
cmd = command
|
||||
out = vars + cmd if cmd.present?
|
||||
out = vars + command if command?
|
||||
out.join(" ")
|
||||
end
|
||||
|
||||
@ -378,7 +406,6 @@ module Homebrew
|
||||
# @return [Boolean]
|
||||
sig { returns(T::Boolean) }
|
||||
def timed?
|
||||
eval_service_block
|
||||
@run_type == RUN_TYPE_CRON || @run_type == RUN_TYPE_INTERVAL
|
||||
end
|
||||
|
||||
@ -388,7 +415,7 @@ module Homebrew
|
||||
def to_plist
|
||||
# command needs to be first because it initializes all other values
|
||||
base = {
|
||||
Label: @formula.plist_name,
|
||||
Label: plist_name,
|
||||
ProgramArguments: command,
|
||||
RunAtLoad: @run_at_load == true,
|
||||
}
|
||||
@ -487,10 +514,9 @@ module Homebrew
|
||||
WantedBy=timers.target
|
||||
|
||||
[Timer]
|
||||
Unit=#{@formula.service_name}
|
||||
Unit=#{service_name}
|
||||
EOS
|
||||
|
||||
eval_service_block
|
||||
options = []
|
||||
options << "Persistent=true" if @run_type == RUN_TYPE_CRON
|
||||
options << "OnUnitActiveSec=#{@interval}" if @run_type == RUN_TYPE_INTERVAL
|
||||
@ -504,19 +530,17 @@ module Homebrew
|
||||
timer + options.join("\n")
|
||||
end
|
||||
|
||||
# Only evaluate the service block once.
|
||||
sig { void }
|
||||
def eval_service_block
|
||||
return if @eval_service_block
|
||||
|
||||
instance_eval(&@service_block)
|
||||
@eval_service_block = true
|
||||
end
|
||||
|
||||
# Prepare the service hash for inclusion in the formula API JSON.
|
||||
sig { returns(Hash) }
|
||||
def serialize
|
||||
eval_service_block
|
||||
name_params = {
|
||||
macos: (plist_name if plist_name != default_plist_name),
|
||||
linux: (service_name if service_name != default_service_name),
|
||||
}.compact
|
||||
|
||||
unless command?
|
||||
return name_params.blank? ? {} : { name: name_params }
|
||||
end
|
||||
|
||||
cron_string = if @cron.present?
|
||||
[:Minute, :Hour, :Day, :Month, :Weekday]
|
||||
@ -527,6 +551,7 @@ module Homebrew
|
||||
sockets_string = "#{@sockets[:type]}://#{@sockets[:host]}:#{@sockets[:port]}" if @sockets.present?
|
||||
|
||||
{
|
||||
name: name_params.presence,
|
||||
run: @run_params,
|
||||
run_type: @run_type,
|
||||
interval: @interval,
|
||||
@ -551,6 +576,12 @@ module Homebrew
|
||||
sig { params(api_hash: Hash).returns(Hash) }
|
||||
def self.deserialize(api_hash)
|
||||
hash = {}
|
||||
hash[:name] = api_hash["name"].transform_keys(&:to_sym) if api_hash.key?("name")
|
||||
|
||||
# The service hash might not have a "run" command if it only documents
|
||||
# an existing service file with the "name" command.
|
||||
return hash unless api_hash.key?("run")
|
||||
|
||||
hash[:run] =
|
||||
case api_hash["run"]
|
||||
when String
|
||||
|
@ -32,6 +32,10 @@ describe Caveats do
|
||||
|
||||
describe "#caveats" do
|
||||
context "when f.plist is not nil", :needs_macos do
|
||||
before do
|
||||
allow(Utils::Service).to receive(:launchctl?).and_return(true)
|
||||
end
|
||||
|
||||
it "prints error when no launchd is present" do
|
||||
f = formula do
|
||||
url "foo-1.0"
|
||||
@ -39,7 +43,7 @@ describe Caveats do
|
||||
"plist_test.plist"
|
||||
end
|
||||
end
|
||||
allow_any_instance_of(Object).to receive(:which).with("launchctl").and_return(nil)
|
||||
expect(Utils::Service).to receive(:launchctl?).once.and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("provides a launchd plist which can only be used on macOS!")
|
||||
end
|
||||
|
||||
@ -50,7 +54,7 @@ describe Caveats do
|
||||
"plist_test.plist"
|
||||
end
|
||||
end
|
||||
expect(described_class.new(f).caveats).to include("login")
|
||||
expect(described_class.new(f).caveats).to include("restart at login")
|
||||
end
|
||||
|
||||
it "gives information about service" do
|
||||
@ -82,12 +86,25 @@ describe Caveats do
|
||||
expect(caveats).to include("WARNING:")
|
||||
expect(caveats).to include("tmux")
|
||||
end
|
||||
|
||||
# @todo This should get deprecated and the service block `plist_name` method should get used instead.
|
||||
it "prints info when there are custom service files" do
|
||||
f = formula do
|
||||
url "foo-1.0"
|
||||
def plist_name
|
||||
"custom.mxcl.foo"
|
||||
end
|
||||
end
|
||||
expect(Utils::Service).to receive(:installed?).with(f).once.and_return(true)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("restart at login")
|
||||
end
|
||||
end
|
||||
|
||||
context "when f.service is not nil" do
|
||||
context "when service block is defined" do
|
||||
before do
|
||||
allow_any_instance_of(Object).to receive(:which).with("launchctl").and_return(true)
|
||||
allow_any_instance_of(Object).to receive(:which).with("systemctl").and_return(true)
|
||||
allow(Utils::Service).to receive(:launchctl?).and_return(true)
|
||||
allow(Utils::Service).to receive(:systemctl?).and_return(true)
|
||||
end
|
||||
|
||||
it "prints warning when no service deamon is found" do
|
||||
@ -97,9 +114,8 @@ describe Caveats do
|
||||
run [bin/"cmd"]
|
||||
end
|
||||
end
|
||||
|
||||
allow_any_instance_of(Object).to receive(:which).with("launchctl").and_return(nil)
|
||||
allow_any_instance_of(Object).to receive(:which).with("systemctl").and_return(nil)
|
||||
expect(Utils::Service).to receive(:launchctl?).twice.and_return(false)
|
||||
expect(Utils::Service).to receive(:systemctl?).once.and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("service which can only be used on macOS or systemd!")
|
||||
end
|
||||
|
||||
@ -111,9 +127,7 @@ describe Caveats do
|
||||
require_root true
|
||||
end
|
||||
end
|
||||
cmd = "#{HOMEBREW_CELLAR}/formula_name/1.0/bin/cmd"
|
||||
allow(Homebrew).to receive(:_system).and_return(true)
|
||||
allow(Homebrew).to receive(:_system).with("ps aux | grep #{cmd}").and_return(false)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("startup")
|
||||
end
|
||||
|
||||
@ -124,10 +138,8 @@ describe Caveats do
|
||||
run [bin/"cmd"]
|
||||
end
|
||||
end
|
||||
cmd = "#{HOMEBREW_CELLAR}/formula_name/1.0/bin/cmd"
|
||||
allow(Homebrew).to receive(:_system).and_return(true)
|
||||
expect(Homebrew).to receive(:_system).with("ps aux | grep #{cmd}").and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("login")
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("restart at login")
|
||||
end
|
||||
|
||||
it "gives information about require_root restarting services after upgrade" do
|
||||
@ -138,10 +150,8 @@ describe Caveats do
|
||||
require_root true
|
||||
end
|
||||
end
|
||||
cmd = "#{HOMEBREW_CELLAR}/formula_name/1.0/bin/cmd"
|
||||
f_obj = described_class.new(f)
|
||||
allow(Homebrew).to receive(:_system).and_return(true)
|
||||
expect(Homebrew).to receive(:_system).with("ps aux | grep #{cmd}").and_return(true)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(true)
|
||||
expect(f_obj.caveats).to include(" sudo brew services restart #{f.full_name}")
|
||||
end
|
||||
|
||||
@ -152,10 +162,8 @@ describe Caveats do
|
||||
run [bin/"cmd"]
|
||||
end
|
||||
end
|
||||
cmd = "#{HOMEBREW_CELLAR}/formula_name/1.0/bin/cmd"
|
||||
f_obj = described_class.new(f)
|
||||
allow(Homebrew).to receive(:_system).and_return(true)
|
||||
expect(Homebrew).to receive(:_system).with("ps aux | grep #{cmd}").and_return(true)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(true)
|
||||
expect(f_obj.caveats).to include(" brew services restart #{f.full_name}")
|
||||
end
|
||||
|
||||
@ -167,10 +175,8 @@ describe Caveats do
|
||||
require_root true
|
||||
end
|
||||
end
|
||||
cmd = "#{HOMEBREW_CELLAR}/formula_name/1.0/bin/cmd"
|
||||
f_obj = described_class.new(f)
|
||||
allow(Homebrew).to receive(:_system).and_return(true)
|
||||
allow(Homebrew).to receive(:_system).with("ps aux | grep #{cmd}").and_return(false)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(false)
|
||||
expect(f_obj.caveats).to include(" sudo brew services start #{f.full_name}")
|
||||
end
|
||||
|
||||
@ -181,10 +187,8 @@ describe Caveats do
|
||||
run [bin/"cmd"]
|
||||
end
|
||||
end
|
||||
cmd = "#{HOMEBREW_CELLAR}/formula_name/1.0/bin/cmd"
|
||||
f_obj = described_class.new(f)
|
||||
allow(Homebrew).to receive(:_system).and_return(true)
|
||||
allow(Homebrew).to receive(:_system).with("ps aux | grep #{cmd}").and_return(false)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(false)
|
||||
expect(f_obj.caveats).to include(" brew services start #{f.full_name}")
|
||||
end
|
||||
|
||||
@ -202,6 +206,18 @@ describe Caveats do
|
||||
expect(caveats).to include("if you don't want/need a background service")
|
||||
expect(caveats).to include("VAR=\"foo\" #{cmd} start")
|
||||
end
|
||||
|
||||
it "prints info when there are custom service files" do
|
||||
f = formula do
|
||||
url "foo-1.0"
|
||||
service do
|
||||
name macos: "custom.mxcl.foo", linux: "custom.foo"
|
||||
end
|
||||
end
|
||||
expect(Utils::Service).to receive(:installed?).with(f).once.and_return(true)
|
||||
expect(Utils::Service).to receive(:running?).with(f).once.and_return(false)
|
||||
expect(described_class.new(f).caveats).to include("restart at login")
|
||||
end
|
||||
end
|
||||
|
||||
context "when f.keg_only is not nil" do
|
||||
|
@ -218,21 +218,21 @@ describe FormulaInstaller do
|
||||
|
||||
it "works if service is set" do
|
||||
formula = Testball.new
|
||||
service = Homebrew::Service.new(formula)
|
||||
launchd_service_path = formula.launchd_service_path
|
||||
service_path = formula.systemd_service_path
|
||||
service = Homebrew::Service.new(formula)
|
||||
formula.opt_prefix.mkpath
|
||||
|
||||
expect(formula).to receive(:plist).and_return(nil)
|
||||
expect(formula).to receive(:service?).exactly(3).and_return(true)
|
||||
expect(formula).to receive(:service).exactly(5).and_return(service)
|
||||
expect(formula).to receive(:service).exactly(7).and_return(service)
|
||||
expect(formula).to receive(:launchd_service_path).and_call_original
|
||||
expect(formula).to receive(:systemd_service_path).and_call_original
|
||||
|
||||
expect(service).to receive(:timed?).and_return(false)
|
||||
expect(service).to receive(:command?).exactly(2).and_return(true)
|
||||
expect(service).to receive(:to_plist).and_return("plist")
|
||||
expect(service).to receive(:to_systemd_unit).and_return("unit")
|
||||
expect(service).to receive(:command).exactly(2).and_return("/bin/sh")
|
||||
|
||||
installer = described_class.new(formula)
|
||||
expect do
|
||||
@ -245,24 +245,24 @@ describe FormulaInstaller do
|
||||
|
||||
it "works if timed service is set" do
|
||||
formula = Testball.new
|
||||
service = Homebrew::Service.new(formula)
|
||||
launchd_service_path = formula.launchd_service_path
|
||||
service_path = formula.systemd_service_path
|
||||
timer_path = formula.systemd_timer_path
|
||||
service = Homebrew::Service.new(formula)
|
||||
formula.opt_prefix.mkpath
|
||||
|
||||
expect(formula).to receive(:plist).and_return(nil)
|
||||
expect(formula).to receive(:service?).exactly(3).and_return(true)
|
||||
expect(formula).to receive(:service).exactly(6).and_return(service)
|
||||
expect(formula).to receive(:service).exactly(9).and_return(service)
|
||||
expect(formula).to receive(:launchd_service_path).and_call_original
|
||||
expect(formula).to receive(:systemd_service_path).and_call_original
|
||||
expect(formula).to receive(:systemd_timer_path).and_call_original
|
||||
|
||||
expect(service).to receive(:to_plist).and_return("plist")
|
||||
expect(service).to receive(:timed?).and_return(true)
|
||||
expect(service).to receive(:command?).exactly(2).and_return(true)
|
||||
expect(service).to receive(:to_plist).and_return("plist")
|
||||
expect(service).to receive(:to_systemd_unit).and_return("unit")
|
||||
expect(service).to receive(:to_systemd_timer).and_return("timer")
|
||||
expect(service).to receive(:command).exactly(2).and_return("/bin/sh")
|
||||
|
||||
installer = described_class.new(formula)
|
||||
expect do
|
||||
|
@ -701,7 +701,7 @@ describe Formula do
|
||||
url "https://brew.sh/test-1.0.tbz"
|
||||
end
|
||||
|
||||
expect(f.service).to be_nil
|
||||
expect(f.service.serialize).to eq({})
|
||||
end
|
||||
|
||||
specify "service complicated" do
|
||||
@ -717,7 +717,8 @@ describe Formula do
|
||||
keep_alive true
|
||||
end
|
||||
end
|
||||
expect(f.service).not_to be_nil
|
||||
expect(f.service.serialize.keys)
|
||||
.to contain_exactly(:run, :run_type, :error_log_path, :log_path, :working_dir, :keep_alive)
|
||||
end
|
||||
|
||||
specify "service uses simple run" do
|
||||
@ -728,7 +729,20 @@ describe Formula do
|
||||
end
|
||||
end
|
||||
|
||||
expect(f.service).not_to be_nil
|
||||
expect(f.service.serialize.keys).to contain_exactly(:run, :run_type)
|
||||
end
|
||||
|
||||
specify "service with only custom names" do
|
||||
f = formula do
|
||||
url "https://brew.sh/test-1.0.tbz"
|
||||
service do
|
||||
name macos: "custom.macos.beanstalkd", linux: "custom.linux.beanstalkd"
|
||||
end
|
||||
end
|
||||
|
||||
expect(f.plist_name).to eq("custom.macos.beanstalkd")
|
||||
expect(f.service_name).to eq("custom.linux.beanstalkd")
|
||||
expect(f.service.serialize.keys).to contain_exactly(:name)
|
||||
end
|
||||
|
||||
specify "service helpers return data" do
|
||||
|
@ -272,6 +272,7 @@ describe Formulary do
|
||||
"link_overwrite" => ["bin/abc"],
|
||||
"caveats" => "example caveat string\n/$HOME\n$HOMEBREW_PREFIX",
|
||||
"service" => {
|
||||
"name" => { macos: "custom.launchd.name", linux: "custom.systemd.name" },
|
||||
"run" => ["$HOMEBREW_PREFIX/opt/formula_name/bin/beanstalkd", "test"],
|
||||
"run_type" => "immediate",
|
||||
"working_dir" => "/$HOME",
|
||||
@ -362,6 +363,8 @@ describe Formulary do
|
||||
expect(formula.service.command).to eq(["#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd", "test"])
|
||||
expect(formula.service.run_type).to eq(:immediate)
|
||||
expect(formula.service.working_dir).to eq(Dir.home)
|
||||
expect(formula.plist_name).to eq("custom.launchd.name")
|
||||
expect(formula.service_name).to eq("custom.systemd.name")
|
||||
|
||||
expect do
|
||||
formula.install
|
||||
|
@ -5,6 +5,46 @@ require "rubocops/service"
|
||||
describe RuboCop::Cop::FormulaAudit::Service do
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
it "reports offenses when a service block is missing a required command" do
|
||||
expect_offense(<<~RUBY)
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
|
||||
service do
|
||||
^^^^^^^^^^ FormulaAudit/Service: Service blocks require `run` or `name` to be defined.
|
||||
run_type :cron
|
||||
working_dir "/tmp/example"
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it "reports no offenses when a service block only includes custom names" do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
|
||||
service do
|
||||
name macos: "custom.mcxl.foo", linux: "custom.foo"
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it "reports offenses when a service block includes more than custom names and no run command" do
|
||||
expect_offense(<<~RUBY)
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
|
||||
service do
|
||||
^^^^^^^^^^ FormulaAudit/Service: `run` must be defined to use methods other than `name` like [:working_dir].
|
||||
name macos: "custom.mcxl.foo", linux: "custom.foo"
|
||||
working_dir "/tmp/example"
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it "reports offenses when a formula's service block uses cellar paths" do
|
||||
expect_offense(<<~RUBY)
|
||||
class Foo < Formula
|
||||
@ -31,7 +71,7 @@ describe RuboCop::Cop::FormulaAudit::Service do
|
||||
RUBY
|
||||
end
|
||||
|
||||
it "reports no offenses when a formula's service block only uses opt paths" do
|
||||
it "reports no offenses when a service block only uses opt paths" do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class Bin < Formula
|
||||
url "https://brew.sh/foo-1.0.tgz"
|
||||
|
@ -949,6 +949,10 @@ describe Homebrew::Service do
|
||||
describe ".deserialize" do
|
||||
let(:serialized_hash) do
|
||||
{
|
||||
"name" => {
|
||||
"linux" => "custom.systemd.name",
|
||||
"macos" => "custom.launchd.name",
|
||||
},
|
||||
"environment_variables" => {
|
||||
"PATH" => "$HOMEBREW_PREFIX/bin:$HOMEBREW_PREFIX/sbin:/usr/bin:/bin:/usr/sbin:/sbin",
|
||||
},
|
||||
@ -961,6 +965,10 @@ describe Homebrew::Service do
|
||||
|
||||
let(:deserialized_hash) do
|
||||
{
|
||||
name: {
|
||||
linux: "custom.systemd.name",
|
||||
macos: "custom.launchd.name",
|
||||
},
|
||||
environment_variables: {
|
||||
PATH: "#{HOMEBREW_PREFIX}/bin:#{HOMEBREW_PREFIX}/sbin:/usr/bin:/bin:/usr/sbin:/sbin",
|
||||
},
|
||||
|
50
Library/Homebrew/utils/service.rb
Normal file
50
Library/Homebrew/utils/service.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Utils
|
||||
# Helpers for `brew services` related code.
|
||||
module Service
|
||||
# Check if a service is running for a specified formula.
|
||||
sig { params(formula: Formula).returns(T::Boolean) }
|
||||
def self.running?(formula)
|
||||
if launchctl?
|
||||
quiet_system(launchctl, "list", formula.plist_name)
|
||||
elsif systemctl?
|
||||
quiet_system(systemctl, "is-active", "--quiet", formula.service_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Check if a service file is installed in the expected location.
|
||||
sig { params(formula: Formula).returns(T::Boolean) }
|
||||
def self.installed?(formula)
|
||||
(launchctl? && formula.launchd_service_path.exist?) ||
|
||||
(systemctl? && formula.systemd_service_path.exist?)
|
||||
end
|
||||
|
||||
# Path to launchctl binary.
|
||||
sig { returns(T.nilable(Pathname)) }
|
||||
def self.launchctl
|
||||
return @launchctl if defined? @launchctl
|
||||
|
||||
@launchctl = which("launchctl")
|
||||
end
|
||||
|
||||
# Path to systemctl binary.
|
||||
sig { returns(T.nilable(Pathname)) }
|
||||
def self.systemctl
|
||||
return @systemctl if defined? @systemctl
|
||||
|
||||
@systemctl = which("systemctl")
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.launchctl?
|
||||
!launchctl.nil?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.systemctl?
|
||||
!systemctl.nil?
|
||||
end
|
||||
end
|
||||
end
|
7
Library/Homebrew/utils/service.rbi
Normal file
7
Library/Homebrew/utils/service.rbi
Normal file
@ -0,0 +1,7 @@
|
||||
# typed: strict
|
||||
|
||||
module Utils
|
||||
module Service
|
||||
include Kernel
|
||||
end
|
||||
end
|
@ -871,13 +871,17 @@ Another example would be configuration files that should not be overwritten on p
|
||||
|
||||
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:
|
||||
|
||||
1. If the package already provides a service file the formula can install it into the prefix:
|
||||
1. If the package already provides a service file the formula can reference it by name:
|
||||
|
||||
```ruby
|
||||
prefix.install_symlink "file.plist" => "#{plist_name}.plist"
|
||||
prefix.install_symlink "file.service" => "#{service_name}.service"
|
||||
service do
|
||||
name macos: "custom.launchd.name",
|
||||
linux: "custom.systemd.name"
|
||||
end
|
||||
```
|
||||
|
||||
To find the file we append `.plist` to the `launchd` service name and `.service` to the `systemd` service name internally.
|
||||
|
||||
2. If the formula does not provide a service file you can generate one using the following stanza:
|
||||
|
||||
```ruby
|
||||
@ -900,7 +904,7 @@ There are two ways to add `launchd` plists and `systemd` services to a formula,
|
||||
|
||||
#### Service block methods
|
||||
|
||||
This table lists the options you can set within a `service` block. Only the `run` field is required which indicates what to run.
|
||||
This table lists the options you can set within a `service` block. The `run` or `name` field must be defined inside the service block. The `run` field indicates what command to run and is required before using fields other than `name`.
|
||||
|
||||
| method | default | macOS | Linux | description |
|
||||
| ----------------------- | ------------ | :---: | :---: | ----------- |
|
||||
@ -921,6 +925,7 @@ This table lists the options you can set within a `service` block. Only the `run
|
||||
| `process_type` | - | yes | no-op | type of process to manage: `:background`, `:standard`, `:interactive` or `:adaptive`
|
||||
| `macos_legacy_timers` | - | yes | no-op | timers created by `launchd` jobs are coalesced unless this is set
|
||||
| `sockets` | - | yes | no-op | socket that is created as an accesspoint to the service
|
||||
| `name` | - | yes | yes | a hash with the `launchd` service name on macOS and/or the `systemd` service name on Linux
|
||||
|
||||
For services that are kept alive after starting you can use the default `run_type`:
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user