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.
- checksums.yaml +4 -4
- data/.travis.yml +14 -0
- data/CHANGELOG.md +4 -0
- data/README.md +2 -2
- data/Rakefile +6 -0
- data/hatchet.gemspec +3 -1
- data/hatchet.json +3 -2
- data/lib/hatchet.rb +2 -0
- data/lib/hatchet/anvil_app.rb +28 -18
- data/lib/hatchet/app.rb +19 -5
- data/lib/hatchet/git_app.rb +5 -2
- data/lib/hatchet/tasks.rb +17 -7
- data/lib/hatchet/version.rb +1 -1
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/.gitignore +4 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/CHANGELOG.md +378 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/Gemfile +10 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/LICENSE +9 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/README.md +192 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/Rakefile +358 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/compile +13 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/detect +12 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/bin/release +9 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/hatchet.json +25 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack.rb +27 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/base.rb +175 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/bundler_lockfile.rb +19 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/disable_deploys.rb +17 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/no_lockfile.rb +16 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rack.rb +43 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails2.rb +91 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails3.rb +86 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/rails4.rb +66 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/ruby.rb +681 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/lib/language_pack/shell_helpers.rb +62 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/bugs_spec.rb +11 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/no_lockfile_spec.rb +10 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails23_spec.rb +11 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails3_spec.rb +22 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rails4_spec.rb +12 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/rubies_spec.rb +38 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/spec/spec_helper.rb +35 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/support/s3/hmac +79 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/support/s3/s3 +223 -0
- data/test/fixtures/buildpacks/heroku-buildpack-ruby/vendor/syck_hack.rb +64 -0
- data/test/hatchet/allow_failure_anvil_test.rb +21 -0
- data/test/hatchet/allow_failure_git_test.rb +17 -0
- data/test/hatchet/anvil_test.rb +7 -7
- data/test/hatchet/config_test.rb +4 -2
- data/test/hatchet/git_test.rb +2 -2
- 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
|