diff --git a/CHANGELOG.md b/CHANGELOG.md index 1418427a..e56ceacd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ -## 1.6.5 +## Next release * Auto-restart server when server and client versions do not match +* Add `spring server` command to explicitly start a Spring server + process in the foreground, which logging to stdout. This will be + useful to those who want to run spring more explicitly, but the real + impetus was to enable running a spring server inside a Docker + container. ## 1.6.4 diff --git a/lib/spring/application.rb b/lib/spring/application.rb index ddcc2d4e..b461d831 100644 --- a/lib/spring/application.rb +++ b/lib/spring/application.rb @@ -6,10 +6,10 @@ module Spring class Application attr_reader :manager, :watcher, :spring_env, :original_env - def initialize(manager, original_env) + def initialize(manager, original_env, spring_env = Env.new) @manager = manager @original_env = original_env - @spring_env = Env.new + @spring_env = spring_env @mutex = Mutex.new @waiting = Set.new @preloaded = false diff --git a/lib/spring/application/boot.rb b/lib/spring/application/boot.rb index 6804b646..8510b886 100644 --- a/lib/spring/application/boot.rb +++ b/lib/spring/application/boot.rb @@ -5,7 +5,8 @@ app = Spring::Application.new( UNIXSocket.for_fd(3), - Spring::JSON.load(ENV.delete("SPRING_ORIGINAL_ENV").dup) + Spring::JSON.load(ENV.delete("SPRING_ORIGINAL_ENV").dup), + Spring::Env.new(log_file: IO.for_fd(4)) ) Signal.trap("TERM") { app.terminate } diff --git a/lib/spring/application_manager.rb b/lib/spring/application_manager.rb index a05aef4a..bb5bdfad 100644 --- a/lib/spring/application_manager.rb +++ b/lib/spring/application_manager.rb @@ -2,9 +2,9 @@ module Spring class ApplicationManager attr_reader :pid, :child, :app_env, :spring_env, :status - def initialize(app_env) + def initialize(app_env, spring_env) @app_env = app_env - @spring_env = Env.new + @spring_env = spring_env @mutex = Mutex.new @state = :running end @@ -104,7 +104,8 @@ def start_child(preload = false) "-I", File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first), "-I", File.expand_path("../..", __FILE__), "-e", "require 'spring/application/boot'", - 3 => child_socket + 3 => child_socket, + 4 => spring_env.log_file, ) end diff --git a/lib/spring/client.rb b/lib/spring/client.rb index 69bb8f5b..065a1aa9 100644 --- a/lib/spring/client.rb +++ b/lib/spring/client.rb @@ -9,6 +9,7 @@ require "spring/client/status" require "spring/client/rails" require "spring/client/version" +require "spring/client/server" module Spring module Client @@ -22,6 +23,7 @@ module Client "rails" => Client::Rails, "-v" => Client::Version, "--version" => Client::Version, + "server" => Client::Server, } def self.run(args) diff --git a/lib/spring/client/server.rb b/lib/spring/client/server.rb new file mode 100644 index 00000000..e096d005 --- /dev/null +++ b/lib/spring/client/server.rb @@ -0,0 +1,14 @@ +module Spring + module Client + class Server < Command + def call + require "spring/server" + Spring::Server.boot(foreground: true) + end + + def self.description + "Explicitly start a Spring server in the foreground" + end + end + end +end diff --git a/lib/spring/env.rb b/lib/spring/env.rb index 38ae65b3..7f9136f0 100644 --- a/lib/spring/env.rb +++ b/lib/spring/env.rb @@ -14,10 +14,10 @@ module Spring class Env attr_reader :log_file - def initialize(root = nil) - @root = root - @project_root = root - @log_file = File.open(ENV["SPRING_LOG"] || File::NULL, "a") + def initialize(options = {}) + @root = options[:root] + @project_root = options[:root] + @log_file = options[:log_file] || File.open(ENV["SPRING_LOG"] || File::NULL, "a") end def root diff --git a/lib/spring/server.rb b/lib/spring/server.rb index b922199f..be908532 100644 --- a/lib/spring/server.rb +++ b/lib/spring/server.rb @@ -10,19 +10,24 @@ module Spring module Spring class Server - def self.boot - new.boot + def self.boot(options = {}) + new(options).boot end attr_reader :env - def initialize(env = Env.new) - @env = env - @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k) } + def initialize(options = {}) + @foreground = options.fetch(:foreground, false) + @env = options[:env] || default_env + @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k, env) } @pidfile = env.pidfile_path.open('a') @mutex = Mutex.new end + def foreground? + @foreground + end + def log(message) env.log "[server] #{message}" end @@ -31,8 +36,8 @@ def boot Spring.verify_environment write_pidfile - set_pgid - ignore_signals + set_pgid unless foreground? + ignore_signals unless foreground? set_exit_hook set_process_title start_server @@ -42,6 +47,7 @@ def start_server server = UNIXServer.open(env.socket_name) log "started on #{env.socket_name}" loop { serve server.accept } + rescue Interrupt end def serve(client) @@ -126,5 +132,19 @@ def set_process_title "spring server | #{env.app_name} | started #{distance} ago" } end + + private + + def default_env + Env.new(log_file: default_log_file) + end + + def default_log_file + if foreground? && !ENV["SPRING_LOG"] + $stdout + else + nil + end + end end end diff --git a/lib/spring/test/acceptance_test.rb b/lib/spring/test/acceptance_test.rb index fe10435a..1672a05c 100644 --- a/lib/spring/test/acceptance_test.rb +++ b/lib/spring/test/acceptance_test.rb @@ -28,6 +28,10 @@ def app @app ||= Spring::Test::Application.new("#{Spring::Test.root}/apps/tmp") end + def spring_env + app.spring_env + end + def assert_output(artifacts, expected) expected.each do |stream, output| assert artifacts[stream].include?(output), @@ -92,14 +96,14 @@ def without_gem(name) test "help message when called without arguments" do assert_success "bin/spring", stdout: 'Usage: spring COMMAND [ARGS]' - assert app.spring_env.server_running? + assert spring_env.server_running? end test "shows help" do assert_success "bin/spring help", stdout: 'Usage: spring COMMAND [ARGS]' assert_success "bin/spring -h", stdout: 'Usage: spring COMMAND [ARGS]' assert_success "bin/spring --help", stdout: 'Usage: spring COMMAND [ARGS]' - refute app.spring_env.server_running? + refute spring_env.server_running? end test "tells the user that spring is being used when used automatically via binstubs" do @@ -184,10 +188,10 @@ def self.omg test "stop command kills server" do app.run app.spring_test_command - assert app.spring_env.server_running?, "The server should be running but it isn't" + assert spring_env.server_running?, "The server should be running but it isn't" assert_success "bin/spring stop" - assert !app.spring_env.server_running?, "The server should not be running but it is" + assert !spring_env.server_running?, "The server should not be running but it is" end test "custom commands" do @@ -508,6 +512,19 @@ def exec_name 2.times { assert_success "bundle exec rails runner ''" } end end + + test "booting a foreground server" do + FileUtils.cd(app.root) do + assert !spring_env.server_running? + app.run "spring server &" + + Timeout.timeout(1) do + sleep 0.1 until spring_env.server_running? + end + + assert_success app.spring_test_command + end + end end end end diff --git a/lib/spring/test/application.rb b/lib/spring/test/application.rb index f6fe74b0..2da25e1e 100644 --- a/lib/spring/test/application.rb +++ b/lib/spring/test/application.rb @@ -9,7 +9,7 @@ class Application def initialize(root) @root = Pathname.new(root) - @spring_env = Spring::Env.new(root) + @spring_env = Spring::Env.new(root: root) end def exists?