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?)