spring-jruby 1.4.3

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +364 -0
  4. data/bin/spring +49 -0
  5. data/lib/spring-jruby/application.rb +283 -0
  6. data/lib/spring-jruby/application/boot.rb +19 -0
  7. data/lib/spring-jruby/binstub.rb +13 -0
  8. data/lib/spring-jruby/boot.rb +9 -0
  9. data/lib/spring-jruby/client.rb +46 -0
  10. data/lib/spring-jruby/client/binstub.rb +188 -0
  11. data/lib/spring-jruby/client/command.rb +18 -0
  12. data/lib/spring-jruby/client/help.rb +62 -0
  13. data/lib/spring-jruby/client/rails.rb +34 -0
  14. data/lib/spring-jruby/client/run.rb +167 -0
  15. data/lib/spring-jruby/client/status.rb +30 -0
  16. data/lib/spring-jruby/client/stop.rb +22 -0
  17. data/lib/spring-jruby/client/version.rb +11 -0
  18. data/lib/spring-jruby/command_wrapper.rb +82 -0
  19. data/lib/spring-jruby/commands.rb +51 -0
  20. data/lib/spring-jruby/commands/rails.rb +112 -0
  21. data/lib/spring-jruby/commands/rake.rb +30 -0
  22. data/lib/spring-jruby/configuration.rb +60 -0
  23. data/lib/spring-jruby/env.rb +109 -0
  24. data/lib/spring-jruby/errors.rb +36 -0
  25. data/lib/spring-jruby/impl/application.rb +7 -0
  26. data/lib/spring-jruby/impl/application_manager.rb +7 -0
  27. data/lib/spring-jruby/impl/fork/application.rb +69 -0
  28. data/lib/spring-jruby/impl/fork/application_manager.rb +137 -0
  29. data/lib/spring-jruby/impl/fork/run.rb +47 -0
  30. data/lib/spring-jruby/impl/pool/application.rb +47 -0
  31. data/lib/spring-jruby/impl/pool/application_manager.rb +226 -0
  32. data/lib/spring-jruby/impl/pool/run.rb +27 -0
  33. data/lib/spring-jruby/impl/run.rb +7 -0
  34. data/lib/spring-jruby/io_helpers.rb +92 -0
  35. data/lib/spring-jruby/json.rb +626 -0
  36. data/lib/spring-jruby/platform.rb +23 -0
  37. data/lib/spring-jruby/process_title_updater.rb +65 -0
  38. data/lib/spring-jruby/server.rb +130 -0
  39. data/lib/spring-jruby/sid.rb +42 -0
  40. data/lib/spring-jruby/test.rb +18 -0
  41. data/lib/spring-jruby/test/acceptance_test.rb +371 -0
  42. data/lib/spring-jruby/test/application.rb +217 -0
  43. data/lib/spring-jruby/test/application_generator.rb +134 -0
  44. data/lib/spring-jruby/test/rails_version.rb +40 -0
  45. data/lib/spring-jruby/test/watcher_test.rb +167 -0
  46. data/lib/spring-jruby/version.rb +3 -0
  47. data/lib/spring-jruby/watcher.rb +30 -0
  48. data/lib/spring-jruby/watcher/abstract.rb +86 -0
  49. data/lib/spring-jruby/watcher/polling.rb +61 -0
  50. metadata +137 -0
