summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Stephan <jstephan@baylibre.com>2023-12-04 16:59:32 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-12-06 22:55:49 +0000
commit85a2a6f68af304ace1d767e3e8367627cea898f6 (patch)
tree8e20b0b68250cf3a8cc0e69d3036d5f87b92fbe9
parent6c06fb0a4395bd98918cbcdb16e93e094e79226e (diff)
downloadpoky-85a2a6f68af304ace1d767e3e8367627cea898f6.tar.gz
recipetool: create_buildsys_python: add pypi support
Today, we can use devtool/recipetool to create recipes for python projects using the github url or the direct release tarball of the project, but the create_buildsys_python plugin doesn't support the pypi class, since we cannot know from the extracted source if the package is available on pypi or not. By implementing the new optional process_url callback, we can detect that the url is a pypi one (i.e 'https://pypi.org/project/<package>') and retrieve the release tarball location. Also detect if the url points to a release tarball hosted on "files.pythonhosted.iorg" (i.e https://files.pythonhosted.org/packages/...) In both cases, adds the pypi class, remove 'S' and 'SRC_URIxxx' variables from the created recipe as they will be handled by the pypi class and add the PYPI_PACKAGE variable This helps to produce cleaner recipes when package is hosted on pypi. If the url points to a github url or a release tarball not coming from "files.pythonhosted.org", the created recipe is the same as before. One can also use the newly added "--no-pypi" switch to NOT inherit from pypi class on matching url, to keep legacy behaviour. To create a recipe for a pypi package, one can now use one of the new following syntax (using recipetool create / devtool add): * recipetool create https://pypi.org/project/<package> * recipetool create https://pypi.org/project/<package>/<version> * recipetool create https://pypi.org/project/<package> --version <version> or the old syntax: * recipetool create https://files.pythonhosted.org/packages/<...> (From OE-Core rev: 097a43846cd99a7d74d004efc57f583ce78970a4) Signed-off-by: Julien Stephan <jstephan@baylibre.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--scripts/lib/devtool/standard.py3
-rw-r--r--scripts/lib/recipetool/create.py1
-rw-r--r--scripts/lib/recipetool/create_buildsys_python.py72
3 files changed, 76 insertions, 0 deletions
diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py
index ad6e346279..16ea328c6a 100644
--- a/scripts/lib/devtool/standard.py
+++ b/scripts/lib/devtool/standard.py
@@ -147,6 +147,8 @@ def add(args, config, basepath, workspace):
147 extracmdopts += ' -a' 147 extracmdopts += ' -a'
148 if args.npm_dev: 148 if args.npm_dev:
149 extracmdopts += ' --npm-dev' 149 extracmdopts += ' --npm-dev'
150 if args.no_pypi:
151 extracmdopts += ' --no-pypi'
150 if args.mirrors: 152 if args.mirrors:
151 extracmdopts += ' --mirrors' 153 extracmdopts += ' --mirrors'
152 if args.srcrev: 154 if args.srcrev:
@@ -2328,6 +2330,7 @@ def register_commands(subparsers, context):
2328 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true") 2330 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
2329 parser_add.add_argument('--fetch', '-f', help='Fetch the specified URI and extract it to create the source tree (deprecated - pass as positional argument instead)', metavar='URI') 2331 parser_add.add_argument('--fetch', '-f', help='Fetch the specified URI and extract it to create the source tree (deprecated - pass as positional argument instead)', metavar='URI')
2330 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true") 2332 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
2333 parser_add.add_argument('--no-pypi', help='Do not inherit pypi class', action="store_true")
2331 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)') 2334 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
2332 parser_add.add_argument('--no-git', '-g', help='If fetching source, do not set up source tree as a git repository', action="store_true") 2335 parser_add.add_argument('--no-git', '-g', help='If fetching source, do not set up source tree as a git repository', action="store_true")
2333 group = parser_add.add_mutually_exclusive_group() 2336 group = parser_add.add_mutually_exclusive_group()
diff --git a/scripts/lib/recipetool/create.py b/scripts/lib/recipetool/create.py
index 5c5ac7ae40..963aa91421 100644
--- a/scripts/lib/recipetool/create.py
+++ b/scripts/lib/recipetool/create.py
@@ -1413,6 +1413,7 @@ def register_commands(subparsers):
1413 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)') 1413 parser_create.add_argument('-B', '--srcbranch', help='Branch in source repository if fetching from an SCM such as git (default master)')
1414 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)') 1414 parser_create.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
1415 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies') 1415 parser_create.add_argument('--npm-dev', action="store_true", help='For npm, also fetch devDependencies')
1416 parser_create.add_argument('--no-pypi', action="store_true", help='Do not inherit pypi class')
1416 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS) 1417 parser_create.add_argument('--devtool', action="store_true", help=argparse.SUPPRESS)
1417 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).') 1418 parser_create.add_argument('--mirrors', action="store_true", help='Enable PREMIRRORS and MIRRORS for source tree fetching (disabled by default).')
1418 parser_create.set_defaults(func=create_recipe) 1419 parser_create.set_defaults(func=create_recipe)
diff --git a/scripts/lib/recipetool/create_buildsys_python.py b/scripts/lib/recipetool/create_buildsys_python.py
index b620e3271b..5e07222ece 100644
--- a/scripts/lib/recipetool/create_buildsys_python.py
+++ b/scripts/lib/recipetool/create_buildsys_python.py
@@ -18,7 +18,11 @@ import os
18import re 18import re
19import sys 19import sys
20import subprocess 20import subprocess
21import json
22import urllib.request
21from recipetool.create import RecipeHandler 23from recipetool.create import RecipeHandler
24from urllib.parse import urldefrag
25from recipetool.create import determine_from_url
22 26
23logger = logging.getLogger('recipetool') 27logger = logging.getLogger('recipetool')
24 28
@@ -111,6 +115,74 @@ class PythonRecipeHandler(RecipeHandler):
111 def __init__(self): 115 def __init__(self):
112 pass 116 pass
113 117
118 def process_url(self, args, classes, handled, extravalues):
119 """
120 Convert any pypi url https://pypi.org/project/<package>/<version> into https://files.pythonhosted.org/packages/source/...
121 which corresponds to the archive location, and add pypi class
122 """
123
124 if 'url' in handled:
125 return None
126
127 fetch_uri = None
128 source = args.source
129 required_version = args.version if args.version else None
130 match = re.match(r'https?://pypi.org/project/([^/]+)(?:/([^/]+))?/?$', urldefrag(source)[0])
131 if match:
132 package = match.group(1)
133 version = match.group(2) if match.group(2) else required_version
134
135 json_url = f"https://pypi.org/pypi/%s/json" % package
136 response = urllib.request.urlopen(json_url)
137 if response.status == 200:
138 data = json.loads(response.read())
139 if not version:
140 # grab latest version
141 version = data["info"]["version"]
142 pypi_package = data["info"]["name"]
143 for release in reversed(data["releases"][version]):
144 if release["packagetype"] == "sdist":
145 fetch_uri = release["url"]
146 break
147 else:
148 logger.warning("Cannot handle pypi url %s: cannot fetch package information using %s", source, json_url)
149 return None
150 else:
151 match = re.match(r'^https?://files.pythonhosted.org/packages.*/(.*)-.*$', source)
152 if match:
153 fetch_uri = source
154 pypi_package = match.group(1)
155 _, version = determine_from_url(fetch_uri)
156
157 if match and not args.no_pypi:
158 if required_version and version != required_version:
159 raise Exception("Version specified using --version/-V (%s) and version specified in the url (%s) do not match" % (required_version, version))
160 # This is optionnal if BPN looks like "python-<pypi_package>" or "python3-<pypi_package>" (see pypi.bbclass)
161 # but at this point we cannot know because because user can specify the output name of the recipe on the command line
162 extravalues["PYPI_PACKAGE"] = pypi_package
163 # If the tarball extension is not 'tar.gz' (default value in pypi.bblcass) whe should set PYPI_PACKAGE_EXT in the recipe
164 pypi_package_ext = re.match(r'.*%s-%s\.(.*)$' % (pypi_package, version), fetch_uri)
165 if pypi_package_ext:
166 pypi_package_ext = pypi_package_ext.group(1)
167 if pypi_package_ext != "tar.gz":
168 extravalues["PYPI_PACKAGE_EXT"] = pypi_package_ext
169
170 # Pypi class will handle S and SRC_URIxxx variables, so remove them
171 # TODO: allow oe.recipeutils.patch_recipe_lines() to accept regexp so we can simplify the following to:
172 # extravalues['SRC_URI(?:\[.*?\])?'] = None
173 extravalues['S'] = None
174 extravalues['SRC_URI'] = None
175 extravalues['SRC_URI[md5sum]'] = None
176 extravalues['SRC_URI[sha1sum]'] = None
177 extravalues['SRC_URI[sha256sum]'] = None
178 extravalues['SRC_URI[sha384sum]'] = None
179 extravalues['SRC_URI[sha512sum]'] = None
180
181 classes.append('pypi')
182
183 handled.append('url')
184 return fetch_uri
185
114 def handle_classifier_license(self, classifiers, existing_licenses=""): 186 def handle_classifier_license(self, classifiers, existing_licenses=""):
115 187
116 licenses = [] 188 licenses = []