diff --git a/Library/Homebrew/cmd/services.rb b/Library/Homebrew/cmd/services.rb index 044db3865b..e2dcd80b95 100644 --- a/Library/Homebrew/cmd/services.rb +++ b/Library/Homebrew/cmd/services.rb @@ -3,7 +3,6 @@ require "abstract_command" require "services/system" -require "services/cli" require "services/commands/list" require "services/commands/cleanup" require "services/commands/info" @@ -73,36 +72,40 @@ module Homebrew # Keep this after the .parse to keep --help fast. require "utils" - if !::Services::System.launchctl? && !::Services::System.systemctl? + if !Homebrew::Services::System.launchctl? && !Homebrew::Services::System.systemctl? raise UsageError, "`brew services` is supported only on macOS or Linux (with systemd)!" end if (sudo_service_user = args.sudo_service_user) - unless ::Services::System.root? + unless Homebrew::Services::System.root? raise UsageError, "`brew services` is supported only when running as root!" end - unless ::Services::System.launchctl? + unless Homebrew::Services::System.launchctl? raise UsageError, "`brew services --sudo-service-user` is currently supported only on macOS " \ "(but we'd love a PR to add Linux support)!" end - ::Services::Cli.sudo_service_user = sudo_service_user + Homebrew::Services::Cli.sudo_service_user = sudo_service_user end # Parse arguments. subcommand, formula, = args.named - if [*::Services::Commands::List::TRIGGERS, *::Services::Commands::Cleanup::TRIGGERS].include?(subcommand) + no_named_formula_commands = [ + *Homebrew::Services::Commands::List::TRIGGERS, + *Homebrew::Services::Commands::Cleanup::TRIGGERS, + ] + if no_named_formula_commands.include?(subcommand) 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? end if args.file - if ::Services::Commands::Start::TRIGGERS.exclude?(subcommand) + if Homebrew::Services::Commands::Start::TRIGGERS.exclude?(subcommand) raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!" elsif args.all? raise UsageError, "The start subcommand does not accept the --all and --file= arguments at the same time!" @@ -113,14 +116,20 @@ module Homebrew targets = if args.all? if subcommand == "start" - ::Services::Formulae.available_services(loaded: false, skip_root: !::Services::System.root?) + Homebrew::Services::Formulae.available_services( + loaded: false, + skip_root: !Homebrew::Services::System.root?, + ) elsif subcommand == "stop" - ::Services::Formulae.available_services(loaded: true, skip_root: !::Services::System.root?) + Homebrew::Services::Formulae.available_services( + loaded: true, + skip_root: !Homebrew::Services::System.root?, + ) else - ::Services::Formulae.available_services + Homebrew::Services::Formulae.available_services end elsif formula - [::Services::FormulaWrapper.new(Formulary.factory(formula))] + [Homebrew::Services::FormulaWrapper.new(Formulary.factory(formula))] else [] end @@ -128,30 +137,30 @@ module Homebrew # Exit successfully if --all was used but there is nothing to do return if args.all? && targets.empty? - if ::Services::System.systemctl? + if Homebrew::Services::System.systemctl? ENV["DBUS_SESSION_BUS_ADDRESS"] = ENV.fetch("HOMEBREW_DBUS_SESSION_BUS_ADDRESS", nil) ENV["XDG_RUNTIME_DIR"] = ENV.fetch("HOMEBREW_XDG_RUNTIME_DIR", nil) end # Dispatch commands and aliases. case subcommand.presence - when *::Services::Commands::List::TRIGGERS - ::Services::Commands::List.run(json: args.json?) - when *::Services::Commands::Cleanup::TRIGGERS - ::Services::Commands::Cleanup.run - when *::Services::Commands::Info::TRIGGERS - ::Services::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?) - when *::Services::Commands::Restart::TRIGGERS - ::Services::Commands::Restart.run(targets, verbose: args.verbose?) - when *::Services::Commands::Run::TRIGGERS - ::Services::Commands::Run.run(targets, verbose: args.verbose?) - when *::Services::Commands::Start::TRIGGERS - ::Services::Commands::Start.run(targets, args.file, verbose: args.verbose?) - when *::Services::Commands::Stop::TRIGGERS + when *Homebrew::Services::Commands::List::TRIGGERS + Homebrew::Services::Commands::List.run(json: args.json?) + when *Homebrew::Services::Commands::Cleanup::TRIGGERS + Homebrew::Services::Commands::Cleanup.run + when *Homebrew::Services::Commands::Info::TRIGGERS + Homebrew::Services::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?) + when *Homebrew::Services::Commands::Restart::TRIGGERS + Homebrew::Services::Commands::Restart.run(targets, verbose: args.verbose?) + when *Homebrew::Services::Commands::Run::TRIGGERS + Homebrew::Services::Commands::Run.run(targets, verbose: args.verbose?) + when *Homebrew::Services::Commands::Start::TRIGGERS + Homebrew::Services::Commands::Start.run(targets, args.file, verbose: args.verbose?) + when *Homebrew::Services::Commands::Stop::TRIGGERS max_wait = args.max_wait.to_f - ::Services::Commands::Stop.run(targets, verbose: args.verbose?, no_wait: args.no_wait?, max_wait:) - when *::Services::Commands::Kill::TRIGGERS - ::Services::Commands::Kill.run(targets, verbose: args.verbose?) + Homebrew::Services::Commands::Stop.run(targets, verbose: args.verbose?, no_wait: args.no_wait?, max_wait:) + when *Homebrew::Services::Commands::Kill::TRIGGERS + Homebrew::Services::Commands::Kill.run(targets, verbose: args.verbose?) else raise UsageError, "unknown subcommand: `#{subcommand}`" end diff --git a/Library/Homebrew/services/cli.rb b/Library/Homebrew/services/cli.rb index 9dc301d5e9..3a9d140afe 100644 --- a/Library/Homebrew/services/cli.rb +++ b/Library/Homebrew/services/cli.rb @@ -1,368 +1,381 @@ # typed: strict # frozen_string_literal: true -module Services - module Cli - extend FileUtils +require "services/formula_wrapper" - sig { returns(T.nilable(String)) } - def self.sudo_service_user - @sudo_service_user - end +module Homebrew + module Services + module Cli + extend FileUtils - sig { params(sudo_service_user: String).void } - def self.sudo_service_user=(sudo_service_user) - @sudo_service_user = T.let(sudo_service_user, T.nilable(String)) - end - - # Binary name. - sig { returns(String) } - def self.bin - "brew services" - end - - # Find all currently running services via launchctl list or systemctl list-units. - sig { returns(T::Array[String]) } - def self.running - if System.launchctl? - Utils.popen_read(System.launchctl, "list") - else - System::Systemctl.popen_read("list-units", - "--type=service", - "--state=running", - "--no-pager", - "--no-legend") - end.chomp.split("\n").filter_map do |svc| - Regexp.last_match(0) if svc =~ /homebrew(?>\.mxcl)?\.([\w+-.@]+)/ + sig { returns(T.nilable(String)) } + def self.sudo_service_user + @sudo_service_user end - end - # Check if formula has been found. - sig { params(targets: T.untyped).returns(TrueClass) } - def self.check(targets) - raise UsageError, "Formula(e) missing, please provide a formula name or use --all" if targets.empty? + sig { params(sudo_service_user: String).void } + def self.sudo_service_user=(sudo_service_user) + @sudo_service_user = T.let(sudo_service_user, T.nilable(String)) + end - true - end + # Binary name. + sig { returns(String) } + def self.bin + "brew services" + end - # Kill services that don't have a service file - sig { returns(T::Array[Services::FormulaWrapper]) } - def self.kill_orphaned_services - cleaned_labels = [] - cleaned_services = [] - running.each do |label| - if (service = FormulaWrapper.from(label)) - unless service.dest.file? - cleaned_labels << label - cleaned_services << service - end + # Find all currently running services via launchctl list or systemctl list-units. + sig { returns(T::Array[String]) } + def self.running + if System.launchctl? + Utils.popen_read(System.launchctl, "list") else - opoo "Service #{label} not managed by `#{bin}` => skipping" + System::Systemctl.popen_read("list-units", + "--type=service", + "--state=running", + "--no-pager", + "--no-legend") + end.chomp.split("\n").filter_map do |svc| + Regexp.last_match(0) if svc =~ /homebrew(?>\.mxcl)?\.([\w+-.@]+)/ end end - kill(cleaned_services) - cleaned_labels - end - sig { returns(T::Array[T.untyped]) } - def self.remove_unused_service_files - cleaned = [] - Dir["#{System.path}homebrew.*.{plist,service}"].each do |file| - next if running.include?(File.basename(file).sub(/\.(plist|service)$/i, "")) + # Check if formula has been found. + sig { params(targets: T.untyped).returns(TrueClass) } + def self.check(targets) + raise UsageError, "Formula(e) missing, please provide a formula name or use --all" if targets.empty? - puts "Removing unused service file #{file}" - rm file - cleaned << file + true end - cleaned - end - - # Run a service as defined in the formula. This does not clean the service file like `start` does. - sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void } - def self.run(targets, verbose: false) - targets.each do |service| - if service.pid? - puts "Service `#{service.name}` already running, use `#{bin} restart #{service.name}` to restart." - next - elsif System.root? - puts "Service `#{service.name}` cannot be run (but can be started) as root." - next - end - - service_load(service, enable: false) - end - end - - # Start a service. - sig { - params(targets: T::Array[Services::FormulaWrapper], service_file: T.nilable(T.any(String, Pathname)), - verbose: T.nilable(T::Boolean)).void - } - def self.start(targets, service_file = nil, verbose: false) - file = T.let(nil, T.nilable(Pathname)) - - if service_file.present? - file = Pathname.new service_file - raise UsageError, "Provided service file does not exist" unless file.exist? - end - - targets.each do |service| - if service.pid? - puts "Service `#{service.name}` already started, use `#{bin} restart #{service.name}` to restart." - next - end - - odie "Formula `#{service.name}` is not installed." unless service.installed? - - file ||= if T.must(service.service_file).exist? || System.systemctl? - nil - elsif service.formula.opt_prefix.exist? && (keg = Keg.for service.formula.opt_prefix) && keg.plist_installed? - service_file = Dir["#{keg}/*#{T.must(service.service_file).extname}"].first - Pathname.new service_file if service_file.present? - end - - install_service_file(service, file) - - if file.blank? && verbose - ohai "Generated service file for #{service.formula.name}:" - puts " #{service.dest.read.gsub("\n", "\n ")}" - puts - end - - next if take_root_ownership(service).nil? && System.root? - - service_load(service, enable: true) - end - end - - # Stop a service and unload it. - sig { - params(targets: T::Array[Services::FormulaWrapper], - verbose: T.nilable(T::Boolean), - no_wait: T.nilable(T::Boolean), - max_wait: T.nilable(T.any(Integer, Float))).void - } - def self.stop(targets, verbose: false, no_wait: false, max_wait: 0) - targets.each do |service| - unless service.loaded? - rm service.dest if service.dest.exist? # get rid of installed service file anyway, dude - if service.service_file_present? - odie <<~EOS - Service `#{service.name}` is started as `#{service.owner}`. Try: - #{"sudo " unless System.root?}#{bin} stop #{service.name} - EOS - elsif System.launchctl? && - quiet_system(System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}") - ohai "Successfully stopped `#{service.name}` (label: #{service.service_name})" + # Kill services that don't have a service file + sig { returns(T::Array[Services::FormulaWrapper]) } + def self.kill_orphaned_services + cleaned_labels = [] + cleaned_services = [] + running.each do |label| + if (service = FormulaWrapper.from(label)) + unless service.dest.file? + cleaned_labels << label + cleaned_services << service + end else - opoo "Service `#{service.name}` is not started." + opoo "Service #{label} not managed by `#{bin}` => skipping" end - next + end + kill(cleaned_services) + cleaned_labels + end + + sig { returns(T::Array[T.untyped]) } + def self.remove_unused_service_files + cleaned = [] + Dir["#{System.path}homebrew.*.{plist,service}"].each do |file| + next if running.include?(File.basename(file).sub(/\.(plist|service)$/i, "")) + + puts "Removing unused service file #{file}" + rm file + cleaned << file end - systemctl_args = [] - if no_wait - systemctl_args << "--no-block" - puts "Stopping `#{service.name}`..." - else - puts "Stopping `#{service.name}`... (might take a while)" + cleaned + end + + # Run a service as defined in the formula. This does not clean the service file like `start` does. + sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void } + def self.run(targets, verbose: false) + targets.each do |service| + if service.pid? + puts "Service `#{service.name}` already running, use `#{bin} restart #{service.name}` to restart." + next + elsif System.root? + puts "Service `#{service.name}` cannot be run (but can be started) as root." + next + end + + service_load(service, enable: false) end + end + + # Start a service. + sig { + params( + targets: T::Array[Services::FormulaWrapper], + service_file: T.nilable(T.any(String, Pathname)), + verbose: T.nilable(T::Boolean), + ).void + } + def self.start(targets, service_file = nil, verbose: false) + file = T.let(nil, T.nilable(Pathname)) + + if service_file.present? + file = Pathname.new service_file + raise UsageError, "Provided service file does not exist" unless file.exist? + end + + targets.each do |service| + if service.pid? + puts "Service `#{service.name}` already started, use `#{bin} restart #{service.name}` to restart." + next + end + + odie "Formula `#{service.name}` is not installed." unless service.installed? + + file ||= if T.must(service.service_file).exist? || System.systemctl? + nil + elsif service.formula.opt_prefix.exist? && + (keg = Keg.for service.formula.opt_prefix) && keg.plist_installed? + service_file = Dir["#{keg}/*#{T.must(service.service_file).extname}"].first + Pathname.new service_file if service_file.present? + end + + install_service_file(service, file) + + if file.blank? && verbose + ohai "Generated service file for #{service.formula.name}:" + puts " #{service.dest.read.gsub("\n", "\n ")}" + puts + end + + next if take_root_ownership(service).nil? && System.root? + + service_load(service, enable: true) + end + end + + # Stop a service and unload it. + sig { + params( + targets: T::Array[Services::FormulaWrapper], + verbose: T.nilable(T::Boolean), + no_wait: T.nilable(T::Boolean), + max_wait: T.nilable(T.any(Integer, Float)), + ).void + } + def self.stop(targets, verbose: false, no_wait: false, max_wait: 0) + targets.each do |service| + unless service.loaded? + rm service.dest if service.dest.exist? # get rid of installed service file anyway, dude + if service.service_file_present? + odie <<~EOS + Service `#{service.name}` is started as `#{service.owner}`. Try: + #{"sudo " unless System.root?}#{bin} stop #{service.name} + EOS + elsif System.launchctl? && + quiet_system(System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}") + ohai "Successfully stopped `#{service.name}` (label: #{service.service_name})" + else + opoo "Service `#{service.name}` is not started." + end + next + end + + systemctl_args = [] + if no_wait + systemctl_args << "--no-block" + puts "Stopping `#{service.name}`..." + else + puts "Stopping `#{service.name}`... (might take a while)" + end + + if System.systemctl? + System::Systemctl.quiet_run(*systemctl_args, "disable", "--now", service.service_name) + elsif System.launchctl? + quiet_system System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}" + unless no_wait + time_slept = 0 + sleep_time = 1 + max_wait = T.must(max_wait) + while ($CHILD_STATUS.to_i == 9216 || service.loaded?) && (max_wait.zero? || time_slept < max_wait) + sleep(sleep_time) + time_slept += sleep_time + quiet_system System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}" + end + end + quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" if service.pid? + end + + rm service.dest if service.dest.exist? + # Run daemon-reload on systemctl to finish unloading stopped and deleted service. + System::Systemctl.run(*systemctl_args, "daemon-reload") if System.systemctl? + + if service.pid? || service.loaded? + opoo "Unable to stop `#{service.name}` (label: #{service.service_name})" + else + ohai "Successfully stopped `#{service.name}` (label: #{service.service_name})" + end + end + end + + # Stop a service but keep it registered. + sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void } + def self.kill(targets, verbose: false) + targets.each do |service| + if !service.pid? + puts "Service `#{service.name}` is not started." + elsif service.keep_alive? + puts "Service `#{service.name}` is set to automatically restart and can't be killed." + else + puts "Killing `#{service.name}`... (might take a while)" + if System.systemctl? + System::Systemctl.quiet_run("stop", service.service_name) + elsif System.launchctl? + quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" + end + + if service.pid? + opoo "Unable to kill `#{service.name}` (label: #{service.service_name})" + else + ohai "Successfully killed `#{service.name}` (label: #{service.service_name})" + end + end + end + end + + # protections to avoid users editing root services + sig { params(service: T.untyped).returns(T.nilable(Integer)) } + def self.take_root_ownership(service) + return unless System.root? + return if sudo_service_user + + root_paths = T.let([], T::Array[Pathname]) if System.systemctl? - System::Systemctl.quiet_run(*systemctl_args, "disable", "--now", service.service_name) + group = "root" elsif System.launchctl? - quiet_system System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}" - unless no_wait - time_slept = 0 - sleep_time = 1 - max_wait = T.must(max_wait) - while ($CHILD_STATUS.to_i == 9216 || service.loaded?) && (max_wait.zero? || time_slept < max_wait) - sleep(sleep_time) - time_slept += sleep_time - quiet_system System.launchctl, "bootout", "#{System.domain_target}/#{service.service_name}" + group = "admin" + chown "root", group, service.dest + plist_data = service.dest.read + plist = begin + Plist.parse_xml(plist_data, marshal: false) + rescue + nil + end + return unless plist + + program_location = plist["ProgramArguments"]&.first + key = "first ProgramArguments value" + if program_location.blank? + program_location = plist["Program"] + key = "Program" + end + + if program_location.present? + Dir.chdir("/") do + if File.exist?(program_location) + program_location_path = Pathname(program_location).realpath + root_paths += [ + program_location_path, + program_location_path.parent.realpath, + ] + else + opoo <<~EOS + #{service.name}: the #{key} does not exist: + #{program_location} + EOS + end end end - quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" if service.pid? end + if (formula = service.formula) + root_paths += [ + formula.opt_prefix, + formula.linked_keg, + formula.bin, + formula.sbin, + ] + end + root_paths = root_paths.sort.uniq.select(&:exist?) + + opoo <<~EOS + Taking root:#{group} ownership of some #{service.formula} paths: + #{root_paths.join("\n ")} + This will require manual removal of these paths using `sudo rm` on + brew upgrade/reinstall/uninstall. + EOS + chown "root", group, root_paths + chmod "+t", root_paths + end + + sig { + params( + service: Services::FormulaWrapper, + file: T.nilable(T.any(String, Pathname)), + enable: T.nilable(T::Boolean), + ).void + } + def self.launchctl_load(service, file:, enable:) + safe_system System.launchctl, "enable", "#{System.domain_target}/#{service.service_name}" if enable + safe_system System.launchctl, "bootstrap", System.domain_target, file + end + + sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void } + def self.systemd_load(service, enable:) + System::Systemctl.run("start", T.must(service.service_name)) + System::Systemctl.run("enable", T.must(service.service_name)) if enable + end + + sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void } + def self.service_load(service, enable:) + if System.root? && !service.service_startup? + opoo "#{service.name} must be run as non-root to start at user login!" + elsif !System.root? && service.service_startup? + opoo "#{service.name} must be run as root to start at system startup!" + end + + if System.launchctl? + file = enable ? service.dest : service.service_file + launchctl_load(service, file:, enable:) + elsif System.systemctl? + # Systemctl loads based upon location so only install service + # file when it is not installed. Used with the `run` command. + install_service_file(service, nil) unless service.dest.exist? + systemd_load(service, enable:) + end + + function = enable ? "started" : "ran" + ohai("Successfully #{function} `#{service.name}` (label: #{service.service_name})") + end + + sig { params(service: Services::FormulaWrapper, file: T.nilable(Pathname)).void } + def self.install_service_file(service, file) + raise UsageError, "Formula `#{service.name}` is not installed" unless service.installed? + + unless T.must(service.service_file).exist? + raise UsageError, + "Formula `#{service.name}` has not implemented #plist, #service or installed a locatable service file" + end + + temp = Tempfile.new(T.must(service.service_name)) + temp << if T.must(file).blank? + contents = T.must(service.service_file).read + + if sudo_service_user && Services::System.launchctl? + # set the username in the new plist file + ohai "Setting username in #{service.service_name} to #{Services::System.user}" + plist_data = Plist.parse_xml(contents, marshal: false) + plist_data["UserName"] = sudo_service_user + plist_data.to_plist + else + contents + end + else + T.must(file).read + end + temp.flush + rm service.dest if service.dest.exist? - # Run daemon-reload on systemctl to finish unloading stopped and deleted service. - System::Systemctl.run(*systemctl_args, "daemon-reload") if System.systemctl? + service.dest_dir.mkpath unless service.dest_dir.directory? + cp T.must(temp.path), service.dest - if service.pid? || service.loaded? - opoo "Unable to stop `#{service.name}` (label: #{service.service_name})" - else - ohai "Successfully stopped `#{service.name}` (label: #{service.service_name})" - end + # Clear tempfile. + temp.close + + chmod 0644, service.dest + + Services::System::Systemctl.run("daemon-reload") if System.systemctl? end end - - # Stop a service but keep it registered. - sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void } - def self.kill(targets, verbose: false) - targets.each do |service| - if !service.pid? - puts "Service `#{service.name}` is not started." - elsif service.keep_alive? - puts "Service `#{service.name}` is set to automatically restart and can't be killed." - else - puts "Killing `#{service.name}`... (might take a while)" - if System.systemctl? - System::Systemctl.quiet_run("stop", service.service_name) - elsif System.launchctl? - quiet_system System.launchctl, "stop", "#{System.domain_target}/#{service.service_name}" - end - - if service.pid? - opoo "Unable to kill `#{service.name}` (label: #{service.service_name})" - else - ohai "Successfully killed `#{service.name}` (label: #{service.service_name})" - end - end - end - end - - # protections to avoid users editing root services - sig { params(service: T.untyped).returns(T.nilable(Integer)) } - def self.take_root_ownership(service) - return unless System.root? - return if sudo_service_user - - root_paths = T.let([], T::Array[Pathname]) - - if System.systemctl? - group = "root" - elsif System.launchctl? - group = "admin" - chown "root", group, service.dest - plist_data = service.dest.read - plist = begin - Plist.parse_xml(plist_data, marshal: false) - rescue - nil - end - return unless plist - - program_location = plist["ProgramArguments"]&.first - key = "first ProgramArguments value" - if program_location.blank? - program_location = plist["Program"] - key = "Program" - end - - if program_location.present? - Dir.chdir("/") do - if File.exist?(program_location) - program_location_path = Pathname(program_location).realpath - root_paths += [ - program_location_path, - program_location_path.parent.realpath, - ] - else - opoo <<~EOS - #{service.name}: the #{key} does not exist: - #{program_location} - EOS - end - end - end - end - - if (formula = service.formula) - root_paths += [ - formula.opt_prefix, - formula.linked_keg, - formula.bin, - formula.sbin, - ] - end - root_paths = root_paths.sort.uniq.select(&:exist?) - - opoo <<~EOS - Taking root:#{group} ownership of some #{service.formula} paths: - #{root_paths.join("\n ")} - This will require manual removal of these paths using `sudo rm` on - brew upgrade/reinstall/uninstall. - EOS - chown "root", group, root_paths - chmod "+t", root_paths - end - - sig { - params(service: Services::FormulaWrapper, file: T.nilable(T.any(String, Pathname)), - enable: T.nilable(T::Boolean)).void - } - def self.launchctl_load(service, file:, enable:) - safe_system System.launchctl, "enable", "#{System.domain_target}/#{service.service_name}" if enable - safe_system System.launchctl, "bootstrap", System.domain_target, file - end - - sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void } - def self.systemd_load(service, enable:) - System::Systemctl.run("start", T.must(service.service_name)) - System::Systemctl.run("enable", T.must(service.service_name)) if enable - end - - sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void } - def self.service_load(service, enable:) - if System.root? && !service.service_startup? - opoo "#{service.name} must be run as non-root to start at user login!" - elsif !System.root? && service.service_startup? - opoo "#{service.name} must be run as root to start at system startup!" - end - - if System.launchctl? - file = enable ? service.dest : service.service_file - launchctl_load(service, file:, enable:) - elsif System.systemctl? - # Systemctl loads based upon location so only install service - # file when it is not installed. Used with the `run` command. - install_service_file(service, nil) unless service.dest.exist? - systemd_load(service, enable:) - end - - function = enable ? "started" : "ran" - ohai("Successfully #{function} `#{service.name}` (label: #{service.service_name})") - end - - sig { params(service: Services::FormulaWrapper, file: T.nilable(Pathname)).void } - def self.install_service_file(service, file) - raise UsageError, "Formula `#{service.name}` is not installed" unless service.installed? - - unless T.must(service.service_file).exist? - raise UsageError, - "Formula `#{service.name}` has not implemented #plist, #service or installed a locatable service file" - end - - temp = Tempfile.new(T.must(service.service_name)) - temp << if T.must(file).blank? - contents = T.must(service.service_file).read - - if sudo_service_user && Services::System.launchctl? - # set the username in the new plist file - ohai "Setting username in #{service.service_name} to #{Services::System.user}" - plist_data = Plist.parse_xml(contents, marshal: false) - plist_data["UserName"] = sudo_service_user - plist_data.to_plist - else - contents - end - else - T.must(file).read - end - temp.flush - - rm service.dest if service.dest.exist? - service.dest_dir.mkpath unless service.dest_dir.directory? - cp T.must(temp.path), service.dest - - # Clear tempfile. - temp.close - - chmod 0644, service.dest - - Services::System::Systemctl.run("daemon-reload") if System.systemctl? - end end end diff --git a/Library/Homebrew/services/commands/cleanup.rb b/Library/Homebrew/services/commands/cleanup.rb index c8c934f684..3c0451db3e 100644 --- a/Library/Homebrew/services/commands/cleanup.rb +++ b/Library/Homebrew/services/commands/cleanup.rb @@ -1,19 +1,23 @@ # typed: strict # frozen_string_literal: true -module Services - module Commands - module Cleanup - TRIGGERS = %w[cleanup clean cl rm].freeze +require "services/cli" - sig { void } - def self.run - cleaned = [] +module Homebrew + module Services + module Commands + module Cleanup + TRIGGERS = %w[cleanup clean cl rm].freeze - cleaned += Services::Cli.kill_orphaned_services - cleaned += Services::Cli.remove_unused_service_files + sig { void } + def self.run + cleaned = [] - puts "All #{System.root? ? "root" : "user-space"} services OK, nothing cleaned..." if cleaned.empty? + cleaned += Services::Cli.kill_orphaned_services + cleaned += Services::Cli.remove_unused_service_files + + puts "All #{System.root? ? "root" : "user-space"} services OK, nothing cleaned..." if cleaned.empty? + end end end end diff --git a/Library/Homebrew/services/commands/info.rb b/Library/Homebrew/services/commands/info.rb index 89f6f61502..4b49656fb0 100644 --- a/Library/Homebrew/services/commands/info.rb +++ b/Library/Homebrew/services/commands/info.rb @@ -1,65 +1,69 @@ # typed: strict # frozen_string_literal: true -require "services/formula_wrapper" require "services/cli" -module Services - module Commands - module Info - TRIGGERS = %w[info i].freeze +module Homebrew + module Services + module Commands + module Info + TRIGGERS = %w[info i].freeze - sig { - params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean), - json: T.nilable(T::Boolean)).void - } - def self.run(targets, verbose:, json:) - Services::Cli.check(targets) + sig { + params( + targets: T::Array[Services::FormulaWrapper], + verbose: T.nilable(T::Boolean), + json: T.nilable(T::Boolean), + ).void + } + def self.run(targets, verbose:, json:) + Services::Cli.check(targets) - output = targets.map(&:to_hash) + output = targets.map(&:to_hash) - if json - puts JSON.pretty_generate(output) - return + if json + puts JSON.pretty_generate(output) + return + end + + output.each do |hash| + puts output(hash, verbose:) + end end - output.each do |hash| - puts output(hash, verbose:) + sig { params(bool: T.nilable(T.any(String, T::Boolean))).returns(String) } + def self.pretty_bool(bool) + return T.must(bool).to_s if !$stdout.tty? || Homebrew::EnvConfig.no_emoji? + + if bool + "#{Tty.bold}#{Formatter.success("✔")}#{Tty.reset}" + else + "#{Tty.bold}#{Formatter.error("✘")}#{Tty.reset}" + end end - end - sig { params(bool: T.nilable(T.any(String, T::Boolean))).returns(String) } - def self.pretty_bool(bool) - return T.must(bool).to_s if !$stdout.tty? || Homebrew::EnvConfig.no_emoji? + sig { params(hash: T.untyped, verbose: T.nilable(T::Boolean)).returns(String) } + def self.output(hash, verbose:) + out = "#{Tty.bold}#{hash[:name]}#{Tty.reset} (#{hash[:service_name]})\n" + out += "Running: #{pretty_bool(hash[:running])}\n" + out += "Loaded: #{pretty_bool(hash[:loaded])}\n" + out += "Schedulable: #{pretty_bool(hash[:schedulable])}\n" + out += "User: #{hash[:user]}\n" unless hash[:pid].nil? + out += "PID: #{hash[:pid]}\n" unless hash[:pid].nil? + return out unless verbose - if bool - "#{Tty.bold}#{Formatter.success("✔")}#{Tty.reset}" - else - "#{Tty.bold}#{Formatter.error("✘")}#{Tty.reset}" + out += "File: #{hash[:file]} #{pretty_bool(hash[:file].present?)}\n" + out += "Command: #{hash[:command]}\n" unless hash[:command].nil? + out += "Working directory: #{hash[:working_dir]}\n" unless hash[:working_dir].nil? + out += "Root directory: #{hash[:root_dir]}\n" unless hash[:root_dir].nil? + out += "Log: #{hash[:log_path]}\n" unless hash[:log_path].nil? + out += "Error log: #{hash[:error_log_path]}\n" unless hash[:error_log_path].nil? + out += "Interval: #{hash[:interval]}s\n" unless hash[:interval].nil? + out += "Cron: #{hash[:cron]}\n" unless hash[:cron].nil? + + out end end - - sig { params(hash: T.untyped, verbose: T.nilable(T::Boolean)).returns(String) } - def self.output(hash, verbose:) - out = "#{Tty.bold}#{hash[:name]}#{Tty.reset} (#{hash[:service_name]})\n" - out += "Running: #{pretty_bool(hash[:running])}\n" - out += "Loaded: #{pretty_bool(hash[:loaded])}\n" - out += "Schedulable: #{pretty_bool(hash[:schedulable])}\n" - out += "User: #{hash[:user]}\n" unless hash[:pid].nil? - out += "PID: #{hash[:pid]}\n" unless hash[:pid].nil? - return out unless verbose - - out += "File: #{hash[:file]} #{pretty_bool(hash[:file].present?)}\n" - out += "Command: #{hash[:command]}\n" unless hash[:command].nil? - out += "Working directory: #{hash[:working_dir]}\n" unless hash[:working_dir].nil? - out += "Root directory: #{hash[:root_dir]}\n" unless hash[:root_dir].nil? - out += "Log: #{hash[:log_path]}\n" unless hash[:log_path].nil? - out += "Error log: #{hash[:error_log_path]}\n" unless hash[:error_log_path].nil? - out += "Interval: #{hash[:interval]}s\n" unless hash[:interval].nil? - out += "Cron: #{hash[:cron]}\n" unless hash[:cron].nil? - - out - end end end end diff --git a/Library/Homebrew/services/commands/kill.rb b/Library/Homebrew/services/commands/kill.rb index cb92f93f66..60d2a57c4a 100644 --- a/Library/Homebrew/services/commands/kill.rb +++ b/Library/Homebrew/services/commands/kill.rb @@ -1,15 +1,19 @@ # typed: strict # frozen_string_literal: true -module Services - module Commands - module Kill - TRIGGERS = %w[kill k].freeze +require "services/cli" - 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:) +module Homebrew + 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 diff --git a/Library/Homebrew/services/commands/list.rb b/Library/Homebrew/services/commands/list.rb index d198d385de..8142f2c05c 100644 --- a/Library/Homebrew/services/commands/list.rb +++ b/Library/Homebrew/services/commands/list.rb @@ -1,83 +1,85 @@ # typed: strict # frozen_string_literal: true -require "services/formulae" require "services/cli" +require "services/formulae" -module Services - module Commands - module List - TRIGGERS = [nil, "list", "ls"].freeze +module Homebrew + module Services + module Commands + module List + TRIGGERS = [nil, "list", "ls"].freeze - sig { params(json: T::Boolean).void } - def self.run(json: false) - formulae = Formulae.services_list - if formulae.blank? - opoo "No services available to control with `#{Services::Cli.bin}`" if $stderr.tty? - return + sig { params(json: T::Boolean).void } + def self.run(json: false) + formulae = Formulae.services_list + if formulae.blank? + opoo "No services available to control with `#{Services::Cli.bin}`" if $stderr.tty? + return + end + + if json + print_json(formulae) + else + print_table(formulae) + end end - if json - print_json(formulae) - else - print_table(formulae) - end - end + JSON_FIELDS = [:name, :status, :user, :file, :exit_code].freeze - JSON_FIELDS = [:name, :status, :user, :file, :exit_code].freeze + # Print the JSON representation in the CLI + # @private + sig { params(formulae: T.untyped).returns(NilClass) } + def self.print_json(formulae) + services = formulae.map do |formula| + formula.slice(*JSON_FIELDS) + end - # Print the JSON representation in the CLI - # @private - sig { params(formulae: T.untyped).returns(NilClass) } - def self.print_json(formulae) - services = formulae.map do |formula| - formula.slice(*JSON_FIELDS) + puts JSON.pretty_generate(services) end - puts JSON.pretty_generate(services) - end + # Print the table in the CLI + # @private + sig { params(formulae: T::Array[T::Hash[T.untyped, T.untyped]]).void } + def self.print_table(formulae) + services = formulae.map do |formula| + status = T.must(get_status_string(formula[:status])) + status += formula[:exit_code].to_s if formula[:status] == :error + file = formula[:file].to_s.gsub(Dir.home, "~").presence if formula[:loaded] - # Print the table in the CLI - # @private - sig { params(formulae: T::Array[T::Hash[T.untyped, T.untyped]]).void } - def self.print_table(formulae) - services = formulae.map do |formula| - status = T.must(get_status_string(formula[:status])) - status += formula[:exit_code].to_s if formula[:status] == :error - file = formula[:file].to_s.gsub(Dir.home, "~").presence if formula[:loaded] + { name: formula[:name], status:, user: formula[:user], file: } + end - { name: formula[:name], status:, user: formula[:user], file: } + longest_name = [*services.map { |service| service[:name].length }, 4].max + longest_status = [*services.map { |service| service[:status].length }, 15].max + longest_user = [*services.map { |service| service[:user]&.length }, 4].compact.max + + # `longest_status` includes 9 color characters from `Tty.color` and `Tty.reset`. + # We don't have these in the header row, so we don't need to add the extra padding. + headers = "#{Tty.bold}%-#{longest_name}.#{longest_name}s " \ + "%-#{longest_status - 9}.#{longest_status - 9}s " \ + "%-#{longest_user}.#{longest_user}s %s#{Tty.reset}" + row = "%-#{longest_name}.#{longest_name}s " \ + "%-#{longest_status}.#{longest_status}s " \ + "%-#{longest_user}.#{longest_user}s %s" + + puts format(headers, name: "Name", status: "Status", user: "User", file: "File") + services.each do |service| + puts format(row, **service) + end end - longest_name = [*services.map { |service| service[:name].length }, 4].max - longest_status = [*services.map { |service| service[:status].length }, 15].max - longest_user = [*services.map { |service| service[:user]&.length }, 4].compact.max - - # `longest_status` includes 9 color characters from `Tty.color` and `Tty.reset`. - # We don't have these in the header row, so we don't need to add the extra padding. - headers = "#{Tty.bold}%-#{longest_name}.#{longest_name}s " \ - "%-#{longest_status - 9}.#{longest_status - 9}s " \ - "%-#{longest_user}.#{longest_user}s %s#{Tty.reset}" - row = "%-#{longest_name}.#{longest_name}s " \ - "%-#{longest_status}.#{longest_status}s " \ - "%-#{longest_user}.#{longest_user}s %s" - - puts format(headers, name: "Name", status: "Status", user: "User", file: "File") - services.each do |service| - puts format(row, **service) - end - end - - # Get formula status output - # @private - sig { params(status: T.anything).returns(T.nilable(String)) } - def self.get_status_string(status) - case status - when :started, :scheduled then "#{Tty.green}#{status}#{Tty.reset}" - when :stopped, :none then "#{Tty.default}#{status}#{Tty.reset}" - when :error then "#{Tty.red}error #{Tty.reset}" - when :unknown then "#{Tty.yellow}unknown#{Tty.reset}" - when :other then "#{Tty.yellow}other#{Tty.reset}" + # Get formula status output + # @private + sig { params(status: T.anything).returns(T.nilable(String)) } + def self.get_status_string(status) + case status + when :started, :scheduled then "#{Tty.green}#{status}#{Tty.reset}" + when :stopped, :none then "#{Tty.default}#{status}#{Tty.reset}" + when :error then "#{Tty.red}error #{Tty.reset}" + when :unknown then "#{Tty.yellow}unknown#{Tty.reset}" + when :other then "#{Tty.yellow}other#{Tty.reset}" + end end end end diff --git a/Library/Homebrew/services/commands/restart.rb b/Library/Homebrew/services/commands/restart.rb index 589a68a07e..dc9384dceb 100644 --- a/Library/Homebrew/services/commands/restart.rb +++ b/Library/Homebrew/services/commands/restart.rb @@ -1,35 +1,39 @@ # typed: strict # frozen_string_literal: true -module Services - module Commands - module Restart - # NOTE: The restart command is used to update service files - # after a package gets updated through `brew upgrade`. - # This works by removing the old file with `brew services stop` - # and installing the new one with `brew services start|run`. +require "services/cli" - TRIGGERS = %w[restart relaunch reload r].freeze +module Homebrew + module Services + module Commands + module Restart + # NOTE: The restart command is used to update service files + # after a package gets updated through `brew upgrade`. + # This works by removing the old file with `brew services stop` + # and installing the new one with `brew services start|run`. - sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).returns(NilClass) } - def self.run(targets, verbose:) - Services::Cli.check(targets) + TRIGGERS = %w[restart relaunch reload r].freeze - ran = [] - started = [] - targets.each do |service| - if service.loaded? && !service.service_file_present? - ran << service - else - # group not-started services with started ones for restart - started << service + sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).returns(NilClass) } + def self.run(targets, verbose:) + Services::Cli.check(targets) + + ran = [] + started = [] + targets.each do |service| + if service.loaded? && !service.service_file_present? + ran << service + else + # group not-started services with started ones for restart + started << service + end + Services::Cli.stop([service], verbose:) if service.loaded? end - Services::Cli.stop([service], verbose:) if service.loaded? - end - Services::Cli.run(targets, verbose:) if ran.present? - Services::Cli.start(started, verbose:) if started.present? - nil + Services::Cli.run(targets, verbose:) if ran.present? + Services::Cli.start(started, verbose:) if started.present? + nil + end end end end diff --git a/Library/Homebrew/services/commands/run.rb b/Library/Homebrew/services/commands/run.rb index 26f3d7f88d..ca0a9a36e1 100644 --- a/Library/Homebrew/services/commands/run.rb +++ b/Library/Homebrew/services/commands/run.rb @@ -1,15 +1,19 @@ # typed: strict # frozen_string_literal: true -module Services - module Commands - module Run - TRIGGERS = ["run"].freeze +require "services/cli" - 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:) +module Homebrew + 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 diff --git a/Library/Homebrew/services/commands/start.rb b/Library/Homebrew/services/commands/start.rb index ef0aba3f42..6f7f37fef0 100644 --- a/Library/Homebrew/services/commands/start.rb +++ b/Library/Homebrew/services/commands/start.rb @@ -1,18 +1,25 @@ # typed: strict # frozen_string_literal: true -module Services - module Commands - module Start - TRIGGERS = %w[start launch load s l].freeze +require "services/cli" - sig { - params(targets: T::Array[Services::FormulaWrapper], custom_plist: T.nilable(String), - verbose: T.nilable(T::Boolean)).void - } - def self.run(targets, custom_plist, verbose:) - Services::Cli.check(targets) - Services::Cli.start(targets, custom_plist, verbose:) +module Homebrew + module Services + module Commands + module Start + TRIGGERS = %w[start launch load s l].freeze + + sig { + params( + targets: T::Array[Services::FormulaWrapper], + custom_plist: T.nilable(String), + verbose: T.nilable(T::Boolean), + ).void + } + def self.run(targets, custom_plist, verbose:) + Services::Cli.check(targets) + Services::Cli.start(targets, custom_plist, verbose:) + end end end end diff --git a/Library/Homebrew/services/commands/stop.rb b/Library/Homebrew/services/commands/stop.rb index b3bc9dc0f8..53443d4424 100644 --- a/Library/Homebrew/services/commands/stop.rb +++ b/Library/Homebrew/services/commands/stop.rb @@ -1,20 +1,26 @@ # typed: strict # frozen_string_literal: true -module Services - module Commands - module Stop - TRIGGERS = %w[stop unload terminate term t u].freeze +require "services/cli" - sig { - params(targets: T::Array[Services::FormulaWrapper], - verbose: T.nilable(T::Boolean), - no_wait: T.nilable(T::Boolean), - max_wait: T.nilable(Float)).void - } - def self.run(targets, verbose:, no_wait:, max_wait:) - Services::Cli.check(targets) - Services::Cli.stop(targets, verbose:, no_wait:, max_wait:) +module Homebrew + module Services + module Commands + module Stop + TRIGGERS = %w[stop unload terminate term t u].freeze + + sig { + params( + targets: T::Array[Services::FormulaWrapper], + verbose: T.nilable(T::Boolean), + no_wait: T.nilable(T::Boolean), + max_wait: T.nilable(Float), + ).void + } + def self.run(targets, verbose:, no_wait:, max_wait:) + Services::Cli.check(targets) + Services::Cli.stop(targets, verbose:, no_wait:, max_wait:) + end end end end diff --git a/Library/Homebrew/services/formula_wrapper.rb b/Library/Homebrew/services/formula_wrapper.rb index 8e507c1617..0fc499dc7e 100644 --- a/Library/Homebrew/services/formula_wrapper.rb +++ b/Library/Homebrew/services/formula_wrapper.rb @@ -3,330 +3,331 @@ # Wrapper for a formula to handle service-related stuff like parsing and # generating the service/plist files. -module Services - class FormulaWrapper - # Access the `Formula` instance. - sig { returns(Formula) } - attr_reader :formula +module Homebrew + module Services + class FormulaWrapper + # Access the `Formula` instance. + sig { returns(Formula) } + attr_reader :formula - # Create a new `Service` instance from either a path or label. - sig { params(path_or_label: T.any(Pathname, String)).returns(T.nilable(FormulaWrapper)) } - def self.from(path_or_label) - return unless path_or_label =~ path_or_label_regex + # Create a new `Service` instance from either a path or label. + sig { params(path_or_label: T.any(Pathname, String)).returns(T.nilable(FormulaWrapper)) } + def self.from(path_or_label) + return unless path_or_label =~ path_or_label_regex - begin - new(Formulary.factory(T.must(Regexp.last_match(1)))) - rescue - nil - end - end - - # Initialize a new `Service` instance with supplied formula. - sig { params(formula: Formula).void } - def initialize(formula) - @formula = T.let(formula, Formula) - @service = T.let(@formula.service? || false, T::Boolean) - @service_name = T.let(if System.launchctl? - formula.plist_name - elsif System.systemctl? - formula.service_name - end, T.nilable(String)) - @service_file = T.let(if System.launchctl? - formula.launchd_service_path - elsif System.systemctl? - formula.systemd_service_path - end, T.nilable(Pathname)) - @service_startup = T.let( - if service? - T.must(load_service).requires_root? - else - false - end, T.nilable(T::Boolean) - ) - @name = T.let(formula.name, String) - end - - # Delegate access to `formula.name`. - sig { returns(String) } - attr_reader :name - - # Delegate access to `formula.service?`. - sig { returns(T::Boolean) } - def service? - @service - end - - # Delegate access to `formula.service.timed?`. - sig { returns(T.nilable(T::Boolean)) } - def timed? - @timed ||= T.let(service? ? T.must(load_service).timed? : nil, T.nilable(T::Boolean)) - end - - # Delegate access to `formula.service.keep_alive?`.` - sig { returns(T.nilable(T::Boolean)) } - def keep_alive? - @keep_alive ||= T.let(T.must(load_service).keep_alive?, T.nilable(T::Boolean)) if service? - end - - # service_name delegates with formula.plist_name or formula.service_name for systemd (e.g., `homebrew.`). - sig { returns(T.nilable(String)) } - attr_reader :service_name - - # service_file delegates with formula.launchd_service_path or formula.systemd_service_path for systemd. - sig { returns(T.nilable(Pathname)) } - attr_reader :service_file - - # Whether the service should be launched at startup - sig { returns(T.nilable(T::Boolean)) } - def service_startup? - @service_startup ||= service? ? T.must(load_service).requires_root? : false - end - - # Path to destination service directory. If run as root, it's `boot_path`, else `user_path`. - sig { returns(Pathname) } - def dest_dir - System.root? ? T.must(System.boot_path) : T.must(System.user_path) - end - - # Path to destination service. If run as root, it's in `boot_path`, else `user_path`. - sig { returns(Pathname) } - def dest - dest_dir + T.must(service_file).basename - end - - # Returns `true` if any version of the formula is installed. - sig { returns(T::Boolean) } - def installed? - formula.any_version_installed? - end - - # Returns `true` if the plist file exists. - sig { returns(T::Boolean) } - def plist? - return false unless installed? - return true if T.must(service_file).file? - return false unless formula.opt_prefix.exist? - return true if Keg.for(formula.opt_prefix).plist_installed? - - false - rescue NotAKegError - false - end - - # Returns `true` if the service is loaded, else false. - sig { params(cached: T::Boolean).returns(T.nilable(T::Boolean)) } - def loaded?(cached: false) - if System.launchctl? - @status_output_success_type = nil unless cached - _, status_success, = status_output_success_type - status_success - elsif System.systemctl? - System::Systemctl.quiet_run("status", T.must(service_file).basename) - end - end - - # Returns `true` if service is present (e.g. .plist is present in boot or user service path), else `false` - # Accepts Hash option `:for` with values `:root` for boot path or `:user` for user path. - sig { params(opts: T.untyped).returns(T::Boolean) } - def service_file_present?(opts = { for: false }) - if opts[:for] && opts[:for] == :root - boot_path_service_file_present? - elsif opts[:for] && opts[:for] == :user - user_path_service_file_present? - else - boot_path_service_file_present? || user_path_service_file_present? - end - end - - sig { returns(T.nilable(String)) } - def owner - if System.launchctl? && dest.exist? - # read the username from the plist file - plist = begin - Plist.parse_xml(dest.read, marshal: false) + begin + new(Formulary.factory(T.must(Regexp.last_match(1)))) rescue nil end - plist_username = plist["UserName"] if plist - - return plist_username if plist_username.present? end - return "root" if boot_path_service_file_present? - return System.user if user_path_service_file_present? - nil - end + # Initialize a new `Service` instance with supplied formula. + sig { params(formula: Formula).void } + def initialize(formula) + @formula = T.let(formula, Formula) + @service = T.let(@formula.service? || false, T::Boolean) + @service_name = T.let(if System.launchctl? + formula.plist_name + elsif System.systemctl? + formula.service_name + end, T.nilable(String)) + @service_file = T.let(if System.launchctl? + formula.launchd_service_path + elsif System.systemctl? + formula.systemd_service_path + end, T.nilable(Pathname)) + @service_startup = T.let( + if service? + T.must(load_service).requires_root? + else + false + end, T.nilable(T::Boolean) + ) + @name = T.let(formula.name, String) + end - sig { returns(T::Boolean) } - def pid? - return false if pid.nil? + # Delegate access to `formula.name`. + sig { returns(String) } + attr_reader :name - !T.must(pid).zero? - end + # Delegate access to `formula.service?`. + sig { returns(T::Boolean) } + def service? + @service + end - sig { returns(T.nilable(T.any(T::Boolean, Integer))) } - def error? - return false if pid? || pid.nil? - return exit_code if exit_code.nil? + # Delegate access to `formula.service.timed?`. + sig { returns(T.nilable(T::Boolean)) } + def timed? + @timed ||= T.let(service? ? T.must(load_service).timed? : nil, T.nilable(T::Boolean)) + end - T.must(exit_code).nonzero? - end + # Delegate access to `formula.service.keep_alive?`.` + sig { returns(T.nilable(T::Boolean)) } + def keep_alive? + @keep_alive ||= T.let(T.must(load_service).keep_alive?, T.nilable(T::Boolean)) if service? + end - sig { returns(T.nilable(T::Boolean)) } - def unknown_status? - status_output.blank? && !pid? - end + # service_name delegates with formula.plist_name or formula.service_name for systemd + # (e.g., `homebrew.`). + sig { returns(T.nilable(String)) } + attr_reader :service_name - # Get current PID of daemon process from status output. - sig { returns(T.nilable(Integer)) } - def pid - status_output, _, status_type = status_output_success_type - return if status_type.nil? + # service_file delegates with formula.launchd_service_path or formula.systemd_service_path for systemd. + sig { returns(T.nilable(Pathname)) } + attr_reader :service_file - Regexp.last_match(1).to_i if status_output =~ pid_regex(status_type) - end + # Whether the service should be launched at startup + sig { returns(T.nilable(T::Boolean)) } + def service_startup? + @service_startup ||= service? ? T.must(load_service).requires_root? : false + end - # Get current exit code of daemon process from status output. - sig { returns(T.nilable(Integer)) } - def exit_code - status_output, _, status_type = status_output_success_type - return if status_type.nil? + # Path to destination service directory. If run as root, it's `boot_path`, else `user_path`. + sig { returns(Pathname) } + def dest_dir + System.root? ? T.must(System.boot_path) : T.must(System.user_path) + end - Regexp.last_match(1).to_i if status_output =~ exit_code_regex(status_type) - end + # Path to destination service. If run as root, it's in `boot_path`, else `user_path`. + sig { returns(Pathname) } + def dest + dest_dir + T.must(service_file).basename + end - sig { returns(T::Hash[T.untyped, T.untyped]) } - def to_hash - hash = { - name:, - service_name:, - running: pid?, - loaded: loaded?(cached: true), - schedulable: timed?, - pid:, - exit_code:, - user: owner, - status: status_symbol, - file: service_file_present? ? dest : service_file, - } + # Returns `true` if any version of the formula is installed. + sig { returns(T::Boolean) } + def installed? + formula.any_version_installed? + end - return hash unless service? + # Returns `true` if the plist file exists. + sig { returns(T::Boolean) } + def plist? + return false unless installed? + return true if T.must(service_file).file? + return false unless formula.opt_prefix.exist? + return true if Keg.for(formula.opt_prefix).plist_installed? - service = load_service + false + rescue NotAKegError + false + end - return hash if T.must(service).command.blank? + # Returns `true` if the service is loaded, else false. + sig { params(cached: T::Boolean).returns(T.nilable(T::Boolean)) } + def loaded?(cached: false) + if System.launchctl? + @status_output_success_type = nil unless cached + _, status_success, = status_output_success_type + status_success + elsif System.systemctl? + System::Systemctl.quiet_run("status", T.must(service_file).basename) + end + end - hash[:command] = T.must(service).manual_command - hash[:working_dir] = T.must(service).working_dir - hash[:root_dir] = T.must(service).root_dir - hash[:log_path] = T.must(service).log_path - hash[:error_log_path] = T.must(service).error_log_path - hash[:interval] = T.must(service).interval - hash[:cron] = T.must(service).cron - - hash - end - - private - - # The purpose of this function is to lazy load the Homebrew::Service class - # and avoid nameclashes with the current Service module. - # It should be used instead of calling formula.service directly. - sig { returns(T.nilable(Homebrew::Service)) } - def load_service - require "formula" - - formula.service - end - - sig { returns(T.nilable(T::Array[T.untyped])) } - def status_output_success_type - @status_output_success_type ||= T.let(nil, T.nilable(T::Array[T.untyped])) - @status_output_success_type ||= if System.launchctl? - cmd = [System.launchctl.to_s, "list", service_name] - output = Utils.popen_read(*cmd).chomp - if $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? - success = true - odebug cmd.join(" "), output - [output, success, :launchctl_list] + # Returns `true` if service is present (e.g. .plist is present in boot or user service path), else `false` + # Accepts Hash option `:for` with values `:root` for boot path or `:user` for user path. + sig { params(opts: T.untyped).returns(T::Boolean) } + def service_file_present?(opts = { for: false }) + if opts[:for] && opts[:for] == :root + boot_path_service_file_present? + elsif opts[:for] && opts[:for] == :user + user_path_service_file_present? else - cmd = [System.launchctl.to_s, "print", - "#{System.domain_target}/#{service_name}"] + boot_path_service_file_present? || user_path_service_file_present? + end + end + + sig { returns(T.nilable(String)) } + def owner + if System.launchctl? && dest.exist? + # read the username from the plist file + plist = begin + Plist.parse_xml(dest.read, marshal: false) + rescue + nil + end + plist_username = plist["UserName"] if plist + + return plist_username if plist_username.present? + end + return "root" if boot_path_service_file_present? + return System.user if user_path_service_file_present? + + nil + end + + sig { returns(T::Boolean) } + def pid? + return false if pid.nil? + + !T.must(pid).zero? + end + + sig { returns(T.nilable(T.any(T::Boolean, Integer))) } + def error? + return false if pid? || pid.nil? + return exit_code if exit_code.nil? + + T.must(exit_code).nonzero? + end + + sig { returns(T.nilable(T::Boolean)) } + def unknown_status? + status_output.blank? && !pid? + end + + # Get current PID of daemon process from status output. + sig { returns(T.nilable(Integer)) } + def pid + status_output, _, status_type = status_output_success_type + return if status_type.nil? + + Regexp.last_match(1).to_i if status_output =~ pid_regex(status_type) + end + + # Get current exit code of daemon process from status output. + sig { returns(T.nilable(Integer)) } + def exit_code + status_output, _, status_type = status_output_success_type + return if status_type.nil? + + Regexp.last_match(1).to_i if status_output =~ exit_code_regex(status_type) + end + + sig { returns(T::Hash[T.untyped, T.untyped]) } + def to_hash + hash = { + name:, + service_name:, + running: pid?, + loaded: loaded?(cached: true), + schedulable: timed?, + pid:, + exit_code:, + user: owner, + status: status_symbol, + file: service_file_present? ? dest : service_file, + } + + return hash unless service? + + service = load_service + + return hash if T.must(service).command.blank? + + hash[:command] = T.must(service).manual_command + hash[:working_dir] = T.must(service).working_dir + hash[:root_dir] = T.must(service).root_dir + hash[:log_path] = T.must(service).log_path + hash[:error_log_path] = T.must(service).error_log_path + hash[:interval] = T.must(service).interval + hash[:cron] = T.must(service).cron + + hash + end + + private + + # The purpose of this function is to lazy load the Homebrew::Service class + # and avoid nameclashes with the current Service module. + # It should be used instead of calling formula.service directly. + sig { returns(T.nilable(Homebrew::Service)) } + def load_service + require "formula" + + formula.service + end + + sig { returns(T.nilable(T::Array[T.untyped])) } + def status_output_success_type + @status_output_success_type ||= T.let(nil, T.nilable(T::Array[T.untyped])) + @status_output_success_type ||= if System.launchctl? + cmd = [System.launchctl.to_s, "list", service_name] output = Utils.popen_read(*cmd).chomp + if $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? + success = true + odebug cmd.join(" "), output + [output, success, :launchctl_list] + else + cmd = [System.launchctl.to_s, "print", "#{System.domain_target}/#{service_name}"] + output = Utils.popen_read(*cmd).chomp + success = $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? + odebug cmd.join(" "), output + [output, success, :launchctl_print] + end + elsif System.systemctl? + cmd = ["status", service_name] + output = System::Systemctl.popen_read(*cmd).chomp success = $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? - odebug cmd.join(" "), output - [output, success, :launchctl_print] + odebug [System::Systemctl.executable, System::Systemctl.scope, *cmd].join(" "), output + [output, success, :systemctl] end - elsif System.systemctl? - cmd = ["status", service_name] - output = System::Systemctl.popen_read(*cmd).chomp - success = $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? - odebug [System::Systemctl.executable, System::Systemctl.scope, *cmd].join(" "), - output - [output, success, :systemctl] end - end - sig { returns(T.nilable(String)) } - def status_output - status_output, = status_output_success_type - status_output - end + sig { returns(T.nilable(String)) } + def status_output + status_output, = status_output_success_type + status_output + end - sig { returns(Symbol) } - def status_symbol - if pid? - :started - elsif !loaded?(cached: true) - :none - elsif T.must(exit_code).zero? - if timed? - :scheduled + sig { returns(Symbol) } + def status_symbol + if pid? + :started + elsif !loaded?(cached: true) + :none + elsif T.must(exit_code).zero? + if timed? + :scheduled + else + :stopped + end + elsif error? + :error + elsif unknown_status? + :unknown else - :stopped + :other end - elsif error? - :error - elsif unknown_status? - :unknown - else - :other end - end - sig { params(status_type: Symbol).returns(Regexp) } - def exit_code_regex(status_type) - @exit_code_regex ||= T.let({ - launchctl_list: /"LastExitStatus"\ =\ ([0-9]*);/, - launchctl_print: /last exit code = ([0-9]+)/, - systemctl: /\(code=exited, status=([0-9]*)\)|\(dead\)/, - }, T.nilable(T::Hash[T.untyped, Regexp])) - @exit_code_regex.fetch(status_type) - end + sig { params(status_type: Symbol).returns(Regexp) } + def exit_code_regex(status_type) + @exit_code_regex ||= T.let({ + launchctl_list: /"LastExitStatus"\ =\ ([0-9]*);/, + launchctl_print: /last exit code = ([0-9]+)/, + systemctl: /\(code=exited, status=([0-9]*)\)|\(dead\)/, + }, T.nilable(T::Hash[T.untyped, Regexp])) + @exit_code_regex.fetch(status_type) + end - sig { params(status_type: Symbol).returns(Regexp) } - def pid_regex(status_type) - @pid_regex ||= T.let({ - launchctl_list: /"PID"\ =\ ([0-9]*);/, - launchctl_print: /pid = ([0-9]+)/, - systemctl: /Main PID: ([0-9]*) \((?!code=)/, - }, T.nilable(T::Hash[T.untyped, Regexp])) - @pid_regex.fetch(status_type) - end + sig { params(status_type: Symbol).returns(Regexp) } + def pid_regex(status_type) + @pid_regex ||= T.let({ + launchctl_list: /"PID"\ =\ ([0-9]*);/, + launchctl_print: /pid = ([0-9]+)/, + systemctl: /Main PID: ([0-9]*) \((?!code=)/, + }, T.nilable(T::Hash[T.untyped, Regexp])) + @pid_regex.fetch(status_type) + end - sig { returns(T::Boolean) } - def boot_path_service_file_present? - (T.must(System.boot_path) + T.must(service_file).basename).exist? - end + sig { returns(T::Boolean) } + def boot_path_service_file_present? + (T.must(System.boot_path) + T.must(service_file).basename).exist? + end - sig { returns(T::Boolean) } - def user_path_service_file_present? - (T.must(System.user_path) + T.must(service_file).basename).exist? - end + sig { returns(T::Boolean) } + def user_path_service_file_present? + (T.must(System.user_path) + T.must(service_file).basename).exist? + end - sig { returns(Regexp) } - private_class_method def self.path_or_label_regex - /homebrew(?>\.mxcl)?\.([\w+-.@]+)(\.plist|\.service)?\z/ + sig { returns(Regexp) } + private_class_method def self.path_or_label_regex + /homebrew(?>\.mxcl)?\.([\w+-.@]+)(\.plist|\.service)?\z/ + end end end end diff --git a/Library/Homebrew/services/formulae.rb b/Library/Homebrew/services/formulae.rb index 29836eee32..711c8bb3cb 100644 --- a/Library/Homebrew/services/formulae.rb +++ b/Library/Homebrew/services/formulae.rb @@ -3,29 +3,31 @@ require "services/formula_wrapper" -module Services - module Formulae - # All available services, with optional filters applied - # @private - 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) - require "formula" +module Homebrew + module Services + module Formulae + # All available services, with optional filters applied + # @private + 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) + require "formula" - formulae = Formula.installed - .map { |formula| FormulaWrapper.new(formula) } - .select(&:service?) - .sort_by(&:name) + formulae = Formula.installed + .map { |formula| FormulaWrapper.new(formula) } + .select(&:service?) + .sort_by(&:name) - formulae = formulae.select { |formula| formula.loaded? == loaded } unless loaded.nil? - formulae = formulae.reject { |formula| formula.owner == "root" } if skip_root + formulae = formulae.select { |formula| formula.loaded? == loaded } unless loaded.nil? + formulae = formulae.reject { |formula| formula.owner == "root" } if skip_root - formulae - end + formulae + end - # List all available services with status, user, and path to the file. - sig { returns(T::Array[T::Hash[T.untyped, T.untyped]]) } - def self.services_list - available_services.map(&:to_hash) + # List all available services with status, user, and path to the file. + sig { returns(T::Array[T::Hash[T.untyped, T.untyped]]) } + def self.services_list + available_services.map(&:to_hash) + end end end end diff --git a/Library/Homebrew/services/system.rb b/Library/Homebrew/services/system.rb index 341a7d129f..5aea3e6040 100644 --- a/Library/Homebrew/services/system.rb +++ b/Library/Homebrew/services/system.rb @@ -3,99 +3,102 @@ require_relative "system/systemctl" -module Services - module System - extend FileUtils +module Homebrew + module Services + module System + extend FileUtils - # Path to launchctl binary. - sig { returns(T.nilable(Pathname)) } - def self.launchctl - @launchctl ||= T.let(which("launchctl"), T.nilable(Pathname)) - end - - # Is this a launchctl system - sig { returns(T::Boolean) } - def self.launchctl? - launchctl.present? - end - - # Is this a systemd system - sig { returns(T::Boolean) } - def self.systemctl? - Systemctl.executable.present? - end - - # Woohoo, we are root dude! - sig { returns(T::Boolean) } - def self.root? - Process.euid.zero? - end - - # Current user running `[sudo] brew services`. - sig { returns(T.nilable(String)) } - def self.user - @user ||= T.let(ENV["USER"].presence || Utils.safe_popen_read("/usr/bin/whoami").chomp, T.nilable(String)) - end - - sig { params(pid: T.nilable(Integer)).returns(T.nilable(String)) } - def self.user_of_process(pid) - if pid.nil? || pid.zero? - user - else - Utils.safe_popen_read("ps", "-o", "user", "-p", pid.to_s).lines.second&.chomp + # Path to launchctl binary. + sig { returns(T.nilable(Pathname)) } + def self.launchctl + @launchctl ||= T.let(which("launchctl"), T.nilable(Pathname)) end - end - # Run at boot. - sig { returns(T.nilable(Pathname)) } - def self.boot_path - if launchctl? - Pathname.new("/Library/LaunchDaemons") - elsif systemctl? - Pathname.new("/usr/lib/systemd/system") + # Is this a launchctl system + sig { returns(T::Boolean) } + def self.launchctl? + launchctl.present? end - end - # Run at login. - sig { returns(T.nilable(Pathname)) } - def self.user_path - if launchctl? - Pathname.new("#{Dir.home}/Library/LaunchAgents") - elsif systemctl? - Pathname.new("#{Dir.home}/.config/systemd/user") + # Is this a systemd system + sig { returns(T::Boolean) } + def self.systemctl? + Systemctl.executable.present? end - end - # If root, return `boot_path`, else return `user_path`. - sig { returns(T.nilable(Pathname)) } - def self.path - root? ? boot_path : user_path - end + # Woohoo, we are root dude! + sig { returns(T::Boolean) } + def self.root? + Process.euid.zero? + end - sig { returns(String) } - def self.domain_target - if root? - "system" - elsif (ssh_tty = ENV.fetch("HOMEBREW_SSH_TTY", nil).present? && File.stat("/dev/console").uid != Process.uid) || - (sudo_user = ENV.fetch("HOMEBREW_SUDO_USER", nil).present?) || - (Process.uid != Process.euid) - if @output_warning.blank? && ENV.fetch("HOMEBREW_SERVICES_NO_DOMAIN_WARNING", nil).blank? - if ssh_tty - opoo "running over SSH without /dev/console ownership, using user/* instead of gui/* domain!" - elsif sudo_user - opoo "running through sudo, using user/* instead of gui/* domain!" - else - opoo "uid and euid do not match, using user/* instead of gui/* domain!" - end - unless Homebrew::EnvConfig.no_env_hints? - puts "Hide this warning by setting HOMEBREW_SERVICES_NO_DOMAIN_WARNING." - puts "Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`)." - end - @output_warning = T.let(true, T.nilable(TrueClass)) + # Current user running `[sudo] brew services`. + sig { returns(T.nilable(String)) } + def self.user + @user ||= T.let(ENV["USER"].presence || Utils.safe_popen_read("/usr/bin/whoami").chomp, T.nilable(String)) + end + + sig { params(pid: T.nilable(Integer)).returns(T.nilable(String)) } + def self.user_of_process(pid) + if pid.nil? || pid.zero? + user + else + Utils.safe_popen_read("ps", "-o", "user", "-p", pid.to_s).lines.second&.chomp + end + end + + # Run at boot. + sig { returns(T.nilable(Pathname)) } + def self.boot_path + if launchctl? + Pathname.new("/Library/LaunchDaemons") + elsif systemctl? + Pathname.new("/usr/lib/systemd/system") + end + end + + # Run at login. + sig { returns(T.nilable(Pathname)) } + def self.user_path + if launchctl? + Pathname.new("#{Dir.home}/Library/LaunchAgents") + elsif systemctl? + Pathname.new("#{Dir.home}/.config/systemd/user") + end + end + + # If root, return `boot_path`, else return `user_path`. + sig { returns(T.nilable(Pathname)) } + def self.path + root? ? boot_path : user_path + end + + sig { returns(String) } + def self.domain_target + if root? + "system" + elsif (ssh_tty = ENV.fetch("HOMEBREW_SSH_TTY", nil).present? && + File.stat("/dev/console").uid != Process.uid) || + (sudo_user = ENV.fetch("HOMEBREW_SUDO_USER", nil).present?) || + (Process.uid != Process.euid) + if @output_warning.blank? && ENV.fetch("HOMEBREW_SERVICES_NO_DOMAIN_WARNING", nil).blank? + if ssh_tty + opoo "running over SSH without /dev/console ownership, using user/* instead of gui/* domain!" + elsif sudo_user + opoo "running through sudo, using user/* instead of gui/* domain!" + else + opoo "uid and euid do not match, using user/* instead of gui/* domain!" + end + unless Homebrew::EnvConfig.no_env_hints? + puts "Hide this warning by setting HOMEBREW_SERVICES_NO_DOMAIN_WARNING." + puts "Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`)." + end + @output_warning = T.let(true, T.nilable(TrueClass)) + end + "user/#{Process.euid}" + else + "gui/#{Process.uid}" end - "user/#{Process.euid}" - else - "gui/#{Process.uid}" end end end diff --git a/Library/Homebrew/services/system/systemctl.rb b/Library/Homebrew/services/system/systemctl.rb index 46244ad4b1..354944a2f7 100644 --- a/Library/Homebrew/services/system/systemctl.rb +++ b/Library/Homebrew/services/system/systemctl.rb @@ -1,52 +1,54 @@ # typed: strict # frozen_string_literal: true -module Services - module System - module Systemctl - sig { returns(T.nilable(Pathname)) } - def self.executable - @executable ||= T.let(which("systemctl"), T.nilable(Pathname)) - end +module Homebrew + module Services + module System + module Systemctl + sig { returns(T.nilable(Pathname)) } + def self.executable + @executable ||= T.let(which("systemctl"), T.nilable(Pathname)) + end - sig { void } - def self.reset_executable! - @executable = nil - end + sig { void } + def self.reset_executable! + @executable = nil + end - sig { returns(String) } - def self.scope - System.root? ? "--system" : "--user" - end + sig { returns(String) } + def self.scope + System.root? ? "--system" : "--user" + end - sig { params(args: T.nilable(T.any(String, Pathname))).void } - def self.run(*args) - _run(*args, mode: :default) - end + sig { params(args: T.nilable(T.any(String, Pathname))).void } + def self.run(*args) + _run(*args, mode: :default) + end - sig { params(args: T.nilable(T.any(String, Pathname))).returns(T::Boolean) } - def self.quiet_run(*args) - _run(*args, mode: :quiet) - end + sig { params(args: T.nilable(T.any(String, Pathname))).returns(T::Boolean) } + def self.quiet_run(*args) + _run(*args, mode: :quiet) + end - sig { params(args: T.nilable(T.any(String, Pathname))).returns(String) } - def self.popen_read(*args) - _run(*args, mode: :read) - end + sig { params(args: T.nilable(T.any(String, Pathname))).returns(String) } + def self.popen_read(*args) + _run(*args, mode: :read) + end - sig { params(args: T.nilable(T.any(String, Pathname)), mode: T.nilable(Symbol)).returns(T.untyped) } - private_class_method def self._run(*args, mode:) - require "system_command" - result = SystemCommand.run(executable, - args: [scope, *args.map(&:to_s)], - print_stdout: mode == :default, - print_stderr: mode == :default, - must_succeed: mode == :default, - reset_uid: true) - if mode == :read - result.stdout - elsif mode == :quiet - result.success? + sig { params(args: T.nilable(T.any(String, Pathname)), mode: T.nilable(Symbol)).returns(T.untyped) } + private_class_method def self._run(*args, mode:) + require "system_command" + result = SystemCommand.run(executable, + args: [scope, *args.map(&:to_s)], + print_stdout: mode == :default, + print_stderr: mode == :default, + must_succeed: mode == :default, + reset_uid: true) + if mode == :read + result.stdout + elsif mode == :quiet + result.success? + end end end end diff --git a/Library/Homebrew/test/services/cli_spec.rb b/Library/Homebrew/test/services/cli_spec.rb index ae3a83f95d..dead5151f8 100644 --- a/Library/Homebrew/test/services/cli_spec.rb +++ b/Library/Homebrew/test/services/cli_spec.rb @@ -4,7 +4,7 @@ require "services/cli" require "services/system" require "services/formula_wrapper" -RSpec.describe Services::Cli do +RSpec.describe Homebrew::Services::Cli do subject(:services_cli) { described_class } let(:service_string) { "service" } @@ -17,7 +17,7 @@ RSpec.describe Services::Cli do describe "#running" do it "macOS - returns the currently running services" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Utils).to receive(:popen_read).and_return <<~EOS 77513 50 homebrew.mxcl.php 495 0 homebrew.mxcl.node_exporter @@ -31,8 +31,8 @@ RSpec.describe Services::Cli do end it "systemD - returns the currently running services" do - allow(Services::System).to receive(:launchctl?).and_return(false) - allow(Services::System::Systemctl).to receive(:popen_read).and_return <<~EOS + allow(Homebrew::Services::System).to receive(:launchctl?).and_return(false) + allow(Homebrew::Services::System::Systemctl).to receive(:popen_read).and_return <<~EOS homebrew.php.service loaded active running Homebrew PHP service systemd-udevd.service loaded active running Rule-based Manager for Device Events and Files udisks2.service loaded active running Disk Manager @@ -74,7 +74,7 @@ RSpec.describe Services::Cli do keep_alive?: false, ) allow(service).to receive(:service_name) - allow(Services::FormulaWrapper).to receive(:from).and_return(service) + allow(Homebrew::Services::FormulaWrapper).to receive(:from).and_return(service) allow(services_cli).to receive(:running).and_return(["example_service"]) expect do services_cli.kill_orphaned_services @@ -84,7 +84,7 @@ RSpec.describe Services::Cli do describe "#run" do it "checks empty targets cause no error" do - expect(Services::System).not_to receive(:root?) + expect(Homebrew::Services::System).not_to receive(:root?) services_cli.run([]) end @@ -100,14 +100,14 @@ RSpec.describe Services::Cli do describe "#start" do it "checks missing file causes error" do - expect(Services::System).not_to receive(:root?) + expect(Homebrew::Services::System).not_to receive(:root?) expect do services_cli.start(["service_name"], "/hfdkjshksdjhfkjsdhf/fdsjghsdkjhb") end.to raise_error(UsageError, "Invalid usage: Provided service file does not exist") end it "checks empty targets cause no error" do - expect(Services::System).not_to receive(:root?) + expect(Homebrew::Services::System).not_to receive(:root?) services_cli.start([]) end @@ -123,14 +123,14 @@ RSpec.describe Services::Cli do describe "#stop" do it "checks empty targets cause no error" do - expect(Services::System).not_to receive(:root?) + expect(Homebrew::Services::System).not_to receive(:root?) services_cli.stop([]) end end describe "#kill" do it "checks empty targets cause no error" do - expect(Services::System).not_to receive(:root?) + expect(Homebrew::Services::System).not_to receive(:root?) services_cli.kill([]) end @@ -153,7 +153,7 @@ RSpec.describe Services::Cli do describe "#install_service_file" do it "checks service is installed" do - service = instance_double(Services::FormulaWrapper, name: "name", installed?: false) + service = instance_double(Homebrew::Services::FormulaWrapper, name: "name", installed?: false) expect do services_cli.install_service_file(service, nil) end.to raise_error(UsageError, "Invalid usage: Formula `name` is not installed") @@ -161,7 +161,7 @@ RSpec.describe Services::Cli do it "checks service file exists" do service = instance_double( - Services::FormulaWrapper, + Homebrew::Services::FormulaWrapper, name: "name", installed?: true, service_file: instance_double(Pathname, exist?: false), @@ -177,17 +177,17 @@ RSpec.describe Services::Cli do describe "#systemd_load", :needs_linux do it "checks non-enabling run" do - expect(Services::System::Systemctl).to receive(:run).once.and_return(true) + expect(Homebrew::Services::System::Systemctl).to receive(:run).once.and_return(true) services_cli.systemd_load( - instance_double(Services::FormulaWrapper, service_name: "name"), + instance_double(Homebrew::Services::FormulaWrapper, service_name: "name"), enable: false, ) end it "checks enabling run" do - expect(Services::System::Systemctl).to receive(:run).twice.and_return(true) + expect(Homebrew::Services::System::Systemctl).to receive(:run).twice.and_return(true) services_cli.systemd_load( - instance_double(Services::FormulaWrapper, service_name: "name"), + instance_double(Homebrew::Services::FormulaWrapper, service_name: "name"), enable: true, ) end @@ -195,57 +195,67 @@ RSpec.describe Services::Cli do describe "#launchctl_load", :needs_macos do it "checks non-enabling run" do - allow(Services::System).to receive(:launchctl).and_return(Pathname.new("/bin/launchctl")) - expect(Services::System).to receive(:domain_target).once.and_return("target") + allow(Homebrew::Services::System).to receive(:launchctl).and_return(Pathname.new("/bin/launchctl")) + expect(Homebrew::Services::System).to receive(:domain_target).once.and_return("target") expect(described_class).to receive(:safe_system).once.and_return(true) - services_cli.launchctl_load(instance_double(Services::FormulaWrapper), file: "a", enable: false) + services_cli.launchctl_load(instance_double(Homebrew::Services::FormulaWrapper), file: "a", enable: false) end it "checks enabling run" do - allow(Services::System).to receive(:launchctl).and_return(Pathname.new("/bin/launchctl")) - expect(Services::System).to receive(:domain_target).twice.and_return("target") + allow(Homebrew::Services::System).to receive(:launchctl).and_return(Pathname.new("/bin/launchctl")) + expect(Homebrew::Services::System).to receive(:domain_target).twice.and_return("target") 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) + services_cli.launchctl_load(instance_double(Homebrew::Services::FormulaWrapper, service_name: "name"), + file: "a", + enable: true) end end describe "#service_load" do it "checks non-root for login" do - expect(Services::System).to receive(:launchctl?).once.and_return(false) - expect(Services::System).to receive(:systemctl?).once.and_return(false) - expect(Services::System).to receive(:root?).once.and_return(true) + expect(Homebrew::Services::System).to receive(:launchctl?).once.and_return(false) + expect(Homebrew::Services::System).to receive(:systemctl?).once.and_return(false) + expect(Homebrew::Services::System).to receive(:root?).once.and_return(true) expect do services_cli.service_load( - instance_double(Services::FormulaWrapper, name: "name", service_name: "service.name", -service_startup?: false), enable: false + instance_double( + Homebrew::Services::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 "checks root for startup" do - expect(Services::System).to receive(:launchctl?).once.and_return(false) - expect(Services::System).to receive(:systemctl?).once.and_return(false) - expect(Services::System).to receive(:root?).twice.and_return(false) + expect(Homebrew::Services::System).to receive(:launchctl?).once.and_return(false) + expect(Homebrew::Services::System).to receive(:systemctl?).once.and_return(false) + expect(Homebrew::Services::System).to receive(:root?).twice.and_return(false) expect do services_cli.service_load( - instance_double(Services::FormulaWrapper, name: "name", service_name: "service.name", -service_startup?: true), + instance_double( + Homebrew::Services::FormulaWrapper, + name: "name", + service_name: "service.name", + service_startup?: true, + ), enable: false, ) end.to output("==> Successfully ran `name` (label: service.name)\n").to_stdout end it "triggers launchctl" do - expect(Services::System).to receive(:launchctl?).once.and_return(true) - expect(Services::System).not_to receive(:systemctl?) - expect(Services::System).to receive(:root?).twice.and_return(false) + expect(Homebrew::Services::System).to receive(:launchctl?).once.and_return(true) + expect(Homebrew::Services::System).not_to receive(:systemctl?) + expect(Homebrew::Services::System).to receive(:root?).twice.and_return(false) expect(described_class).to receive(:launchctl_load).once.and_return(true) expect do services_cli.service_load( instance_double( - Services::FormulaWrapper, + Homebrew::Services::FormulaWrapper, name: "name", service_name: "service.name", service_startup?: false, @@ -257,14 +267,14 @@ service_startup?: true), 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(Homebrew::Services::System).to receive(:launchctl?).once.and_return(false) + expect(Homebrew::Services::System).to receive(:systemctl?).once.and_return(true) + expect(Homebrew::Services::System).to receive(:root?).twice.and_return(false) + expect(Homebrew::Services::System::Systemctl).to receive(:run).once.and_return(true) expect do services_cli.service_load( instance_double( - Services::FormulaWrapper, + Homebrew::Services::FormulaWrapper, name: "name", service_name: "service.name", service_startup?: false, @@ -276,14 +286,14 @@ service_startup?: true), end it "represents correct action" 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).twice.and_return(true) + expect(Homebrew::Services::System).to receive(:launchctl?).once.and_return(false) + expect(Homebrew::Services::System).to receive(:systemctl?).once.and_return(true) + expect(Homebrew::Services::System).to receive(:root?).twice.and_return(false) + expect(Homebrew::Services::System::Systemctl).to receive(:run).twice.and_return(true) expect do services_cli.service_load( instance_double( - Services::FormulaWrapper, + Homebrew::Services::FormulaWrapper, name: "name", service_name: "service.name", service_startup?: false, diff --git a/Library/Homebrew/test/services/commands/cleanup_spec.rb b/Library/Homebrew/test/services/commands/cleanup_spec.rb index bb9aa046d8..a6d019e9a4 100644 --- a/Library/Homebrew/test/services/commands/cleanup_spec.rb +++ b/Library/Homebrew/test/services/commands/cleanup_spec.rb @@ -4,7 +4,7 @@ require "services/commands/cleanup" require "services/system" require "services/cli" -RSpec.describe Services::Commands::Cleanup do +RSpec.describe Homebrew::Services::Commands::Cleanup do describe "#TRIGGERS" do it "contains all restart triggers" do expect(described_class::TRIGGERS).to eq(%w[cleanup clean cl rm]) @@ -13,9 +13,9 @@ RSpec.describe Services::Commands::Cleanup do describe "#run" do it "root - prints on empty cleanup" do - expect(Services::System).to receive(:root?).once.and_return(true) - expect(Services::Cli).to receive(:kill_orphaned_services).once.and_return([]) - expect(Services::Cli).to receive(:remove_unused_service_files).once.and_return([]) + expect(Homebrew::Services::System).to receive(:root?).once.and_return(true) + expect(Homebrew::Services::Cli).to receive(:kill_orphaned_services).once.and_return([]) + expect(Homebrew::Services::Cli).to receive(:remove_unused_service_files).once.and_return([]) expect do described_class.run @@ -23,9 +23,9 @@ RSpec.describe Services::Commands::Cleanup do end it "user - prints on empty cleanup" do - expect(Services::System).to receive(:root?).once.and_return(false) - expect(Services::Cli).to receive(:kill_orphaned_services).once.and_return([]) - expect(Services::Cli).to receive(:remove_unused_service_files).once.and_return([]) + expect(Homebrew::Services::System).to receive(:root?).once.and_return(false) + expect(Homebrew::Services::Cli).to receive(:kill_orphaned_services).once.and_return([]) + expect(Homebrew::Services::Cli).to receive(:remove_unused_service_files).once.and_return([]) expect do described_class.run @@ -33,9 +33,9 @@ RSpec.describe Services::Commands::Cleanup do end it "prints nothing on cleanup" do - expect(Services::System).not_to receive(:root?) - expect(Services::Cli).to receive(:kill_orphaned_services).once.and_return(["a"]) - expect(Services::Cli).to receive(:remove_unused_service_files).once.and_return(["b"]) + expect(Homebrew::Services::System).not_to receive(:root?) + expect(Homebrew::Services::Cli).to receive(:kill_orphaned_services).once.and_return(["a"]) + expect(Homebrew::Services::Cli).to receive(:remove_unused_service_files).once.and_return(["b"]) expect do described_class.run diff --git a/Library/Homebrew/test/services/commands/info_spec.rb b/Library/Homebrew/test/services/commands/info_spec.rb index 08ab6c344b..62f03099eb 100644 --- a/Library/Homebrew/test/services/commands/info_spec.rb +++ b/Library/Homebrew/test/services/commands/info_spec.rb @@ -2,7 +2,7 @@ require "services/commands/info" -RSpec.describe Services::Commands::Info do +RSpec.describe Homebrew::Services::Commands::Info do before do allow_any_instance_of(IO).to receive(:tty?).and_return(false) end diff --git a/Library/Homebrew/test/services/commands/list_spec.rb b/Library/Homebrew/test/services/commands/list_spec.rb index 40c38e8227..208ee06143 100644 --- a/Library/Homebrew/test/services/commands/list_spec.rb +++ b/Library/Homebrew/test/services/commands/list_spec.rb @@ -2,7 +2,7 @@ require "services/commands/list" -RSpec.describe Services::Commands::List do +RSpec.describe Homebrew::Services::Commands::List do describe "#TRIGGERS" do it "contains all restart triggers" do expect(described_class::TRIGGERS).to eq([nil, "list", "ls"]) @@ -11,7 +11,7 @@ RSpec.describe Services::Commands::List do describe "#run" do it "fails with empty list" do - expect(Services::Formulae).to receive(:services_list).and_return([]) + expect(Homebrew::Services::Formulae).to receive(:services_list).and_return([]) expect do allow($stderr).to receive(:tty?).and_return(true) described_class.run @@ -27,7 +27,7 @@ RSpec.describe Services::Commands::List do file: "/dev/null", loaded: true, } - expect(Services::Formulae).to receive(:services_list).and_return([formula]) + expect(Homebrew::Services::Formulae).to receive(:services_list).and_return([formula]) expect do described_class.run end.to output(out).to_stdout @@ -47,7 +47,7 @@ RSpec.describe Services::Commands::List do filtered_formula = formula.slice(*described_class::JSON_FIELDS) expected_output = "#{JSON.pretty_generate([filtered_formula])}\n" - expect(Services::Formulae).to receive(:services_list).and_return([formula]) + expect(Homebrew::Services::Formulae).to receive(:services_list).and_return([formula]) expect do described_class.run(json: true) end.to output(expected_output).to_stdout diff --git a/Library/Homebrew/test/services/commands/restart_spec.rb b/Library/Homebrew/test/services/commands/restart_spec.rb index 5af0acfef3..d37eeab741 100644 --- a/Library/Homebrew/test/services/commands/restart_spec.rb +++ b/Library/Homebrew/test/services/commands/restart_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true require "services/commands/restart" -require "services/cli" -require "services/formula_wrapper" -RSpec.describe Services::Commands::Restart do + +RSpec.describe Homebrew::Services::Commands::Restart do describe "#TRIGGERS" do it "contains all restart triggers" do expect(described_class::TRIGGERS).to eq(%w[restart relaunch reload r]) @@ -18,27 +17,27 @@ RSpec.describe Services::Commands::Restart do end it "starts if services are not loaded" do - expect(Services::Cli).not_to receive(:run) - expect(Services::Cli).not_to receive(:stop) - expect(Services::Cli).to receive(:start).once - service = instance_double(Services::FormulaWrapper, service_name: "name", loaded?: false) + expect(Homebrew::Services::Cli).not_to receive(:run) + expect(Homebrew::Services::Cli).not_to receive(:stop) + expect(Homebrew::Services::Cli).to receive(:start).once + service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: false) expect(described_class.run([service], verbose: false)).to be_nil end it "starts if services are loaded with file" do - expect(Services::Cli).not_to receive(:run) - expect(Services::Cli).to receive(:start).once - expect(Services::Cli).to receive(:stop).once - service = instance_double(Services::FormulaWrapper, service_name: "name", loaded?: true, + expect(Homebrew::Services::Cli).not_to receive(:run) + expect(Homebrew::Services::Cli).to receive(:start).once + expect(Homebrew::Services::Cli).to receive(:stop).once + service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: true, service_file_present?: true) expect(described_class.run([service], verbose: false)).to be_nil end it "runs if services are loaded without file" do - expect(Services::Cli).not_to receive(:start) - expect(Services::Cli).to receive(:run).once - expect(Services::Cli).to receive(:stop).once - service = instance_double(Services::FormulaWrapper, service_name: "name", loaded?: true, + expect(Homebrew::Services::Cli).not_to receive(:start) + expect(Homebrew::Services::Cli).to receive(:run).once + expect(Homebrew::Services::Cli).to receive(:stop).once + service = instance_double(Homebrew::Services::FormulaWrapper, service_name: "name", loaded?: true, service_file_present?: false) expect(described_class.run([service], verbose: false)).to be_nil end diff --git a/Library/Homebrew/test/services/formulae_spec.rb b/Library/Homebrew/test/services/formulae_spec.rb index 96f4e3e74c..d6b08c33ed 100644 --- a/Library/Homebrew/test/services/formulae_spec.rb +++ b/Library/Homebrew/test/services/formulae_spec.rb @@ -2,7 +2,7 @@ require "services/formulae" -RSpec.describe Services::Formulae do +RSpec.describe Homebrew::Services::Formulae do describe "#services_list" do it "empty list without available formulae" do allow(described_class).to receive(:available_services).and_return({}) @@ -10,7 +10,7 @@ RSpec.describe Services::Formulae do end it "list with available formulae" do - formula = instance_double(Services::FormulaWrapper) + formula = instance_double(Homebrew::Services::FormulaWrapper) expected = [ { file: Pathname.new("/Library/LaunchDaemons/file.plist"), diff --git a/Library/Homebrew/test/services/formulae_wrapper_spec.rb b/Library/Homebrew/test/services/formulae_wrapper_spec.rb index 317995b777..2e443678fa 100644 --- a/Library/Homebrew/test/services/formulae_wrapper_spec.rb +++ b/Library/Homebrew/test/services/formulae_wrapper_spec.rb @@ -4,7 +4,7 @@ require "services/system" require "services/formula_wrapper" require "tempfile" -RSpec.describe Services::FormulaWrapper do +RSpec.describe Homebrew::Services::FormulaWrapper do subject(:service) { described_class.new(formula) } let(:formula) do @@ -41,17 +41,17 @@ RSpec.describe Services::FormulaWrapper do describe "#service_file" do it "macOS - outputs the full service file path" do - allow(Services::System).to receive(:launchctl?).and_return(true) + allow(Homebrew::Services::System).to receive(:launchctl?).and_return(true) expect(service.service_file.to_s).to eq("/usr/local/opt/mysql/homebrew.mysql.plist") end it "systemD - outputs the full service file path" do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: true) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: true) expect(service.service_file.to_s).to eq("/usr/local/opt/mysql/homebrew.mysql.service") end it "Other - outputs no service file" do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: false) expect(service.service_file).to be_nil end end @@ -64,45 +64,45 @@ RSpec.describe Services::FormulaWrapper do describe "#service_name" do it "macOS - outputs the service name" do - allow(Services::System).to receive(:launchctl?).and_return(true) + allow(Homebrew::Services::System).to receive(:launchctl?).and_return(true) expect(service.service_name).to eq("plist-mysql-test") end it "systemD - outputs the service name" do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: true) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: true) expect(service.service_name).to eq("plist-mysql-test") end it "Other - outputs no service name" do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: false) expect(service.service_name).to be_nil end end describe "#dest_dir" do before do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: false) end it "macOS - user - outputs the destination directory for the service file" do ENV["HOME"] = "/tmp_home" - allow(Services::System).to receive_messages(root?: false, launchctl?: true) + allow(Homebrew::Services::System).to receive_messages(root?: false, launchctl?: true) expect(service.dest_dir.to_s).to eq("/tmp_home/Library/LaunchAgents") end it "macOS - root - outputs the destination directory for the service file" do - allow(Services::System).to receive_messages(launchctl?: true, root?: true) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, root?: true) expect(service.dest_dir.to_s).to eq("/Library/LaunchDaemons") end it "systemD - user - outputs the destination directory for the service file" do ENV["HOME"] = "/tmp_home" - allow(Services::System).to receive_messages(root?: false, launchctl?: false, systemctl?: true) + allow(Homebrew::Services::System).to receive_messages(root?: false, launchctl?: false, systemctl?: true) expect(service.dest_dir.to_s).to eq("/tmp_home/.config/systemd/user") end it "systemD - root - outputs the destination directory for the service file" do - allow(Services::System).to receive_messages(root?: true, launchctl?: false, systemctl?: true) + allow(Homebrew::Services::System).to receive_messages(root?: true, launchctl?: false, systemctl?: true) expect(service.dest_dir.to_s).to eq("/usr/lib/systemd/system") end end @@ -110,16 +110,16 @@ RSpec.describe Services::FormulaWrapper do describe "#dest" do before do ENV["HOME"] = "/tmp_home" - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: false) end it "macOS - outputs the destination for the service file" do - allow(Services::System).to receive(:launchctl?).and_return(true) + allow(Homebrew::Services::System).to receive(:launchctl?).and_return(true) expect(service.dest.to_s).to eq("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist") end it "systemD - outputs the destination for the service file" do - allow(Services::System).to receive(:systemctl?).and_return(true) + allow(Homebrew::Services::System).to receive(:systemctl?).and_return(true) expect(service.dest.to_s).to eq("/tmp_home/.config/systemd/user/homebrew.mysql.service") end end @@ -132,20 +132,20 @@ RSpec.describe Services::FormulaWrapper do describe "#loaded?" do it "macOS - outputs if the service is loaded" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) allow(Utils).to receive(:safe_popen_read) expect(service.loaded?).to be(false) end it "systemD - outputs if the service is loaded" do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: true) - allow(Services::System::Systemctl).to receive(:quiet_run).and_return(false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: true) + allow(Homebrew::Services::System::Systemctl).to receive(:quiet_run).and_return(false) allow(Utils).to receive(:safe_popen_read) expect(service.loaded?).to be(false) end it "Other - outputs no status" do - allow(Services::System).to receive_messages(launchctl?: false, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: false, systemctl?: false) expect(service.loaded?).to be_nil end end @@ -182,7 +182,7 @@ RSpec.describe Services::FormulaWrapper do it "user if file present" do allow(service).to receive_messages(boot_path_service_file_present?: false, user_path_service_file_present?: true) - allow(Services::System).to receive(:user).and_return("user") + allow(Homebrew::Services::System).to receive(:user).and_return("user") expect(service.owner).to eq("user") end @@ -195,24 +195,24 @@ RSpec.describe Services::FormulaWrapper do describe "#service_file_present?" do it "macOS - outputs if the service file is present" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) expect(service.service_file_present?).to be(false) end it "macOS - outputs if the service file is present for root" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) expect(service.service_file_present?(for: :root)).to be(false) end it "macOS - outputs if the service file is present for user" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) expect(service.service_file_present?(for: :user)).to be(false) end end describe "#owner?" do it "macOS - outputs the service file owner" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) expect(service.owner).to be_nil end end @@ -321,7 +321,7 @@ RSpec.describe Services::FormulaWrapper do describe "#to_hash" do it "represents non-service values" do - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) allow_any_instance_of(described_class).to receive_messages(service?: false, service_file_present?: false) expected = { exit_code: nil, @@ -340,7 +340,7 @@ RSpec.describe Services::FormulaWrapper do it "represents running non-service values" do ENV["HOME"] = "/tmp_home" - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) expect(service).to receive(:service?).twice.and_return(false) expect(service).to receive(:service_file_present?).and_return(true) expected = { @@ -360,7 +360,7 @@ RSpec.describe Services::FormulaWrapper do it "represents service values" do ENV["HOME"] = "/tmp_home" - allow(Services::System).to receive_messages(launchctl?: true, systemctl?: false) + allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false) expect(service).to receive(:service?).twice.and_return(true) expect(service).to receive(:service_file_present?).and_return(true) expect(service).to receive(:load_service).twice.and_return(service_object) diff --git a/Library/Homebrew/test/services/system/systemctl_spec.rb b/Library/Homebrew/test/services/system/systemctl_spec.rb index b66b3884c4..88d95c7e6d 100644 --- a/Library/Homebrew/test/services/system/systemctl_spec.rb +++ b/Library/Homebrew/test/services/system/systemctl_spec.rb @@ -3,15 +3,15 @@ require "services/system" require "services/system/systemctl" -RSpec.describe Services::System::Systemctl do +RSpec.describe Homebrew::Services::System::Systemctl do describe ".scope" do it "outputs systemctl scope for user" do - allow(Services::System).to receive(:root?).and_return(false) + allow(Homebrew::Services::System).to receive(:root?).and_return(false) expect(described_class.scope).to eq("--user") end it "outputs systemctl scope for root" do - allow(Services::System).to receive(:root?).and_return(true) + allow(Homebrew::Services::System).to receive(:root?).and_return(true) expect(described_class.scope).to eq("--system") end end diff --git a/Library/Homebrew/test/services/system_spec.rb b/Library/Homebrew/test/services/system_spec.rb index 8968261bf9..7334d26a0e 100644 --- a/Library/Homebrew/test/services/system_spec.rb +++ b/Library/Homebrew/test/services/system_spec.rb @@ -2,7 +2,7 @@ require "services/system" -RSpec.describe Services::System do +RSpec.describe Homebrew::Services::System do describe "#launchctl" do it "macOS - outputs launchctl command location", :needs_macos do expect(described_class.launchctl).to eq(Pathname.new("/bin/launchctl"))