-
Notifications
You must be signed in to change notification settings - Fork 125
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
WIP: [Tapioca Addon] Support gem RBI generation #2063
base: tapioca-addon-feature-branch
Are you sure you want to change the base?
Conversation
By leveraging file checksums using `Zlib.crc32`, we can avoid triggering the DSL generation process for files that have not changed.
Co-authored-by: Andy Waite <[email protected]>
Independent of the code changes themselves, I still have doubts about the UX here. Take this scenario for example:
I believe we need to try to come up with a strategy to be able to tell if the current developer made the update to the lockfile vs it being updated by something else (like git pull/switching branches/rebasing). Otherwise, it will result in a pretty annoying experience to constantly get unrelated changes. Maybe, we could remember the launch timestamp of the add-on. When the lockfile is updated, it will trigger a restart and then the add-on can check if the update to the lockfile happened after the previous launch (which should indicate that the current developer made the update). I don't know, there might be better options on how to achieve this. |
@vinistock, thanks for pointing this out. I agree that the current behaviour could quickly become frustrating, especially when rebasing and getting unrelated gem RBIs. I appreciate your suggestion about using the launch timestamp of the add-on to distinguish between lockfile updates made by the developer and those from other actions. I'll experiment with that idea and explore other strategies. Your feedback has given me a lot to think about. Thanks again for your input! |
Do not reload rake tasks when under add-on environment
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 creating a snapshot of Gemfile.lock, we can persist data across server restarts. Upon restart, we parse both the snapshot and current Gemfile.lock using Bundler::LockfileParser. If differences are found, we extract the relevant gem names and specifications, allowing us to trigger the gem RBI generation.
1952bd0
to
32b6afc
Compare
Skip gem RBI generation by comparing the last modified times of the Gemfile and Gemfile.lock. If both files are modified within a fraction of a second of each other, it is likely due to a git operation, and gem RBI generation can be skipped.
898a5aa
to
52045e7
Compare
If the Ruby LSP was stopped shortly after the last git checkout/pull operation, we don't need to regenerate RBIs since any Gemfile.lock changes were likely from version control, not from bundle install
52045e7
to
8cfe769
Compare
09b2e33
to
ff9ccec
Compare
return unless File.exist?(".git") && File.exist?(".ruby-lsp/shutdown-timestamp") | ||
|
||
git_timestamp = fetch_last_git_operation_time | ||
return unless git_timestamp |
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 think this isn't right.
Let's say you're working on a new repo, where the reflog doesn't contain any checkout
or pull
entries. fetch_last_git_operation_time
will return nil, and so we will never reach process_gemfile_changes
.
@@ -1760,7 +1760,7 @@ def self.gather_constants | |||
|
|||
it "generates RBIs for lower versions of activerecord-typedstore" do | |||
@project.require_real_gem("activerecord-typedstore", "1.4.0") | |||
@project.require_real_gem("sqlite3", "1.7.3") | |||
@project.require_real_gem("sqlite3", "~> 2.1") |
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 like something was incorrectly rebased?)
|
||
sig { returns(T.nilable(Time)) } | ||
def fetch_last_git_operation_time | ||
git_reflog_output = %x(git reflog --date=iso | grep -E "checkout|pull" | head -n 1).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.
git_reflog_output = %x(git reflog --date=iso | grep -E "checkout|pull" | head -n 1).strip | |
git_reflog_output = %x(git reflog --date=iso --grep-reflog='^\(checkout\|pull\)' -1).strip |
This approach has a couple of advantages:
- It helps prevent mismatches due to a commit messages co-incidentally containing the word 'checkout' or 'pull'. (The
^
means match only at the start of the line). - It avoids the need for external programs which may not be available on Windows.
|
||
sig { void } | ||
def handle_gemfile_changes | ||
return unless File.exist?(".git") && File.exist?(".ruby-lsp/shutdown-timestamp") |
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.
Let's extract this path to a constant since we refer to it multiple times.
@@ -19,6 +19,9 @@ module Tapioca | |||
class Addon < ::RubyLsp::Addon | |||
extend T::Sig | |||
|
|||
GEMFILE_LOCK_SNAPSHOT = "tmp/tapioca/.gemfile_lock_snapshot" | |||
GIT_OPERATION_THRESHOLD = 15.0 # seconds |
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.
GIT_OPERATION_THRESHOLD = 15.0 # seconds | |
GIT_OPERATION_THRESHOLD = 15 # seconds |
return if git_reflog_output.empty? | ||
|
||
timestamp_string = T.must(git_reflog_output.match(/\{(.*)\}/))[1] | ||
Time.iso8601(T.must(timestamp_string).sub(" ", "T").delete(" ")) |
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.
We could avoid the need for this parsing by setting the output so it contains only the timestamp:
git reflog ..... --format=tformat:'%cI'
(cI
means strict ISO, so we don't need to add the 'T' ourselves).
cc19ba0
to
3ce0a60
Compare
WIP
This Draft PR is still a work in progress.
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
To address this, a snapshot file of
Gemfile.lock
is created to persist data across server restarts. Upon restart, both the snapshot and the currentGemfile.lock
are parsed usingBundler::LockfileParser
. If differences are detected, the relevant gem names and specifications are extracted, allowing us to then trigger the gem RBI generation.This implementation handles gem additions, spec changes, and gem removals.
Tests