Skip to content

Cache only bundle and stdlib gems by default - #1342

Closed
apiology wants to merge 7 commits into
castwide:masterfrom
apiology:cache-gems-from-bundle
Closed

apiology wants to merge 7 commits into
castwide:masterfrom
apiology:cache-gems-from-bundle

Conversation

@apiology

@apiology apiology commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem: solargraph gems with no arguments builds documentation for every gem version RubyGems can see, rather than the ones the workspace can actually load. Outside bundle exec on one developer box that is 575 installed specs across 370 unique names, 205 of them stale versions nothing in the project can require.

$ solargraph gems
...
Documentation cached for all 575 gems.

Under bundle exec the figure is smaller — 141 here — but still includes gems the bundle does not resolve, and each one pays for a YARD build and an RBS collection lookup.

Solution: Select from the workspace instead — the gemspecs its bundle resolves, plus the standard libraries of the running Ruby that resolve to a gemspec here — which is 121 in this repo, at the cost that a gem installed outside the bundle now has to be named explicitly.

🤖 Generated with Claude Code

apiology and others added 4 commits September 5, 2026 23:35
`solargraph gems` with no arguments walked `Gem::Specification.to_a`,
which is every gem version installed on the machine. On one developer
box that is 575 specs across 370 unique names, so 205 of them are
stale versions the workspace can never load. Each one still paid for a
YARD build and an RBS collection lookup.

Select from the workspace instead: the gemspecs its bundle resolves,
plus the standard libraries of the running Ruby that resolve to a
gemspec here. In this repo that is 121 gemspecs rather than the 133
`Gem::Specification` reports under `bundle exec`, and far fewer when
run outside a restricted bundle.

`Workspace#gemspecs_to_cache` performs the selection and
`PinCache.possible_stdlibs` lists the standard library candidates, so
both are testable without building any pins.

This narrows coverage on purpose. A gem installed on the machine but
absent from the workspace's bundle is no longer cached by a bare
`solargraph gems`; name it explicitly to cache it. In a directory with
no resolvable bundle the selection falls back to standard libraries
alone rather than to everything installed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRZoPdBqpztWbGk7KmkgBX
`solargraph gems core` raises NoMethodError: Shell#gems calls
PinCache.cache_core, which is defined on RbsMap::CoreMap and nowhere on
PinCache. The comment above the call claiming both it and PinCache.core?
are dynamically defined is wrong; nothing defines either.

Assert the behaviour that should hold, marked pending so it fails
silently now and breaks the build the day the defect is fixed, which is
what forces the marker out. The assertion targets CoreMap.cache_core
having been called, so it holds whether the fix delegates from PinCache
or calls CoreMap directly.
cache_core is an instance method on RbsMap::CoreMap, so stubbing it on
the class meant the expectation could never be satisfied and the pending
marker would never flip. Stub CoreMap.new instead.

Verified both directions: the example still fails at shell.rb:194 today
and is reported pending, and applying the fix locally turns it FIXED and
fails the suite, which is what forces the marker out.
Scoping to the bundle silently removed the ability to work outside one.
In a directory with no Gemfile, all_gemspecs_from_external_bundle rescues
BundleNotFoundError to an empty list and nothing put the installed set
back, so gemspecs_to_cache returned 1 gemspec where master cached 1021,
and references to any installed gem stopped resolving.

Fall back to every installed gemspec when the bundle yields nothing.
Keying on the empty result rather than on the presence of a Gemfile
covers both the no-Gemfile case and a bundle that resolves to nothing,
and needs no new predicate.

The spec added with the original change asserted the fallback never
happened, which is why nothing caught this; it now describes the bundled
case, alongside a new example for the unbundled one.
Workspace#gemfile? decides whether gem caching is scoped to the
bundle, and had no direct coverage. Two examples exercise both
outcomes: a workspace directory holding a Gemfile, and one without.
apiology and others added 2 commits September 24, 2026 22:26
Nine of the eleven specs on this branch reached PinCache.possible_stdlibs,
Workspace#gemspecs_to_cache or a stubbed Shell#do_cache.
castwide#1369 deletes all three, and it already scopes the
no-argument path to the bundle through Repo, so the behaviour outlives
the specs describing it.

`solargraph gems` reads the current directory in either design. A fixture
holding a one-gem bundle and a file requiring it is enough to say what
the command is for: it documents fewer gems than RubyGems can see, and it
documents the gem the bundle resolves. The second is checked by asking an
ApiMap for the gem afterwards without letting it cache anything itself.

The count is read as "cached for all N gems" too, which is how the
unscoped path words it. An earlier draft matched only the scoped wording,
so the unscoped run parsed as zero and the example passed against the
very behaviour it exists to reject.

Verified in both directions: both pass here, and with pin_cache.rb,
shell.rb and workspace.rb restored from master the first fails, 133
against an expected fewer than 133.

Dropped with the files: two examples listing stdlib names from the Ruby
installation directory, and one already pending on Shell#gems calling a
PinCache.cache_core that is defined nowhere. The Workspace#gemfile?
examples are untouched.
#103 names five user-visible failures and assigns two
of them here: solargraph cache reports success while the editor still
treats the gem as uncached, and solargraph gems can never finish the job
on its own. Both are one mechanism. The CLI writes a gem YARD half and
its RBS half, never the merge of the two, so the first editor session to
open the workspace writes that.

Measured on this branch: after uncaching backport, solargraph cache
backport leaves two entries and an ApiMap load in a fresh process writes
a third. On #103 head 6755c8e the same sequence
leaves three entries and the load writes none, so both examples flip
from pending to passing when that lands.

The load runs in a subprocess because gem pins are memoized for the life
of a process. Run in this one, the second example answered from memory,
wrote nothing, and passed against the behaviour it exists to reject.

The cache is globbed for the gem name under this Solargraph version,
which both the current layout and castwide#1369 spell into
the path, so the examples outlive that merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E8ckZEYYMDXe9eDNnyxaim
@castwide

Copy link
Copy Markdown
Owner

Superseded by #1369.

@castwide castwide closed this Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants