Fix services types
Some of the typing/`T.must` usage when moving the Homebrew/homebrew-services code to Homebrew/brew was not quite correct. Rather than trying to make everything `strict` and import at the same time: let's mostly loosen the typing to fix a few bugs and allow us to add more later.
This commit is contained in:
		
							parent
							
								
									0344030256
								
							
						
					
					
						commit
						c82518032e
					
				@ -1,4 +1,4 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "services/formula_wrapper"
 | 
			
		||||
@ -15,7 +15,7 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
      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))
 | 
			
		||||
        @sudo_service_user = sudo_service_user
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Binary name.
 | 
			
		||||
@ -41,7 +41,6 @@ module Homebrew
 | 
			
		||||
      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?
 | 
			
		||||
 | 
			
		||||
@ -49,7 +48,6 @@ module Homebrew
 | 
			
		||||
      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 = []
 | 
			
		||||
@ -67,7 +65,6 @@ module Homebrew
 | 
			
		||||
        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|
 | 
			
		||||
@ -82,7 +79,7 @@ module Homebrew
 | 
			
		||||
      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 }
 | 
			
		||||
      sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T::Boolean).void }
 | 
			
		||||
      def self.run(targets, verbose: false)
 | 
			
		||||
        targets.each do |service|
 | 
			
		||||
          if service.pid?
 | 
			
		||||
@ -102,7 +99,7 @@ module Homebrew
 | 
			
		||||
        params(
 | 
			
		||||
          targets:      T::Array[Services::FormulaWrapper],
 | 
			
		||||
          service_file: T.nilable(T.any(String, Pathname)),
 | 
			
		||||
          verbose:      T.nilable(T::Boolean),
 | 
			
		||||
          verbose:      T::Boolean,
 | 
			
		||||
        ).void
 | 
			
		||||
      }
 | 
			
		||||
      def self.start(targets, service_file = nil, verbose: false)
 | 
			
		||||
@ -121,11 +118,12 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
          odie "Formula `#{service.name}` is not installed." unless service.installed?
 | 
			
		||||
 | 
			
		||||
          file ||= if T.must(service.service_file).exist? || System.systemctl?
 | 
			
		||||
          file ||= if 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
 | 
			
		||||
                (keg = Keg.for service.formula.opt_prefix) &&
 | 
			
		||||
                keg.plist_installed?
 | 
			
		||||
            service_file = Dir["#{keg}/*#{service.service_file.extname}"].first
 | 
			
		||||
            Pathname.new service_file if service_file.present?
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
@ -147,8 +145,8 @@ module Homebrew
 | 
			
		||||
      sig {
 | 
			
		||||
        params(
 | 
			
		||||
          targets:  T::Array[Services::FormulaWrapper],
 | 
			
		||||
          verbose:  T.nilable(T::Boolean),
 | 
			
		||||
          no_wait:  T.nilable(T::Boolean),
 | 
			
		||||
          verbose:  T::Boolean,
 | 
			
		||||
          no_wait:  T::Boolean,
 | 
			
		||||
          max_wait: T.nilable(T.any(Integer, Float)),
 | 
			
		||||
        ).void
 | 
			
		||||
      }
 | 
			
		||||
@ -208,7 +206,7 @@ module Homebrew
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Stop a service but keep it registered.
 | 
			
		||||
      sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
 | 
			
		||||
      sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T::Boolean).void }
 | 
			
		||||
      def self.kill(targets, verbose: false)
 | 
			
		||||
        targets.each do |service|
 | 
			
		||||
          if !service.pid?
 | 
			
		||||
@ -233,7 +231,6 @@ module Homebrew
 | 
			
		||||
      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
 | 
			
		||||
@ -302,7 +299,7 @@ module Homebrew
 | 
			
		||||
        params(
 | 
			
		||||
          service: Services::FormulaWrapper,
 | 
			
		||||
          file:    T.nilable(T.any(String, Pathname)),
 | 
			
		||||
          enable:  T.nilable(T::Boolean),
 | 
			
		||||
          enable:  T::Boolean,
 | 
			
		||||
        ).void
 | 
			
		||||
      }
 | 
			
		||||
      def self.launchctl_load(service, file:, enable:)
 | 
			
		||||
