Skip to content

Commit 2bd3d70

Browse files
pockeclaude
andcommitted
Replace the backtraces on the missing-file paths of rbs collection
Running `rbs collection install` before the files it needs exist ended in an uncaught exception with a full backtrace, because `exe/rbs` has no rescue. Someone -- or a coding agent -- who sees that concludes that `rbs collection` is broken and goes back to listing gems with `library`. `rbs collection init` now points at `rbs collection install`, so these paths are easier to reach than they were. Four of them: - No `rbs_collection.yaml`: `Errno::ENOENT` from `Config.from_path`, for both `install` and `update`. Now a message naming `rbs collection init`. - No `rbs_collection.lock.yaml` under `--frozen`: `Errno::ENOENT` from `Installer#initialize`. The message says to run without `--frozen`, or, when the config is missing too, to run `rbs collection init` first, so that one message is enough to get unstuck. - No `Gemfile`: `Bundler::GemfileNotFound` from `Bundler.definition`. - A `Gemfile` but no `Gemfile.lock`: `Bundler.definition` returns, and `LockfileGenerator#initialize` dies with `undefined method 'specs' for nil` on `definition.locked_gems.specs`. Now a message naming `bundle install`. Raising `RBS::Collection::Config::CollectionNotAvailable`, as the issue suggested, would keep the backtrace for exactly the same reason, so these write to `stderr` and return 1 like the rest of `run_collection`. `already exists` and `should exist to clean` used `Kernel#puts`, which writes to the real `$stdout` and ignores the injected IO. Both are failures that return 1, so they go to `stderr` now; this changes which stream they appear on. Tests cover each of the four paths, `--frozen` with neither file, and `--frozen` with a lock file and no config, which keeps working. Ref: #3093 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BXtMkmp27LieHgSjNyEWZ8
1 parent b572aba commit 2bd3d70

4 files changed

Lines changed: 151 additions & 7 deletions

File tree

lib/rbs/cli.rb

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,17 +1089,30 @@ def run_collection(args, options)
10891089

10901090
case args[0]
10911091
when 'install', 'instal', 'insta', 'inst', 'ins', 'in', 'i'
1092-
unless params[:frozen]
1093-
Collection::Config.generate_lockfile(config_path: config_path, definition: Bundler.definition)
1092+
if params[:frozen]
1093+
unless lock_path.exist?
1094+
if config_path.exist?
1095+
stderr.puts "#{lock_path} not found. Run `rbs collection install` without `--frozen` to generate it."
1096+
else
1097+
stderr.puts "#{lock_path} not found. Run `rbs collection init` and `rbs collection install` to generate it."
1098+
end
1099+
return 1
1100+
end
1101+
else
1102+
return 1 unless collection_config_available?(config_path)
1103+
definition = bundler_definition or return 1
1104+
Collection::Config.generate_lockfile(config_path: config_path, definition: definition)
10941105
end
10951106
Collection::Installer.new(lockfile_path: lock_path, stdout: stdout).install_from_lockfile
10961107
when 'update', 'updat', 'upda', 'upd', 'up', 'u'
1108+
return 1 unless collection_config_available?(config_path)
1109+
definition = bundler_definition or return 1
10971110
# TODO: Be aware of argv to update only specified gem
1098-
Collection::Config.generate_lockfile(config_path: config_path, definition: Bundler.definition, with_lockfile: false)
1111+
Collection::Config.generate_lockfile(config_path: config_path, definition: definition, with_lockfile: false)
10991112
Collection::Installer.new(lockfile_path: lock_path, stdout: stdout).install_from_lockfile
11001113
when 'init'
11011114
if config_path.exist?
1102-
puts "#{config_path} already exists"
1115+
stderr.puts "#{config_path} already exists"
11031116
return 1
11041117
end
11051118

@@ -1142,20 +1155,38 @@ def run_collection(args, options)
11421155
MESSAGE
11431156
when 'clean'
11441157
unless lock_path.exist?
1145-
puts "#{lock_path} should exist to clean"
1158+
stderr.puts "#{lock_path} should exist to clean"
11461159
return 1
11471160
end
11481161
Collection::Cleaner.new(lockfile_path: lock_path)
11491162
when 'help', 'hel', 'he', 'h'
1150-
puts opts.help
1163+
stdout.puts opts.help
11511164
else
1152-
puts opts.help
1165+
stdout.puts opts.help
11531166
return 1
11541167
end
11551168

11561169
0
11571170
end
11581171

1172+
def collection_config_available?(config_path)
1173+
return true if config_path.exist?
1174+
1175+
stderr.puts "#{config_path} not found. Run `rbs collection init` to generate it."
1176+
false
1177+
end
1178+
1179+
def bundler_definition
1180+
definition = Bundler.definition
1181+
return definition if definition.lockfile.file?
1182+
1183+
stderr.puts "#{definition.lockfile} not found. Run `bundle install` to generate it."
1184+
nil
1185+
rescue Bundler::GemfileNotFound
1186+
stderr.puts "Gemfile not found. `rbs collection` reads the gems to install from Gemfile.lock."
1187+
nil
1188+
end
1189+
11591190
def collection_options(args)
11601191
OptionParser.new do |opts|
11611192
opts.banner = <<~HELP

