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:
parent
84fc526aaa
commit
d75db62fa3
|
@ -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
|
||||
===============================
|
||||
|
|
Loading…
Reference in New Issue