heroku_hatchet 0.0.2 → 0.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +14 -0
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +2 -2
  5. data/Rakefile +6 -0
  6. data/hatchet.gemspec +3 -1
  7. data/hatchet.json +3 -2
  8. data/lib/hatchet.rb +2 -0
  9. data/lib/hatchet/anvil_app.rb +28 -18
  10. data/lib/hatchet/app.rb +19 -5
  11. data/lib/hatchet/git_app.rb +5 -2
  12. data/lib/hatchet/tasks.rb +17 -7
  13. data/lib/hatchet/version.rb +1 -1
  14. data/test/fixtures/buildpacks/heroku-buildpack-ruby/.gitignore +4 -0
  15. data/test/fixtures/buildpacks/heroku-buildpack-ruby/CHANGELOG.md +378 -0
  16. data/test/fixtures/buildpacks/heroku-buildpack-ruby/Gemfile +10 -0
  17. data/test/fixtures/buildpacks/heroku-buildpack-ruby/LICENSE +9 -0
  18. data/test/fixtures/buildpacks/heroku-buildpack-ruby/README.md +192 -0
  19. data/test/fixtures/buildpacks/heroku-buildpack-ruby/Rakefile +358 -0
  20. data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/compile +13 -0
  21. data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/detect +12 -0
  22. data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/release +9 -0
  23. data/test/fixtures/buildpacks/heroku-buildpack-ruby/hatchet.json +25 -0
  24. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack.rb +27 -0
  25. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/base.rb +175 -0
  26. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/bundler_lockfile.rb +19 -0
  27. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/disable_deploys.rb +17 -0
  28. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/no_lockfile.rb +16 -0
  29. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rack.rb +43 -0
  30. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails2.rb +91 -0
  31. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails3.rb +86 -0
  32. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails4.rb +66 -0
  33. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/ruby.rb +681 -0
  34. data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/shell_helpers.rb +62 -0
  35. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/bugs_spec.rb +11 -0
  36. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/no_lockfile_spec.rb +10 -0
  37. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails23_spec.rb +11 -0
  38. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails3_spec.rb +22 -0
  39. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails4_spec.rb +12 -0
  40. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rubies_spec.rb +38 -0
  41. data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/spec_helper.rb +35 -0
  42. data/test/fixtures/buildpacks/heroku-buildpack-ruby/support/s3/hmac +79 -0
  43. data/test/fixtures/buildpacks/heroku-buildpack-ruby/support/s3/s3 +223 -0
  44. data/test/fixtures/buildpacks/heroku-buildpack-ruby/vendor/syck_hack.rb +64 -0
  45. data/test/hatchet/allow_failure_anvil_test.rb +21 -0
  46. data/test/hatchet/allow_failure_git_test.rb +17 -0
  47. data/test/hatchet/anvil_test.rb +7 -7
  48. data/test/hatchet/config_test.rb +4 -2
  49. data/test/hatchet/git_test.rb +2 -2
  50. metadata +89 -8
