Skip to content

Commit 3b61b44

Browse files
authored
Merge pull request #20324 from Homebrew/dug/low-shape-service
Reduce shape variations in Homebrew::Service
2 parents d6111a2 + 0a4b064 commit 3b61b44

File tree

3 files changed

+69
-61
lines changed

3 files changed

+69
-61
lines changed

Library/Homebrew/formula_cellar_checks.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def check_service_command(formula)
306306
return unless formula.service?
307307
return unless formula.service.command?
308308

309-
"Service command does not exist" unless File.exist?(T.must(formula.service.command).first)
309+
"Service command does not exist" unless File.exist?(formula.service.command.first)
310310
end
311311

312312
sig { params(formula: Formula).returns(T.nilable(String)) }

Library/Homebrew/service.rb

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "ipaddr"
@@ -23,13 +23,38 @@ class Service
2323
PROCESS_TYPE_ADAPTIVE = :adaptive
2424

2525
KEEP_ALIVE_KEYS = [:always, :successful_exit, :crashed, :path].freeze
26+
SOCKET_STRING_REGEX = %r{^([a-z]+)://(.+):([0-9]+)$}i
27+
28+
RunParam = T.type_alias { T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)) }
29+
Sockets = T.type_alias { T::Hash[Symbol, { host: String, port: String, type: String }] }
2630

27-
# sig { params(formula: Formula).void }
31+
sig { returns(String) }
32+
attr_reader :plist_name, :service_name
33+
34+
sig { params(formula: Formula, block: T.nilable(T.proc.void)).void }
2835
def initialize(formula, &block)
36+
@cron = T.let({}, T::Hash[Symbol, T.any(Integer, String)])
37+
@environment_variables = T.let({}, T::Hash[Symbol, String])
38+
@error_log_path = T.let(nil, T.nilable(String))
2939
@formula = formula
30-
@run_type = RUN_TYPE_IMMEDIATE
31-
@run_at_load = true
32-
@environment_variables = {}
40+
@input_path = T.let(nil, T.nilable(String))
41+
@interval = T.let(nil, T.nilable(Integer))
42+
@keep_alive = T.let({}, T::Hash[Symbol, T.untyped])
43+
@launch_only_once = T.let(false, T::Boolean)
44+
@log_path = T.let(nil, T.nilable(String))
45+
@macos_legacy_timers = T.let(false, T::Boolean)
46+
@plist_name = T.let(default_plist_name, String)
47+
@process_type = T.let(nil, T.nilable(Symbol))
48+
@require_root = T.let(false, T::Boolean)
49+
@restart_delay = T.let(nil, T.nilable(Integer))
50+
@root_dir = T.let(nil, T.nilable(String))
51+
@run = T.let([], T::Array[String])
52+
@run_at_load = T.let(true, T::Boolean)
53+
@run_params = T.let(nil, T.any(RunParam, T::Hash[Symbol, RunParam]))
54+
@run_type = T.let(RUN_TYPE_IMMEDIATE, Symbol)
55+
@service_name = T.let(default_service_name, String)
56+
@sockets = T.let({}, Sockets)
57+
@working_dir = T.let(nil, T.nilable(String))
3358
instance_eval(&block) if block
3459
end
3560

@@ -43,21 +68,11 @@ def default_plist_name
4368
"homebrew.mxcl.#{@formula.name}"
4469
end
4570

46-
sig { returns(String) }
47-
def plist_name
48-
@plist_name ||= default_plist_name
49-
end
50-
5171
sig { returns(String) }
5272
def default_service_name
5373
"homebrew.#{@formula.name}"
5474
end
5575

56-
sig { returns(String) }
57-
def service_name
58-
@service_name ||= default_service_name
59-
end
60-
6176
sig { params(macos: T.nilable(String), linux: T.nilable(String)).void }
6277
def name(macos: nil, linux: nil)
6378
raise TypeError, "Service#name expects at least one String" if [macos, linux].none?(String)
@@ -68,9 +83,9 @@ def name(macos: nil, linux: nil)
6883

6984
sig {
7085
params(
71-
command: T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)),
72-
macos: T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)),
73-
linux: T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)),
86+
command: T.nilable(RunParam),
87+
macos: T.nilable(RunParam),
88+
linux: T.nilable(RunParam),
7489
).returns(T.nilable(T::Array[T.any(String, Pathname)]))
7590
}
7691
def run(command = nil, macos: nil, linux: nil)
@@ -86,9 +101,9 @@ def run(command = nil, macos: nil, linux: nil)
86101
when nil
87102
@run
88103
when String, Pathname
89-
@run = [command]
104+
@run = [command.to_s]
90105
when Array
91-
@run = command
106+
@run = command.map(&:to_s)
92107
end
93108
end
94109

@@ -161,18 +176,17 @@ def keep_alive(value = nil)
161176
end
162177
end
163178

