spring 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +95 -55
- data/bin/spring +2 -3
- data/lib/spring/application.rb +7 -8
- data/lib/spring/application_manager.rb +12 -5
- data/lib/spring/application_watcher.rb +4 -1
- data/lib/spring/client.rb +31 -0
- data/lib/spring/client/binstub.rb +67 -0
- data/lib/spring/client/command.rb +18 -0
- data/lib/spring/client/help.rb +23 -0
- data/lib/spring/client/run.rb +101 -0
- data/lib/spring/client/stop.rb +11 -0
- data/lib/spring/commands.rb +84 -22
- data/lib/spring/configuration.rb +31 -0
- data/lib/spring/env.rb +43 -5
- data/lib/spring/errors.rb +33 -0
- data/lib/spring/server.rb +9 -2
- data/lib/spring/sid.rb +1 -1
- data/lib/spring/version.rb +2 -2
- data/test/acceptance/app_test.rb +80 -18
- data/test/apps/rails-3-2/.gitignore +3 -0
- data/test/apps/rails-3-2/Gemfile +1 -2
- data/test/helper.rb +0 -1
- data/test/unit/application_watcher_test.rb +15 -3
- data/test/unit/commands_test.rb +34 -0
- metadata +14 -3
- data/lib/spring.rb +0 -151
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spring/version"
|
2
|
+
|
3
|
+
module Spring
|
4
|
+
module Client
|
5
|
+
class Help < Command
|
6
|
+
def call
|
7
|
+
puts <<-EOT
|
8
|
+
Usage: spring COMMAND [ARGS]
|
9
|
+
|
10
|
+
The most common spring commands are:
|
11
|
+
rake Run a rake task
|
12
|
+
console Start the Rails console
|
13
|
+
runner Execute a command with the Rails runner
|
14
|
+
generate Trigger a Rails generator
|
15
|
+
|
16
|
+
test Execute a Test::Unit test
|
17
|
+
rspec Execute an RSpec spec
|
18
|
+
cucumber Execute a Cucumber feature
|
19
|
+
EOT
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
require "spring/commands"
|
5
|
+
|
6
|
+
module Spring
|
7
|
+
module Client
|
8
|
+
class Run < Command
|
9
|
+
SERVER_COMMAND = [
|
10
|
+
File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')),
|
11
|
+
"-I", File.expand_path("../../..", __FILE__),
|
12
|
+
"-r", "spring/server",
|
13
|
+
"-r", "bundler/setup",
|
14
|
+
"-e", "Spring::Server.boot"
|
15
|
+
]
|
16
|
+
|
17
|
+
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO)
|
18
|
+
|
19
|
+
def call
|
20
|
+
Spring.verify_environment!
|
21
|
+
boot_server unless env.server_running?
|
22
|
+
|
23
|
+
application, client = UNIXSocket.pair
|
24
|
+
|
25
|
+
server = UNIXSocket.open(env.socket_name)
|
26
|
+
|
27
|
+
verify_server_version(server)
|
28
|
+
server.send_io client
|
29
|
+
server.puts rails_env_for(args.first)
|
30
|
+
|
31
|
+
application.send_io STDOUT
|
32
|
+
application.send_io STDERR
|
33
|
+
application.send_io STDIN
|
34
|
+
|
35
|
+
application.puts args.length
|
36
|
+
|
37
|
+
args.each do |arg|
|
38
|
+
application.puts arg.length
|
39
|
+
application.write arg
|
40
|
+
end
|
41
|
+
|
42
|
+
pid = server.gets.chomp
|
43
|
+
|
44
|
+
# We must not close the client socket until we are sure that the application has
|
45
|
+
# received the FD. Otherwise the FD can end up getting closed while it's in the server
|
46
|
+
# socket buffer on OS X. This doesn't happen on Linux.
|
47
|
+
client.close
|
48
|
+
|
49
|
+
if pid.empty?
|
50
|
+
exit 1
|
51
|
+
else
|
52
|
+
forward_signals(pid.to_i)
|
53
|
+
application.read # FIXME: receive exit status from server
|
54
|
+
end
|
55
|
+
rescue Errno::ECONNRESET
|
56
|
+
exit 1
|
57
|
+
ensure
|
58
|
+
application.close if application
|
59
|
+
server.close if server
|
60
|
+
end
|
61
|
+
|
62
|
+
# Boot the server into the process group of the current session.
|
63
|
+
# This will cause it to be automatically killed once the session
|
64
|
+
# ends (i.e. when the user closes their terminal).
|
65
|
+
def boot_server
|
66
|
+
env.socket_path.unlink if env.socket_path.exist?
|
67
|
+
Process.spawn(*SERVER_COMMAND, pgroup: SID.pgid)
|
68
|
+
sleep 0.1 until env.socket_path.exist?
|
69
|
+
end
|
70
|
+
|
71
|
+
def verify_server_version(server)
|
72
|
+
server_version = server.gets.chomp
|
73
|
+
if server_version != env.version
|
74
|
+
STDERR.puts <<-ERROR
|
75
|
+
There is a version mismatch beween the spring client and the server.
|
76
|
+
You should restart the server and make sure to use the same version.
|
77
|
+
|
78
|
+
CLIENT: #{env.version}, SERVER: #{server_version}
|
79
|
+
ERROR
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def rails_env_for(command_name)
|
85
|
+
command = Spring.command(command_name)
|
86
|
+
|
87
|
+
if command.respond_to?(:env)
|
88
|
+
command.env
|
89
|
+
else
|
90
|
+
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def forward_signals(pid)
|
95
|
+
(FORWARDED_SIGNALS & Signal.list.keys).each do |sig|
|
96
|
+
trap(sig) { Process.kill(sig, -Process.getpgid(pid)) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/spring/commands.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
class
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module Spring
|
2
4
|
@commands = {}
|
3
5
|
|
4
6
|
class << self
|
@@ -13,56 +15,108 @@ class Spring
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def self.
|
17
|
-
commands.
|
18
|
+
def self.command?(name)
|
19
|
+
commands.include? name
|
18
20
|
end
|
19
21
|
|
20
22
|
def self.command(name)
|
21
23
|
commands.fetch name
|
22
24
|
end
|
23
25
|
|
24
|
-
# Load custom commands, if any
|
25
|
-
begin
|
26
|
-
require "./config/spring"
|
27
|
-
rescue LoadError
|
28
|
-
end
|
29
|
-
|
30
26
|
module Commands
|
31
|
-
class
|
27
|
+
class Command
|
28
|
+
class_attribute :preloads
|
29
|
+
self.preloads = []
|
30
|
+
|
31
|
+
def setup
|
32
|
+
preload_files
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def preload_files
|
38
|
+
preloads.each do |file|
|
39
|
+
begin
|
40
|
+
require file
|
41
|
+
rescue LoadError => e
|
42
|
+
$stderr.puts <<-MESSAGE
|
43
|
+
The #{self.class} command tried to preload #{file} but could not find it.
|
44
|
+
You can configure what to preload in your `config/spring.rb` with:
|
45
|
+
#{self.class}.preloads = %w(files to preload)
|
46
|
+
MESSAGE
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Test < Command
|
53
|
+
self.preloads += %w(test_helper)
|
54
|
+
|
32
55
|
def env
|
33
56
|
"test"
|
34
57
|
end
|
35
58
|
|
36
59
|
def setup
|
37
60
|
$LOAD_PATH.unshift "test"
|
38
|
-
|
61
|
+
super
|
39
62
|
end
|
40
63
|
|
41
64
|
def call(args)
|
42
|
-
|
43
|
-
|
65
|
+
if args.size > 0
|
66
|
+
ARGV.replace args
|
67
|
+
path = File.expand_path(args.first)
|
68
|
+
|
69
|
+
if File.directory?(path)
|
70
|
+
Dir[File.join path, "**", "*_test.rb"].each { |f| require f }
|
71
|
+
else
|
72
|
+
require path
|
73
|
+
end
|
74
|
+
else
|
75
|
+
$stderr.puts "you need to specify what test to run: spring test TEST_NAME"
|
76
|
+
end
|
44
77
|
end
|
45
78
|
end
|
46
79
|
Spring.register_command "test", Test.new
|
47
80
|
|
48
|
-
class RSpec
|
81
|
+
class RSpec < Command
|
82
|
+
self.preloads += %w(spec_helper)
|
83
|
+
|
49
84
|
def env
|
50
85
|
"test"
|
51
86
|
end
|
52
87
|
|
53
88
|
def setup
|
54
89
|
$LOAD_PATH.unshift "spec"
|
55
|
-
|
90
|
+
super
|
91
|
+
require 'rspec/core'
|
56
92
|
end
|
57
93
|
|
58
94
|
def call(args)
|
95
|
+
$0 = "rspec"
|
59
96
|
::RSpec::Core::Runner.run(args)
|
60
97
|
end
|
61
98
|
end
|
62
99
|
Spring.register_command "rspec", RSpec.new
|
63
100
|
|
64
|
-
class
|
101
|
+
class Cucumber < Command
|
102
|
+
def env
|
103
|
+
"test"
|
104
|
+
end
|
105
|
+
|
106
|
+
def setup
|
107
|
+
super
|
108
|
+
require 'cucumber'
|
109
|
+
end
|
110
|
+
|
111
|
+
def call(args)
|
112
|
+
::Cucumber::Cli::Main.execute(args)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
Spring.register_command "cucumber", Cucumber.new
|
116
|
+
|
117
|
+
class Rake < Command
|
65
118
|
def setup
|
119
|
+
super
|
66
120
|
require "rake"
|
67
121
|
end
|
68
122
|
|
@@ -73,20 +127,22 @@ class Spring
|
|
73
127
|
end
|
74
128
|
Spring.register_command "rake", Rake.new
|
75
129
|
|
76
|
-
class Console
|
77
|
-
def setup
|
78
|
-
require "rails/commands/console"
|
79
|
-
end
|
80
130
|
|
131
|
+
class Console < Command
|
81
132
|
def call(args)
|
133
|
+
# This cannot be preloaded as it messes up the IRB prompt on OS X
|
134
|
+
# for unknown reasons. See discussion in issue #34.
|
135
|
+
require "rails/commands/console"
|
136
|
+
|
82
137
|
ARGV.replace args
|
83
138
|
::Rails::Console.start(::Rails.application)
|
84
139
|
end
|
85
140
|
end
|
86
141
|
Spring.register_command "console", Console.new, alias: "c"
|
87
142
|
|
88
|
-
class Generate
|
143
|
+
class Generate < Command
|
89
144
|
def setup
|
145
|
+
super
|
90
146
|
Rails.application.load_generators
|
91
147
|
end
|
92
148
|
|
@@ -97,7 +153,7 @@ class Spring
|
|
97
153
|
end
|
98
154
|
Spring.register_command "generate", Generate.new, alias: "g"
|
99
155
|
|
100
|
-
class Runner
|
156
|
+
class Runner < Command
|
101
157
|
def call(args)
|
102
158
|
Object.const_set(:APP_PATH, Rails.root.join('config/application'))
|
103
159
|
ARGV.replace args
|
@@ -106,4 +162,10 @@ class Spring
|
|
106
162
|
end
|
107
163
|
Spring.register_command "runner", Runner.new, alias: "r"
|
108
164
|
end
|
165
|
+
|
166
|
+
# Load custom commands, if any.
|
167
|
+
# needs to be at the end to allow modification of existing commands.
|
168
|
+
config = File.expand_path("./config/spring.rb")
|
169
|
+
require config if File.exist?(config)
|
170
|
+
|
109
171
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spring/errors"
|
2
|
+
|
3
|
+
module Spring
|
4
|
+
class << self
|
5
|
+
attr_accessor :application_root
|
6
|
+
|
7
|
+
def verify_environment!
|
8
|
+
application_root_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def application_root_path
|
12
|
+
@application_root_path ||= begin
|
13
|
+
path = Pathname.new(File.expand_path(application_root || find_project_root))
|
14
|
+
raise MissingApplication.new(path) unless path.join("config/application.rb").exist?
|
15
|
+
path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_project_root(current_dir = Pathname.new(Dir.pwd))
|
22
|
+
if current_dir.join("Gemfile").exist?
|
23
|
+
current_dir
|
24
|
+
elsif current_dir.root?
|
25
|
+
raise UnknownProject.new(Dir.pwd)
|
26
|
+
else
|
27
|
+
find_project_root(current_dir.parent)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/spring/env.rb
CHANGED
@@ -1,19 +1,25 @@
|
|
1
1
|
require "pathname"
|
2
|
-
require "spring/sid"
|
3
2
|
require "fileutils"
|
4
3
|
|
5
|
-
|
4
|
+
require "spring/version"
|
5
|
+
require "spring/sid"
|
6
|
+
|
7
|
+
module Spring
|
6
8
|
IGNORE_SIGNALS = %w(INT QUIT)
|
7
9
|
|
8
10
|
class Env
|
9
11
|
attr_reader :root
|
10
12
|
|
11
|
-
def initialize
|
12
|
-
@root = Pathname.new(File.expand_path('.'))
|
13
|
+
def initialize(root = nil)
|
14
|
+
@root = root || Pathname.new(File.expand_path('.'))
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
Spring::VERSION
|
13
19
|
end
|
14
20
|
|
15
21
|
def tmp_path
|
16
|
-
path =
|
22
|
+
path = default_tmp_path
|
17
23
|
FileUtils.mkdir_p(path) unless path.exist?
|
18
24
|
path
|
19
25
|
end
|
@@ -29,5 +35,37 @@ class Spring
|
|
29
35
|
def pidfile_path
|
30
36
|
tmp_path.join("#{SID.sid}.pid")
|
31
37
|
end
|
38
|
+
|
39
|
+
def pid
|
40
|
+
pidfile_path.exist? ? pidfile_path.read.to_i : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def app_name
|
44
|
+
root.basename
|
45
|
+
end
|
46
|
+
|
47
|
+
def server_running?
|
48
|
+
if pidfile_path.exist?
|
49
|
+
pidfile = pidfile_path.open('r')
|
50
|
+
!pidfile.flock(File::LOCK_EX | File::LOCK_NB)
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
if pidfile
|
56
|
+
pidfile.flock(File::LOCK_UN)
|
57
|
+
pidfile.close
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def default_tmp_path
|
64
|
+
if ENV['SPRING_TMP_PATH']
|
65
|
+
Pathname.new(ENV['SPRING_TMP_PATH'])
|
66
|
+
else
|
67
|
+
root.join('tmp/spring')
|
68
|
+
end
|
69
|
+
end
|
32
70
|
end
|
33
71
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spring
|
2
|
+
class ClientError < StandardError; end
|
3
|
+
|
4
|
+
class UnknownProject < ClientError
|
5
|
+
attr_reader :current_dir
|
6
|
+
|
7
|
+
def initialize(current_dir)
|
8
|
+
@current_dir = current_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"Spring was unable to locate the root of your project. There was no Gemfile " \
|
13
|
+
"present in the current directory (#{current_dir}) or any of the parent " \
|
14
|
+
"directories."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MissingApplication < ClientError
|
19
|
+
attr_reader :project_root
|
20
|
+
|
21
|
+
def initialize(project_root)
|
22
|
+
@project_root = project_root
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"Spring was unable to find your config/application.rb file. " \
|
27
|
+
"Your project root was detected at #{project_root}, so spring " \
|
28
|
+
"looked for #{project_root}/config/application.rb but it doesn't exist. You can " \
|
29
|
+
"configure the root of your application by setting Spring.application_root in " \
|
30
|
+
"config/spring.rb."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|