@ -310,13 +307,13 @@ module Homebrew
 | 
			
		||||
        safe_system System.launchctl, "bootstrap", System.domain_target, file
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void }
 | 
			
		||||
      sig { params(service: Services::FormulaWrapper, enable: 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
 | 
			
		||||
        System::Systemctl.run("start", service.service_name)
 | 
			
		||||
        System::Systemctl.run("enable", service.service_name) if enable
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(service: Services::FormulaWrapper, enable: T.nilable(T::Boolean)).void }
 | 
			
		||||
      sig { params(service: Services::FormulaWrapper, enable: 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!"
 | 
			
		||||
@ -338,22 +335,21 @@ module Homebrew
 | 
			
		||||
        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?
 | 
			
		||||
        unless 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
 | 
			
		||||
        temp = Tempfile.new(service.service_name)
 | 
			
		||||
        temp << if file.blank?
 | 
			
		||||
          contents = service.service_file.read
 | 
			
		||||
 | 
			
		||||
          if sudo_service_user && Services::System.launchctl?
 | 
			
		||||
          if sudo_service_user && System.launchctl?
 | 
			
		||||
            # set the username in the new plist file
 | 
			
		||||
            ohai "Setting username in #{service.service_name} to #{Services::System.user}"
 | 
			
		||||
            ohai "Setting username in #{service.service_name} to #{System.user}"
 | 
			
		||||
            plist_data = Plist.parse_xml(contents, marshal: false)
 | 
			
		||||
            plist_data["UserName"] = sudo_service_user
 | 
			
		||||
            plist_data.to_plist
 | 
			
		||||
@ -361,7 +357,7 @@ module Homebrew
 | 
			
		||||
            contents
 | 
			
		||||
          end
 | 
			
		||||
        else
 | 
			
		||||
          T.must(file).read
 | 
			
		||||
          file.read
 | 
			
		||||
        end
 | 
			
		||||
        temp.flush
 | 
			
		||||
 | 
			
		||||
@ -374,7 +370,7 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        chmod 0644, service.dest
 | 
			
		||||
 | 
			
		||||
        Services::System::Systemctl.run("daemon-reload") if System.systemctl?
 | 
			
		||||
        System::Systemctl.run("daemon-reload") if System.systemctl?
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,8 @@ module Homebrew
 | 
			
		||||
        sig {
 | 
			
		||||
          params(
 | 
			
		||||
            targets: T::Array[Services::FormulaWrapper],
 | 
			
		||||
            verbose: T.nilable(T::Boolean),
 | 
			
		||||
            json:    T.nilable(T::Boolean),
 | 
			
		||||
            verbose: T::Boolean,
 | 
			
		||||
            json:    T::Boolean,
 | 
			
		||||
          ).void
 | 
			
		||||
        }
 | 
			
		||||
        def self.run(targets, verbose:, json:)
 | 
			
		||||
@ -33,7 +33,7 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        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?
 | 
			
		||||
          return bool.to_s if !$stdout.tty? || Homebrew::EnvConfig.no_emoji?
 | 
			
		||||
 | 
			
		||||
          if bool
 | 
			
		||||
            "#{Tty.bold}#{Formatter.success("✔")}#{Tty.reset}"
 | 
			
		||||
@ -42,7 +42,7 @@ module Homebrew
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        sig { params(hash: T.untyped, verbose: T.nilable(T::Boolean)).returns(String) }
 | 
			
		||||
        sig { params(hash: T::Hash[Symbol, T.untyped], verbose: 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"
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ module Homebrew
 | 
			
		||||
      module Kill
 | 
			
		||||
        TRIGGERS = %w[kill k].freeze
 | 
			
		||||
 | 
			
		||||
        sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
 | 
			
		||||
        sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T::Boolean).void }
 | 
			
		||||
        def self.run(targets, verbose:)
 | 
			
		||||
          Services::Cli.check(targets)
 | 
			
		||||
          Services::Cli.kill(targets, verbose:)
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "services/cli"
 | 
			
		||||
@ -29,7 +29,6 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
@ -40,7 +39,6 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        # 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]))
 | 
			
		||||
@ -71,7 +69,7 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        # Get formula status output
 | 
			
		||||
        # @private
 | 
			
		||||
        sig { params(status: T.anything).returns(T.nilable(String)) }
 | 
			
		||||
        sig { params(status: Symbol).returns(T.nilable(String)) }
 | 
			
		||||
        def self.get_status_string(status)
 | 
			
		||||
          case status
 | 
			
		||||
          when :started, :scheduled then "#{Tty.green}#{status}#{Tty.reset}"
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        TRIGGERS = %w[restart relaunch reload r].freeze
 | 
			
		||||
 | 
			
		||||
        sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).returns(NilClass) }
 | 
			
		||||
        sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T::Boolean).void }
 | 
			
		||||
        def self.run(targets, verbose:)
 | 
			
		||||
          Services::Cli.check(targets)
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
          Services::Cli.run(targets, verbose:) if ran.present?
 | 
			
		||||
          Services::Cli.start(started, verbose:) if started.present?
 | 
			
		||||
          nil
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ module Homebrew
 | 
			
		||||
      module Run
 | 
			
		||||
        TRIGGERS = ["run"].freeze
 | 
			
		||||
 | 
			
		||||
        sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T.nilable(T::Boolean)).void }
 | 
			
		||||
        sig { params(targets: T::Array[Services::FormulaWrapper], verbose: T::Boolean).void }
 | 
			
		||||
        def self.run(targets, verbose:)
 | 
			
		||||
          Services::Cli.check(targets)
 | 
			
		||||
          Services::Cli.run(targets, verbose:)
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ module Homebrew
 | 
			
		||||
          params(
 | 
			
		||||
            targets:      T::Array[Services::FormulaWrapper],
 | 
			
		||||
            custom_plist: T.nilable(String),
 | 
			
		||||
            verbose:      T.nilable(T::Boolean),
 | 
			
		||||
            verbose:      T::Boolean,
 | 
			
		||||
          ).void
 | 
			
		||||
        }
 | 
			
		||||
        def self.run(targets, custom_plist, verbose:)
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,8 @@ module Homebrew
 | 
			
		||||
        sig {
 | 
			
		||||
          params(
 | 
			
		||||
            targets:  T::Array[Services::FormulaWrapper],
 | 
			
		||||
            verbose:  T.nilable(T::Boolean),
 | 
			
		||||
            no_wait:  T.nilable(T::Boolean),
 | 
			
		||||
            verbose:  T::Boolean,
 | 
			
		||||
            no_wait:  T::Boolean,
 | 
			
		||||
            max_wait: T.nilable(Float),
 | 
			
		||||
          ).void
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Wrapper for a formula to handle service-related stuff like parsing and
 | 
			
		||||
@ -25,75 +25,73 @@ module Homebrew
 | 
			
		||||
      # 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)
 | 
			
		||||
        @formula = formula
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Delegate access to `formula.name`.
 | 
			
		||||
      sig { returns(String) }
 | 
			
		||||
      attr_reader :name
 | 
			
		||||
      def name
 | 
			
		||||
        @name ||= formula.name
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Delegate access to `formula.service?`.
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def service?
 | 
			
		||||
        @service
 | 
			
		||||
        @service ||= @formula.service?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Delegate access to `formula.service.timed?`.
 | 
			
		||||
      # TODO: this should either be T::Boolean or renamed to `timed`
 | 
			
		||||
      sig { returns(T.nilable(T::Boolean)) }
 | 
			
		||||
      def timed?
 | 
			
		||||
        @timed ||= T.let(service? ? T.must(load_service).timed? : nil, T.nilable(T::Boolean))
 | 
			
		||||
        @timed ||= (load_service.timed? if service?)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Delegate access to `formula.service.keep_alive?`.`
 | 
			
		||||
      # Delegate access to `formula.service.keep_alive?`.
 | 
			
		||||
      # TODO: this should either be T::Boolean or renamed to `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?
 | 
			
		||||
        @keep_alive ||= (load_service.keep_alive? if service?)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # service_name delegates with formula.plist_name or formula.service_name for systemd
 | 
			
		||||
      # (e.g., `homebrew.<formula>`).
 | 
			
		||||
      # service_name delegates with formula.plist_name or formula.service_name
 | 
			
		||||
      # for systemd (e.g., `homebrew.<formula>`).
 | 
			
		||||
      sig { returns(T.nilable(String)) }
 | 
			
		||||
      attr_reader :service_name
 | 
			
		||||
      def service_name
 | 
			
		||||
        @service_name ||= if System.launchctl?
 | 
			
		||||
          formula.plist_name
 | 
			
		||||
        elsif System.systemctl?
 | 
			
		||||
          formula.service_name
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # service_file delegates with formula.launchd_service_path or formula.systemd_service_path for systemd.
 | 
			
		||||
      sig { returns(T.nilable(Pathname)) }
 | 
			
		||||
      attr_reader :service_file
 | 
			
		||||
      def service_file
 | 
			
		||||
        @service_file ||= if System.launchctl?
 | 
			
		||||
          formula.launchd_service_path
 | 
			
		||||
        elsif System.systemctl?
 | 
			
		||||
          formula.systemd_service_path
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Whether the service should be launched at startup
 | 
			
		||||
      sig { returns(T.nilable(T::Boolean)) }
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def service_startup?
 | 
			
		||||
        @service_startup ||= service? ? T.must(load_service).requires_root? : false
 | 
			
		||||
        @service_startup ||= if service?
 | 
			
		||||
          load_service.requires_root?
 | 
			
		||||
        else
 | 
			
		||||
          false
 | 
			
		||||
        end
 | 
			
		||||
      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)
 | 
			
		||||
        System.root? ? System.boot_path : 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
 | 
			
		||||
        dest_dir + service_file.basename
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Returns `true` if any version of the formula is installed.
 | 
			
		||||
