packaging-problems icon indicating copy to clipboard operation
packaging-problems copied to clipboard

Trouble with a packaging tutorial: Licence build issue

Open Nordicus opened this issue 1 year ago • 18 comments

OS version

Ubuntu 20.04 LTS

Python version

3.12.8

Pip version

25.0

Guide link

https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives

Problem description

Trying to follow the guide as best as possible, but I run into an error when running python3 -m build for some backends but not others. The error for setuptools is included below. Flit has a different error, also apparently related to the license and license-files fields. Hatchling and PDM both build successfully.

If I comment out the license field only, I get the error that configuration error: `project` must not contain {'license-files'} properties and if I comment out both, then the contents of LICENSE appears to be included in the output. (adding 'example_package_[MY_UNAME]-0.0.1.dist-info/LICENSE' is not present if the LICENSE file is deleted).

My question is, why is the behaviour of each build system so different WRT including the license, and how do I know the correct way to specify the license for each individual build system? If I want to use setuptools specifically (it seems to be considered standard practice in my org) what is the "right" way to include a license?

Can the tutorial also be updated to reflect the fact that the body of the [project] section of pyproject.toml changes based on the build system used?

Error message

$python3 -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - setuptools>=61.0
* Getting build dependencies for sdist...
configuration error: `project.license` must be valid exactly by one definition (2 matches found):

    - keys:
        'file': {type: string}
      required: ['file']
    - keys:
        'text': {type: string}
      required: ['text']

DESCRIPTION:
    `Project license <https://peps.python.org/pep-0621/#license>`_.

GIVEN VALUE:
    "MIT"

OFFENDING RULE: 'oneOf'

DEFINITION:
    {
        "oneOf": [
            {
                "properties": {
                    "file": {
                        "type": "string",
                        "$$description": [
                            "Relative path to the file (UTF-8) which contains the license for the",
                            "project."
                        ]
                    }
                },
                "required": [
                    "file"
                ]
            },
            {
                "properties": {
                    "text": {
                        "type": "string",
                        "$$description": [
                            "The license of the project whose meaning is that of the",
                            "`License field from the core metadata",
                            "<https://packaging.python.org/specifications/core-metadata/#license>`_."
                        ]
                    }
                },
                "required": [
                    "text"
                ]
            }
        ]
    }
