diff mbox series

recipetool: support PEP639-variant of license key in pyproject.toml

Message ID 20251203-devtool-pep-0639-v1-1-c39997c88996@cherry.de
State New
Headers show
Series recipetool: support PEP639-variant of license key in pyproject.toml | expand

Commit Message

Quentin Schulz Dec. 3, 2025, 11:24 a.m. UTC
From: Quentin Schulz <quentin.schulz@cherry.de>

Python modules using PEP639-variant of the license key[1] are currently
returning Unknown as LICENSE when using recipetool create on them
because we try to parse the key as a dict but it is now an SPDX license
expression.

This adds support for PEP639-variant of the license key, though it does
not handle the newly added license-files key[2] as I couldn't find a
dual-licensed recipe which uses that mechanism yet.

[1] https://peps.python.org/pep-0639/#add-string-value-to-license-key
[2] https://peps.python.org/pep-0639/#add-license-files-key

Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
---
 meta/lib/oeqa/selftest/cases/recipetool.py       | 45 ++++++++++++++++++++++++
 scripts/lib/recipetool/create_buildsys_python.py | 12 +++++--
 2 files changed, 54 insertions(+), 3 deletions(-)


---
base-commit: ac7327583aef83fd400190506f3147623de38b68
change-id: 20251202-devtool-pep-0639-1484448d8d05

Best regards,
diff mbox series

Patch

diff --git a/meta/lib/oeqa/selftest/cases/recipetool.py b/meta/lib/oeqa/selftest/cases/recipetool.py
index 0bd724c8ee..e23055ded1 100644
--- a/meta/lib/oeqa/selftest/cases/recipetool.py
+++ b/meta/lib/oeqa/selftest/cases/recipetool.py
@@ -595,6 +595,51 @@  class RecipetoolCreateTests(RecipetoolBase):
 
         self._test_recipe_contents(recipefile, checkvars, inherits)
 
+    def test_recipetool_create_python3_pep517_setuptools_build_meta_license_prepep639(self):
+        # Test pre-PEP639 where the license field is a table
+        # This test require python 3.11 or above for the tomllib module or tomli module to be installed
+        needTomllib(self)
+
+        # Test creating python3 package from tarball (using setuptools.build_meta class)
+        temprecipe = os.path.join(self.tempdir, 'recipe')
+        os.makedirs(temprecipe)
+        pv = '1.37.1'
+        pn = 'yamllint'
+        recipefile = os.path.join(temprecipe, 'python3-%s_%s.bb' % (pn, pv))
+        srcuri = 'https://files.pythonhosted.org/packages/46/f2/cd8b7584a48ee83f0bc94f8a32fea38734cefcdc6f7324c4d3bfc699457b//%s-%s.tar.gz' % (pn, pv)
+        result = runCmd('recipetool create -o %s %s' % (temprecipe, srcuri))
+        self.assertTrue(os.path.isfile(recipefile))
+        checkvars = {}
+        checkvars['SUMMARY'] = 'A linter for YAML files.'
+        checkvars['LICENSE'] = set(['GPL-3.0-only'])
+        checkvars['LIC_FILES_CHKSUM'] = 'file://LICENSE;md5=1ebbd3e34237af26da5dc08a4e440464'
+        checkvars['SRC_URI[sha256sum]'] = '81f7c0c5559becc8049470d86046b36e96113637bcbe4753ecef06977c00245d'
+        inherits = ['python_setuptools_build_meta', 'pypi']
+
+        self._test_recipe_contents(recipefile, checkvars, inherits)
+
+    def test_recipetool_create_python3_pep517_setuptools_build_meta_license_pep639(self):
+        # Test PEP639 where the license field is an SPDX license expression string
+        # This test require python 3.11 or above for the tomllib module or tomli module to be installed
+        needTomllib(self)
+
+        # Test creating python3 package from tarball (using setuptools.build_meta class)
+        temprecipe = os.path.join(self.tempdir, 'recipe')
+        os.makedirs(temprecipe)
+        pv = '2.0.0'
+        recipefile = os.path.join(temprecipe, 'python3-sphinxcontrib-svg2pdfconverter_%s.bb' % pv)
+        srcuri = 'https://files.pythonhosted.org/packages/21/7a/21930ae148f94c458ca2f8f554d4e737e17c1db8b152238f530fc0c89e32/sphinxcontrib_svg2pdfconverter-%s.tar.gz' % pv
+        result = runCmd('recipetool create -o %s %s' % (temprecipe, srcuri))
+        self.assertTrue(os.path.isfile(recipefile))
+        checkvars = {}
+        checkvars['SUMMARY'] = 'Sphinx SVG to PDF or PNG converter extension'
+        checkvars['LICENSE'] = set(['BSD-2-Clause'])
+        checkvars['LIC_FILES_CHKSUM'] = 'file://LICENSE.txt;md5=b11cf936853a71258d4b57bb1849a3f9'
+        checkvars['SRC_URI[sha256sum]'] = 'ab9c8f1080391e231812d20abf2657a69ee35574563b1014414f953964a95fa3'
+        inherits = ['python_setuptools_build_meta', 'pypi']
+
+        self._test_recipe_contents(recipefile, checkvars, inherits)
+
     def test_recipetool_create_python3_pep517_poetry_core_masonry_api(self):
         # This test require python 3.11 or above for the tomllib module or tomli module to be installed
         needTomllib(self)
diff --git a/scripts/lib/recipetool/create_buildsys_python.py b/scripts/lib/recipetool/create_buildsys_python.py
index a807dafae5..61979c842c 100644
--- a/scripts/lib/recipetool/create_buildsys_python.py
+++ b/scripts/lib/recipetool/create_buildsys_python.py
@@ -858,10 +858,16 @@  class PythonPyprojectTomlRecipeHandler(PythonRecipeHandler):
             if metadata:
                 for field, values in metadata.items():
                     if field == "license":
-                        # For setuptools.build_meta and flit, licence is a table
-                        # but for poetry licence is a string
+                        # for flit, licence is a table
+                        # for setuptools.build_meta, license is either:
+                        # - a table, pre-PEP639,
+                        # - an SPDX license expression as string, since PEP639
+                        # for poetry licence is a string
                         # for hatchling, both table (jsonschema) and string (iniconfig) have been used
-                        if build_backend == "poetry.core.masonry.api":
+                        # TODO: support license-files array of glob paths since PEP639
+                        if build_backend == "poetry.core.masonry.api" or \
+                                (build_backend == "setuptools.build_meta" and
+                                 isinstance(values, str)):
                             value = values
                         else:
                             value = values.get("text", "")