<commit_before>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/jvm_installer"
require "language_pack/helpers/layer"
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
    @jvm_installer     = LanguagePack::Helpers::JvmInstaller.new(slug_vendor_jvm, @stack)
  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({
        "JAVA_OPTS" => default_java_opts,
        "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
      install_ruby(slug_vendor_ruby, build_ruby_path)
      install_jvm
      setup_language_pack_environment
      setup_export
      setup_profiled
      allow_git do
        vendor_libpq
        install_bundler_in_app(slug_vendor_base)
        load_bundler_cache
        build_bundler(path: "vendor/bundle", default_bundle_without: "development:test")
        post_bundler
        create_database_yml
        install_binaries
        run_assets_precompile_rake_task
      end
      config_detect
      best_practice_warnings
      warn_outdated_ruby
      cleanup
      super
    end
  rescue => e
    warn_outdated_ruby
    raise e
  end

  def vendor_libpq
    # Check for existing libraries
    return unless File.exist?("/usr/lib/x86_64-linux-gnu/libpq.so.5.11")
    return unless ENV['STACK'] == 'heroku-18'

    topic("Vendoring libpq 5.12.1")

    @metadata.fetch("vendor_libpq12") do
      warn(<<~EOF)
        Replacing libpq with version libpq 5.12.1

        This version includes a bug fix that can cause an exception
        on boot for applications with incorrectly configured connection
        values. For more information see:

          https://devcenter.heroku.com/articles/libpq-5-12-1-breaking-connection-behavior

        If your application breaks you can rollback to your last build.
        You can also temporarially opt out of this behavior by setting:

        ```
        $ heroku config:set HEROKU_SKIP_LIBPQ12=1
        ```

        In the future libpq 5.12 will be the default on the platform and
        you will not be able to opt-out of the library. For more information see:

          https://devcenter.heroku.com/articles/libpq-5-12-1-breaking-connection-behavior
      EOF

      "true" # Set future cache value
    end

    Dir.chdir("vendor") do
      @fetchers[:mri].fetch("libpq5_12.1-1.deb")
      run!("dpkg -x libpq5_12.1-1.deb .")
      run!("rm libpq5_12.1-1.deb")

      load_libpq_12_unless_env_var = <<~EOF
        if [ "$HEROKU_SKIP_LIBPQ12" == "" ]; then
          export LD_LIBRARY_PATH="$HOME/vendor/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH"
        fi
      EOF
      add_to_export load_libpq_12_unless_env_var
      add_to_profiled load_libpq_12_unless_env_var
      ENV["LD_LIBRARY_PATH"] = Dir.pwd + "/usr/lib/x86_64-linux-gnu:#{ENV["LD_LIBRARY_PATH"]}"
    end
  end

  def build
    remove_vendor_bundle

    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)
    setup_language_pack_environment(ruby_layer.path, gem_layer.path)
    setup_export(gem_layer)
    setup_profiled(ruby_layer.path, gem_layer.path)
    allow_git do
      # TODO install bundler in separate layer
      topic "Loading Bundler Cache"
      gem_layer.validate! do |metadata|<commit_msg>check new_app at beginning, removing purging stack message on first build<commit_after>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/jvm_installer"
require "language_pack/helpers/layer"
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
    @jvm_installer     = LanguagePack::Helpers::JvmInstaller.new(slug_vendor_jvm, @stack)
  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({
        "JAVA_OPTS" => default_java_opts,
        "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
      install_ruby(slug_vendor_ruby, build_ruby_path)
      install_jvm
      setup_language_pack_environment
      setup_export
      setup_profiled
      allow_git do
        vendor_libpq
        install_bundler_in_app(slug_vendor_base)
        load_bundler_cache
        build_bundler(path: "vendor/bundle", default_bundle_without: "development:test")
        post_bundler
        create_database_yml
        install_binaries
        run_assets_precompile_rake_task
      end
      config_detect
      best_practice_warnings
      warn_outdated_ruby
      cleanup
      super
    end
  rescue => e
    warn_outdated_ruby
    raise e
  end

  def vendor_libpq
    # Check for existing libraries
    return unless File.exist?("/usr/lib/x86_64-linux-gnu/libpq.so.5.11")
    return unless ENV['STACK'] == 'heroku-18'

    topic("Vendoring libpq 5.12.1")

    @metadata.fetch("vendor_libpq12") do
      warn(<<~EOF)
        Replacing libpq with version libpq 5.12.1

        This version includes a bug fix that can cause an exception
        on boot for applications with incorrectly configured connection
        values. For more information see:

          https://devcenter.heroku.com/articles/libpq-5-12-1-breaking-connection-behavior

        If your application breaks you can rollback to your last build.
        You can also temporarially opt out of this behavior by setting:

        ```
        $ heroku config:set HEROKU_SKIP_LIBPQ12=1
        ```

        In the future libpq 5.12 will be the default on the platform and
        you will not be able to opt-out of the library. For more information see:

          https://devcenter.heroku.com/articles/libpq-5-12-1-breaking-connection-behavior
      EOF

      "true" # Set future cache value
    end

    Dir.chdir("vendor") do
      @fetchers[:mri].fetch("libpq5_12.1-1.deb")
      run!("dpkg -x libpq5_12.1-1.deb .")
      run!("rm libpq5_12.1-1.deb")

      load_libpq_12_unless_env_var = <<~EOF
        if [ "$HEROKU_SKIP_LIBPQ12" == "" ]; then
          export LD_LIBRARY_PATH="$HOME/vendor/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH"
        fi
      EOF
      add_to_export load_libpq_12_unless_env_var
      add_to_profiled load_libpq_12_unless_env_var
      ENV["LD_LIBRARY_PATH"] = Dir.pwd + "/usr/lib/x86_64-linux-gnu:#{ENV["LD_LIBRARY_PATH"]}"
    end
  end

  def build
    new_app?
    remove_vendor_bundle

    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)
    setup_language_pack_environment(ruby_layer.path, gem_layer.path)
    setup_export(gem_layer)
    setup_profiled(ruby_layer.path, gem_layer.path)
    allow_git do
      # TODO install bundler in separate layer
      topic "Loading Bundler Cache"
      gem_layer.validate! do |metadata|
{'fraction_non_alphanumeric': 0.0931831857646351, 'fraction_numerical': 0.011862804091807788, 'mean_word_length': 3.067832167832168, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 15, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4006620', 'n_tokens_mistral': 4050, 'n_tokens_neox': 3789, 'n_words': 818}