spring 0.0.8 → 1.0.0
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.
- checksums.yaml +7 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +1 -3
- data/README.md +129 -187
- data/bin/spring +27 -0
- data/lib/spring/application.rb +121 -20
- data/lib/spring/application_manager.rb +21 -13
- data/lib/spring/client/binstub.rb +45 -41
- data/lib/spring/client/help.rb +4 -1
- data/lib/spring/client/rails.rb +8 -3
- data/lib/spring/client/run.rb +33 -10
- data/lib/spring/client/status.rb +2 -2
- data/lib/spring/client.rb +3 -3
- data/lib/spring/commands/rails.rb +60 -0
- data/lib/spring/commands/rake.rb +34 -0
- data/lib/spring/commands.rb +12 -195
- data/lib/spring/configuration.rb +6 -2
- data/lib/spring/env.rb +27 -18
- data/lib/spring/errors.rb +1 -1
- data/lib/spring/json.rb +626 -0
- data/lib/spring/server.rb +60 -29
- data/lib/spring/version.rb +1 -1
- data/lib/spring/watcher/abstract.rb +11 -7
- data/lib/spring/watcher/listen.rb +8 -10
- data/lib/spring/watcher/polling.rb +1 -1
- data/lib/spring/watcher.rb +17 -11
- data/spring.gemspec +1 -0
- data/test/acceptance/app_test.rb +270 -73
- data/test/apps/.gitignore +3 -0
- data/test/unit/client/help_test.rb +2 -0
- data/test/unit/commands_test.rb +8 -49
- data/test/unit/watcher_test.rb +22 -2
- metadata +18 -151
- data/lib/spring/client/start.rb +0 -17
- data/test/apps/rails-3-2/.gitignore +0 -18
- data/test/apps/rails-3-2/Gemfile +0 -9
- data/test/apps/rails-3-2/README.rdoc +0 -261
- data/test/apps/rails-3-2/Rakefile +0 -7
- data/test/apps/rails-3-2/app/assets/images/rails.png +0 -0
- data/test/apps/rails-3-2/app/assets/javascripts/application.js +0 -15
- data/test/apps/rails-3-2/app/assets/javascripts/posts.js.coffee +0 -3
- data/test/apps/rails-3-2/app/assets/stylesheets/application.css +0 -13
- data/test/apps/rails-3-2/app/assets/stylesheets/posts.css.scss +0 -3
- data/test/apps/rails-3-2/app/assets/stylesheets/scaffolds.css.scss +0 -69
- data/test/apps/rails-3-2/app/controllers/application_controller.rb +0 -3
- data/test/apps/rails-3-2/app/controllers/posts_controller.rb +0 -83
- data/test/apps/rails-3-2/app/helpers/application_helper.rb +0 -2
- data/test/apps/rails-3-2/app/helpers/posts_helper.rb +0 -2
- data/test/apps/rails-3-2/app/mailers/.gitkeep +0 -0
- data/test/apps/rails-3-2/app/models/.gitkeep +0 -0
- data/test/apps/rails-3-2/app/models/post.rb +0 -3
- data/test/apps/rails-3-2/app/views/layouts/application.html.erb +0 -14
- data/test/apps/rails-3-2/app/views/posts/_form.html.erb +0 -21
- data/test/apps/rails-3-2/app/views/posts/edit.html.erb +0 -6
- data/test/apps/rails-3-2/app/views/posts/index.html.erb +0 -23
- data/test/apps/rails-3-2/app/views/posts/new.html.erb +0 -5
- data/test/apps/rails-3-2/app/views/posts/show.html.erb +0 -10
- data/test/apps/rails-3-2/config/application.rb +0 -62
- data/test/apps/rails-3-2/config/boot.rb +0 -6
- data/test/apps/rails-3-2/config/database.yml +0 -25
- data/test/apps/rails-3-2/config/environment.rb +0 -5
- data/test/apps/rails-3-2/config/environments/development.rb +0 -37
- data/test/apps/rails-3-2/config/environments/production.rb +0 -67
- data/test/apps/rails-3-2/config/environments/test.rb +0 -37
- data/test/apps/rails-3-2/config/initializers/backtrace_silencers.rb +0 -7
- data/test/apps/rails-3-2/config/initializers/inflections.rb +0 -15
- data/test/apps/rails-3-2/config/initializers/mime_types.rb +0 -5
- data/test/apps/rails-3-2/config/initializers/secret_token.rb +0 -7
- data/test/apps/rails-3-2/config/initializers/session_store.rb +0 -8
- data/test/apps/rails-3-2/config/initializers/wrap_parameters.rb +0 -14
- data/test/apps/rails-3-2/config/locales/en.yml +0 -5
- data/test/apps/rails-3-2/config/routes.rb +0 -60
- data/test/apps/rails-3-2/config/spring.rb +0 -7
- data/test/apps/rails-3-2/config.ru +0 -4
- data/test/apps/rails-3-2/db/migrate/20121109171227_create_posts.rb +0 -9
- data/test/apps/rails-3-2/db/schema.rb +0 -22
- data/test/apps/rails-3-2/db/seeds.rb +0 -7
- data/test/apps/rails-3-2/lib/assets/.gitkeep +0 -0
- data/test/apps/rails-3-2/lib/tasks/.gitkeep +0 -0
- data/test/apps/rails-3-2/log/.gitkeep +0 -0
- data/test/apps/rails-3-2/public/404.html +0 -26
- data/test/apps/rails-3-2/public/422.html +0 -26
- data/test/apps/rails-3-2/public/500.html +0 -25
- data/test/apps/rails-3-2/public/favicon.ico +0 -0
- data/test/apps/rails-3-2/public/index.html +0 -241
- data/test/apps/rails-3-2/public/robots.txt +0 -5
- data/test/apps/rails-3-2/script/rails +0 -6
- data/test/apps/rails-3-2/test/fixtures/.gitkeep +0 -0
- data/test/apps/rails-3-2/test/fixtures/posts.yml +0 -7
- data/test/apps/rails-3-2/test/functional/.gitkeep +0 -0
- data/test/apps/rails-3-2/test/functional/posts_controller_test.rb +0 -49
- data/test/apps/rails-3-2/test/integration/.gitkeep +0 -0
- data/test/apps/rails-3-2/test/performance/browsing_test.rb +0 -12
- data/test/apps/rails-3-2/test/test_helper.rb +0 -13
- data/test/apps/rails-3-2/test/unit/.gitkeep +0 -0
- data/test/apps/rails-3-2/test/unit/helpers/posts_helper_test.rb +0 -4
- data/test/apps/rails-3-2/test/unit/post_test.rb +0 -7
- data/test/apps/rails-3-2/vendor/assets/javascripts/.gitkeep +0 -0
- data/test/apps/rails-3-2/vendor/assets/stylesheets/.gitkeep +0 -0
- data/test/apps/rails-3-2/vendor/plugins/.gitkeep +0 -0
data/lib/spring/application.rb
CHANGED
|
@@ -1,87 +1,162 @@
|
|
|
1
1
|
require "set"
|
|
2
|
-
require "json"
|
|
3
|
-
|
|
4
|
-
require "spring/configuration"
|
|
5
2
|
require "spring/watcher"
|
|
3
|
+
require "thread"
|
|
6
4
|
|
|
7
5
|
module Spring
|
|
8
6
|
class Application
|
|
9
|
-
attr_reader :manager, :watcher
|
|
7
|
+
attr_reader :manager, :watcher, :spring_env
|
|
10
8
|
|
|
11
9
|
def initialize(manager, watcher = Spring.watcher)
|
|
12
|
-
@manager
|
|
13
|
-
@watcher
|
|
14
|
-
@setup
|
|
10
|
+
@manager = manager
|
|
11
|
+
@watcher = watcher
|
|
12
|
+
@setup = Set.new
|
|
13
|
+
@spring_env = Env.new
|
|
14
|
+
@preloaded = false
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
@waiting = 0
|
|
17
|
+
@exiting = false
|
|
15
18
|
|
|
16
19
|
# Workaround for GC bug in Ruby 2 which causes segfaults if watcher.to_io
|
|
17
20
|
# instances get dereffed.
|
|
18
21
|
@fds = [manager, watcher.to_io]
|
|
19
22
|
end
|
|
20
23
|
|
|
21
|
-
def
|
|
24
|
+
def log(message)
|
|
25
|
+
spring_env.log "[application:#{ENV['RAILS_ENV']}] #{message}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def preloaded?
|
|
29
|
+
@preloaded
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exiting?
|
|
33
|
+
@exiting
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def preload
|
|
37
|
+
log "preloading app"
|
|
38
|
+
|
|
22
39
|
require Spring.application_root_path.join("config", "application")
|
|
23
40
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
41
|
+
# config/environments/test.rb will have config.cache_classes = true. However
|
|
42
|
+
# we want it to be false so that we can reload files. This is a hack to
|
|
43
|
+
# override the effect of config.cache_classes = true. We can then actually
|
|
44
|
+
# set config.cache_classes = false after loading the environment.
|
|
27
45
|
Rails::Application.initializer :initialize_dependency_mechanism, group: :all do
|
|
28
46
|
ActiveSupport::Dependencies.mechanism = :load
|
|
29
47
|
end
|
|
30
48
|
|
|
31
49
|
require Spring.application_root_path.join("config", "environment")
|
|
32
50
|
|
|
51
|
+
Rails.application.config.cache_classes = false
|
|
52
|
+
disconnect_database
|
|
53
|
+
|
|
33
54
|
watcher.add loaded_application_features
|
|
34
|
-
watcher.add
|
|
55
|
+
watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"
|
|
35
56
|
watcher.add Rails.application.paths["config/initializers"]
|
|
36
57
|
|
|
37
|
-
|
|
58
|
+
@preloaded = true
|
|
38
59
|
end
|
|
39
60
|
|
|
40
61
|
def run
|
|
62
|
+
log "running"
|
|
41
63
|
watcher.start
|
|
42
64
|
|
|
43
65
|
loop do
|
|
44
66
|
IO.select(@fds)
|
|
45
67
|
|
|
46
68
|
if watcher.stale?
|
|
47
|
-
|
|
69
|
+
log "watcher stale; exiting"
|
|
70
|
+
manager.close
|
|
71
|
+
@exiting = true
|
|
72
|
+
try_exit
|
|
73
|
+
sleep
|
|
48
74
|
else
|
|
49
75
|
serve manager.recv_io(UNIXSocket)
|
|
50
76
|
end
|
|
51
77
|
end
|
|
52
78
|
end
|
|
53
79
|
|
|
80
|
+
def try_exit
|
|
81
|
+
@mutex.synchronize {
|
|
82
|
+
exit if exiting? && @waiting == 0
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
54
86
|
def serve(client)
|
|
87
|
+
log "got client"
|
|
55
88
|
manager.puts
|
|
56
89
|
|
|
57
90
|
streams = 3.times.map { client.recv_io }
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
[STDOUT, STDERR].zip(streams).each { |a, b| a.reopen(b) }
|
|
92
|
+
|
|
93
|
+
preload unless preloaded?
|
|
94
|
+
|
|
95
|
+
args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
|
|
96
|
+
command = Spring.command(args.shift)
|
|
60
97
|
|
|
98
|
+
connect_database
|
|
61
99
|
setup command
|
|
62
100
|
|
|
63
|
-
|
|
64
|
-
|
|
101
|
+
if Rails.application.reloaders.any?(&:updated?)
|
|
102
|
+
ActionDispatch::Reloader.cleanup!
|
|
103
|
+
ActionDispatch::Reloader.prepare!
|
|
104
|
+
end
|
|
65
105
|
|
|
66
106
|
pid = fork {
|
|
67
107
|
Process.setsid
|
|
68
|
-
|
|
108
|
+
STDIN.reopen(streams.last)
|
|
69
109
|
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
|
|
110
|
+
|
|
111
|
+
ARGV.replace(args)
|
|
112
|
+
|
|
113
|
+
# Delete all env vars which are unchanged from before spring started
|
|
114
|
+
Spring.original_env.each { |k, v| ENV.delete k if ENV[k] == v }
|
|
115
|
+
|
|
116
|
+
# Load in the current env vars, except those which *were* changed when spring started
|
|
117
|
+
env.each { |k, v| ENV[k] ||= v }
|
|
118
|
+
|
|
119
|
+
connect_database
|
|
120
|
+
srand
|
|
121
|
+
|
|
70
122
|
invoke_after_fork_callbacks
|
|
71
|
-
|
|
123
|
+
shush_backtraces
|
|
124
|
+
|
|
125
|
+
if command.respond_to?(:call)
|
|
126
|
+
command.call
|
|
127
|
+
else
|
|
128
|
+
exec_name = command.exec_name
|
|
129
|
+
gem_name = command.gem_name if command.respond_to?(:gem_name)
|
|
130
|
+
|
|
131
|
+
exec = Gem.bin_path(gem_name || exec_name, exec_name)
|
|
132
|
+
$0 = exec
|
|
133
|
+
load exec
|
|
134
|
+
end
|
|
72
135
|
}
|
|
73
136
|
|
|
137
|
+
disconnect_database
|
|
138
|
+
[STDOUT, STDERR].each { |stream| stream.reopen(spring_env.log_file) }
|
|
139
|
+
|
|
140
|
+
log "forked #{pid}"
|
|
74
141
|
manager.puts pid
|
|
75
142
|
|
|
76
143
|
# Wait in a separate thread so we can run multiple commands at once
|
|
77
144
|
Thread.new {
|
|
145
|
+
@mutex.synchronize { @waiting += 1 }
|
|
146
|
+
|
|
78
147
|
_, status = Process.wait2 pid
|
|
148
|
+
log "#{pid} exited with #{status.exitstatus}"
|
|
149
|
+
|
|
79
150
|
streams.each(&:close)
|
|
80
151
|
client.puts(status.exitstatus)
|
|
81
152
|
client.close
|
|
153
|
+
|
|
154
|
+
@mutex.synchronize { @waiting -= 1 }
|
|
155
|
+
try_exit
|
|
82
156
|
}
|
|
83
157
|
|
|
84
158
|
rescue => e
|
|
159
|
+
log "exception: #{e}"
|
|
85
160
|
streams.each(&:close) if streams
|
|
86
161
|
client.puts(1)
|
|
87
162
|
client.close
|
|
@@ -110,5 +185,31 @@ module Spring
|
|
|
110
185
|
def loaded_application_features
|
|
111
186
|
$LOADED_FEATURES.select { |f| f.start_with?(File.realpath(Rails.root)) }
|
|
112
187
|
end
|
|
188
|
+
|
|
189
|
+
def disconnect_database
|
|
190
|
+
ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def connect_database
|
|
194
|
+
ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# This feels very naughty
|
|
198
|
+
def shush_backtraces
|
|
199
|
+
Kernel.module_eval do
|
|
200
|
+
old_raise = Kernel.method(:raise)
|
|
201
|
+
remove_method :raise
|
|
202
|
+
define_method :raise do |*args|
|
|
203
|
+
begin
|
|
204
|
+
old_raise.call(*args)
|
|
205
|
+
ensure
|
|
206
|
+
if $!
|
|
207
|
+
lib = File.expand_path("..", __FILE__)
|
|
208
|
+
$!.backtrace.reject! { |line| line.start_with?(lib) }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
113
214
|
end
|
|
114
215
|
end
|
|
@@ -4,16 +4,19 @@ require "spring/application"
|
|
|
4
4
|
|
|
5
5
|
module Spring
|
|
6
6
|
class ApplicationManager
|
|
7
|
-
attr_reader :pid, :child, :app_env, :spring_env
|
|
8
|
-
|
|
9
|
-
def initialize(app_env)
|
|
10
|
-
super()
|
|
7
|
+
attr_reader :pid, :child, :app_env, :spring_env, :server
|
|
11
8
|
|
|
9
|
+
def initialize(server, app_env)
|
|
10
|
+
@server = server
|
|
12
11
|
@app_env = app_env
|
|
13
12
|
@spring_env = Env.new
|
|
14
13
|
@mutex = Mutex.new
|
|
15
14
|
end
|
|
16
15
|
|
|
16
|
+
def log(message)
|
|
17
|
+
server.log(message)
|
|
18
|
+
end
|
|
19
|
+
|
|
17
20
|
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
|
|
18
21
|
# line which messes with backtraces in e.g. rspec
|
|
19
22
|
def synchronize
|
|
@@ -29,9 +32,6 @@ module Spring
|
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def restart
|
|
32
|
-
# Restarting is a background operation. If it fails, we don't want
|
|
33
|
-
# any terminal output. The user will see the output when they next
|
|
34
|
-
# try to run a command.
|
|
35
35
|
start_child(true)
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -47,10 +47,12 @@ module Spring
|
|
|
47
47
|
rescue Errno::ECONNRESET, Errno::EPIPE
|
|
48
48
|
# The child has died but has not been collected by the wait thread yet,
|
|
49
49
|
# so start a new child and try again.
|
|
50
|
+
log "child dead; starting"
|
|
50
51
|
start
|
|
51
52
|
yield
|
|
52
53
|
end
|
|
53
54
|
else
|
|
55
|
+
log "child not running; starting"
|
|
54
56
|
start
|
|
55
57
|
yield
|
|
56
58
|
end
|
|
@@ -64,8 +66,12 @@ module Spring
|
|
|
64
66
|
child.gets
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
child.gets
|
|
68
|
-
|
|
69
|
+
pid = child.gets
|
|
70
|
+
pid = pid.chomp.to_i if pid
|
|
71
|
+
log "got worker pid #{pid}"
|
|
72
|
+
pid
|
|
73
|
+
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
|
74
|
+
log "#{e} while reading from child; returning no pid"
|
|
69
75
|
nil
|
|
70
76
|
ensure
|
|
71
77
|
client.close
|
|
@@ -77,11 +83,11 @@ module Spring
|
|
|
77
83
|
|
|
78
84
|
private
|
|
79
85
|
|
|
80
|
-
def start_child(
|
|
86
|
+
def start_child(preload = false)
|
|
87
|
+
server.application_starting
|
|
88
|
+
|
|
81
89
|
@child, child_socket = UNIXSocket.pair
|
|
82
90
|
@pid = fork {
|
|
83
|
-
[STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
|
|
84
|
-
|
|
85
91
|
(ObjectSpace.each_object(IO).to_a - [STDOUT, STDERR, STDIN, child_socket])
|
|
86
92
|
.reject(&:closed?)
|
|
87
93
|
.each(&:close)
|
|
@@ -92,7 +98,9 @@ module Spring
|
|
|
92
98
|
"spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
Application.new(child_socket)
|
|
101
|
+
app = Application.new(child_socket)
|
|
102
|
+
app.preload if preload
|
|
103
|
+
app.run
|
|
96
104
|
}
|
|
97
105
|
child_socket.close
|
|
98
106
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module Spring
|
|
2
2
|
module Client
|
|
3
3
|
class Binstub < Command
|
|
4
|
-
attr_reader :bindir, :
|
|
4
|
+
attr_reader :bindir, :commands
|
|
5
5
|
|
|
6
6
|
def self.description
|
|
7
|
-
"Generate spring based binstubs."
|
|
7
|
+
"Generate spring based binstubs. Use --all to generate a binstub for all known commands."
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def self.call(args)
|
|
@@ -12,64 +12,68 @@ module Spring
|
|
|
12
12
|
super
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
class RailsCommand
|
|
16
|
+
def fallback
|
|
17
|
+
<<CODE
|
|
18
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
|
19
|
+
require_relative '../config/boot'
|
|
20
|
+
require 'rails/commands'
|
|
21
|
+
CODE
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
15
25
|
def initialize(args)
|
|
16
26
|
super
|
|
17
27
|
|
|
18
|
-
@bindir
|
|
19
|
-
@
|
|
28
|
+
@bindir = env.root.join("bin")
|
|
29
|
+
@commands = args.drop(1).inject({}) { |mem, name| mem.merge(find_commands(name)) }
|
|
20
30
|
end
|
|
21
31
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
def find_commands(name)
|
|
33
|
+
case name
|
|
34
|
+
when "--all"
|
|
35
|
+
commands = Spring.commands
|
|
36
|
+
commands.delete_if { |name, _| name.start_with?("rails_") }
|
|
37
|
+
commands["rails"] = RailsCommand.new
|
|
38
|
+
commands
|
|
39
|
+
when "rails"
|
|
40
|
+
{ name => RailsCommand.new }
|
|
27
41
|
else
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
if command = Spring.commands[name]
|
|
43
|
+
{ name => command }
|
|
44
|
+
else
|
|
45
|
+
$stderr.puts "The '#{name}' command is not known to spring."
|
|
46
|
+
exit 1
|
|
47
|
+
end
|
|
30
48
|
end
|
|
31
49
|
end
|
|
32
50
|
|
|
33
|
-
def
|
|
34
|
-
bindir.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def command_binstub
|
|
38
|
-
bindir.join(name)
|
|
51
|
+
def call
|
|
52
|
+
bindir.mkdir unless bindir.exist?
|
|
53
|
+
commands.each { |name, command| generate_binstub(name, command) }
|
|
39
54
|
end
|
|
40
55
|
|
|
41
|
-
def
|
|
42
|
-
File.write(
|
|
56
|
+
def generate_binstub(name, command)
|
|
57
|
+
File.write(bindir.join(name), <<CODE)
|
|
43
58
|
#!/usr/bin/env ruby
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
# https://github.com/rubygems/rubygems/pull/435
|
|
48
|
-
|
|
49
|
-
glob = "{#{Gem::Specification.dirs.join(",")}}/spring-*.gemspec"
|
|
50
|
-
candidates = Dir[glob].to_a.sort
|
|
51
|
-
|
|
52
|
-
spec = Gem::Specification.load(candidates.last)
|
|
53
|
-
|
|
54
|
-
if spec
|
|
55
|
-
spec.activate
|
|
56
|
-
load spec.bin_file("spring")
|
|
60
|
+
if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?
|
|
61
|
+
#{fallback(name, command).strip.gsub(/^/, " ")}
|
|
57
62
|
else
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
ARGV.unshift "#{name}"
|
|
64
|
+
load Gem.bin_path("spring", "spring")
|
|
60
65
|
end
|
|
61
66
|
CODE
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
bindir.join(name).chmod 0755
|
|
64
69
|
end
|
|
65
70
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
command_binstub.chmod 0755
|
|
71
|
+
def fallback(name, command)
|
|
72
|
+
if command.respond_to?(:fallback)
|
|
73
|
+
command.fallback
|
|
74
|
+
else
|
|
75
|
+
%{exec "bundle", "exec", "#{name}", *ARGV}
|
|
76
|
+
end
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
end
|
data/lib/spring/client/help.rb
CHANGED
|
@@ -28,7 +28,8 @@ module Spring
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def formatted_help
|
|
31
|
-
["
|
|
31
|
+
["Version: #{env.version}\n",
|
|
32
|
+
"Usage: spring COMMAND [ARGS]\n",
|
|
32
33
|
*command_help("spring itself", spring_commands),
|
|
33
34
|
'',
|
|
34
35
|
*command_help("your application", application_commands)].join("\n")
|
|
@@ -48,6 +49,8 @@ module Spring
|
|
|
48
49
|
def description_for_command(command)
|
|
49
50
|
if command.respond_to?(:description)
|
|
50
51
|
command.description
|
|
52
|
+
elsif command.respond_to?(:exec_name)
|
|
53
|
+
"Runs the #{command.exec_name} command"
|
|
51
54
|
else
|
|
52
55
|
"No description given."
|
|
53
56
|
end
|
data/lib/spring/client/rails.rb
CHANGED
|
@@ -3,12 +3,13 @@ require "set"
|
|
|
3
3
|
module Spring
|
|
4
4
|
module Client
|
|
5
5
|
class Rails < Command
|
|
6
|
-
COMMANDS = Set.new %w(console runner generate)
|
|
6
|
+
COMMANDS = Set.new %w(console runner generate destroy)
|
|
7
7
|
|
|
8
8
|
ALIASES = {
|
|
9
9
|
"c" => "console",
|
|
10
10
|
"r" => "runner",
|
|
11
|
-
"g" => "generate"
|
|
11
|
+
"g" => "generate",
|
|
12
|
+
"d" => "destroy"
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
def self.description
|
|
@@ -21,7 +22,11 @@ module Spring
|
|
|
21
22
|
if COMMANDS.include?(command_name)
|
|
22
23
|
Run.call(["rails_#{command_name}", *args.drop(2)])
|
|
23
24
|
else
|
|
24
|
-
|
|
25
|
+
require "spring/configuration"
|
|
26
|
+
ARGV.shift
|
|
27
|
+
Object.const_set(:APP_PATH, Spring.application_root_path.join("config/application").to_s)
|
|
28
|
+
require Spring.application_root_path.join("config/boot")
|
|
29
|
+
require "rails/commands"
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
32
|
end
|
data/lib/spring/client/run.rb
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
require "rbconfig"
|
|
2
2
|
require "socket"
|
|
3
|
-
require "json"
|
|
4
3
|
|
|
5
4
|
module Spring
|
|
6
5
|
module Client
|
|
7
6
|
class Run < Command
|
|
8
7
|
FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO) & Signal.list.keys
|
|
9
8
|
|
|
9
|
+
def log(message)
|
|
10
|
+
env.log "[client] #{message}"
|
|
11
|
+
end
|
|
12
|
+
|
|
10
13
|
def server
|
|
11
14
|
@server ||= UNIXSocket.open(env.socket_name)
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def call
|
|
15
|
-
Spring.verify_environment!
|
|
16
|
-
|
|
17
18
|
boot_server unless env.server_running?
|
|
18
19
|
verify_server_version
|
|
19
20
|
|
|
@@ -29,15 +30,24 @@ module Spring
|
|
|
29
30
|
|
|
30
31
|
def boot_server
|
|
31
32
|
env.socket_path.unlink if env.socket_path.exist?
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
pid = fork {
|
|
35
|
+
require "spring/server"
|
|
36
|
+
Spring::Server.boot
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
until env.socket_path.exist?
|
|
40
|
+
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
41
|
+
exit status.exitstatus if status
|
|
42
|
+
sleep 0.1
|
|
43
|
+
end
|
|
34
44
|
end
|
|
35
45
|
|
|
36
46
|
def verify_server_version
|
|
37
47
|
server_version = server.gets.chomp
|
|
38
48
|
if server_version != env.version
|
|
39
49
|
$stderr.puts <<-ERROR
|
|
40
|
-
There is a version mismatch
|
|
50
|
+
There is a version mismatch between the spring client and the server.
|
|
41
51
|
You should restart the server and make sure to use the same version.
|
|
42
52
|
|
|
43
53
|
CLIENT: #{env.version}, SERVER: #{server_version}
|
|
@@ -48,16 +58,18 @@ ERROR
|
|
|
48
58
|
|
|
49
59
|
def connect_to_application(client)
|
|
50
60
|
server.send_io client
|
|
51
|
-
send_json server, args
|
|
61
|
+
send_json server, "args" => args, "default_rails_env" => default_rails_env
|
|
52
62
|
server.gets or raise CommandNotFound
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
def run_command(client, application)
|
|
66
|
+
log "sending command"
|
|
67
|
+
|
|
56
68
|
application.send_io STDOUT
|
|
57
69
|
application.send_io STDERR
|
|
58
70
|
application.send_io STDIN
|
|
59
71
|
|
|
60
|
-
send_json application, args
|
|
72
|
+
send_json application, "args" => args, "env" => ENV.to_hash
|
|
61
73
|
|
|
62
74
|
pid = server.gets
|
|
63
75
|
pid = pid.chomp if pid
|
|
@@ -68,9 +80,16 @@ ERROR
|
|
|
68
80
|
client.close
|
|
69
81
|
|
|
70
82
|
if pid && !pid.empty?
|
|
83
|
+
log "got pid: #{pid}"
|
|
84
|
+
|
|
71
85
|
forward_signals(pid.to_i)
|
|
72
|
-
|
|
86
|
+
status = application.read.to_i
|
|
87
|
+
|
|
88
|
+
log "got exit status #{status}"
|
|
89
|
+
|
|
90
|
+
exit status
|
|
73
91
|
else
|
|
92
|
+
log "got no pid"
|
|
74
93
|
exit 1
|
|
75
94
|
end
|
|
76
95
|
ensure
|
|
@@ -95,9 +114,13 @@ ERROR
|
|
|
95
114
|
def send_json(socket, data)
|
|
96
115
|
data = JSON.dump(data)
|
|
97
116
|
|
|
98
|
-
socket.puts data.
|
|
117
|
+
socket.puts data.bytesize
|
|
99
118
|
socket.write data
|
|
100
119
|
end
|
|
120
|
+
|
|
121
|
+
def default_rails_env
|
|
122
|
+
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
123
|
+
end
|
|
101
124
|
end
|
|
102
125
|
end
|
|
103
126
|
end
|
data/lib/spring/client/status.rb
CHANGED
|
@@ -21,8 +21,8 @@ module Spring
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def application_pids
|
|
24
|
-
candidates = `ps -
|
|
25
|
-
candidates.select { |l| l =~
|
|
24
|
+
candidates = `ps -A -o ppid= -o pid=`.lines
|
|
25
|
+
candidates.select { |l| l =~ /^(\s+)?#{env.pid} / }
|
|
26
26
|
.map { |l| l.split(" ").last }
|
|
27
27
|
end
|
|
28
28
|
end
|
data/lib/spring/client.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
require "spring/
|
|
1
|
+
require "spring/errors"
|
|
2
|
+
require "spring/json"
|
|
3
|
+
|
|
2
4
|
require "spring/client/command"
|
|
3
5
|
require "spring/client/run"
|
|
4
6
|
require "spring/client/help"
|
|
5
7
|
require "spring/client/binstub"
|
|
6
|
-
require "spring/client/start"
|
|
7
8
|
require "spring/client/stop"
|
|
8
9
|
require "spring/client/status"
|
|
9
10
|
require "spring/client/rails"
|
|
@@ -13,7 +14,6 @@ module Spring
|
|
|
13
14
|
COMMANDS = {
|
|
14
15
|
"help" => Client::Help,
|
|
15
16
|
"binstub" => Client::Binstub,
|
|
16
|
-
"start" => Client::Start,
|
|
17
17
|
"stop" => Client::Stop,
|
|
18
18
|
"status" => Client::Status,
|
|
19
19
|
"rails" => Client::Rails
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Spring
|
|
2
|
+
module Commands
|
|
3
|
+
class Rails
|
|
4
|
+
def call
|
|
5
|
+
ARGV.unshift command_name
|
|
6
|
+
Object.const_set(:APP_PATH, ::Rails.root.join('config/application'))
|
|
7
|
+
require "rails/commands"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def description
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class RailsConsole < Rails
|
|
16
|
+
def env(args)
|
|
17
|
+
args.first if args.first && !args.first.index("-")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def command_name
|
|
21
|
+
"console"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class RailsGenerate < Rails
|
|
26
|
+
def command_name
|
|
27
|
+
"generate"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class RailsDestroy < Rails
|
|
32
|
+
def command_name
|
|
33
|
+
"destroy"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class RailsRunner < Rails
|
|
38
|
+
def env(tail)
|
|
39
|
+
previous_option = nil
|
|
40
|
+
tail.reverse.each do |option|
|
|
41
|
+
case option
|
|
42
|
+
when /--environment=(\w+)/ then return $1
|
|
43
|
+
when '-e' then return previous_option
|
|
44
|
+
end
|
|
45
|
+
previous_option = option
|
|
46
|
+
end
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def command_name
|
|
51
|
+
"runner"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
Spring.register_command "rails_console", RailsConsole.new
|
|
56
|
+
Spring.register_command "rails_generate", RailsGenerate.new
|
|
57
|
+
Spring.register_command "rails_destroy", RailsDestroy.new
|
|
58
|
+
Spring.register_command "rails_runner", RailsRunner.new
|
|
59
|
+
end
|
|
60
|
+
end
|