From a0f4b1ae19c3cb23ef11830cefb9ac2681e93340 Mon Sep 17 00:00:00 2001 From: Jarmo Pertman Date: Sun, 9 Sep 2012 01:26:21 +0300 Subject: [PATCH] Add support for Windows. --- guard-rails.gemspec | 2 + lib/guard/rails/runner.rb | 102 ++++++++++++++++++---------- spec/lib/guard/rails/runner_spec.rb | 31 ++++----- 3 files changed, 84 insertions(+), 51 deletions(-) diff --git a/guard-rails.gemspec b/guard-rails.gemspec index 2073910..a5ce2d6 100644 --- a/guard-rails.gemspec +++ b/guard-rails.gemspec @@ -20,6 +20,8 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency 'guard', '>= 0.2.2' + s.add_dependency 'childprocess' + s.add_dependency 'sys-proctable' s.add_development_dependency 'rspec', '~> 2.6.0' s.add_development_dependency 'mocha' diff --git a/lib/guard/rails/runner.rb b/lib/guard/rails/runner.rb index fea5996..6370ffc 100644 --- a/lib/guard/rails/runner.rb +++ b/lib/guard/rails/runner.rb @@ -1,9 +1,10 @@ require 'fileutils' +require 'timeout' +require 'childprocess' +require 'sys/proctable' module Guard class RailsRunner - MAX_WAIT_COUNT = 10 - attr_reader :options def initialize(options) @@ -12,14 +13,19 @@ module Guard def start kill_unmanaged_pid! if options[:force_run] - run_rails_command! - wait_for_pid + process = run_rails_command! + rails_started = wait_for_rails + File.open(pid_file, "w") {|file| file.write process.pid} if rails_started + rails_started end def stop - if File.file?(pid_file) - system %{kill -SIGINT #{File.read(pid_file).strip}} - wait_for_no_pid if $?.exitstatus == 0 + if has_pid? + unless windows? + Process.kill Signal.list["INT"], pid rescue nil + else + kill_all_child_processes + end FileUtils.rm pid_file, :force => true end end @@ -40,7 +46,11 @@ module Guard rails_options << '-u' if options[:debugger] rails_options << options[:server] if options[:server] - %{sh -c 'cd #{Dir.pwd} && RAILS_ENV=#{options[:environment]} rails s #{rails_options.join(' ')} &'} + cmd = [] + if windows? + cmd << 'cmd' << '/C' + end + cmd << "rails server #{rails_options.join(' ')}" end def pid_file @@ -51,56 +61,78 @@ module Guard File.file?(pid_file) ? File.read(pid_file).to_i : nil end - def sleep_time - options[:timeout].to_f / MAX_WAIT_COUNT.to_f - end - private + def run_rails_command! - system build_rails_command + process = ChildProcess.build *build_rails_command + process.environment["RAILS_ENV"] = options[:environment] + process.io.inherit! + process.start + process end def has_pid? File.file?(pid_file) end - def wait_for_pid_action - sleep sleep_time - end - def kill_unmanaged_pid! if pid = unmanaged_pid - system %{kill -KILL #{pid}} - FileUtils.rm pid_file - wait_for_no_pid + Process.kill Signal.list["KILL"], pid + FileUtils.rm pid_file if has_pid? end end def unmanaged_pid - %x{lsof -n -i TCP:#{options[:port]}}.each_line { |line| - if line["*:#{options[:port]} "] - return line.split("\s")[1] - end - } + unless windows? + %x{lsof -n -i TCP:#{options[:port]}}.each_line { |line| + if line["*:#{options[:port]} "] + return line.split("\s")[1] + end + } + else + %x{netstat -ano}.each_line { |line| + protocol, local_address, _, state, pid = line.strip.split(/\s+/) + return pid.to_i if protocol == "TCP" && + state == "LISTENING" && + local_address =~ /:#{options[:port]}$/ + } + end nil end private - def wait_for_pid - wait_for_pid_loop + + def wait_for_rails + Timeout.timeout(options[:timeout]) {sleep 1 until rails_running?} + true + rescue Timeout::Error + false end - def wait_for_no_pid - wait_for_pid_loop(false) + def rails_running? + return false unless has_pid? + TCPSocket.new('127.0.0.1', options[:port]).close + true + rescue Errno::ECONNREFUSED + false end - def wait_for_pid_loop(check_for_existince = true) - count = 0 - while !(check_for_existince ? has_pid? : !has_pid?) && count < MAX_WAIT_COUNT - wait_for_pid_action - count += 1 + def windows? + RUBY_PLATFORM =~ /mswin|msys|mingw/ + end + + def kill_all_child_processes + all_pids_for(pid).each do |pid| + Process.kill Signal.list["KILL"], pid rescue nil end - !(count == MAX_WAIT_COUNT) + end + + def all_pids_for(parent_pid) + pids = [parent_pid] + Sys::ProcTable.ps do |process| + pids += all_pids_for(process.pid) if process.ppid == parent_pid + end + pids end end end diff --git a/spec/lib/guard/rails/runner_spec.rb b/spec/lib/guard/rails/runner_spec.rb index 182705c..2e77931 100644 --- a/spec/lib/guard/rails/runner_spec.rb +++ b/spec/lib/guard/rails/runner_spec.rb @@ -36,7 +36,7 @@ describe Guard::RailsRunner do describe '#build_rails_command' do context 'no daemon' do it "should not have a daemon switch" do - runner.build_rails_command.should_not match(%r{ -d}) + runner.build_rails_command.last.should_not match(%r{ -d}) end end @@ -44,7 +44,7 @@ describe Guard::RailsRunner do let(:options) { default_options.merge(:daemon => true) } it "should have a daemon switch" do - runner.build_rails_command.should match(%r{ -d}) + runner.build_rails_command.last.should match(%r{ -d}) end end @@ -52,7 +52,7 @@ describe Guard::RailsRunner do let(:options) { default_options.merge(:debugger => true) } it "should have a debugger switch" do - runner.build_rails_command.should match(%r{ -u}) + runner.build_rails_command.last.should match(%r{ -u}) end end @@ -60,24 +60,29 @@ describe Guard::RailsRunner do let(:options) { default_options.merge(:server => 'thin') } it "should have the server name" do - runner.build_rails_command.should match(%r{thin}) + runner.build_rails_command.last.should match(%r{thin}) end end end describe '#start' do + include FakeFS::SpecHelpers + let(:kill_expectation) { runner.expects(:kill_unmanaged_pid!) } let(:pid_stub) { runner.stubs(:has_pid?) } before do - runner.expects(:run_rails_command!).once + FileUtils.mkdir_p File.split(runner.pid_file).first + process = mock('process') + process.stubs(:pid).returns(123) + runner.expects(:run_rails_command!).once.returns(process) end context 'do not force run' do before do pid_stub.returns(true) kill_expectation.never - runner.expects(:wait_for_pid_action).never + runner.stubs(:rails_running?).once.returns(true) end it "should act properly" do @@ -91,7 +96,7 @@ describe Guard::RailsRunner do before do pid_stub.returns(true) kill_expectation.once - runner.expects(:wait_for_pid_action).never + runner.stubs(:rails_running?).once.returns(true) end it "should act properly" do @@ -100,10 +105,12 @@ describe Guard::RailsRunner do end context "don't write the pid" do + let(:options) { default_options.merge(:timeout => 0.1) } + before do pid_stub.returns(false) kill_expectation.never - runner.expects(:wait_for_pid_action).times(Guard::RailsRunner::MAX_WAIT_COUNT) + runner.stubs(:rails_running?).once.returns(false) end it "should act properly" do @@ -112,12 +119,4 @@ describe Guard::RailsRunner do end end - describe '#sleep_time' do - let(:timeout) { 30 } - let(:options) { default_options.merge(:timeout => timeout) } - - it "should adjust the sleep time as necessary" do - runner.sleep_time.should == (timeout.to_f / Guard::RailsRunner::MAX_WAIT_COUNT.to_f) - end - end end