heroku_hatchet 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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