spring 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab103d29ee132ac8637c7ddca8d5c41fc567c31e
4
- data.tar.gz: 88e600c6e1d59b4150625769f20f6d543958087c
3
+ metadata.gz: a2357648d0fc0f884098f352ab693e66556de6df
4
+ data.tar.gz: df070385ccdf9fbbb6d48770f21886dbab93516e
5
5
  SHA512:
6
- metadata.gz: 7b4d762562b8d4c1832bcec43819b6bcc6d9168473095fac089a743c0c421d4450f77e7c0a3cf31902256ed42a3d0fa57e7767cd4a0cf9d0bd55dc24c758f460
7
- data.tar.gz: 9c929b2636980dc30486f475caeacb1f3de296420c22e9f0a52c67ce732a8953e28f1b090258a18456d210ce217610e8e4df8ffeb7a1a8ebd0e2b546554235ec
6
+ metadata.gz: cc65ad0c92b3a134505805c9b8ed999029b49030ab0ca8e46473c47c18a6ff17804de47897a7493e28765dc3fa6dc18df2de5beb4be7b8f6bdf04bd979d2a7da
7
+ data.tar.gz: 1b408b559590cf9a1b13da2d390d191964809107edc00c4b67e6c54384d05df1a5f9673fcc796318e1962c1f3c4e9fd49141596a3692c80b831d9cc933dd67c5
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
+ ## 1.1.0
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
data/bin/spring CHANGED
@@ -21,6 +21,7 @@ if defined?(Gem)
21
21
 
22
22
  if unstubbed.any?
23
23
  warn "Warning: Running `gem pristine --all` to regenerate your installed gemspecs " \
24
+ "(and deleting then reinstalling your bundle if you use bundle --path) " \
24
25
  "will improve the startup performance of Spring."
25
26
  end
26
27
  end
@@ -0,0 +1,16 @@
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
+
8
+ Signal.trap("TERM") { app.terminate }
9
+ Signal.trap("TTOU", "IGNORE")
10
+
11
+ Spring::ProcessTitleUpdater.run { |distance|
12
+ "spring app | #{app.app_name} | started #{distance} ago | #{app.app_env} mode"
13
+ }
14
+
15
+ app.preload if ENV.delete("SPRING_PRELOAD") == "1"
16
+ app.run
@@ -1,41 +1,83 @@
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
29
+
30
+ def app_env
31
+ ENV['RAILS_ENV']
32
+ end
18
33
 
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]
34
+ def app_name
35
+ spring_env.app_name
22
36
  end
23
37
 
24
38
  def log(message)
25
- spring_env.log "[application:#{ENV['RAILS_ENV']}] #{message}"
39
+ spring_env.log "[application:#{app_env}] #{message}"
26
40
  end
27
41
 
28
42
  def preloaded?
29
43
  @preloaded
30
44
  end
31
45
 
46
+ def preload_failed?
47
+ @preloaded == :failure
48
+ end
49
+
32
50
  def exiting?
33
- @exiting
51
+ @state == :exiting
52
+ end
53
+
54
+ def terminating?
55
+ @state == :terminating
56
+ end
57
+
58
+ def watcher_stale?
59
+ @state == :watcher_stale
60
+ end
61
+
62
+ def initialized?
63
+ @state == :initialized
64
+ end
65
+
66
+ def start_watcher
67
+ @watcher = Spring.watcher
68
+ @watcher.on_stale { state! :watcher_stale }
69
+ @watcher.start
34
70
  end
35
71
 
36
72
  def preload
37
73
  log "preloading app"
38
74
 
75
+ begin
76
+ require "spring/commands"
77
+ ensure
78
+ start_watcher
79
+ end
80
+
39
81
  require Spring.application_root_path.join("config", "application")
40
82
 
41
83
  # config/environments/test.rb will have config.cache_classes = true. However
@@ -51,44 +93,42 @@ module Spring
51
93
  Rails.application.config.cache_classes = false
52
94
  disconnect_database
53
95
 
96
+ @preloaded = :success
97
+ rescue Exception => e
98
+ @preloaded = :failure
99
+ watcher.add e.backtrace.map { |line| line.match(/^(.*)\:\d+\:in /)[1] }
100
+ raise e unless initialized?
101
+ ensure
54
102
  watcher.add loaded_application_features
55
103
  watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"
56
- watcher.add Rails.application.paths["config/initializers"]
57
104
 
58
- @preloaded = true
105
+ if defined?(Rails)
106
+ watcher.add Rails.application.paths["config/initializers"]
107
+ watcher.add Rails.application.paths["config/database"]
108
+ end
59
109
  end
60
110
 
61
111
  def run
62
- log "running"
63
- watcher.start
112
+ state :running
113
+ manager.puts
64
114
 
65
115
  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
116
+ IO.select [manager, @interrupt.first]
117
+
118
+ if terminating? || watcher_stale? || preload_failed?
119
+ exit
74
120
  else
75
121
  serve manager.recv_io(UNIXSocket)
76
122
  end
77
123
  end
78
124
  end
79
125
 
80
- def try_exit
81
- @mutex.synchronize {
82
- exit if exiting? && @waiting == 0
83
- }
84
- end
85
-
86
126
  def serve(client)
87
127
  log "got client"
88
128
  manager.puts
89
129
 
90
- streams = 3.times.map { client.recv_io }
91
- [STDOUT, STDERR].zip(streams).each { |a, b| a.reopen(b) }
130
+ stdout, stderr, stdin = streams = 3.times.map { client.recv_io }
131
+ [STDOUT, STDERR].zip([stdout, stderr]).each { |a, b| a.reopen(b) }
92
132
 
93
133
  preload unless preloaded?
94
134
 
@@ -105,33 +145,30 @@ module Spring
105
145
 
106
146
  pid = fork {
107
147
  Process.setsid
108
- STDIN.reopen(streams.last)
148
+ STDIN.reopen(stdin)
109
149
  IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
150
+ trap("TERM", "DEFAULT")
110
151
 
111
152
  ARGV.replace(args)
153
+ $0 = command.process_title
112
154
 
113
155
  # 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 }
156
+ original_env.each { |k, v| ENV.delete k if ENV[k] == v }
115
157
 
116
158
  # Load in the current env vars, except those which *were* changed when spring started
117
159
  env.each { |k, v| ENV[k] ||= v }
118
160
 
161
+ # requiring is faster, and we don't need constant reloading in this process
162
+ ActiveSupport::Dependencies.mechanism = :require
163
+ Rails.application.config.cache_classes = true
164
+
119
165
  connect_database
120
166
  srand
121
167
 
122
168
  invoke_after_fork_callbacks
123
169
  shush_backtraces
124
170
 
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
171
+ command.call
135
172
  }
136
173
 
137
174
  disconnect_database
@@ -152,26 +189,44 @@ module Spring
152
189
  client.close
153
190
 
154
191
  @mutex.synchronize { @waiting -= 1 }
155
- try_exit
192
+ exit_if_finished
156
193
  }
157
194
 
158
- rescue => e
195
+ rescue Exception => e
159
196
  log "exception: #{e}"
160
- streams.each(&:close) if streams
161
- client.puts(1)
197
+ manager.puts unless pid
198
+
199
+ if streams && !e.is_a?(SystemExit)
200
+ print_exception(stderr, e)
201
+ streams.each(&:close)
202
+ end
203
+
204
+ client.puts(1) if pid
162
205
  client.close
163
- raise
206
+ end
207
+
208
+ def terminate
209
+ state! :terminating
210
+ end
211
+
212
+ def exit
213
+ state :exiting
214
+ manager.shutdown(:RDWR)
215
+ exit_if_finished
216
+ sleep
217
+ end
218
+
219
+ def exit_if_finished
220
+ @mutex.synchronize {
221
+ Kernel.exit if exiting? && @waiting == 0
222
+ }
164
223
  end
165
224
 
166
225
  # The command might need to require some files in the
167
226
  # main process so that they are cached. For example a test command wants to
168
227
  # load the helper file once and have it cached.
169
228
  def setup(command)
170
- return if @setup.include?(command.class)
171
- @setup << command.class
172
-
173
- if command.respond_to?(:setup)
174
- command.setup
229
+ if command.setup
175
230
  watcher.add loaded_application_features # loaded features may have changed
176
231
  end
177
232
  end
@@ -183,7 +238,8 @@ module Spring
183
238
  end
184
239
 
185
240
  def loaded_application_features
186
- $LOADED_FEATURES.select { |f| f.start_with?(File.realpath(Rails.root)) }
241
+ root = Spring.application_root_path.to_s
242
+ $LOADED_FEATURES.select { |f| f.start_with?(root) }
187
243
  end
188
244
 
189
245
  def disconnect_database
@@ -211,5 +267,11 @@ module Spring
211
267
  end
212
268
  end
213
269
  end
270
+
271
+ def print_exception(stream, error)
272
+ first, rest = error.backtrace.first, error.backtrace.drop(1)
273
+ stream.puts("#{first}: #{error} (#{error.class})")
274
+ rest.each { |line| stream.puts("\tfrom #{line}") }
275
+ end
214
276
  end
215
277
  end