spring 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +1 -0
- data/README.md +69 -11
- data/Rakefile +1 -1
- data/lib/spring/application.rb +36 -34
- data/lib/spring/application_manager.rb +3 -1
- data/lib/spring/client.rb +3 -1
- data/lib/spring/client/binstub.rb +4 -0
- data/lib/spring/client/help.rb +69 -13
- data/lib/spring/client/run.rb +7 -9
- data/lib/spring/client/status.rb +30 -0
- data/lib/spring/client/stop.rb +27 -1
- data/lib/spring/commands.rb +46 -10
- data/lib/spring/configuration.rb +8 -0
- data/lib/spring/env.rb +5 -2
- data/lib/spring/process_title_updater.rb +65 -0
- data/lib/spring/server.rb +34 -3
- data/lib/spring/sid.rb +31 -8
- data/lib/spring/version.rb +1 -1
- data/test/acceptance/app_test.rb +119 -116
- data/test/apps/rails-3-2/.gitignore +1 -1
- data/test/unit/client/help_test.rb +50 -0
- data/test/unit/commands_test.rb +21 -2
- data/test/unit/process_title_updater_test.rb +24 -0
- metadata +9 -5
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -40,7 +40,17 @@ real shell and a terminal running the rails "commands console".
|
|
40
40
|
|
41
41
|
## Compatibility
|
42
42
|
|
43
|
-
|
43
|
+
Ruby versions supported:
|
44
|
+
|
45
|
+
* MRI 1.9.3
|
46
|
+
* MRI 2.0.0
|
47
|
+
|
48
|
+
Rails versions supported:
|
49
|
+
|
50
|
+
* 3.2
|
51
|
+
|
52
|
+
Spring makes extensive use of `Process#fork`, so won't be able to run on
|
53
|
+
any platform which doesn't support that (Windows, JRuby).
|
44
54
|
|
45
55
|
## Usage
|
46
56
|
|
@@ -76,9 +86,11 @@ sys 0m0.066s
|
|
76
86
|
That booted our app in the background:
|
77
87
|
|
78
88
|
```
|
79
|
-
$
|
80
|
-
|
81
|
-
|
89
|
+
$ spring status
|
90
|
+
Spring is running:
|
91
|
+
|
92
|
+
26150 spring server | rails-3-2 | started 3 secs ago
|
93
|
+
26155 spring app | rails-3-2 | started 3 secs ago | test mode
|
82
94
|
```
|
83
95
|
|
84
96
|
We can see two processes, one is the Spring server, the other is the
|
@@ -147,9 +159,11 @@ automatically. Let's "edit" `config/application.rb`:
|
|
147
159
|
|
148
160
|
```
|
149
161
|
$ touch config/application.rb
|
150
|
-
$
|
151
|
-
|
152
|
-
|
162
|
+
$ spring status
|
163
|
+
Spring is running:
|
164
|
+
|
165
|
+
26150 spring server | rails-3-2 | started 36 secs ago
|
166
|
+
26556 spring app | rails-3-2 | started 1 sec ago | test mode
|
153
167
|
```
|
154
168
|
|
155
169
|
The application process detected the change and exited. The server process
|
@@ -178,10 +192,19 @@ We now have 3 processes: the server, and application in test mode and
|
|
178
192
|
the application in development mode.
|
179
193
|
|
180
194
|
```
|
181
|
-
$
|
182
|
-
|
183
|
-
|
184
|
-
|
195
|
+
$ bin/spring status
|
196
|
+
Spring is running:
|
197
|
+
|
198
|
+
26150 spring server | rails-3-2 | started 1 min ago
|
199
|
+
26556 spring app | rails-3-2 | started 42 secs ago | test mode
|
200
|
+
26707 spring app | rails-3-2 | started 2 secs ago | development mode
|
201
|
+
```
|
202
|
+
|
203
|
+
To stop the background processes:
|
204
|
+
|
205
|
+
```
|
206
|
+
$ bin/spring stop
|
207
|
+
Spring stopped.
|
185
208
|
```
|
186
209
|
|
187
210
|
## Commands
|
@@ -246,6 +269,41 @@ Spring where your app is located:
|
|
246
269
|
Spring.application_root = './test/dummy'
|
247
270
|
```
|
248
271
|
|
272
|
+
### preload files
|
273
|
+
|
274
|
+
Every Spring command has the ability to preload a set of files. The
|
275
|
+
`test` command for example preloads `test_helper` (it also adds the
|
276
|
+
`test/` directory to your load path). If the
|
277
|
+
defaults don't work for your application you can configure the
|
278
|
+
preloads for every command:
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
# if your test helper is called "helper"
|
282
|
+
Commands::Command::Test.preloads = %w(helper)
|
283
|
+
|
284
|
+
# if you don't want to preload spec_helper.rb
|
285
|
+
Commands::Command::RSpec.preloads = []
|
286
|
+
|
287
|
+
# if you want to preload additional files for the console
|
288
|
+
Commands::Command::Console.preloads << 'extenstions/console_helper'
|
289
|
+
```
|
290
|
+
|
291
|
+
### after fork callbacks
|
292
|
+
|
293
|
+
You might want to run code after Spring forked off the process but
|
294
|
+
before the actual command is run. You might want to use an
|
295
|
+
`after_fork` callback if you have to connect to an external service,
|
296
|
+
do some general cleanup or set up dynamic configuration.
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
Spring.after_fork do
|
300
|
+
# run arbitrary code
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
304
|
+
If you want to register multiple callbacks you can simply call
|
305
|
+
`Spring.after_fork` multiple times with different blocks.
|
306
|
+
|
249
307
|
### tmp directory
|
250
308
|
|
251
309
|
Spring needs a tmp directory. This will default to `Rails.root.join('tmp', 'spring')`.
|
data/Rakefile
CHANGED
data/lib/spring/application.rb
CHANGED
@@ -19,9 +19,6 @@ module Spring
|
|
19
19
|
@manager = manager
|
20
20
|
@watcher = watcher
|
21
21
|
@setup = Set.new
|
22
|
-
|
23
|
-
@stdout = IO.new(STDOUT.fileno)
|
24
|
-
@stderr = IO.new(STDERR.fileno)
|
25
22
|
end
|
26
23
|
|
27
24
|
def start
|
@@ -57,30 +54,39 @@ module Spring
|
|
57
54
|
end
|
58
55
|
|
59
56
|
def serve(client)
|
60
|
-
|
61
|
-
|
62
|
-
args_length
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
57
|
+
streams = 3.times.map { client.recv_io }
|
58
|
+
args_length = client.gets.to_i
|
59
|
+
args = args_length.times.map { client.read(client.gets.to_i) }
|
60
|
+
command = Spring.command(args.shift)
|
61
|
+
|
62
|
+
setup command
|
63
|
+
|
64
|
+
ActionDispatch::Reloader.cleanup!
|
65
|
+
ActionDispatch::Reloader.prepare!
|
66
|
+
|
67
|
+
pid = fork {
|
68
|
+
Process.setsid
|
69
|
+
[STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
|
70
|
+
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
71
|
+
invoke_after_fork_callbacks
|
72
|
+
command.call(args)
|
73
|
+
}
|
74
|
+
|
75
|
+
manager.puts pid
|
76
|
+
|
77
|
+
# Wait in a separate thread so we can run multiple commands at once
|
78
|
+
Thread.new {
|
79
|
+
_, status = Process.wait2 pid
|
80
|
+
streams.each(&:close)
|
81
|
+
client.puts(status.exitstatus)
|
82
|
+
client.close
|
83
|
+
}
|
84
|
+
|
85
|
+
rescue => e
|
86
|
+
streams.each(&:close) if streams
|
87
|
+
client.puts(1)
|
83
88
|
client.close
|
89
|
+
raise
|
84
90
|
end
|
85
91
|
|
86
92
|
# The command might need to require some files in the
|
@@ -99,14 +105,10 @@ module Spring
|
|
99
105
|
end
|
100
106
|
end
|
101
107
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
yield
|
107
|
-
ensure
|
108
|
-
STDOUT.reopen @stdout
|
109
|
-
STDERR.reopen @stderr
|
108
|
+
def invoke_after_fork_callbacks
|
109
|
+
Spring.after_fork_callbacks.each do |callback|
|
110
|
+
callback.call
|
111
|
+
end
|
110
112
|
end
|
111
113
|
end
|
112
114
|
end
|
@@ -70,7 +70,9 @@ module Spring
|
|
70
70
|
[STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
|
71
71
|
@client.close if @client
|
72
72
|
ENV['RAILS_ENV'] = ENV['RACK_ENV'] = app_env
|
73
|
-
|
73
|
+
ProcessTitleUpdater.run { |distance|
|
74
|
+
"spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
|
75
|
+
}
|
74
76
|
Application.new(child_socket).start
|
75
77
|
}
|
76
78
|
child_socket.close
|
data/lib/spring/client.rb
CHANGED
@@ -4,13 +4,15 @@ require "spring/client/run"
|
|
4
4
|
require "spring/client/help"
|
5
5
|
require "spring/client/binstub"
|
6
6
|
require "spring/client/stop"
|
7
|
+
require "spring/client/status"
|
7
8
|
|
8
9
|
module Spring
|
9
10
|
module Client
|
10
11
|
COMMANDS = {
|
11
12
|
"help" => Client::Help,
|
12
13
|
"binstub" => Client::Binstub,
|
13
|
-
"stop" => Client::Stop
|
14
|
+
"stop" => Client::Stop,
|
15
|
+
"status" => Client::Status
|
14
16
|
}
|
15
17
|
|
16
18
|
def self.run(args)
|
data/lib/spring/client/help.rb
CHANGED
@@ -3,20 +3,76 @@ require "spring/version"
|
|
3
3
|
module Spring
|
4
4
|
module Client
|
5
5
|
class Help < Command
|
6
|
+
attr_reader :spring_commands, :application_commands
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
"Print available commands."
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(args, spring_commands = nil, application_commands = nil)
|
13
|
+
super args
|
14
|
+
|
15
|
+
@spring_commands = spring_commands || Spring::Client::COMMANDS
|
16
|
+
@application_commands = application_commands || Spring.commands
|
17
|
+
end
|
18
|
+
|
6
19
|
def call
|
7
|
-
puts
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
puts formatted_help
|
21
|
+
end
|
22
|
+
|
23
|
+
def formatted_help
|
24
|
+
["Usage: spring COMMAND [ARGS]\n",
|
25
|
+
*spring_command_help,
|
26
|
+
'',
|
27
|
+
*application_command_help].join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def spring_command_help
|
31
|
+
["Commands for spring itself:\n",
|
32
|
+
*client_commands.map { |c,n| display_value(c,n) }]
|
33
|
+
end
|
34
|
+
|
35
|
+
def application_command_help
|
36
|
+
["Commands for your application:\n",
|
37
|
+
*registered_commands.map { |c,n| display_value(c,n) }]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def client_commands
|
43
|
+
spring_commands.invert
|
44
|
+
end
|
45
|
+
|
46
|
+
def registered_commands
|
47
|
+
Hash[unique_commands.collect { |c| [c, command_aliases(c)] }]
|
48
|
+
end
|
49
|
+
|
50
|
+
def all_commands
|
51
|
+
@all_commands ||= client_commands.merge(registered_commands)
|
52
|
+
end
|
53
|
+
|
54
|
+
def unique_commands
|
55
|
+
application_commands.collect { |k,v| v }.uniq
|
56
|
+
end
|
57
|
+
|
58
|
+
def command_aliases(command)
|
59
|
+
spring_commands.merge(application_commands).select { |k,v| v == command }.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def description_for_command(command)
|
63
|
+
if command.respond_to?(:description)
|
64
|
+
command.description
|
65
|
+
else
|
66
|
+
"No description given."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def display_value(command, names)
|
71
|
+
" #{ Array(names).join(', ').ljust(max_name_width) } #{ description_for_command(command) }"
|
72
|
+
end
|
73
|
+
|
74
|
+
def max_name_width
|
75
|
+
@max_name_width ||= all_commands.collect { |_,n| Array(n).join(', ').length }.max
|
20
76
|
end
|
21
77
|
end
|
22
78
|
end
|
data/lib/spring/client/run.rb
CHANGED
@@ -39,18 +39,19 @@ module Spring
|
|
39
39
|
application.write arg
|
40
40
|
end
|
41
41
|
|
42
|
-
pid = server.gets
|
42
|
+
pid = server.gets
|
43
|
+
pid = pid.chomp if pid
|
43
44
|
|
44
45
|
# We must not close the client socket until we are sure that the application has
|
45
46
|
# received the FD. Otherwise the FD can end up getting closed while it's in the server
|
46
47
|
# socket buffer on OS X. This doesn't happen on Linux.
|
47
48
|
client.close
|
48
49
|
|
49
|
-
if pid.empty?
|
50
|
-
exit 1
|
51
|
-
else
|
50
|
+
if pid && !pid.empty?
|
52
51
|
forward_signals(pid.to_i)
|
53
|
-
application.read
|
52
|
+
exit application.read.to_i
|
53
|
+
else
|
54
|
+
exit 1
|
54
55
|
end
|
55
56
|
rescue Errno::ECONNRESET
|
56
57
|
exit 1
|
@@ -59,12 +60,9 @@ module Spring
|
|
59
60
|
server.close if server
|
60
61
|
end
|
61
62
|
|
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
63
|
def boot_server
|
66
64
|
env.socket_path.unlink if env.socket_path.exist?
|
67
|
-
Process.spawn(*SERVER_COMMAND
|
65
|
+
Process.spawn(*SERVER_COMMAND)
|
68
66
|
sleep 0.1 until env.socket_path.exist?
|
69
67
|
end
|
70
68
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spring
|
2
|
+
module Client
|
3
|
+
class Status < Command
|
4
|
+
def self.description
|
5
|
+
"Show current status."
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
if env.server_running?
|
10
|
+
puts "Spring is running:"
|
11
|
+
puts
|
12
|
+
print_process env.pid
|
13
|
+
application_pids.each { |pid| print_process pid }
|
14
|
+
else
|
15
|
+
puts "Spring is not running."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def print_process(pid)
|
20
|
+
puts `ps -p #{pid} -o pid= -o args=`
|
21
|
+
end
|
22
|
+
|
23
|
+
def application_pids
|
24
|
+
candidates = `ps -o ppid= -o pid=`.lines
|
25
|
+
candidates.select { |l| l =~ /^#{env.pid} / }
|
26
|
+
.map { |l| l.split(" ").last }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/spring/client/stop.rb
CHANGED
@@ -3,8 +3,34 @@ require "spring/version"
|
|
3
3
|
module Spring
|
4
4
|
module Client
|
5
5
|
class Stop < Command
|
6
|
+
TIMEOUT = 2 # seconds
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
"Stop all spring processes for this project."
|
10
|
+
end
|
11
|
+
|
6
12
|
def call
|
7
|
-
|
13
|
+
if env.server_running?
|
14
|
+
timeout = Time.now + TIMEOUT
|
15
|
+
kill 'TERM'
|
16
|
+
sleep 0.1 until !env.server_running? || Time.now >= timeout
|
17
|
+
|
18
|
+
if env.server_running?
|
19
|
+
STDERR.puts "Spring did not stop; killing forcibly."
|
20
|
+
kill 'KILL'
|
21
|
+
else
|
22
|
+
puts "Spring stopped."
|
23
|
+
end
|
24
|
+
else
|
25
|
+
puts "Spring is not running"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def kill(sig)
|
30
|
+
pid = env.pid
|
31
|
+
Process.kill(sig, pid) if pid
|
32
|
+
rescue Errno::ESRCH
|
33
|
+
# already dead
|
8
34
|
end
|
9
35
|
end
|
10
36
|
end
|