mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Extract prune_bundler code into it's own class. (#2797)
Co-authored-by: shields <shields@tablecheck.com>
This commit is contained in:
parent
c92b69fc0f
commit
d7cfe19eb3
4 changed files with 167 additions and 132 deletions
|
@ -6,6 +6,7 @@ require 'puma/cluster'
|
|||
require 'puma/single'
|
||||
require 'puma/const'
|
||||
require 'puma/binder'
|
||||
require 'puma/launcher/bundle_pruner'
|
||||
|
||||
module Puma
|
||||
# Puma::Launcher is the single entry point for starting a Puma server based on user
|
||||
|
@ -80,7 +81,7 @@ module Puma
|
|||
|
||||
Dir.chdir(@restart_dir)
|
||||
|
||||
prune_bundler if prune_bundler?
|
||||
prune_bundler!
|
||||
|
||||
@environment = @options[:environment] if @options[:environment]
|
||||
set_rack_environment
|
||||
|
@ -300,67 +301,10 @@ module Puma
|
|||
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
||||
end
|
||||
|
||||
# @!attribute [r] files_to_require_after_prune
|
||||
def files_to_require_after_prune
|
||||
puma = spec_for_gem("puma")
|
||||
|
||||
require_paths_for_gem(puma) + extra_runtime_deps_directories
|
||||
end
|
||||
|
||||
# @!attribute [r] extra_runtime_deps_directories
|
||||
def extra_runtime_deps_directories
|
||||
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
||||
if (spec = spec_for_gem(d_name))
|
||||
require_paths_for_gem(spec)
|
||||
else
|
||||
log "* Could not load extra dependency: #{d_name}"
|
||||
nil
|
||||
end
|
||||
end.flatten.compact
|
||||
end
|
||||
|
||||
# @!attribute [r] puma_wild_location
|
||||
def puma_wild_location
|
||||
puma = spec_for_gem("puma")
|
||||
dirs = require_paths_for_gem(puma)
|
||||
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
||||
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
||||
end
|
||||
|
||||
def prune_bundler
|
||||
return if ENV['PUMA_BUNDLER_PRUNED']
|
||||
return unless defined?(Bundler)
|
||||
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
||||
unless puma_wild_location
|
||||
log "! Unable to prune Bundler environment, continuing"
|
||||
return
|
||||
end
|
||||
|
||||
dirs = files_to_require_after_prune
|
||||
|
||||
log '* Pruning Bundler environment'
|
||||
home = ENV['GEM_HOME']
|
||||
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
||||
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
||||
with_unbundled_env do
|
||||
ENV['GEM_HOME'] = home
|
||||
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
||||
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
||||
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
||||
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
|
||||
# Ruby 2.0+ defaults to true which breaks socket activation
|
||||
args += [{:close_others => false}]
|
||||
Kernel.exec(*args)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Puma's systemd integration allows Puma to inform systemd:
|
||||
# 1. when it has successfully started
|
||||
# 2. when it is starting shutdown
|
||||
# 3. periodically for a liveness check with a watchdog thread
|
||||
#
|
||||
|
||||
def integrate_with_systemd
|
||||
return unless ENV["NOTIFY_SOCKET"]
|
||||
|
||||
|
@ -378,14 +322,6 @@ module Puma
|
|||
systemd.start_watchdog
|
||||
end
|
||||
|
||||
def spec_for_gem(gem_name)
|
||||
Bundler.rubygems.loaded_specs(gem_name)
|
||||
end
|
||||
|
||||
def require_paths_for_gem(gem_spec)
|
||||
gem_spec.full_require_paths
|
||||
end
|
||||
|
||||
def log(str)
|
||||
@events.log str
|
||||
end
|
||||
|
@ -424,6 +360,11 @@ module Puma
|
|||
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
||||
end
|
||||
|
||||
def prune_bundler!
|
||||
return unless prune_bundler?
|
||||
BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @events).prune
|
||||
end
|
||||
|
||||
def generate_restart_data
|
||||
if dir = @options[:directory]
|
||||
@restart_dir = dir
|
||||
|
@ -534,23 +475,6 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
def require_rubygems_min_version!(min_version, feature)
|
||||
return if min_version <= Gem::Version.new(Gem::VERSION)
|
||||
|
||||
raise "#{feature} is not supported on your version of RubyGems. " \
|
||||
"You must have RubyGems #{min_version}+ to use this feature."
|
||||
end
|
||||
|
||||
# @version 5.0.0
|
||||
def with_unbundled_env
|
||||
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
||||
if bundler_ver < Gem::Version.new('2.1.0')
|
||||
Bundler.with_clean_env { yield }
|
||||
else
|
||||
Bundler.with_unbundled_env { yield }
|
||||
end
|
||||
end
|
||||
|
||||
def log_config
|
||||
log "Configuration:"
|
||||
|
||||
|
|
103
lib/puma/launcher/bundle_pruner.rb
Normal file
103
lib/puma/launcher/bundle_pruner.rb
Normal file
|
@ -0,0 +1,103 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Puma
|
||||
class Launcher
|
||||
|
||||
# This class is used to pickup Gemfile changes during
|
||||
# application restarts.
|
||||
class BundlePruner
|
||||
|
||||
def initialize(original_argv, extra_runtime_dependencies, events)
|
||||
@original_argv = Array(original_argv)
|
||||
@extra_runtime_dependencies = Array(extra_runtime_dependencies)
|
||||
@events = events
|
||||
end
|
||||
|
||||
def prune
|
||||
return if ENV['PUMA_BUNDLER_PRUNED']
|
||||
return unless defined?(Bundler)
|
||||
|
||||
require_rubygems_min_version!
|
||||
|
||||
unless puma_wild_path
|
||||
log "! Unable to prune Bundler environment, continuing"
|
||||
return
|
||||
end
|
||||
|
||||
dirs = paths_to_require_after_prune
|
||||
|
||||
log '* Pruning Bundler environment'
|
||||
home = ENV['GEM_HOME']
|
||||
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
||||
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
||||
|
||||
with_unbundled_env do
|
||||
ENV['GEM_HOME'] = home
|
||||
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
||||
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
||||
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
||||
args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
|
||||
# Ruby 2.0+ defaults to true which breaks socket activation
|
||||
args += [{:close_others => false}]
|
||||
Kernel.exec(*args)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_rubygems_min_version!
|
||||
min_version = Gem::Version.new('2.2')
|
||||
|
||||
return if min_version <= Gem::Version.new(Gem::VERSION)
|
||||
|
||||
raise "prune_bundler is not supported on your version of RubyGems. " \
|
||||
"You must have RubyGems #{min_version}+ to use this feature."
|
||||
end
|
||||
|
||||
def puma_wild_path
|
||||
puma_lib_dir = puma_require_paths.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
||||
File.expand_path(File.join(puma_lib_dir, '../bin/puma-wild'))
|
||||
end
|
||||
|
||||
def with_unbundled_env
|
||||
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
||||
if bundler_ver < Gem::Version.new('2.1.0')
|
||||
Bundler.with_clean_env { yield }
|
||||
else
|
||||
Bundler.with_unbundled_env { yield }
|
||||
end
|
||||
end
|
||||
|
||||
def paths_to_require_after_prune
|
||||
puma_require_paths + extra_runtime_deps_paths
|
||||
end
|
||||
|
||||
def extra_runtime_deps_paths
|
||||
@extra_runtime_dependencies.map do |dep_name|
|
||||
if (spec = spec_for_gem(dep_name))
|
||||
require_paths_for_gem(spec)
|
||||
else
|
||||
log "* Could not load extra dependency: #{dep_name}"
|
||||
nil
|
||||
end
|
||||
end.flatten.compact
|
||||
end
|
||||
|
||||
def puma_require_paths
|
||||
require_paths_for_gem(spec_for_gem('puma'))
|
||||
end
|
||||
|
||||
def spec_for_gem(gem_name)
|
||||
Bundler.rubygems.loaded_specs(gem_name)
|
||||
end
|
||||
|
||||
def require_paths_for_gem(gem_spec)
|
||||
gem_spec.full_require_paths
|
||||
end
|
||||
|
||||
def log(str)
|
||||
@events.log(str)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
57
test/test_bundle_pruner.rb
Normal file
57
test/test_bundle_pruner.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
require_relative 'helper'
|
||||
|
||||
require 'puma/events'
|
||||
require 'puma/launcher/bundle_pruner'
|
||||
|
||||
class TestBundlePruner < Minitest::Test
|
||||
|
||||
def test_paths_to_require_after_prune_is_correctly_built_for_no_extra_deps
|
||||
skip_if :no_bundler
|
||||
|
||||
dirs = bundle_pruner.send(:paths_to_require_after_prune)
|
||||
|
||||
assert_equal(2, dirs.length)
|
||||
assert_match(%r{puma/lib$}, dirs[0]) # lib dir
|
||||
assert_match(%r{puma-#{Puma::Const::PUMA_VERSION}$}, dirs[1]) # native extension dir
|
||||
refute_match(%r{gems/rdoc-[\d.]+/lib$}, dirs[2])
|
||||
end
|
||||
|
||||
def test_paths_to_require_after_prune_is_correctly_built_with_extra_deps
|
||||
skip_if :no_bundler
|
||||
|
||||
dirs = bundle_pruner([], ['rdoc']).send(:paths_to_require_after_prune)
|
||||
|
||||
assert_equal(3, dirs.length)
|
||||
assert_match(%r{puma/lib$}, dirs[0]) # lib dir
|
||||
assert_match(%r{puma-#{Puma::Const::PUMA_VERSION}$}, dirs[1]) # native extension dir
|
||||
assert_match(%r{gems/rdoc-[\d.]+/lib$}, dirs[2]) # rdoc dir
|
||||
end
|
||||
|
||||
def test_extra_runtime_deps_paths_is_empty_for_no_config
|
||||
assert_equal([], bundle_pruner.send(:extra_runtime_deps_paths))
|
||||
end
|
||||
|
||||
def test_extra_runtime_deps_paths_is_correctly_built
|
||||
skip_if :no_bundler
|
||||
|
||||
dep_dirs = bundle_pruner([], ['rdoc']).send(:extra_runtime_deps_paths)
|
||||
|
||||
assert_equal(1, dep_dirs.length)
|
||||
assert_match(%r{gems/rdoc-[\d.]+/lib$}, dep_dirs.first)
|
||||
end
|
||||
|
||||
def test_puma_wild_path_is_an_absolute_path
|
||||
skip_if :no_bundler
|
||||
puma_wild_path = bundle_pruner.send(:puma_wild_path)
|
||||
|
||||
assert_match(%r{bin/puma-wild$}, puma_wild_path)
|
||||
# assert no "/../" in path
|
||||
refute_match(%r{/\.\./}, puma_wild_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bundle_pruner(original_argv = nil, extra_runtime_dependencies = nil)
|
||||
@bundle_pruner ||= Puma::Launcher::BundlePruner.new(original_argv, extra_runtime_dependencies, Puma::Events.null)
|
||||
end
|
||||
end
|
|
@ -7,55 +7,6 @@ require 'puma/events'
|
|||
class TestLauncher < Minitest::Test
|
||||
include TmpPath
|
||||
|
||||
def test_files_to_require_after_prune_is_correctly_built_for_no_extra_deps
|
||||
skip_if :no_bundler
|
||||
|
||||
dirs = launcher.send(:files_to_require_after_prune)
|
||||
|
||||
assert_equal(2, dirs.length)
|
||||
assert_match(%r{puma/lib$}, dirs[0]) # lib dir
|
||||
assert_match(%r{puma-#{Puma::Const::PUMA_VERSION}$}, dirs[1]) # native extension dir
|
||||
refute_match(%r{gems/rdoc-[\d.]+/lib$}, dirs[2])
|
||||
end
|
||||
|
||||
def test_files_to_require_after_prune_is_correctly_built_with_extra_deps
|
||||
skip_if :no_bundler
|
||||
conf = Puma::Configuration.new do |c|
|
||||
c.extra_runtime_dependencies ['rdoc']
|
||||
end
|
||||
|
||||
dirs = launcher(conf).send(:files_to_require_after_prune)
|
||||
|
||||
assert_equal(3, dirs.length)
|
||||
assert_match(%r{puma/lib$}, dirs[0]) # lib dir
|
||||
assert_match(%r{puma-#{Puma::Const::PUMA_VERSION}$}, dirs[1]) # native extension dir
|
||||
assert_match(%r{gems/rdoc-[\d.]+/lib$}, dirs[2]) # rdoc dir
|
||||
end
|
||||
|
||||
def test_extra_runtime_deps_directories_is_empty_for_no_config
|
||||
assert_equal([], launcher.send(:extra_runtime_deps_directories))
|
||||
end
|
||||
|
||||
def test_extra_runtime_deps_directories_is_correctly_built
|
||||
skip_if :no_bundler
|
||||
conf = Puma::Configuration.new do |c|
|
||||
c.extra_runtime_dependencies ['rdoc']
|
||||
end
|
||||
dep_dirs = launcher(conf).send(:extra_runtime_deps_directories)
|
||||
|
||||
assert_equal(1, dep_dirs.length)
|
||||
assert_match(%r{gems/rdoc-[\d.]+/lib$}, dep_dirs.first)
|
||||
end
|
||||
|
||||
def test_puma_wild_location_is_an_absolute_path
|
||||
skip_if :no_bundler
|
||||
puma_wild_location = launcher.send(:puma_wild_location)
|
||||
|
||||
assert_match(%r{bin/puma-wild$}, puma_wild_location)
|
||||
# assert no "/../" in path
|
||||
refute_match(%r{/\.\./}, puma_wild_location)
|
||||
end
|
||||
|
||||
def test_prints_thread_traces
|
||||
launcher.thread_status do |name, _backtrace|
|
||||
assert_match "Thread: TID", name
|
||||
|
|
Loading…
Add table
Reference in a new issue