Skip to content

Deps: gemspec-driven Gemfile support for gem projects (and omit the ruby directive) #71

Description

@JPDuchesne

Motivation

The gem repos (simplecov-sorbet, ast-transform, rspock) can't use dev's Gemfile generation today — their dependencies.rb is toolchain-only (ruby "4.0.6" and nothing else) and the Gemfile stays hand-written. Two gaps in BundlerRepository block adoption:

  1. No gemspec directive. A packaged gem's runtime dependencies must stay single-sourced in the gemspec; its Gemfile needs a gemspec line so bundle resolves the gem under development plus its declared deps. The generated Gemfile can only render gem lines.
  2. The ruby directive is unconditionally the exact toolchain pin. write_gemfile emits ruby "<exact version>" whenever dependencies.rb declares a Ruby. For an app like ai-flow that's correct: CI runs a single Ruby, and reading .ruby-version in setup-ruby keeps the interpreter aligned with the pin, so bundler's Gemfile check passes. Gems are structurally different: their CI matrix runs 3.3 / 3.4 / 4.0 on purpose — proving the gem works on Rubies other than the development pin. There is no single version to align to, so an exact ruby directive fails bundle install on every non-pinned matrix row ("Your Ruby version is 3.3.x, but your Gemfile specified 4.0.6"), regardless of workflow configuration.

Design

DSL (Dev::Deps::DSL): a gemspec declaration:

Dev::Deps.define do
  ruby "4.0.6"
  gemspec

  group(:development) do
    gem "rubocop-shopify"
    # ...
  end
end

Generated Gemfile (BundlerRepository.write_gemfile):

  • Emit gemspec after source.
  • When gemspec is declared, omit the ruby directive entirely. The version pin does not disappear — it moves to the layers where it belongs:
    • .ruby-version + shadowenv (dev-provisioned) keep local development and dev commands on the exact toolchain pin;
    • the gemspec's required_ruby_version expresses the supported range (the contract the CI matrix verifies);
    • the Gemfile asserts nothing, so every matrix row installs cleanly.

Locking/staleness:

  • bundle lock reads the gemspec natively, so resolution needs no special handling.
  • Gemspec dependency edits are a resolution input that dependencies.rb's manifest digest doesn't capture. Options: hash the gemspec into the staleness digest, or rely on the frozen install (BUNDLE_FROZEN=true) failing loudly on gemspec/lock drift — which it already does — with a message pointing at dev update-deps.

Adoption

simplecov-sorbet, ast-transform, rspock: move dev/test gems from the hand-written Gemfile into dependencies.rb groups, delete the hand-written Gemfile, run dev update-deps. Runtime deps stay in the gemspec, unchanged. Contributors without dev keep working — the generated Gemfile/Gemfile.lock are committed, so plain bundle install still works.

Non-goals

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions