nrispring 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ command = File.basename($0)
2
+ bin_path = File.expand_path("../../../bin/spring", __FILE__)
3
+
4
+ if command == "spring"
5
+ load bin_path
6
+ else
7
+ disable = ENV["DISABLE_SPRING"]
8
+
9
+ if Process.respond_to?(:fork) && (disable.nil? || disable.empty? || disable == "0")
10
+ ARGV.unshift(command)
11
+ load bin_path
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ require "socket"
2
+ require "thread"
3
+
4
+ require "spring/configuration"
5
+ require "spring/env"
6
+ require "spring/process_title_updater"
7
+ require "spring/json"
8
+ require "spring/watcher"
9
+ require "spring/failsafe_thread"
10
+
@@ -0,0 +1,48 @@
1
+ require "spring/errors"
2
+ require "spring/json"
3
+
4
+ require "spring/client/command"
5
+ require "spring/client/run"
6
+ require "spring/client/help"
7
+ require "spring/client/binstub"
8
+ require "spring/client/stop"
9
+ require "spring/client/status"
10
+ require "spring/client/rails"
11
+ require "spring/client/version"
12
+ require "spring/client/server"
13
+
14
+ module Spring
15
+ module Client
16
+ COMMANDS = {
17
+ "help" => Client::Help,
18
+ "-h" => Client::Help,
19
+ "--help" => Client::Help,
20
+ "binstub" => Client::Binstub,
21
+ "stop" => Client::Stop,
22
+ "status" => Client::Status,
23
+ "rails" => Client::Rails,
24
+ "-v" => Client::Version,
25
+ "--version" => Client::Version,
26
+ "server" => Client::Server,
27
+ }
28
+
29
+ def self.run(args)
30
+ command_for(args.first).call(args)
31
+ rescue CommandNotFound
32
+ Client::Help.call(args)
33
+ rescue ClientError => e
34
+ $stderr.puts e.message
35
+ exit 1
36
+ end
37
+
38
+ def self.command_for(name)
39
+ COMMANDS[name] || Client::Run
40
+ end
41
+ end
42
+ end
43
+
44
+ # allow users to add hooks that do not run in the server
45
+ # or modify start/stop
46
+ if File.exist?("config/spring_client.rb")
47
+ require "./config/spring_client.rb"
48
+ end
@@ -0,0 +1,197 @@
1
+ require 'set'
2
+
3
+ module Spring
4
+ module Client
5
+ class Binstub < Command
6
+ SHEBANG = /\#\!.*\n(\#.*\n)*/
7
+
8
+ # If loading the bin/spring file works, it'll run Spring which will
9
+ # eventually call Kernel.exit. This means that in the client process
10
+ # we will never execute the lines after this block. But if the Spring
11
+ # client is not invoked for whatever reason, then the Kernel.exit won't
12
+ # happen, and so we'll fall back to the lines after this block, which
13
+ # should cause the "unsprung" version of the command to run.
14
+ LOADER = <<CODE
15
+ begin
16
+ load File.expand_path('../spring', __FILE__)
17
+ rescue LoadError => e
18
+ raise unless e.message.include?('spring')
19
+ end
20
+ CODE
21
+
22
+ # The defined? check ensures these lines don't execute when we load the
23
+ # binstub from the application process. Which means that in the application
24
+ # process we'll execute the lines which come after the LOADER block, which
25
+ # is what we want.
26
+ SPRING = <<'CODE'
27
+ #!/usr/bin/env ruby
28
+
29
+ # This file loads Spring without using Bundler, in order to be fast.
30
+ # It gets overwritten when you run the `spring binstub` command.
31
+
32
+ unless defined?(Spring)
33
+ require 'rubygems'
34
+ require 'bundler'
35
+
36
+ lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
37
+ spring = lockfile.specs.detect { |spec| spec.name == 'spring' }
38
+ if spring
39
+ Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
40
+ gem 'spring', spring.version
41
+ require 'spring/binstub'
42
+ end
43
+ end
44
+ CODE
45
+
46
+ OLD_BINSTUB = %{if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?}
47
+
48
+ BINSTUB_VARIATIONS = Regexp.union [
49
+ %{begin\n load File.expand_path('../spring', __FILE__)\nrescue LoadError\nend\n},
50
+ %{begin\n spring_bin_path = File.expand_path('../spring', __FILE__)\n load spring_bin_path\nrescue LoadError => e\n raise unless e.message.end_with? spring_bin_path, 'spring/binstub'\nend\n},
51
+ LOADER
52
+ ].map { |binstub| /#{Regexp.escape(binstub).gsub("'", "['\"]")}/ }
53
+
54
+ class Item
55
+ attr_reader :command, :existing
56
+
57
+ def initialize(command)
58
+ @command = command
59
+
60
+ if command.binstub.exist?
61
+ @existing = command.binstub.read
62
+ elsif command.name == "rails"
63
+ scriptfile = Spring.application_root_path.join("script/rails")
64
+ @existing = scriptfile.read if scriptfile.exist?
65
+ end
66
+ end
67
+
68
+ def status(text, stream = $stdout)
69
+ stream.puts "* #{command.binstub_name}: #{text}"
70
+ end
71
+
72
+ def add
73
+ if existing
74
+ if existing.include?(OLD_BINSTUB)
75
+ fallback = existing.match(/#{Regexp.escape OLD_BINSTUB}\n(.*)else/m)[1]
76
+ fallback.gsub!(/^ /, "")
77
+ fallback = nil if fallback.include?("exec")
78
+ generate(fallback)
79
+ status "upgraded"
80
+ elsif existing.include?(LOADER)
81
+ status "Spring already present"
82
+ elsif existing =~ BINSTUB_VARIATIONS
83
+ upgraded = existing.sub(BINSTUB_VARIATIONS, LOADER)
84
+ File.write(command.binstub, upgraded)
85
+ status "upgraded"
86
+ else
87
+ head, shebang, tail = existing.partition(SHEBANG)
88
+
89
+ if shebang.include?("ruby")
90
+ unless command.binstub.exist?
91
+ FileUtils.touch command.binstub
92
+ command.binstub.chmod 0755
93
+ end
94
+
95
+ File.write(command.binstub, "#{head}#{shebang}#{LOADER}#{tail}")
96
+ status "Spring inserted"
97
+ else
98
+ status "doesn't appear to be ruby, so cannot use Spring", $stderr
99
+ exit 1
100
+ end
101
+ end
102
+ else
103
+ generate
104
+ status "generated with Spring"
105
+ end
106
+ end
107
+
108
+ def generate(fallback = nil)
109
+ unless fallback
110
+ fallback = "require 'bundler/setup'\n" \
111
+ "load Gem.bin_path('#{command.gem_name}', '#{command.exec_name}')\n"
112
+ end
113
+
114
+ File.write(command.binstub, "#!/usr/bin/env ruby\n#{LOADER}#{fallback}")
115
+ command.binstub.chmod 0755
116
+ end
117
+
118
+ def remove
119
+ if existing
120
+ File.write(command.binstub, existing.sub(BINSTUB_VARIATIONS, ""))
121
+ status "Spring removed"
122
+ end
123
+ end
124
+ end
125
+
126
+ attr_reader :bindir, :items
127
+
128
+ def self.description
129
+ "Generate Spring based binstubs. Use --all to generate a binstub for all known commands. Use --remove to revert."
130
+ end
131
+
132
+ def self.rails_command
133
+ @rails_command ||= CommandWrapper.new("rails")
134
+ end
135
+
136
+ def self.call(args)
137
+ require "spring/commands"
138
+ super
139
+ end
140
+
141
+ def initialize(args)
142
+ super
143
+
144
+ @bindir = env.root.join("bin")
145
+ @all = false
146
+ @mode = :add
147
+ @items = args.drop(1)
148
+ .map { |name| find_commands name }
149
+ .inject(Set.new, :|)
150
+ .map { |command| Item.new(command) }
151
+ end
152
+
153
+ def find_commands(name)
154
+ case name
155
+ when "--all"
156
+ @all = true
157
+ commands = Spring.commands.dup
158
+ commands.delete_if { |command_name, _| command_name.start_with?("rails_") }
159
+ commands.values + [self.class.rails_command]
160
+ when "--remove"
161
+ @mode = :remove
162
+ []
163
+ when "rails"
164
+ [self.class.rails_command]
165
+ else
166
+ if command = Spring.commands[name]
167
+ [command]
168
+ else
169
+ $stderr.puts "The '#{name}' command is not known to spring."
170
+ exit 1
171
+ end
172
+ end
173
+ end
174
+
175
+ def call
176
+ case @mode
177
+ when :add
178
+ bindir.mkdir unless bindir.exist?
179
+
180
+ File.write(spring_binstub, SPRING)
181
+ spring_binstub.chmod 0755
182
+
183
+ items.each(&:add)
184
+ when :remove
185
+ spring_binstub.delete if @all
186
+ items.each(&:remove)
187
+ else
188
+ raise ArgumentError
189
+ end
190
+ end
191
+
192
+ def spring_binstub
193
+ bindir.join("spring")
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,18 @@
1
+ require "spring/env"
2
+
3
+ module Spring
4
+ module Client
5
+ class Command
6
+ def self.call(args)
7
+ new(args).call
8
+ end
9
+
10
+ attr_reader :args, :env
11
+
12
+ def initialize(args)
13
+ @args = args
14
+ @env = Env.new
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ require "spring/version"
2
+
3
+ module Spring
4
+ module Client
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 self.call(args)
13
+ require "spring/commands"
14
+ super
15
+ end
16
+
17
+ def initialize(args, spring_commands = nil, application_commands = nil)
18
+ super args
19
+
20
+ @spring_commands = spring_commands || Spring::Client::COMMANDS.dup
21
+ @application_commands = application_commands || Spring.commands.dup
22
+
23
+ @spring_commands.delete_if { |k, v| k.start_with?("-") }
24
+
25
+ @application_commands["rails"] = @spring_commands.delete("rails")
26
+ end
27
+
28
+ def call
29
+ puts formatted_help
30
+ end
31
+
32
+ def formatted_help
33
+ ["Version: #{env.version}\n",
34
+ "Usage: spring COMMAND [ARGS]\n",
35
+ *command_help("Spring itself", spring_commands),
36
+ '',
37
+ *command_help("your application", application_commands)].join("\n")
38
+ end
39
+
40
+ def command_help(subject, commands)
41
+ ["Commands for #{subject}:\n",
42
+ *commands.sort_by(&:first).map { |name, command| display(name, command) }.compact]
43
+ end
44
+
45
+ private
46
+
47
+ def all_commands
48
+ spring_commands.merge application_commands
49
+ end
50
+
51
+ def display(name, command)
52
+ if command.description
53
+ " #{name.ljust(max_name_width)} #{command.description}"
54
+ end
55
+ end
56
+
57
+ def max_name_width
58
+ @max_name_width ||= all_commands.keys.map(&:length).max
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ require "set"
2
+
3
+ module Spring
4
+ module Client
5
+ class Rails < Command
6
+ COMMANDS = Set.new %w(console runner generate destroy test)
7
+
8
+ ALIASES = {
9
+ "c" => "console",
10
+ "r" => "runner",
11
+ "g" => "generate",
12
+ "d" => "destroy",
13
+ "t" => "test"
14
+ }
15
+
16
+ def self.description
17
+ "Run a rails command. The following sub commands will use Spring: #{COMMANDS.to_a.join ', '}."
18
+ end
19
+
20
+ def call
21
+ command_name = ALIASES[args[1]] || args[1]
22
+
23
+ if COMMANDS.include?(command_name)
24
+ Run.call(["rails_#{command_name}", *args.drop(2)])
25
+ else
26
+ require "spring/configuration"
27
+ ARGV.shift
28
+ load Dir.glob(Spring.application_root_path.join("{bin,script}/rails")).first
29
+ exit
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,232 @@
1
+ require "rbconfig"
2
+ require "socket"
3
+ require "bundler"
4
+
5
+ module Spring
6
+ module Client
7
+ class Run < Command
8
+ FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO WINCH) & Signal.list.keys
9
+ CONNECT_TIMEOUT = 1
10
+ BOOT_TIMEOUT = 20
11
+
12
+ attr_reader :server
13
+
14
+ def initialize(args)
15
+ super
16
+
17
+ @signal_queue = []
18
+ @server_booted = false
19
+ end
20
+
21
+ def log(message)
22
+ env.log "[client] #{message}"
23
+ end
24
+
25
+ def connect
26
+ @server = UNIXSocket.open(env.socket_name)
27
+ end
28
+
29
+ def call
30
+ begin
31
+ connect
32
+ rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED
33
+ cold_run
34
+ else
35
+ warm_run
36
+ end
37
+ ensure
38
+ server.close if server
39
+ end
40
+
41
+ def warm_run
42
+ run
43
+ rescue CommandNotFound
44
+ require "spring/commands"
45
+
46
+ if Spring.command?(args.first)
47
+ # Command installed since Spring started
48
+ stop_server
49
+ cold_run
50
+ else
51
+ raise
52
+ end
53
+ end
54
+
55
+ def cold_run
56
+ boot_server
57
+ connect
58
+ run
59
+ end
60
+
61
+ def run
62
+ verify_server_version
63
+
64
+ application, client = UNIXSocket.pair
65
+
66
+ queue_signals
67
+ connect_to_application(client)
68
+ run_command(client, application)
69
+ rescue Errno::ECONNRESET
70
+ exit 1
71
+ end
72
+
73
+ def boot_server
74
+ env.socket_path.unlink if env.socket_path.exist?
75
+
76
+ pid = Process.spawn(gem_env, env.server_command, out: File::NULL)
77
+ timeout = Time.now + BOOT_TIMEOUT
78
+
79
+ @server_booted = true
80
+
81
+ until env.socket_path.exist?
82
+ _, status = Process.waitpid2(pid, Process::WNOHANG)
83
+
84
+ if status
85
+ exit status.exitstatus
86
+ elsif Time.now > timeout
87
+ $stderr.puts "Starting Spring server with `#{env.server_command}` " \
88
+ "timed out after #{BOOT_TIMEOUT} seconds"
89
+ exit 1
90
+ end
91
+
92
+ sleep 0.1
93
+ end
94
+ end
95
+
96
+ def server_booted?
97
+ @server_booted
98
+ end
99
+
100
+ def gem_env
101
+ bundle = Bundler.bundle_path.to_s
102
+ paths = Gem.path + ENV["GEM_PATH"].to_s.split(File::PATH_SEPARATOR)
103
+
104
+ {
105
+ "GEM_PATH" => [bundle, *paths].uniq.join(File::PATH_SEPARATOR),
106
+ "GEM_HOME" => bundle
107
+ }
108
+ end
109
+
110
+ def stop_server
111
+ server.close
112
+ @server = nil
113
+ env.stop
114
+ end
115
+
116
+ def verify_server_version
117
+ server_version = server.gets.chomp
118
+ if server_version != env.version
119
+ $stderr.puts "There is a version mismatch between the Spring client " \
120
+ "(#{env.version}) and the server (#{server_version})."
121
+
122
+ if server_booted?
123
+ $stderr.puts "We already tried to reboot the server, but the mismatch is still present."
124
+ exit 1
125
+ else
126
+ $stderr.puts "Restarting to resolve."
127
+ stop_server
128
+ cold_run
129
+ end
130
+ end
131
+ end
132
+
133
+ def connect_to_application(client)
134
+ server.send_io client
135
+ send_json server, "args" => args, "default_rails_env" => default_rails_env
136
+
137
+ if IO.select([server], [], [], CONNECT_TIMEOUT)
138
+ server.gets or raise CommandNotFound
139
+ else
140
+ raise "Error connecting to Spring server"
141
+ end
142
+ end
143
+
144
+ def run_command(client, application)
145
+ log "sending command"
146
+
147
+ application.send_io STDOUT
148
+ application.send_io STDERR
149
+ application.send_io STDIN
150
+
151
+ send_json application, "args" => args, "env" => ENV.to_hash
152
+
153
+ pid = server.gets
154
+ pid = pid.chomp if pid
155
+
156
+ # We must not close the client socket until we are sure that the application has
157
+ # received the FD. Otherwise the FD can end up getting closed while it's in the server
158
+ # socket buffer on OS X. This doesn't happen on Linux.
159
+ client.close
160
+
161
+ if pid && !pid.empty?
162
+ log "got pid: #{pid}"
163
+
164
+ suspend_resume_on_tstp_cont(pid)
165
+
166
+ forward_signals(application)
167
+ status = application.read.to_i
168
+
169
+ log "got exit status #{status}"
170
+
171
+ exit status
172
+ else
173
+ log "got no pid"
174
+ exit 1
175
+ end
176
+ ensure
177
+ application.close
178
+ end
179
+
180
+ def queue_signals
181
+ FORWARDED_SIGNALS.each do |sig|
182
+ trap(sig) { @signal_queue << sig }
183
+ end
184
+ end
185
+
186
+ def suspend_resume_on_tstp_cont(pid)
187
+ trap("TSTP") {
188
+ log "suspended"
189
+ Process.kill("STOP", pid.to_i)
190
+ Process.kill("STOP", Process.pid)
191
+ }
192
+ trap("CONT") {
193
+ log "resumed"
194
+ Process.kill("CONT", pid.to_i)
195
+ }
196
+ end
197
+
198
+ def forward_signals(application)
199
+ @signal_queue.each { |sig| kill sig, application }
200
+
201
+ FORWARDED_SIGNALS.each do |sig|
202
+ trap(sig) { forward_signal sig, application }
203
+ end
204
+ end
205
+
206
+ def forward_signal(sig, application)
207
+ if kill(sig, application) != 0
208
+ # If the application process is gone, then don't block the
209
+ # signal on this process.
210
+ trap(sig, 'DEFAULT')
211
+ Process.kill(sig, Process.pid)
212
+ end
213
+ end
214
+
215
+ def kill(sig, application)
216
+ application.puts(sig)
217
+ application.gets.to_i
218
+ end
219
+
220
+ def send_json(socket, data)
221
+ data = JSON.dump(data)
222
+
223
+ socket.puts data.bytesize
224
+ socket.write data
225
+ end
226
+
227
+ def default_rails_env
228
+ ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
229
+ end
230
+ end
231
+ end
232
+ end