Skip to content
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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

alexcrocha
Copy link
Contributor

@alexcrocha alexcrocha commented Nov 20, 2024

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 track Gemfile.lock changes:

  1. Change Detection

    • Added git_repo? check to ensure we're in a git repository
    • Use git diff HEAD Gemfile.lock to detect uncommitted changes
    • Parse the diff output to identify gem removals, additions or modifications
  2. Gem Change Analysis

    • Created LockfileDiffParser class to analyze diff output
    • Identifies:
      • Removed gems (completely deleted)
      • Added or modified gems (new or version changes)
  3. Gem RBI Processing

    • Remove RBI files for deleted gems
    • Triggers gem rbis generation command for added or modified gems

Key Components

  • Addon: Monitors Gemfile.lock changes and handles RBI file operations when changes are detected
  • LockfileDiffParser: Analyzes git diff output to identify added, modified, and removed gems

This approach specifically targets manual bundle updates while avoiding unnecessary RBI regeneration during normal git operations like branch switches.

Tests

@alexcrocha alexcrocha added the enhancement New feature or request label Nov 20, 2024
lib/ruby_lsp/tapioca/addon.rb Outdated Show resolved Hide resolved
lib/ruby_lsp/tapioca/server_addon.rb Outdated Show resolved Hide resolved
lib/ruby_lsp/tapioca/addon.rb Outdated Show resolved Hide resolved
current_lockfile = File.read("Gemfile.lock")
snapshot_lockfile = File.read(GEMFILE_LOCK_SNAPSHOT) if File.exist?(GEMFILE_LOCK_SNAPSHOT)

unless snapshot_lockfile
Copy link
Contributor

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?

Copy link
Contributor Author

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.

def handle_gemfile_changes
return unless File.exist?(".git")

gemfile_status = %x(git status --porcelain Gemfile.lock).strip
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@vinistock vinistock force-pushed the tapioca-addon-feature-branch branch from cc19ba0 to 3ce0a60 Compare November 22, 2024 14:23
@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch from 351a2fb to f387c8d Compare November 22, 2024 19:10
attr_reader :removed_gems

def initialize(diff_content)
@diff_content = diff_content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on this!

@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch from 7f173a8 to 5f3dc43 Compare November 26, 2024 02:27
@alexcrocha alexcrocha changed the title WIP: [Tapioca Addon] Support gem RBI generation on dirty lockfile [Tapioca Addon] Support gem RBI generation Nov 26, 2024
@andyw8
Copy link
Contributor

andyw8 commented Nov 26, 2024

Some feedback from testing. It's possible some won't need addressed:

  • I noticed the output from Tapioca is double-spaced (vertically) in the logs. It's likely that Ruby LSP itself is adding extra newlines unnecessarily.
  • Once the add-on succesfully creates/update some RBIs, it will so again on every restart and report them as identical, until committed/staged. We could be smart and check the new/changed RBIs files on disk but this may add unwanted complexity.

@andyw8
Copy link
Contributor

andyw8 commented Nov 26, 2024

(Continued)

  • After running, we show a message in the logs "Please review changes and commit them." but users would not normally be reading that. We may want to consider showing an alert instead.
  • It would be nice if we could eliminate the FILE_HEADER_OPTION_DESC warnings in Tapioca to prevent them showing on startup.
  • I added a gem to the Gemfile and the add-on generated the RBI. Without committing, then removed the new entry from the Gemfile. The 'orphaned' RBI remained on disk.

@alexcrocha alexcrocha marked this pull request as ready for review November 27, 2024 20:15
@alexcrocha alexcrocha requested a review from a team as a code owner November 27, 2024 20:15
@alexcrocha alexcrocha requested review from KaanOzkan, andyw8 and vinistock and removed request for a team November 27, 2024 20:15
@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch from 1098d0d to 21df7e6 Compare November 27, 2024 23:09
@alexcrocha alexcrocha changed the base branch from tapioca-addon-feature-branch to main November 27, 2024 23:09
@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch from 21df7e6 to ebfb33d Compare November 28, 2024 01:34
@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch from ebfb33d to e19934f Compare November 30, 2024 00:40
@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch 2 times, most recently from e2697ce to b28ebb0 Compare December 18, 2024 19:21
Copy link
Contributor

@andyw8 andyw8 left a 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 Show resolved Hide resolved

sig { returns(T::Boolean) }
def lockfile_changed?
!fetch_lockfile_diff.empty?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
!fetch_lockfile_diff.empty?
fetch_lockfile_diff
!@lockfile_diff.empty?

(to avoid violating https://en.wikipedia.org/wiki/Command%E2%80%93query_separation )

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

@KaanOzkan KaanOzkan Dec 19, 2024

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.

lib/ruby_lsp/tapioca/addon.rb Outdated Show resolved Hide resolved
Copy link
Contributor

@andyw8 andyw8 left a 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.

alexcrocha and others added 5 commits December 18, 2024 14:40
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.
@alexcrocha alexcrocha force-pushed the ar/gem-regeneration-git-status branch from b28ebb0 to 3ab954c Compare December 18, 2024 22:57

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(
Copy link
Contributor

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
Copy link
Contributor

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants