puma 2.2.0 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3334 -0
  3. data/LICENSE +23 -20
  4. data/README.md +387 -100
  5. data/bin/puma-wild +25 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/5.0-Upgrade.md +98 -0
  8. data/docs/6.0-Upgrade.md +56 -0
  9. data/docs/7.0-Upgrade.md +52 -0
  10. data/docs/8.0-Upgrade.md +100 -0
  11. data/docs/architecture.md +74 -0
  12. data/docs/compile_options.md +55 -0
  13. data/docs/deployment.md +137 -0
  14. data/docs/fork_worker.md +41 -0
  15. data/docs/grpc.md +62 -0
  16. data/docs/images/favicon.svg +1 -0
  17. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  18. data/docs/images/puma-connection-flow.png +0 -0
  19. data/docs/images/puma-general-arch.png +0 -0
  20. data/docs/images/running-puma.svg +1 -0
  21. data/docs/images/standard-logo.svg +1 -0
  22. data/docs/java_options.md +54 -0
  23. data/docs/jungle/README.md +9 -0
  24. data/docs/jungle/rc.d/README.md +74 -0
  25. data/docs/jungle/rc.d/puma +61 -0
  26. data/docs/jungle/rc.d/puma.conf +10 -0
  27. data/docs/kubernetes.md +73 -0
  28. data/docs/nginx.md +5 -5
  29. data/docs/plugins.md +42 -0
  30. data/docs/rails_dev_mode.md +28 -0
  31. data/docs/restart.md +65 -0
  32. data/docs/signals.md +98 -0
  33. data/docs/stats.md +148 -0
  34. data/docs/systemd.md +253 -0
  35. data/docs/testing_benchmarks_local_files.md +150 -0
  36. data/docs/testing_test_rackup_ci_files.md +36 -0
  37. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  38. data/ext/puma_http11/extconf.rb +59 -2
  39. data/ext/puma_http11/http11_parser.c +319 -487
  40. data/ext/puma_http11/http11_parser.h +15 -13
  41. data/ext/puma_http11/http11_parser.java.rl +64 -94
  42. data/ext/puma_http11/http11_parser.rl +27 -24
  43. data/ext/puma_http11/http11_parser_common.rl +8 -8
  44. data/ext/puma_http11/mini_ssl.c +696 -38
  45. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  46. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  47. data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
  48. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
  49. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
  50. data/ext/puma_http11/puma_http11.c +183 -175
  51. data/lib/puma/app/status.rb +77 -29
  52. data/lib/puma/binder.rb +349 -119
  53. data/lib/puma/cli.rb +163 -801
  54. data/lib/puma/client.rb +627 -144
  55. data/lib/puma/client_env.rb +171 -0
  56. data/lib/puma/cluster/worker.rb +183 -0
  57. data/lib/puma/cluster/worker_handle.rb +127 -0
  58. data/lib/puma/cluster.rb +634 -0
  59. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  60. data/lib/puma/commonlogger.rb +115 -0
  61. data/lib/puma/configuration.rb +420 -198
  62. data/lib/puma/const.rb +266 -115
  63. data/lib/puma/control_cli.rb +219 -120
  64. data/lib/puma/detect.rb +56 -2
  65. data/lib/puma/dsl.rb +1562 -0
  66. data/lib/puma/error_logger.rb +115 -0
  67. data/lib/puma/events.rb +44 -60
  68. data/lib/puma/io_buffer.rb +48 -5
  69. data/lib/puma/jruby_restart.rb +2 -51
  70. data/lib/puma/json_serialization.rb +96 -0
  71. data/lib/puma/launcher/bundle_pruner.rb +102 -0
  72. data/lib/puma/launcher.rb +501 -0
  73. data/lib/puma/log_writer.rb +153 -0
  74. data/lib/puma/minissl/context_builder.rb +96 -0
  75. data/lib/puma/minissl.rb +355 -37
  76. data/lib/puma/null_io.rb +79 -12
  77. data/lib/puma/plugin/systemd.rb +90 -0
  78. data/lib/puma/plugin/tmp_restart.rb +36 -0
  79. data/lib/puma/plugin.rb +111 -0
  80. data/lib/puma/rack/builder.rb +297 -0
  81. data/lib/puma/rack/urlmap.rb +93 -0
  82. data/lib/puma/rack_default.rb +21 -4
  83. data/lib/puma/reactor.rb +102 -133
  84. data/lib/puma/response.rb +532 -0
  85. data/lib/puma/runner.rb +211 -0
  86. data/lib/puma/sd_notify.rb +146 -0
  87. data/lib/puma/server.rb +582 -445
  88. data/lib/puma/server_plugin_control.rb +32 -0
  89. data/lib/puma/single.rb +72 -0
  90. data/lib/puma/state_file.rb +69 -0
  91. data/lib/puma/thread_pool.rb +389 -57
  92. data/lib/puma/util.rb +125 -0
  93. data/lib/puma.rb +80 -6
  94. data/lib/rack/handler/puma.rb +125 -47
  95. data/tools/Dockerfile +26 -0
  96. data/tools/trickletest.rb +44 -0
  97. metadata +88 -148
  98. data/COPYING +0 -55
  99. data/Gemfile +0 -10
  100. data/History.txt +0 -302
  101. data/Manifest.txt +0 -60
  102. data/Rakefile +0 -165
  103. data/TODO +0 -5
  104. data/docs/config.md +0 -0
  105. data/ext/puma_http11/ext_help.h +0 -15
  106. data/ext/puma_http11/io_buffer.c +0 -154
  107. data/lib/puma/accept_nonblock.rb +0 -23
  108. data/lib/puma/capistrano.rb +0 -34
  109. data/lib/puma/compat.rb +0 -11
  110. data/lib/puma/daemon_ext.rb +0 -20
  111. data/lib/puma/delegation.rb +0 -11
  112. data/lib/puma/java_io_buffer.rb +0 -45
  113. data/lib/puma/rack_patch.rb +0 -25
  114. data/puma.gemspec +0 -45
  115. data/test/test_app_status.rb +0 -88
  116. data/test/test_cli.rb +0 -171
  117. data/test/test_config.rb +0 -16
  118. data/test/test_http10.rb +0 -27
  119. data/test/test_http11.rb +0 -126
  120. data/test/test_integration.rb +0 -154
  121. data/test/test_iobuffer.rb +0 -38
  122. data/test/test_minissl.rb +0 -25
  123. data/test/test_null_io.rb +0 -31
  124. data/test/test_persistent.rb +0 -238
  125. data/test/test_puma_server.rb +0 -224
  126. data/test/test_rack_handler.rb +0 -10
  127. data/test/test_rack_server.rb +0 -141
  128. data/test/test_thread_pool.rb +0 -146
  129. data/test/test_unix_socket.rb +0 -39
  130. data/test/test_ws.rb +0 -89
  131. data/tools/jungle/init.d/README.md +0 -54
  132. data/tools/jungle/init.d/puma +0 -332
  133. data/tools/jungle/init.d/run-puma +0 -3
  134. data/tools/jungle/upstart/README.md +0 -61
  135. data/tools/jungle/upstart/puma-manager.conf +0 -31
  136. data/tools/jungle/upstart/puma.conf +0 -52
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ # @version 5.0.0
8
+ #
9
+ class ErrorLogger
10
+ include Const
11
+
12
+ attr_reader :ioerr
13
+
14
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
15
+
16
+ LOG_QUEUE = Queue.new
17
+
18
+ def initialize(ioerr, env: ENV)
19
+ @ioerr = ioerr
20
+
21
+ @debug = env.key?('PUMA_DEBUG')
22
+ end
23
+
24
+ def self.stdio(env: ENV)
25
+ new($stderr, env: env)
26
+ end
27
+
28
+ # Print occurred error details.
29
+ # +options+ hash with additional options:
30
+ # - +error+ is an exception object
31
+ # - +req+ the http request
32
+ # - +text+ (default nil) custom string to print in title
33
+ # and before all remaining info.
34
+ #
35
+ def info(options={})
36
+ internal_write title(options)
37
+ end
38
+
39
+ # Print occurred error details only if
40
+ # environment variable PUMA_DEBUG is defined.
41
+ # +options+ hash with additional options:
42
+ # - +error+ is an exception object
43
+ # - +req+ the http request
44
+ # - +text+ (default nil) custom string to print in title
45
+ # and before all remaining info.
46
+ #
47
+ def debug(options={})
48
+ return unless @debug
49
+
50
+ error = options[:error]
51
+ req = options[:req]
52
+
53
+ string_block = []
54
+ string_block << title(options)
55
+ string_block << request_dump(req) if request_parsed?(req)
56
+ string_block << error.backtrace if error
57
+
58
+ internal_write string_block.join("\n")
59
+ end
60
+
61
+ def title(options={})
62
+ text = options[:text]
63
+ req = options[:req]
64
+ error = options[:error]
65
+
66
+ string_block = ["#{Time.now}"]
67
+ string_block << " #{text}" if text
68
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
69
+ string_block << ": #{error.inspect}" if error
70
+ string_block.join('')
71
+ end
72
+
73
+ def request_dump(req)
74
+ "Headers: #{request_headers(req)}\n" \
75
+ "Body: #{req.body}"
76
+ end
77
+
78
+ def request_title(req)
79
+ env = req.env
80
+
81
+ query_string = env[QUERY_STRING]
82
+
83
+ REQUEST_FORMAT % [
84
+ env[REQUEST_METHOD],
85
+ env[REQUEST_PATH] || env[PATH_INFO],
86
+ query_string.nil? || query_string.empty? ? "" : "?#{query_string}",
87
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
88
+ ]
89
+ end
90
+
91
+ def request_headers(req)
92
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
93
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
94
+ end
95
+
96
+ def request_parsed?(req)
97
+ req && req.env[REQUEST_METHOD]
98
+ end
99
+
100
+ def internal_write(str)
101
+ LOG_QUEUE << str
102
+ while (w_str = LOG_QUEUE.pop(true)) do
103
+ begin
104
+ @ioerr.is_a?(IO) and @ioerr.wait_writable(1)
105
+ @ioerr.write "#{w_str}\n"
106
+ @ioerr.flush unless @ioerr.sync
107
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
108
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
109
+ end
110
+ end
111
+ rescue ThreadError
112
+ end
113
+ private :internal_write
114
+ end
115
+ end
data/lib/puma/events.rb CHANGED
@@ -1,88 +1,72 @@
1
- require 'puma/const'
2
- require 'stringio'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Puma
5
- # The default implement of an event sink object used by Server
6
- # for when certain kinds of events occur in the life of the server.
7
- #
8
- # The methods available are the events that the Server fires.
9
- #
10
- class Events
11
4
 
