Repository: pypa/packaging.python.org
Pull request #1837: Latest setuptools and flit now support PEP 639
Author: login: not-my-profile
Created: 2025-03-25T07:22:38Z | Merged: 2025-03-30T02:04:39Z

This updates the now outdated notes added in
faf4c7d882ef2be310d5dc46d54ca666aa18821c.

Closes #1831.

<!-- readthedocs-preview python-packaging-user-guide start -->
----
📚 Documentation preview 📚:
* https://python-packaging-user-guide--1837.org.readthedocs.build/en/1837/guides/writing-pyproject-toml/
* https://python-packaging-user-guide--1837.org.readthedocs.build/en/1837/guides/licensing-examples-and-user-scenarios/
* https://python-packaging-user-guide--1837.org.readthedocs.build/en/1837/tutorials/packaging-projects/

<!-- readthedocs-preview python-packaging-user-guide end -->

Linked issue #1831: [TODO] Document that `setuptools` now supports PEP 639
> UPD: `setuptools` implemented PEP 639 support two days ago. 

 _Originally posted by @webknjaz in [#1806](https://github.com/pypa/packaging.python.org/issues/1806#issuecomment-2743213036)_


----


https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license and https://packaging.python.org/en/latest/guides/licensing-examples-and-user-scenarios/#basic-example are places to update.
Linked issue comments: user: login: henryiii
created_at: 2025-03-21T16:39:09Z
body: Also flit-core 3.11 (single license expressions only)
author_association: CONTRIBUTOR

Diff:
diff --git a/source/guides/licensing-examples-and-user-scenarios.rst b/source/guides/licensing-examples-and-user-scenarios.rst
index ae0066581..2c25ddfb0 100644
--- a/source/guides/licensing-examples-and-user-scenarios.rst
+++ b/source/guides/licensing-examples-and-user-scenarios.rst
@@ -12,8 +12,6 @@ This document aims to provide clear guidance how to migrate from the legacy
 to the standardized way of declaring licenses.
 Make sure your preferred build backend supports :pep:`639` before
 trying to apply the newer guidelines.
-As of February 2025, :doc:`setuptools <setuptools:userguide/pyproject_config>`
-and :ref:`flit <flit:pyproject_toml_project>` don't support :pep:`639` yet.
 
 
 Licensing Examples
diff --git a/source/guides/writing-pyproject-toml.rst b/source/guides/writing-pyproject-toml.rst
index f77b83b7f..318fe0d51 100644
--- a/source/guides/writing-pyproject-toml.rst
+++ b/source/guides/writing-pyproject-toml.rst
@@ -56,38 +56,7 @@ Usually, you'll just copy what your build backend's documentation
 suggests (after :ref:`choosing your build backend <choosing-build-backend>`).
 Here are the values for some common build backends:
 
-.. tab:: Hatchling
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["hatchling"]
-        build-backend = "hatchling.build"
-
-.. tab:: setuptools
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["setuptools >= 61.0"]
-        build-backend = "setuptools.build_meta"
-
-.. tab:: Flit
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["flit_core >= 3.4"]
-        build-backend = "flit_core.buildapi"
-
-.. tab:: PDM
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["pdm-backend"]
-        build-backend = "pdm.backend"
-
+.. include:: ../shared/build-backend-tabs.rst
 
 
 Static vs. dynamic metadata
@@ -322,22 +291,43 @@ You can also specify the format explicitly, like this:
    readme = {file = "README.txt", content-type = "text/x-rst"}
 
 
-.. _license:
+.. _license-and-license-files:
 
-``license``
------------
+``license`` and ``license-files``
+---------------------------------
 
-:pep:`639` (accepted in August 2024) has changed the way the ``license`` field
-is declared. Make sure your preferred build backend supports :pep:`639` before
-trying to apply the newer guidelines.
-As of February 2025, :doc:`setuptools <setuptools:userguide/pyproject_config>`
-and :ref:`flit <flit:pyproject_toml_project>` don't support :pep:`639` yet.
+As per :pep:`639` licenses should be declared with two fields:
 
-:pep:`639` license declaration
-''''''''''''''''''''''''''''''
+- ``license`` is an :term:`SPDX license expression <License Expression>` consisting
+  of one or more :term:`license identifiers <License Identifier>`.
+- ``license-files`` is a list of license file glob patterns.
 
-This is a valid :term:`SPDX license expression <License Expression>` consisting
-of one or more :term:`license identifiers <License Identifier>`.
+A previous PEP had specified ``license`` to be a table with a ``file`` or a
+``text`` key, this format is now deprecated. Most :term:`build backends<build
+backend>` now support the new format as shown in the following table.
+
+.. list-table:: build backend versions that introduced :pep:`639` support
+   :header-rows: 1
+
+   * - hatchling
+     - setuptools
+     - flit-core [#flit-core-pep639]_
+     - pdm-backend
+     - poetry-core
+   * - 1.27.0
+     - 77.0.3
+     - 3.12
+     - 2.4.0
+     - `not yet <poetry-pep639-issue_>`_
+
+
+.. _license:
+
+``license``
+'''''''''''
+
+The new format for ``license`` is a valid :term:`SPDX license expression <License Expression>`
+consisting of one or more :term:`license identifiers <License Identifier>`.
 The full license list is available at the
 `SPDX license list page <spdxlicenselist_>`_. The supported list version is
 3.17 or any later compatible one.
@@ -349,6 +339,11 @@ The full license list is available at the
     # or
     license = "MIT AND (Apache-2.0 OR BSD-2-Clause)"
 
+.. note:: If you get a build error that ``license`` should be a dict/table,
+   your build backend doesn't yet support the new format. See the
+   `above section <license-and-license-files_>`_ for more context.
+   The now deprecated format is `described in PEP 621 <https://peps.python.org/pep-0621/#license>`__.
+
 As a general rule, it is a good idea to use a standard, well-known
 license, both to avoid confusion and because some organizations avoid software
 whose license is unapproved.
@@ -363,41 +358,11 @@ The custom identifiers must follow the SPDX specification,
     [project]
     license = "LicenseRef-My-Custom-License"
 
-Legacy license declaration
-''''''''''''''''''''''''''
-
-This can take two forms. You can put your license in a file, typically
-:file:`LICENSE` or :file:`LICENSE.txt`, and link that file here:
-
-.. code-block:: toml
-
-    [project]
-    license = {file = "LICENSE"}
-
-or you can write the name of the license:
-
-.. code-block:: toml
-
-    [project]
-    license = {text = "MIT License"}
-
-If you are using a standard, well-known license, it is not necessary to use this
-field. Instead, you should use one of the :ref:`classifiers` starting with ``License
-::``. (As a general rule, it is a good idea to use a standard, well-known
-license, both to avoid confusion and because some organizations avoid software
-whose license is unapproved.)
-
 
 .. _license-files:
 
 ``license-files``
------------------
-
-:pep:`639` (accepted in August 2024) has introduced the ``license-files`` field.
-Make sure your preferred build backend supports :pep:`639` before declaring the
-field.
-As of February 2025, :doc:`setuptools <setuptools:userguide/pyproject_config>`
-and :ref:`flit <flit:pyproject_toml_project>` don't support :pep:`639` yet.
+'''''''''''''''''
 
 This is a list of license files and files containing other legal
 information you want to distribute with your package.
@@ -572,7 +537,7 @@ A full example
    ]
    description = "Lovely Spam! Wonderful Spam!"
    readme = "README.rst"
-   license = "MIT"  # or license = {file = "LICENSE.txt"} for legacy declaration
+   license = "MIT"
    license-files = ["LICEN[CS]E.*"]
    keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
    classifiers = [
@@ -610,6 +575,9 @@ A full example
    like ``requires-python = "<= 3.10"`` here. `This blog post <requires-python-blog-post_>`_
    contains some information regarding possible problems.
 
+.. [#flit-core-pep639] flit-core `does not yet <flit-issue-735_>`_ support WITH in SPDX license expressions.
+
+.. _flit-issue-735: https://github.com/pypa/flit/issues/735
 .. _gfm: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax
 .. _setuptools: https://setuptools.pypa.io
 .. _poetry: https://python-poetry.org
@@ -617,6 +585,7 @@ A full example
 .. _pypi-search-pip: https://pypi.org/search?q=pip
 .. _classifier-list: https://pypi.org/classifiers
 .. _requires-python-blog-post: https://iscinumpy.dev/post/bound-version-constraints/#pinning-the-python-version-is-special
+.. _poetry-pep639-issue: https://github.com/python-poetry/poetry/issues/9670
 .. _pytest: https://pytest.org
 .. _pygments: https://pygments.org
 .. _rest: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
diff --git a/source/shared/build-backend-tabs.rst b/source/shared/build-backend-tabs.rst
new file mode 100644
index 000000000..7fc3a61da
--- /dev/null
+++ b/source/shared/build-backend-tabs.rst
@@ -0,0 +1,34 @@
+.. (comment) This file is included in guides/writing-pyproject-toml.rst and tutorials/packaging-projects.rst.
+.. The minimum versions here are the versions that introduced support for PEP 639.
+
+.. tab:: Hatchling
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["hatchling >= 1.26"]
+        build-backend = "hatchling.build"
+
+.. tab:: setuptools
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["setuptools >= 77.0.3"]
+        build-backend = "setuptools.build_meta"
+
+.. tab:: Flit
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["flit_core >= 3.12.0, <4"]
+        build-backend = "flit_core.buildapi"
+
+.. tab:: PDM
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["pdm-backend >= 2.4.0"]
+        build-backend = "pdm.backend"
diff --git a/source/tutorials/packaging-projects.rst b/source/tutorials/packaging-projects.rst
index 8992fffb0..6d0a95d5c 100644
--- a/source/tutorials/packaging-projects.rst
+++ b/source/tutorials/packaging-projects.rst
@@ -136,38 +136,7 @@ The :file:`pyproject.toml` tells :term:`build frontend <Build Frontend>` tools l
 examples for common build backends, but check your backend's own documentation
 for more details.
 
-.. tab:: Hatchling
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["hatchling"]
-        build-backend = "hatchling.build"
-
-.. tab:: setuptools
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["setuptools>=61.0"]
-        build-backend = "setuptools.build_meta"
-
-.. tab:: Flit
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["flit_core>=3.4"]
-        build-backend = "flit_core.buildapi"
-
-.. tab:: PDM
-
-    .. code-block:: toml
-
-        [build-system]
-        requires = ["pdm-backend"]
-        build-backend = "pdm.backend"
-
+.. include:: ../shared/build-backend-tabs.rst
 
 The ``requires`` key is a list of packages that are needed to build your package.
 The :term:`frontend <Build Frontend>` should install them automatically when building your package.
@@ -175,6 +144,8 @@ Frontends usually run builds in isolated environments, so omitting dependencies
 here may cause build-time errors.
 This should always include your backend's package, and might have other build-time
 dependencies.
+The minimum version specified in the above code block is the one that introduced support
+for :ref:`the new license metadata <license-and-license-files>`.
 
 The ``build-backend`` key is the name of the Python object that frontends will use
 to perform the build.
@@ -200,52 +171,27 @@ to include your username; this ensures that you have a unique
 package name that doesn't conflict with packages uploaded by other people
 following this tutorial.
 
-.. tab:: hatchling/pdm
-
-  .. code-block:: toml
-
-      [project]
-      name = "example_package_YOUR_USERNAME_HERE"
-      version = "0.0.1"
-      authors = [
-        { name="Example Author", email="author@example.com" },
-      ]
-      description = "A small example package"
-      readme = "README.md"
-      requires-python = ">=3.8"
-      classifiers = [
-          "Programming Language :: Python :: 3",
-          "Operating System :: OS Independent",
-      ]
-      license = "MIT"
-      license-files = ["LICEN[CS]E*"]
-
-      [project.urls]
-      Homepage = "https://github.com/pypa/sampleproject"
-      Issues = "https://github.com/pypa/sampleproject/issues"
-
-.. tab:: setuptools/flit
-
-  .. code-block:: toml
-
-      [project]
-      name = "example_package_YOUR_USERNAME_HERE"
-      version = "0.0.1"
-      authors = [
-        { name="Example Author", email="author@example.com" },
-      ]
-      description = "A small example package"
-      readme = "README.md"
-      requires-python = ">=3.8"
-      classifiers = [
-          "Programming Language :: Python :: 3",
-          "Operating System :: OS Independent",
-          "License :: OSI Approved :: MIT License",
-      ]
-
-      [project.urls]
-      Homepage = "https://github.com/pypa/sampleproject"
-      Issues = "https://github.com/pypa/sampleproject/issues"
+.. code-block:: toml
+
+    [project]
+    name = "example_package_YOUR_USERNAME_HERE"
+    version = "0.0.1"
+    authors = [
+      { name="Example Author", email="author@example.com" },
+    ]
+    description = "A small example package"
+    readme = "README.md"
+    requires-python = ">=3.8"
+    classifiers = [
+        "Programming Language :: Python :: 3",
+        "Operating System :: OS Independent",
+    ]
+    license = "MIT"
+    license-files = ["LICEN[CS]E*"]
+
+    [project.urls]
+    Homepage = "https://github.com/pypa/sampleproject"
+    Issues = "https://github.com/pypa/sampleproject/issues"
 
 - ``name`` is the *distribution name* of your package. This can be any name as
   long as it only contains letters, numbers, ``.``, ``_`` , and ``-``. It also
@@ -274,10 +220,9 @@ following this tutorial.
   your package will work on. For a complete list of classifiers, see
   https://pypi.org/classifiers/.
 - ``license`` is the :term:`SPDX license expression <License Expression>` of
-  your package. Not supported by all the build backends yet.
+  your package.
 - ``license-files`` is the list of glob paths to the license files,
   relative to the directory where :file:`pyproject.toml` is located.
-  Not supported by all the build backends yet.
 - ``urls`` lets you list any number of extra links to show on PyPI.
   Generally this could be to the source, documentation, issue trackers, etc.
 


Reviews:
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T07:28:42Z
user: login: webknjaz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T10:57:51Z
user: login: webknjaz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T17:50:55Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T04:30:03Z
user: login: cdce8p
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T22:44:41Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T05:27:49Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T05:28:02Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T05:30:53Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T05:38:39Z
user: login: cdce8p
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-27T08:34:53Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T05:13:42Z
user: login: cdce8p
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-28T19:43:35Z
user: login: AA-Turner
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T21:14:57Z
user: login: not-my-profile
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T05:40:07Z
user: login: webknjaz
body: Thank you!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-29T18:28:20Z

Review comments:
diff_hunk: @@ -175,6 +175,9 @@ Frontends usually run builds in isolated environments, so omitting dependencies
 here may cause build-time errors.
 This should always include your backend's package, and might have other build-time
 dependencies.
+The minimum version specified in the above code block is the one that introduced support
+for `license-files` being a list, if you need to use an older version you can adapt
+this requirement but will also need to specify the license files in a legacy format.
path: source/tutorials/packaging-projects.rst
user: login: not-my-profile
body: It would be neat to link the *Legacy license declaration* section from writing-pyproject-toml.rst here ... how do I do that in RST?
created_at: 2025-03-25T07:28:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +175,9 @@ Frontends usually run builds in isolated environments, so omitting dependencies
 here may cause build-time errors.
 This should always include your backend's package, and might have other build-time
 dependencies.
+The minimum version specified in the above code block is the one that introduced support
+for `license-files` being a list, if you need to use an older version you can adapt
+this requirement but will also need to specify the license files in a legacy format.
path: source/tutorials/packaging-projects.rst
user: login: webknjaz
body: If there's a label in that place, use the `:ref:` role. In general, you can see all linkable objects using intersphinx, for example.
created_at: 2025-03-25T10:57:51Z
author_association: MEMBER
diff_hunk: @@ -175,6 +175,9 @@ Frontends usually run builds in isolated environments, so omitting dependencies
 here may cause build-time errors.
 This should always include your backend's package, and might have other build-time
 dependencies.
+The minimum version specified in the above code block is the one that introduced support
+for `license-files` being a list, if you need to use an older version you can adapt
+this requirement but will also need to specify the license files in a legacy format.
path: source/tutorials/packaging-projects.rst
user: login: webknjaz
body: You'd have to add a label here https://github.com/pypa/packaging.python.org/blob/99469d7/source/guides/writing-pyproject-toml.rst?plain=1#L365-L367 to be able to reference it.
created_at: 2025-03-25T17:50:55Z
author_association: MEMBER
diff_hunk: @@ -175,6 +175,9 @@ Frontends usually run builds in isolated environments, so omitting dependencies
 here may cause build-time errors.
 This should always include your backend's package, and might have other build-time
 dependencies.
+The minimum version specified in the above code block is the one that introduced support
+for `license-files` being a list, if you need to use an older version you can adapt
+this requirement but will also need to specify the license files in a legacy format.
path: source/tutorials/packaging-projects.rst
user: login: not-my-profile
body: Thanks, I added a link to the relevant section of  writing-pyproject-toml.rst.
created_at: 2025-03-26T04:30:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -322,22 +322,43 @@ You can also specify the format explicitly, like this:
    readme = {file = "README.txt", content-type = "text/x-rst"}
 
 
-.. _license:
+.. _license-and-license-files:
 
-``license``
------------
+``license`` and ``license-files``
+---------------------------------
+
+As per :pep:`639` licenses should be declared with two fields:
+
+- ``license`` is an :term:`SPDX license expression <License Expression>` consisting
+  of one or more :term:`license identifiers <License Identifier>`.
+- ``license-files`` is a list of license file glob patterns.
+
+A previous PEP had specified ``license`` to be a table with a ``file`` or a
+``text`` key, this format is now deprecated. Most :term:`build backends<build
+backend>` now support the new format as shown in the following table.
+
+.. list-table:: build backend versions that introduced :pep:`639` support
+   :header-rows: 1
 
-:pep:`639` (accepted in August 2024) has changed the way the ``license`` field
-is declared. Make sure your preferred build backend supports :pep:`639` before
-trying to apply the newer guidelines.
-As of February 2025, :doc:`setuptools <setuptools:userguide/pyproject_config>`
-and :ref:`flit <flit:pyproject_toml_project>` don't support :pep:`639` yet.
+   * - hatchling
+     - setuptools
+     - flit-core [#flit-core-pep639]_
+     - pdm-backend
+     - poetry-core
+   * - 1.27.0
+     - 77.0.1
path: source/guides/writing-pyproject-toml.rst
user: login: cdce8p
body: ```suggestion
     - 77.0.3
```
I'd suggest `77.0.3` here as it includes a few bug fixes for the initial implementation.
created_at: 2025-03-26T22:39:05Z
author_association: MEMBER
diff_hunk: @@ -141,31 +141,31 @@ for more details.
     .. code-block:: toml
 
         [build-system]
-        requires = ["hatchling"]
+        requires = ["hatchling>=1.26"]
         build-backend = "hatchling.build"
 
 .. tab:: setuptools
 
     .. code-block:: toml
 
         [build-system]
-        requires = ["setuptools>=61.0"]
+        requires = ["setuptools>=77.0.1"]
path: source/tutorials/packaging-projects.rst
user: login: cdce8p
body: ```suggestion
        requires = ["setuptools>=77.0.3"]
```
created_at: 2025-03-26T22:40:35Z
author_association: MEMBER
diff_hunk: @@ -363,41 +384,11 @@ The custom identifiers must follow the SPDX specification,
     [project]
     license = "LicenseRef-My-Custom-License"
 
-Legacy license declaration
-''''''''''''''''''''''''''
-
-This can take two forms. You can put your license in a file, typically
-:file:`LICENSE` or :file:`LICENSE.txt`, and link that file here:
-
-.. code-block:: toml
-
-    [project]
-    license = {file = "LICENSE"}
-
-or you can write the name of the license:
-
-.. code-block:: toml
-
-    [project]
-    license = {text = "MIT License"}
-
-If you are using a standard, well-known license, it is not necessary to use this
-field. Instead, you should use one of the :ref:`classifiers` starting with ``License
-::``. (As a general rule, it is a good idea to use a standard, well-known
-license, both to avoid confusion and because some organizations avoid software
-whose license is unapproved.)
-
path: source/guides/writing-pyproject-toml.rst
user: login: cdce8p
body: Even with support for PEP 639 in almost all build backends, I'm unsure if that should really be removed. The packaging ecosystems moves quite slow. It might be helpful to understand old configs.

Adding a warning here (if possible) might be the better choice.
created_at: 2025-03-26T22:44:35Z
author_association: MEMBER
diff_hunk: @@ -322,22 +322,43 @@ You can also specify the format explicitly, like this:
    readme = {file = "README.txt", content-type = "text/x-rst"}
 
 
-.. _license:
+.. _license-and-license-files:
 
-``license``
------------
+``license`` and ``license-files``
+---------------------------------
+
+As per :pep:`639` licenses should be declared with two fields:
+
+- ``license`` is an :term:`SPDX license expression <License Expression>` consisting
+  of one or more :term:`license identifiers <License Identifier>`.
+- ``license-files`` is a list of license file glob patterns.
+
+A previous PEP had specified ``license`` to be a table with a ``file`` or a
+``text`` key, this format is now deprecated. Most :term:`build backends<build
+backend>` now support the new format as shown in the following table.
+
+.. list-table:: build backend versions that introduced :pep:`639` support
+   :header-rows: 1
 
-:pep:`639` (accepted in August 2024) has changed the way the ``license`` field
-is declared. Make sure your preferred build backend supports :pep:`639` before
-trying to apply the newer guidelines.
-As of February 2025, :doc:`setuptools <setuptools:userguide/pyproject_config>`
-and :ref:`flit <flit:pyproject_toml_project>` don't support :pep:`639` yet.
+   * - hatchling
+     - setuptools
+     - flit-core [#flit-core-pep639]_
+     - pdm-backend
+     - poetry-core
+   * - 1.27.0
+     - 77.0.1
path: source/guides/writing-pyproject-toml.rst
user: login: not-my-profile
body: Changed :)
created_at: 2025-03-27T05:27:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,31 +141,31 @@ for more details.
     .. code-block:: toml
 
         [build-system]
-        requires = ["hatchling"]
+        requires = ["hatchling>=1.26"]
         build-backend = "hatchling.build"
 
 .. tab:: setuptools
 
     .. code-block:: toml
 
         [build-system]
-        requires = ["setuptools>=61.0"]
+        requires = ["setuptools>=77.0.1"]
path: source/tutorials/packaging-projects.rst
user: login: not-my-profile
body: Changed :)
created_at: 2025-03-27T05:28:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -363,41 +384,11 @@ The custom identifiers must follow the SPDX specification,
     [project]
     license = "LicenseRef-My-Custom-License"
 
-Legacy license declaration
-''''''''''''''''''''''''''
-
-This can take two forms. You can put your license in a file, typically
-:file:`LICENSE` or :file:`LICENSE.txt`, and link that file here:
-
-.. code-block:: toml
-
-    [project]
-    license = {file = "LICENSE"}
-
-or you can write the name of the license:
-
-.. code-block:: toml
-
-    [project]
-    license = {text = "MIT License"}
-
-If you are using a standard, well-known license, it is not necessary to use this
-field. Instead, you should use one of the :ref:`classifiers` starting with ``License
-::``. (As a general rule, it is a good idea to use a standard, well-known
-license, both to avoid confusion and because some organizations avoid software
-whose license is unapproved.)
-
path: source/guides/writing-pyproject-toml.rst
user: login: not-my-profile
body: You're right that we should still help users learn about the legacy format but I think referencing the respective section of PEP 621 for that is enough. I have added [a Note in the license section](https://python-packaging-user-guide--1837.org.readthedocs.build/en/1837/guides/writing-pyproject-toml/#license) saying:

> If you get a build error that license should be a dict/table, your build backend doesn’t yet support the new format. See the [above section](https://python-packaging-user-guide--1837.org.readthedocs.build/en/1837/guides/writing-pyproject-toml/#license-and-license-files) for more context. The now deprecated format is [described in PEP 621](https://peps.python.org/pep-0621/#license).
created_at: 2025-03-27T05:30:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -322,22 +322,43 @@ You can also specify the format explicitly, like this:
    readme = {file = "README.txt", content-type = "text/x-rst"}
 
 
-.. _license:
+.. _license-and-license-files:
 
-``license``
------------
+``license`` and ``license-files``
+---------------------------------
+
+As per :pep:`639` licenses should be declared with two fields:
+
+- ``license`` is an :term:`SPDX license expression <License Expression>` consisting
+  of one or more :term:`license identifiers <License Identifier>`.
+- ``license-files`` is a list of license file glob patterns.
+
+A previous PEP had specified ``license`` to be a table with a ``file`` or a
+``text`` key, this format is now deprecated. Most :term:`build backends<build
+backend>` now support the new format as shown in the following table.
+
+.. list-table:: build backend versions that introduced :pep:`639` support
+   :header-rows: 1
 
-:pep:`639` (accepted in August 2024) has changed the way the ``license`` field
-is declared. Make sure your preferred build backend supports :pep:`639` before
-trying to apply the newer guidelines.
-As of February 2025, :doc:`setuptools <setuptools:userguide/pyproject_config>`
-and :ref:`flit <flit:pyproject_toml_project>` don't support :pep:`639` yet.
+   * - hatchling
+     - setuptools
+     - flit-core [#flit-core-pep639]_
+     - pdm-backend
+     - poetry-core
+   * - 1.27.0
+     - 77.0.1
path: source/guides/writing-pyproject-toml.rst
user: login: not-my-profile
body: (I also added a respective footnote to the version table, linking the setuptools changelog.)
created_at: 2025-03-27T05:38:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -610,15 +575,23 @@ A full example
    like ``requires-python = "<= 3.10"`` here. `This blog post <requires-python-blog-post_>`_
    contains some information regarding possible problems.
 
+.. [#flit-core-pep639] flit-core `does not yet <flit-issue-735_>`_ support WITH in SPDX license expressions.
+
+.. [#setuptools-pep639] setuptools 77.0.1 introduced support for :pep:`639`
+   but `77.0.3 fixed some related bugs <setuptools-changelog-77.0.3_>`_.
path: source/guides/writing-pyproject-toml.rst
user: login: cdce8p
body: I'd just remove the footnote. In a few months it will be irrelevant that support was technically added with `77.0.1`.
created_at: 2025-03-27T08:32:33Z
author_association: MEMBER
diff_hunk: @@ -610,15 +575,23 @@ A full example
    like ``requires-python = "<= 3.10"`` here. `This blog post <requires-python-blog-post_>`_
    contains some information regarding possible problems.
 
+.. [#flit-core-pep639] flit-core `does not yet <flit-issue-735_>`_ support WITH in SPDX license expressions.
+
+.. [#setuptools-pep639] setuptools 77.0.1 introduced support for :pep:`639`
+   but `77.0.3 fixed some related bugs <setuptools-changelog-77.0.3_>`_.
path: source/guides/writing-pyproject-toml.rst
user: login: not-my-profile
body: Fair enough.
created_at: 2025-03-28T05:13:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+.. (comment) This file is included in guides/writing-pyproject-toml.rst and tutorials/packaging-projects.rst.
+.. The minimum versions here are the versions that introduced support for PEP 639.
+
+.. tab:: Hatchling
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["hatchling >= 1.26"]
+        build-backend = "hatchling.build"
+
+.. tab:: setuptools
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["setuptools >= 77.0.3"]
+        build-backend = "setuptools.build_meta"
+
+.. tab:: Flit
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["flit_core >= 3.12.0"]
path: source/shared/build-backend-tabs.rst
user: login: AA-Turner
body: Flit has always included a `<4` in its own documentation:
```suggestion
        requires = ["flit_core>=3.12,<4"]
```
created_at: 2025-03-28T21:14:56Z
author_association: NONE
diff_hunk: @@ -0,0 +1,34 @@
+.. (comment) This file is included in guides/writing-pyproject-toml.rst and tutorials/packaging-projects.rst.
+.. The minimum versions here are the versions that introduced support for PEP 639.
+
+.. tab:: Hatchling
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["hatchling >= 1.26"]
+        build-backend = "hatchling.build"
+
+.. tab:: setuptools
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["setuptools >= 77.0.3"]
+        build-backend = "setuptools.build_meta"
+
+.. tab:: Flit
+
+    .. code-block:: toml
+
+        [build-system]
+        requires = ["flit_core >= 3.12.0"]
path: source/shared/build-backend-tabs.rst
user: login: not-my-profile
body: Added.
created_at: 2025-03-29T05:40:06Z
author_association: CONTRIBUTOR

PR comments:
user: login: webknjaz
created_at: 2025-03-25T10:59:57Z
body: Henry mentioned there's some limitations in Flit still. Perhaps, this should be documented.
author_association: MEMBER
user: login: cdce8p
created_at: 2025-03-25T13:04:28Z
body: > Henry mentioned there's some limitations in Flit still.

Flit-core `3.12` was released today with support for compound license expressions. Only thing missing now is `WITH` support. https://github.com/pypa/flit/issues/735 Though I suspect that won't be relevant to most people.

https://flit.pypa.io/en/stable/history.html#version-3-12

--
The PR mentions that "all" recommended backends now support PEP 639. I'm not sure about poetry-core. Yes, they accept the new syntax for `project.license` but I don't believe they have implemented any logic for it yet. AFAICT the metadata-version is also still kept at `2.3`.

https://github.com/python-poetry/poetry/issues/9670

--
As an aside, I did wonder if it would make sense to maintain a compatibility table for build-backends somewhere. I.e. a place that list you should use setuptools `>=77.0.3`, flit-core `>=3.12`, hatchling `>=1.27.0`, ... for support of PEP 639. There might be other major feature for which this might be relevant too. The alternative of course is to just always recommend the lastest release whenever possible.
author_association: MEMBER
user: login: not-my-profile
created_at: 2025-03-26T04:46:16Z
body: > As an aside, I did wonder if it would make sense to maintain a compatibility table for build-backends somewhere.

I think that's a good idea. I now introduced a joined section [*license and license-files*](https://python-packaging-user-guide--1837.org.readthedocs.build/en/1837/guides/writing-pyproject-toml/#license-and-license-files) on the *Writing your pyproject.toml* page where I am now explaining the situation with the PEP and that there was a different format that's now deprecated. Then I have added a table listing build backend versions that introduced PEP 639 support. I think this should clear up user counfusion about why a build backend is complaining that `license` should be a dict.

I also went ahead and removed the *Legacy license declaration* section.

> The PR mentions that "all" recommended backends now support PEP 639. I'm not sure about poetry-core. Yes, they accept the new syntax for project.license but I don't believe they have implemented any logic for it yet. AFAICT the metadata-version is also still kept at 2.3.

Thanks for pointing this out! I tried the latest version of poetry and can confirm that `poetry build` still generates `Metadata-Version: 2.3`. I removed the "all" sentence and added poetry-core to the new version table with *not yet* linked to the relevant poetry issue.

>Flit-core 3.12 was released today with support for compound license expressions. Only thing missing now is WITH support. Though I suspect that won't be relevant to most people.

Great, I bumped the minimum version for flit-core to 3.12. Below the code block the aforementioned new *license and license-files* section is now linked for more context. I also added a footnote in the table there mentioning the lacking WITH support and linking the relevant flit issue.
author_association: CONTRIBUTOR
user: login: not-my-profile
created_at: 2025-03-27T05:34:55Z
body: (Since I found out that the build backend tabs are present on two pages I have move them to a separate file and included them in both pages so that the minimum versions we specify there don't drift in the future.)
author_association: CONTRIBUTOR