Add brew bundle services helper
				
					
				
			This commit is contained in:
		
							parent
							
								
									0b58e8fd37
								
							
						
					
					
						commit
						615fb764a1
					
				
							
								
								
									
										30
									
								
								Library/Homebrew/bundle/commands/services.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Library/Homebrew/bundle/commands/services.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "bundle/brewfile"
 | 
			
		||||
require "bundle/services"
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
  module Bundle
 | 
			
		||||
    module Commands
 | 
			
		||||
      module Services
 | 
			
		||||
        sig { params(args: String, global: T::Boolean, file: T.nilable(String)).void }
 | 
			
		||||
        def self.run(*args, global:, file:)
 | 
			
		||||
          raise UsageError, "invalid `brew bundle services` arguments" if args.length != 1
 | 
			
		||||
 | 
			
		||||
          parsed_entries = Brewfile.read(global:, file:).entries
 | 
			
		||||
 | 
			
		||||
          subcommand = args.first
 | 
			
		||||
          case subcommand
 | 
			
		||||
          when "run"
 | 
			
		||||
            Homebrew::Bundle::Services.run(parsed_entries)
 | 
			
		||||
          when "stop"
 | 
			
		||||
            Homebrew::Bundle::Services.stop(parsed_entries)
 | 
			
		||||
          else
 | 
			
		||||
            raise UsageError, "unknown bundle services subcommand: #{subcommand}"
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										112
									
								
								Library/Homebrew/bundle/services.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Library/Homebrew/bundle/services.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "bundle/dsl"
 | 
			
		||||
require "formula"
 | 
			
		||||