Traceback (most recent call last):
  File "[REDACTED_FILE_LOCATION]/.conda/envs/PackagingTutoral/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 389, in <module>
    main()
  File "[REDACTED_FILE_LOCATION]/.conda/envs/PackagingTutoral/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 373, in main
    json_out["return_val"] = hook(**hook_input["kwargs"])
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[REDACTED_FILE_LOCATION]/.conda/envs/PackagingTutoral/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 317, in get_requires_for_build_sdist
    return hook(config_settings)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/build_meta.py", line 337, in get_requires_for_build_sdist
    return self._get_build_requires(config_settings, requirements=[])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/build_meta.py", line 304, in _get_build_requires
    self.run_setup()
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/build_meta.py", line 320, in run_setup
    exec(code, locals())
  File "<string>", line 1, in <module>
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/__init__.py", line 117, in setup
    return distutils.core.setup(**attrs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/_distutils/core.py", line 160, in setup
    dist.parse_config_files()
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/dist.py", line 652, in parse_config_files
    pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 72, in apply_configuration
    config = read_configuration(filepath, True, ignore_option_errors, dist)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 140, in read_configuration
    validate(subset, filepath)
  File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 61, in validate
    raise ValueError(f"{error}\n{summary}") from None
ValueError: invalid pyproject.toml config: `project.license`.
configuration error: `project.license` must be valid exactly by one definition (2 matches found):

    - keys:
        'file': {type: string}
      required: ['file']
    - keys:
        'text': {type: string}
      required: ['text']


ERROR Backend subprocess exited when trying to invoke get_requires_for_build_sdist

Nordicus avatar Jan 31 '25 17:01 Nordicus

The new license-files field was recently standardized and isn't handled in a standard way across backends yet. The license field is optional, and I'd recommend not setting it for now. It's only not optional if you set license-files. The current standard is to specify the license in your Trove Classifiers, and all backends should find LICENSE.* and copy those into the SDist/wheels. In the future, once all backends implement PEP 639 (license-files and string-valued license field in METADATA 2.4) properly, then you can use those two fields instead. Things like Pip, PyPI, and twine are only just now started to support METADATA 2.4. Setuptools is still stuck on something like METADATA 2.2 IIRC.

It looks like someone was a bit forward-thinking and updated the tutorial for PEP 639. Flit and setuptools have not added support yet.

henryiii avatar Jan 31 '25 18:01 henryiii

Hello, Please review the updated page: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license The diff of changes can be seen here: https://github.com/pypa/packaging.python.org/pull/1815/files

befeleme avatar Feb 19 '25 09:02 befeleme

Hello, Please review the updated page: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license The diff of changes can be seen here: https://github.com/pypa/packaging.python.org/pull/1815/files

That page hasn't updated for me. I'd have suspected a caching issue but the PR was merged two days ago.

cdce8p avatar Feb 19 '25 12:02 cdce8p

Huh, it's true. @webknjaz - could you take a look? The cron jobs of packaging.python.org appear to have run successfully from commits containing the change.

befeleme avatar Feb 19 '25 12:02 befeleme

The current standard is to specify the license in your Trove Classifiers

@henryiii just a nit: it's not a standard but rather a convention.

webknjaz avatar Feb 19 '25 13:02 webknjaz

@befeleme apparently, the latest build got stuck: https://app.readthedocs.org/projects/python-packaging-user-guide/builds/27216162/.

FTR, GHA cron runs don't publish the docs. GHA only runs as a convenient check for whether it builds. RTD has their own build system that runs on pull_request and push events (on merge essentially).

webknjaz avatar Feb 19 '25 13:02 webknjaz

Clicked Rebuild. It should be fine now: https://app.readthedocs.org/projects/python-packaging-user-guide/builds/27241778/.

webknjaz avatar Feb 19 '25 13:02 webknjaz

That page hasn't updated for me. I'd have suspected a caching issue but the PR was merged two days ago.

Verified that the updated page has new text.

webknjaz avatar Feb 19 '25 13:02 webknjaz

Flit-core supports it now too, has for a little bit.

henryiii avatar Mar 28 '25 19:03 henryiii

The current standard is to specify the license in your Trove Classifiers

setuptools actively fails the build and asks for the license to be removed from the classifiers: Please remove: License :: OSI Approved :: Apache Software License

  File "C:\Users\Ark\AppData\Local\Temp\build-env-8fi_34mn\Lib\site-packages\setuptools\config\_apply_pyprojecttoml.py", line 61, in apply
    dist._finalize_license_expression()
  File "C:\Users\Ark\AppData\Local\Temp\build-env-8fi_34mn\Lib\site-packages\setuptools\dist.py", line 430, in _finalize_license_expression
    raise InvalidConfigError(
setuptools.errors.InvalidConfigError: License classifiers have been superseded by license expressions (see https://peps.python.org/pep-0639/). Please remove:

License :: OSI Approved :: Apache Software License

ERROR Backend subprocess exited when trying to invoke get_requires_for_build_sdist

Ark-kun avatar May 23 '25 22:05 Ark-kun

I believe this is only an error if you specify a new-style License expression (license = "..."), and doesn't trigger if you specify a classic license (license.text). If you specify an SPDX license using the new syntax you aren't supposed to put license classifiers too.

henryiii avatar May 24 '25 04:05 henryiii

I hear the recommendation to remove the license entries until the dust settles. Unfortunately, the dust will not settle until 2029 (Ubuntu 24.04 + 5 years). I propose maintaining some backward compatibility with deprecation here rather than outright rejection of what has worked in the past.

mhosken avatar Jun 20 '25 07:06 mhosken

I hear the recommendation to remove the license entries until the dust settles.

Actually for the common case I think most backends are in a good state now, and the new license field can be used in most situations.

Unfortunately, the dust will not settle until 2029 (Ubuntu 24.04 + 5 years).

The is a relatively new standard and therefore has to be implemented as a new feature in backends, if you are using a LTS distribution then you can not expect to receive and use new features or build tools that require those new features. You can either create your own virtual environment and use newer packages or use the packages Ubuntu provides for LTS. Trying to have it both ways, that is using LTS packages with new packages, won't always work, this is the trade off of LTS.

I propose maintaining some backward compatibility with deprecation here rather than outright rejection of what has worked in the past.

I don't think there's any plan to make the old license field incompatible.

First, millions of older distributions will continue to have it and need to be installable. Second, pip has not yet accepted the new license field, so it's highly unlikely the old license field will be made incompatible any time in the future.

notatallshaw avatar Jun 20 '25 18:06 notatallshaw

That pip PR is to change the format of metadata for pip itself. It does not mean pip does not support the new PEP. To the best of my understanding, pip does not validate or use these metadata fields (apart from name and version), it only displays them in pip show.

merwok avatar Jun 21 '25 14:06 merwok

Correct, as a frontend installer most metadata fields are not relevant for pip. The point I was making is if pip, a central tool to the Python packaging ecosystem, has erred on implementing the new field, it's unlikely that other tooling is going to ban the old convention, e.g. PyPI.

notatallshaw avatar Jun 21 '25 14:06 notatallshaw

I would disagree with that point!

edit: meaning pip as a python project has maintainers with their preferences, compatibility policy, availability… that means they may or may not update their metadata to latest specs, but that is not a judgment call on their approval or support of the specs for the projects that people download with pip.

merwok avatar Jun 22 '25 14:06 merwok

I would disagree with that point!

You think PyPI will implement something which means pip can't be uploaded to it over the enforcement of a PEP that there are known edge case issues with?

notatallshaw avatar Jun 22 '25 15:06 notatallshaw