@@ -0,0 +1,19 @@
1
+ require "spring-jruby/platform"
2
+ # This is necessary for the terminal to work correctly when we reopen stdin.
3
+ Process.setsid if Spring.fork?
4
+
5
+ require "spring-jruby/application"
6
+
7
+ app = Spring::Application.new(
8
+ Spring::WorkerChannel.remote_endpoint,
9
+ Spring::JSON.load(ENV.delete("SPRING_ORIGINAL_ENV").dup)
10
+ )
11
+
12
+ Signal.trap("TERM") { app.terminate }
13
+
14
+ Spring::ProcessTitleUpdater.run { |distance|
15
+ "spring app | #{app.app_name} | started #{distance} ago | #{app.app_env} mode"
16
+ }
17
+
18
+ app.eager_preload if ENV.delete("SPRING_PRELOAD") == "1"
19
+ app.run
@@ -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 disable.nil? || disable.empty? || disable == "0"
10
+ ARGV.unshift(command)
11
+ load bin_path
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ require "socket"
2
+ require "thread"
3
+
4
+ require "spring-jruby/configuration"
5
+ require "spring-jruby/env"
6
+ require "spring-jruby/process_title_updater"
7
+ require "spring-jruby/json"
8
+ require "spring-jruby/watcher"
9
+ require "spring-jruby/io_helpers"
@@ -0,0 +1,46 @@
1
+ require "spring-jruby/errors"
2
+ require "spring-jruby/json"
3
+
4
+ require "spring-jruby/client/command"
5
+ require "spring-jruby/client/run"
6
+ require "spring-jruby/client/help"
7
+ require "spring-jruby/client/binstub"
8
+ require "spring-jruby/client/stop"
9
+ require "spring-jruby/client/status"
10
+ require "spring-jruby/client/rails"
11
+ require "spring-jruby/client/version"
12
+
13
+ module Spring
14
+ module Client
15
+ COMMANDS = {
16
+ "help" => Client::Help,
17
+ "-h" => Client::Help,
18
+ "--help" => Client::Help,
19
+ "binstub" => Client::Binstub,
20
+ "stop" => Client::Stop,
21
+ "status" => Client::Status,
22
+ "rails" => Client::Rails,
23
+ "-v" => Client::Version,
24
+ "--version" => Client::Version,
25
+ }
26
+
27
+ def self.run(args)
28
+ command_for(args.first).call(args)
29
+ rescue CommandNotFound
30
+ Client::Help.call(args)
31
+ rescue ClientError => e
32
+ $stderr.puts e.message
33
+ exit 1
34
+ end
35
+
36
+ def self.command_for(name)
37
+ COMMANDS[name] || Client::Run
38
+ end
39
+ end
40
+ end
41
+
42
+ # allow users to add hooks that do not run in the server
43
+ # or modify start/stop
44
+ if File.exist?("config/spring_client.rb")
45
+ require "./config/spring_client.rb"
46
+ end
@@ -0,0 +1,188 @@
1
+ require 'set'
2
+
3
+ module Spring
4
+ module Client
5
+ class Binstub < Command
6
+ SHEBANG = /\#\!.*\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
18
+ end
19
+ CODE
20
+
21
+ # The defined? check ensures these lines don't execute when we load the
22
+ # binstub from the application process. Which means that in the application
23
+ # process we'll execute the lines which come after the LOADER block, which
24
+ # is what we want.
25
+ #
26
+ # Parsing the lockfile in this way is pretty nasty but reliable enough
27
+ # The regex ensures that the match must be between a GEM line and an empty
28
+ # line, so it won't go on to the next section.
29
+ SPRING = <<'CODE'
30
+ #!/usr/bin/env ruby
31
+
32
+ # This file loads spring without using Bundler, in order to be fast.
33
+ # It gets overwritten when you run the `spring binstub` command.
34
+
35
+ unless defined?(Spring)
36
+ require 'rubygems'
37
+ require 'bundler'
38
+
39
+ if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring-jruby \((.*?)\)$.*?^$/m))
40
+ Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq }
41
+ gem 'spring-jruby', match[1]
42
+ require 'spring-jruby/binstub'
43
+ end
44
+ end
45
+ CODE
46
+
47
+ OLD_BINSTUB = %{if Gem::Specification.find_all_by_name("spring-jruby").empty?}
48
+
49
+ class Item
50
+ attr_reader :command, :existing
51
+
52
+ def initialize(command)
53
+ @command = command
54
+
55
+ if command.binstub.exist?
56
+ @existing = command.binstub.read
57
+ elsif command.name == "rails"
58
+ scriptfile = Spring.application_root_path.join("script/rails")
59
+ @existing = scriptfile.read if scriptfile.exist?
60
+ end
61
+ end
62
+
63
+ def status(text, stream = $stdout)
64
+ stream.puts "* #{command.binstub_name}: #{text}"
65
+ end
66
+
67
+ def add
68
+ if existing
69
+ if existing.include?(OLD_BINSTUB)
70
+ fallback = existing.match(/#{Regexp.escape OLD_BINSTUB}\n(.*)else/m)[1]
71
+ fallback.gsub!(/^ /, "")
72
+ fallback = nil if fallback.include?("exec")
73
+ generate(fallback)
74
+ status "upgraded"
75
+ elsif existing =~ /load .*spring-jruby/
76
+ status "spring already present"
77
+ else
78
+ head, shebang, tail = existing.partition(SHEBANG)
79
+
80
+ if shebang.include?("ruby")
81
+ unless command.binstub.exist?
82
+ FileUtils.touch command.binstub
83
+ command.binstub.chmod 0755
84
+ end
85
+
86
+ File.write(command.binstub, "#{head}#{shebang}#{LOADER}#{tail}")
87
+ status "spring inserted"
88
+ else
89
+ status "doesn't appear to be ruby, so cannot use spring", $stderr
90
+ exit 1
91
+ end
92
+ end
93
+ else
94
+ generate
95
+ status "generated with spring"
96
+ end
97
+ end
98
+
99
+ def generate(fallback = nil)
100
+ unless fallback
101
+ fallback = "require 'bundler/setup'\n" \
102
+ "load Gem.bin_path('#{command.gem_name}', '#{command.exec_name}')\n"
103
+ end
104
+
105
+ File.write(command.binstub, "#!/usr/bin/env ruby\n#{LOADER}#{fallback}")
106
+ command.binstub.chmod 0755
107
+ end
108
+
109
+ def remove
110
+ if existing
111
+ File.write(command.binstub, existing.sub(LOADER, ""))
112
+ status "spring removed"
113
+ end
114
+ end
115
+ end
116
+
117
+ attr_reader :bindir, :items
118
+
119
+ def self.description
120
+ "Generate spring based binstubs. Use --all to generate a binstub for all known commands."
121
+ end
122
+
123
+ def self.rails_command
124
+ @rails_command ||= CommandWrapper.new("rails")
125
+ end
126
+
127
+ def self.call(args)
128
+ require "spring-jruby/commands"
129
+ super
130
+ end
131
+
132
+ def initialize(args)
133
+ super
134
+
135
+ @bindir = env.root.join("bin")
136
+ @all = false
137
+ @mode = :add
138
+ @items = args.drop(1)
139
+ .map { |name| find_commands name }
140
+ .inject(Set.new, :|)
141
+ .map { |command| Item.new(command) }
142
+ end
143
+
144
+ def find_commands(name)
145
+ case name
146
+ when "--all"
147
+ @all = true
148
+ commands = Spring.commands.dup
149
+ commands.delete_if { |command_name, _| command_name.start_with?("rails_") }
150
+ commands.values + [self.class.rails_command]
151
+ when "--remove"
152
+ @mode = :remove
153
+ []
154
+ when "rails"
155
+ [self.class.rails_command]
156
+ else
157
+ if command = Spring.commands[name]
158
+ [command]
159
+ else
160
+ $stderr.puts "The '#{name}' command is not known to spring."
161
+ exit 1
162
+ end
163
+ end
164
+ end
165
+
166
+ def call
167
+ case @mode
168
+ when :add
169
+ bindir.mkdir unless bindir.exist?
170
+
171
+ File.write(spring_binstub, SPRING)
172
+ spring_binstub.chmod 0755
173
+
174
+ items.each(&:add)
175
+ when :remove
176
+ spring_binstub.delete if @all
177
+ items.each(&:remove)
178
+ else
179
+ raise ArgumentError
180
+ end
181
+ end
182
+
183
+ def spring_binstub
184
+ bindir.join("spring")
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,18 @@
1
+ require "spring-jruby/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-jruby/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-jruby/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-jruby/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,167 @@
1
+ require "rbconfig"
2
+ require "socket"
3
+ require "bundler"
4
+ require "spring-jruby/io_helpers"
5
+ require "spring-jruby/impl/run"
6
+
7
+ module Spring
8
+ module Client
9
+ class Run < Command
10
+ include RunImpl
11
+ TIMEOUT = RunImpl::TIMEOUT
12
+
13
+ def initialize(args)
14
+ super
15
+ @signal_queue = []
16
+ end
17
+
18
+ def log(message)
19
+ env.log "[client] #{message}"
20
+ end
21
+
22
+ def server
23
+ @server ||= UNIXSocket.open(env.socket_name)
24
+ end
25
+
26
+ def call
27
+ if env.server_running?
28
+ warm_run
29
+ else
30
+ cold_run
31
+ end
32
+ rescue Errno::ECONNRESET
33
+ exit 1
34
+ ensure
35
+ server.close if @server
36
+ end
37
+
38
+ def warm_run
39
+ run
40
+ rescue CommandNotFound
41
+ require "spring-jruby/commands"
42
+
43
+ if Spring.command?(args.first)
44
+ # Command installed since spring started
45
+ stop_server
46
+ cold_run
47
+ else
48
+ raise
49
+ end
50
+ end
51
+
52
+ def cold_run
53
+ boot_server
54
+ run
55
+ end
56
+
57
+ def run
58
+ verify_server_version
59
+
60
+ application, client = WorkerChannel.pair
61
+
62
+ queue_signals
63
+ connect_to_application(client)
64
+ run_command(client, application.to_io)
65
+ end
66
+
67
+ def boot_server
68
+ env.socket_path.unlink if env.socket_path.exist?
69
+
70
+ pid = Process.spawn(
71
+ gem_env,
72
+ "ruby",
73
+ "-e", "gem 'spring-jruby', '#{Spring::VERSION}'; require 'spring-jruby/server'; Spring::Server.boot"
74
+ )
75
+
76
+ until env.socket_path.exist?
77
+ _, status = Process.waitpid2(pid, Process::WNOHANG)
78
+ exit status.exitstatus if status
79
+ sleep 0.1
80
+ end
81
+ end
82
+
83
+ def gem_env
84
+ bundle = Bundler.bundle_path.to_s
85
+ paths = Gem.path + ENV["GEM_PATH"].to_s.split(File::PATH_SEPARATOR)
86
+
87
+ {
88
+ "GEM_PATH" => [bundle, *paths].uniq.join(File::PATH_SEPARATOR),
89
+ "GEM_HOME" => bundle
90
+ }
91
+ end
92
+
93
+ def stop_server
94
+ server.close
95
+ @server = nil
96
+ env.stop
97
+ end
98
+
99
+ def verify_server_version
100
+ server_version = server.gets.chomp
101
+ if server_version != env.version
102
+ $stderr.puts <<-ERROR
103
+ There is a version mismatch between the spring client and the server.
104
+ You should restart the server and make sure to use the same version.
105
+
106
+ CLIENT: #{env.version}, SERVER: #{server_version}
107
+ ERROR
108
+ exit 1
109
+ end
110
+ end
111
+
112
+ def connect_to_application(client)
113
+ client.forward_to(server)
114
+ send_json server, "args" => args, "default_rails_env" => default_rails_env
115
+
116
+ if IO.select([server], [], [], TIMEOUT)
117
+ server.gets or raise CommandNotFound
118
+ else
119
+ raise "Error connecting to Spring server"
120
+ end
121
+ end
122
+
123
+ def run_command(client, application)
124
+ log "sending command"
125
+
126
+ send_std_io_to(application)
127
+
128
+ send_json application, "args" => args, "env" => ENV.to_hash
129
+
130
+ IO.select([server])
131
+ pid = server.gets
132
+ pid = pid.chomp if pid
133
+
134
+ # We must not close the client socket until we are sure that the application has
135
+ # received the FD. Otherwise the FD can end up getting closed while it's in the server
136
+ # socket buffer on OS X. This doesn't happen on Linux.
137
+ client.close
138
+
139
+ if pid && !pid.empty?
140
+ log "got pid: #{pid}"
141
+
142
+ run_on(application, pid)
143
+ else
144
+ log "got no pid"
145
+ exit 1
146
+ end
147
+ ensure
148
+ application.close
149
+ end
150
+
151
+ def kill(sig, pid)
152
+ Process.kill(sig, -Process.getpgid(pid))
153
+ end
154
+
155
+ def send_json(socket, data)
156
+ data = JSON.dump(data)
157
+
158
+ socket.puts data.bytesize
159
+ socket.write data
160
+ end
161
+
162
+ def default_rails_env
163
+ ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
164
+ end
165
+ end
166
+ end
167
+ end