12
- include Const
5
+ # This is an event sink used by `Puma::Server` to handle
6
+ # lifecycle events such as :after_booted, :before_restart, and :after_stopped.
7
+ # Using `Puma::DSL` it is possible to register callback hooks
8
+ # for each event type.
9
+ class Events
13
10
 
14
- # Create an Events object that prints to +stdout+ and +stderr+.
15
- #
16
- def initialize(stdout, stderr)
17
- @stdout = stdout
18
- @stderr = stderr
11
+ def initialize
12
+ @hooks = Hash.new { |h,k| h[k] = [] }
13
+ end
19
14
 
20
- @stdout.sync = true
21
- @stderr.sync = true
15
+ # Fire callbacks for the named hook
16
+ def fire(hook, *args)
17
+ @hooks[hook].each { |t| t.call(*args) }
22
18
  end
23
19
 
24
- attr_reader :stdout, :stderr
20
+ # Register a callback for a given hook
21
+ def register(hook, obj=nil, &blk)
22
+ if obj and blk
23
+ raise "Specify either an object or a block, not both"
24
+ end
25
+
26
+ h = obj || blk
25
27
 
26
- # Write +str+ to +@stdout+
27
- #
28
- def log(str)
29
- @stdout.puts str
28
+ @hooks[hook] << h
29
+
30
+ h
30
31
  end
