PEP 735: Add prior art sections for JS and Ruby to appendices (#3557)

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
Co-authored-by: Brett Cannon <brett@python.org>
This commit is contained in:
Stephen Rosen 2023-11-30 15:37:22 -06:00 committed by GitHub
parent 84fc526aaa
commit d75db62fa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 314 additions and 1 deletions

View File

@ -633,7 +633,320 @@ Footnotes
Appendix A: Prior Art in Non-Python Languages
=============================================
TODO
This section is primarily informational and serves to document how other
language ecosystems solve similar problems.
JavaScript and ``package.json``
-------------------------------
In the JavaScript community, packages contain a canonical configuration and
data file, similar in scope to ``pyproject.toml``, at ``package.json``.
Two keys in ``package.json`` control dependency data: ``"dependencies"`` and
``"devDependencies"``. The role of ``"dependencies"`` is effectively the same
as that of ``[project.dependencies]`` in ``pyproject.toml``, declaring the
direct dependencies of a package.
``"dependencies"`` data
'''''''''''''''''''''''
Dependency data is declared in ``package.json`` as a mapping from package names
to version specifiers.
Version specifiers support a small grammar of possible versions, ranges, and
other values, similar to Python's :pep:`440` version specifiers.
For example, here is a partial ``package.json`` file declaring a few
dependencies:
.. code-block:: json
{
"dependencies": {
"@angular/compiler": "^17.0.2",
"camelcase": "8.0.0",
"diff": ">=5.1.0 <6.0.0"
}
}
The use of the ``@`` symbol is a `scope
<https://docs.npmjs.com/cli/v10/using-npm/scope>`__ which declares the package
owner, for organizationally owned packages.
``"@angular/compiler"`` therefore declares a package named ``compiler`` grouped
under ``angular`` ownership.
Dependencies Referencing URLs and Local Paths
'''''''''''''''''''''''''''''''''''''''''''''
Dependency specifiers support a syntax for URLs and Git repositories, similar
to the provisions in Python packaging.
URLs may be used in lieu of version numbers.
When used, they implicitly refer to tarballs of package source code.
Git repositories may be similarly used, including support for committish
specifiers.
Unlike :pep:`440`, NPM allows for the use of local paths to package source code
directories for dependencies. When these data are added to ``package.json`` via
the standard ``npm install --save`` command, the path is normalized to a
relative path, from the directory containing ``package.json``, and prefixed
with ``file:``. For example, the following partial ``package.json`` contains a
reference to a sibling of the current directory:
.. code-block:: json
{
"dependencies": {
"my-package": "file:../foo"
}
}
The `official NPM documentation
<https://docs.npmjs.com/cli/v8/configuring-npm/package-json#local-paths>`__
states that local path dependencies "should not" be published to public package
repositories, but makes no statement about the inherent validity or invalidity
of such dependency data in published packages.
``"devDependencies"`` data
''''''''''''''''''''''''''
``package.json`` is permitted to contain a second section named
``"devDependencies"``, in the same format as ``"dependencies"``.
The dependencies declared in ``"devDependencies"`` are not installed by default
when a package is installed from the package repository (e.g. as part of a
dependency being resolved) but are installed when ``npm install`` is run in the
source tree containing ``package.json``.
Just as ``"dependencies"`` supports URLs and local paths, so does
``"devDependencies"``.
``"peerDependencies"`` and ``"optionalDependencies"``
'''''''''''''''''''''''''''''''''''''''''''''''''''''
There are two additional, related sections in ``package.json`` which have
relevance.
``"peerDependencies"`` declares a list of dependencies in the same format as
``"dependencies"``, but with the meaning that these are a compatibility
declaration.
For example, the following data declares compatibility with package ``foo``
version 2:
.. code-block:: json
{
"peerDependencies": {
"foo": "2.x"
}
}
``"optionalDependencies"`` declares a list of dependencies which should be
installed if possible, but which should not be treated as failures if they are
unavailable. It also uses the same mapping format as ``"dependencies"``.
``"peerDependenciesMeta"``
~~~~~~~~~~~~~~~~~~~~~~~~~~
``"peerDependenciesMeta"`` is a section which allows for additional control
over how ``"peerDependencies"`` are treated.
Warnings about missing dependencies can be disabled by setting packages to
``optional`` in this section, as in the following sample:
.. code-block:: json
{
"peerDependencies": {
"foo": "2.x"
},
"peerDependenciesMeta": {
"foo": {
"optional": true
}
}
}
``--omit`` and ``--include``
''''''''''''''''''''''''''''
The ``npm install`` command supports two options, ``--omit`` and ``--include``,
which can control whether "prod", "dev", "optional", or "peer" dependencies are installed.
The "prod" name refers to dependencies listed under ``"dependencies"``.
By default, all four groups are installed when ``npm install`` is executed
against a source tree, but these options can be used to control installation
behavior more precisely.
Furthermore, these values can be declared in ``.npmrc`` files, allowing
per-user and per-project configurations to control installation behaviors.
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 language 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 <https://bundler.io/v1.12/man/gemfile.5.html>`__ 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:
.. code-block:: ruby
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 <https://guides.rubygems.org/specification-reference/>`__ is a
ruby file containing a `Gem::Specification
<https://ruby-doc.org/stdlib-3.0.1/libdoc/rubygems/rdoc/Gem/Specification.html>`__
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 ``rails.gemspec`` file, with many fields removed or
shortened to simplify:
.. code-block:: ruby
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`` makes use of
development dependencies, containing the following specification in its
``Rakefile``:
.. code-block:: ruby
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``.
For full details, see the ``gemspec`` directive in ``bundler``\'s
`documentation on Gemfiles
<https://bundler.io/v1.12/man/gemfile.5.html#GEMSPEC-gemspec->`__.
However, 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:
.. code-block:: ruby
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``:
.. code-block:: ruby
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:
.. code-block:: ruby
gem 'cool-gem', :path => '.'
group :development do
gem 'kramdown'
end
Appendix B: Prior Art in Python
===============================