I’ve just done some more reading and tinkering with Gemfile and Gemspec, for some prior art from Ruby. I plan to put this into an appendix for prior art. Please read the below as a rough draft of a part of the PEP.
I’m not sure that I’ll include a full section on npm/NodeJS. There is no official support for multiple dependency groups but there is devDependencies as a separate section. There’s clear community interest out there for such a thing, and their core team seems receptive to the idea, but there’s still no official or mainstream implementation.
Ruby & Ruby Gems
Ruby projects may or may not be intended to produce packages (“gems”) in the ruby ecosystem. In fact, the expectation is that most users of the langauge do not want to produce gems and have no interest in producing their own packages. Many tutorials do not touch on how to produce packages, and the toolchain never requires user code to be packaged for supported use-cases.
Ruby splits requirement specification into two separate files.
Gemfile: a dedicated file which only supports requirement data in the form of dependency groups
<package>.gemspec: a dedicated file for declaring package (gem) metadata
The bundler tool, providing the bundle command, is the primary interface for using Gemfile data.
The gem tool is responsible for building gems from .gemspec data, via the gem build command.
Gemfiles & bundle
A Gemfile is a ruby file containing gem directives enclosed in any number of group declarations. gem directives may also be used outside of the group declaration, in which case they form an implicitly unnamed group of dependencies.
For example, the following Gemfile lists rails as a project dependency. All other dependencies are listed under groups:
source 'https://rubygems.org'
gem 'rails'
group :test do
gem 'rspec'
end
group :lint do
gem 'rubocop'
end
group :docs do
gem 'kramdown'
gem 'nokogiri'
end
If a user executes bundle install with these data, all groups are installed.
Users can deselect groups by creating or modifying a bundler config in .bundle/config, either manually or via the CLI. For example, bundle config set --local without 'lint:docs'.
It is not possible, with the above data, to exclude the top-level use of the 'rails' gem or to refer to that implicit grouping by name.
gemspec and packaged dependency data
A gemspec file is a ruby file containing a Gem::Specification instance declaration.
Only two fields in a Gem::Specification pertain to package dependency data.
These are add_development_dependency and add_runtime_dependency.
A Gem::Specification object also provides methods for adding dependencies dynamically, including add_dependency (which adds a runtime dependency).
Here is a variant of the current rails.gemspec file at time of writing, with many fields removed or shortened to simplify:
version = '7.1.2'
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "rails"
s.version = version
s.summary = "Full-stack web application framework."
s.license = "MIT"
s.author = "David Heinemeier Hansson"
s.files = ["README.md", "MIT-LICENSE"]
# shortened from the real 'rails' project
s.add_dependency "activesupport", version
s.add_dependency "activerecord", version
s.add_dependency "actionmailer", version
s.add_dependency "activestorage", version
s.add_dependency "railties", version
end
Note that there is no use of add_development_dependency. Some other mainstream, major packages (e.g. rubocop) do not use development dependencies in their gems.
Other projects do use this feature. For example, kramdown does make use of development dependencies, containing the following specification in its Rakefile:
s.add_dependency "rexml"
s.add_development_dependency 'minitest', '~> 5.0'
s.add_development_dependency 'rouge', '~> 3.0', '>= 3.26.0'
s.add_development_dependency 'stringex', '~> 1.5.1'
The purpose of development dependencies is only to declare an implicit group, as part of the .gemspec, which can then be used by bundler.
See details on the gemspec directive in Gemfiles: Bundler: gemfile
The integration between .gemspec development dependencies and Gemfile/bundle usage is best understood via an example.
gemspec development dependency example
Consider the following simple project in the form of a Gemfile and .gemspec.
The cool-gem.gemspec file:
Gem::Specification.new do |s|
s.author = 'Stephen Rosen'
s.name = 'cool-gem'
s.version = '0.0.1'
s.summary = 'A very cool gem that does cool stuff'
s.license = 'MIT'
s.files = []
s.add_dependency 'rails'
s.add_development_dependency 'kramdown'
end
and the Gemfile:
source 'https://rubygems.org'
gemspec
The gemspec directive in Gemfile declares a dependency on the local package, cool-gem, defined in the locally available cool-gem.gemspec file.
It also implicitly adds all development dependencies to a dependency group named development.
Therefore, in this case, the gemspec directive is equivalent to the following Gemfile content:
gem 'cool-gem', :path => '.'
group :development do
gem 'kramdown'
end
Lessons from the Ruby Model for Python Dependency Groups
??? TODO ???
(I haven’t really drawn conclusions yet, but surely we will be able to draw some?)