spring 0.0.8 → 1.0.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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +113 -0
  4. data/Gemfile +1 -3
  5. data/README.md +129 -187
  6. data/bin/spring +27 -0
  7. data/lib/spring/application.rb +121 -20
  8. data/lib/spring/application_manager.rb +21 -13
  9. data/lib/spring/client/binstub.rb +45 -41
  10. data/lib/spring/client/help.rb +4 -1
  11. data/lib/spring/client/rails.rb +8 -3
  12. data/lib/spring/client/run.rb +33 -10
  13. data/lib/spring/client/status.rb +2 -2
  14. data/lib/spring/client.rb +3 -3
  15. data/lib/spring/commands/rails.rb +60 -0
  16. data/lib/spring/commands/rake.rb +34 -0
  17. data/lib/spring/commands.rb +12 -195
  18. data/lib/spring/configuration.rb +6 -2
  19. data/lib/spring/env.rb +27 -18
  20. data/lib/spring/errors.rb +1 -1
  21. data/lib/spring/json.rb +626 -0
  22. data/lib/spring/server.rb +60 -29
  23. data/lib/spring/version.rb +1 -1
  24. data/lib/spring/watcher/abstract.rb +11 -7
  25. data/lib/spring/watcher/listen.rb +8 -10
  26. data/lib/spring/watcher/polling.rb +1 -1
  27. data/lib/spring/watcher.rb +17 -11
  28. data/spring.gemspec +1 -0
  29. data/test/acceptance/app_test.rb +270 -73
  30. data/test/apps/.gitignore +3 -0
  31. data/test/unit/client/help_test.rb +2 -0
  32. data/test/unit/commands_test.rb +8 -49
  33. data/test/unit/watcher_test.rb +22 -2
  34. metadata +18 -151
  35. data/lib/spring/client/start.rb +0 -17
  36. data/test/apps/rails-3-2/.gitignore +0 -18
  37. data/test/apps/rails-3-2/Gemfile +0 -9
  38. data/test/apps/rails-3-2/README.rdoc +0 -261
  39. data/test/apps/rails-3-2/Rakefile +0 -7
  40. data/test/apps/rails-3-2/app/assets/images/rails.png +0 -0
  41. data/test/apps/rails-3-2/app/assets/javascripts/application.js +0 -15
  42. data/test/apps/rails-3-2/app/assets/javascripts/posts.js.coffee +0 -3
  43. data/test/apps/rails-3-2/app/assets/stylesheets/application.css +0 -13
  44. data/test/apps/rails-3-2/app/assets/stylesheets/posts.css.scss +0 -3
  45. data/test/apps/rails-3-2/app/assets/stylesheets/scaffolds.css.scss +0 -69
  46. data/test/apps/rails-3-2/app/controllers/application_controller.rb +0 -3
  47. data/test/apps/rails-3-2/app/controllers/posts_controller.rb +0 -83
  48. data/test/apps/rails-3-2/app/helpers/application_helper.rb +0 -2
  49. data/test/apps/rails-3-2/app/helpers/posts_helper.rb +0 -2
  50. data/test/apps/rails-3-2/app/mailers/.gitkeep +0 -0
  51. data/test/apps/rails-3-2/app/models/.gitkeep +0 -0
  52. data/test/apps/rails-3-2/app/models/post.rb +0 -3
  53. data/test/apps/rails-3-2/app/views/layouts/application.html.erb +0 -14
  54. data/test/apps/rails-3-2/app/views/posts/_form.html.erb +0 -21
  55. data/test/apps/rails-3-2/app/views/posts/edit.html.erb +0 -6
  56. data/test/apps/rails-3-2/app/views/posts/index.html.erb +0 -23
  57. data/test/apps/rails-3-2/app/views/posts/new.html.erb +0 -5
  58. data/test/apps/rails-3-2/app/views/posts/show.html.erb +0 -10
  59. data/test/apps/rails-3-2/config/application.rb +0 -62
  60. data/test/apps/rails-3-2/config/boot.rb +0 -6
  61. data/test/apps/rails-3-2/config/database.yml +0 -25
  62. data/test/apps/rails-3-2/config/environment.rb +0 -5
  63. data/test/apps/rails-3-2/config/environments/development.rb +0 -37
  64. data/test/apps/rails-3-2/config/environments/production.rb +0 -67
  65. data/test/apps/rails-3-2/config/environments/test.rb +0 -37
  66. data/test/apps/rails-3-2/config/initializers/backtrace_silencers.rb +0 -7
  67. data/test/apps/rails-3-2/config/initializers/inflections.rb +0 -15
  68. data/test/apps/rails-3-2/config/initializers/mime_types.rb +0 -5
  69. data/test/apps/rails-3-2/config/initializers/secret_token.rb +0 -7
  70. data/test/apps/rails-3-2/config/initializers/session_store.rb +0 -8
  71. data/test/apps/rails-3-2/config/initializers/wrap_parameters.rb +0 -14
  72. data/test/apps/rails-3-2/config/locales/en.yml +0 -5
  73. data/test/apps/rails-3-2/config/routes.rb +0 -60
  74. data/test/apps/rails-3-2/config/spring.rb +0 -7
  75. data/test/apps/rails-3-2/config.ru +0 -4
  76. data/test/apps/rails-3-2/db/migrate/20121109171227_create_posts.rb +0 -9
  77. data/test/apps/rails-3-2/db/schema.rb +0 -22
  78. data/test/apps/rails-3-2/db/seeds.rb +0 -7
  79. data/test/apps/rails-3-2/lib/assets/.gitkeep +0 -0
  80. data/test/apps/rails-3-2/lib/tasks/.gitkeep +0 -0
  81. data/test/apps/rails-3-2/log/.gitkeep +0 -0
  82. data/test/apps/rails-3-2/public/404.html +0 -26
  83. data/test/apps/rails-3-2/public/422.html +0 -26
  84. data/test/apps/rails-3-2/public/500.html +0 -25
  85. data/test/apps/rails-3-2/public/favicon.ico +0 -0
  86. data/test/apps/rails-3-2/public/index.html +0 -241
  87. data/test/apps/rails-3-2/public/robots.txt +0 -5
  88. data/test/apps/rails-3-2/script/rails +0 -6
  89. data/test/apps/rails-3-2/test/fixtures/.gitkeep +0 -0
  90. data/test/apps/rails-3-2/test/fixtures/posts.yml +0 -7
  91. data/test/apps/rails-3-2/test/functional/.gitkeep +0 -0
  92. data/test/apps/rails-3-2/test/functional/posts_controller_test.rb +0 -49
  93. data/test/apps/rails-3-2/test/integration/.gitkeep +0 -0
  94. data/test/apps/rails-3-2/test/performance/browsing_test.rb +0 -12
  95. data/test/apps/rails-3-2/test/test_helper.rb +0 -13
  96. data/test/apps/rails-3-2/test/unit/.gitkeep +0 -0
  97. data/test/apps/rails-3-2/test/unit/helpers/posts_helper_test.rb +0 -4
  98. data/test/apps/rails-3-2/test/unit/post_test.rb +0 -7
  99. data/test/apps/rails-3-2/vendor/assets/javascripts/.gitkeep +0 -0
  100. data/test/apps/rails-3-2/vendor/assets/stylesheets/.gitkeep +0 -0
  101. data/test/apps/rails-3-2/vendor/plugins/.gitkeep +0 -0
