Gems and Bundler
Carton works with or without Bundler. Each Box has its own RubyGems state, so a Carton’s gems never leak into its importer.
A Carton’s own bundle
Section titled “A Carton’s own bundle”A Carton that needs its own gems sets up a Carton Bundle, from its own Gemfile, in its Entrypoint. Other Cartons need no setup.
# cartons/math_helper/lib/math_helper.rb: the EntrypointCarton.setup_bundle # uses cartons/math_helper/Gemfile, only in this Box
require 'bigdecimal' # this Carton's locked version
export version: BigDecimal::VERSIONCarton.setup_bundle works like Bundler.setup, and the top-level app uses the same line. RUBY_BOX=1 bundle exec ruby main.rb also works.
ENV is shared by the whole process. setup_bundle points BUNDLE_GEMFILE at the Carton’s Gemfile while Bundler sets up, then restores all of ENV.
- While
setup_bundleruns, other threads can see the Carton’sBUNDLE_GEMFILEand Bundler’s exported variables. Call it while booting, before starting threads that readENV. - Subprocesses never inherit a Carton’s bundle, including ones the Carton starts. Wrap a subprocess that needs it in
bundle exec, or pass itBUNDLE_GEMFILE.
Gem Cartons
Section titled “Gem Cartons”Importing a file from a gem the importer has activated, such as a path gem in the app’s Gemfile, activates that gem and its runtime dependencies in the Carton’s Box at the importer’s versions. The gem needs no Gemfile of its own:
# main.rb, with a Gemfile of: gem 'bigdecimal', '3.3.1'; gem 'pricing', path: 'pricing'Carton.setup_bundlePricing = import 'pricing' # pricing's require 'bigdecimal' gets 3.3.1- A Gem Carton loads once per process, so its First Importer fixes its gem versions. A later importer that activates or locks a different version of one of them raises
Carton::ImportErrornaming the gem and both versions. - If the importer ran Bundler setup, the Gem Carton’s Box is restricted to that bundle, as
Bundler.setuprestricts the Main Box. Every locked gem loads at its locked version, and a gem outside the lockfile raisesLoadError. - Without Bundler, only declared dependencies the importer activated are shared. Anything else falls back to RubyGems picking the newest installed version inside the Box.
- If the Gem Carton also calls
setup_bundle, its Gemfile must agree with the importer’s versions, or Bundler raisesGem::LoadError.
Because a bundled importer exposes its whole lockfile, a Gem Carton can load a gem it never declared and still work, until it is installed somewhere without that gem. carton check reports each Undeclared Gem, so the gemspec stays an accurate manifest.
Monorepos
Section titled “Monorepos”To split an app into Cartons, make each one a path gem in one Gemfile and import it by gem name. Each gemspec lists the Carton’s dependencies, and the app’s lockfile pins every version. See the monorepo example.
Different versions side by side
Section titled “Different versions side by side”Plain Ruby cannot load two versions of one gem in a process. Two Cartons with their own bundles can, which suits plugin hosts whose plugins disagree. See the plugins example.