@ -106,7 +104,7 @@ module Homebrew
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def plist?
 | 
			
		||||
        return false unless installed?
 | 
			
		||||
        return true if T.must(service_file).file?
 | 
			
		||||
        return true if service_file.file?
 | 
			
		||||
        return false unless formula.opt_prefix.exist?
 | 
			
		||||
        return true if Keg.for(formula.opt_prefix).plist_installed?
 | 
			
		||||
 | 
			
		||||
@ -116,6 +114,7 @@ module Homebrew
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Returns `true` if the service is loaded, else false.
 | 
			
		||||
      # TODO: this should either be T::Boolean or renamed to `loaded`
 | 
			
		||||
      sig { params(cached: T::Boolean).returns(T.nilable(T::Boolean)) }
 | 
			
		||||
      def loaded?(cached: false)
 | 
			
		||||
        if System.launchctl?
 | 
			
		||||
@ -123,17 +122,18 @@ module Homebrew
 | 
			
		||||
          _, status_success, = status_output_success_type
 | 
			
		||||
          status_success
 | 
			
		||||
        elsif System.systemctl?
 | 
			
		||||
          System::Systemctl.quiet_run("status", T.must(service_file).basename)
 | 
			
		||||
          System::Systemctl.quiet_run("status", 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
 | 
			
		||||
      # Accepts `type` with values `:root` for boot path or `:user` for user path.
 | 
			
		||||
      sig { params(type: T.nilable(Symbol)).returns(T::Boolean) }
 | 
			
		||||
      def service_file_present?(type: nil)
 | 
			
		||||
        case type
 | 
			
		||||
        when :root
 | 
			
		||||
          boot_path_service_file_present?
 | 
			
		||||
        elsif opts[:for] && opts[:for] == :user
 | 
			
		||||
        when :user
 | 
			
		||||
          user_path_service_file_present?
 | 
			
		||||
        else
 | 
			
		||||
          boot_path_service_file_present? || user_path_service_file_present?
 | 
			
		||||
@ -161,43 +161,34 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def pid?
 | 
			
		||||
        return false if pid.nil?
 | 
			
		||||
 | 
			
		||||
        !T.must(pid).zero?
 | 
			
		||||
        pid.present? && !pid.zero?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T.nilable(T.any(T::Boolean, Integer))) }
 | 
			
		||||
      sig { returns(T::Boolean) }
 | 
			
		||||
      def error?
 | 
			
		||||
        return false if pid? || pid.nil?
 | 
			
		||||
        return exit_code if exit_code.nil?
 | 
			
		||||
        return false if pid?
 | 
			
		||||
 | 
			
		||||
        T.must(exit_code).nonzero?
 | 
			
		||||
        exit_code.present? && exit_code.nonzero?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T.nilable(T::Boolean)) }
 | 
			
		||||
      sig { returns(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]) }
 | 
			
		||||
      sig { returns(T::Hash[Symbol, T.anything]) }
 | 
			
		||||
      def to_hash
 | 
			
		||||
        hash = {
 | 
			
		||||
          name:,
 | 
			
		||||
@ -216,15 +207,15 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
        service = load_service
 | 
			
		||||
 | 
			
		||||
        return hash if T.must(service).command.blank?
 | 
			
		||||
        return hash if 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[:command] = service.manual_command
 | 
			
		||||
        hash[:working_dir] = service.working_dir
 | 
			
		||||
        hash[:root_dir] = service.root_dir
 | 
			
		||||
        hash[:log_path] = service.log_path
 | 
			
		||||
        hash[:error_log_path] = service.error_log_path
 | 
			
		||||
        hash[:interval] = service.interval
 | 
			
		||||
        hash[:cron] = service.cron
 | 
			
		||||
 | 
			
		||||
        hash
 | 
			
		||||
      end
 | 
			
		||||
@ -234,16 +225,14 @@ module Homebrew
 | 
			
		||||
      # 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)) }
 | 
			
		||||
      sig { returns(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
 | 
			
		||||
@ -279,7 +268,7 @@ module Homebrew
 | 
			
		||||
          :started
 | 
			
		||||
        elsif !loaded?(cached: true)
 | 
			
		||||
          :none
 | 
			
		||||
        elsif T.must(exit_code).zero?
 | 
			
		||||
        elsif exit_code.present? && exit_code.zero?
 | 
			
		||||
          if timed?
 | 
			
		||||
            :scheduled
 | 
			
		||||
          else
 | 
			
		||||
@ -294,34 +283,38 @@ module Homebrew
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(status_type: Symbol).returns(Regexp) }
 | 
			
		||||
      def exit_code_regex(status_type)
 | 
			
		||||
        @exit_code_regex ||= T.let({
 | 
			
		||||
        @exit_code_regex ||= {
 | 
			
		||||
          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({
 | 
			
		||||
        @pid_regex ||= {
 | 
			
		||||
          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?
 | 
			
		||||
        boot_path = System.boot_path
 | 
			
		||||
        return false if boot_path.blank?
 | 
			
		||||
 | 
			
		||||
        (boot_path + 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?
 | 
			
		||||
        user_path = System.user_path
 | 
			
		||||
        return false if user_path.blank?
 | 
			
		||||
 | 
			
		||||
        (user_path + service_file.basename).exist?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(Regexp) }
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "services/formula_wrapper"
 | 
			
		||||
@ -24,7 +24,6 @@ module Homebrew
 | 
			
		||||
      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)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
@ -20,22 +20,18 @@ module Homebrew
 | 
			
		||||
          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))).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)), mode: T.nilable(Symbol)).returns(T.untyped) }
 | 
			
		||||
        private_class_method def self._run(*args, mode:)
 | 
			
		||||
          require "system_command"
 | 
			
		||||
          result = SystemCommand.run(executable,
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ RSpec.describe Homebrew::Services::Commands::Restart do
 | 
			
		||||
      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
 | 
			
		||||
      expect { described_class.run([service], verbose: false) }.not_to raise_error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "starts if services are loaded with file" do
 | 
			
		||||
@ -30,7 +30,7 @@ RSpec.describe Homebrew::Services::Commands::Restart do
 | 
			
		||||
      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
 | 
			
		||||
      expect { described_class.run([service], verbose: false) }.not_to raise_error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "runs if services are loaded without file" do
 | 
			
		||||
@ -39,7 +39,7 @@ service_file_present?: true)
 | 
			
		||||
      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
 | 
			
		||||
      expect { described_class.run([service], verbose: false) }.not_to raise_error
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -201,12 +201,12 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
 | 
			
		||||
    it "macOS - outputs if the service file is present for root" do
 | 
			
		||||
      allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false)
 | 
			
		||||
      expect(service.service_file_present?(for: :root)).to be(false)
 | 
			
		||||
      expect(service.service_file_present?(type: :root)).to be(false)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "macOS - outputs if the service file is present for user" do
 | 
			
		||||
      allow(Homebrew::Services::System).to receive_messages(launchctl?: true, systemctl?: false)
 | 
			
		||||
      expect(service.service_file_present?(for: :user)).to be(false)
 | 
			
		||||
      expect(service.service_file_present?(type: :user)).to be(false)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -224,13 +224,13 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#pid" do
 | 
			
		||||
  describe "#pid", :needs_systemd do
 | 
			
		||||
    it "outputs nil because there is not pid" do
 | 
			
		||||
      expect(service.pid).to be_nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#error?" do
 | 
			
		||||
  describe "#error?", :needs_systemd do
 | 
			
		||||
    it "outputs false because there a no PID" do
 | 
			
		||||
      allow(service).to receive(:pid).and_return(nil)
 | 
			
		||||
      expect(service.error?).to be(false)
 | 
			
		||||
@ -242,13 +242,13 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#exit_code" do
 | 
			
		||||
  describe "#exit_code", :needs_systemd do
 | 
			
		||||
    it "outputs nil because there is no exit code" do
 | 
			
		||||
      expect(service.exit_code).to be_nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#unknown_status?" do
 | 
			
		||||
  describe "#unknown_status?", :needs_systemd do
 | 
			
		||||
    it "outputs true because there is no PID" do
 | 
			
		||||
      expect(service.unknown_status?).to be(true)
 | 
			
		||||
    end
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user