From 83851ced0d175c1e83dc49f41fdcbcee24487ff9 Mon Sep 17 00:00:00 2001 From: John Bolliger Date: Mon, 28 Sep 2026 14:33:57 -0700 Subject: [PATCH] Let gem build load the gemspec gem build failed with "undefined local variable or method 'gem_files' for class Gem::Specification". Rubygems evals the gemspec inside Gem::Specification, so the top-level def became an instance method, and the Gem::Specification.new block could not call it. Bundler evals the gemspec at the top level, so bundle install and rspec did not show it. gem_files and homepage are now locals, which work in both places. The new spec loads the gemspec with Gem::Specification.load in a fresh ruby without Bundler. It fails with the same error on the old gemspec. Co-Authored-By: Claude Opus 5.5 (1M context) --- active_remote-cached.gemspec | 28 +++++++++++++--------------- spec/gemspec_spec.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 spec/gemspec_spec.rb diff --git a/active_remote-cached.gemspec b/active_remote-cached.gemspec index da0ae7f..6db23bb 100644 --- a/active_remote-cached.gemspec +++ b/active_remote-cached.gemspec @@ -6,20 +6,18 @@ lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'active_remote/cached/version' -HOMEPAGE = 'https://github.com/mxenabled/active_remote-cached' +# Locals, not a constant or a def: `gem build` evals this file inside +# Gem::Specification, so top-level definitions land on that class. +homepage = 'https://github.com/mxenabled/active_remote-cached' # git ls-files returns nothing outside a checkout, so a build from a released # tarball needs the glob. -def gem_files - files = if File.directory?(File.join(__dir__, '.git')) - `git ls-files`.split($INPUT_RECORD_SEPARATOR) - else - Dir.glob('{lib,spec}/**/*', File::FNM_DOTMATCH) + - %w[LICENSE.txt README.md Rakefile Appraisals active_remote-cached.gemspec] - end - - files.reject { |file| File.directory?(file) } -end +gem_files = if File.directory?(File.join(__dir__, '.git')) + `git ls-files`.split($INPUT_RECORD_SEPARATOR) + else + Dir.glob('{lib,spec}/**/*', File::FNM_DOTMATCH) + + %w[LICENSE.txt README.md Rakefile Appraisals active_remote-cached.gemspec] + end Gem::Specification.new do |gem| gem.name = 'active_remote-cached' @@ -28,17 +26,17 @@ Gem::Specification.new do |gem| gem.email = ['brandonsdewitt@gmail.com', 'devexperience@mx.com'] gem.description = ' Provides "cached" finders and a DSL to enumerate which finders should have cached versions ' gem.summary = ' Provides a configuration for caching mechanisms and finders on ActiveRemote models' - gem.homepage = HOMEPAGE + gem.homepage = homepage gem.license = 'MIT' gem.metadata = { - 'homepage_uri' => HOMEPAGE, - 'source_code_uri' => HOMEPAGE, + 'homepage_uri' => homepage, + 'source_code_uri' => homepage, 'rubygems_mfa_required' => 'true' } gem.required_ruby_version = '>= 3.1' - gem.files = gem_files + gem.files = gem_files.reject { |file| File.directory?(file) } gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.require_paths = ['lib'] diff --git a/spec/gemspec_spec.rb b/spec/gemspec_spec.rb new file mode 100644 index 0000000..8b51204 --- /dev/null +++ b/spec/gemspec_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'open3' + +# `gem build` evals the gemspec inside Gem::Specification, but Bundler evals it +# at the top level. Bundler has already loaded the gemspec in this process, so +# a top-level def or constant from that load would hide the bug here. Load it +# in a fresh ruby without Bundler instead. +describe 'active_remote-cached.gemspec' do + let(:root) { File.expand_path('..', __dir__) } + let(:script) do + <<~RUBY + spec = Gem::Specification.load('active_remote-cached.gemspec') + abort 'Gem::Specification.load returned nil' unless spec + spec.validate + puts spec.full_name + RUBY + end + + it 'loads and validates with Rubygems alone' do + stdout, stderr, status = ::Bundler.with_unbundled_env do + ::Open3.capture3(::RbConfig.ruby, '-e', script, :chdir => root) + end + + expect(status).to be_success, stderr + expect(stdout).to eq("active_remote-cached-#{::ActiveRemote::Cached::VERSION}\n") + end +end