31
32
 
32
- def write(str)
33
- @stdout.write str
33
+ def after_booted(&block)
34
+ register(:after_booted, &block)
34
35
  end
35
36
 
36
- # Write +str+ to +@stderr+
37
- #
38
- def error(str)
39
- @stderr.puts "ERROR: #{str}"
40
- exit 1
37
+ def before_restart(&block)
38
+ register(:before_restart, &block)
41
39
  end
42
40
 
43
- # An HTTP parse error has occured.
44
- # +server+ is the Server object, +env+ the request, and +error+ a
45
- # parsing exception.
46
- #
47
- def parse_error(server, env, error)
48
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}"
49
- @stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
41
+ def after_stopped(&block)
42
+ register(:after_stopped, &block)
50
43
  end
51
44
 
52
- # An unknown error has occured.
53
- # +server+ is the Server object, +env+ the request, +error+ an exception
54
- # object, and +kind+ some additional info.
55
- #
56
- def unknown_error(server, error, kind="Unknown")
57
- if error.respond_to? :render
58
- error.render "#{Time.now}: #{kind} error", @stderr
59
- else
60
- @stderr.puts "#{Time.now}: #{kind} error: #{error.inspect}"
61
- @stderr.puts error.backtrace.join("\n")
62
- end
45
+ def on_booted(&block)
46
+ Puma.deprecate_method_change :on_booted, __callee__, :after_booted
47
+ after_booted(&block)
63
48
  end