data/lib/spring/server.rb CHANGED
@@ -1,8 +1,20 @@
1
+ module Spring
2
+ class << self
3
+ attr_reader :original_env
4
+ end
5
+ @original_env = ENV.to_hash
6
+ end
7
+
1
8
  require "socket"
9
+ require "thread"
2
10
 
11
+ require "spring/configuration"
3
12
  require "spring/env"
4
13
  require "spring/application_manager"
5
14
  require "spring/process_title_updater"
15
+ require "spring/json"
16
+
17
+ # Must be last, as it requires bundler/setup
6
18
  require "spring/commands"
7
19
 
8
20
  # readline must be required before we setpgid, otherwise the require may hang,
@@ -19,47 +31,64 @@ module Spring
19
31
 
20
32
  def initialize(env = Env.new)
21
33
  @env = env
22
- @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k) }
34
+ @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(self, k) }
23
35
  @pidfile = env.pidfile_path.open('a')
36
+ @mutex = Mutex.new
37
+ end
38
+
39
+ def log(message)
40
+ env.log "[server] #{message}"
24
41
  end
25
42
 
26
43
  def boot
44
+ Spring.verify_environment
45
+
46
+ write_pidfile
27
47
  set_pgid
28
48
  ignore_signals
29
49
  set_exit_hook