require "services/system"
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
  module Bundle
 | 
			
		||||
    module Services
 | 
			
		||||
      sig {
 | 
			
		||||
        params(
 | 
			
		||||
          entries: T::Array[Homebrew::Bundle::Dsl::Entry],
 | 
			
		||||
          _block:  T.proc.params(
 | 
			
		||||
            info:                 T::Hash[String, T.anything],
 | 
			
		||||
            service_file:         Pathname,
 | 
			
		||||
            conflicting_services: T::Array[T::Hash[String, T.anything]],
 | 
			
		||||
          ).void,
 | 
			
		||||
        ).void
 | 
			
		||||
      }
 | 
			
		||||
      private_class_method def self.map_entries(entries, &_block)
 | 
			
		||||
        formula_versions = Bundle.formula_versions_from_env
 | 
			
		||||
 | 
			
		||||
        entries_formulae = entries.filter_map do |entry|
 | 
			
		||||
          next if entry.type != :brew
 | 
			
		||||
 | 
			
		||||
          formula = Formula[entry.name]
 | 
			
		||||
          next unless formula.any_version_installed?
 | 
			
		||||
 | 
			
		||||
          [entry, formula]
 | 
			
		||||
        end.to_h
 | 
			
		||||
 | 
			
		||||
        # The formula + everything that could possible conflict with the service
 | 
			
		||||
        names_to_query = entries_formulae.flat_map do |entry, formula|
 | 
			
		||||
          [
 | 
			
		||||
            formula.name,
 | 
			
		||||
            *formula.versioned_formulae_names,
 | 
			
		||||
            *formula.conflicts.map(&:name),
 | 
			
		||||
            *entry.options[:conflicts_with],
 | 
			
		||||
          ]
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # We parse from a command invocation so that brew wrappers can invoke special actions
 | 
			
		||||
        # for the elevated nature of `brew services`
 | 
			
		||||
        services_info = JSON.parse(
 | 
			
		||||
          Utils.safe_popen_read(HOMEBREW_BREW_FILE, "services", "info", "--json", *names_to_query),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        entries_formulae.filter_map do |entry, formula|
 | 
			
		||||
          version = formula_versions[entry.name.downcase]
 | 
			
		||||
          prefix = formula.rack/version if version
 | 
			
		||||
 | 
			
		||||
          service_file = if prefix&.directory?
 | 
			
		||||
            if Homebrew::Services::System.launchctl?
 | 
			
		||||
              prefix/"#{formula.plist_name}.plist"
 | 
			
		||||
            else
 | 
			
		||||
              prefix/"#{formula.service_name}.service"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          unless service_file&.file?
 | 
			
		||||
            prefix = formula.any_installed_prefix
 | 
			
		||||
            next if prefix.nil?
 | 
			
		||||
 | 
			
		||||
            service_file = if Homebrew::Services::System.launchctl?
 | 
			
		||||
              prefix/"#{formula.plist_name}.plist"
 | 
			
		||||
            else
 | 
			
		||||
              prefix/"#{formula.service_name}.service"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          next unless service_file.file?
 | 
			
		||||
 | 
			
		||||
          info = services_info.find { |candidate| candidate["name"] == formula.name }
 | 
			
		||||
          conflicting_services = services_info.select do |candidate|
 | 
			
		||||
            next unless candidate["running"]
 | 
			
		||||
 | 
			
		||||
            formula.versioned_formulae_names.include?(candidate["name"])
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          raise "Failed to get service info for #{entry.name}" if info.nil?
 | 
			
		||||
 | 
			
		||||
          yield info, service_file, conflicting_services
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry]).void }
 | 
			
		||||
      def self.run(entries)
 | 
			
		||||
        map_entries(entries) do |info, service_file, conflicting_services|
 | 
			
		||||
          safe_system HOMEBREW_BREW_FILE, "services", "stop", "--keep", info["name"] if info["running"]
 | 
			
		||||
          conflicting_services.each do |conflicting_service|
 | 
			
		||||
            safe_system HOMEBREW_BREW_FILE, "services", "stop", "--keep", conflicting_service["name"]
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          safe_system HOMEBREW_BREW_FILE, "services", "run", "--file=#{service_file}", info["name"]
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry]).void }
 | 
			
		||||
      def self.stop(entries)
 | 
			
		||||
        map_entries(entries) do |info, _, _|
 | 
			
		||||
          next unless info["loaded"]
 | 
			
		||||
 | 
			
		||||
          # Try avoid services not started by `brew bundle services`
 | 
			
		||||
          next if Homebrew::Services::System.launchctl? && info["registered"]
 | 
			
		||||
 | 
			
		||||
          safe_system HOMEBREW_BREW_FILE, "services", "stop", info["name"]
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -61,6 +61,12 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
          `brew bundle env`:
 | 
			
		||||
          Print the environment variables that would be set in a `brew bundle exec` environment.
 | 
			
		||||
 | 
			
		||||
          `brew bundle services run`:
 | 
			
		||||
          Start services for formulae specified in the `Brewfile`.
 | 
			
		||||
 | 
			
		||||
          `brew bundle services stop`:
 | 
			
		||||
          Stop services for formulae specified in the `Brewfile`.
 | 
			
		||||
        EOS
 | 
			
		||||
        flag "--file=",
 | 
			
		||||
             description: "Read from or write to the `Brewfile` from this location. " \
 | 
			
		||||
@ -133,7 +139,7 @@ module Homebrew
 | 
			
		||||
        require "bundle"
 | 
			
		||||
 | 
			
		||||
        subcommand = args.named.first.presence
 | 
			
		||||
        if ["exec", "add", "remove"].exclude?(subcommand) && args.named.size > 1
 | 
			
		||||
        if %w[exec add remove services].exclude?(subcommand) && args.named.size > 1
 | 
			
		||||
          raise UsageError, "This command does not take more than 1 subcommand argument."
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
@ -273,6 +279,10 @@ module Homebrew
 | 
			
		||||
            require "bundle/commands/remove"
 | 
			
		||||
            Homebrew::Bundle::Commands::Remove.run(*named_args, type: selected_types.first, global:, file:)
 | 
			
		||||
          end
 | 
			
		||||
        when "services"
 | 
			
		||||
          _, *named_args = args.named
 | 
			
		||||
          require "bundle/commands/services"
 | 
			
		||||
          Homebrew::Bundle::Commands::Services.run(*named_args, global:, file:)
 | 
			
		||||
        else
 | 
			
		||||
          raise UsageError, "unknown subcommand: #{subcommand}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,7 @@ module Homebrew
 | 
			
		||||
          return out unless verbose
 | 
			
		||||
 | 
			
		||||
          out += "File: #{hash[:file]} #{pretty_bool(hash[:file].present?)}\n"
 | 
			
		||||
          out += "Registered at login: #{pretty_bool(hash[:registered])}\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?
 | 
			
		||||
 | 
			
		||||
