diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d724fc52..12d0c3a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Main (unreleased) +* Persistent bundler config is now being set using the `BUNDLE_*` env vars (https://github.com/heroku/heroku-buildpack-ruby/pull/1039) * Rake task "assets:clean" will not get called if it does not exist (https://github.com/heroku/heroku-buildpack-ruby/pull/1018) * CNB: Fix the `gems` layer not being made accessible by subsequent buildpacks (https://github.com/heroku/heroku-buildpack-ruby/pull/1033) diff --git a/lib/language_pack/rails4.rb b/lib/language_pack/rails4.rb index 703343a6f..86e11b6fd 100644 --- a/lib/language_pack/rails4.rb +++ b/lib/language_pack/rails4.rb @@ -30,12 +30,6 @@ def default_process_types end end - def build_bundler(default_bundle_without) - instrument "rails4.build_bundler" do - super - end - end - def compile instrument "rails4.compile" do super diff --git a/lib/language_pack/ruby.rb b/lib/language_pack/ruby.rb index 10284d5c3..0d384b15a 100644 --- a/lib/language_pack/ruby.rb +++ b/lib/language_pack/ruby.rb @@ -100,12 +100,16 @@ def compile warn_bad_binstubs install_ruby(slug_vendor_ruby, build_ruby_path) install_jvm - setup_language_pack_environment(ruby_layer_path: File.expand_path("."), gem_layer_path: File.expand_path(".")) - setup_profiled(ruby_layer_path: "$HOME", gem_layer_path: "$HOME") # $HOME is set to /app at run time + 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(bundle_path: "vendor/bundle", default_bundle_without: "development:test") + build_bundler post_bundler create_database_yml install_binaries @@ -114,6 +118,7 @@ def compile 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 @@ -137,8 +142,12 @@ def build 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) - setup_profiled(ruby_layer_path: ruby_layer.path, gem_layer_path: gem_layer.path) + 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" @@ -146,7 +155,7 @@ def build valid_bundler_cache?(gem_layer.path, gem_layer.metadata) end install_bundler_in_app("#{gem_layer.path}/#{slug_vendor_base}") - build_bundler(bundle_path: "#{gem_layer.path}/vendor/bundle", default_bundle_without: "development:test") + 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")) @@ -161,6 +170,7 @@ def build 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 @@ -345,7 +355,7 @@ def default_java_mem end # sets up the environment variables for the build process - def setup_language_pack_environment(ruby_layer_path:, gem_layer_path:) + 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" @@ -393,6 +403,11 @@ def setup_language_pack_environment(ruby_layer_path:, gem_layer_path:) paths << ENV["PATH"] ENV["PATH"] = paths.join(":") + + ENV["BUNDLE_WITHOUT"] = env("BUNDLE_WITHOUT") || bundle_default_without + ENV["BUNDLE_PATH"] = bundle_path + ENV["BUNDLE_BIN"] = bundler_binstubs_path + ENV["BUNDLE_DEPLOYMENT"] = "1" end end @@ -426,6 +441,11 @@ def setup_export(layer = nil) set_export_default "JAVA_OPTS", default_java_opts 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_DEPLOYMENT", ENV["BUNDLE_DEPLOYMENT"], layer if ENV["BUNDLE_DEPLOYMENT"] # Unset on windows since we delete the Gemfile.lock end end @@ -461,6 +481,11 @@ def setup_profiled(ruby_layer_path: , gem_layer_path: ) set_env_default "JAVA_OPTS", default_java_opts 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_DEPLOYMENT", ENV["BUNDLE_DEPLOYMENT"] if ENV["BUNDLE_DEPLOYMENT"] # Unset on windows since we delete the Gemfile.lock end end @@ -839,44 +864,47 @@ def write_bundler_shim(path) end # runs bundler to install the dependencies - def build_bundler(bundle_path:, default_bundle_without:) + def build_bundler instrument 'ruby.build_bundler' do log("bundle") do - bundle_without = env("BUNDLE_WITHOUT") || default_bundle_without - bundle_bin = "bundle" - bundle_command = "#{bundle_bin} install --without #{bundle_without} --path #{bundle_path} --binstubs #{bundler_binstubs_path}" - bundle_command << " -j4" - 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 + 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? - 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 - log("bundle", "has_windows_gemfile_lock") + File.unlink("Gemfile.lock") - else - # using --deployment is preferred if we can - bundle_command += " --deployment" + 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 install -j4" + topic("Installing dependencies using bundler #{bundler.version}") - bundler_output = "" + bundler_output = String.new("") bundle_time = nil env_vars = {} Dir.mktmpdir("libyaml-") do |tmpdir| @@ -918,9 +946,9 @@ def build_bundler(bundle_path:, default_bundle_without:) instrument "ruby.bundle_clean" do # Only show bundle clean output when not using default cache if load_default_cache? - run("#{bundle_bin} clean > /dev/null", user_env: true, env: env_vars) + run("bundle clean > /dev/null", user_env: true, env: env_vars) else - pipe("#{bundle_bin} clean", out: "2> /dev/null", user_env: true, env: env_vars) + pipe("bundle clean", out: "2> /dev/null", user_env: true, env: env_vars) end end @bundler_cache.store @@ -934,28 +962,28 @@ def build_bundler(bundle_path:, default_bundle_without:) puts "Bundler Output: #{bundler_output}" if bundler_output.match(/An error occurred while installing sqlite3/) mcount "fail.sqlite3" - error_message += <<-ERROR + error_message += <<~ERROR -Detected sqlite3 gem which is not supported on Heroku: -https://devcenter.heroku.com/articles/sqlite3 + 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 + 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: + 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" + $ bundle update --ruby + $ git add Gemfile.lock + $ git commit -m "update ruby version" -If this does not solve the issue please see this documentation: + 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 + https://devcenter.heroku.com/articles/ruby-versions#your-ruby-version-is-x-but-your-gemfile-specified-y ERROR end diff --git a/lib/language_pack/test/ruby.rb b/lib/language_pack/test/ruby.rb index 39ff78d31..19cb97fd2 100644 --- a/lib/language_pack/test/ruby.rb +++ b/lib/language_pack/test/ruby.rb @@ -8,18 +8,23 @@ def compile warn_bad_binstubs install_ruby(slug_vendor_ruby, build_ruby_path) install_jvm - setup_language_pack_environment(ruby_layer_path: File.expand_path("."), gem_layer_path: File.expand_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" + ) setup_export - setup_profiled(ruby_layer_path: "$HOME", gem_layer_path: "$HOME") # $HOME is set to /app at run time allow_git do install_bundler_in_app(slug_vendor_base) load_bundler_cache - build_bundler(bundle_path: "vendor/bundle", default_bundle_without: "development") + build_bundler post_bundler create_database_yml install_binaries prepare_tests end + setup_profiled(ruby_layer_path: "$HOME", gem_layer_path: "$HOME") # $HOME is set to /app at run time super end end diff --git a/spec/hatchet/node_spec.rb b/spec/hatchet/node_spec.rb index 3461adc6e..7d58b22d2 100644 --- a/spec/hatchet/node_spec.rb +++ b/spec/hatchet/node_spec.rb @@ -6,7 +6,9 @@ :default, "https://github.com/sharpstone/force_absolute_paths_buildpack" ] - Hatchet::Runner.new("minimal_webpacker", buildpacks: buildpacks).deploy do |app, heroku| + config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} + + Hatchet::Runner.new("minimal_webpacker", buildpacks: buildpacks, config: config).deploy do |app, heroku| # https://rubular.com/r/4bkL8fYFTQwt0Q expect(app.output).to match(/vendor\/yarn-v\d+\.\d+\.\d+\/bin\/yarn is the yarn directory/) expect(app.output).to_not include(".heroku/yarn/bin/yarn is the yarn directory") @@ -25,8 +27,9 @@ :default, "https://github.com/sharpstone/force_absolute_paths_buildpack" ] + config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} - Hatchet::Runner.new("minimal_webpacker", buildpacks: buildpacks).deploy do |app, heroku| + Hatchet::Runner.new("minimal_webpacker", buildpacks: buildpacks, config: config).deploy do |app, heroku| expect(app.output).to include("yarn install") expect(app.output).to include(".heroku/yarn/bin/yarn is the yarn directory") expect(app.output).to include(".heroku/node/bin/node is the node directory") diff --git a/spec/hatchet/ruby_spec.rb b/spec/hatchet/ruby_spec.rb index 3d543a3c5..1fc7b4f74 100644 --- a/spec/hatchet/ruby_spec.rb +++ b/spec/hatchet/ruby_spec.rb @@ -113,7 +113,9 @@ :default, "https://github.com/sharpstone/force_absolute_paths_buildpack" ] - Hatchet::Runner.new('cd_ruby', stack: DEFAULT_STACK, buildpacks: buildpacks).deploy do |app| + config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} + + Hatchet::Runner.new('cd_ruby', stack: DEFAULT_STACK, buildpacks: buildpacks, config: config).deploy do |app| expect(app.output).to match("cd version ruby 2.5.1") expect(app.run("which ruby").chomp).to eq("/app/bin/ruby")