From be879920ed9c7a5ac77e1a0cd2259b0ad4fac7a4 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Thu, 30 Jul 2026 14:51:10 -0400 Subject: [PATCH 1/2] Reset Bootsnap RBS cache on lockfile changes Stores the current `Gemfile.lock` digest in Tapioca's dedicated Bootsnap cache and resets the Bootsnap payload when the digest changes. Skips stale read-only caches so consumers do not use rewritten iseqs built for a different lockfile. --- README.md | 11 +++++- lib/tapioca/rbs/bootsnap_cache.rb | 63 +++++++++++++++++++++++++++++++ lib/tapioca/rbs/rewriter.rb | 62 +++++++++++++++++++----------- spec/tapioca/cli/dsl_spec.rb | 57 ++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 lib/tapioca/rbs/bootsnap_cache.rb diff --git a/README.md b/README.md index dd107435c..b9d1d0703 100644 --- a/README.md +++ b/README.md @@ -857,7 +857,16 @@ The rewriting is automatic on every `tapioca` invocation: [`require-hooks`](http $ TAPIOCA_RBS_CACHE=1 bin/tapioca dsl ``` -Tapioca configures Bootsnap's iseq cache against a dedicated directory (`tmp/cache/bootsnap-tapioca-rbs` by default; override with `TAPIOCA_BOOTSNAP_CACHE_DIR`). The first run is slower because every file is rewritten and the result is baked into the iseq cache; subsequent runs against the same directory skip the rewrite entirely. +Tapioca configures Bootsnap's iseq cache against a dedicated directory (`tmp/cache/bootsnap-tapioca-rbs` by +default; override with `TAPIOCA_BOOTSNAP_CACHE_DIR`). + +Tapioca writes the current `Gemfile.lock` digest to `.gemfile-lock-digest` inside that cache directory. When the +lockfile changes, Tapioca sees the digest mismatch and resets Bootsnap's cache payload before configuring Bootsnap. +This lets gem bumps that affect rewriting, such as `tapioca`, start from a fresh cache without accumulating old cache +directories. + +The first run is slower because every file is rewritten and the result is baked into the iseq cache; subsequent runs +against the same lockfile skip the rewrite entirely. `Bootsnap.setup` mutates a process-wide singleton, and a second call would overwrite Tapioca's dedicated cache directory and start writing rewritten iseqs into the host's normal cache. Tapioca enforces this under `TAPIOCA_RBS_CACHE=1`: after its own setup runs, any subsequent `Bootsnap.setup` raises a clear error pointing at the fix. Gate your host's `Bootsnap.setup` on the same env var. Rails apps do this in `config/boot.rb`: diff --git a/lib/tapioca/rbs/bootsnap_cache.rb b/lib/tapioca/rbs/bootsnap_cache.rb new file mode 100644 index 000000000..7a423034d --- /dev/null +++ b/lib/tapioca/rbs/bootsnap_cache.rb @@ -0,0 +1,63 @@ +# typed: strict +# frozen_string_literal: true + +require "bundler" +require "digest" +require "fileutils" + +module Tapioca + module RBS + # Prepares the Bootsnap iseq cache used for RBS rewrite output. + # + # RBS rewrite output can change when the lockfile changes, even if the + # source files are unchanged. + # To account for this, we store the current Gemfile.lock SHA256 in a + # `.gemfile-lock-digest` file. + # On writable runs, a digest mismatch deletes Bootsnap's cache payload and + # records the new digest, so this run rebuilds the cache from scratch. On + # read-only runs, a digest mismatch means the cache is stale and must not be + # used. + module BootsnapCache + PrepareResult = Struct.new(:setup_bootsnap, keyword_init: true) + + DIGEST_FILE = ".gemfile-lock-digest" #: String + + class << self + #: (String, readonly: bool) -> PrepareResult + def prepare_for_setup(cache_dir, readonly:) + digest = gemfile_lock_digest + + if readonly + return PrepareResult.new(setup_bootsnap: digest_matches?(cache_dir, digest)) + end + + unless digest_matches?(cache_dir, digest) + FileUtils.rm_rf(File.join(cache_dir, "bootsnap")) + FileUtils.mkdir_p(cache_dir) + File.write(digest_path(cache_dir), digest) + end + + PrepareResult.new(setup_bootsnap: true) + end + + private + + #: -> String + def gemfile_lock_digest + Digest::SHA256.file(Bundler.default_lockfile).hexdigest + end + + #: (String, String) -> bool + def digest_matches?(cache_dir, digest) + path = digest_path(cache_dir) + File.file?(path) && File.read(path).chomp == digest + end + + #: (String) -> String + def digest_path(cache_dir) + File.join(cache_dir, DIGEST_FILE) + end + end + end + end +end diff --git a/lib/tapioca/rbs/rewriter.rb b/lib/tapioca/rbs/rewriter.rb index ac5bb1974..817917071 100644 --- a/lib/tapioca/rbs/rewriter.rb +++ b/lib/tapioca/rbs/rewriter.rb @@ -1,6 +1,8 @@ # typed: strict # frozen_string_literal: true +require "tapioca/rbs/bootsnap_cache" + # This code rewrites RBS comments back into Sorbet's signatures as the files are being loaded. # This will allow `sorbet-runtime` to wrap the methods as if they were originally written with the `sig{}` blocks. # This will in turn allow Tapioca to use this signatures to generate typed RBI files. @@ -29,32 +31,50 @@ def setup(**_kwargs) MSG end end + + module BootsnapSetup + class << self + extend T::Sig + + sig { void } + def setup + require "bootsnap" + + # Respect BOOTSNAP_READONLY for consumers reading a pre-populated cache + # (e.g. a CI prime step). + readonly = !["0", "false", false].include?(ENV.fetch("BOOTSNAP_READONLY") { false }) + cache_dir = ENV.fetch("TAPIOCA_BOOTSNAP_CACHE_DIR", File.join(Dir.pwd, "tmp/cache/bootsnap-tapioca-rbs")) + # A read-only cache with a mismatched lockfile digest may contain stale rewritten iseqs, + # and this process cannot reset it. + return unless Tapioca::RBS::BootsnapCache.prepare_for_setup( + cache_dir, + readonly: readonly, + ).setup_bootsnap + + Bootsnap.setup( + cache_dir: cache_dir, + development_mode: true, + load_path_cache: true, + compile_cache_iseq: true, + compile_cache_yaml: true, + readonly: readonly, + revalidation: true, + ) + Bootsnap.log_stats! + ensure + Bootsnap.singleton_class.prepend(Tapioca::RBS::BootsnapGuard) if defined?(Bootsnap) + end + end + end end end -# When TAPIOCA_RBS_CACHE=1, set up bootsnap with a dedicated cache directory -# and load require-hooks so the RBS-rewritten iseqs get cached. Subsequent -# runs read the rewritten iseq directly and skip the rewrite. -# -# After our setup, BootsnapGuard is prepended so the host application can't -# replace our cache directory. +# When TAPIOCA_RBS_CACHE=1, use a dedicated Bootsnap cache directory for +# RBS-rewritten iseqs. Stale read-only caches are skipped because this process +# cannot reset them. if ENV["TAPIOCA_RBS_CACHE"] == "1" begin - require "bootsnap" - # Respect BOOTSNAP_READONLY for consumers reading a pre-populated cache - # (e.g. a CI prime step). - readonly = !["0", "false", false].include?(ENV.fetch("BOOTSNAP_READONLY") { false }) - Bootsnap.setup( - cache_dir: ENV.fetch("TAPIOCA_BOOTSNAP_CACHE_DIR", File.join(Dir.pwd, "tmp/cache/bootsnap-tapioca-rbs")), - development_mode: true, - load_path_cache: true, - compile_cache_iseq: true, - compile_cache_yaml: true, - readonly: readonly, - revalidation: true, - ) - Bootsnap.log_stats! - Bootsnap.singleton_class.prepend(Tapioca::RBS::BootsnapGuard) + Tapioca::RBS::BootsnapSetup.setup rescue LoadError # Bootsnap is not in the bundle, skip iseq caching. end diff --git a/spec/tapioca/cli/dsl_spec.rb b/spec/tapioca/cli/dsl_spec.rb index b8ae36369..358a94558 100644 --- a/spec/tapioca/cli/dsl_spec.rb +++ b/spec/tapioca/cli/dsl_spec.rb @@ -680,6 +680,63 @@ class Post assert_success_status(result) end + it "resets the bootsnap cache when Gemfile.lock changes" do + @project.write!("lib/post.rb", <<~RB) + require "smart_properties" + + class Post + include SmartProperties + property :title, accepts: String + end + RB + + env = { + "TAPIOCA_RBS_CACHE" => "1", + "TAPIOCA_BOOTSNAP_CACHE_DIR" => "tmp/cache/test-bootsnap-tapioca-rbs", + } + + result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: env) + + assert_success_status(result) + @project.write!("tmp/cache/test-bootsnap-tapioca-rbs/bootsnap/stale-cache-entry", "stale") + + @project.write!("Gemfile.lock", "#{@gemfile_lock}\n") + result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: env) + + assert_success_status(result) + refute_project_file_exist("tmp/cache/test-bootsnap-tapioca-rbs/bootsnap/stale-cache-entry") + assert_project_file_exist("tmp/cache/test-bootsnap-tapioca-rbs/.gemfile-lock-digest") + end + + it "skips a stale read-only bootsnap cache when Gemfile.lock changes" do + @project.write!("lib/post.rb", <<~RB) + require "smart_properties" + + class Post + include SmartProperties + property :title, accepts: String + end + RB + + env = { + "TAPIOCA_RBS_CACHE" => "1", + "TAPIOCA_BOOTSNAP_CACHE_DIR" => "tmp/cache/test-readonly-bootsnap-tapioca-rbs", + } + + result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: env) + + assert_success_status(result) + original_digest = @project.read("tmp/cache/test-readonly-bootsnap-tapioca-rbs/.gemfile-lock-digest") + @project.write!("Gemfile.lock", "#{@gemfile_lock}\n") + + result = @project.tapioca("dsl Post", env: env.merge("BOOTSNAP_READONLY" => "1")) + + assert_success_status(result) + assert_empty_stderr(result) + assert_project_file_exist("sorbet/rbi/dsl/post.rbi") + assert_project_file_equal("tmp/cache/test-readonly-bootsnap-tapioca-rbs/.gemfile-lock-digest", original_digest) + end + it "warns when --only-bootsnap-rbs-cache is set without TAPIOCA_RBS_CACHE=1" do @project.write!("lib/post.rb", <<~RB) require "smart_properties" From 7d2c01ca7a36b4f019bed771be20cb460e986bb9 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Thu, 30 Jul 2026 14:51:41 -0400 Subject: [PATCH 2/2] Remove Bootsnap cache priming support The `--only-bootsnap-rbs-cache` flag and read-only cache path supported sharing a primed cache between CI jobs. That workflow is no longer used. Keep the cache local and writable. Normal `tapioca dsl` runs populate it, and lockfile changes reset the cached iseq payload. --- README.md | 78 +++++++++-------------- lib/tapioca/cli.rb | 12 +--- lib/tapioca/commands/dsl_generate.rb | 15 ----- lib/tapioca/rbs/bootsnap_cache.rb | 27 +++----- lib/tapioca/rbs/rewriter.rb | 23 ++----- spec/tapioca/cli/dsl_spec.rb | 95 +--------------------------- 6 files changed, 47 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index b9d1d0703..904755733 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ Tapioca makes it easy to work with [Sorbet](https://sorbet.org) in your codebase * [Writing custom DSL extensions](#writing-custom-dsl-extensions) * [Rewriting RBS comments to Sorbet signatures](#rewriting-rbs-comments-to-sorbet-signatures) * [Caching rewrites with Bootsnap](#caching-rewrites-with-bootsnap) - * [Priming the cache from CI](#priming-the-cache-from-ci) * [RBI files for missing constants and methods](#rbi-files-for-missing-constants-and-methods) * [Configuration](#configuration) * [Editor Integration](#editor-integration) @@ -492,37 +491,35 @@ Usage: tapioca dsl [constant...] Options: - --out, -o, [--outdir=directory] # The output directory for generated DSL RBI files - # Default: sorbet/rbi/dsl - [--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file - # Default: true - [--only=compiler [compiler ...]] # Only run supplied DSL compiler(s) - [--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s) - [--verify], [--no-verify], [--skip-verify] # Verifies RBIs are up-to-date - # Default: false - [--only-bootsnap-rbs-cache], [--no-only-bootsnap-rbs-cache], [--skip-only-bootsnap-rbs-cache] # Only boot the application and load DSL extensions/compilers to populate the bootsnap iseq cache, then exit. Skips compiler execution and RBI generation. Mutually exclusive with --verify and --list-compilers. - # Default: false - -q, [--quiet], [--no-quiet], [--skip-quiet] # Suppresses file creation output - # Default: false - -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto) - [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped - # Default: 120 - [--max-diff-lines=N] # Max number of diff lines to include in the `dsl --verify` output - # Default: 250 - -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs - # Default: development - -l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers - # Default: false - [--app-root=APP_ROOT] # The path to the Rails application - # Default: . - [--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application - # Default: true - [--skip-constant=constant [constant ...]] # Do not generate RBI definitions for the given application constant(s) - [--compiler-options=key:value] # Options to pass to the DSL compilers - -c, [--config=] # Path to the Tapioca configuration file - # Default: sorbet/tapioca/config.yml - -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes - # Default: false + --out, -o, [--outdir=directory] # The output directory for generated DSL RBI files + # Default: sorbet/rbi/dsl + [--file-header], [--no-file-header], [--skip-file-header] # Add a "This file is generated" header on top of each generated RBI file + # Default: true + [--only=compiler [compiler ...]] # Only run supplied DSL compiler(s) + [--exclude=compiler [compiler ...]] # Exclude supplied DSL compiler(s) + [--verify], [--no-verify], [--skip-verify] # Verifies RBIs are up-to-date + # Default: false + -q, [--quiet], [--no-quiet], [--skip-quiet] # Suppresses file creation output + # Default: false + -w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto) + [--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped + # Default: 120 + [--max-diff-lines=N] # Max number of diff lines to include in the `dsl --verify` output + # Default: 250 + -e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs + # Default: development + -l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers + # Default: false + [--app-root=APP_ROOT] # The path to the Rails application + # Default: . + [--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error] # Halt upon a load error while loading the Rails application + # Default: true + [--skip-constant=constant [constant ...]] # Do not generate RBI definitions for the given application constant(s) + [--compiler-options=key:value] # Options to pass to the DSL compilers + -c, [--config=] # Path to the Tapioca configuration file + # Default: sorbet/tapioca/config.yml + -V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes + # Default: false Generate RBIs for dynamic methods ``` @@ -865,9 +862,6 @@ lockfile changes, Tapioca sees the digest mismatch and resets Bootsnap's cache p This lets gem bumps that affect rewriting, such as `tapioca`, start from a fresh cache without accumulating old cache directories. -The first run is slower because every file is rewritten and the result is baked into the iseq cache; subsequent runs -against the same lockfile skip the rewrite entirely. - `Bootsnap.setup` mutates a process-wide singleton, and a second call would overwrite Tapioca's dedicated cache directory and start writing rewritten iseqs into the host's normal cache. Tapioca enforces this under `TAPIOCA_RBS_CACHE=1`: after its own setup runs, any subsequent `Bootsnap.setup` raises a clear error pointing at the fix. Gate your host's `Bootsnap.setup` on the same env var. Rails apps do this in `config/boot.rb`: ```ruby @@ -875,19 +869,6 @@ against the same lockfile skip the rewrite entirely. require "bootsnap/setup" unless ENV["TAPIOCA_RBS_CACHE"] == "1" ``` -#### Priming the cache from CI - -For CI pipelines that want to populate the cache once and have downstream jobs read from a warm copy, use `--only-bootsnap-rbs-cache`. This pattern lets you scope cache writes to a single job (the prime) so PR-side jobs read from it without uploading on every successful build: - -```shell -# Prime: populate the cache. -$ TAPIOCA_RBS_CACHE=1 bin/tapioca dsl --only-bootsnap-rbs-cache - -# Consumer: read from the populated cache. -# BOOTSNAP_READONLY=1 prevents bootsnap from writing back to a read-only mount. -$ TAPIOCA_RBS_CACHE=1 BOOTSNAP_READONLY=1 bin/tapioca dsl -``` - ### RBI files for missing constants and methods Even after generating the RBIs, it is possible that some constants or methods are still undefined for Sorbet. @@ -1009,7 +990,6 @@ dsl: only: [] exclude: [] verify: false - only_bootsnap_rbs_cache: false quiet: false workers: 1 rbi_max_line_length: 120 diff --git a/lib/tapioca/cli.rb b/lib/tapioca/cli.rb index a46468f27..ab5a605f9 100644 --- a/lib/tapioca/cli.rb +++ b/lib/tapioca/cli.rb @@ -103,10 +103,6 @@ def todo type: :boolean, default: false, desc: "Verifies RBIs are up-to-date" - option :only_bootsnap_rbs_cache, - type: :boolean, - default: false, - desc: "Only boot the application and load DSL extensions/compilers to populate the bootsnap iseq cache, then exit. Skips compiler execution and RBI generation. Mutually exclusive with --verify and --list-compilers." option :quiet, aliases: ["-q"], type: :boolean, @@ -154,12 +150,6 @@ def todo def dsl(*constant_or_paths) set_environment(options) - if options[:only_bootsnap_rbs_cache] && (options[:verify] || options[:list_compilers]) - conflicting = options[:verify] ? "--verify" : "--list-compilers" - raise MalformattedArgumentError, - "Options '--only-bootsnap-rbs-cache' and '#{conflicting}' are mutually exclusive" - end - # Assume anything starting with a capital letter or colon is a class, otherwise a path constants, paths = constant_or_paths.partition { |c| c =~ /\A[A-Z:]/ } @@ -192,7 +182,7 @@ def dsl(*constant_or_paths) elsif options[:list_compilers] Commands::DslCompilerList.new(**command_args) else - Commands::DslGenerate.new(**command_args, only_bootsnap_rbs_cache: options[:only_bootsnap_rbs_cache]) + Commands::DslGenerate.new(**command_args) end command.run diff --git a/lib/tapioca/commands/dsl_generate.rb b/lib/tapioca/commands/dsl_generate.rb index f1c8d0971..3114bebd6 100644 --- a/lib/tapioca/commands/dsl_generate.rb +++ b/lib/tapioca/commands/dsl_generate.rb @@ -4,12 +4,6 @@ module Tapioca module Commands class DslGenerate < AbstractDsl - #: (?only_bootsnap_rbs_cache: bool, **untyped) -> void - def initialize(only_bootsnap_rbs_cache: false, **kwargs) - @only_bootsnap_rbs_cache = only_bootsnap_rbs_cache - super(**T.unsafe(kwargs)) - end - private # @override @@ -17,15 +11,6 @@ def initialize(only_bootsnap_rbs_cache: false, **kwargs) def execute load_application - if @only_bootsnap_rbs_cache - if ENV["TAPIOCA_RBS_CACHE"] == "1" - say("Bootsnap RBS cache populated, exiting before RBI generation.", :green) - else - say_error("Warning: --only-bootsnap-rbs-cache requires TAPIOCA_RBS_CACHE=1 to populate the cache", :yellow) - end - return - end - say("Compiling DSL RBI files...") say("") diff --git a/lib/tapioca/rbs/bootsnap_cache.rb b/lib/tapioca/rbs/bootsnap_cache.rb index 7a423034d..f7bfcb8a6 100644 --- a/lib/tapioca/rbs/bootsnap_cache.rb +++ b/lib/tapioca/rbs/bootsnap_cache.rb @@ -13,31 +13,20 @@ module RBS # source files are unchanged. # To account for this, we store the current Gemfile.lock SHA256 in a # `.gemfile-lock-digest` file. - # On writable runs, a digest mismatch deletes Bootsnap's cache payload and - # records the new digest, so this run rebuilds the cache from scratch. On - # read-only runs, a digest mismatch means the cache is stale and must not be - # used. + # A digest mismatch deletes Bootsnap's cache payload and records the new + # digest, so this run rebuilds the cache from scratch. module BootsnapCache - PrepareResult = Struct.new(:setup_bootsnap, keyword_init: true) - DIGEST_FILE = ".gemfile-lock-digest" #: String class << self - #: (String, readonly: bool) -> PrepareResult - def prepare_for_setup(cache_dir, readonly:) + #: (String) -> void + def prepare_for_setup(cache_dir) digest = gemfile_lock_digest + return if digest_matches?(cache_dir, digest) - if readonly - return PrepareResult.new(setup_bootsnap: digest_matches?(cache_dir, digest)) - end - - unless digest_matches?(cache_dir, digest) - FileUtils.rm_rf(File.join(cache_dir, "bootsnap")) - FileUtils.mkdir_p(cache_dir) - File.write(digest_path(cache_dir), digest) - end - - PrepareResult.new(setup_bootsnap: true) + FileUtils.rm_rf(File.join(cache_dir, "bootsnap")) + FileUtils.mkdir_p(cache_dir) + File.write(digest_path(cache_dir), digest) end private diff --git a/lib/tapioca/rbs/rewriter.rb b/lib/tapioca/rbs/rewriter.rb index 817917071..def5bd00e 100644 --- a/lib/tapioca/rbs/rewriter.rb +++ b/lib/tapioca/rbs/rewriter.rb @@ -32,7 +32,7 @@ def setup(**_kwargs) end end - module BootsnapSetup + module BootsnapIntegration class << self extend T::Sig @@ -40,16 +40,8 @@ class << self def setup require "bootsnap" - # Respect BOOTSNAP_READONLY for consumers reading a pre-populated cache - # (e.g. a CI prime step). - readonly = !["0", "false", false].include?(ENV.fetch("BOOTSNAP_READONLY") { false }) cache_dir = ENV.fetch("TAPIOCA_BOOTSNAP_CACHE_DIR", File.join(Dir.pwd, "tmp/cache/bootsnap-tapioca-rbs")) - # A read-only cache with a mismatched lockfile digest may contain stale rewritten iseqs, - # and this process cannot reset it. - return unless Tapioca::RBS::BootsnapCache.prepare_for_setup( - cache_dir, - readonly: readonly, - ).setup_bootsnap + Tapioca::RBS::BootsnapCache.prepare_for_setup(cache_dir) Bootsnap.setup( cache_dir: cache_dir, @@ -57,12 +49,12 @@ def setup load_path_cache: true, compile_cache_iseq: true, compile_cache_yaml: true, - readonly: readonly, + readonly: false, revalidation: true, ) Bootsnap.log_stats! - ensure - Bootsnap.singleton_class.prepend(Tapioca::RBS::BootsnapGuard) if defined?(Bootsnap) + + Bootsnap.singleton_class.prepend(Tapioca::RBS::BootsnapGuard) end end end @@ -70,11 +62,10 @@ def setup end # When TAPIOCA_RBS_CACHE=1, use a dedicated Bootsnap cache directory for -# RBS-rewritten iseqs. Stale read-only caches are skipped because this process -# cannot reset them. +# RBS-rewritten iseqs. if ENV["TAPIOCA_RBS_CACHE"] == "1" begin - Tapioca::RBS::BootsnapSetup.setup + Tapioca::RBS::BootsnapIntegration.setup rescue LoadError # Bootsnap is not in the bundle, skip iseq caching. end diff --git a/spec/tapioca/cli/dsl_spec.rb b/spec/tapioca/cli/dsl_spec.rb index 358a94558..246dbbe33 100644 --- a/spec/tapioca/cli/dsl_spec.rb +++ b/spec/tapioca/cli/dsl_spec.rb @@ -659,27 +659,6 @@ class Post assert_success_status(result) end - it "exits before RBI generation when --only-bootsnap-rbs-cache is set" do - @project.write!("lib/post.rb", <<~RB) - require "smart_properties" - - class Post - include SmartProperties - property :title, accepts: String - end - RB - - result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: { "TAPIOCA_RBS_CACHE" => "1" }) - - assert_stdout_includes(result, <<~OUT) - Bootsnap RBS cache populated, exiting before RBI generation. - OUT - - assert_stderr_includes(result, "bootsnap miss:") - refute_project_file_exist("sorbet/rbi/dsl/post.rbi") - assert_success_status(result) - end - it "resets the bootsnap cache when Gemfile.lock changes" do @project.write!("lib/post.rb", <<~RB) require "smart_properties" @@ -695,69 +674,19 @@ class Post "TAPIOCA_BOOTSNAP_CACHE_DIR" => "tmp/cache/test-bootsnap-tapioca-rbs", } - result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: env) + result = @project.tapioca("dsl Post", env: env) assert_success_status(result) @project.write!("tmp/cache/test-bootsnap-tapioca-rbs/bootsnap/stale-cache-entry", "stale") @project.write!("Gemfile.lock", "#{@gemfile_lock}\n") - result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: env) + result = @project.tapioca("dsl Post", env: env) assert_success_status(result) refute_project_file_exist("tmp/cache/test-bootsnap-tapioca-rbs/bootsnap/stale-cache-entry") assert_project_file_exist("tmp/cache/test-bootsnap-tapioca-rbs/.gemfile-lock-digest") end - it "skips a stale read-only bootsnap cache when Gemfile.lock changes" do - @project.write!("lib/post.rb", <<~RB) - require "smart_properties" - - class Post - include SmartProperties - property :title, accepts: String - end - RB - - env = { - "TAPIOCA_RBS_CACHE" => "1", - "TAPIOCA_BOOTSNAP_CACHE_DIR" => "tmp/cache/test-readonly-bootsnap-tapioca-rbs", - } - - result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post", env: env) - - assert_success_status(result) - original_digest = @project.read("tmp/cache/test-readonly-bootsnap-tapioca-rbs/.gemfile-lock-digest") - @project.write!("Gemfile.lock", "#{@gemfile_lock}\n") - - result = @project.tapioca("dsl Post", env: env.merge("BOOTSNAP_READONLY" => "1")) - - assert_success_status(result) - assert_empty_stderr(result) - assert_project_file_exist("sorbet/rbi/dsl/post.rbi") - assert_project_file_equal("tmp/cache/test-readonly-bootsnap-tapioca-rbs/.gemfile-lock-digest", original_digest) - end - - it "warns when --only-bootsnap-rbs-cache is set without TAPIOCA_RBS_CACHE=1" do - @project.write!("lib/post.rb", <<~RB) - require "smart_properties" - - class Post - include SmartProperties - property :title, accepts: String - end - RB - - result = @project.tapioca("dsl --only-bootsnap-rbs-cache Post") - - assert_stderr_includes( - result, - "Warning: --only-bootsnap-rbs-cache requires TAPIOCA_RBS_CACHE=1 to populate the cache", - ) - refute_includes(result.out, "Bootsnap RBS cache populated") - refute_project_file_exist("sorbet/rbi/dsl/post.rbi") - assert_success_status(result) - end - it "preserves RBS comment rewriting when the host sets up Bootsnap without TAPIOCA_RBS_CACHE" do @project.write!("lib/00_bootsnap.rb", <<~RB) require "bootsnap" @@ -2202,26 +2131,6 @@ class Post assert_success_status(result) end - it "rejects --only-bootsnap-rbs-cache combined with --verify" do - result = @project.tapioca("dsl --verify --only-bootsnap-rbs-cache") - - assert_stderr_includes( - result, - "Options '--only-bootsnap-rbs-cache' and '--verify' are mutually exclusive", - ) - refute_success_status(result) - end - - it "rejects --only-bootsnap-rbs-cache combined with --list-compilers" do - result = @project.tapioca("dsl --list-compilers --only-bootsnap-rbs-cache") - - assert_stderr_includes( - result, - "Options '--only-bootsnap-rbs-cache' and '--list-compilers' are mutually exclusive", - ) - refute_success_status(result) - end - it "rejects negative --max-diff-lines values" do ["0", "-1"].each do |value| result = @project.tapioca("dsl --verify --max-diff-lines=#{value}")