diff --git a/CHANGELOG.md b/CHANGELOG.md index 27f31dbec..e1305f3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Main (unreleased) +* Ruby buildpack now relies on the JVM buildpack to install java for Jruby apps (https://github.com/heroku/heroku-buildpack-ruby/pull/1119) + ## v223 (1/22/2021) * Fix Gemfile.lock read bug from preventing propper removal of BUNDLED WITH declaration (https://github.com/heroku/heroku-buildpack-ruby/pull/1108) diff --git a/bin/compile b/bin/compile index a38c4ba54..e3c85c27e 100755 --- a/bin/compile +++ b/bin/compile @@ -2,10 +2,30 @@ # The actual compilation code lives in `bin/support/ruby_compile`. This file instead # bootstraps the ruby needed and then executes `bin/support/ruby_compile` +BUILD_DIR=$1 +CACHE_DIR=$2 +ENV_DIR=$3 BIN_DIR=$(cd $(dirname $0); pwd) BUILDPACK_DIR=$(dirname $BIN_DIR) source "$BIN_DIR/support/bash_functions.sh" heroku_buildpack_ruby_install_ruby "$BIN_DIR" "$BUILDPACK_DIR" +if detect_needs_java "$BUILD_DIR"; then + cat < Installing Java + +EOM + + compile_buildpack_v2 "$BUILD_DIR" "$CACHE_DIR" "$ENV_DIR" "https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/jvm.tgz" "heroku/jvm" +fi + $heroku_buildpack_ruby_dir/bin/ruby $BIN_DIR/support/ruby_compile $@ diff --git a/bin/support/bash_functions.sh b/bin/support/bash_functions.sh index c6a9ce98f..4bc4a70e5 100755 --- a/bin/support/bash_functions.sh +++ b/bin/support/bash_functions.sh @@ -1,3 +1,15 @@ +#!/usr/bin/env bash + +curl_retry_on_18() { + local ec=18; + local attempts=0; + while (( ec == 18 && attempts++ < 3 )); do + curl "$@" # -C - would return code 33 if unsupported by server + ec=$? + done + return $ec +} + # This function will install a version of Ruby onto the # system for the buidlpack to use. It coordinates download # and setting appropriate env vars for execution @@ -44,3 +56,100 @@ heroku_buildpack_ruby_install_ruby() export PATH=$heroku_buildpack_ruby_dir/bin/:$PATH unset GEM_PATH } + +which_java() +{ + which java > /dev/null +} + +# Detects if a given Gemfile.lock has jruby in it +# $ cat Gemfile.lock | grep jruby # => ruby 2.5.7p001 (jruby 9.2.13.0) +detect_needs_java() +{ + local app_dir=$1 + local gemfile_lock="$app_dir/Gemfile.lock" + # local needs_jruby=0 + local skip_java_install=1 + + if which_java; then + return $skip_java_install + fi + grep "(jruby " "$gemfile_lock" --quiet +} + +# Runs another buildpack against the build dir +# +# Example: +# +# compile_buildpack_v2 "$build_dir" "$cache_dir" "$env_dir" "https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/nodejs.tgz" "heroku/nodejs" +# +compile_buildpack_v2() +{ + local build_dir=$1 + local cache_dir=$2 + local env_dir=$3 + local buildpack=$4 + local name=$5 + + local dir + local url + local branch + + dir=$(mktemp -t buildpackXXXXX) + rm -rf "$dir" + + url=${buildpack%#*} + branch=${buildpack#*#} + + if [ "$branch" == "$url" ]; then + branch="" + fi + + if [ "$url" != "" ]; then + echo "-----> Downloading Buildpack: ${name}" + + if [[ "$url" =~ \.tgz$ ]] || [[ "$url" =~ \.tgz\? ]]; then + mkdir -p "$dir" + curl_retry_on_18 -s "$url" | tar xvz -C "$dir" >/dev/null 2>&1 + else + git clone "$url" "$dir" >/dev/null 2>&1 + fi + cd "$dir" || return + + if [ "$branch" != "" ]; then + git checkout "$branch" >/dev/null 2>&1 + fi + + # we'll get errors later if these are needed and don't exist + chmod -f +x "$dir/bin/{detect,compile,release}" || true + + framework=$("$dir"/bin/detect "$build_dir") + + # shellcheck disable=SC2181 + if [ $? == 0 ]; then + echo "-----> Detected Framework: $framework" + "$dir"/bin/compile "$build_dir" "$cache_dir" "$env_dir" + + # shellcheck disable=SC2181 + if [ $? != 0 ]; then + exit 1 + fi + + # check if the buildpack left behind an environment for subsequent ones + if [ -e "$dir/export" ]; then + set +u # http://redsymbol.net/articles/unofficial-bash-strict-mode/#sourcing-nonconforming-document + # shellcheck disable=SC1090 + source "$dir/export" + set -u # http://redsymbol.net/articles/unofficial-bash-strict-mode/#sourcing-nonconforming-document + fi + + if [ -x "$dir/bin/release" ]; then + "$dir"/bin/release "$build_dir" > "$1"/last_pack_release.out + fi + else + echo "Couldn't detect any framework for this buildpack. Exiting." + exit 1 + fi + fi +} + diff --git a/bin/test-compile b/bin/test-compile index 79c482e0f..1c996c15a 100755 --- a/bin/test-compile +++ b/bin/test-compile @@ -2,10 +2,30 @@ # The actual `bin/test-compile` code lives in `bin/ruby_test-compile`. This file instead # bootstraps the ruby needed and then executes `bin/ruby_test-compile` +BUILD_DIR=$1 +CACHE_DIR=$2 +ENV_DIR=$3 BIN_DIR=$(cd $(dirname $0); pwd) BUILDPACK_DIR=$(dirname $BIN_DIR) source "$BIN_DIR/support/bash_functions.sh" heroku_buildpack_ruby_install_ruby "$BIN_DIR" "$BUILDPACK_DIR" +if detect_needs_java "$BUILD_DIR"; then + cat < Installing Java + +EOM + + compile_buildpack_v2 "$BUILD_DIR" "$CACHE_DIR" "$ENV_DIR" "https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/jvm.tgz" "heroku/jvm" +fi + $heroku_buildpack_ruby_dir/bin/ruby $BIN_DIR/support/ruby_test-compile $@ diff --git a/changelogs/unreleased/jvm_installer_default.md b/changelogs/unreleased/jvm_installer_default.md new file mode 100644 index 000000000..742cf721c --- /dev/null +++ b/changelogs/unreleased/jvm_installer_default.md @@ -0,0 +1,13 @@ +## JVM support for JRuby apps is now provided by the `heroku/jvm` buildpack + +JRuby applications require the JVM to run. Before this change, the JVM for JRuby apps was provided by the `heroku/ruby` buildpack for JRuby applications. + +Now the custom JVM installation logic in the `heroku/ruby` buildpack has been removed, and instead, the `heroku/jvm` buildpack will be called directly by the Ruby buildpack. + +Instead of relying on this functionality, it is recommended to manually require the `heroku/jvm` buildpack for your application: + +``` +$ heroku buildpacks:add heroku/jvm --index=1 +``` + +For more information on JRuby support, see the [Heroku Ruby Support page](https://devcenter.heroku.com/articles/ruby-support). diff --git a/hatchet.json b/hatchet.json index a42cf1e30..df02f33e2 100644 --- a/hatchet.json +++ b/hatchet.json @@ -22,7 +22,6 @@ "sharpstone/cd_ruby", "sharpstone/ruby_193_jruby_173", "sharpstone/ruby_193_jruby_176", - "sharpstone/ruby_193_jruby_1_7_27", "sharpstone/ruby_193_jruby_17161_jdk7", "sharpstone/ruby_193_bad_patch_cedar_14", "sharpstone/ruby_25", diff --git a/hatchet.lock b/hatchet.lock index 07afb37c4..0a94efcd8 100644 --- a/hatchet.lock +++ b/hatchet.lock @@ -95,8 +95,6 @@ - a08e8b2cc61d08bd611accaa455dbcbfff80f831 - - "./repos/ruby/ruby_193_jruby_176" - 9fcbc184cb386abc8784e9935d52d403e35c532c -- - "./repos/ruby/ruby_193_jruby_1_7_27" - - 06d5e86180ff0e5b6894f823736cd649dcfebed4 - - "./repos/ruby/ruby_25" - 0cb3df80d55b61e9417f2ac00adb06e15ae37982 - - "./repos/ruby/ruby_version_does_not_exist" diff --git a/lib/language_pack/helpers/jvm_installer.rb b/lib/language_pack/helpers/jvm_installer.rb deleted file mode 100644 index f4d1ac71c..000000000 --- a/lib/language_pack/helpers/jvm_installer.rb +++ /dev/null @@ -1,120 +0,0 @@ -require "language_pack/shell_helpers" - -class LanguagePack::Helpers::JvmInstaller - include LanguagePack::ShellHelpers - - SYS_PROPS_FILE = "system.properties" - JVM_BUCKET = "https://lang-jvm.s3.amazonaws.com" - JVM_BASE_URL = "#{JVM_BUCKET}/jdk" - JVM_1_9_PATH = "openjdk1.9-latest.tar.gz" - JVM_1_8_PATH = "openjdk1.8-latest.tar.gz" - JVM_1_7_PATH = "openjdk1.7-latest.tar.gz" - JVM_1_7_25_PATH = "openjdk1.7.0_25.tar.gz" - JVM_1_6_PATH = "openjdk1.6-latest.tar.gz" - - PG_CONFIG_JAR = "pgconfig.jar" - - PREINSTALLED_JDK_DIR = ".jdk" - - def initialize(slug_vendor_jvm, stack) - @vendor_dir = slug_vendor_jvm - @stack = stack - @fetcher = LanguagePack::Fetcher.new(JVM_BASE_URL, stack) - @pg_config_jar_fetcher = LanguagePack::Fetcher.new(JVM_BUCKET) - end - - def java_home - if preinstalled_jdk? - PREINSTALLED_JDK_DIR - else - @vendor_dir - end - end - - def preinstalled_jdk? - Dir.exist?(PREINSTALLED_JDK_DIR) - end - - def system_properties - props = {} - File.read(SYS_PROPS_FILE).split("\n").each do |line| - key = line.split("=").first - val = line.split("=").last - props[key] = val - end if File.exists?(SYS_PROPS_FILE) - props - end - - def install(jruby_version, forced = false) - if preinstalled_jdk? - topic "Using pre-installed JDK" - return - end - - jvm_version = system_properties['java.runtime.version'] - case jvm_version - when "1.9", "9" - fetch_env_untar('JDK_URL_1_9') || fetch_untar(JVM_1_9_PATH, "openjdk-9") - when "1.7", "7" - fetch_env_untar('JDK_URL_1_7') || fetch_untar(JVM_1_7_PATH, "openjdk-7") - when "1.6", "6" - fetch_env_untar('JDK_URL_1_6') || fetch_untar(JVM_1_6_PATH, "openjdk-6") - when "1.8", "8", nil - if @stack == "cedar" - if forced || Gem::Version.new(jruby_version) >= Gem::Version.new("1.7.4") - fetch_untar(JVM_1_7_PATH, "openjdk-7") - else - fetch_untar(JVM_1_7_25_PATH) - end - else - fetch_env_untar('JDK_URL_1_8') || fetch_untar(JVM_1_8_PATH, "openjdk-8") - end - else - fetch_untar("openjdk#{jvm_version}.tar.gz", "openjdk-#{jvm_version}") - end - - bin_dir = "bin" - FileUtils.mkdir_p bin_dir - Dir["#{@vendor_dir}/bin/*"].each do |bin| - run("ln -s ../#{bin} #{bin_dir}") - end - - install_pgconfig_jar - end - - def fetch_untar(jvm_path, jvm_version=nil) - topic "Installing JVM: #{jvm_version || jvm_path}" - - FileUtils.mkdir_p(@vendor_dir) - Dir.chdir(@vendor_dir) do - @fetcher.fetch_untar(jvm_path) - end - rescue LanguagePack::Fetcher::FetchError - error < default_java_opts, "JRUBY_OPTS" => default_jruby_opts }) : vars end @@ -99,7 +96,6 @@ def compile warn_bundler_upgrade 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("."), @@ -255,12 +251,6 @@ def slug_vendor_ruby "vendor/#{ruby_version.version_without_patchlevel}" end - # the relative path to the vendored jvm - # @return [String] resulting path - def slug_vendor_jvm - "vendor/jvm" - end - # the absolute path of the build ruby to use during the buildpack # @return [String] resulting path def build_ruby_path @@ -284,40 +274,6 @@ def ruby_version end end - # default JAVA_OPTS - # return [String] string of JAVA_OPTS - def default_java_opts - "-Dfile.encoding=UTF-8" - end - - def set_jvm_max_heap - <<-EOF -limit=$(ulimit -u) -case $limit in -512) # 2X, private-s: memory.limit_in_bytes=1073741824 - echo "$opts -Xmx671m -XX:CICompilerCount=2" - ;; -16384) # perf-m, private-m: memory.limit_in_bytes=2684354560 - echo "$opts -Xmx2g" - ;; -32768) # perf-l, private-l: memory.limit_in_bytes=15032385536 - echo "$opts -Xmx12g" - ;; -*) # Free, Hobby, 1X: memory.limit_in_bytes=536870912 - echo "$opts -Xmx300m -Xss512k -XX:CICompilerCount=2" - ;; -esac -EOF - end - - def set_java_mem - <<-EOF -if ! [[ "${JAVA_OPTS}" == *-Xmx* ]]; then - export JAVA_MEM=${JAVA_MEM:--Xmx${JVM_MAX_HEAP:-384}m} -fi -EOF - end - def set_default_web_concurrency <<-EOF case $(ulimit -u) in @@ -349,23 +305,12 @@ def default_jruby_opts "-Xcompile.invokedynamic=false" end - # default Java Xmx - # return [String] string of Java Xmx - def default_java_mem - "-Xmx${JVM_MAX_HEAP:-384}m" - 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["JAVA_MEM"] = run(<<~SHELL).strip - #{set_jvm_max_heap} - echo #{default_java_mem} - SHELL ENV["JRUBY_OPTS"] = env('JRUBY_BUILD_OPTS') || env('JRUBY_OPTS') - ENV["JAVA_HOME"] = @jvm_installer.java_home end setup_ruby_install_env(ruby_layer_path) @@ -400,7 +345,6 @@ def setup_language_pack_environment(ruby_layer_path:, gem_layer_path:, bundle_pa 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 << "#{slug_vendor_jvm}/bin" if ruby_version.jruby? paths << ENV["PATH"] ENV["PATH"] = paths.join(":") @@ -443,9 +387,6 @@ def setup_export(layer = nil) # TODO handle jruby if ruby_version.jruby? - add_to_export set_jvm_max_heap - add_to_export set_java_mem - set_export_default "JAVA_OPTS", default_java_opts set_export_default "JRUBY_OPTS", default_jruby_opts end @@ -484,9 +425,6 @@ def setup_profiled(ruby_layer_path: , gem_layer_path: ) # TODO handle JRUBY if ruby_version.jruby? - add_to_profiled set_jvm_max_heap - add_to_profiled set_java_mem - set_env_default "JAVA_OPTS", default_java_opts set_env_default "JRUBY_OPTS", default_jruby_opts end @@ -693,15 +631,6 @@ def new_app? @new_app ||= !File.exist?("vendor/heroku") end - # vendors JVM into the slug for JRuby - def install_jvm(forced = false) - instrument 'ruby.install_jvm' do - if ruby_version.jruby? || forced - @jvm_installer.install(ruby_version.engine_version, forced) - end - end - 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 = ".") @@ -719,10 +648,6 @@ def ruby_install_binstub_path(ruby_layer_path = ".") 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"]}" - - if ruby_version.jruby? - ENV['JAVA_OPTS'] = default_java_opts - end end end @@ -937,7 +862,6 @@ def build_bundler env_vars["RUBYOPT"] = syck_hack env_vars["NOKOGIRI_USE_SYSTEM_LIBRARIES"] = "true" env_vars["BUNDLE_DISABLE_VERSION_CHECK"] = "true" - env_vars["JAVA_HOME"] = noshellescape("#{pwd}/$JAVA_HOME") if ruby_version.jruby? env_vars["BUNDLER_LIB_PATH"] = "#{bundler_path}" if ruby_version.ruby_version == "1.8.7" env_vars["BUNDLE_DISABLE_VERSION_CHECK"] = "true" diff --git a/lib/language_pack/test/ruby.rb b/lib/language_pack/test/ruby.rb index 19cb97fd2..73d3351c7 100644 --- a/lib/language_pack/test/ruby.rb +++ b/lib/language_pack/test/ruby.rb @@ -7,7 +7,6 @@ def compile remove_vendor_bundle 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("."), diff --git a/spec/hatchet/jvm_spec.rb b/spec/hatchet/jvm_spec.rb deleted file mode 100644 index d33b745b4..000000000 --- a/spec/hatchet/jvm_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' - -describe "JvmInstaller" do - it "JVM is installed by jvm-common only" do - buildpacks = [ - "heroku/jvm", - :default # default is heroku-ruby-buildpack here - - ] - Hatchet::Runner.new("ruby_193_jruby_1_7_27", stack: 'heroku-18', buildpacks: buildpacks).deploy do |app| - expect(app.output).to match("Using pre-installed JDK") - expect(app.run("java -version")).to match("1.8.0") - sleep 3 - expect(app.run("ls .jdk/jre/lib/ext")).to match("pgconfig.jar") - end - end -end diff --git a/spec/hatchet/rubies_spec.rb b/spec/hatchet/rubies_spec.rb index 666a50a26..49b2ec815 100644 --- a/spec/hatchet/rubies_spec.rb +++ b/spec/hatchet/rubies_spec.rb @@ -13,19 +13,45 @@ end describe "Ruby versions" do - it "should deploy jdk 8 on heroku-18 by default" do - app = Hatchet::Runner.new("ruby_193_jruby_1_7_27", stack: "heroku-18") - app.deploy do |app| - expect(app.output).to match("Installing JVM: openjdk-8") - expect(app.output).to match("JRUBY_OPTS is: -Xcompile.invokedynamic=false") - expect(app.output).not_to include("OpenJDK 64-Bit Server VM warning") + it "should deploy jdk on heroku-20" do + Hatchet::Runner.new("default_ruby", stack: "heroku-20").tap do |app| + app.before_deploy do |app| + Pathname("Gemfile.lock").write(<<~EOM) + GEM + remote: https://rubygems.org/ + specs: + rack (2.2.2) + rake (13.0.1) - run!('git commit -am "redeploy" --allow-empty') - app.set_config("JRUBY_BUILD_OPTS" => "--dev") - app.push! - expect(app.output).to match("JRUBY_OPTS is: --dev") + PLATFORMS + java + + DEPENDENCIES + rack + rake + + + RUBY VERSION + ruby 2.5.7p0 (jruby 9.2.13.0) + EOM + + Pathname("Rakefile").write(<<~'EOM') + task "assets:precompile" do + puts "JRUBY_OPTS is: #{ENV['JRUBY_OPTS']}" + end + EOM + end + + app.deploy do + expect(app.output).to match("JRUBY_OPTS is: -Xcompile.invokedynamic=false") + + app.set_config("JRUBY_BUILD_OPTS" => "--dev") + app.commit! + app.push! + expect(app.output).to match("JRUBY_OPTS is: --dev") - expect(app.run("ls vendor/jvm/jre/lib/ext")).to match("pgconfig.jar") + expect(app.run("ls .jdk/jre/lib/ext/")).to match("pgconfig.jar") + end end end diff --git a/spec/helpers/bundler_wrapper_spec.rb b/spec/helpers/bundler_wrapper_spec.rb index d20fcc9f5..279b4f617 100644 --- a/spec/helpers/bundler_wrapper_spec.rb +++ b/spec/helpers/bundler_wrapper_spec.rb @@ -42,34 +42,44 @@ def wrapper.topic(*args); end # Silence output in tests it "detects windows gemfiles" do Hatchet::App.new("rails4_windows_mri193").in_directory_fork do |dir| - expect(@bundler.install.windows_gemfile_lock?).to be_truthy + Bundler.with_unbundled_env do + expect(@bundler.install.windows_gemfile_lock?).to be_truthy + end end end describe "when executing bundler" do - before do - @bundler.install - end - it "handles apps with ruby versions locked in Gemfile.lock" do Hatchet::App.new("problem_gemfile_version").in_directory_fork do |dir| - expect(@bundler.ruby_version).to eq("ruby-2.5.1-p0") + Bundler.with_unbundled_env do + @bundler.install - ruby_version = LanguagePack::RubyVersion.new(@bundler.ruby_version, is_new: true) - expect(ruby_version.version_for_download).to eq("ruby-2.5.1") + expect(@bundler.ruby_version).to eq("ruby-2.5.1-p0") + + ruby_version = LanguagePack::RubyVersion.new(@bundler.ruby_version, is_new: true) + expect(ruby_version.version_for_download).to eq("ruby-2.5.1") + end end end it "handles JRuby pre gemfiles" do Hatchet::App.new("jruby-minimal").in_directory_fork do |dir| - expect(@bundler.ruby_version).to eq("ruby-2.3.1-p0-jruby-9.1.7.0") + Bundler.with_unbundled_env do + @bundler.install + + expect(@bundler.ruby_version).to eq("ruby-2.3.1-p0-jruby-9.1.7.0") + end end end it "handles app with output in their Gemfile" do Hatchet::App.new("problem_gemfile_version").in_directory_fork do |dir| - run!(%{echo '\nputs "some output"\n' >> Gemfile}) - expect(@bundler.ruby_version).to eq("ruby-2.5.1-p0") + Bundler.with_unbundled_env do + @bundler.install + + run!(%{echo '\nputs "some output"\n' >> Gemfile}) + expect(@bundler.ruby_version).to eq("ruby-2.5.1-p0") + end end end end diff --git a/spec/helpers/jvm_installer_spec.rb b/spec/helpers/jvm_installer_spec.rb deleted file mode 100644 index 7a4d806b3..000000000 --- a/spec/helpers/jvm_installer_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -require 'spec_helper' - -describe "JvmInstall" do - - it "downloads custom JDK" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - begin - ENV['JDK_URL_1_8'] = "http://lang-jvm.s3.amazonaws.com/jdk/openjdk1.8.0_51-cedar14.tar.gz" - - jvm_installer = LanguagePack::Helpers::JvmInstaller.new(dir, "heroku-16") - jvm_installer.install("9.0.1.0") - - expect(`ls bin`).to match("java") - expect(`cat release 2>&1`).to match("1.8.0_51") - ensure - ENV['JDK_URL_1_8'] = nil - end - end - end - end - - it "downloads standard JDK 7" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - File.open('system.properties', 'w') { |f| f.write("java.runtime.version=1.7") } - - jvm_installer = LanguagePack::Helpers::JvmInstaller.new(dir, "heroku-16") - jvm_installer.install("9.0.1.0") - - expect(`ls bin`).to match("java") - expect(`cat release 2>&1`).not_to match("1.8.0") - expect(`cat release 2>&1`).to match("1.7.0") - end - end - end - - it "downloads standard JDK 9" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - File.open('system.properties', 'w') { |f| f.write("java.runtime.version=1.9") } - - jvm_installer = LanguagePack::Helpers::JvmInstaller.new(dir, "heroku-16") - jvm_installer.install("9.0.1.0") - - expect(`ls bin`).to match("java") - expect(`cat release 2>&1`).not_to match("1.8.0") - expect(`cat release 2>&1`).to match("9") - end - end - end - - it "downloads previous JDK version" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - File.open('system.properties', 'w') { |f| f.write("java.runtime.version=1.8.0_51") } - - jvm_installer = LanguagePack::Helpers::JvmInstaller.new(dir, "cedar-14") - jvm_installer.install("9.0.1.0") - - expect(`ls bin`).to match("java") - expect(`cat release 2>&1`).to match("1.8.0_51") - end - end - end - - it "downloads default JDK" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - jvm_installer = LanguagePack::Helpers::JvmInstaller.new(dir, "heroku-16") - jvm_installer.install("9.0.1.0") - - expect(`ls bin`).to match("java") - expect(`cat release 2>&1`).not_to match("1.8.0_51") - expect(`cat release 2>&1`).to match("1.8.0") - expect(`ls #{dir}/jre/lib/ext`).to match("pgconfig.jar") - end - end - end - - it "fails download gracefully" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - File.open('system.properties', 'w') { |f| f.write("java.runtime.version=foobar") } - - jvm_installer = LanguagePack::Helpers::JvmInstaller.new(dir, "heroku-16") - - expect{ jvm_installer.install("9.0.1.0") }.to raise_error(BuildpackError) - end - end - end -end diff --git a/spec/helpers/ruby_version_spec.rb b/spec/helpers/ruby_version_spec.rb index 1fe2eff89..46a8d28cb 100644 --- a/spec/helpers/ruby_version_spec.rb +++ b/spec/helpers/ruby_version_spec.rb @@ -17,25 +17,23 @@ end it "knows the next logical version" do - Hatchet::App.new("ruby_25").in_directory_fork do |dir| - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = "2.5.0" - version = "ruby-#{version_number}" - - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.next_logical_version).to eq("ruby-2.5.1") - expect(ruby_version.next_logical_version).to eq("ruby-2.5.1") - expect(ruby_version.next_logical_version(2)).to eq("ruby-2.5.2") - expect(ruby_version.next_logical_version(20)).to eq("ruby-2.5.20") - - # Minor version - expect(ruby_version.next_minor_version).to eq("ruby-2.6.0") - expect(ruby_version.next_minor_version(2)).to eq("ruby-2.7.0") - - # Major Version - expect(ruby_version.next_major_version).to eq("ruby-3.0.0") - expect(ruby_version.next_major_version(2)).to eq("ruby-4.0.0") - end + version_number = "2.5.0" + ruby_version = LanguagePack::RubyVersion.new("ruby-#{version_number}-p0", is_new: true) + version = "ruby-#{version_number}" + + expect(ruby_version.version_without_patchlevel).to eq(version) + expect(ruby_version.next_logical_version).to eq("ruby-2.5.1") + expect(ruby_version.next_logical_version).to eq("ruby-2.5.1") + expect(ruby_version.next_logical_version(2)).to eq("ruby-2.5.2") + expect(ruby_version.next_logical_version(20)).to eq("ruby-2.5.20") + + # Minor version + expect(ruby_version.next_minor_version).to eq("ruby-2.6.0") + expect(ruby_version.next_minor_version(2)).to eq("ruby-2.7.0") + + # Major Version + expect(ruby_version.next_major_version).to eq("ruby-3.0.0") + expect(ruby_version.next_major_version(2)).to eq("ruby-4.0.0") end it "does not include patchlevels when the patchlevel is negative for download" do @@ -64,66 +62,76 @@ it "correctly sets ruby version for bundler specified versions" do Hatchet::App.new("mri_193").in_directory_fork do |dir| - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = "1.9.3" - version = "ruby-#{version_number}" - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(version_number) - expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") - expect(ruby_version.engine).to eq(:ruby) + Bundler.with_unbundled_env do + ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) + version_number = "1.9.3" + version = "ruby-#{version_number}" + expect(ruby_version.version_without_patchlevel).to eq(version) + expect(ruby_version.engine_version).to eq(version_number) + expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") + expect(ruby_version.engine).to eq(:ruby) + end end end it "correctly sets default ruby versions" do Hatchet::App.new("default_ruby").in_directory_fork do |dir| - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER - version = LanguagePack::RubyVersion::DEFAULT_VERSION - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(version_number) - expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") - expect(ruby_version.engine).to eq(:ruby) - expect(ruby_version.default?).to eq(true) + Bundler.with_unbundled_env do + ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) + version_number = LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER + version = LanguagePack::RubyVersion::DEFAULT_VERSION + expect(ruby_version.version_without_patchlevel).to eq(version) + expect(ruby_version.engine_version).to eq(version_number) + expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") + expect(ruby_version.engine).to eq(:ruby) + expect(ruby_version.default?).to eq(true) + end end end it "correctly sets default legacy version" do Hatchet::App.new("default_ruby").in_directory_fork do |dir| - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: false) - version_number = LanguagePack::RubyVersion::LEGACY_VERSION_NUMBER - version = LanguagePack::RubyVersion::LEGACY_VERSION - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(version_number) - expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") - expect(ruby_version.engine).to eq(:ruby) + Bundler.with_unbundled_env do + ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: false) + version_number = LanguagePack::RubyVersion::LEGACY_VERSION_NUMBER + version = LanguagePack::RubyVersion::LEGACY_VERSION + expect(ruby_version.version_without_patchlevel).to eq(version) + expect(ruby_version.engine_version).to eq(version_number) + expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") + expect(ruby_version.engine).to eq(:ruby) + end end end it "detects Ruby 2.0.0" do Hatchet::App.new("mri_200").in_directory_fork do |dir| - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = "2.0.0" - version = "ruby-#{version_number}" - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(version_number) - expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") - expect(ruby_version.engine).to eq(:ruby) + Bundler.with_unbundled_env do + ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) + version_number = "2.0.0" + version = "ruby-#{version_number}" + expect(ruby_version.version_without_patchlevel).to eq(version) + expect(ruby_version.engine_version).to eq(version_number) + expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") + expect(ruby_version.engine).to eq(:ruby) + end end end it "detects non mri engines" do Hatchet::App.new("ruby_193_jruby_173").in_directory_fork do |dir| - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = "1.9.3" - engine_version = "1.7.3" - engine = :jruby - version = "ruby-#{version_number}-#{engine}-#{engine_version}" - to_gemfile = "ruby '#{version_number}', :engine => '#{engine}', :engine_version => '#{engine_version}'" - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(engine_version) - expect(ruby_version.to_gemfile).to eq(to_gemfile) - expect(ruby_version.engine).to eq(engine) + Bundler.with_unbundled_env do + ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) + version_number = "1.9.3" + engine_version = "1.7.3" + engine = :jruby + version = "ruby-#{version_number}-#{engine}-#{engine_version}" + to_gemfile = "ruby '#{version_number}', :engine => '#{engine}', :engine_version => '#{engine_version}'" + expect(ruby_version.version_without_patchlevel).to eq(version) + expect(ruby_version.engine_version).to eq(engine_version) + expect(ruby_version.to_gemfile).to eq(to_gemfile) + expect(ruby_version.engine).to eq(engine) + end end end @@ -131,8 +139,10 @@ bundle_error_msg = "Zomg der was a problem in da gemfile" error_klass = LanguagePack::Helpers::BundlerWrapper::GemfileParseError Hatchet::App.new("bad_gemfile_on_platform").in_directory_fork do |dir| - @bundler = LanguagePack::Helpers::BundlerWrapper.new().install - expect { LanguagePack::RubyVersion.new(@bundler.ruby_version) }.to raise_error(error_klass, /#{Regexp.escape(bundle_error_msg)}/) + Bundler.with_unbundled_env do + @bundler = LanguagePack::Helpers::BundlerWrapper.new().install + expect { LanguagePack::RubyVersion.new(@bundler.ruby_version) }.to raise_error(error_klass, /#{Regexp.escape(bundle_error_msg)}/) + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 84b3a4e74..70d6ddd7e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -80,3 +80,7 @@ def wait_for_dyno_boot(app, ps_name = "web", sleep_val = 1) def web_boot_status(app) wait_for_dyno_boot(app)["state"] end + +def root_dir + Pathname(__dir__).join("..") +end diff --git a/spec/unit/bash_functions_spec.rb b/spec/unit/bash_functions_spec.rb new file mode 100644 index 000000000..5226e656d --- /dev/null +++ b/spec/unit/bash_functions_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe "Bash functions" do + it "Detects jruby in the Gemfile.lock" do + Dir.mktmpdir do |dir| + dir = Pathname(dir) + dir.join("Gemfile.lock").write <<~EOM + RUBY VERSION + ruby 2.5.7p001 (jruby 9.2.13.0) + EOM + + + out = exec_with_bash_functions <<~EOM + which_java() + { + return 1 + } + + if detect_needs_java "#{dir}"; then + echo "jruby detected" + else + echo "nope" + fi + EOM + + expect(out).to eq("jruby detected") + + dir.join("Gemfile.lock").write <<~EOM + EOM + + out = exec_with_bash_functions <<~EOM + which_java() + { + return 1 + } + + if detect_needs_java "#{dir}"; then + echo "jruby detected" + else + echo "nope" + fi + EOM + + expect(out).to eq("nope") + end + end + + it "Detects java for jruby detection" do + Dir.mktmpdir do |dir| + dir = Pathname(dir) + dir.join("Gemfile.lock").write <<~EOM + RUBY VERSION + ruby 2.5.7p001 (jruby 9.2.13.0) + EOM + + out = exec_with_bash_functions <<~EOM + which_java() + { + return 0 + } + + if detect_needs_java "#{dir}"; then + echo "jruby detected" + else + echo "already installed" + fi + EOM + + expect(out).to eq("already installed") + end + end + + + def bash_functions_file + root_dir.join("bin", "support", "bash_functions.sh") + end + + def exec_with_bash_functions(code, stack: "heroku-18") + contents = <<~EOM + #! /usr/bin/env bash + set -eu + + STACK="#{stack}" + + #{bash_functions_file.read} + + #{code} + EOM + + file = Tempfile.new + file.write(contents) + file.close + FileUtils.chmod("+x", file.path) + + out = nil + success = false + begin + Timeout.timeout(60) do + out = `#{file.path} 2>&1`.strip + success = $?.success? + end + rescue Timeout::Error + out = "Command timed out" + success = false + end + unless success + message = <<~EOM + Contents: + + #{contents.lines.map.with_index { |line, number| " #{number.next} #{line.chomp}"}.join("\n") } + + Expected running script to succeed, but it did not + + Output: + + #{out} + EOM + + raise message + end + out + end +end