64
49
 
65
- DEFAULT = new(STDOUT, STDERR)
50
+ def on_restart(&block)
51
+ Puma.deprecate_method_change :on_restart, __callee__, :before_restart
52
+ before_restart(&block)
53
+ end
66
54
 
67
- # Returns an Events object which writes it's status to 2 StringIO
68
- # objects.
69
- #
70
- def self.strings
71
- Events.new StringIO.new, StringIO.new
55
+ def on_stopped(&block)
56
+ Puma.deprecate_method_change :on_stopped, __callee__, :after_stopped
57
+ after_stopped(&block)
72
58
  end
73
- end
74
59
 
75
- class PidEvents < Events
76
- def log(str)
77
- super "[#{$$}] #{str}"
60
+ def fire_after_booted!
61
+ fire(:after_booted)
78
62
  end
79
63
 
80
- def write(str)
81
- super "[#{$$}] #{str}"
64
+ def fire_before_restart!
65
+ fire(:before_restart)
82
66
  end
83
67
 
84
- def error(str)
85
- super "[#{$$}] #{str}"
68
+ def fire_after_stopped!
69
+ fire(:after_stopped)
86
70
  end
87
71
  end
88
72
  end
@@ -1,7 +1,50 @@
1
- require 'puma/detect'
1
+ # frozen_string_literal: true
2
2
 
3
- if Puma::IS_JRUBY
4
- require 'puma/java_io_buffer'
5
- else
6
- require 'puma/puma_http11'
3
+ require 'stringio'
4
+
5
+ module Puma
6
+ class IOBuffer < StringIO
7
+ def initialize
8
+ super.binmode
9
+ end
10
+
11
+ def empty?
12
+ length.zero?
13
+ end
14
+
15
+ def reset
16
+ truncate 0
17
+ rewind
18
+ end
19
+
20
+ def to_s
21
+ rewind
22
+ read
23
+ end
24
+
25
+ # Read & Reset - returns contents and resets
26
+ # @return [String] StringIO contents
27
+ def read_and_reset
28
+ rewind
29
+ str = read
30
+ truncate 0
31
+ rewind
32
+ str
33
+ end
34
+
35
+ alias_method :clear, :reset
36
+
37
+ # Create an `IoBuffer#append` method that accepts multiple strings and writes them
38
+ if RUBY_ENGINE == 'truffleruby'
39
+ # truffleruby (24.2.1, like ruby 3.3.7)
40
+ # StringIO.new.write("a", "b") # => `write': wrong number of arguments (given 2, expected 1) (ArgumentError)
41
+ def append(*strs)
42
+ strs.each { |str| write str }
43
+ end
44
+ else
45
+ # Ruby 3+
46
+ # StringIO.new.write("a", "b") # => 2
47
+ alias_method :append, :write
48
+ end
49
+ end
7
50
  end
