spring 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ ## 0.0.8
2
+
3
+ * Renamed `spring test` to `spring testunit`.
4
+ * Implemented `spring rails` to replace `spring
5
+ [console|runner|generate]`.
6
+ * `config/spring.rb` is only loaded in the server process, so you can
7
+ require stuff from other gems there without performance implications.
8
+ * File watcher no longer pays attention to files outside of your
9
+ application root directory.
10
+ * You can use the `listen` gem for less CPU intensive file watching. See
11
+ README.
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in spring.gemspec
4
4
  gemspec
5
+
6
+ gem 'listen', :require => false
7
+ gem 'rb-inotify', :require => false
8
+ gem 'rb-fsevent', :require => false
data/README.md CHANGED
@@ -67,7 +67,7 @@ cd /path/to/spring/test/apps/rails-3-2
67
67
  We can run a test:
68
68
 
69
69
  ```
70
- $ time spring test test/functional/posts_controller_test.rb
70
+ $ time spring testunit test/functional/posts_controller_test.rb
71
71
  Run options:
72
72
 
73
73
  # Running tests:
@@ -100,7 +100,7 @@ the processes will be killed automatically.
100
100
  Running the test is faster next time:
101
101
 
102
102
  ```
103
- $ time spring test test/functional/posts_controller_test.rb
103
+ $ time spring testunit test/functional/posts_controller_test.rb
104
104
  Run options:
105
105
 
106
106
  # Running tests:
@@ -116,25 +116,25 @@ user 0m0.276s
116
116
  sys 0m0.059s
117
117
  ```
118
118
 
119
- Running `spring test`, `spring rake`, `spring console`, etc gets a bit
119
+ Running `spring testunit`, `spring rake`, `spring rails`, etc gets a bit
120
120
  tedious. It also suffers from a performance issue in Rubygems ([which I
121
121
  am actively working on](https://github.com/rubygems/rubygems/pull/435))
122
122
  which means the `spring` command takes a while to start up. The more
123
123
  gems you have, the longer it takes.
124
124
 
125
125
  Spring binstubs solve both of these problems. If you will be running the
126
- `test` command regularly, run:
126
+ `testunit` command regularly, run:
127
127
 
128
128
  ```
129
- $ spring binstub test
129
+ $ spring binstub testunit
130
130
  ```
131
131
 
132
- This generates a `bin/spring` and a `bin/test`, which allows you to run
133
- `spring` and `spring test` in a way that doesn't trigger the Rubygems
132
+ This generates a `bin/spring` and a `bin/testunit`, which allows you to run
133
+ `spring` and `spring testunit` in a way that doesn't trigger the Rubygems
134
134
  performance bug:
135
135
 
136
136
  ```
137
- $ time bin/test test/functional/posts_controller_test.rb
137
+ $ time bin/testunit test/functional/posts_controller_test.rb
138
138
  Run options:
139
139
 
140
140
  # Running tests:
@@ -150,6 +150,10 @@ user 0m0.077s
150
150
  sys 0m0.059s
151
151
  ```
152
152
 
153
+ You can add "./bin" to your `PATH` when in your application's directory
154
+ [with direnv](https://github.com/zimbatm/direnv), but you should
155
+ recognise and understand the security implications of using that.
156
+
153
157
  If we edit any of the application files, or test files, the change will
154
158
  be picked up on the next run, without the background process
155
159
  having to be restarted.
@@ -220,12 +224,12 @@ order to make it easy for people to try spring. However in the future
220
224
  the code to use a specific test framework should not be contained in the
221
225
  spring repository.
222
226
 
223
- ### `test`
227
+ ### `testunit`
224
228
 
225
229
  Runs a test (e.g. Test::Unit, MiniTest::Unit, etc.) Preloads the `test_helper` file.
226
230
 
227
231
  This command can also recursively run a directory of tests. For example,
228
- `spring test test/functional` will run `test/functional/**/*_test.rb`.
232
+ `spring testunit test/functional` will run `test/functional/**/*_test.rb`.
229
233
 
230
234
  ### `rspec`
231
235
 
@@ -240,19 +244,12 @@ Runs a cucumber feature.
240
244
 
241
245
  Runs a rake task.
242
246
 
243
- ### `console`
244
-
245
- Boots into the Rails console. Currently this is usable but not perfect,
246
- for example you can't scroll back through command history. (That will be
247
- fixed.)
248
-
249
- ### `generate`
250
-
251
- Runs a Rails generator.
252
-
253
- ### `runner`
247
+ ### `rails console`, `rails generate`, `rails runner`
254
248
 
255
- The Rails runner.
249
+ These execute the rails command you already know and love. If you run
250
+ a different sub command (e.g. `rails server`) then spring will automatically
251
+ pass it through to the underlying `rails` executable (without the
252
+ speed-up).
256
253
 
257
254
  ## Configuration
258
255
 
@@ -279,13 +276,13 @@ preloads for every command:
279
276
 
280
277
  ```ruby