@ -201,6 +201,7 @@ module Homebrew
 | 
			
		||||
          user:         owner,
 | 
			
		||||
          status:       status_symbol,
 | 
			
		||||
          file:         service_file_present? ? dest : service_file,
 | 
			
		||||
          registered:   service_file_present?,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return hash unless service?
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
 | 
			
		||||
    it "returns verbose output" do
 | 
			
		||||
      out = "service ()\nRunning: true\n"
 | 
			
		||||
      out += "Loaded: true\nSchedulable: false\n"
 | 
			
		||||
      out += "User: user\nPID: 42\nFile: /dev/null true\nCommand: /bin/command\n"
 | 
			
		||||
      out += "User: user\nPID: 42\nFile: /dev/null true\nRegistered at login: true\nCommand: /bin/command\n"
 | 
			
		||||
      out += "Working directory: /working/dir\nRoot directory: /root/dir\nLog: /log/dir\nError log: /log/dir/error\n"
 | 
			
		||||
      out += "Interval: 3600s\nCron: 5 * * * *\n"
 | 
			
		||||
      formula = {
 | 
			
		||||
@ -97,6 +97,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
 | 
			
		||||
        user:           "user",
 | 
			
		||||
        status:         :started,
 | 
			
		||||
        file:           "/dev/null",
 | 
			
		||||
        registered:     true,
 | 
			
		||||
        running:        true,
 | 
			
		||||
        loaded:         true,
 | 
			
		||||
        schedulable:    false,
 | 
			
		||||
 | 
			
		||||
@ -371,6 +371,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
        loaded:       false,
 | 
			
		||||
        name:         "mysql",
 | 
			
		||||
        pid:          nil,
 | 
			
		||||
        registered:   false,
 | 
			
		||||
        running:      false,
 | 
			
		||||
        schedulable:  nil,
 | 
			
		||||
        service_name: "plist-mysql-test",
 | 
			
		||||
@ -384,13 +385,14 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
      ENV["HOME"] = "/tmp_home"
 | 
			
		||||
      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)
 | 
			
		||||
      expect(service).to receive(:service_file_present?).twice.and_return(true)
 | 
			
		||||
      expected = {
 | 
			
		||||
        exit_code:    nil,
 | 
			
		||||
        file:         Pathname.new("/tmp_home/Library/LaunchAgents/homebrew.mysql.plist"),
 | 
			
		||||
        loaded:       false,
 | 
			
		||||
        name:         "mysql",
 | 
			
		||||
        pid:          nil,
 | 
			
		||||
        registered:   true,
 | 
			
		||||
        running:      false,
 | 
			
		||||
        schedulable:  nil,
 | 
			
		||||
        service_name: "plist-mysql-test",
 | 
			
		||||
@ -404,7 +406,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
      ENV["HOME"] = "/tmp_home"
 | 
			
		||||
      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(:service_file_present?).twice.and_return(true)
 | 
			
		||||
      expect(service).to receive(:load_service).twice.and_return(service_object)
 | 
			
		||||
      expected = {
 | 
			
		||||
        command:        "/bin/cmd",
 | 
			
		||||
@ -417,6 +419,7 @@ RSpec.describe Homebrew::Services::FormulaWrapper do
 | 
			
		||||
        log_path:       nil,
 | 
			
		||||
        name:           "mysql",
 | 
			
		||||
        pid:            nil,
 | 
			
		||||
        registered:     true,
 | 
			
		||||
        root_dir:       nil,
 | 
			
		||||
        running:        false,
 | 
			
		||||
        schedulable:    false,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user