@@ -1,60 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ffi'
2
4
 
3
5
  module Puma
4
6
  module JRubyRestart
5
7
  extend FFI::Library
6
8
  ffi_lib 'c'
7
-
8
- attach_function :execlp, [:string, :varargs], :int
9
9
  attach_function :chdir, [:string], :int
10
- attach_function :fork, [], :int
11
- attach_function :exit, [:int], :void
12
- attach_function :setsid, [], :int
13
-
14
- def self.chdir_exec(dir, argv)
15
- chdir(dir)
16
- cmd = argv.first
17
- argv = ([:string] * argv.size).zip(argv).flatten
18
- argv << :string
19
- argv << nil
20
- execlp(cmd, *argv)
21
- raise SystemCallError.new(FFI.errno)
22
- end
23
-
24
- def self.daemon?
25
- ENV.key? 'PUMA_DAEMON_RESTART'
26
- end
27
-
28
- def self.daemon_init
29
- return false unless ENV.key? 'PUMA_DAEMON_RESTART'
30
-
31
- master = ENV['PUMA_DAEMON_RESTART']
32
- Process.kill "SIGUSR2", master.to_i
33
-
34
- setsid
35
-
36
- null = File.open "/dev/null", "w+"
37
- STDIN.reopen null
38
- STDOUT.reopen null
39
- STDERR.reopen null
40
-
41
- true
42
- end
43
-
44
- def self.daemon_start(dir, argv)
45
- ENV['PUMA_DAEMON_RESTART'] = Process.pid.to_s
46
-
47
- cmd = argv.first
48
- argv = ([:string] * argv.size).zip(argv).flatten
49
- argv << :string
50
- argv << nil
51
-
52
- chdir(dir)
53
- ret = fork
54
- return ret if ret != 0
55
- execlp(cmd, *argv)
56
- raise SystemCallError.new(FFI.errno)
57
- end
58
10
  end
59
11
  end
