spring 1.0.0 → 1.1.0.beta3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab103d29ee132ac8637c7ddca8d5c41fc567c31e
4
- data.tar.gz: 88e600c6e1d59b4150625769f20f6d543958087c
3
+ metadata.gz: ec57b11dfce83e9e7ca74433cd0e18e0e4b499f3
4
+ data.tar.gz: 61e4462c800903ab2d66710ccc92544a2a5f251b
5
5
  SHA512:
6
- metadata.gz: 7b4d762562b8d4c1832bcec43819b6bcc6d9168473095fac089a743c0c421d4450f77e7c0a3cf31902256ed42a3d0fa57e7767cd4a0cf9d0bd55dc24c758f460
7
- data.tar.gz: 9c929b2636980dc30486f475caeacb1f3de296420c22e9f0a52c67ce732a8953e28f1b090258a18456d210ce217610e8e4df8ffeb7a1a8ebd0e2b546554235ec
6
+ metadata.gz: 38741e9b52477feee7d28b28ea299438f46174f89e64ca16a5a34a9b673d723fd9b4830d9a216c958f3b5e2b8f66bc8bea2ca536fcfb498fb8c524ef508771ce
7
+ data.tar.gz: d0c5bdfcaacf59dae01f4701c2d974675a624485d10efbe6aab155055e773b696d332fc54ba4e24a285ce1127bc16a11f3323f6328d771c34e9fe0cb57898314
data/.travis.yml CHANGED
@@ -2,6 +2,8 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+ - 2.1.0
5
6
  env:
6
7
  - RAILS_VERSION="~> 3.2.0"
7
8
  - RAILS_VERSION="~> 4.0.0"
9
+ - RAILS_VERSION="~> 4.1.0.beta1"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## Next version
2
+
3
+ * A `bin/spring` binstub is now generated. This allows us to load spring
4
+ correctly if you have it installed locally with a `BUNDLE_PATH`, so
5
+ it's no longer necessary to install spring system-wide. We also
6
+ activate the correct version from your Gemfile.lock. Note that you
7
+ still can't have spring in your Gemfile as a git repository or local
8
+ path; it must be a proper gem.
9
+ * Various changes to how springified binstubs are implemented. Existing
10
+ binstubs will continue to work, but it's recommended to run `spring binstub`
11
+ again to upgrade them to the new format.
12
+ * `spring binstub --remove` option added for removing spring from
13
+ binstubs. This won't work unless you have upgraded your binstubs to
14
+ the new format.
15
+ * `config/database.yml` is watched
16
+ * Better application restarts - if you introduce an error, for example
17
+ by editing `config/application.rb`, spring will now continue to watch
18
+ your files and will immediately try to restart the application when
19
+ you edit `config/application.rb` again (hopefully to correct the error).
20
+ This means that by the time you come to run a command the application
21
+ may well already be running.
22
+ * Gemfile changes are now gracefully handled. Previously they would
23
+ cause spring to simply quit, meaning that you'd incur the full startup
24
+ penalty on the next run. Now spring doesn't quit, and will try to load
25
+ up your new bundle in the background.
26
+ * Fix support for using spring with Rails engines/plugins
27
+
1
28
  ## 1.0.0
2
29
 
3
30
  * Enterprise ready secret sauce added
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Spring
2
2
 
