Skip to content

Commit

Permalink
Automatically install ruby-lsp-rails as part of custom bundle (#1381)
Browse files Browse the repository at this point in the history
Automatically install ruby-lsp-rails as part of custom bundle
  • Loading branch information
andyw8 authored Feb 16, 2024
1 parent c9d9a85 commit e43ac52
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 19 deletions.
52 changes: 34 additions & 18 deletions lib/ruby_lsp/setup_bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,18 @@ def initialize(project_path, **options)
def setup!
raise BundleNotLocked if @gemfile&.exist? && !@lockfile&.exist?

# Do not setup a custom bundle if both `ruby-lsp` and `debug` are already in the Gemfile
if @dependencies["ruby-lsp"] && @dependencies["debug"]
# Do not set up a custom bundle if LSP dependencies are already in the Gemfile
if @dependencies["ruby-lsp"] &&
@dependencies["debug"] &&
(@dependencies["rails"] ? @dependencies["ruby-lsp-rails"] : true)
$stderr.puts(
"Ruby LSP> Skipping custom bundle setup since both `ruby-lsp` and `debug` are already in #{@gemfile}",
"Ruby LSP> Skipping custom bundle setup since LSP dependencies are already in #{@gemfile}",
)

# If the user decided to add the `ruby-lsp` and `debug` to their Gemfile after having already run the Ruby LSP,
# then we need to remove the `.ruby-lsp` folder, otherwise we will run `bundle install` for the top level and
# try to execute the Ruby LSP using the custom bundle, which will fail since the gems are not installed there
# If the user decided to add `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) to their Gemfile after
# having already run the Ruby LSP, then we need to remove the `.ruby-lsp` folder, otherwise we will run `bundle
# install` for the top level and try to execute the Ruby LSP using the custom bundle, which will fail since the
# gems are not installed there
@custom_dir.rmtree if @custom_dir.exist?
return run_bundle_install
end
Expand Down Expand Up @@ -143,6 +146,10 @@ def write_custom_gemfile
parts << 'gem "debug", require: false, group: :development, platforms: :mri'
end

if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
parts << 'gem "ruby-lsp-rails", require: false, group: :development'
end

content = parts.join("\n")
@custom_gemfile.write(content) unless @custom_gemfile.exist? && @custom_gemfile.read == content
end
Expand Down Expand Up @@ -183,22 +190,24 @@ def run_bundle_install(bundle_gemfile = @gemfile)
local_config_path = File.join(Dir.pwd, ".bundle")
env["BUNDLE_APP_CONFIG"] = local_config_path if Dir.exist?(local_config_path)

# If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
# produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
# weren't a part of the Gemfile, then we need to run `bundle install` for the first time to generate the
# Gemfile.lock with them included or else Bundler will complain that they're missing. We can only update if the
# custom `.ruby-lsp/Gemfile.lock` already exists and includes both gems
# If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
# to upgrade them or else we'll produce undesired source control changes. If the custom bundle was just created
# and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
# install` for the first time to generate the Gemfile.lock with them included or else Bundler will complain that
# they're missing. We can only update if the custom `.ruby-lsp/Gemfile.lock` already exists and includes all gems

# When not updating, we run `(bundle check || bundle install)`
# When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
command = +"(bundle check"

if should_bundle_update?
# If ruby-lsp or debug are not in the Gemfile, try to update them to the latest version
# If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
# version
command.prepend("(")
command << " && bundle update "
command << "ruby-lsp " unless @dependencies["ruby-lsp"]
command << "debug " unless @dependencies["debug"]
command << "ruby-lsp-rails " if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
command << "--pre" if @experimental
command.delete_suffix!(" ")
command << ")"
Expand All @@ -221,13 +230,20 @@ def run_bundle_install(bundle_gemfile = @gemfile)

sig { returns(T::Boolean) }
def should_bundle_update?
# If both `ruby-lsp` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it will produce
# version control changes
return false if @dependencies["ruby-lsp"] && @dependencies["debug"]
# If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
# will produce version control changes
if @dependencies["rails"]
return false if @dependencies.values_at("ruby-lsp", "ruby-lsp-rails", "debug").all?

# If the custom lockfile doesn't include `ruby-lsp`, `ruby-lsp-rails` or `debug`, we need to run bundle install
# before updating
return false if custom_bundle_dependencies.values_at("ruby-lsp", "debug", "ruby-lsp-rails").any?(&:nil?)
else
return false if @dependencies.values_at("ruby-lsp", "debug").all?

# If the custom lockfile doesn't include either the `ruby-lsp` or `debug`, we need to run bundle install before
# updating
return false if custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
# If the custom lockfile doesn't include `ruby-lsp` or `debug`, we need to run bundle install before updating
return false if custom_bundle_dependencies.values_at("ruby-lsp", "debug").any?(&:nil?)
end

# If the last updated file doesn't exist or was updated more than 4 hours ago, we should update
!@last_updated_path.exist? || Time.parse(@last_updated_path.read) < (Time.now - FOUR_HOURS)
Expand Down
81 changes: 80 additions & 1 deletion test/setup_bundler_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ def test_does_nothing_if_both_ruby_lsp_and_debug_are_in_the_bundle
refute_path_exists(".ruby-lsp")
end

def test_does_nothing_if_both_ruby_lsp_and_debug_are_in_the_bundle2
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({
"ruby-lsp" => true,
"rails" => true,
"ruby-lsp-rails" => true,
"debug" => true,
})
run_script
refute_path_exists(".ruby-lsp")
end

def test_removes_ruby_lsp_folder_if_both_gems_were_added_to_the_bundle
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({ "ruby-lsp" => true, "debug" => true })
Expand All @@ -22,6 +34,27 @@ def test_removes_ruby_lsp_folder_if_both_gems_were_added_to_the_bundle
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
end

def test_in_a_rails_app_does_nothing_if_ruby_lsp_and_ruby_lsp_rails_and_debug_are_in_the_bundle
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
Bundler::LockfileParser.any_instance.expects(:dependencies)
.returns({ "ruby-lsp" => true, "ruby-lsp-rails" => true, "debug" => true })
run_script
refute_path_exists(".ruby-lsp")
ensure
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
end

def test_in_a_rails_app_removes_ruby_lsp_folder_if_all_gems_were_added_to_the_bundle
Object.any_instance.expects(:system).with(bundle_env, "(bundle check || bundle install) 1>&2")
Bundler::LockfileParser.any_instance.expects(:dependencies)
.returns({ "ruby-lsp" => true, "ruby-lsp-rails" => true, "debug" => true })
FileUtils.mkdir(".ruby-lsp")
run_script
refute_path_exists(".ruby-lsp")
ensure
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
end

def test_creates_custom_bundle
Object.any_instance.expects(:system).with(bundle_env(".ruby-lsp/Gemfile"), "(bundle check || bundle install) 1>&2")
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({}).at_least_once
Expand All @@ -32,9 +65,26 @@ def test_creates_custom_bundle
assert_path_exists(".ruby-lsp/Gemfile.lock")
assert_path_exists(".ruby-lsp/main_lockfile_hash")
assert_match("ruby-lsp", File.read(".ruby-lsp/Gemfile"))
refute_match("ruby-lsp-rails", File.read(".ruby-lsp/Gemfile"))
assert_match("debug", File.read(".ruby-lsp/Gemfile"))
ensure
FileUtils.rm_r(".ruby-lsp")
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
end

def test_creates_custom_bundle_for_a_rails_app
Object.any_instance.expects(:system).with(bundle_env(".ruby-lsp/Gemfile"), "(bundle check || bundle install) 1>&2")
Bundler::LockfileParser.any_instance.expects(:dependencies).returns({ "rails" => true }).at_least_once
run_script

assert_path_exists(".ruby-lsp")
assert_path_exists(".ruby-lsp/Gemfile")
assert_path_exists(".ruby-lsp/Gemfile.lock")
assert_path_exists(".ruby-lsp/main_lockfile_hash")
assert_match("ruby-lsp", File.read(".ruby-lsp/Gemfile"))
assert_match("debug", File.read(".ruby-lsp/Gemfile"))
assert_match("ruby-lsp-rails", File.read(".ruby-lsp/Gemfile"))
ensure
FileUtils.rm_r(".ruby-lsp") if Dir.exist?(".ruby-lsp")
end

def test_changing_lockfile_causes_custom_bundle_to_be_rebuilt
Expand Down Expand Up @@ -422,6 +472,35 @@ def test_ensures_lockfile_remotes_are_relative_to_default_gemfile
end
end

def test_ruby_lsp_rails_is_automatically_included_in_rails_apps
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
File.write(File.join(dir, "Gemfile"), <<~GEMFILE)
source "https://rubygems.org"
gem "rails"
GEMFILE

capture_subprocess_io do
Bundler.with_unbundled_env do
# Run bundle install to generate the lockfile
system("bundle install")
end
end

Object.any_instance.expects(:system).with(
bundle_env(".ruby-lsp/Gemfile"),
"(bundle check || bundle install) 1>&2",
)
Bundler.with_unbundled_env do
run_script
end

assert_path_exists(".ruby-lsp/Gemfile")
assert_match('gem "ruby-lsp-rails"', File.read(".ruby-lsp/Gemfile"))
end
end
end

private

# This method runs the script and then immediately unloads it. This allows us to make assertions against the effects
Expand Down

0 comments on commit e43ac52

Please sign in to comment.