sig/cli.rbs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ module RBS
7676

7777
def run_collection: (Array[String], LibraryOptions) -> Integer
7878

79+
# Returns `true` if the collection config exists, otherwise prints a message to `stderr` and returns `false`
80+
def collection_config_available?: (Pathname) -> bool
81+
82+
# Returns `nil` if `Gemfile` or `Gemfile.lock` is not found, after printing a message to `stderr`
83+
def bundler_definition: () -> Bundler::Definition?
84+
7985
def run_annotate: (Array[String], top) -> Integer
8086

8187
def run_subtract: (Array[String], top) -> Integer

sig/shims/bundler.rbs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
module Bundler
2+
class GemfileNotFound < StandardError
3+
end
4+
25
class LockfileParser
36
def initialize: (String) -> void
47

test/rbs/cli_test.rb

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,110 @@ def test_collection_init
11061106
end
11071107
end
11081108

1109+
def test_collection_install_without_config
1110+
Dir.mktmpdir do |dir|
1111+
Dir.chdir(dir) do
1112+
with_cli do |cli|
1113+
refute_cli_success do
1114+
cli.run(%w(collection install))
1115+
end
1116+
1117+
assert_match(/rbs_collection\.yaml not found/, stderr.string)
1118+
assert_match(/rbs collection init/, stderr.string)
1119+
end
1120+
end
1121+
end
1122+
end
1123+
1124+
def test_collection_update_without_config
1125+
Dir.mktmpdir do |dir|
1126+
Dir.chdir(dir) do
1127+
with_cli do |cli|
1128+
refute_cli_success do
1129+
cli.run(%w(collection update))
1130+
end
1131+
1132+
assert_match(/rbs_collection\.yaml not found/, stderr.string)
1133+
assert_match(/rbs collection init/, stderr.string)
1134+
end
1135+
end
1136+
end
1137+
end
1138+
1139+
def test_collection_install_without_gemfile
1140+
Dir.mktmpdir do |dir|
1141+
Dir.chdir(dir) do
1142+
Pathname(dir).join(RBS::Collection::Config::PATH).write(<<~YAML)
1143+
sources: []
1144+
path: .gem_rbs_collection
1145+
YAML
1146+
1147+
_stdout, stderr = run_rbs_collection("install", bundler: false) do |status|
1148+
refute_predicate status, :success?
1149+
end
1150+
1151+
assert_match(/Gemfile not found/, stderr)
1152+
end
1153+
end
1154+
end
1155+
1156+
def test_collection_install_without_gemfile_lock
1157+
Dir.mktmpdir do |dir|
1158+
Dir.chdir(dir) do
1159+
dir = Pathname(dir)
1160+
dir.join(RBS::Collection::Config::PATH).write(<<~YAML)
1161+
sources: []
1162+
path: .gem_rbs_collection
1163+
YAML
1164+
dir.join("Gemfile").write(<<~RUBY)
1165+
source "https://rubygems.org"
1166+
RUBY
1167+
1168+
_stdout, stderr = run_rbs_collection("install", bundler: false) do |status|
1169+
refute_predicate status, :success?
1170+
end
1171+
1172+
assert_match(/Gemfile\.lock not found/, stderr)
1173+
assert_match(/bundle install/, stderr)
1174+
end
1175+
end
1176+
end
1177+
1178+
def test_collection_install_frozen_without_lockfile
1179+
Dir.mktmpdir do |dir|
1180+
Dir.chdir(dir) do
1181+
Pathname(dir).join(RBS::Collection::Config::PATH).write(<<~YAML)
1182+
sources: []
1183+
path: .gem_rbs_collection
1184+
YAML
1185+
1186+
with_cli do |cli|
1187+
refute_cli_success do
1188+
cli.run(%w(collection install --frozen))
1189+
end
1190+
1191+
assert_match(/rbs_collection\.lock\.yaml not found/, stderr.string)
1192+
assert_match(/without `--frozen`/, stderr.string)
1193+
end
1194+
end
1195+
end
1196+
end
1197+
1198+
def test_collection_install_frozen_without_config_and_lockfile
1199+
Dir.mktmpdir do |dir|
1200+
Dir.chdir(dir) do
1201+
with_cli do |cli|
1202+
refute_cli_success do
1203+
cli.run(%w(collection install --frozen))
1204+
end
1205+
1206+
assert_match(/rbs_collection\.lock\.yaml not found/, stderr.string)
1207+
assert_match(/rbs collection init/, stderr.string)
1208+
end
1209+
end
1210+
end
1211+
end
1212+
11091213
def test_collection_install
11101214
omit_on_jruby! "`rbs collection install` runs `bundle install`, which builds native gem extensions that do not compile on JRuby"
11111215

0 commit comments

Comments
 (0)