* [Buildroot] [PATCH v2 1/1] utils/scanpypi: refactor setuptools handling to not use imp @ 2023-11-30 21:21 James Hilliard 2024-02-07 19:22 ` Thomas Petazzoni via buildroot 0 siblings, 1 reply; 3+ messages in thread From: James Hilliard @ 2023-11-30 21:21 UTC (permalink / raw) To: buildroot; +Cc: James Hilliard The imp module is deprecated as of python verison 3.12. Refactor setuptools handling to remove monkeypatching hack and instead do pep517 metadata generation and dependency resolution. Invert setuptools/pyproject fallback ordering so that we try parsing pyproject.toml files first. Signed-off-by: James Hilliard <james.hilliard1@gmail.com> --- Changes v1 -> v2: - split out set comprehension changes --- utils/scanpypi | 88 +++++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 59 deletions(-) diff --git a/utils/scanpypi b/utils/scanpypi index 021c99a172..9de1b5d402 100755 --- a/utils/scanpypi +++ b/utils/scanpypi @@ -18,8 +18,8 @@ import hashlib import re import textwrap import tempfile -import imp -from functools import wraps +import importlib +from setuptools.build_meta import prepare_metadata_for_build_wheel import six.moves.urllib.request import six.moves.urllib.error import six.moves.urllib.parse @@ -93,32 +93,6 @@ def toml_load(f): raise ex -def setup_decorator(func, method): - """ - Decorator for distutils.core.setup and setuptools.setup. - Puts the arguments with which setup is called as a dict - Add key 'method' which should be either 'setuptools' or 'distutils'. - - Keyword arguments: - func -- either setuptools.setup or distutils.core.setup - method -- either 'setuptools' or 'distutils' - """ - - @wraps(func) - def closure(*args, **kwargs): - # Any python packages calls its setup function to be installed. - # Argument 'name' of this setup function is the package's name - BuildrootPackage.setup_args[kwargs['name']] = kwargs - BuildrootPackage.setup_args[kwargs['name']]['method'] = method - return closure - -# monkey patch -import setuptools # noqa E402 -setuptools.setup = setup_decorator(setuptools.setup, 'setuptools') -import distutils # noqa E402 -distutils.core.setup = setup_decorator(setuptools.setup, 'distutils') - - def find_file_upper_case(filenames, path='./'): """ List generator: @@ -345,26 +319,18 @@ class BuildrootPackage(): """ current_dir = os.getcwd() os.chdir(self.tmp_extract) - sys.path.insert(0, self.tmp_extract) try: - s_file, s_path, s_desc = imp.find_module('setup', [self.tmp_extract]) - imp.load_module('__main__', s_file, s_path, s_desc) - if self.metadata_name in self.setup_args: - pass - elif self.metadata_name.replace('_', '-') in self.setup_args: - self.metadata_name = self.metadata_name.replace('_', '-') - elif self.metadata_name.replace('-', '_') in self.setup_args: - self.metadata_name = self.metadata_name.replace('-', '_') + metadata = prepare_metadata_for_build_wheel(self.tmp_extract) try: - self.setup_metadata = self.setup_args[self.metadata_name] - except KeyError: - # This means setup was not called - print('ERROR: Could not determine package metadata for {pkg}.\n' - .format(pkg=self.real_name)) - raise + dist = importlib.metadata.Distribution.at(metadata) + self.metadata_name = dist.name + self.setup_metadata = {'method': 'setuptools'} + if dist.requires: + self.setup_metadata['install_requires'] = dist.requires + finally: + shutil.rmtree(metadata) finally: os.chdir(current_dir) - sys.path.remove(self.tmp_extract) def load_pyproject(self): """ @@ -372,7 +338,6 @@ class BuildrootPackage(): """ current_dir = os.getcwd() os.chdir(self.tmp_extract) - sys.path.insert(0, self.tmp_extract) try: pyproject_data = toml_load('pyproject.toml') try: @@ -380,20 +345,25 @@ class BuildrootPackage(): self.metadata_name = self.setup_metadata.get('name', self.real_name) build_system = pyproject_data.get('build-system', {}) build_backend = build_system.get('build-backend', None) - if build_backend and build_backend == 'flit_core.buildapi': - self.setup_metadata['method'] = 'flit' + if build_backend: + if build_backend == 'flit_core.buildapi': + self.setup_metadata['method'] = 'flit' + elif build_backend == 'setuptools.build_meta': + raise Exception("handle setuptools") + else: + self.setup_metadata['method'] = 'unknown' elif build_system.get('backend-path', None): self.setup_metadata['method'] = 'pep517' else: - self.setup_metadata['method'] = 'unknown' + raise Exception("handle setuptools") except KeyError: print('ERROR: Could not determine package metadata for {pkg}.\n' .format(pkg=self.real_name)) raise except FileNotFoundError: raise - os.chdir(current_dir) - sys.path.remove(self.tmp_extract) + finally: + os.chdir(current_dir) def get_requirements(self, pkg_folder): """ @@ -778,15 +748,15 @@ def main(): # Loading the package install info from the package try: - package.load_setup() - except ImportError as err: - if 'buildutils' in str(err): - print('This package needs buildutils') - continue - else: - try: - package.load_pyproject() - except Exception: + package.load_pyproject() + except Exception: + try: + package.load_setup() + except ImportError as err: + if 'buildutils' in str(err): + print('This package needs buildutils') + continue + else: raise except (AttributeError, KeyError) as error: print('Error: Could not install package {pkg}: {error}'.format( -- 2.34.1 _______________________________________________ buildroot mailing list buildroot@buildroot.org https://lists.buildroot.org/mailman/listinfo/buildroot ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [Buildroot] [PATCH v2 1/1] utils/scanpypi: refactor setuptools handling to not use imp 2023-11-30 21:21 [Buildroot] [PATCH v2 1/1] utils/scanpypi: refactor setuptools handling to not use imp James Hilliard @ 2024-02-07 19:22 ` Thomas Petazzoni via buildroot 2024-05-09 22:28 ` James Hilliard 0 siblings, 1 reply; 3+ messages in thread From: Thomas Petazzoni via buildroot @ 2024-02-07 19:22 UTC (permalink / raw) To: James Hilliard; +Cc: buildroot Hello James, On Thu, 30 Nov 2023 14:21:53 -0700 James Hilliard <james.hilliard1@gmail.com> wrote: > The imp module is deprecated as of python verison 3.12. > > Refactor setuptools handling to remove monkeypatching hack and > instead do pep517 metadata generation and dependency resolution. How does it work without monkeypatching? Why was monkeypatching needed before and no longer needed now? > Invert setuptools/pyproject fallback ordering so that we try > parsing pyproject.toml files first. What is the motivation for this particular change (inverting the order between setuptools and pyproject)? Also, this patch should be split into several ones, as you're really doing several independent things, as far as I can see. > diff --git a/utils/scanpypi b/utils/scanpypi > index 021c99a172..9de1b5d402 100755 > --- a/utils/scanpypi > +++ b/utils/scanpypi > @@ -18,8 +18,8 @@ import hashlib > import re > import textwrap > import tempfile > -import imp > -from functools import wraps > +import importlib > +from setuptools.build_meta import prepare_metadata_for_build_wheel This is part of the "don't use imp / remove monkeypatching" thing if I understand correctly. > import six.moves.urllib.request > import six.moves.urllib.error > import six.moves.urllib.parse > @@ -93,32 +93,6 @@ def toml_load(f): > raise ex > > > -def setup_decorator(func, method): > - """ > - Decorator for distutils.core.setup and setuptools.setup. > - Puts the arguments with which setup is called as a dict > - Add key 'method' which should be either 'setuptools' or 'distutils'. > - > - Keyword arguments: > - func -- either setuptools.setup or distutils.core.setup > - method -- either 'setuptools' or 'distutils' > - """ > - > - @wraps(func) > - def closure(*args, **kwargs): > - # Any python packages calls its setup function to be installed. > - # Argument 'name' of this setup function is the package's name > - BuildrootPackage.setup_args[kwargs['name']] = kwargs > - BuildrootPackage.setup_args[kwargs['name']]['method'] = method > - return closure > - > -# monkey patch > -import setuptools # noqa E402 > -setuptools.setup = setup_decorator(setuptools.setup, 'setuptools') > -import distutils # noqa E402 > -distutils.core.setup = setup_decorator(setuptools.setup, 'distutils') This is part of the "don't use imp / remove monkeypatching" thing if I understand correctly. > def find_file_upper_case(filenames, path='./'): > """ > List generator: > @@ -345,26 +319,18 @@ class BuildrootPackage(): > """ > current_dir = os.getcwd() > os.chdir(self.tmp_extract) > - sys.path.insert(0, self.tmp_extract) Not sure to what this is related? The monkeypatching as well? The use of imp? > try: > - s_file, s_path, s_desc = imp.find_module('setup', [self.tmp_extract]) > - imp.load_module('__main__', s_file, s_path, s_desc) > - if self.metadata_name in self.setup_args: > - pass > - elif self.metadata_name.replace('_', '-') in self.setup_args: > - self.metadata_name = self.metadata_name.replace('_', '-') > - elif self.metadata_name.replace('-', '_') in self.setup_args: > - self.metadata_name = self.metadata_name.replace('-', '_') > + metadata = prepare_metadata_for_build_wheel(self.tmp_extract) > try: > - self.setup_metadata = self.setup_args[self.metadata_name] > - except KeyError: > - # This means setup was not called > - print('ERROR: Could not determine package metadata for {pkg}.\n' > - .format(pkg=self.real_name)) > - raise > + dist = importlib.metadata.Distribution.at(metadata) > + self.metadata_name = dist.name > + self.setup_metadata = {'method': 'setuptools'} > + if dist.requires: > + self.setup_metadata['install_requires'] = dist.requires > + finally: > + shutil.rmtree(metadata) This is part of the "don't use imp / remove monkeypatching" thing if I understand correctly. > finally: > os.chdir(current_dir) > - sys.path.remove(self.tmp_extract) > > def load_pyproject(self): > """ > @@ -372,7 +338,6 @@ class BuildrootPackage(): > """ > current_dir = os.getcwd() > os.chdir(self.tmp_extract) > - sys.path.insert(0, self.tmp_extract) > try: > pyproject_data = toml_load('pyproject.toml') > try: > @@ -380,20 +345,25 @@ class BuildrootPackage(): > self.metadata_name = self.setup_metadata.get('name', self.real_name) > build_system = pyproject_data.get('build-system', {}) > build_backend = build_system.get('build-backend', None) > - if build_backend and build_backend == 'flit_core.buildapi': > - self.setup_metadata['method'] = 'flit' > + if build_backend: > + if build_backend == 'flit_core.buildapi': > + self.setup_metadata['method'] = 'flit' > + elif build_backend == 'setuptools.build_meta': > + raise Exception("handle setuptools") > + else: > + self.setup_metadata['method'] = 'unknown' This seems more related to PEP517 handling improvements? > elif build_system.get('backend-path', None): > self.setup_metadata['method'] = 'pep517' > else: > - self.setup_metadata['method'] = 'unknown' > + raise Exception("handle setuptools") So we don't support setuptools as a PEP517 backend? I guess it was already the case before your patch? > except KeyError: > print('ERROR: Could not determine package metadata for {pkg}.\n' > .format(pkg=self.real_name)) > raise > except FileNotFoundError: > raise > - os.chdir(current_dir) > - sys.path.remove(self.tmp_extract) Related to what? > + finally: > + os.chdir(current_dir) > > def get_requirements(self, pkg_folder): > """ > @@ -778,15 +748,15 @@ def main(): > > # Loading the package install info from the package > try: > - package.load_setup() > - except ImportError as err: > - if 'buildutils' in str(err): > - print('This package needs buildutils') > - continue > - else: > - try: > - package.load_pyproject() > - except Exception: > + package.load_pyproject() > + except Exception: > + try: > + package.load_setup() > + except ImportError as err: > + if 'buildutils' in str(err): > + print('This package needs buildutils') > + continue > + else: This is a separate change, about the ordering between .load_setup() and .load_pyproject(). Could you split this useful patch into smaller chunks, with a better justification for each chunk? Believe me, if you do this, patches like this will be merged, much, much, much faster. Thanks a lot for your contribution! Thomas -- Thomas Petazzoni, co-owner and CEO, Bootlin Embedded Linux and Kernel engineering and training https://bootlin.com _______________________________________________ buildroot mailing list buildroot@buildroot.org https://lists.buildroot.org/mailman/listinfo/buildroot ^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [Buildroot] [PATCH v2 1/1] utils/scanpypi: refactor setuptools handling to not use imp 2024-02-07 19:22 ` Thomas Petazzoni via buildroot @ 2024-05-09 22:28 ` James Hilliard 0 siblings, 0 replies; 3+ messages in thread From: James Hilliard @ 2024-05-09 22:28 UTC (permalink / raw) To: Thomas Petazzoni; +Cc: buildroot On Wed, Feb 7, 2024 at 12:22 PM Thomas Petazzoni <thomas.petazzoni@bootlin.com> wrote: > > Hello James, > > On Thu, 30 Nov 2023 14:21:53 -0700 > James Hilliard <james.hilliard1@gmail.com> wrote: > > > The imp module is deprecated as of python verison 3.12. > > > > Refactor setuptools handling to remove monkeypatching hack and > > instead do pep517 metadata generation and dependency resolution. > > How does it work without monkeypatching? Why was monkeypatching needed > before and no longer needed now? This is due to pep517 having a standardized way of retrieving metadata, the monkeypatching was a hack to deal with setuptools/distutils not providing a proper method for getting metadata. > > > Invert setuptools/pyproject fallback ordering so that we try > > parsing pyproject.toml files first. > > What is the motivation for this particular change (inverting the order > between setuptools and pyproject)? We want this to follow the same order of preference as our build infrastructure, which now uses pep517 for setuptools based packages and thus pyproject.toml is checked first. > > Also, this patch should be split into several ones, as you're really > doing several independent things, as far as I can see. It's all kind of intertwined and hard to split out. > > > diff --git a/utils/scanpypi b/utils/scanpypi > > index 021c99a172..9de1b5d402 100755 > > --- a/utils/scanpypi > > +++ b/utils/scanpypi > > @@ -18,8 +18,8 @@ import hashlib > > import re > > import textwrap > > import tempfile > > -import imp > > -from functools import wraps > > +import importlib > > +from setuptools.build_meta import prepare_metadata_for_build_wheel > > This is part of the "don't use imp / remove monkeypatching" thing if I > understand correctly. Yes, this is related to replacing the monkeypatching. > > > import six.moves.urllib.request > > import six.moves.urllib.error > > import six.moves.urllib.parse > > @@ -93,32 +93,6 @@ def toml_load(f): > > raise ex > > > > > > -def setup_decorator(func, method): > > - """ > > - Decorator for distutils.core.setup and setuptools.setup. > > - Puts the arguments with which setup is called as a dict > > - Add key 'method' which should be either 'setuptools' or 'distutils'. > > - > > - Keyword arguments: > > - func -- either setuptools.setup or distutils.core.setup > > - method -- either 'setuptools' or 'distutils' > > - """ > > - > > - @wraps(func) > > - def closure(*args, **kwargs): > > - # Any python packages calls its setup function to be installed. > > - # Argument 'name' of this setup function is the package's name > > - BuildrootPackage.setup_args[kwargs['name']] = kwargs > > - BuildrootPackage.setup_args[kwargs['name']]['method'] = method > > - return closure > > - > > -# monkey patch > > -import setuptools # noqa E402 > > -setuptools.setup = setup_decorator(setuptools.setup, 'setuptools') > > -import distutils # noqa E402 > > -distutils.core.setup = setup_decorator(setuptools.setup, 'distutils') > > This is part of the "don't use imp / remove monkeypatching" thing if I > understand correctly. Yes. > > > def find_file_upper_case(filenames, path='./'): > > """ > > List generator: > > @@ -345,26 +319,18 @@ class BuildrootPackage(): > > """ > > current_dir = os.getcwd() > > os.chdir(self.tmp_extract) > > - sys.path.insert(0, self.tmp_extract) > > Not sure to what this is related? The monkeypatching as well? The use > of imp? Yeah, so this is being removed due to it being used by the monkeypatching hack. > > > try: > > - s_file, s_path, s_desc = imp.find_module('setup', [self.tmp_extract]) > > - imp.load_module('__main__', s_file, s_path, s_desc) > > - if self.metadata_name in self.setup_args: > > - pass > > - elif self.metadata_name.replace('_', '-') in self.setup_args: > > - self.metadata_name = self.metadata_name.replace('_', '-') > > - elif self.metadata_name.replace('-', '_') in self.setup_args: > > - self.metadata_name = self.metadata_name.replace('-', '_') > > + metadata = prepare_metadata_for_build_wheel(self.tmp_extract) > > try: > > - self.setup_metadata = self.setup_args[self.metadata_name] > > - except KeyError: > > - # This means setup was not called > > - print('ERROR: Could not determine package metadata for {pkg}.\n' > > - .format(pkg=self.real_name)) > > - raise > > + dist = importlib.metadata.Distribution.at(metadata) > > + self.metadata_name = dist.name > > + self.setup_metadata = {'method': 'setuptools'} > > + if dist.requires: > > + self.setup_metadata['install_requires'] = dist.requires > > + finally: > > + shutil.rmtree(metadata) > > This is part of the "don't use imp / remove monkeypatching" thing if I > understand correctly. Yep, this is also part of replacing the monkeypatching. > > > finally: > > os.chdir(current_dir) > > - sys.path.remove(self.tmp_extract) > > > > def load_pyproject(self): > > """ > > @@ -372,7 +338,6 @@ class BuildrootPackage(): > > """ > > current_dir = os.getcwd() > > os.chdir(self.tmp_extract) > > - sys.path.insert(0, self.tmp_extract) > > try: > > pyproject_data = toml_load('pyproject.toml') > > try: > > @@ -380,20 +345,25 @@ class BuildrootPackage(): > > self.metadata_name = self.setup_metadata.get('name', self.real_name) > > build_system = pyproject_data.get('build-system', {}) > > build_backend = build_system.get('build-backend', None) > > - if build_backend and build_backend == 'flit_core.buildapi': > > - self.setup_metadata['method'] = 'flit' > > + if build_backend: > > + if build_backend == 'flit_core.buildapi': > > + self.setup_metadata['method'] = 'flit' > > + elif build_backend == 'setuptools.build_meta': > > + raise Exception("handle setuptools") > > + else: > > + self.setup_metadata['method'] = 'unknown' > > This seems more related to PEP517 handling improvements? Yes, but it's related to removing the monkeypatching still in that otherwise stuff will break due to incorrect ordering. > > > elif build_system.get('backend-path', None): > > self.setup_metadata['method'] = 'pep517' > > else: > > - self.setup_metadata['method'] = 'unknown' > > + raise Exception("handle setuptools") > > So we don't support setuptools as a PEP517 backend? I guess it was > already the case before your patch? This is just to force the codepath to use the fallback for setuptools which was previously used preferentially with the monkeypatching. I'm thinking I should probably refactor this to handle pyproject.toml setuptools within load_pyproject as it's a bit confusing. > > > except KeyError: > > print('ERROR: Could not determine package metadata for {pkg}.\n' > > .format(pkg=self.real_name)) > > raise > > except FileNotFoundError: > > raise > > - os.chdir(current_dir) > > - sys.path.remove(self.tmp_extract) > > Related to what? Removal of the monkeypatching hack. > > > + finally: > > + os.chdir(current_dir) > > > > def get_requirements(self, pkg_folder): > > """ > > @@ -778,15 +748,15 @@ def main(): > > > > # Loading the package install info from the package > > try: > > - package.load_setup() > > - except ImportError as err: > > - if 'buildutils' in str(err): > > - print('This package needs buildutils') > > - continue > > - else: > > - try: > > - package.load_pyproject() > > - except Exception: > > + package.load_pyproject() > > + except Exception: > > + try: > > + package.load_setup() > > + except ImportError as err: > > + if 'buildutils' in str(err): > > + print('This package needs buildutils') > > + continue > > + else: > > This is a separate change, about the ordering between .load_setup() and > .load_pyproject(). This is actually related as the new pep517 based setuptools method does also look at pyproject.toml and will in some cases cause the wrong setup method to be used if we don't change the ordering. > > Could you split this useful patch into smaller chunks, with a better > justification for each chunk? It's a bit tricky in this case due to all the side effects of migrating away from the monkeypatching which makes testing stuff in isolation hard, the existing code here is also broken in a bunch of random ways to to begin with. My development process here was to just hack on stuff until things work properly. I'll try and clean some stuff up more and see if I can split out or minimize the reordering related changes but it's a bit tricky to test things incrementally. > > Believe me, if you do this, patches like this will be merged, much, > much, much faster. > > Thanks a lot for your contribution! > > Thomas > -- > Thomas Petazzoni, co-owner and CEO, Bootlin > Embedded Linux and Kernel engineering and training > https://bootlin.com _______________________________________________ buildroot mailing list buildroot@buildroot.org https://lists.buildroot.org/mailman/listinfo/buildroot ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2024-05-09 22:28 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-11-30 21:21 [Buildroot] [PATCH v2 1/1] utils/scanpypi: refactor setuptools handling to not use imp James Hilliard 2024-02-07 19:22 ` Thomas Petazzoni via buildroot 2024-05-09 22:28 ` James Hilliard
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox