mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
1344 lines
46 KiB
Text
1344 lines
46 KiB
Text
require "tmpdir"
|
|
require "digest/md5"
|
|
require "benchmark"
|
|
require "rubygems"
|
|
require "language_pack"
|
|
require "language_pack/base"
|
|
require "language_pack/ruby_version"
|
|
require "language_pack/helpers/nodebin"
|
|
require "language_pack/helpers/node_installer"
|
|
require "language_pack/helpers/yarn_installer"
|
|
require "language_pack/helpers/layer"
|
|
require "language_pack/helpers/binstub_check"
|
|
require "language_pack/version"
|
|
|
|
# base Ruby Language Pack. This is for any base ruby app.
|
|
class LanguagePack::Ruby < LanguagePack::Base
|
|
NAME = "ruby"
|
|
LIBYAML_VERSION = "0.1.7"
|
|
LIBYAML_PATH = "libyaml-#{LIBYAML_VERSION}"
|
|
RBX_BASE_URL = "http://binaries.rubini.us/heroku"
|
|
NODE_BP_PATH = "vendor/node/bin"
|
|
|
|
Layer = LanguagePack::Helpers::Layer
|
|
|
|
# detects if this is a valid Ruby app
|
|
# @return [Boolean] true if it's a Ruby app
|
|
def self.use?
|
|
instrument "ruby.use" do
|
|
File.exist?("Gemfile")
|
|
end
|
|
end
|
|
|
|
def self.bundler
|
|
@@bundler ||= LanguagePack::Helpers::BundlerWrapper.new.install
|
|
end
|
|
|
|
def bundler
|
|
self.class.bundler
|
|
end
|
|
|
|
def initialize(*args)
|
|
super(*args)
|
|
@fetchers[:mri] = LanguagePack::Fetcher.new(VENDOR_URL, @stack)
|
|
@fetchers[:rbx] = LanguagePack::Fetcher.new(RBX_BASE_URL, @stack)
|
|
@node_installer = LanguagePack::Helpers::NodeInstaller.new
|
|
@yarn_installer = LanguagePack::Helpers::YarnInstaller.new
|
|
end
|
|
|
|
def name
|
|
"Ruby"
|
|
end
|
|
|
|
def default_addons
|
|
instrument "ruby.default_addons" do
|
|
add_dev_database_addon
|
|
end
|
|
end
|
|
|
|
def default_config_vars
|
|
instrument "ruby.default_config_vars" do
|
|
vars = {
|
|
"LANG" => env("LANG") || "en_US.UTF-8",
|
|
}
|
|
|
|
ruby_version.jruby? ? vars.merge({
|
|
"JRUBY_OPTS" => default_jruby_opts
|
|
}) : vars
|
|
end
|
|
end
|
|
|
|
def default_process_types
|
|
instrument "ruby.default_process_types" do
|
|
{
|
|
"rake" => "bundle exec rake",
|
|
"console" => "bundle exec irb"
|
|
}
|
|
end
|
|
end
|
|
|
|
def best_practice_warnings
|
|
if bundler.has_gem?("asset_sync")
|
|
warn(<<-WARNING)
|
|
You are using the `asset_sync` gem.
|
|
This is not recommended.
|
|
See https://devcenter.heroku.com/articles/please-do-not-use-asset-sync for more information.
|
|
WARNING
|
|
end
|
|
end
|
|
|
|
def compile
|
|
instrument 'ruby.compile' do
|
|
# check for new app at the beginning of the compile
|
|
new_app?
|
|
Dir.chdir(build_path)
|
|
remove_vendor_bundle
|
|
warn_bundler_upgrade
|
|
warn_bad_binstubs
|
|
install_ruby(slug_vendor_ruby, build_ruby_path)
|
|
setup_language_pack_environment(
|
|
ruby_layer_path: File.expand_path("."),
|
|
gem_layer_path: File.expand_path("."),
|
|
bundle_path: "vendor/bundle",
|
|
bundle_default_without: "development:test"
|
|
)
|
|
allow_git do
|
|
install_bundler_in_app(slug_vendor_base)
|
|
load_bundler_cache
|
|
build_bundler
|
|
post_bundler
|
|
create_database_yml
|
|
install_binaries
|
|
run_assets_precompile_rake_task
|
|
end
|
|
config_detect
|
|
best_practice_warnings
|
|
warn_outdated_ruby
|
|
setup_profiled(ruby_layer_path: "$HOME", gem_layer_path: "$HOME") # $HOME is set to /app at run time
|
|
setup_export
|
|
cleanup
|
|
super
|
|
end
|
|
rescue => e
|
|
warn_outdated_ruby
|
|
raise e
|
|
end
|
|
|
|
|
|
def build
|
|
new_app?
|
|
remove_vendor_bundle
|
|
warn_bad_binstubs
|
|
ruby_layer = Layer.new(@layer_dir, "ruby", launch: true)
|
|
install_ruby("#{ruby_layer.path}/#{slug_vendor_ruby}")
|
|
ruby_layer.metadata[:version] = ruby_version.version
|
|
ruby_layer.metadata[:patchlevel] = ruby_version.patchlevel if ruby_version.patchlevel
|
|
ruby_layer.metadata[:engine] = ruby_version.engine.to_s
|
|
ruby_layer.metadata[:engine_version] = ruby_version.engine_version
|
|
ruby_layer.write
|
|
|
|
gem_layer = Layer.new(@layer_dir, "gems", launch: true, cache: true, build: true)
|
|
setup_language_pack_environment(
|
|
ruby_layer_path: ruby_layer.path,
|
|
gem_layer_path: gem_layer.path,
|
|
bundle_path: "#{gem_layer.path}/vendor/bundle",
|
|
bundle_default_without: "development:test"
|
|
)
|
|
allow_git do
|
|
# TODO install bundler in separate layer
|
|
topic "Loading Bundler Cache"
|
|
gem_layer.validate! do |metadata|
|
|
valid_bundler_cache?(gem_layer.path, gem_layer.metadata)
|
|
end
|
|
install_bundler_in_app("#{gem_layer.path}/#{slug_vendor_base}")
|
|
build_bundler
|
|
# TODO post_bundler might need to be done in a new layer
|
|
bundler.clean
|
|
gem_layer.metadata[:gems] = Digest::SHA2.hexdigest(File.read("Gemfile.lock"))
|
|
gem_layer.metadata[:stack] = @stack
|
|
gem_layer.metadata[:ruby_version] = run_stdout(%q(ruby -v)).strip
|
|
gem_layer.metadata[:rubygems_version] = run_stdout(%q(gem -v)).strip
|
|
gem_layer.metadata[:buildpack_version] = BUILDPACK_VERSION
|
|
gem_layer.write
|
|
|
|
create_database_yml
|
|
# TODO replace this with multibuildpack stuff? put binaries in their own layer?
|
|
install_binaries
|
|
run_assets_precompile_rake_task
|
|
end
|
|
setup_profiled(ruby_layer_path: ruby_layer.path, gem_layer_path: gem_layer.path)
|
|
setup_export(gem_layer)
|
|
config_detect
|
|
best_practice_warnings
|
|
cleanup
|
|
|
|
super
|
|
end
|
|
|
|
def cleanup
|
|
end
|
|
|
|
def config_detect
|
|
end
|
|
|
|
private
|
|
|
|
# A bad shebang line looks like this:
|
|
#
|
|
# ```
|
|
# #!/usr/bin/env ruby2.5
|
|
# ```
|
|
#
|
|
# Since `ruby2.5` is not a valid binary name
|
|
#
|
|
def warn_bad_binstubs
|
|
check = LanguagePack::Helpers::BinstubCheck.new(app_root_dir: Dir.pwd, warn_object: self)
|
|
check.call
|
|
end
|
|
|
|
def default_malloc_arena_max?
|
|
return true if @metadata.exists?("default_malloc_arena_max")
|
|
return @metadata.touch("default_malloc_arena_max") if new_app?
|
|
|
|
return false
|
|
end
|
|
|
|
def warn_bundler_upgrade
|
|
old_bundler_version = @metadata.read("bundler_version").strip if @metadata.exists?("bundler_version")
|
|
|
|
if old_bundler_version && old_bundler_version != bundler.version
|
|
warn(<<-WARNING, inline: true)
|
|
Your app was upgraded to bundler #{ bundler.version }.
|
|
Previously you had a successful deploy with bundler #{ old_bundler_version }.
|
|
|
|
If you see problems related to the bundler version please refer to:
|
|
https://devcenter.heroku.com/articles/bundler-version#known-upgrade-issues
|
|
|
|
WARNING
|
|
end
|
|
end
|
|
|
|
# For example "vendor/bundle/ruby/2.6.0"
|
|
def self.slug_vendor_base
|
|
@slug_vendor_base ||= begin
|
|
command = %q(ruby -e "require 'rbconfig';puts \"vendor/bundle/#{RUBY_ENGINE}/#{RbConfig::CONFIG['ruby_version']}\"")
|
|
out = run_no_pipe(command, user_env: true).strip
|
|
error "Problem detecting bundler vendor directory: #{out}" unless $?.success?
|
|
out
|
|
end
|
|
end
|
|
|
|
# the relative path to the bundler directory of gems
|
|
# @return [String] resulting path
|
|
def slug_vendor_base
|
|
instrument 'ruby.slug_vendor_base' do
|
|
@slug_vendor_base ||= self.class.slug_vendor_base
|
|
end
|
|
end
|
|
|
|
# the relative path to the vendored ruby directory
|
|
# @return [String] resulting path
|
|
def slug_vendor_ruby
|
|
"vendor/#{ruby_version.version_without_patchlevel}"
|
|
end
|
|
|
|
# the absolute path of the build ruby to use during the buildpack
|
|
# @return [String] resulting path
|
|
def build_ruby_path
|
|
"/tmp/#{ruby_version.version_without_patchlevel}"
|
|
end
|
|
|
|
# fetch the ruby version from bundler
|
|
# @return [String, nil] returns the ruby version if detected or nil if none is detected
|
|
def ruby_version
|
|
instrument 'ruby.ruby_version' do
|
|
return @ruby_version if @ruby_version
|
|
new_app = !File.exist?("vendor/heroku")
|
|
last_version_file = "buildpack_ruby_version"
|
|
last_version = nil
|
|
last_version = @metadata.read(last_version_file).strip if @metadata.exists?(last_version_file)
|
|
|
|
@ruby_version = LanguagePack::RubyVersion.new(bundler.ruby_version,
|
|
is_new: new_app,
|
|
last_version: last_version)
|
|
return @ruby_version
|
|
end
|
|
end
|
|
|
|
def set_default_web_concurrency
|
|
<<-EOF
|
|
case $(ulimit -u) in
|
|
256)
|
|
export HEROKU_RAM_LIMIT_MB=${HEROKU_RAM_LIMIT_MB:-512}
|
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-2}
|
|
;;
|
|
512)
|
|
export HEROKU_RAM_LIMIT_MB=${HEROKU_RAM_LIMIT_MB:-1024}
|
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-4}
|
|
;;
|
|
16384)
|
|
export HEROKU_RAM_LIMIT_MB=${HEROKU_RAM_LIMIT_MB:-2560}
|
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-8}
|
|
;;
|
|
32768)
|
|
export HEROKU_RAM_LIMIT_MB=${HEROKU_RAM_LIMIT_MB:-6144}
|
|
export WEB_CONCURRENCY=${WEB_CONCURRENCY:-16}
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
EOF
|
|
end
|
|
|
|
# default JRUBY_OPTS
|
|
# return [String] string of JRUBY_OPTS
|
|
def default_jruby_opts
|
|
"-Xcompile.invokedynamic=false"
|
|
end
|
|
|
|
# sets up the environment variables for the build process
|
|
def setup_language_pack_environment(ruby_layer_path:, gem_layer_path:, bundle_path:, bundle_default_without:)
|
|
instrument 'ruby.setup_language_pack_environment' do
|
|
if ruby_version.jruby?
|
|
ENV["PATH"] += ":bin"
|
|
ENV["JRUBY_OPTS"] = env('JRUBY_BUILD_OPTS') || env('JRUBY_OPTS')
|
|
end
|
|
setup_ruby_install_env(ruby_layer_path)
|
|
|
|
# By default Node can address 1.5GB of memory, a limitation it inherits from
|
|
# the underlying v8 engine. This can occasionally cause issues during frontend
|
|
# builds where memory use can exceed this threshold.
|
|
#
|
|
# This passes an argument to all Node processes during the build, so that they
|
|
# can take advantage of all available memory on the build dynos.
|
|
ENV["NODE_OPTIONS"] ||= "--max_old_space_size=2560"
|
|
|
|
# TODO when buildpack-env-args rolls out, we can get rid of
|
|
# ||= and the manual setting below
|
|
default_config_vars.each do |key, value|
|
|
ENV[key] ||= value
|
|
end
|
|
|
|
paths = []
|
|
gem_path = "#{gem_layer_path}/#{slug_vendor_base}"
|
|
ENV["GEM_PATH"] = gem_path
|
|
ENV["GEM_HOME"] = gem_path
|
|
|
|
ENV["DISABLE_SPRING"] = "1"
|
|
|
|
# Rails has a binstub for yarn that doesn't work for all applications
|
|
# we need to ensure that yarn comes before local bin dir for that case
|
|
paths << yarn_preinstall_bin_path if yarn_preinstalled?
|
|
|
|
# Need to remove `./bin` folder since it links to the wrong --prefix ruby binstubs breaking require in Ruby 1.9.2 and 1.8.7.
|
|
# Because for 1.9.2 and 1.8.7 there is a "build" ruby and a non-"build" Ruby
|
|
paths << "#{File.expand_path(".")}/bin" unless ruby_version.ruby_192_or_lower?
|
|
|
|
paths << "#{gem_layer_path}/#{bundler_binstubs_path}" # Binstubs from bundler, eg. vendor/bundle/bin
|
|
paths << "#{gem_layer_path}/#{slug_vendor_base}/bin" # Binstubs from rubygems, eg. vendor/bundle/ruby/2.6.0/bin
|
|
paths << ENV["PATH"]
|
|
|
|
ENV["PATH"] = paths.join(":")
|
|
|
|
ENV["BUNDLE_WITHOUT"] = env("BUNDLE_WITHOUT") || bundle_default_without
|
|
if ENV["BUNDLE_WITHOUT"].include?(' ')
|
|
ENV["BUNDLE_WITHOUT"] = ENV["BUNDLE_WITHOUT"].tr(' ', ':')
|
|
|
|
warn("Your BUNDLE_WITHOUT contains a space, we are converting it to a colon `:` BUNDLE_WITHOUT=#{ENV["BUNDLE_WITHOUT"]}", inline: true)
|
|
end
|
|
ENV["BUNDLE_PATH"] = bundle_path
|
|
ENV["BUNDLE_BIN"] = bundler_binstubs_path
|
|
ENV["BUNDLE_DEPLOYMENT"] = "1"
|
|
ENV["BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE"] = "1" if bundler.needs_ruby_global_append_path?
|
|
end
|
|
end
|
|
|
|
# Sets up the environment variables for subsequent processes run by
|
|
# muiltibuildpack. We can't use profile.d because $HOME isn't set up
|
|
def setup_export(layer = nil)
|
|
instrument 'ruby.setup_export' do
|
|
if layer
|
|
paths = ENV["PATH"]
|
|
else
|
|
paths = ENV["PATH"].split(":").map do |path|
|
|
/^\/.*/ !~ path ? "#{build_path}/#{path}" : path
|
|
end.join(":")
|
|
end
|
|
|
|
# TODO ensure path exported is correct
|
|
set_export_path "PATH", paths, layer
|
|
|
|
if layer
|
|
gem_path = "#{layer.path}/#{slug_vendor_base}"
|
|
else
|
|
gem_path = "#{build_path}/#{slug_vendor_base}"
|
|
end
|
|
set_export_path "GEM_PATH", gem_path, layer
|
|
set_export_default "LANG", "en_US.UTF-8", layer
|
|
|
|
# TODO handle jruby
|
|
if ruby_version.jruby?
|
|
set_export_default "JRUBY_OPTS", default_jruby_opts
|
|
end
|
|
|
|
set_export_default "BUNDLE_PATH", ENV["BUNDLE_PATH"], layer
|
|
set_export_default "BUNDLE_WITHOUT", ENV["BUNDLE_WITHOUT"], layer
|
|
set_export_default "BUNDLE_BIN", ENV["BUNDLE_BIN"], layer
|
|
set_export_default "BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE", ENV["BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE"], layer if bundler.needs_ruby_global_append_path?
|
|
set_export_default "BUNDLE_DEPLOYMENT", ENV["BUNDLE_DEPLOYMENT"], layer if ENV["BUNDLE_DEPLOYMENT"] # Unset on windows since we delete the Gemfile.lock
|
|
end
|
|
end
|
|
|
|
# sets up the profile.d script for this buildpack
|
|
def setup_profiled(ruby_layer_path: , gem_layer_path: )
|
|
instrument 'setup_profiled' do
|
|
profiled_path = []
|
|
|
|
# Rails has a binstub for yarn that doesn't work for all applications
|
|
# we need to ensure that yarn comes before local bin dir for that case
|
|
if yarn_preinstalled?
|
|
profiled_path << yarn_preinstall_bin_path.gsub(File.expand_path("."), "$HOME")
|
|
elsif has_yarn_binary?
|
|
profiled_path << "#{ruby_layer_path}/vendor/#{@yarn_installer.binary_path}"
|
|
end
|
|
profiled_path << "$HOME/bin" # /app in production
|
|
profiled_path << "#{gem_layer_path}/#{bundler_binstubs_path}" # Binstubs from bundler, eg. vendor/bundle/bin
|
|
profiled_path << "#{gem_layer_path}/#{slug_vendor_base}/bin" # Binstubs from rubygems, eg. vendor/bundle/ruby/2.6.0/bin
|
|
profiled_path << "$PATH"
|
|
|
|
set_env_default "LANG", "en_US.UTF-8"
|
|
set_env_override "GEM_PATH", "#{gem_layer_path}/#{slug_vendor_base}:$GEM_PATH"
|
|
set_env_override "PATH", profiled_path.join(":")
|
|
set_env_override "DISABLE_SPRING", "1"
|
|
|
|
set_env_default "MALLOC_ARENA_MAX", "2" if default_malloc_arena_max?
|
|
|
|
web_concurrency = env("SENSIBLE_DEFAULTS") ? set_default_web_concurrency : ""
|
|
add_to_profiled(web_concurrency, filename: "WEB_CONCURRENCY.sh", mode: "w") # always write that file, even if its empty (meaning no defaults apply), for interop with other buildpacks - and we overwrite the file rather than appending (which is the default)
|
|
|
|
# TODO handle JRUBY
|
|
if ruby_version.jruby?
|
|
set_env_default "JRUBY_OPTS", default_jruby_opts
|
|
end
|
|
|
|
set_env_default "BUNDLE_PATH", ENV["BUNDLE_PATH"]
|
|
set_env_default "BUNDLE_WITHOUT", ENV["BUNDLE_WITHOUT"]
|
|
set_env_default "BUNDLE_BIN", ENV["BUNDLE_BIN"]
|
|
set_env_default "BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE", ENV["BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE"] if bundler.needs_ruby_global_append_path?
|
|
set_env_default "BUNDLE_DEPLOYMENT", ENV["BUNDLE_DEPLOYMENT"] if ENV["BUNDLE_DEPLOYMENT"] # Unset on windows since we delete the Gemfile.lock
|
|
end
|
|
end
|
|
|
|
def warn_outdated_ruby
|
|
return unless defined?(@outdated_version_check)
|
|
|
|
@warn_outdated ||= begin
|
|
@outdated_version_check.join
|
|
|
|
warn_outdated_minor
|
|
warn_outdated_eol
|
|
warn_stack_upgrade
|
|
true
|
|
end
|
|
end
|
|
|
|
def warn_stack_upgrade
|
|
return unless defined?(@ruby_download_check)
|
|
return unless @ruby_download_check.next_stack(current_stack: stack)
|
|
return if @ruby_download_check.exists_on_next_stack?(current_stack: stack)
|
|
|
|
warn(<<~WARNING)
|
|
Your Ruby version is not present on the next stack
|
|
|
|
You are currently using #{ruby_version.version_for_download} on #{stack} stack.
|
|
This version does not exist on #{@ruby_download_check.next_stack(current_stack: stack)}. In order to upgrade your stack you will
|
|
need to upgrade to a supported Ruby version.
|
|
|
|
For a list of supported Ruby versions see:
|
|
https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
|
|
|
|
For a list of the oldest Ruby versions present on a given stack see:
|
|
https://devcenter.heroku.com/articles/ruby-support#oldest-available-runtimes
|
|
WARNING
|
|
end
|
|
|
|
def warn_outdated_eol
|
|
return unless @outdated_version_check.maybe_eol?
|
|
|
|
if @outdated_version_check.eol?
|
|
warn(<<~WARNING)
|
|
EOL Ruby Version
|
|
|
|
You are using a Ruby version that has reached its End of Life (EOL)
|
|
|
|
We strongly suggest you upgrade to Ruby #{@outdated_version_check.suggest_ruby_eol_version} or later
|
|
|
|
Your current Ruby version no longer receives security updates from
|
|
Ruby Core and may have serious vulnerabilities. While you will continue
|
|
to be able to deploy on Heroku with this Ruby version you must upgrade
|
|
to a non-EOL version to be eligible to receive support.
|
|
|
|
Upgrade your Ruby version as soon as possible.
|
|
|
|
For a list of supported Ruby versions see:
|
|
https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
|
|
WARNING
|
|
else
|
|
# Maybe EOL
|
|
warn(<<~WARNING)
|
|
Potential EOL Ruby Version
|
|
|
|
You are using a Ruby version that has either reached its End of Life (EOL)
|
|
or will reach its End of Life on December 25th of this year.
|
|
|
|
We suggest you upgrade to Ruby #{@outdated_version_check.suggest_ruby_eol_version} or later
|
|
|
|
Once a Ruby version becomes EOL, it will no longer receive
|
|
security updates from Ruby core and may have serious vulnerabilities.
|
|
|
|
Please upgrade your Ruby version.
|
|
|
|
For a list of supported Ruby versions see:
|
|
https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
|
|
WARNING
|
|
end
|
|
end
|
|
|
|
def warn_outdated_minor
|
|
return if @outdated_version_check.latest_minor_version?
|
|
|
|
warn(<<~WARNING)
|
|
There is a more recent Ruby version available for you to use:
|
|
|
|
#{@outdated_version_check.suggested_ruby_minor_version}
|
|
|
|
The latest version will include security and bug fixes. We always recommend
|
|
running the latest version of your minor release.
|
|
|
|
Please upgrade your Ruby version.
|
|
|
|
For all available Ruby versions see:
|
|
https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
|
|
WARNING
|
|
end
|
|
|
|
# install the vendored ruby
|
|
# @return [Boolean] true if it installs the vendored ruby and false otherwise
|
|
def install_ruby(install_path, build_ruby_path = nil)
|
|
instrument 'ruby.install_ruby' do
|
|
# Could do a compare operation to avoid re-downloading ruby
|
|
return false unless ruby_version
|
|
installer = LanguagePack::Installers::RubyInstaller.installer(ruby_version).new(@stack)
|
|
|
|
@ruby_download_check = LanguagePack::Helpers::DownloadPresence.new(ruby_version.file_name)
|
|
@ruby_download_check.call
|
|
|
|
if ruby_version.build?
|
|
installer.fetch_unpack(ruby_version, build_ruby_path, true)
|
|
end
|
|
|
|
installer.install(ruby_version, install_path)
|
|
|
|
@outdated_version_check = LanguagePack::Helpers::OutdatedRubyVersion.new(
|
|
current_ruby_version: ruby_version,
|
|
fetcher: installer.fetcher
|
|
)
|
|
@outdated_version_check.call
|
|
|
|
@metadata.write("buildpack_ruby_version", ruby_version.version_for_download)
|
|
|
|
topic "Using Ruby version: #{ruby_version.version_for_download}"
|
|
if !ruby_version.set
|
|
warn(<<~WARNING)
|
|
You have not declared a Ruby version in your Gemfile.
|
|
|
|
To declare a Ruby version add this line to your Gemfile:
|
|
|
|
```
|
|
ruby "#{LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER}"
|
|
```
|
|
|
|
For more information see:
|
|
https://devcenter.heroku.com/articles/ruby-versions
|
|
WARNING
|
|
end
|
|
|
|
if ruby_version.warn_ruby_26_bundler?
|
|
warn(<<~WARNING, inline: true)
|
|
There is a known bundler bug with your version of Ruby
|
|
|
|
Your version of Ruby contains a problem with the built-in integration of bundler. If
|
|
you encounter a bundler error you need to upgrade your Ruby version. We suggest you upgrade to:
|
|
|
|
#{@outdated_version_check.suggested_ruby_minor_version}
|
|
|
|
For more information see:
|
|
https://devcenter.heroku.com/articles/bundler-version#known-upgrade-issues
|
|
WARNING
|
|
end
|
|
end
|
|
|
|
true
|
|
rescue LanguagePack::Fetcher::FetchError
|
|
if @ruby_download_check.does_not_exist?
|
|
message = <<~ERROR
|
|
The Ruby version you are trying to install does not exist: #{ruby_version.version_for_download}
|
|
ERROR
|
|
else
|
|
message = <<~ERROR
|
|
The Ruby version you are trying to install does not exist on this stack.
|
|
|
|
You are trying to install #{ruby_version.version_for_download} on #{stack}.
|
|
|
|
Ruby #{ruby_version.version_for_download} is present on the following stacks:
|
|
|
|
- #{@ruby_download_check.valid_stack_list.join("\n - ")}
|
|
ERROR
|
|
|
|
if env("CI")
|
|
message << <<~ERROR
|
|
|
|
On Heroku CI you can set your stack in the `app.json`. For example:
|
|
|
|
```
|
|
"stack": "heroku-20"
|
|
```
|
|
ERROR
|
|
end
|
|
end
|
|
|
|
message << <<~ERROR
|
|
|
|
Heroku recommends you use the latest supported Ruby version listed here:
|
|
https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
|
|
|
|
For more information on syntax for declaring a Ruby version see:
|
|
https://devcenter.heroku.com/articles/ruby-versions
|
|
ERROR
|
|
|
|
error message
|
|
end
|
|
|
|
# TODO make this compatible with CNB
|
|
def new_app?
|
|
@new_app ||= !File.exist?("vendor/heroku")
|
|
end
|
|
|
|
# find the ruby install path for its binstubs during build
|
|
# @return [String] resulting path or empty string if ruby is not vendored
|
|
def ruby_install_binstub_path(ruby_layer_path = ".")
|
|
@ruby_install_binstub_path ||=
|
|
if ruby_version.build?
|
|
"#{build_ruby_path}/bin"
|
|
elsif ruby_version
|
|
"#{ruby_layer_path}/#{slug_vendor_ruby}/bin"
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
# setup the environment so we can use the vendored ruby
|
|
def setup_ruby_install_env(ruby_layer_path = ".")
|
|
instrument 'ruby.setup_ruby_install_env' do
|
|
ENV["PATH"] = "#{File.expand_path(ruby_install_binstub_path(ruby_layer_path))}:#{ENV["PATH"]}"
|
|
end
|
|
end
|
|
|
|
# installs vendored gems into the slug
|
|
def install_bundler_in_app(bundler_dir)
|
|
instrument 'ruby.install_language_pack_gems' do
|
|
FileUtils.mkdir_p(bundler_dir)
|
|
Dir.chdir(bundler_dir) do |dir|
|
|
`cp -R #{bundler.bundler_path}/. .`
|
|
end
|
|
|
|
# write bundler shim, so we can control the version bundler used
|
|
# Ruby 2.6.0 started vendoring bundler
|
|
write_bundler_shim("vendor/bundle/bin") if ruby_version.vendored_bundler?
|
|
end
|
|
end
|
|
|
|
# default set of binaries to install
|
|
# @return [Array] resulting list
|
|
def binaries
|
|
add_node_js_binary + add_yarn_binary
|
|
end
|
|
|
|
# vendors binaries into the slug
|
|
def install_binaries
|
|
instrument 'ruby.install_binaries' do
|
|
binaries.each {|binary| install_binary(binary) }
|
|
Dir["bin/*"].each {|path| run("chmod +x #{path}") }
|
|
end
|
|
end
|
|
|
|
# vendors individual binary into the slug
|
|
# @param [String] name of the binary package from S3.
|
|
# Example: https://s3.amazonaws.com/language-pack-ruby/node-0.4.7.tgz, where name is "node-0.4.7"
|
|
def install_binary(name)
|
|
topic "Installing #{name}"
|
|
bin_dir = "bin"
|
|
FileUtils.mkdir_p bin_dir
|
|
Dir.chdir(bin_dir) do |dir|
|
|
if name.match(/^node\-/)
|
|
@node_installer.install
|
|
# need to set PATH here b/c `node-gyp` can change the CWD, but still depends on executing node.
|
|
# the current PATH is relative, but it needs to be absolute for this.
|
|
# doing this here also prevents it from being exported during runtime
|
|
node_bin_path = File.absolute_path(".")
|
|
# this needs to be set after so other binaries in bin/ don't take precedence"
|
|
ENV["PATH"] = "#{ENV["PATH"]}:#{node_bin_path}"
|
|
elsif name.match(/^yarn\-/)
|
|
FileUtils.mkdir_p("../vendor")
|
|
Dir.chdir("../vendor") do |vendor_dir|
|
|
@yarn_installer.install
|
|
yarn_path = File.absolute_path("#{vendor_dir}/#{@yarn_installer.binary_path}")
|
|
ENV["PATH"] = "#{yarn_path}:#{ENV["PATH"]}"
|
|
end
|
|
else
|
|
@fetchers[:buildpack].fetch_untar("#{name}.tgz")
|
|
end
|
|
end
|
|
end
|
|
|
|
# removes a binary from the slug
|
|
# @param [String] relative path of the binary on the slug
|
|
def uninstall_binary(path)
|
|
FileUtils.rm File.join('bin', File.basename(path)), :force => true
|
|
end
|
|
|
|
def load_default_cache?
|
|
new_app? && ruby_version.default?
|
|
end
|
|
|
|
# loads a default bundler cache for new apps to speed up initial bundle installs
|
|
def load_default_cache
|
|
instrument "ruby.load_default_cache" do
|
|
if false # load_default_cache?
|
|
puts "New app detected loading default bundler cache"
|
|
patchlevel = run("ruby -e 'puts RUBY_PATCHLEVEL'").strip
|
|
cache_name = "#{LanguagePack::RubyVersion::DEFAULT_VERSION}-p#{patchlevel}-default-cache"
|
|
@fetchers[:buildpack].fetch_untar("#{cache_name}.tgz")
|
|
end
|
|
end
|
|
end
|
|
|
|
# remove `vendor/bundle` that comes from the git repo
|
|
# in case there are native ext.
|
|
# users should be using `bundle pack` instead.
|
|
# https://github.com/heroku/heroku-buildpack-ruby/issues/21
|
|
def remove_vendor_bundle
|
|
if File.exists?("vendor/bundle")
|
|
warn(<<-WARNING)
|
|
Removing `vendor/bundle`.
|
|
Checking in `vendor/bundle` is not supported. Please remove this directory
|
|
and add it to your .gitignore. To vendor your gems with Bundler, use
|
|
`bundle pack` instead.
|
|
WARNING
|
|
FileUtils.rm_rf("vendor/bundle")
|
|
end
|
|
end
|
|
|
|
def bundler_binstubs_path
|
|
"vendor/bundle/bin"
|
|
end
|
|
|
|
def bundler_path
|
|
@bundler_path ||= "#{slug_vendor_base}/gems/#{bundler.dir_name}"
|
|
end
|
|
|
|
def write_bundler_shim(path)
|
|
FileUtils.mkdir_p(path)
|
|
shim_path = "#{path}/bundle"
|
|
File.open(shim_path, "w") do |file|
|
|
file.print <<-BUNDLE
|
|
#!/usr/bin/env ruby
|
|
require 'rubygems'
|
|
|
|
version = "#{bundler.version}"
|
|
|
|
if ARGV.first
|
|
str = ARGV.first
|
|
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
|
|
if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
|
|
version = $1
|
|
ARGV.shift
|
|
end
|
|
end
|
|
|
|
if Gem.respond_to?(:activate_bin_path)
|
|
load Gem.activate_bin_path('bundler', 'bundle', version)
|
|
else
|
|
gem "bundler", version
|
|
load Gem.bin_path("bundler", "bundle", version)
|
|
end
|
|
BUNDLE
|
|
end
|
|
FileUtils.chmod(0755, shim_path)
|
|
end
|
|
|
|
# runs bundler to install the dependencies
|
|
def build_bundler
|
|
instrument 'ruby.build_bundler' do
|
|
log("bundle") do
|
|
if File.exist?("#{Dir.pwd}/.bundle/config")
|
|
warn(<<~WARNING, inline: true)
|
|
You have the `.bundle/config` file checked into your repository
|
|
It contains local state like the location of the installed bundle
|
|
as well as configured git local gems, and other settings that should
|
|
not be shared between multiple checkouts of a single repo. Please
|
|
remove the `.bundle/` folder from your repo and add it to your `.gitignore` file.
|
|
|
|
https://devcenter.heroku.com/articles/bundler-configuration
|
|
WARNING
|
|
end
|
|
|
|
if bundler.windows_gemfile_lock?
|
|
log("bundle", "has_windows_gemfile_lock")
|
|
|
|
File.unlink("Gemfile.lock")
|
|
ENV.delete("BUNDLE_DEPLOYMENT")
|
|
|
|
warn(<<~WARNING, inline: true)
|
|
Removing `Gemfile.lock` because it was generated on Windows.
|
|
Bundler will do a full resolve so native gems are handled properly.
|
|
This may result in unexpected gem versions being used in your app.
|
|
In rare occasions Bundler may not be able to resolve your dependencies at all.
|
|
|
|
https://devcenter.heroku.com/articles/bundler-windows-gemfile
|
|
WARNING
|
|
end
|
|
|
|
bundle_command = String.new("")
|
|
bundle_command << "BUNDLE_WITHOUT='#{ENV["BUNDLE_WITHOUT"]}' "
|
|
bundle_command << "BUNDLE_PATH=#{ENV["BUNDLE_PATH"]} "
|
|
bundle_command << "BUNDLE_BIN=#{ENV["BUNDLE_BIN"]} "
|
|
bundle_command << "BUNDLE_DEPLOYMENT=#{ENV["BUNDLE_DEPLOYMENT"]} " if ENV["BUNDLE_DEPLOYMENT"] # Unset on windows since we delete the Gemfile.lock
|
|
bundle_command << "BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE=#{ENV["BUNDLE_GLOBAL_PATH_APPENDS_RUBY_SCOPE"]} " if bundler.needs_ruby_global_append_path?
|
|
bundle_command << "bundle install -j4"
|
|
|
|
topic("Installing dependencies using bundler #{bundler.version}")
|
|
|
|
bundler_output = String.new("")
|
|
bundle_time = nil
|
|
env_vars = {}
|
|
Dir.mktmpdir("libyaml-") do |tmpdir|
|
|
libyaml_dir = "#{tmpdir}/#{LIBYAML_PATH}"
|
|
|
|
# need to setup compile environment for the psych gem
|
|
yaml_include = File.expand_path("#{libyaml_dir}/include").shellescape
|
|
yaml_lib = File.expand_path("#{libyaml_dir}/lib").shellescape
|
|
pwd = Dir.pwd
|
|
bundler_path = "#{pwd}/#{slug_vendor_base}/gems/#{bundler.dir_name}/lib"
|
|
|
|
# we need to set BUNDLE_CONFIG and BUNDLE_GEMFILE for
|
|
# codon since it uses bundler.
|
|
env_vars["BUNDLE_GEMFILE"] = "#{pwd}/Gemfile"
|
|
env_vars["BUNDLE_CONFIG"] = "#{pwd}/.bundle/config"
|
|
env_vars["CPATH"] = noshellescape("#{yaml_include}:$CPATH")
|
|
env_vars["CPPATH"] = noshellescape("#{yaml_include}:$CPPATH")
|
|
env_vars["LIBRARY_PATH"] = noshellescape("#{yaml_lib}:$LIBRARY_PATH")
|
|
env_vars["RUBYOPT"] = syck_hack
|
|
env_vars["NOKOGIRI_USE_SYSTEM_LIBRARIES"] = "true"
|
|
env_vars["BUNDLE_DISABLE_VERSION_CHECK"] = "true"
|
|
env_vars["BUNDLER_LIB_PATH"] = "#{bundler_path}" if ruby_version.ruby_version == "1.8.7"
|
|
env_vars["BUNDLE_DISABLE_VERSION_CHECK"] = "true"
|
|
|
|
puts "Running: #{bundle_command}"
|
|
instrument "ruby.bundle_install" do
|
|
bundle_time = Benchmark.realtime do
|
|
bundler_output << pipe("#{bundle_command} --no-clean", out: "2>&1", env: env_vars, user_env: true)
|
|
end
|
|
end
|
|
end
|
|
|
|
if $?.success?
|
|
puts "Bundle completed (#{"%.2f" % bundle_time}s)"
|
|
log "bundle", :status => "success"
|
|
puts "Cleaning up the bundler cache."
|
|
instrument "ruby.bundle_clean" do
|
|
# Only show bundle clean output when not using default cache
|
|
if load_default_cache?
|
|
run("bundle clean > /dev/null", user_env: true, env: env_vars)
|
|
else
|
|
pipe("bundle clean", out: "2> /dev/null", user_env: true, env: env_vars)
|
|
end
|
|
end
|
|
@bundler_cache.store
|
|
|
|
# Keep gem cache out of the slug
|
|
FileUtils.rm_rf("#{slug_vendor_base}/cache")
|
|
else
|
|
mcount "fail.bundle.install"
|
|
log "bundle", :status => "failure"
|
|
error_message = "Failed to install gems via Bundler."
|
|
puts "Bundler Output: #{bundler_output}"
|
|
if bundler_output.match(/An error occurred while installing sqlite3/)
|
|
mcount "fail.sqlite3"
|
|
error_message += <<~ERROR
|
|
|
|
Detected sqlite3 gem which is not supported on Heroku:
|
|
https://devcenter.heroku.com/articles/sqlite3
|
|
ERROR
|
|
end
|
|
|
|
if bundler_output.match(/but your Gemfile specified/)
|
|
mcount "fail.ruby_version_mismatch"
|
|
error_message += <<~ERROR
|
|
|
|
Detected a mismatch between your Ruby version installed and
|
|
Ruby version specified in Gemfile or Gemfile.lock. You can
|
|
correct this by running:
|
|
|
|
$ bundle update --ruby
|
|
$ git add Gemfile.lock
|
|
$ git commit -m "update ruby version"
|
|
|
|
If this does not solve the issue please see this documentation:
|
|
|
|
https://devcenter.heroku.com/articles/ruby-versions#your-ruby-version-is-x-but-your-gemfile-specified-y
|
|
ERROR
|
|
end
|
|
|
|
error error_message
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def post_bundler
|
|
instrument "ruby.post_bundler" do
|
|
Dir[File.join(slug_vendor_base, "**", ".git")].each do |dir|
|
|
FileUtils.rm_rf(dir)
|
|
end
|
|
bundler.clean
|
|
end
|
|
end
|
|
|
|
# RUBYOPT line that requires syck_hack file
|
|
# @return [String] require string if needed or else an empty string
|
|
def syck_hack
|
|
instrument "ruby.syck_hack" do
|
|
syck_hack_file = File.expand_path(File.join(File.dirname(__FILE__), "../../vendor/syck_hack"))
|
|
rv = run_stdout('ruby -e "puts RUBY_VERSION"').strip
|
|
# < 1.9.3 includes syck, so we need to use the syck hack
|
|
if Gem::Version.new(rv) < Gem::Version.new("1.9.3")
|
|
"-r#{syck_hack_file}"
|
|
else
|
|
""
|
|
end
|
|
end
|
|
end
|
|
|
|
# writes ERB based database.yml for Rails. The database.yml uses the DATABASE_URL from the environment during runtime.
|
|
def create_database_yml
|
|
instrument 'ruby.create_database_yml' do
|
|
return false unless File.directory?("config")
|
|
return false if bundler.has_gem?('activerecord') && bundler.gem_version('activerecord') >= Gem::Version.new('4.1.0.beta1')
|
|
|
|
log("create_database_yml") do
|
|
topic("Writing config/database.yml to read from DATABASE_URL")
|
|
File.open("config/database.yml", "w") do |file|
|
|
file.puts <<-DATABASE_YML
|
|
<%
|
|
|
|
require 'cgi'
|
|
require 'uri'
|
|
|
|
begin
|
|
uri = URI.parse(ENV["DATABASE_URL"])
|
|
rescue URI::InvalidURIError
|
|
raise "Invalid DATABASE_URL"
|
|
end
|
|
|
|
raise "No RACK_ENV or RAILS_ENV found" unless ENV["RAILS_ENV"] || ENV["RACK_ENV"]
|
|
|
|
def attribute(name, value, force_string = false)
|
|
if value
|
|
value_string =
|
|
if force_string
|
|
'"' + value + '"'
|
|
else
|
|
value
|
|
end
|
|
"\#{name}: \#{value_string}"
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
adapter = uri.scheme
|
|
adapter = "postgresql" if adapter == "postgres"
|
|
|
|
database = (uri.path || "").split("/")[1]
|
|
|
|
username = uri.user
|
|
password = uri.password
|
|
|
|
host = uri.host
|
|
port = uri.port
|
|
|
|
params = CGI.parse(uri.query || "")
|
|
|
|
%>
|
|
|
|
<%= ENV["RAILS_ENV"] || ENV["RACK_ENV"] %>:
|
|
<%= attribute "adapter", adapter %>
|
|
<%= attribute "database", database %>
|
|
<%= attribute "username", username %>
|
|
<%= attribute "password", password, true %>
|
|
<%= attribute "host", host %>
|
|
<%= attribute "port", port %>
|
|
|
|
<% params.each do |key, value| %>
|
|
<%= key %>: <%= value.first %>
|
|
<% end %>
|
|
DATABASE_YML
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def rake
|
|
@rake ||= begin
|
|
rake_gem_available = bundler.has_gem?("rake") || ruby_version.rake_is_vendored?
|
|
raise_on_fail = bundler.gem_version('railties') && bundler.gem_version('railties') > Gem::Version.new('3.x')
|
|
|
|
topic "Detecting rake tasks"
|
|
rake = LanguagePack::Helpers::RakeRunner.new(rake_gem_available)
|
|
rake.load_rake_tasks!({ env: rake_env }, raise_on_fail)
|
|
rake
|
|
end
|
|
end
|
|
|
|
def rake_env
|
|
if database_url
|
|
{ "DATABASE_URL" => database_url }
|
|
else
|
|
{}
|
|
end.merge(user_env_hash)
|
|
end
|
|
|
|
def database_url
|
|
env("DATABASE_URL") if env("DATABASE_URL")
|
|
end
|
|
|
|
# executes the block with GIT_DIR environment variable removed since it can mess with the current working directory git thinks it's in
|
|
# @param [block] block to be executed in the GIT_DIR free context
|
|
def allow_git(&blk)
|
|
git_dir = ENV.delete("GIT_DIR") # can mess with bundler
|
|
blk.call
|
|
ENV["GIT_DIR"] = git_dir
|
|
end
|
|
|
|
# decides if we need to enable the dev database addon
|
|
# @return [Array] the database addon if the pg gem is detected or an empty Array if it isn't.
|
|
def add_dev_database_addon
|
|
pg_adapters.any? {|a| bundler.has_gem?(a) } ? ['heroku-postgresql'] : []
|
|
end
|
|
|
|
def pg_adapters
|
|
[
|
|
"pg",
|
|
"activerecord-jdbcpostgresql-adapter",
|
|
"jdbc-postgres",
|
|
"jdbc-postgresql",
|
|
"jruby-pg",
|
|
"rjack-jdbc-postgres",
|
|
"tgbyte-activerecord-jdbcpostgresql-adapter"
|
|
]
|
|
end
|
|
|
|
# decides if we need to install the node.js binary
|
|
# @note execjs will blow up if no JS RUNTIME is detected and is loaded.
|
|
# @return [Array] the node.js binary path if we need it or an empty Array
|
|
def add_node_js_binary
|
|
return [] if node_js_preinstalled?
|
|
|
|
if Pathname(build_path).join("package.json").exist? ||
|
|
bundler.has_gem?('execjs') ||
|
|
bundler.has_gem?('webpacker')
|
|
[@node_installer.binary_path]
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def add_yarn_binary
|
|
return [] if yarn_preinstalled?
|
|
|
|
|
if Pathname(build_path).join("yarn.lock").exist? || bundler.has_gem?('webpacker')
|
|
[@yarn_installer.name]
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def has_yarn_binary?
|
|
add_yarn_binary.any?
|
|
end
|
|
|
|
# checks if node.js is installed via the official heroku-buildpack-nodejs using multibuildpack
|
|
# @return String if it's detected and false if it isn't
|
|
def node_preinstall_bin_path
|
|
return @node_preinstall_bin_path if defined?(@node_preinstall_bin_path)
|
|
|
|
legacy_path = "#{Dir.pwd}/#{NODE_BP_PATH}"
|
|
path = run("which node").strip
|
|
if path && $?.success?
|
|
@node_preinstall_bin_path = path
|
|
elsif run("#{legacy_path}/node -v") && $?.success?
|
|
@node_preinstall_bin_path = legacy_path
|
|
else
|
|
@node_preinstall_bin_path = false
|
|
end
|
|
end
|
|
alias :node_js_preinstalled? :node_preinstall_bin_path
|
|
|
|
def node_not_preinstalled?
|
|
!node_js_preinstalled?
|
|
end
|
|
|
|
# Example: tmp/build_8523f77fb96a956101d00988dfeed9d4/.heroku/yarn/bin/ (without the `yarn` at the end)
|
|
def yarn_preinstall_bin_path
|
|
(yarn_preinstall_binary_path || "").chomp("/yarn")
|
|
end
|
|
|
|
# Example `tmp/build_8523f77fb96a956101d00988dfeed9d4/.heroku/yarn/bin/yarn`
|
|
def yarn_preinstall_binary_path
|
|
return @yarn_preinstall_binary_path if defined?(@yarn_preinstall_binary_path)
|
|
|
|
path = run("which yarn").strip
|
|
if path && $?.success?
|
|
@yarn_preinstall_binary_path = path
|
|
else
|
|
@yarn_preinstall_binary_path = false
|
|
end
|
|
end
|
|
|
|
def yarn_preinstalled?
|
|
yarn_preinstall_binary_path
|
|
end
|
|
|
|
def yarn_not_preinstalled?
|
|
!yarn_preinstalled?
|
|
end
|
|
|
|
def run_assets_precompile_rake_task
|
|
instrument 'ruby.run_assets_precompile_rake_task' do
|
|
|
|
precompile = rake.task("assets:precompile")
|
|
return true unless precompile.is_defined?
|
|
|
|
topic "Precompiling assets"
|
|
precompile.invoke(env: rake_env)
|
|
if precompile.success?
|
|
puts "Asset precompilation completed (#{"%.2f" % precompile.time}s)"
|
|
else
|
|
precompile_fail(precompile.output)
|
|
end
|
|
end
|
|
end
|
|
|
|
def precompile_fail(output)
|
|
mcount "fail.assets_precompile"
|
|
log "assets_precompile", :status => "failure"
|
|
msg = "Precompiling assets failed.\n"
|
|
if output.match(/(127\.0\.0\.1)|(org\.postgresql\.util)/)
|
|
msg << "Attempted to access a nonexistent database:\n"
|
|
msg << "https://devcenter.heroku.com/articles/pre-provision-database\n"
|
|
end
|
|
|
|
sprockets_version = bundler.gem_version('sprockets')
|
|
if output.match(/Sprockets::FileNotFound/) && (sprockets_version < Gem::Version.new('4.0.0.beta7') && sprockets_version > Gem::Version.new('4.0.0.beta4'))
|
|
mcount "fail.assets_precompile.file_not_found_beta"
|
|
msg << "If you have this file in your project\n"
|
|
msg << "try upgrading to Sprockets 4.0.0.beta7 or later:\n"
|
|
msg << "https://github.com/rails/sprockets/pull/547\n"
|
|
end
|
|
|
|
error msg
|
|
end
|
|
|
|
def bundler_cache
|
|
"vendor/bundle"
|
|
end
|
|
|
|
def valid_bundler_cache?(path, metadata)
|
|
full_ruby_version = run_stdout(%q(ruby -v)).strip
|
|
rubygems_version = run_stdout(%q(gem -v)).strip
|
|
old_rubygems_version = nil
|
|
|
|
old_rubygems_version = metadata[:ruby_version]
|
|
old_stack = metadata[:stack]
|
|
old_stack ||= DEFAULT_LEGACY_STACK
|
|
|
|
stack_change = old_stack != @stack
|
|
if !new_app? && stack_change
|
|
return [false, "Purging Cache. Changing stack from #{old_stack} to #{@stack}"]
|
|
end
|
|
|
|
# fix bug from v37 deploy
|
|
if File.exists?("#{path}/vendor/ruby_version")
|
|
puts "Broken cache detected. Purging build cache."
|
|
cache.clear("vendor")
|
|
FileUtils.rm_rf("#{path}/vendor/ruby_version")
|
|
return [false, "Broken cache detected. Purging build cache."]
|
|
# fix bug introduced in v38
|
|
elsif !metadata.include?(:buildpack_version) && metadata.include?(:ruby_version)
|
|
puts "Broken cache detected. Purging build cache."
|
|
return [false, "Broken cache detected. Purging build cache."]
|
|
elsif (@bundler_cache.exists? || @bundler_cache.old?) && full_ruby_version != metadata[:ruby_version]
|
|
return [false, <<-MESSAGE]
|
|
Ruby version change detected. Clearing bundler cache.
|
|
Old: #{metadata[:ruby_version]}
|
|
New: #{full_ruby_version}
|
|
MESSAGE
|
|
end
|
|
|
|
# fix git gemspec bug from Bundler 1.3.0+ upgrade
|
|
if File.exists?(bundler_cache) && !metadata.include?(:bundler_version) && !run("find #{path}/vendor/bundle/*/*/bundler/gems/*/ -name *.gemspec").include?("No such file or directory")
|
|
return [false, "Old bundler cache detected. Clearing bundler cache."]
|
|
end
|
|
|
|
# fix for https://github.com/heroku/heroku-buildpack-ruby/issues/86
|
|
if (!metadata.include?(:rubygems_version) ||
|
|
(old_rubygems_version == "2.0.0" && old_rubygems_version != rubygems_version)) &&
|
|
metadata.include?(:ruby_version) && metadata[:ruby_version].strip.include?("ruby 2.0.0p0")
|
|
return [false, "Updating to rubygems #{rubygems_version}. Clearing bundler cache."]
|
|
end
|
|
|
|
# fix for https://github.com/sparklemotion/nokogiri/issues/923
|
|
if metadata.include?(:buildpack_version) && (bv = metadata[:buildpack_version].sub('v', '').to_i) && bv != 0 && bv <= 76
|
|
return [false, <<-MESSAGE]
|
|
Fixing nokogiri install. Clearing bundler cache.
|
|
See https://github.com/sparklemotion/nokogiri/issues/923.
|
|
MESSAGE
|
|
end
|
|
|
|
# recompile nokogiri to use new libyaml
|
|
if metadata.include?(:buildpack_version) && (bv = metadata[:buildpack_version].sub('v', '').to_i) && bv != 0 && bv <= 99 && bundler.has_gem?("psych")
|
|
return [false, <<-MESSAGE]
|
|
Need to recompile psych for CVE-2013-6393. Clearing bundler cache.
|
|
See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=737076.
|
|
MESSAGE
|
|
end
|
|
|
|
# recompile gems for libyaml 0.1.7 update
|
|
if metadata.include?(:buildpack_version) && (bv = metadata[:buildpack_version].sub('v', '').to_i) && bv != 0 && bv <= 147 &&
|
|
(metadata.include?(:ruby_version) && metadata[:ruby_version].match(/ruby 2\.1\.(9|10)/) ||
|
|
bundler.has_gem?("psych")
|
|
)
|
|
return [false, <<-MESSAGE]
|
|
Need to recompile gems for CVE-2014-2014-9130. Clearing bundler cache.
|
|
See https://devcenter.heroku.com/changelog-items/1016.
|
|
MESSAGE
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def load_bundler_cache
|
|
instrument "ruby.load_bundler_cache" do
|
|
cache.load "vendor"
|
|
|
|
full_ruby_version = run_stdout(%q(ruby -v)).strip
|
|
rubygems_version = run_stdout(%q(gem -v)).strip
|
|
heroku_metadata = "vendor/heroku"
|
|
old_rubygems_version = nil
|
|
ruby_version_cache = "ruby_version"
|
|
buildpack_version_cache = "buildpack_version"
|
|
bundler_version_cache = "bundler_version"
|
|
rubygems_version_cache = "rubygems_version"
|
|
stack_cache = "stack"
|
|
|
|
# bundle clean does not remove binstubs
|
|
FileUtils.rm_rf("vendor/bundler/bin")
|
|
|
|
old_rubygems_version = @metadata.read(ruby_version_cache).strip if @metadata.exists?(ruby_version_cache)
|
|
old_stack = @metadata.read(stack_cache).strip if @metadata.exists?(stack_cache)
|
|
old_stack ||= DEFAULT_LEGACY_STACK
|
|
|
|
stack_change = old_stack != @stack
|
|
convert_stack = @bundler_cache.old?
|
|
@bundler_cache.convert_stack(stack_change) if convert_stack
|
|
if !new_app? && stack_change
|
|
puts "Purging Cache. Changing stack from #{old_stack} to #{@stack}"
|
|
purge_bundler_cache(old_stack)
|
|
elsif !new_app? && !convert_stack
|
|
@bundler_cache.load
|
|
end
|
|
|
|
# fix bug from v37 deploy
|
|
if File.exists?("vendor/ruby_version")
|
|
puts "Broken cache detected. Purging build cache."
|
|
cache.clear("vendor")
|
|
FileUtils.rm_rf("vendor/ruby_version")
|
|
purge_bundler_cache
|
|
# fix bug introduced in v38
|
|
elsif !@metadata.include?(buildpack_version_cache) && @metadata.exists?(ruby_version_cache)
|
|
puts "Broken cache detected. Purging build cache."
|
|
purge_bundler_cache
|
|
elsif (@bundler_cache.exists? || @bundler_cache.old?) && @metadata.exists?(ruby_version_cache) && full_ruby_version != @metadata.read(ruby_version_cache).strip
|
|
puts "Ruby version change detected. Clearing bundler cache."
|
|
puts "Old: #{@metadata.read(ruby_version_cache).strip}"
|
|
puts "New: #{full_ruby_version}"
|
|
purge_bundler_cache
|
|
end
|
|
|
|
# fix git gemspec bug from Bundler 1.3.0+ upgrade
|
|
if File.exists?(bundler_cache) && !@metadata.exists?(bundler_version_cache) && !run("find vendor/bundle/*/*/bundler/gems/*/ -name *.gemspec").include?("No such file or directory")
|
|
puts "Old bundler cache detected. Clearing bundler cache."
|
|
purge_bundler_cache
|
|
end
|
|
|
|
# fix for https://github.com/heroku/heroku-buildpack-ruby/issues/86
|
|
if (!@metadata.exists?(rubygems_version_cache) ||
|
|
(old_rubygems_version == "2.0.0" && old_rubygems_version != rubygems_version)) &&
|
|
@metadata.exists?(ruby_version_cache) && @metadata.read(ruby_version_cache).strip.include?("ruby 2.0.0p0")
|
|
puts "Updating to rubygems #{rubygems_version}. Clearing bundler cache."
|
|
purge_bundler_cache
|
|
end
|
|
|
|
# fix for https://github.com/sparklemotion/nokogiri/issues/923
|
|
if @metadata.exists?(buildpack_version_cache) && (bv = @metadata.read(buildpack_version_cache).sub('v', '').to_i) && bv != 0 && bv <= 76
|
|
puts "Fixing nokogiri install. Clearing bundler cache."
|
|
puts "See https://github.com/sparklemotion/nokogiri/issues/923."
|
|
purge_bundler_cache
|
|
end
|
|
|
|
# recompile nokogiri to use new libyaml
|
|
if @metadata.exists?(buildpack_version_cache) && (bv = @metadata.read(buildpack_version_cache).sub('v', '').to_i) && bv != 0 && bv <= 99 && bundler.has_gem?("psych")
|
|
puts "Need to recompile psych for CVE-2013-6393. Clearing bundler cache."
|
|
puts "See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=737076."
|
|
purge_bundler_cache
|
|
end
|
|
|
|
# recompile gems for libyaml 0.1.7 update
|
|
if @metadata.exists?(buildpack_version_cache) && (bv = @metadata.read(buildpack_version_cache).sub('v', '').to_i) && bv != 0 && bv <= 147 &&
|
|
(@metadata.exists?(ruby_version_cache) && @metadata.read(ruby_version_cache).strip.match(/ruby 2\.1\.(9|10)/) ||
|
|
bundler.has_gem?("psych")
|
|
)
|
|
puts "Need to recompile gems for CVE-2014-2014-9130. Clearing bundler cache."
|
|
puts "See https://devcenter.heroku.com/changelog-items/1016."
|
|
purge_bundler_cache
|
|
end
|
|
|
|
FileUtils.mkdir_p(heroku_metadata)
|
|
@metadata.write(ruby_version_cache, full_ruby_version, false)
|
|
@metadata.write(buildpack_version_cache, BUILDPACK_VERSION, false)
|
|
@metadata.write(bundler_version_cache, bundler.version, false)
|
|
@metadata.write(rubygems_version_cache, rubygems_version, false)
|
|
@metadata.write(stack_cache, @stack, false)
|
|
@metadata.save
|
|
end
|
|
end
|
|
|
|
def purge_bundler_cache(stack = nil)
|
|
instrument "ruby.purge_bundler_cache" do
|
|
@bundler_cache.clear(stack)
|
|
# need to reinstall language pack gems
|
|
install_bundler_in_app(slug_vendor_base)
|
|
end
|
|
end
|
|
end
|