@@ -0,0 +1,86 @@
1
+ require "language_pack"
2
+ require "language_pack/rails2"
3
+
4
+ # Rails 3 Language Pack. This is for all Rails 3.x apps.
5
+ class LanguagePack::Rails3 < LanguagePack::Rails2
6
+ # detects if this is a Rails 3.x app
7
+ # @return [Boolean] true if it's a Rails 3.x app
8
+ def self.use?
9
+ if gemfile_lock?
10
+ rails_version = LanguagePack::Ruby.gem_version('railties')
11
+ rails_version >= Gem::Version.new('3.0.0') && rails_version < Gem::Version.new('4.0.0') if rails_version
12
+ end
13
+ end
14
+
15
+ def name
16
+ "Ruby/Rails"
17
+ end
18
+
19
+ def default_process_types
20
+ # let's special case thin here
21
+ web_process = gem_is_bundled?("thin") ?
22
+ "bundle exec thin start -R config.ru -e $RAILS_ENV -p $PORT" :
23
+ "bundle exec rails server -p $PORT"
24
+
25
+ super.merge({
26
+ "web" => web_process,
27
+ "console" => "bundle exec rails console"
28
+ })
29
+ end
30
+
31
+ private
32
+
33
+ def plugins
34
+ super.concat(%w( rails3_serve_static_assets )).uniq
35
+ end
36
+
37
+ # runs the tasks for the Rails 3.1 asset pipeline
38
+ def run_assets_precompile_rake_task
39
+ log("assets_precompile") do
40
+ setup_database_url_env
41
+
42
+ if rake_task_defined?("assets:precompile")
43
+ topic("Preparing app for Rails asset pipeline")
44
+ if File.exists?("public/assets/manifest.yml")
45
+ puts "Detected manifest.yml, assuming assets were compiled locally"
46
+ else
47
+ ENV["RAILS_GROUPS"] ||= "assets"
48
+ ENV["RAILS_ENV"] ||= "production"
49
+
50
+ puts "Running: rake assets:precompile"
51
+ require 'benchmark'
52
+ time = Benchmark.realtime { pipe("env PATH=$PATH:bin bundle exec rake assets:precompile 2>&1") }
53
+
54
+ if $?.success?
55
+ log "assets_precompile", :status => "success"
56
+ puts "Asset precompilation completed (#{"%.2f" % time}s)"
57
+ else
58
+ log "assets_precompile", :status => "failure"
59
+ puts "Precompiling assets failed, enabling runtime asset compilation"
60
+ install_plugin("rails31_enable_runtime_asset_compilation")
61
+ puts "Please see this article for troubleshooting help:"
62
+ puts "http://devcenter.heroku.com/articles/rails31_heroku_cedar#troubleshooting"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # setup the database url as an environment variable
70
+ def setup_database_url_env
71
+ ENV["DATABASE_URL"] ||= begin
72
+ # need to use a dummy DATABASE_URL here, so rails can load the environment
73
+ scheme =
74
+ if gem_is_bundled?("pg")
75
+ "postgres"
76
+ elsif gem_is_bundled?("mysql")
77
+ "mysql"
78
+ elsif gem_is_bundled?("mysql2")
79
+ "mysql2"
80
+ elsif gem_is_bundled?("sqlite3") || gem_is_bundled?("sqlite3-ruby")
81
+ "sqlite3"
82
+ end
83
+ "#{scheme}://user:pass@127.0.0.1/dbname"
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,66 @@
1
+ require "language_pack"
2
+ require "language_pack/rails3"
3
+
4
+ # Rails 4 Language Pack. This is for all Rails 4.x apps.
5
+ class LanguagePack::Rails4 < LanguagePack::Rails3
6
+ # detects if this is a Rails 3.x app
7
+ # @return [Boolean] true if it's a Rails 3.x app
8
+ def self.use?
9
+ if gemfile_lock?
10
+ rails_version = LanguagePack::Ruby.gem_version('railties')
11
+ rails_version >= Gem::Version.new('4.0.0.beta') && rails_version < Gem::Version.new('5.0.0') if rails_version
12
+ end
13
+ end
14
+
15
+ def name
16
+ "Ruby/Rails"
17
+ end
18
+
19
+ def default_process_types
20
+ web_process = gem_is_bundled?("thin") ?
21
+ "bin/rails server thin -p $PORT -e $RAILS_ENV" :
22
+ "bin/rails server -p $PORT -e $RAILS_ENV"
23
+ super.merge({
24
+ "web" => web_process,
25
+ "console" => "bin/rails console"
26
+ })
27
+ end
28
+
29
+ private
30
+ def plugins
31
+ []
32
+ end
33
+
34
+ def run_assets_precompile_rake_task
35
+ log("assets_precompile") do
36
+ setup_database_url_env
37
+
38
+ if rake_task_defined?("assets:precompile")
39
+ topic("Preparing app for Rails asset pipeline")
40
+ if Dir.glob('public/assets/manifest-*.json').any?
41
+ puts "Detected manifest file, assuming assets were compiled locally"
42
+ else
43
+ ENV["RAILS_GROUPS"] ||= "assets"
44
+ ENV["RAILS_ENV"] ||= "production"
45
+
46
+ puts "Running: rake assets:precompile"
47
+ require 'benchmark'
48
+ time = Benchmark.realtime { pipe("env PATH=$PATH:bin bundle exec rake assets:precompile 2>&1") }
49
+
50
+ if $?.success?
51
+ log "assets_precompile", :status => "success"
52
+ puts "Asset precompilation completed (#{"%.2f" % time}s)"
53
+ else
54
+ log "assets_precompile", :status => "failure"
55
+ error "Precompiling assets failed."
56
+ end
57
+ end
58
+ else
59
+ puts "Error detecting the assets:precompile task"
60
+ end
61
+ end
62
+ end
63
+
64
+ def create_database_yml
65
+ end
66
+ end
@@ -0,0 +1,681 @@
1
+ require "tmpdir"
2
+ require "rubygems"
3
+ require "language_pack"
4
+ require "language_pack/base"
5
+ require "language_pack/bundler_lockfile"
6
+
7
+ # base Ruby Language Pack. This is for any base ruby app.
8
+ class LanguagePack::Ruby < LanguagePack::Base
9
+ include LanguagePack::BundlerLockfile
10
+ extend LanguagePack::BundlerLockfile
11
+
12
+ BUILDPACK_VERSION = "v61"
13
+ LIBYAML_VERSION = "0.1.4"
14
+ LIBYAML_PATH = "libyaml-#{LIBYAML_VERSION}"
15
+ BUNDLER_VERSION = "1.3.2"
16
+ BUNDLER_GEM_PATH = "bundler-#{BUNDLER_VERSION}"
17
+ NODE_VERSION = "0.4.7"
18
+ NODE_JS_BINARY_PATH = "node-#{NODE_VERSION}"
19
+ JVM_BASE_URL = "http://heroku-jdk.s3.amazonaws.com"
20
+ JVM_VERSION = "openjdk7-latest"
21
+
22
+ # detects if this is a valid Ruby app
23
+ # @return [Boolean] true if it's a Ruby app
24
+ def self.use?
25
+ File.exist?("Gemfile")
26
+ end
27
+
28
+ def self.lockfile_parser
29
+ require "bundler"
30
+ Bundler::LockfileParser.new(File.read("Gemfile.lock"))
31
+ end
32
+
33
+ def self.gem_version(name)
34
+ gem_version = nil
35
+ bootstrap_bundler do |bundler_path|
36
+ $: << "#{bundler_path}/gems/bundler-#{LanguagePack::Ruby::BUNDLER_VERSION}/lib"
37
+ gem = lockfile_parser.specs.detect {|gem| gem.name == name }
38
+ gem_version = gem.version if gem
39
+ end
40
+
41
+ gem_version
42
+ end
43
+
44
+ def name
45
+ "Ruby"
46
+ end
47
+
48
+ def default_addons
49
+ add_dev_database_addon
50
+ end
51
+
52
+ def default_config_vars
53
+ vars = {
54
+ "LANG" => "en_US.UTF-8",
55
+ "PATH" => default_path,
56
+ "GEM_PATH" => slug_vendor_base,
57
+ }
58
+
59
+ ruby_version_jruby? ? vars.merge({
60
+ "JAVA_OPTS" => default_java_opts,
61
+ "JRUBY_OPTS" => default_jruby_opts,
62
+ "JAVA_TOOL_OPTIONS" => default_java_tool_options
63
+ }) : vars
64
+ end
65
+
66
+ def default_process_types
67
+ {
68
+ "rake" => "bundle exec rake",
69
+ "console" => "bundle exec irb"
70
+ }
71
+ end
72
+
73
+ def compile
74
+ Dir.chdir(build_path)
75
+ remove_vendor_bundle
76
+ install_ruby
77
+ install_jvm
78
+ setup_language_pack_environment
79
+ setup_profiled
80
+ allow_git do
81
+ install_language_pack_gems
82
+ build_bundler
83
+ create_database_yml
84
+ install_binaries
85
+ run_assets_precompile_rake_task
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ # the base PATH environment variable to be used
92
+ # @return [String] the resulting PATH
93
+ def default_path
94
+ "bin:#{slug_vendor_base}/bin:/usr/local/bin:/usr/bin:/bin"
95
+ end
96
+
97
+ # the relative path to the bundler directory of gems
98
+ # @return [String] resulting path
99
+ def slug_vendor_base
100
+ if @slug_vendor_base
101
+ @slug_vendor_base
102
+ elsif @ruby_version == "ruby-1.8.7"
103
+ @slug_vendor_base = "vendor/bundle/1.8"
104
+ else
105
+ @slug_vendor_base = run(%q(ruby -e "require 'rbconfig';puts \"vendor/bundle/#{RUBY_ENGINE}/#{RbConfig::CONFIG['ruby_version']}\"")).chomp
106
+ end
107
+ end
108
+
109
+ # the relative path to the vendored ruby directory
110
+ # @return [String] resulting path
111
+ def slug_vendor_ruby
112
+ "vendor/#{ruby_version}"
113
+ end
114
+
115
+ # the relative path to the vendored jvm
116
+ # @return [String] resulting path
117
+ def slug_vendor_jvm
118
+ "vendor/jvm"
119
+ end
120
+
121
+ # the absolute path of the build ruby to use during the buildpack
122
+ # @return [String] resulting path
123
+ def build_ruby_path
124
+ "/tmp/#{ruby_version}"
125
+ end
126
+
127
+ # fetch the ruby version from bundler
128
+ # @return [String, nil] returns the ruby version if detected or nil if none is detected
129
+ def ruby_version
130
+ return @ruby_version if @ruby_version_run
131
+
132
+ @ruby_version_run = true
133
+
134
+ bootstrap_bundler do |bundler_path|
135
+ old_system_path = "/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin"
136
+ @ruby_version = run_stdout("env PATH=#{old_system_path}:#{bundler_path}/bin GEM_PATH=#{bundler_path} bundle platform --ruby").chomp
137
+ end
138
+
139
+ if @ruby_version == "No ruby version specified" && ENV['RUBY_VERSION']
140
+ # for backwards compatibility.
141
+ # this will go away in the future
142
+ @ruby_version = ENV['RUBY_VERSION']
143
+ @ruby_version_env_var = true
144
+ elsif @ruby_version == "No ruby version specified"
145
+ @ruby_version = nil
146
+ else
147
+ @ruby_version = @ruby_version.sub('(', '').sub(')', '').split.join('-')
148
+ @ruby_version_env_var = false
149
+ end
150
+
151
+ @ruby_version
152
+ end
153
+
154
+ # determine if we're using rbx
155
+ # @return [Boolean] true if we are and false if we aren't
156
+ def ruby_version_rbx?
157
+ ruby_version ? ruby_version.match(/rbx-/) : false
158
+ end
159
+
160
+ # determine if we're using jruby
161
+ # @return [Boolean] true if we are and false if we aren't
162
+ def ruby_version_jruby?
163
+ @ruby_version_jruby ||= ruby_version ? ruby_version.match(/jruby-/) : false
164
+ end
165
+
166
+ # default JAVA_OPTS
167
+ # return [String] string of JAVA_OPTS
168
+ def default_java_opts
169
+ "-Xmx384m -Xss512k -XX:+UseCompressedOops -Dfile.encoding=UTF-8"
170
+ end
171
+
172
+ # default JRUBY_OPTS
173
+ # return [String] string of JRUBY_OPTS
174
+ def default_jruby_opts
175
+ "-Xcompile.invokedynamic=true"
176
+ end
177
+
178
+ # default JAVA_TOOL_OPTIONS
179
+ # return [String] string of JAVA_TOOL_OPTIONS
180
+ def default_java_tool_options
181
+ "-Djava.rmi.server.useCodebaseOnly=true"
182
+ end
183
+
184
+ # list the available valid ruby versions
185
+ # @note the value is memoized
186
+ # @return [Array] list of Strings of the ruby versions available
187
+ def ruby_versions
188
+ return @ruby_versions if @ruby_versions
189
+
190
+ Dir.mktmpdir("ruby_versions-") do |tmpdir|
191
+ Dir.chdir(tmpdir) do
192
+ run("curl -O #{VENDOR_URL}/ruby_versions.yml")
193
+ @ruby_versions = YAML::load_file("ruby_versions.yml")
194
+ end
195
+ end
196
+
197
+ @ruby_versions
198
+ end
199
+
200
+ # sets up the environment variables for the build process
201
+ def setup_language_pack_environment
202
+ setup_ruby_install_env
203
+
204
+ config_vars = default_config_vars.each do |key, value|
205
+ ENV[key] ||= value
206
+ end
207
+ ENV["GEM_HOME"] = slug_vendor_base
208
+ ENV["PATH"] = "#{ruby_install_binstub_path}:#{config_vars["PATH"]}"
209
+ end
210
+
211
+ # sets up the profile.d script for this buildpack
212
+ def setup_profiled
213
+ set_env_override "GEM_PATH", "$HOME/#{slug_vendor_base}:$GEM_PATH"
214
+ set_env_default "LANG", "en_US.UTF-8"
215
+ set_env_override "PATH", "$HOME/bin:$HOME/#{slug_vendor_base}/bin:$PATH"
216
+
217
+ if ruby_version_jruby?
218
+ set_env_default "JAVA_OPTS", default_java_opts
219
+ set_env_default "JRUBY_OPTS", default_jruby_opts
220
+ set_env_default "JAVA_TOOL_OPTIONS", default_java_tool_options
221
+ end
222
+ end
223
+
224
+ # determines if a build ruby is required
225
+ # @return [Boolean] true if a build ruby is required
226
+ def build_ruby?
227
+ @build_ruby ||= !ruby_version_rbx? && !ruby_version_jruby? && !%w{ruby-1.9.3 ruby-2.0.0}.include?(ruby_version)
228
+ end
229
+
230
+ # install the vendored ruby
231
+ # @return [Boolean] true if it installs the vendored ruby and false otherwise
232
+ def install_ruby
233
+ return false unless ruby_version
234
+
235
+ invalid_ruby_version_message = <<ERROR
236
+ Invalid RUBY_VERSION specified: #{ruby_version}
237
+ Valid versions: #{ruby_versions.join(", ")}
238
+ ERROR
239
+
240
+ if build_ruby?
241
+ FileUtils.mkdir_p(build_ruby_path)
242
+ Dir.chdir(build_ruby_path) do
243
+ ruby_vm = ruby_version_rbx? ? "rbx" : "ruby"
244
+ run("curl #{VENDOR_URL}/#{ruby_version.sub(ruby_vm, "#{ruby_vm}-build")}.tgz -s -o - | tar zxf -")
245
+ end
246
+ error invalid_ruby_version_message unless $?.success?
247
+ end
248
+
249
+ FileUtils.mkdir_p(slug_vendor_ruby)
250
+ Dir.chdir(slug_vendor_ruby) do
251
+ run("curl #{VENDOR_URL}/#{ruby_version}.tgz -s -o - | tar zxf -")
252
+ end
253
+ error invalid_ruby_version_message unless $?.success?
254
+
255
+ bin_dir = "bin"
256
+ FileUtils.mkdir_p bin_dir
257
+ Dir["#{slug_vendor_ruby}/bin/*"].each do |bin|
258
+ run("ln -s ../#{bin} #{bin_dir}")
259
+ end
260
+
261
+ if !@ruby_version_env_var
262
+ topic "Using Ruby version: #{ruby_version}"
263
+ else
264
+ topic "Using RUBY_VERSION: #{ruby_version}"
265
+ puts "WARNING: RUBY_VERSION support has been deprecated and will be removed entirely on August 1, 2012."
266
+ puts "See https://devcenter.heroku.com/articles/ruby-versions#selecting_a_version_of_ruby for more information."
267
+ end
268
+
269
+ true
270
+ end
271
+
272
+ # vendors JVM into the slug for JRuby
273
+ def install_jvm
274
+ if ruby_version_jruby?
275
+ topic "Installing JVM: #{JVM_VERSION}"
276
+
277
+ FileUtils.mkdir_p(slug_vendor_jvm)
278
+ Dir.chdir(slug_vendor_jvm) do
279
+ run("curl #{JVM_BASE_URL}/#{JVM_VERSION}.tar.gz -s -o - | tar xzf -")
280
+ end
281
+
282
+ bin_dir = "bin"
283
+ FileUtils.mkdir_p bin_dir
284
+ Dir["#{slug_vendor_jvm}/bin/*"].each do |bin|
285
+ run("ln -s ../#{bin} #{bin_dir}")
286
+ end
287
+ end
288
+ end
289
+
290
+ # find the ruby install path for its binstubs during build
291
+ # @return [String] resulting path or empty string if ruby is not vendored
292
+ def ruby_install_binstub_path
293
+ @ruby_install_binstub_path ||=
294
+ if build_ruby?
295
+ "#{build_ruby_path}/bin"
296
+ elsif ruby_version
297
+ "#{slug_vendor_ruby}/bin"
298
+ else
299
+ ""
300
+ end
301
+ end
302
+
303
+ # setup the environment so we can use the vendored ruby
304
+ def setup_ruby_install_env
305
+ ENV["PATH"] = "#{ruby_install_binstub_path}:#{ENV["PATH"]}"
306
+
307
+ if ruby_version_jruby?
308
+ ENV['JAVA_OPTS'] = default_java_opts
309
+ end
310
+ end
311
+
312
+ # list of default gems to vendor into the slug
313
+ # @return [Array] resulting list of gems
314
+ def gems
315
+ [BUNDLER_GEM_PATH]
316
+ end
317
+
318
+ # installs vendored gems into the slug
319
+ def install_language_pack_gems
320
+ FileUtils.mkdir_p(slug_vendor_base)
321
+ Dir.chdir(slug_vendor_base) do |dir|
322
+ gems.each do |gem|
323
+ run("curl #{VENDOR_URL}/#{gem}.tgz -s -o - | tar xzf -")
324
+ end
325
+ Dir["bin/*"].each {|path| run("chmod 755 #{path}") }
326
+ end
327
+ end
328
+
329
+ # default set of binaries to install
330
+ # @return [Array] resulting list
331
+ def binaries
332
+ add_node_js_binary
333
+ end
334
+
335
+ # vendors binaries into the slug
336
+ def install_binaries
337
+ binaries.each {|binary| install_binary(binary) }
338
+ Dir["bin/*"].each {|path| run("chmod +x #{path}") }
339
+ end
340
+
341
+ # vendors individual binary into the slug
342
+ # @param [String] name of the binary package from S3.
343
+ # Example: https://s3.amazonaws.com/language-pack-ruby/node-0.4.7.tgz, where name is "node-0.4.7"
344
+ def install_binary(name)
345
+ bin_dir = "bin"
346
+ FileUtils.mkdir_p bin_dir
347
+ Dir.chdir(bin_dir) do |dir|
348
+ run("curl #{VENDOR_URL}/#{name}.tgz -s -o - | tar xzf -")
349
+ end
350
+ end
351
+
352
+ # removes a binary from the slug
353
+ # @param [String] relative path of the binary on the slug
354
+ def uninstall_binary(path)
355
+ FileUtils.rm File.join('bin', File.basename(path)), :force => true
356
+ end
357
+
358
+ # install libyaml into the LP to be referenced for psych compilation
359
+ # @param [String] tmpdir to store the libyaml files
360
+ def install_libyaml(dir)
361
+ FileUtils.mkdir_p dir
362
+ Dir.chdir(dir) do |dir|
363
+ run("curl #{VENDOR_URL}/#{LIBYAML_PATH}.tgz -s -o - | tar xzf -")
364
+ end
365
+ end
366
+
367
+ # remove `vendor/bundle` that comes from the git repo
368
+ # in case there are native ext.
369
+ # users should be using `bundle pack` instead.
370
+ # https://github.com/heroku/heroku-buildpack-ruby/issues/21
371
+ def remove_vendor_bundle
372
+ if File.exists?("vendor/bundle")
373
+ topic "WARNING: Removing `vendor/bundle`."
374
+ puts "Checking in `vendor/bundle` is not supported. Please remove this directory"
375
+ puts "and add it to your .gitignore. To vendor your gems with Bundler, use"
376
+ puts "`bundle pack` instead."
377
+ FileUtils.rm_rf("vendor/bundle")
378
+ end
379
+ end
380
+
381
+ # runs bundler to install the dependencies
382
+ def build_bundler
383
+ log("bundle") do
384
+ bundle_without = ENV["BUNDLE_WITHOUT"] || "development:test"
385
+ bundle_command = "bundle install --without #{bundle_without} --path vendor/bundle --binstubs vendor/bundle/bin"
386
+
387
+ unless File.exist?("Gemfile.lock")
388
+ error "Gemfile.lock is required. Please run \"bundle install\" locally\nand commit your Gemfile.lock."
389
+ end
390
+
391
+ if has_windows_gemfile_lock?
392
+ topic "WARNING: Removing `Gemfile.lock` because it was generated on Windows."
393
+ puts "Bundler will do a full resolve so native gems are handled properly."
394
+ puts "This may result in unexpected gem versions being used in your app."
395
+
396
+ log("bundle", "has_windows_gemfile_lock")
397
+ File.unlink("Gemfile.lock")
398
+ else
399
+ # using --deployment is preferred if we can
400
+ bundle_command += " --deployment"
401
+ cache_load ".bundle"
402
+ end
403
+
404
+ version = run_stdout("bundle version").strip
405
+ topic("Installing dependencies using #{version}")
406
+
407
+ load_bundler_cache
408
+
409
+ bundler_output = ""
410
+ Dir.mktmpdir("libyaml-") do |tmpdir|
411
+ libyaml_dir = "#{tmpdir}/#{LIBYAML_PATH}"
412
+ install_libyaml(libyaml_dir)
413
+
414
+ # need to setup compile environment for the psych gem
415
+ yaml_include = File.expand_path("#{libyaml_dir}/include")
416
+ yaml_lib = File.expand_path("#{libyaml_dir}/lib")
417
+ pwd = run("pwd").chomp
418
+ bundler_path = "#{pwd}/#{slug_vendor_base}/gems/#{BUNDLER_GEM_PATH}/lib"
419
+ # we need to set BUNDLE_CONFIG and BUNDLE_GEMFILE for
420
+ # codon since it uses bundler.
421
+ env_vars = "env BUNDLE_GEMFILE=#{pwd}/Gemfile BUNDLE_CONFIG=#{pwd}/.bundle/config CPATH=#{yaml_include}:$CPATH CPPATH=#{yaml_include}:$CPPATH LIBRARY_PATH=#{yaml_lib}:$LIBRARY_PATH RUBYOPT=\"#{syck_hack}\""
422
+ env_vars += " BUNDLER_LIB_PATH=#{bundler_path}" if ruby_version.match(/^ruby-1\.8\.7/)
423
+ puts "Running: #{bundle_command}"
424
+ bundler_output << pipe("#{env_vars} #{bundle_command} --no-clean 2>&1")
425
+
426
+ end
427
+
428
+ if $?.success?
429
+ log "bundle", :status => "success"
430
+ puts "Cleaning up the bundler cache."
431
+ pipe "bundle clean 2> /dev/null"
432
+ cache_store ".bundle"
433
+ cache_store "vendor/bundle"
434
+
435
+ # Keep gem cache out of the slug
436
+ FileUtils.rm_rf("#{slug_vendor_base}/cache")
437
+
438
+ # symlink binstubs
439
+ bin_dir = "bin"
440
+ FileUtils.mkdir_p bin_dir
441
+ Dir["#{slug_vendor_base}/bin/*"].each do |bin|
442
+ run("ln -s ../#{bin} #{bin_dir}") unless File.exist?("#{bin_dir}/#{bin}")
443
+ end
444
+ else
445
+ log "bundle", :status => "failure"
446
+ error_message = "Failed to install gems via Bundler."
447
+ if bundler_output.match(/Installing sqlite3 \([\w.]+\) with native extensions\s+Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension./)
448
+ error_message += <<ERROR
449
+
450
+
451
+ Detected sqlite3 gem which is not supported on Heroku.
452
+ http://devcenter.heroku.com/articles/how-do-i-use-sqlite3-for-development
453
+ ERROR
454
+ end
455
+
456
+ error error_message
457
+ end
458
+ end
459
+ end
460
+
461
+ # RUBYOPT line that requires syck_hack file
462
+ # @return [String] require string if needed or else an empty string
463
+ def syck_hack
464
+ syck_hack_file = File.expand_path(File.join(File.dirname(__FILE__), "../../vendor/syck_hack"))
465
+ ruby_version = run_stdout('ruby -e "puts RUBY_VERSION"').chomp
466
+ # < 1.9.3 includes syck, so we need to use the syck hack
467
+ if Gem::Version.new(ruby_version) < Gem::Version.new("1.9.3")
468
+ "-r#{syck_hack_file}"
469
+ else
470
+ ""
471
+ end
472
+ end
473
+
474
+ # writes ERB based database.yml for Rails. The database.yml uses the DATABASE_URL from the environment during runtime.
475
+ def create_database_yml
476
+ log("create_database_yml") do
477
+ return unless File.directory?("config")
478
+ topic("Writing config/database.yml to read from DATABASE_URL")
479
+ File.open("config/database.yml", "w") do |file|
480
+ file.puts <<-DATABASE_YML
481
+ <%
482
+
483
+ require 'cgi'
484
+ require 'uri'
485
+
486
+ begin
487
+ uri = URI.parse(ENV["DATABASE_URL"])
488
+ rescue URI::InvalidURIError
489
+ raise "Invalid DATABASE_URL"
490
+ end
491
+
492
+ raise "No RACK_ENV or RAILS_ENV found" unless ENV["RAILS_ENV"] || ENV["RACK_ENV"]
493
+
494
+ def attribute(name, value, force_string = false)
495
+ if value
496
+ value_string =
497
+ if force_string
498
+ '"' + value + '"'
499
+ else
500
+ value
501
+ end
502
+ "\#{name}: \#{value_string}"
503
+ else
504
+ ""
505
+ end
506
+ end
507
+
508
+ adapter = uri.scheme
509
+ adapter = "postgresql" if adapter == "postgres"
510
+
511
+ database = (uri.path || "").split("/")[1]
512
+
513
+ username = uri.user
514
+ password = uri.password
515
+
516
+ host = uri.host
517
+ port = uri.port
518
+
519
+ params = CGI.parse(uri.query || "")
520
+
521
+ %>
522
+
523
+ <%= ENV["RAILS_ENV"] || ENV["RACK_ENV"] %>:
524
+ <%= attribute "adapter", adapter %>
525
+ <%= attribute "database", database %>
526
+ <%= attribute "username", username %>
527
+ <%= attribute "password", password, true %>
528
+ <%= attribute "host", host %>
529
+ <%= attribute "port", port %>
530
+
531
+ <% params.each do |key, value| %>
532
+ <%= key %>: <%= value.first %>
533
+ <% end %>
534
+ DATABASE_YML
535
+ end
536
+ end
537
+ end
538
+
539
+ # add bundler to the load path
540
+ # @note it sets a flag, so the path can only be loaded once
541
+ def add_bundler_to_load_path
542
+ return if @bundler_loadpath
543
+ $: << File.expand_path(Dir["#{slug_vendor_base}/gems/bundler*/lib"].first)
544
+ @bundler_loadpath = true
545
+ end
546
+
547
+ # detects whether the Gemfile.lock contains the Windows platform
548
+ # @return [Boolean] true if the Gemfile.lock was created on Windows
549
+ def has_windows_gemfile_lock?
550
+ lockfile_parser.platforms.detect do |platform|
551
+ /mingw|mswin/.match(platform.os) if platform.is_a?(Gem::Platform)
552
+ end
553
+ end
554
+
555
+ # detects if a gem is in the bundle.
556
+ # @param [String] name of the gem in question
557
+ # @return [String, nil] if it finds the gem, it will return the line from bundle show or nil if nothing is found.
558
+ def gem_is_bundled?(gem)
559
+ @bundler_gems ||= lockfile_parser.specs.map(&:name)
560
+ @bundler_gems.include?(gem)
561
+ end
562
+
563
+ # setup the lockfile parser
564
+ # @return [Bundler::LockfileParser] a Bundler::LockfileParser
565
+ def lockfile_parser
566
+ add_bundler_to_load_path
567
+ @lockfile_parser ||= LanguagePack::Ruby.lockfile_parser
568
+ end
569
+
570
+ # detects if a rake task is defined in the app
571
+ # @param [String] the task in question
572
+ # @return [Boolean] true if the rake task is defined in the app
573
+ def rake_task_defined?(task)
574
+ run("env PATH=$PATH bundle exec rake #{task} --dry-run") && $?.success?
575
+ end
576
+
577
+ # executes the block with GIT_DIR environment variable removed since it can mess with the current working directory git thinks it's in
578
+ # @param [block] block to be executed in the GIT_DIR free context
579
+ def allow_git(&blk)
580
+ git_dir = ENV.delete("GIT_DIR") # can mess with bundler
581
+ blk.call
582
+ ENV["GIT_DIR"] = git_dir
583
+ end
584
+
585
+ # decides if we need to enable the dev database addon
586
+ # @return [Array] the database addon if the pg gem is detected or an empty Array if it isn't.
587
+ def add_dev_database_addon
588
+ gem_is_bundled?("pg") ? ['heroku-postgresql:dev'] : []
589
+ end
590
+
591
+ # decides if we need to install the node.js binary
592
+ # @note execjs will blow up if no JS RUNTIME is detected and is loaded.
593
+ # @return [Array] the node.js binary path if we need it or an empty Array
594
+ def add_node_js_binary
595
+ gem_is_bundled?('execjs') ? [NODE_JS_BINARY_PATH] : []
596
+ end
597
+
598
+ def run_assets_precompile_rake_task
599
+ if rake_task_defined?("assets:precompile")
600
+ require 'benchmark'
601
+
602
+ topic "Running: rake assets:precompile"
603
+ time = Benchmark.realtime { pipe("env PATH=$PATH:bin bundle exec rake assets:precompile 2>&1") }
604
+ if $?.success?
605
+ puts "Asset precompilation completed (#{"%.2f" % time}s)"
606
+ end
607
+ end
608
+ end
609
+
610
+ def bundler_cache
611
+ "vendor/bundle"
612
+ end
613
+
614
+ def load_bundler_cache
615
+ cache_load "vendor"
616
+
617
+ full_ruby_version = run_stdout(%q(ruby -v)).chomp
618
+ rubygems_version = run_stdout(%q(gem -v)).chomp
619
+ heroku_metadata = "vendor/heroku"
620
+ old_rubygems_version = nil
621
+ ruby_version_cache = "#{heroku_metadata}/ruby_version"
622
+ buildpack_version_cache = "#{heroku_metadata}/buildpack_version"
623
+ bundler_version_cache = "#{heroku_metadata}/bundler_version"
624
+ rubygems_version_cache = "#{heroku_metadata}/rubygems_version"
625
+
626
+ old_rubygems_version = File.read(rubygems_version_cache).chomp if File.exists?(rubygems_version_cache)
627
+
628
+ # fix bug from v37 deploy
629
+ if File.exists?("vendor/ruby_version")
630
+ puts "Broken cache detected. Purging build cache."
631
+ cache_clear("vendor")
632
+ FileUtils.rm_rf("vendor/ruby_version")
633
+ purge_bundler_cache
634
+ # fix bug introduced in v38
635
+ elsif !File.exists?(buildpack_version_cache) && File.exists?(ruby_version_cache)
636
+ puts "Broken cache detected. Purging build cache."
637
+ purge_bundler_cache
638
+ elsif cache_exists?(bundler_cache) && File.exists?(ruby_version_cache) && full_ruby_version != File.read(ruby_version_cache).chomp
639
+ puts "Ruby version change detected. Clearing bundler cache."
640
+ puts "Old: #{File.read(ruby_version_cache).chomp}"
641
+ puts "New: #{full_ruby_version}"
642
+ purge_bundler_cache
643
+ end
644
+
645
+ # fix git gemspec bug from Bundler 1.3.0+ upgrade
646
+ if File.exists?(bundler_cache) && !File.exists?(bundler_version_cache) && !run("find vendor/bundle/*/*/bundler/gems/*/ -name *.gemspec").include?("No such file or directory")
647
+ puts "Old bundler cache detected. Clearing bundler cache."
648
+ purge_bundler_cache
649
+ end
650
+
651
+ # fix for https://github.com/heroku/heroku-buildpack-ruby/issues/86
652
+ if (!File.exists?(rubygems_version_cache) ||
653
+ (old_rubygems_version == "2.0.0" && old_rubygems_version != rubygems_version)) &&
654
+ File.exists?(ruby_version_cache) && File.read(ruby_version_cache).chomp.include?("ruby 2.0.0p0")
655
+ puts "Updating to rubygems #{rubygems_version}. Clearing bundler cache."
656
+ purge_bundler_cache
657
+ end
658
+
659
+ FileUtils.mkdir_p(heroku_metadata)
660
+ File.open(ruby_version_cache, 'w') do |file|
661
+ file.puts full_ruby_version
662
+ end
663
+ File.open(buildpack_version_cache, 'w') do |file|
664
+ file.puts BUILDPACK_VERSION
665
+ end
666
+ File.open(bundler_version_cache, 'w') do |file|
667
+ file.puts BUNDLER_VERSION
668
+ end
669
+ File.open(rubygems_version_cache, 'w') do |file|
670
+ file.puts rubygems_version
671
+ end
672
+ cache_store heroku_metadata
673
+ end
674
+
675
+ def purge_bundler_cache
676
+ FileUtils.rm_rf(bundler_cache)
677
+ cache_clear bundler_cache
678
+ # need to reinstall language pack gems
679
+ install_language_pack_gems
680
+ end
681
+ end