164-
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
179+
sig { params(value: T.nilable(T::Boolean)).returns(T::Boolean) }
165180
def require_root(value = nil)
166181
case value
167182
when nil
168183
@require_root
169-
when true, false
184+
when TrueClass, FalseClass
170185
@require_root = value
171186
end
172187
end
173188

174189
# Returns a `Boolean` describing if a service requires root access.
175-
# @return [Boolean]
176190
sig { returns(T::Boolean) }
177191
def requires_root?
178192
@require_root.present? && @require_root == true
@@ -183,16 +197,14 @@ def run_at_load(value = nil)
183197
case value
184198
when nil
185199
@run_at_load
186-
when true, false
200+
when TrueClass, FalseClass
187201
@run_at_load = value
188202
end
189203
end
190204

191-
SOCKET_STRING_REGEX = %r{^([a-z]+)://(.+):([0-9]+)$}i
192-
193205
sig {
194206
params(value: T.nilable(T.any(String, T::Hash[Symbol, String])))
195-
.returns(T.nilable(T::Hash[Symbol, T::Hash[Symbol, String]]))
207+
.returns(T::Hash[Symbol, T::Hash[Symbol, String]])
196208
}
197209
def sockets(value = nil)
198210
return @sockets if value.nil?
@@ -204,9 +216,11 @@ def sockets(value = nil)
204216
value
205217
end.transform_values do |socket_string|
206218
match = socket_string.match(SOCKET_STRING_REGEX)
207-
raise TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>" if match.blank?
219+
raise TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>" unless match
208220

209-
type, host, port = match.captures
221+
type = T.must(match[1])
222+
host = T.must(match[2])
223+
port = T.must(match[3])
210224

211225
begin
212226
IPAddr.new(host)
@@ -219,18 +233,17 @@ def sockets(value = nil)
219233
end
220234

221235
# Returns a `Boolean` describing if a service is set to be kept alive.
222-
# @return [Boolean]
223236
sig { returns(T::Boolean) }
224237
def keep_alive?
225-
@keep_alive.present? && @keep_alive[:always] != false
238+
!@keep_alive.empty? && @keep_alive[:always] != false
226239
end
227240

228-
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
241+
sig { params(value: T.nilable(T::Boolean)).returns(T::Boolean) }
229242
def launch_only_once(value = nil)
230243
case value
231244
when nil
232245
@launch_only_once
233-
when true, false
246+
when TrueClass, FalseClass
234247
@launch_only_once = value
235248
end
236249
end
@@ -281,13 +294,13 @@ def interval(value = nil)
281294
end
282295
end
283296

284-
sig { params(value: T.nilable(String)).returns(T.nilable(Hash)) }
297+
sig { params(value: T.nilable(String)).returns(T::Hash[Symbol, T.any(Integer, String)]) }
285298
def cron(value = nil)
286299
case value
287300
when nil
288301
@cron
289302
when String
290-
@cron = parse_cron(T.must(value))
303+
@cron = parse_cron(value)
291304
end
292305
end
293306

@@ -345,12 +358,12 @@ def environment_variables(variables = {})
345358
end
346359
end
347360

348-
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
361+
sig { params(value: T.nilable(T::Boolean)).returns(T::Boolean) }
349362
def macos_legacy_timers(value = nil)
350363
case value
351364
when nil
352365
@macos_legacy_timers
353-
when true, false
366+
when TrueClass, FalseClass
354367
@macos_legacy_timers = value
355368
end
356369
end
@@ -362,36 +375,33 @@ def std_service_path_env
362375
"#{HOMEBREW_PREFIX}/bin:#{HOMEBREW_PREFIX}/sbin:/usr/bin:/bin:/usr/sbin:/sbin"
363376
end
364377

365-
sig { returns(T.nilable(T::Array[String])) }
378+
sig { returns(T::Array[String]) }
366379
def command
367-
@run&.map(&:to_s)&.map { |arg| arg.start_with?("~") ? File.expand_path(arg) : arg }
380+
@run.map(&:to_s).map { |arg| arg.start_with?("~") ? File.expand_path(arg) : arg }
368381
end
369382

370383
sig { returns(T::Boolean) }
371384
def command?
372-
@run.present?
385+
!@run.empty?
373386
end
374387

375388
# Returns the `String` command to run manually instead of the service.
376-
# @return [String]
377389
sig { returns(String) }
378390
def manual_command
379391
vars = @environment_variables.except(:PATH)
380392
.map { |k, v| "#{k}=\"#{v}\"" }
381393

382-
out = vars + T.must(command).map { |arg| Utils::Shell.sh_quote(arg) } if command?
383-
out.join(" ")
394+
vars.concat(command.map { |arg| Utils::Shell.sh_quote(arg) })
395+
vars.join(" ")
384396
end
385397

386398
# Returns a `Boolean` describing if a service is timed.
387-
# @return [Boolean]
388399
sig { returns(T::Boolean) }
389400
def timed?
390401
@run_type == RUN_TYPE_CRON || @run_type == RUN_TYPE_INTERVAL
391402
end
392403

393404
# Returns a `String` plist.
394-
# @return [String]
395405
sig { returns(String) }
396406
def to_plist
397407
# command needs to be first because it initializes all other values
@@ -425,7 +435,7 @@ def to_plist
425435
end
426436
end
427437

428-
if @sockets.present?
438+
unless @sockets.empty?
429439
base[:Sockets] = {}
430440
@sockets.each do |name, info|
431441
base[:Sockets][name] = {
@@ -436,7 +446,7 @@ def to_plist
436446
end
437447
end
438448

439-
if @cron.present? && @run_type == RUN_TYPE_CRON
449+
if !@cron.empty? && @run_type == RUN_TYPE_CRON
440450
base[:StartCalendarInterval] = @cron.reject { |_, value| value == "*" }
441451
end
442452

@@ -452,12 +462,11 @@ def to_plist
452462
end
453463

454464
# Returns a `String` systemd unit.
455-
# @return [String]
456465
sig { returns(String) }
457466
def to_systemd_unit
458467
# command needs to be first because it initializes all other values
459-
cmd = command&.map { |arg| Utils::Service.systemd_quote(arg) }
460-
&.join(" ")
468+
cmd = command.map { |arg| Utils::Service.systemd_quote(arg) }
469+
.join(" ")
461470

462471
options = []
463472
options << "Type=#{(@launch_only_once == true) ? "oneshot" : "simple"}"
@@ -485,7 +494,6 @@ def to_systemd_unit
485494
end
486495

487496
# Returns a `String` systemd unit timer.
488-
# @return [String]
489497
sig { returns(String) }
490498
def to_systemd_timer
491499
options = []
@@ -512,7 +520,7 @@ def to_systemd_timer
512520
end
513521

514522
# Prepare the service hash for inclusion in the formula API JSON.
515-
sig { returns(Hash) }
523+
sig { returns(T::Hash[Symbol, T.untyped]) }
516524
def to_hash
517525
name_params = {
518526
macos: (plist_name if plist_name != default_plist_name),
@@ -521,13 +529,13 @@ def to_hash
521529

522530
return { name: name_params }.compact_blank if @run_params.blank?
523531

524-
cron_string = if @cron.present?
532+
cron_string = unless @cron.empty?
525533
[:Minute, :Hour, :Day, :Month, :Weekday]
526534
.map { |key| @cron[key].to_s }
527535
.join(" ")
528536
end
529537

530-
sockets_var = if @sockets.present?
538+
sockets_var = unless @sockets.empty?
531539
@sockets.transform_values { |info| "#{info[:type]}://#{info[:host]}:#{info[:port]}" }
532540
.then do |sockets_hash|
533541
# TODO: Remove this code when all users are running on versions of Homebrew
@@ -561,11 +569,11 @@ def to_hash
561569
process_type: @process_type,
562570
macos_legacy_timers: @macos_legacy_timers,
563571
sockets: sockets_var,
564-
}.compact
572+
}.compact_blank
565573
end
566574

567575
# Turn the service API hash values back into what is expected by the formula DSL.
568-
sig { params(api_hash: Hash).returns(Hash) }
576+
sig { params(api_hash: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
569577
def self.from_hash(api_hash)
570578
hash = {}
571579
hash[:name] = api_hash["name"].transform_keys(&:to_sym) if api_hash.key?("name")

Library/Homebrew/test/service_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ def stub_formula_with_service_sockets(sockets_var)
984984
expect(command).to eq(["#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd", "test"])
985985
end
986986

987-
it "returns nil on Linux", :needs_linux do
987+
it "returns empty on Linux", :needs_linux do
988988
f = stub_formula do
989989
service do
990990
run macos: [opt_bin/"beanstalkd", "test"]
@@ -993,7 +993,7 @@ def stub_formula_with_service_sockets(sockets_var)
993993
end
994994

995995
command = f.service.command
996-
expect(command).to be_nil
996+
expect(command).to be_empty
997997
end
998998

999999
it "returns @run data on macOS", :needs_macos do
@@ -1008,7 +1008,7 @@ def stub_formula_with_service_sockets(sockets_var)
10081008
expect(command).to eq(["#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd", "test"])
10091009
end
10101010

1011-
it "returns nil on macOS", :needs_macos do
1011+
it "returns empty on macOS", :needs_macos do
10121012
f = stub_formula do
10131013
service do
10141014
run linux: [opt_bin/"beanstalkd", "test"]
@@ -1017,7 +1017,7 @@ def stub_formula_with_service_sockets(sockets_var)
10171017
end
10181018

10191019
command = f.service.command
1020-
expect(command).to be_nil
1020+
expect(command).to be_empty
10211021
end
10221022

10231023
it "returns appropriate @run data on Linux", :needs_linux do

0 commit comments

Comments
 (0)