3
- [![Build Status](https://travis-ci.org/jonleighton/spring.png?branch=master)](https://travis-ci.org/jonleighton/spring)
3
+ [![Build Status](https://travis-ci.org/rails/spring.png?branch=master)](https://travis-ci.org/rails/spring)
4
+ [![Gem Version](https://badge.fury.io/rb/spring.png)](http://badge.fury.io/rb/spring)
4
5
 
5
6
  Spring is a Rails application preloader. It speeds up development by
6
7
  keeping your application running in the background so you don't need to
@@ -15,24 +16,50 @@ boot it every time you run a test, rake task or migration.
15
16
 
16
17
  ## Compatibility
17
18
 
18
- * Ruby versions: MRI 1.9.3, MRI 2.0.0
19
+ * Ruby versions: MRI 1.9.3, MRI 2.0, MRI 2.1
19
20
  * Rails versions: 3.2, 4.0
20
21
 
21
- Spring makes extensive use of `Process#fork`, so won't be able to
22
+ Spring makes extensive use of `Process.fork`, so won't be able to
22
23
  provide a speed up on platforms which don't support forking (Windows, JRuby).
23
24
 
24
25
  ## Walkthrough
25
26
 
26
- Either `gem install spring` or add it to your Gemfile:
27
+ ### Setup
28
+
29
+ Add spring to your Gemfile:
27
30
 
28
31
  ``` ruby
29
- group :development do
30
- gem "spring"
32
+ gem "spring", group: :development
33
+ ```
34
+
35
+ It's recommended to 'springify' the executables in your `bin/`
36
+ directory:
37
+
38
+ ```
39
+ $ bundle install
40
+ $ bundle exec spring binstub --all
41
+ ```
42
+
43
+ This generates a `bin/spring` executable, and inserts a small snippet of
44
+ code into relevant existing executables. The snippet looks like this:
45
+
46
+ ``` ruby
47
+ begin
48
+ load File.expand_path("../spring", __FILE__)
49
+ rescue LoadError
31
50
  end
32
51
  ```
33
52
 
34
- Spring is designed to be used *without* bundle exec, so use `spring
35
- [command]` rather than `bundle exec spring [command]`.
53
+ On platforms where spring is installed and supported, this snippet
54
+ hooks spring into the execution of commands. In other cases, the snippet
55
+ will just be silently ignored and the lines after it will be executed as
56
+ normal.
57
+
58
+ If you don't want to prefix every command you type with `bin/`, you
59
+ can [use direnv](https://github.com/zimbatm/direnv) to automatically add
60
+ `./bin` to your `PATH` when you `cd` into your application.
61
+
62
+ ### Usage
36
63
 
37
64
  For this walkthrough I've generated a new Rails application, and run
38
65
  `rails generate scaffold posts name:string`.
@@ -40,7 +67,7 @@ For this walkthrough I've generated a new Rails application, and run
40
67
  Let's run a test:
41
68
 
42
69
  ```
43
- $ time spring rake test test/functional/posts_controller_test.rb
70
+ $ time bin/rake test test/functional/posts_controller_test.rb
44
71
  Run options:
45
72
 
46
73
  # Running tests:
@@ -60,7 +87,7 @@ That wasn't particularly fast because it was the first run, so spring
60
87
  had to boot the application. It's now running:
61
88
 
62
89
  ```
63
- $ spring status
90
+ $ bin/spring status
64
91
  Spring is running:
65
92
 
66
93
  26150 spring server | spring-demo-app | started 3 secs ago
@@ -70,7 +97,7 @@ Spring is running:
70
97
  The next run is faster:
71
98
 
72
99
  ```
73
- $ time spring rake test test/functional/posts_controller_test.rb
100
+ $ time bin/rake test test/functional/posts_controller_test.rb
74
101
  Run options:
75
102
 
76
103
  # Running tests:
@@ -86,20 +113,6 @@ user 0m0.276s
86
113
  sys 0m0.059s
87
114
  ```
88
115
 
89
- Writing `spring` before every command gets a bit tedious. Spring binstubs solve this:
90
-
91
- ```
92
- $ spring binstub rake rails
93
- ```
94
-
95
- This will generate `bin/rake` and `bin/rails`. They
96
- replace any binstubs that you might already have in your `bin/`
97
- directory. Check them in to source control.
98
-
99
- If you don't want to prefix every command you type with `bin/`, you
100
- can [use direnv](https://github.com/zimbatm/direnv) to automatically add
101
- `./bin` to your `PATH` when you `cd` into your application.
102
-
103
116
  If we edit any of the application files, or test files, the changes will
104
117
  be picked up on the next run without the background process having to
105
118
  restart. This works in exactly the same way as the code reloading
@@ -114,7 +127,7 @@ Let's "edit" `config/application.rb`:
114
127
 
115
128
  ```
116
129
  $ touch config/application.rb
117
- $ spring status
130
+ $ bin/spring status
118
131
  Spring is running:
119
132
 
120
133
  26150 spring server | spring-demo-app | started 36 secs ago
@@ -137,7 +150,7 @@ edit_post GET /posts/:id/edit(.:format) posts#edit
137
150
  PUT /posts/:id(.:format) posts#update
138
151
  DELETE /posts/:id(.:format) posts#destroy
139
152
 
140
- $ spring status
153
+ $ bin/spring status
141
154
  Spring is running:
142
155
 
143
156
  26150 spring server | spring-demo-app | started 1 min ago
@@ -150,25 +163,18 @@ when you close your terminal. However if you do want to do a manual shut
150
163
  down, use the `stop` command:
151
164
 
152
165
  ```
153
- $ spring stop
166
+ $ bin/spring stop
154
167
  Spring stopped.
155
168
  ```
156
169
 
157
- ## Commands
158
-
159
- The following commands are shipped by default.
170
+ ### Removal
160
171
 
161
- Custom commands can be specified in the Spring config file. See
162
- [`lib/spring/commands/`](https://github.com/jonleighton/spring/blob/master/lib/spring/commands/)
163
- for examples.
172
+ To remove spring:
164
173
 
165
- You can add the following gems to your Gemfile for additional commands:
174
+ * 'Unspring' your bin/ executables: `bin/spring binstub --remove --all`
175
+ * Remove spring from your Gemfile
166
176
 
167
- * [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec)
168
- * [spring-commands-cucumber](https://github.com/jonleighton/spring-commands-cucumber)
169
- * [spring-commands-testunit](https://github.com/jonleighton/spring-commands-testunit) - useful for
170
- running `Test::Unit` tests on Rails 3, since only Rails 4 allows you
171
- to use `rake test path/to/test` to run a particular test/directory.
177
+ ## Commands
172
178
 
173
179
  ### `rake`
174
180
 
@@ -194,6 +200,74 @@ a different sub command (e.g. `rails server`) then spring will automatically
194
200
  pass it through to the underlying `rails` executable (without the
195
201
  speed-up).
196
202
 
203
+ ### Additional commands
204
+
205
+ You can add these to your Gemfile for additional commands (run `spring stop` afterwards
206
+ to pick up the changes):
207
+
208
+ * [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec)
209
+ * [spring-commands-cucumber](https://github.com/jonleighton/spring-commands-cucumber)
210
+ * [spring-commands-testunit](https://github.com/jonleighton/spring-commands-testunit) - useful for
211
+ running `Test::Unit` tests on Rails 3, since only Rails 4 allows you
212
+ to use `rake test path/to/test` to run a particular test/directory.
213
+
214
+ ## Use without adding to bundle
215
+
216
+ If you don't want spring-related code checked into your source
217
+ repository, it's possible to use spring without adding to your Gemfile.
218
+ However, using spring binstubs without adding spring to the Gemfile is not
219
+ supported.
220
+
221
+ To use spring like this, do a `gem install spring` and then prefix
222
+ commands with `spring`. For example, rather than running `bin/rake -T`,
223
+ you'd run `spring rake -T`.
224
+
225
+ ## Temporarily disabling Spring
226
+
227
+ If you're using Spring binstubs, but temporarily don't want commands to
228
+ run through Spring, set the `DISABLE_SPRING` environment variable.
229
+
230
+ ## Class reloading
231
+
232
+ Spring uses Rails' class reloading mechanism
233
+ (`ActiveSupport::Dependencies`) to keep your code up to date between
234
+ test runs. This is the same mechanism which allows you to see changes
235
+ during development when you refresh the page. However, you may never
236
+ have used this mechanism with your `test` environment before, and this
237
+ can cause problems.
238
+
239
+ It's important to realise that code reloading means that the constants
240
+ in your application are *different objects* after files have changed:
241
+
242
+ ```
243
+ $ bin/rails runner 'puts User.object_id'
244
+ 70127987886040
245
+ $ touch app/models/user.rb
246
+ $ bin/rails runner 'puts User.object_id'
247
+ 70127976764620
248
+ ```
249
+
250
+ Suppose you have an initializer `config/initializers/save_user_class.rb`
251
+ like so:
252
+
253
+ ``` ruby
254
+ USER_CLASS = User
255
+ ```
256
+
257
+ This saves off the *first* version of the `User` class, which will not
258
+ be the same object as `User` after the code has been reloaded:
259
+
260
+ ```
261
+ $ bin/rails runner 'puts User == USER_CLASS'
262
+ true
263
+ $ touch app/models/user.rb
264
+ $ bin/rails runner 'puts User == USER_CLASS'
265
+ false
266
+ ```
267
+
268
+ So to avoid this problem, don't save off references to application
269
+ constants in your initialization code.
270
+
197
271
  ## Configuration
198
272
 
199
273
  Spring will read `~/.spring.rb` and `config/spring.rb` for custom
@@ -222,11 +296,6 @@ which get run when your application initializers, such as
222
296
  `config/application.rb`, `config/environments/*.rb` or
223
297
  `config/initializers/*.rb`.
224
298
 
225
- For example, if loading your test helper is slow, you might like to
226
- preload it to speed up your test runs. To do this you could put a
227
- `require Rails.root.join("test/helper")` in
228
- `config/environments/test.rb`.
229
-
230
299
  ### Running code after forking
231
300
 
232
301
  You might want to run code after Spring forked off the process but
@@ -0,0 +1,10 @@
1
+ require "spring/application"
2
+
3
+ app = Spring::Application.new(
4
+ UNIXSocket.for_fd(3),
5
+ Spring::JSON.load(ENV.delete("SPRING_ORIGINAL_ENV").dup)
6
+ )
7
+ Signal.trap("TERM") { app.terminate }
8
+
9
+ app.preload if ENV.delete("SPRING_PRELOAD") == "1"
10
+ app.run
@@ -1,41 +1,79 @@
1
+ require "spring/boot"
1
2
  require "set"
2
- require "spring/watcher"
3
- require "thread"
4
3
 
5
4
  module Spring
6
5
  class Application
7
- attr_reader :manager, :watcher, :spring_env
6
+ attr_reader :manager, :watcher, :spring_env, :original_env
7
+
8
+ def initialize(manager, original_env)
9
+ @manager = manager
10
+ @original_env = original_env
11
+ @spring_env = Env.new
12
+ @mutex = Mutex.new
13
+ @waiting = 0
14
+ @preloaded = false
15
+ @state = :initialized
16
+ @interrupt = IO.pipe
17
+ end
18
+
19
+ def state(val)
20
+ return if exiting?
21
+ log "#{@state} -> #{val}"
22
+ @state = val
23
+ end
8
24
 
9
- def initialize(manager, watcher = Spring.watcher)
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
25
+ def state!(val)
26
+ state val
27
+ @interrupt.last.write "."
28
+ end
18
29
 
19
- # Workaround for GC bug in Ruby 2 which causes segfaults if watcher.to_io
20
- # instances get dereffed.
21
- @fds = [manager, watcher.to_io]
30
+ def app_env
31
+ ENV['RAILS_ENV']
22
32
  end
23
33
 
24
34
  def log(message)
25
- spring_env.log "[application:#{ENV['RAILS_ENV']}] #{message}"
35
+ spring_env.log "[application:#{app_env}] #{message}"
26
36
  end
27
37
 
28
38
  def preloaded?
29
39
  @preloaded
30
40
  end
31
41
 
42
+ def preload_failed?
43
+ @preloaded == :failure
44
+ end
45
+
32
46
  def exiting?
33
- @exiting
47
+ @state == :exiting
48
+ end
49
+
50
+ def terminating?
51
+ @state == :terminating
52
+ end
53
+
54
+ def watcher_stale?
55
+ @state == :watcher_stale
56
+ end
57
+
58
+ def initialized?
59
+ @state == :initialized
60
+ end
61
+
62
+ def start_watcher
63
+ @watcher = Spring.watcher
64
+ @watcher.on_stale { state! :watcher_stale }
65
+ @watcher.start
34
66
  end
35
67
 
36
68
  def preload
37
69
  log "preloading app"
38
70
 
71
+ begin
72
+ require "spring/commands"
73
+ ensure
74
+ start_watcher
75
+ end
76
+
39
77
  require Spring.application_root_path.join("config", "application")
40
78
 
41
79
  # config/environments/test.rb will have config.cache_classes = true. However
@@ -51,44 +89,43 @@ module Spring
51
89
  Rails.application.config.cache_classes = false
52
90
  disconnect_database
53
91
 
92
+ @preloaded = :success
93
+ rescue Exception => e
94
+ @preloaded = :failure
95
+ watcher.add e.backtrace.map { |line| line.match(/^(.*)\:\d+\:in /)[1] }
96
+ raise e unless initialized?
97
+ ensure
54
98
  watcher.add loaded_application_features
55
99
  watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"
56
- watcher.add Rails.application.paths["config/initializers"]
57
100
 
58
- @preloaded = true
101
+ if defined?(Rails)
102
+ watcher.add Rails.application.paths["config/initializers"]
103
+ watcher.add Rails.application.paths["config/database"]
104
+ end
59
105
  end
60
106
 
61
107
  def run
62
- log "running"
63
- watcher.start
108
+ state :running
109
+ manager.puts
110
+ update_process_title
64
111
 
65
112
  loop do
66
- IO.select(@fds)
67
-
68
- if watcher.stale?
69
- log "watcher stale; exiting"
70
- manager.close
71
- @exiting = true
72
- try_exit
73
- sleep
113
+ IO.select [manager, @interrupt.first]
114
+
115
+ if terminating? || watcher_stale? || preload_failed?
116
+ exit
74
117
  else
75
118
  serve manager.recv_io(UNIXSocket)
76
119
  end
77
120
  end
78
121
  end
79
122
 
80
- def try_exit
81
- @mutex.synchronize {
82
- exit if exiting? && @waiting == 0
83
- }
84
- end
85
-
86
123
  def serve(client)
87
124
  log "got client"
88
125
  manager.puts
89
126
 
90
- streams = 3.times.map { client.recv_io }
91
- [STDOUT, STDERR].zip(streams).each { |a, b| a.reopen(b) }
127
+ stdout, stderr, stdin = streams = 3.times.map { client.recv_io }
128
+ [STDOUT, STDERR].zip([stdout, stderr]).each { |a, b| a.reopen(b) }
92
129
 
93
130
  preload unless preloaded?
94
131
 
@@ -105,33 +142,29 @@ module Spring
105
142
 
106
143
  pid = fork {
107
144
  Process.setsid
108
- STDIN.reopen(streams.last)
145
+ STDIN.reopen(stdin)
109
146
  IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
147
+ trap("TERM", "DEFAULT")
110
148
 
111
149
  ARGV.replace(args)
112
150
 
113
151
  # 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 }
152
+ original_env.each { |k, v| ENV.delete k if ENV[k] == v }
115
153
 
116
154
  # Load in the current env vars, except those which *were* changed when spring started
117
155
  env.each { |k, v| ENV[k] ||= v }
118
156
 
157
+ # requiring is faster, and we don't need constant reloading in this process
158
+ ActiveSupport::Dependencies.mechanism = :require
159
+ Rails.application.config.cache_classes = true
160
+
119
161
  connect_database
120
162
  srand
121
163
 
122
164
  invoke_after_fork_callbacks
123
165
  shush_backtraces
124
166
 
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
167
+ command.call
135
168
  }
136
169
 
137
170
  disconnect_database
@@ -152,26 +185,44 @@ module Spring
152
185
  client.close
153
186
 
154
187
  @mutex.synchronize { @waiting -= 1 }
155
- try_exit
188
+ exit_if_finished
156
189
  }
157
190
 
158
- rescue => e
191
+ rescue Exception => e
159
192
  log "exception: #{e}"
160
- streams.each(&:close) if streams
161
- client.puts(1)
193
+ manager.puts unless pid
194
+
195
+ if streams && !e.is_a?(SystemExit)
196
+ print_exception(stderr, e)
197
+ streams.each(&:close)
198
+ end
199
+
200
+ client.puts(1) if pid
162
201
  client.close
163
- raise
202
+ end
203
+
204
+ def terminate
205
+ state! :terminating
206
+ end
207
+
208
+ def exit
209
+ state :exiting
210
+ manager.shutdown(:RDWR)
211
+ exit_if_finished
212
+ sleep
213
+ end
214
+
215
+ def exit_if_finished
216
+ @mutex.synchronize {
217
+ Kernel.exit if exiting? && @waiting == 0
218
+ }
164
219
  end
165
220
 
166
221
  # The command might need to require some files in the
167
222
  # main process so that they are cached. For example a test command wants to
168
223
  # load the helper file once and have it cached.
169
224
  def setup(command)
170
- return if @setup.include?(command.class)
171
- @setup << command.class
172
-
173
- if command.respond_to?(:setup)
174
- command.setup
225
+ if command.setup
175
226
  watcher.add loaded_application_features # loaded features may have changed
176
227
  end
177
228
  end
@@ -183,7 +234,8 @@ module Spring
183
234
  end
184
235
 
185
236
  def loaded_application_features
186
- $LOADED_FEATURES.select { |f| f.start_with?(File.realpath(Rails.root)) }
237
+ root = Spring.application_root_path.to_s
238
+ $LOADED_FEATURES.select { |f| f.start_with?(root) }
187
239
  end
188
240
 
189
241
  def disconnect_database
@@ -211,5 +263,17 @@ module Spring
211
263
  end
212
264
  end
213
265
  end
266
+
267
+ def print_exception(stream, error)
268
+ first, rest = error.backtrace.first, error.backtrace.drop(1)
269
+ stream.puts("#{first}: #{error} (#{error.class})")
270
+ rest.each { |line| stream.puts("\tfrom #{line}") }
271
+ end
272
+
273
+ def update_process_title
274
+ ProcessTitleUpdater.run { |distance|
275
+ "spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
276
+ }
277
+ end
214
278
  end
215
279
  end