30
- write_pidfile
31
- redirect_output
32
50
  set_process_title
51
+ watch_bundle
52
+ start_server
53
+ end
33
54
 
55
+ def start_server
34
56
  server = UNIXServer.open(env.socket_name)
57
+ log "started on #{env.socket_name}"
35
58
  loop { serve server.accept }
36
59
  end
37
60
 
38
61
  def serve(client)
62
+ log "accepted client"
39
63
  client.puts env.version
40
64
 
41
- app_client = client.recv_io
42
- command = JSON.parse(client.read(client.gets.to_i))
43
- args, client_env = command.values_at('args', 'env')
65
+ app_client = client.recv_io
66
+ command = JSON.load(client.read(client.gets.to_i))
67
+
68
+ args, default_rails_env = command.values_at('args', 'default_rails_env')
44
69
 
45
70
  if Spring.command?(args.first)
71
+ log "running command #{args.first}"
46
72
  client.puts
47
- client.puts @applications[rails_env_for(args, client_env)].run(app_client)
73
+ client.puts @applications[rails_env_for(args, default_rails_env)].run(app_client)
48
74
  else
75
+ log "command not found #{args.first}"
49
76
  client.close
50
77
  end
51
78
  rescue SocketError => e
52
79
  raise e unless client.eof?
80
+ ensure
81
+ redirect_output
53
82
  end
54
83
 
55
- def rails_env_for(args, client_env)
84
+ def rails_env_for(args, default_rails_env)
56
85
  command = Spring.command(args.first)
57
86
 
58
87
  if command.respond_to?(:env)
59
88
  env = command.env(args.drop(1))
60
89
  end
61
90
 
62
- env || client_env['RAILS_ENV'] || client_env['RACK_ENV'] || 'development'
91
+ env || default_rails_env
63
92
  end
64
93
 
65
94
  # Boot the server into the process group of the current session.
@@ -78,14 +107,16 @@ module Spring
78
107
  def set_exit_hook
79
108
  server_pid = Process.pid
80
109
 
81
- at_exit do
82
- # We don't want this hook to run in any forks of the current process
83
- if Process.pid == server_pid
84
- [env.socket_path, env.pidfile_path].each do |path|
85
- path.unlink if path.exist?
86
- end
110
+ # We don't want this hook to run in any forks of the current process
111
+ at_exit { shutdown if Process.pid == server_pid }
112
+ end
113
+
114
+ def shutdown
115
+ @applications.values.each(&:stop)
87
116
 
88
- @applications.values.each(&:stop)
117
+ [env.socket_path, env.pidfile_path].each do |path|
118
+ if path.exist?
119
+ path.unlink rescue nil
89
120
  end
90
121
  end
91
122
  end
@@ -96,24 +127,16 @@ module Spring
96
127
  @pidfile.write("#{Process.pid}\n")
97
128
  @pidfile.fsync
98
129
  else
99
- $stderr.puts "#{@pidfile.path} is locked; it looks like a server is already running"
100
130
  exit 1
101
131
  end
102
132
  end
103
133
 
104
- # We can't leave STDOUT, STDERR as they as because then they will
105
- # never get closed for the lifetime of the server. This means that
106
- # piping, e.g. "spring rake -T | grep db" won't work correctly
107
- # because grep will hang while waiting for its stdin to reach EOF.
108
- #
109
- # However we do want server output to go to the terminal in case
110
- # there are exceptions etc, so we just open the current terminal
111
- # device directly.
134
+ # We need to redirect STDOUT and STDERR, otherwise the server will
135
+ # keep the original FDs open which would break piping. (e.g.
136
+ # `spring rake -T | grep db` would hang forever because the server
137
+ # would keep the stdout FD open.)
112
138
  def redirect_output
113
- # ruby doesn't expose ttyname()
114
- file = open(STDIN.tty? ? `tty`.chomp : "/dev/null", "a")
115
- STDOUT.reopen(file)
116
- STDERR.reopen(file)
139
+ [STDOUT, STDERR].each { |stream| stream.reopen(env.log_file) }
117
140
  end
118
141
 
119
142
  def set_process_title