281
278
  # if your test helper is called "helper"
282
- Commands::Command::Test.preloads = %w(helper)
279
+ Commands::Command::TestUnit.preloads = %w(helper)
283
280
 
284
281
  # if you don't want to preload spec_helper.rb
285
282
  Commands::Command::RSpec.preloads = []
286
283
 
287
284
  # if you want to preload additional files for the console
288
- Commands::Command::Console.preloads << 'extenstions/console_helper'
285
+ Commands::Command::RailsConsole.preloads << 'extenstions/console_helper'
289
286
  ```
290
287
 
291
288
  ### after fork callbacks
@@ -308,3 +305,31 @@ If you want to register multiple callbacks you can simply call
308
305
 
309
306
  Spring needs a tmp directory. This will default to `Rails.root.join('tmp', 'spring')`.
310
307
  You can set your own configuration directory by setting the `SPRING_TMP_PATH` environment variable.
308
+
309
+ ### Watching files and directories
310
+
311
+ As mentioned above, Spring will automatically detect file changes to any file loaded when the server
312
+ boots. If you would like to watch additional files or directories, use
313
+ `Spring.watch`:
314
+
315
+ ```ruby
316
+ Spring.watch "#{Rails.root}/spec/factories"
317
+ ```
318
+
319
+ ### Filesystem polling
320
+
321
+ By default Spring will check the filesystem for changes once every 0.2 seconds. This
322
+ method requires zero configuration, but if you find that it's using too
323
+ much CPU, then you can turn on event-based file system listening by
324
+ adding the following to to your `Gemfile`:
325
+
326
+ ```ruby
327
+ group :development, :test do
328
+ gem 'listen'
329
+ gem 'rb-inotify', :require => false # linux
330
+ gem 'rb-fsevent', :require => false # mac os x
331
+ gem 'rb-kqueue', :require => false # bsd
332
+ end
333
+ ```
334
+
335
+ Note that this make the initial application startup slightly slower.
@@ -1,24 +1,21 @@
1
- require "spring/configuration"
2
- require "spring/application_watcher"
3
- require "spring/commands"
4
1
  require "set"
2
+ require "json"
5
3
 
6
- module Spring
7
- class << self
8
- attr_accessor :application_watcher
9
- end
10
-
11
- self.application_watcher = ApplicationWatcher.new
4
+ require "spring/configuration"
5
+ require "spring/watcher"
12
6
 
7
+ module Spring
13
8
  class Application
14
- WATCH_INTERVAL = 0.2
15
-
16
9
  attr_reader :manager, :watcher
17
10
 
18
- def initialize(manager, watcher = Spring.application_watcher)
11
+ def initialize(manager, watcher = Spring.watcher)
19
12
  @manager = manager
20
13
  @watcher = watcher
21
14
  @setup = Set.new
15
+
16
+ # Workaround for GC bug in Ruby 2 which causes segfaults if watcher.to_io
17
+ # instances get dereffed.
18
+ @fds = [manager, watcher.to_io]
22
19
  end
23
20
 
24
21
  def start
@@ -33,31 +30,33 @@ module Spring
33
30
 
34
31
  require Spring.application_root_path.join("config", "environment")
35
32
 
36
- watcher.add_files $LOADED_FEATURES
37
- watcher.add_files ["Gemfile", "Gemfile.lock"].map { |f| "#{Rails.root}/#{f}" }
38
- watcher.add_globs Rails.application.paths["config/initializers"].map { |p| "#{Rails.root}/#{p}/*.rb" }
33
+ watcher.add loaded_application_features
34
+ watcher.add "Gemfile", "Gemfile.lock"
35
+ watcher.add Rails.application.paths["config/initializers"]
39
36
 
40
37
  run
41
38
  end
42
39
 
43
40
  def run
41
+ watcher.start
42
+
44
43
  loop do
45
- watch_application
46
- serve manager.recv_io(UNIXSocket)
47
- end
48
- end
44
+ IO.select(@fds)
49
45
 
50
- def watch_application
51
- until IO.select([manager], [], [], WATCH_INTERVAL)
52
- exit if watcher.stale?
46
+ if watcher.stale?
47
+ exit
48
+ else
49
+ serve manager.recv_io(UNIXSocket)
50
+ end
53
51
  end
54
52
  end
55
53
 
56
54
  def serve(client)
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)
55
+ manager.puts
56
+
57
+ streams = 3.times.map { client.recv_io }
58
+ args = JSON.parse(client.read(client.gets.to_i))
59
+ command = Spring.command(args.shift)
61
60
 
62
61
  setup command
63
62
 
@@ -92,16 +91,13 @@ module Spring
92
91
  # The command might need to require some files in the
93
92
  # main process so that they are cached. For example a test command wants to
94
93
  # load the helper file once and have it cached.
95
- #
96
- # FIXME: The watcher.add_files will reset the watcher, which may mean that
97
- # previous changes to already-loaded files are missed.
98
94
  def setup(command)
99
95
  return if @setup.include?(command.class)
100
96
  @setup << command.class
101
97
 
102
98
  if command.respond_to?(:setup)
103
99
  command.setup
104
- watcher.add_files $LOADED_FEATURES # loaded features may have changed
100
+ watcher.add loaded_application_features # loaded features may have changed
105
101
  end
106
102
  end
107
103
 
@@ -110,5 +106,9 @@ module Spring
110
106
  callback.call
111
107
  end
112
108
  end
109
+
110
+ def loaded_application_features
111
+ $LOADED_FEATURES.select { |f| f.start_with?(File.realpath(Rails.root)) }
112
+ end
113
113
  end
114
114
  end
@@ -1,11 +1,9 @@
1
1
  require "socket"
2
+ require "thread"
2
3
  require "spring/application"
3
- require "mutex_m"
4
4
 
5
5
  module Spring
6
6
  class ApplicationManager
7
- include Mutex_m
8
-
9
7
  attr_reader :pid, :child, :app_env, :spring_env
10
8
 
11
9
  def initialize(app_env)
@@ -13,6 +11,16 @@ module Spring
13
11
 
14
12
  @app_env = app_env
15
13
  @spring_env = Env.new
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ # We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
18
+ # line which messes with backtraces in e.g. rspec
19
+ def synchronize
20
+ @mutex.lock
21
+ yield
22
+ ensure
23
+ @mutex.unlock
16
24
  end
17
25
 
18
26
  def start
@@ -31,31 +39,36 @@ module Spring
31
39
  @pid
32
40
  end
33
41
 
34
- # Returns the pid of the process running the command, or nil if the application process died.
35
- def run(client)
36
- @client = client
37
-
42
+ def with_child
38
43
  synchronize do
39
44
  if alive?
40
45
  begin
41
- child.send_io @client
42
- rescue Errno::EPIPE
43
- # EPIPE indicates child has died but has not been collected by the wait thread yet
46
+ yield
47
+ rescue Errno::ECONNRESET, Errno::EPIPE
48
+ # The child has died but has not been collected by the wait thread yet,
49
+ # so start a new child and try again.
44
50
  start
45
- child.send_io @client
51
+ yield
46
52
  end
47
53
  else
48
54
  start
49
- child.send_io @client
55
+ yield
50
56
  end
51
57
  end
58
+ end
59
+
60
+ # Returns the pid of the process running the command, or nil if the application process died.
61
+ def run(client)
62
+ with_child do
63
+ child.send_io client
64
+ child.gets
65
+ end
52
66
 
53
67
  child.gets.chomp.to_i # get the pid
54
68
  rescue Errno::ECONNRESET, Errno::EPIPE
55
69
  nil
56
70
  ensure
57
- @client.close
58
- @client = nil
71
+ client.close
59
72
  end
60
73
 
61
74
  def stop
@@ -68,11 +81,17 @@ module Spring
68
81
  @child, child_socket = UNIXSocket.pair
69
82
  @pid = fork {
70
83
  [STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
71
- @client.close if @client
84
+
85
+ (ObjectSpace.each_object(IO).to_a - [STDOUT, STDERR, STDIN, child_socket])
86
+ .reject(&:closed?)
87
+ .each(&:close)
88
+
72
89
  ENV['RAILS_ENV'] = ENV['RACK_ENV'] = app_env
90
+
73
91
  ProcessTitleUpdater.run { |distance|
74
92
  "spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
75
93
  }
94
+
76
95
  Application.new(child_socket).start
77
96
  }
78
97
  child_socket.close
data/lib/spring/client.rb CHANGED
@@ -3,31 +3,33 @@ require "spring/client/command"
3
3
  require "spring/client/run"
4
4
  require "spring/client/help"
5
5
  require "spring/client/binstub"
6
+ require "spring/client/start"
6
7
  require "spring/client/stop"
7
8
  require "spring/client/status"
9
+ require "spring/client/rails"
8
10
 
9
11
  module Spring
10
12
  module Client
11
13
  COMMANDS = {
12
14
  "help" => Client::Help,
13
15
  "binstub" => Client::Binstub,
16
+ "start" => Client::Start,
14
17
  "stop" => Client::Stop,
15
- "status" => Client::Status
18
+ "status" => Client::Status,
19
+ "rails" => Client::Rails
16
20
  }
17
21
 
18
22
  def self.run(args)
19
23
  command_for(args.first).call(args)
24
+ rescue CommandNotFound
25
+ Client::Help.call(args)
20
26
  rescue ClientError => e
21
- STDERR.puts e.message
27
+ $stderr.puts e.message
22
28
  exit 1
23
29
  end
24
30
 
25
31
  def self.command_for(name)
26
- if Spring.command?(name)
27
- Client::Run
28
- else
29
- COMMANDS.fetch(name) { Client::Help }
30
- end
32
+ COMMANDS[name] || Client::Run
31
33
  end
32
34
  end
33
35
  end
@@ -7,6 +7,11 @@ module Spring
7
7
  "Generate spring based binstubs."
8
8
  end
9
9
 
10
+ def self.call(args)
11
+ require "spring/commands"
12
+ super
13
+ end
14
+
10
15
  def initialize(args)
11
16
  super
12
17
 
@@ -15,12 +20,12 @@ module Spring
15
20
  end
16
21
 
17
22
  def call
18
- if Spring.command?(name)
23
+ if Spring.command?(name) || name == "rails"
19
24
  bindir.mkdir unless bindir.exist?
20
25
  generate_spring_binstub
21
26
  generate_command_binstub
22
27
  else
23
- STDERR.puts "The '#{name}' command is not known to spring."
28
+ $stderr.puts "The '#{name}' command is not known to spring."
24
29
  exit 1
25
30
  end
26
31
  end
@@ -50,7 +55,7 @@ if spec
50
55
  spec.activate
51
56
  load spec.bin_file("spring")
52
57
  else
53
- STDERR.puts "Could not find spring gem in #{Gem::Specification.dirs.join(", ")}."
58
+ $stderr.puts "Could not find spring gem in #{Gem::Specification.dirs.join(", ")}."
54
59
  exit 1
55
60
  end
56
61
  CODE