All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alexandre Belloni <alexandre.belloni@bootlin.com>
To: Julien Stephan <jstephan@baylibre.com>
Cc: openembedded-core@lists.openembedded.org
Subject: Re: [OE-core] [PATCH v2 4/4] scripts:recipetool:create_buildsys_python: add PEP517 support
Date: Thu, 19 Oct 2023 20:34:10 +0200	[thread overview]
Message-ID: <202310191834102ba91f1a@mail.local> (raw)
In-Reply-To: <CAEHHSvb3xuimjwfStOW84f-L8DJNaV_53dKHK8F4fHSX_OD2FQ@mail.gmail.com>

On 19/10/2023 20:20:33+0200, Julien Stephan wrote:
> Le jeu. 19 oct. 2023 � 15:49, Alexandre Belloni
> <alexandre.belloni@bootlin.com> a �crit :
> >
> > Hello,
> >
> > On 19/10/2023 09:36:53+0200, Julien Stephan wrote:
> > > add support for PEP517 [1]
> > >
> > > if a pyproject.toml file is found, use it to create the recipe,
> > > otherwise fallback to the old setup.py method.
> > >
> > > [YOCTO #14737]
> > >
> > > [1]: https://peps.python.org/pep-0517/
> > >
> > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > ---
> > >  .../lib/recipetool/create_buildsys_python.py  | 234 +++++++++++++++++-
> > >  1 file changed, 233 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/scripts/lib/recipetool/create_buildsys_python.py b/scripts/lib/recipetool/create_buildsys_python.py
> > > index 69f6f5ca511..0b601d50a4b 100644
> > > --- a/scripts/lib/recipetool/create_buildsys_python.py
> > > +++ b/scripts/lib/recipetool/create_buildsys_python.py
> > > @@ -18,6 +18,7 @@ import os
> > >  import re
> > >  import sys
> > >  import subprocess
> > > +import toml
> >
> > This fails on the autobuilders because we don't have the toml module installed so I guess you need to add a dependency.
> >
> 
> Hello,
> 
> Sure I 'll do it. Just to confirm, I should add it here:
> https://docs.yoctoproject.org/ref-manual/system-requirements.html#required-packages-for-the-build-host
> ?

I guess the preferred way would be to depend on python3-toml-native
instead of requiring installation on the host.

> 
> Cheers
> Julien
> 
> > >  from recipetool.create import RecipeHandler
> > >
> > >  logger = logging.getLogger('recipetool')
> > > @@ -656,6 +657,235 @@ class PythonSetupPyRecipeHandler(PythonRecipeHandler):
> > >
> > >          handled.append('buildsystem')
> > >
> > > +class PythonPyprojectTomlRecipeHandler(PythonRecipeHandler):
> > > +    """Base class to support PEP517 and PEP518
> > > +
> > > +    PEP517 https://peps.python.org/pep-0517/#source-trees
> > > +    PEP518 https://peps.python.org/pep-0518/#build-system-table
> > > +    """
> > > +
> > > +    # PEP621: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
> > > +    # add only the ones that map to a BB var
> > > +    # potentially missing: optional-dependencies
> > > +    bbvar_map = {
> > > +        "name": "PN",
> > > +        "version": "PV",
> > > +        "Homepage": "HOMEPAGE",
> > > +        "description": "SUMMARY",
> > > +        "license": "LICENSE",
> > > +        "dependencies": "RDEPENDS:${PN}",
> > > +        "requires": "DEPENDS",
> > > +    }
> > > +
> > > +    replacements = [
> > > +        ("license", r" +$", ""),
> > > +        ("license", r"^ +", ""),
> > > +        ("license", r" ", "-"),
> > > +        ("license", r"^GNU-", ""),
> > > +        ("license", r"-[Ll]icen[cs]e(,?-[Vv]ersion)?", ""),
> > > +        ("license", r"^UNKNOWN$", ""),
> > > +        # Remove currently unhandled version numbers from these variables
> > > +        ("requires", r"\[[^\]]+\]$", ""),
> > > +        ("requires", r"^([^><= ]+).*", r"\1"),
> > > +        ("dependencies", r"\[[^\]]+\]$", ""),
> > > +        ("dependencies", r"^([^><= ]+).*", r"\1"),
> > > +    ]
> > > +
> > > +    build_backend_map = {
> > > +        "setuptools.build_meta": "python_setuptools_build_meta",
> > > +        "poetry.core.masonry.api": "python_poetry_core",
> > > +        "flit_core.buildapi": "python_flit_core",
> > > +    }
> > > +
> > > +    excluded_native_pkgdeps = [
> > > +        # already provided by python_setuptools_build_meta.bbclass
> > > +        "python3-setuptools-native",
> > > +        "python3-wheel-native",
> > > +        # already provided by python_poetry_core.bbclass
> > > +        "python3-poetry-core-native",
> > > +        # already provided by python_flit_core.bbclass
> > > +        "python3-flit-core-native",
> > > +    ]
> > > +
> > > +    # add here a list of known and often used packages and the corresponding bitbake package
> > > +    known_deps_map = {
> > > +        "setuptools": "python3-setuptools",
> > > +        "wheel": "python3-wheel",
> > > +        "poetry-core": "python3-poetry-core",
> > > +        "flit_core": "python3-flit-core",
> > > +        "setuptools-scm": "python3-setuptools-scm",
> > > +    }
> > > +
> > > +    def __init__(self):
> > > +        pass
> > > +
> > > +    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
> > > +        info = {}
> > > +
> > > +        if 'buildsystem' in handled:
> > > +            return False
> > > +
> > > +        # Check for non-zero size setup.py files
> > > +        setupfiles = RecipeHandler.checkfiles(srctree, ["pyproject.toml"])
> > > +        for fn in setupfiles:
> > > +            if os.path.getsize(fn):
> > > +                break
> > > +        else:
> > > +            return False
> > > +
> > > +        setupscript = os.path.join(srctree, "pyproject.toml")
> > > +
> > > +        try:
> > > +            config = self.parse_pyproject_toml(setupscript)
> > > +            build_backend = config["build-system"]["build-backend"]
> > > +            if build_backend in self.build_backend_map:
> > > +                classes.append(self.build_backend_map[build_backend])
> > > +            else:
> > > +                logger.error(
> > > +                    "Unsupported build-backend: %s, cannot use pyproject.toml. Will try to use legacy setup.py"
> > > +                    % build_backend
> > > +                )
> > > +                return False
> > > +
> > > +            licfile = ""
> > > +            if "project" in config:
> > > +                for field, values in config["project"].items():
> > > +                    if field == "license":
> > > +                        value = values.get("text", "")
> > > +                        if not value:
> > > +                            licfile = values.get("file", "")
> > > +                    elif isinstance(values, dict):
> > > +                        for k, v in values.items():
> > > +                            info[k] = v
> > > +                        continue
> > > +                    else:
> > > +                        value = values
> > > +
> > > +                    info[field] = value
> > > +
> > > +            # Grab the license value before applying replacements
> > > +            license_str = info.get("license", "").strip()
> > > +
> > > +            if license_str:
> > > +                for i, line in enumerate(lines_before):
> > > +                    if line.startswith("##LICENSE_PLACEHOLDER##"):
> > > +                        lines_before.insert(
> > > +                            i, "# NOTE: License in pyproject.toml is: %s" % license_str
> > > +                        )
> > > +                        break
> > > +
> > > +            info["requires"] = config["build-system"]["requires"]
> > > +
> > > +            self.apply_info_replacements(info)
> > > +
> > > +            if "classifiers" in info:
> > > +                license = self.handle_classifier_license(
> > > +                    info["classifiers"], info.get("license", "")
> > > +                )
> > > +                if license:
> > > +                    if licfile:
> > > +                        lines = []
> > > +                        md5value = bb.utils.md5_file(os.path.join(srctree, licfile))
> > > +                        lines.append('LICENSE = "%s"' % license)
> > > +                        lines.append(
> > > +                            'LIC_FILES_CHKSUM = "file://%s;md5=%s"'
> > > +                            % (licfile, md5value)
> > > +                        )
> > > +                        lines.append("")
> > > +
> > > +                        # Replace the placeholder so we get the values in the right place in the recipe file
> > > +                        try:
> > > +                            pos = lines_before.index("##LICENSE_PLACEHOLDER##")
> > > +                        except ValueError:
> > > +                            pos = -1
> > > +                        if pos == -1:
> > > +                            lines_before.extend(lines)
> > > +                        else:
> > > +                            lines_before[pos : pos + 1] = lines
> > > +
> > > +                        handled.append(("license", [license, licfile, md5value]))
> > > +                    else:
> > > +                        info["license"] = license
> > > +
> > > +            provided_packages = self.parse_pkgdata_for_python_packages()
> > > +            provided_packages.update(self.known_deps_map)
> > > +            native_mapped_deps, native_unmapped_deps = set(), set()
> > > +            mapped_deps, unmapped_deps = set(), set()
> > > +
> > > +            if "requires" in info:
> > > +                for require in info["requires"]:
> > > +                    mapped = provided_packages.get(require)
> > > +
> > > +                    if mapped:
> > > +                        logger.error("Mapped %s to %s" % (require, mapped))
> > > +                        native_mapped_deps.add(mapped)
> > > +                    else:
> > > +                        logger.error("Could not map %s" % require)
> > > +                        native_unmapped_deps.add(require)
> > > +
> > > +                info.pop("requires")
> > > +
> > > +                if native_mapped_deps != set():
> > > +                    native_mapped_deps = {
> > > +                        item + "-native" for item in native_mapped_deps
> > > +                    }
> > > +                    native_mapped_deps -= set(self.excluded_native_pkgdeps)
> > > +                    if native_mapped_deps != set():
> > > +                        info["requires"] = " ".join(sorted(native_mapped_deps))
> > > +
> > > +                if native_unmapped_deps:
> > > +                    lines_after.append("")
> > > +                    lines_after.append(
> > > +                        "# WARNING: We were unable to map the following python package/module"
> > > +                    )
> > > +                    lines_after.append(
> > > +                        "# dependencies to the bitbake packages which include them:"
> > > +                    )
> > > +                    lines_after.extend(
> > > +                        "#    {}".format(d) for d in sorted(native_unmapped_deps)
> > > +                    )
> > > +
> > > +            if "dependencies" in info:
> > > +                for dependency in info["dependencies"]:
> > > +                    mapped = provided_packages.get(dependency)
> > > +                    if mapped:
> > > +                        logger.error("Mapped %s to %s" % (dependency, mapped))
> > > +                        mapped_deps.add(mapped)
> > > +                    else:
> > > +                        logger.error("Could not map %s" % dependency)
> > > +                        unmapped_deps.add(dependency)
> > > +
> > > +                info.pop("dependencies")
> > > +
> > > +                if mapped_deps != set():
> > > +                    if mapped_deps != set():
> > > +                        info["dependencies"] = " ".join(sorted(mapped_deps))
> > > +
> > > +                if unmapped_deps:
> > > +                    lines_after.append("")
> > > +                    lines_after.append(
> > > +                        "# WARNING: We were unable to map the following python package/module"
> > > +                    )
> > > +                    lines_after.append(
> > > +                        "# runtime dependencies to the bitbake packages which include them:"
> > > +                    )
> > > +                    lines_after.extend(
> > > +                        "#    {}".format(d) for d in sorted(unmapped_deps)
> > > +                    )
> > > +
> > > +            self.map_info_to_bbvar(info, extravalues)
> > > +
> > > +            handled.append("buildsystem")
> > > +        except Exception:
> > > +            logger.exception("Failed to parse pyproject.toml")
> > > +            return False
> > > +
> > > +    def parse_pyproject_toml(self, setupscript):
> > > +        with open(setupscript, "r") as f:
> > > +            config = toml.load(f)
> > > +        return config
> > > +
> > > +
> > >  def gather_setup_info(fileobj):
> > >      parsed = ast.parse(fileobj.read(), fileobj.name)
> > >      visitor = SetupScriptVisitor()
> > > @@ -769,5 +999,7 @@ def has_non_literals(value):
> > >
> > >
> > >  def register_recipe_handlers(handlers):
> > > -    # We need to make sure this is ahead of the makefile fallback handler
> > > +    # We need to make sure these are ahead of the makefile fallback handler
> > > +    # and the pyproject.toml handler ahead of the setup.py handler
> > > +    handlers.append((PythonPyprojectTomlRecipeHandler(), 75))
> > >      handlers.append((PythonSetupPyRecipeHandler(), 70))
> > > --
> > > 2.42.0
> > >
> >
> > >
> > > -=-=-=-=-=-=-=-=-=-=-=-
> > > Links: You receive all messages sent to this group.
> > > View/Reply Online (#189431): https://lists.openembedded.org/g/openembedded-core/message/189431
> > > Mute This Topic: https://lists.openembedded.org/mt/102055999/3617179
> > > Group Owner: openembedded-core+owner@lists.openembedded.org
> > > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [alexandre.belloni@bootlin.com]
> > > -=-=-=-=-=-=-=-=-=-=-=-
> > >
> >
> >
> > --
> > Alexandre Belloni, co-owner and COO, Bootlin
> > Embedded Linux and Kernel engineering
> > https://bootlin.com

-- 
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


  reply	other threads:[~2023-10-19 18:34 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-10-19  7:36 [PATCH v2 1/4] scripts:recipetool:create_buildsys_python: fix license note Julien Stephan
2023-10-19  7:36 ` [PATCH v2 2/4] scripts:recipetool:create_buildsys_python: prefix created recipes with python3- Julien Stephan
2023-10-19  7:36 ` [PATCH v2 3/4] scripts:recipetool:create_buildsys_python: refactor code for futur PEP517 addition Julien Stephan
2023-10-20  6:01   ` [OE-core] " Alexandre Belloni
2023-10-20 10:33     ` Julien Stephan
2023-10-19  7:36 ` [PATCH v2 4/4] scripts:recipetool:create_buildsys_python: add PEP517 support Julien Stephan
2023-10-19 13:49   ` [OE-core] " Alexandre Belloni
2023-10-19 14:16     ` Tim Orling
2023-10-19 18:20     ` Julien Stephan
2023-10-19 18:34       ` Alexandre Belloni [this message]
2023-10-20 12:57         ` Julien Stephan
2023-10-20 14:04           ` Richard Purdie
2023-10-20 14:49             ` Julien Stephan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=202310191834102ba91f1a@mail.local \
    --to=alexandre.belloni@bootlin.com \
    --cc=jstephan@baylibre.com \
    --cc=openembedded-core@lists.openembedded.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.