-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Tapioca Addon] Support gem RBI generation #2081
base: main
Are you sure you want to change the base?
Conversation
lib/ruby_lsp/tapioca/addon.rb
Outdated
current_lockfile = File.read("Gemfile.lock") | ||
snapshot_lockfile = File.read(GEMFILE_LOCK_SNAPSHOT) if File.exist?(GEMFILE_LOCK_SNAPSHOT) | ||
|
||
unless snapshot_lockfile |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we compared the snapshot approach to git diff
? I can think of one scenario where you update the lockfile before snapshot is created so upon the consequent restart there are changes in git status
however, snapshot doesn't exist yet so we don't generate. It's not a common scenario so not important. But feels like running git diff
instead is a viable solution. Curious about the reasoning here. Does it have downsides for changing branches?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for mentioning git diff
again. I looked into it with more attention and I do agree it is a much nicer solution.
I have pushed a refactor to take advantage of git diff
. Let me know what you think.
lib/ruby_lsp/tapioca/addon.rb
Outdated
def handle_gemfile_changes | ||
return unless File.exist?(".git") | ||
|
||
gemfile_status = %x(git status --porcelain Gemfile.lock).strip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With shelling out and the file parsing I'm curious how long this adds to the boot time. Could you add some simple timings to the PR description, before and after this change for core?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now an older implementation, but I do agree some timings would be nice to have. I'll take a look on Monday.
cc19ba0
to
3ce0a60
Compare
351a2fb
to
f387c8d
Compare
attr_reader :removed_gems | ||
|
||
def initialize(diff_content) | ||
@diff_content = diff_content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@diff_content = diff_content | |
@diff_content = diff_content.lines |
If the only thing we do is call .lines
on it we could do it here and do it only once instead of 2 calls below.
# typed: true | ||
# frozen_string_literal: true | ||
|
||
module RubyLsp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work on this!
7f173a8
to
5f3dc43
Compare
Some feedback from testing. It's possible some won't need addressed:
|
(Continued)
|
1098d0d
to
21df7e6
Compare
21df7e6
to
ebfb33d
Compare
ebfb33d
to
e19934f
Compare
e2697ce
to
b28ebb0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good but I think we need a few more tests for the main flows in addon.rb
.
lib/ruby_lsp/tapioca/addon.rb
Outdated
|
||
sig { returns(T::Boolean) } | ||
def lockfile_changed? | ||
!fetch_lockfile_diff.empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!fetch_lockfile_diff.empty? | |
fetch_lockfile_diff | |
!@lockfile_diff.empty? |
(to avoid violating https://en.wikipedia.org/wiki/Command%E2%80%93query_separation )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're doing this then it might be better to assign the return value of fetch_lockfile_diff
to the instance variable in lockfile_changed?
instead. No strong opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was aiming to avoid any side-effects in the predicate method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Main side effect is in fetch_lockfile_diff
with the ivar which is triggered by the predicate method. I don't know the best solution but trying to move it above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KaanOzkan I think it's in a pretty good shape and I've marked as approved for if you want to release. It does need some more tests, but it would valuable to ship it so that we can start getting feedback and iterating. We can expand on the tests while that is happening.
To support gem RBI generation, we needed a way to detect changes in Gemfile.lock. Currently, changes to this file cause the Ruby LSP to restart, resulting in loss of access to any previous state information. By running git diff on Gemfile.lock, we can detect changes to the file, and trigger the gem RBI generation process. Then we can parse the diff output to determine which gems have been removed, added or modified, and either remove the corresponding RBI files or trigger the RBI generation process for the gems.
b28ebb0
to
3ab954c
Compare
|
||
if added_or_modified_gems.any? | ||
# Resetting BUNDLE_GEMFILE to root folder to use the project's Gemfile instead of Ruby LSP's composed Gemfile | ||
stdout, stderr, status = T.unsafe(Open3).capture3( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tapioca doesn't log clearly before starting generating RBIs. I think it'll be good to mention here, something like:
Identified lockfile changes, attempting to generate gem RBIs
@@ -13,6 +13,7 @@ | |||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I noticed is we keep generating RBIs for updated gems. An ideal solution would be like GemSync
where we determine that gem RBI is up to date and not generate. It may not be needed yet assuming restarts are rare and folks will stage their lockfile changes. Any thoughts?
Motivation
To support gem RBI generation, we needed a way to detect changes in
Gemfile.lock
. Currently, changes to this file cause the Ruby LSP to restart, resulting in loss of access to any previous state information. This limitation prevents monitoring and processing of gem changes.Implementation
We've implemented a solution that uses
git diff
to trackGemfile.lock
changes:Change Detection
git_repo?
check to ensure we're in a git repositorygit diff HEAD Gemfile.lock
to detect uncommitted changesGem Change Analysis
LockfileDiffParser
class to analyze diff outputGem RBI Processing
Key Components
Addon
: MonitorsGemfile.lock
changes and handles RBI file operations when changes are detectedLockfileDiffParser
: Analyzes git diff output to identify added, modified, and removed gemsThis approach specifically targets manual bundle updates while avoiding unnecessary RBI regeneration during normal git operations like branch switches.
Tests