60
-
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ require 'stringio'
3
+
4
+ module Puma
5
+
6
+ # Puma deliberately avoids the use of the json gem and instead performs JSON
7
+ # serialization without any external dependencies. In a puma cluster, loading
8
+ # any gem into the puma master process means that operators cannot use a
9
+ # phased restart to upgrade their application if the new version of that
10
+ # application uses a different version of that gem. The json gem in
11
+ # particular is additionally problematic because it leverages native
12
+ # extensions. If the puma master process relies on a gem with native
13
+ # extensions and operators remove gems from disk related to old releases,
14
+ # subsequent phased restarts can fail.
15
+ #
16
+ # The implementation of JSON serialization in this module is not designed to
17
+ # be particularly full-featured or fast. It just has to handle the few places
18
+ # where Puma relies on JSON serialization internally.
19
+
20
+ module JSONSerialization
21
+ QUOTE = /"/
22
+ BACKSLASH = /\\/
23
+ CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
24
+ CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
25
+
26
+ class SerializationError < StandardError; end
27
+
28
+ class << self
29
+ def generate(value)
30
+ StringIO.open do |io|
31
+ serialize_value io, value
32
+ io.string
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_value(output, value)
39
+ case value
40
+ when Hash
41
+ output << '{'
42
+ value.each_with_index do |(k, v), index|
43
+ output << ',' if index != 0
44
+ serialize_object_key output, k
45
+ output << ':'
46
+ serialize_value output, v
47
+ end
48
+ output << '}'
49
+ when Array
50
+ output << '['
51
+ value.each_with_index do |member, index|
52
+ output << ',' if index != 0
53
+ serialize_value output, member
54
+ end
55
+ output << ']'
56
+ when Integer, Float
57
+ output << value.to_s
58
+ when String
59
+ serialize_string output, value
60
+ when true
61
+ output << 'true'
62
+ when false
63
+ output << 'false'
64
+ when nil
65
+ output << 'null'
66
+ else
67
+ raise SerializationError, "Unexpected value of type #{value.class}"
68
+ end
69
+ end
70
+
71
+ def serialize_string(output, value)
72
+ output << '"'
73
+ output << value.gsub(CHAR_TO_ESCAPE) do |character|
74
+ case character
75
+ when BACKSLASH
76
+ '\\\\'
77
+ when QUOTE
78
+ '\\"'
79
+ when CONTROL_CHAR_TO_ESCAPE
80
+ '\u%.4X' % character.ord
81
+ end
82
+ end
83
+ output << '"'
84
+ end
85
+
86
+ def serialize_object_key(output, value)
87
+ case value
88
+ when Symbol, String
89
+ serialize_string output, value.to_s
90
+ else
91
+ raise SerializationError, "Could not serialize object of type #{value.class} as object key"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class Launcher
5
+
6
+ # This class is used to pickup Gemfile changes during
7
+ # application restarts.
8
+ class BundlePruner
9
+
10
+ def initialize(original_argv, extra_runtime_dependencies, log_writer)
11
+ @original_argv = Array(original_argv)
12
+ @extra_runtime_dependencies = Array(extra_runtime_dependencies)
13
+ @log_writer = log_writer
14
+ end
15
+
16
+ def prune
17
+ return if ENV['PUMA_BUNDLER_PRUNED']
18
+ return unless defined?(Bundler)
19
+
20
+ require_rubygems_min_version!
21
+
22
+ unless puma_wild_path
23
+ log "! Unable to prune Bundler environment, continuing"
24
+ return
25
+ end
26
+
27
+ dirs = paths_to_require_after_prune
28
+
29
+ log '* Pruning Bundler environment'
30
+ home = ENV['GEM_HOME']
31
+ original_bundle_env = Bundler.original_env.select { |k, v| k.start_with?('BUNDLE_') && v }
32
+
33
+ with_unbundled_env do
34
+ ENV['GEM_HOME'] = home
35
+ ENV['PUMA_BUNDLER_PRUNED'] = '1'
36
+ original_bundle_env.each { |k, v| ENV[k] = v }
37
+ args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
38
+ # Defaults to true which breaks socket activation
39
+ args += [{:close_others => false}]
40
+ Kernel.exec(*args)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def require_rubygems_min_version!
47
+ min_version = Gem::Version.new('2.2')
48
+
49
+ return if min_version <= Gem::Version.new(Gem::VERSION)
50
+
51
+ raise "prune_bundler is not supported on your version of RubyGems. " \
52
+ "You must have RubyGems #{min_version}+ to use this feature."
53
+ end
54
+
55
+ def puma_wild_path
56
+ puma_lib_dir = puma_require_paths.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
57
+ File.expand_path(File.join(puma_lib_dir, '../bin/puma-wild'))
58
+ end
59
+
60
+ def with_unbundled_env
61
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
62
+ if bundler_ver < Gem::Version.new('2.1.0')
63
+ Bundler.with_clean_env { yield }
64
+ else
65
+ Bundler.with_unbundled_env { yield }
66
+ end
67
+ end
68
+
69
+ def paths_to_require_after_prune
70
+ puma_require_paths + extra_runtime_deps_paths
71
+ end
72
+
73
+ def extra_runtime_deps_paths
74
+ t = @extra_runtime_dependencies.map do |dep_name|
75
+ if (spec = spec_for_gem(dep_name))
76
+ require_paths_for_gem(spec)
77
+ else
78
+ log "* Could not load extra dependency: #{dep_name}"
79
+ nil
80
+ end
81
+ end
82
+ t.flatten!; t.compact!; t
83
+ end
84
+
85
+ def puma_require_paths
86
+ require_paths_for_gem(spec_for_gem('puma'))
87
+ end
88
+
89
+ def spec_for_gem(gem_name)
90
+ Bundler.rubygems.loaded_specs(gem_name)
91
+ end
92
+
93
+ def require_paths_for_gem(gem_spec)
94
+ gem_spec.full_require_paths
95
+ end
96
+
97
+ def log(str)
98
+ @log_writer.log(str)
99
+ end
100
+ end
101
+ end
102
+ end