Skip to content

Modernize spring binstubs and disable it in production #662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 21 additions & 26 deletions lib/spring/client/binstub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,36 @@ class Binstub < Command
# client is not invoked for whatever reason, then the Kernel.exit won't
# happen, and so we'll fall back to the lines after this block, which
# should cause the "unsprung" version of the command to run.
LOADER = <<CODE
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
CODE
LOADER = <<~CODE
load File.expand_path("spring", __dir__)
CODE

# The defined? check ensures these lines don't execute when we load the
# binstub from the application process. Which means that in the application
# process we'll execute the lines which come after the LOADER block, which
# is what we want.
SPRING = <<'CODE'
#!/usr/bin/env ruby

# This file loads Spring without using Bundler, in order to be fast.
# It gets overwritten when you run the `spring binstub` command.

unless defined?(Spring)
require 'rubygems'
require 'bundler'

lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
spring = lockfile.specs.detect { |spec| spec.name == 'spring' }
if spring
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem 'spring', spring.version
require 'spring/binstub'
end
end
CODE
SPRING = <<~CODE
#!/usr/bin/env ruby

# This file loads Spring without using loading other gems in the Gemfile, in order to be fast.
# It gets overwritten when you run the `spring binstub` command.

if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"])
require "bundler"

Bundler.locked_gems.specs.find { |spec| spec.name == "spring" }&.tap do |spring|
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem "spring", spring.version
require "spring/binstub"
end
end
CODE

OLD_BINSTUB = %{if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?}

BINSTUB_VARIATIONS = Regexp.union [
%{load File.expand_path("spring", __dir__)\n},
%{begin\n load File.expand_path('../spring', __FILE__)\nrescue LoadError => e\n raise unless e.message.include?('spring')\nend\n},
%{begin\n load File.expand_path('../spring', __FILE__)\nrescue LoadError\nend\n},
%{begin\n spring_bin_path = File.expand_path('../spring', __FILE__)\n load spring_bin_path\nrescue LoadError => e\n raise unless e.message.end_with? spring_bin_path, 'spring/binstub'\nend\n},
LOADER
Expand Down
61 changes: 60 additions & 1 deletion test/support/acceptance_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def exec_name
test "binstub when spring binary is missing" do
begin
File.rename(app.path("bin/spring"), app.path("bin/spring.bak"))
assert_success "bin/rake -T", stdout: "rake db:migrate"
assert_failure "bin/rake -T", stderr: "`load': cannot load such file"
ensure
File.rename(app.path("bin/spring.bak"), app.path("bin/spring"))
end
Expand Down Expand Up @@ -407,6 +407,65 @@ def exec_name

assert_success "bin/spring binstub rake", stdout: "bin/rake: upgraded"
assert_equal expected, app.path("bin/rake").read

# newer variation which checks end of exception message using include
File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc)
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rake', 'rake')
RUBY

assert_success "bin/spring binstub rake", stdout: "bin/rake: upgraded"
assert_equal expected, app.path("bin/rake").read
end

test "binstub remove with new binstub variations which checks end of the exception message using include" do
# newer variation which checks end of exception message using include
File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc)
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rake', 'rake')
RUBY

File.write(app.path("bin/rails"), <<-RUBY.strip_heredoc)
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
RUBY

assert_success "bin/spring binstub --remove rake", stdout: "bin/rake: Spring removed"
assert_success "bin/spring binstub --remove rails", stdout: "bin/rails: Spring removed"

expected = <<-RUBY.strip_heredoc
#!/usr/bin/env ruby
require 'bundler/setup'
load Gem.bin_path('rake', 'rake')
RUBY
assert_equal expected, app.path("bin/rake").read

expected = <<-RUBY.strip_heredoc
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
RUBY
assert_equal expected, app.path("bin/rails").read
end

test "binstub remove with new binstub variations" do
Expand Down