@@ -121,5 +144,13 @@ module Spring
121
144
  "spring server | #{env.app_name} | started #{distance} ago"
122
145
  }
123
146
  end
147
+
148
+ def watch_bundle
149
+ @bundle_mtime = env.bundle_mtime
150
+ end
151
+
152
+ def application_starting
153
+ @mutex.synchronize { exit if env.bundle_mtime != @bundle_mtime }
154
+ end
124
155
  end
125
156
  end
@@ -1,3 +1,3 @@
1
1
  module Spring
2
- VERSION = "0.0.8"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -36,15 +36,19 @@ module Spring
36
36
  end
37
37
  end
38
38
 
39
- items.each do |item|
40
- if item.directory?
41
- directories << item.realpath.to_s
42
- else
43
- files << item.realpath.to_s
39
+ items = items.select(&:exist?)
40
+
41
+ synchronize {
42
+ items.each do |item|
43
+ if item.directory?
44
+ directories << item.realpath.to_s
45
+ else
46
+ files << item.realpath.to_s
47
+ end
44
48
  end
45
- end
46
49
 
47
- subjects_changed
50
+ subjects_changed
51
+ }
48
52
  end
49
53
 
50
54
  def stale?
@@ -1,21 +1,18 @@
1
+ gem "listen", "~> 1.0"
2
+ require "listen"
3
+ require "listen/version"
4
+
1
5
  module Spring
2
6
  module Watcher
3
7
  class Listen < Abstract
4
8
  attr_reader :listener
5
9
 
6
- def self.available?
7
- require "listen"
8
- true
9
- rescue LoadError
10
- false
11
- end
12
-
13
10
  def start
14
11
  unless @listener
15
- @listener = ::Listen::MultiListener.new(*base_directories)
12
+ @listener = ::Listen.to(*base_directories, relative_paths: false)
16
13
  @listener.latency(latency)
17
14
  @listener.change(&method(:changed))
18
- @listener.start(false)
15
+ @listener.start
19
16
  end
20
17
  end
21
18
 
@@ -45,9 +42,10 @@ module Spring
45
42
  end
46
43
 
47
44
  def base_directories
48
- [root] +
45
+ ([root] +
49
46
  files.reject { |f| f.start_with? root }.map { |f| File.expand_path("#{f}/..") } +
50
47
  directories.reject { |d| d.start_with? root }
48
+ ).uniq
51
49
  end
52
50
  end
53
51
  end
@@ -5,7 +5,7 @@ module Spring
5
5
 
6
6
  def initialize(root, latency)
7
7
  super
8
- @mtime = nil
8
+ @mtime = 0
9
9
  @poller = nil
10
10
  end
11
11
 
@@ -1,25 +1,31 @@
1
1
  require "spring/watcher/abstract"
2
- require "spring/watcher/listen"
3
- require "spring/watcher/polling"
2
+ require "spring/configuration"
4
3
 
5
4
  module Spring
6
5
  class << self
7
6
  attr_accessor :watch_interval
8
7
  attr_writer :watcher
8
+ attr_reader :watch_method
9
+ end
10
+
11
+ def self.watch_method=(method)
12
+ case method
13
+ when :polling
14
+ require_relative "watcher/polling"
15
+ @watch_method = Watcher::Polling
16
+ when :listen
17
+ require_relative "watcher/listen"
18
+ @watch_method = Watcher::Listen
19
+ else
20
+ @watch_method = method
21
+ end
9
22
  end
10
23
 
11
24
  self.watch_interval = 0.2
25
+ self.watch_method = :polling
12
26
 
13
27
  def self.watcher
14
- @watcher ||= watcher_class.new(Spring.application_root_path, watch_interval)
15
- end
16
-
17
- def self.watcher_class
18
- if Watcher::Listen.available?
19
- Watcher::Listen
20
- else
21
- Watcher::Polling
22
- end
28
+ @watcher ||= watch_method.new(Spring.application_root_path, watch_interval)
23
29
  end
24
30
 
25
31
  def self.watch(*items)
data/spring.gemspec CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |gem|
11
11
  gem.description = %q{Rails application preloader}
12
12
  gem.summary = %q{Rails application preloader}
13
13
  gem.homepage = "http://github.com/jonleighton/spring"
14
+ gem.license = "MIT"
14
15
 
15
16
  gem.files = `git ls-files`.split($/)
16
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }