From 4bc2ec3710e7db02280d74902907411c28519456 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 12 Nov 2021 22:32:14 -0500 Subject: [PATCH 1/6] Create hooks and implement Debian's patches against those hooks. Fixes #2. --- distutils/command/install_egg_info.py | 11 ++- distutils/debian.py | 123 ++++++++++++++++++++++++++ distutils/sysconfig.py | 14 ++- 3 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 distutils/debian.py diff --git a/distutils/command/install_egg_info.py b/distutils/command/install_egg_info.py index 0ddc7367..47e50e55 100644 --- a/distutils/command/install_egg_info.py +++ b/distutils/command/install_egg_info.py @@ -19,14 +19,17 @@ class install_egg_info(Command): def initialize_options(self): self.install_dir = None - def finalize_options(self): - self.set_undefined_options('install_lib',('install_dir','install_dir')) - basename = "%s-%s-py%d.%d.egg-info" % ( + @property + def basename(self): + return "%s-%s-py%d.%d.egg-info" % ( to_filename(safe_name(self.distribution.get_name())), to_filename(safe_version(self.distribution.get_version())), *sys.version_info[:2] ) - self.target = os.path.join(self.install_dir, basename) + + def finalize_options(self): + self.set_undefined_options('install_lib',('install_dir','install_dir')) + self.target = os.path.join(self.install_dir, self.basename) self.outputs = [self.target] def run(self): diff --git a/distutils/debian.py b/distutils/debian.py new file mode 100644 index 00000000..4723c248 --- /dev/null +++ b/distutils/debian.py @@ -0,0 +1,123 @@ +""" +Apply Debian-specific patches to distutils commands and sysconfig. + +Extracts the customized behavior from patches as reported +in pypa/distutils#2 and applies those customizations (except +for scheme definitions) to those commands. + +Call ``apply_customizations`` to have these customizations +take effect. Debian can do that from site.py or similar:: + + with contextlib.suppress(ImportError): + import distutils.debian + distutils.debian.apply_customizations() +""" + +import os +import sys + +import distutils.sysconfig +import distutils.command.install as orig_install +import distutils.command.install_egg_info as orig_install_egg_info +from distutils.command.install_egg_info import ( + to_filename, + safe_name, + safe_version, + ) +from distutils.errors import DistutilsOptionError + + +class install(orig_install.install): + user_options = list(orig_install.install.user_options) + [ + ('install-layout=', None, + "installation layout to choose (known values: deb, unix)"), + ] + + def initialize_options(self): + super().initialize_options() + self.prefix_option = None + self.install_layout = None + + def finalize_unix(self): + self.prefix_option = self.prefix + super().finalize_unix() + if self.install_layout: + if self.install_layout.lower() in ['deb']: + self.select_scheme("deb_system") + elif self.install_layout.lower() in ['unix']: + self.select_scheme("unix_prefix") + else: + raise DistutilsOptionError( + "unknown value for --install-layout") + elif ((self.prefix_option and + os.path.normpath(self.prefix) != '/usr/local') + or sys.base_prefix != sys.prefix + or 'PYTHONUSERBASE' in os.environ + or 'VIRTUAL_ENV' in os.environ + or 'real_prefix' in sys.__dict__): + self.select_scheme("unix_prefix") + else: + if os.path.normpath(self.prefix) == '/usr/local': + self.prefix = self.exec_prefix = '/usr' + self.install_base = self.install_platbase = '/usr' + self.select_scheme("unix_local") + + +class install_egg_info(orig_install_egg_info.install_egg_info): + user_options = list(orig_install_egg_info.install_egg_info.user_options) + [ + ('install-layout', None, "custom installation layout"), + ] + + def initialize_options(self): + super().initialize_options() + self.prefix_option = None + self.install_layout = None + + def finalize_options(self): + self.set_undefined_options('install',('install_layout','install_layout')) + self.set_undefined_options('install',('prefix_option','prefix_option')) + super().finalize_options() + + @property + def basename(self): + if self.install_layout: + if not self.install_layout.lower() in ['deb', 'unix']: + raise DistutilsOptionError( + "unknown value for --install-layout") + no_pyver = (self.install_layout.lower() == 'deb') + elif self.prefix_option: + no_pyver = False + else: + no_pyver = True + if no_pyver: + basename = "%s-%s.egg-info" % ( + to_filename(safe_name(self.distribution.get_name())), + to_filename(safe_version(self.distribution.get_version())) + ) + else: + basename = "%s-%s-py%d.%d.egg-info" % ( + to_filename(safe_name(self.distribution.get_name())), + to_filename(safe_version(self.distribution.get_version())), + *sys.version_info[:2] + ) + return basename + + +def _posix_lib(standard_lib, libpython, early_prefix, prefix): + is_default_prefix = not early_prefix or os.path.normpath(early_prefix) in ('/usr', '/usr/local') + if standard_lib: + return libpython + elif (is_default_prefix and + 'PYTHONUSERBASE' not in os.environ and + 'VIRTUAL_ENV' not in os.environ and + 'real_prefix' not in sys.__dict__ and + sys.prefix == sys.base_prefix): + return os.path.join(prefix, "lib", "python3", "dist-packages") + else: + return os.path.join(libpython, "site-packages") + + +def apply_customizations(): + orig_install.install = install + orig_install_egg_info.install_egg_info = install_egg_info + distutils.sysconfig._posix_lib = _posix_lib diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 8832b3ec..1c025892 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -129,6 +129,13 @@ def get_python_inc(plat_specific=0, prefix=None): "on platform '%s'" % os.name) +def _posix_lib(standard_lib, libpython, early_prefix, prefix): + if standard_lib: + return libpython + else: + return os.path.join(libpython, "site-packages") + + def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): """Return the directory containing the Python library (standard or site additions). @@ -152,6 +159,8 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): return os.path.join(prefix, "lib-python", sys.version[0]) return os.path.join(prefix, 'site-packages') + early_prefix = prefix + if prefix is None: if standard_lib: prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX @@ -169,10 +178,7 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): implementation = 'pypy' if IS_PYPY else 'python' libpython = os.path.join(prefix, libdir, implementation + get_python_version()) - if standard_lib: - return libpython - else: - return os.path.join(libpython, "site-packages") + return _posix_lib(standard_lib, libpython, early_prefix, prefix) elif os.name == "nt": if standard_lib: return os.path.join(prefix, "Lib") From 26db6fea75f45f79d79897a3f99c6d1b1d1eddb4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Nov 2021 09:12:56 -0500 Subject: [PATCH 2/6] Scheme now uses 'posix' --- distutils/debian.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distutils/debian.py b/distutils/debian.py index 4723c248..91d006d2 100644 --- a/distutils/debian.py +++ b/distutils/debian.py @@ -45,7 +45,7 @@ def finalize_unix(self): if self.install_layout.lower() in ['deb']: self.select_scheme("deb_system") elif self.install_layout.lower() in ['unix']: - self.select_scheme("unix_prefix") + self.select_scheme("posix_prefix") else: raise DistutilsOptionError( "unknown value for --install-layout") @@ -55,12 +55,12 @@ def finalize_unix(self): or 'PYTHONUSERBASE' in os.environ or 'VIRTUAL_ENV' in os.environ or 'real_prefix' in sys.__dict__): - self.select_scheme("unix_prefix") + self.select_scheme("posix_prefix") else: if os.path.normpath(self.prefix) == '/usr/local': self.prefix = self.exec_prefix = '/usr' self.install_base = self.install_platbase = '/usr' - self.select_scheme("unix_local") + self.select_scheme("posix_local") class install_egg_info(orig_install_egg_info.install_egg_info): From 888ab6de173f703b13b36ab278170e2004e0752d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Nov 2021 09:26:28 -0500 Subject: [PATCH 3/6] Add a hook on import of distutils that will allow Debian to override the default behavior. Fixes #2. --- distutils/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/distutils/__init__.py b/distutils/__init__.py index d823d040..0ba5e44a 100644 --- a/distutils/__init__.py +++ b/distutils/__init__.py @@ -9,5 +9,15 @@ """ import sys +import importlib __version__ = sys.version[:sys.version.index(' ')] + + +try: + # Allow Debian (only) to customize system behavior. + # Ref pypa/distutils#2. This hook is deprecated and + # no other environments should use it. + importlib.import_module('_distutils_system_mod') +except ImportError: + pass From 115da201da0e648d003dabbbd597d285d0d271f2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Nov 2021 09:29:56 -0500 Subject: [PATCH 4/6] Move the monkeypatch behavior to the hook target and invoke it on import. --- distutils/debian.py => _distutils_system_mod.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) rename distutils/debian.py => _distutils_system_mod.py (94%) diff --git a/distutils/debian.py b/_distutils_system_mod.py similarity index 94% rename from distutils/debian.py rename to _distutils_system_mod.py index 91d006d2..43e93428 100644 --- a/distutils/debian.py +++ b/_distutils_system_mod.py @@ -5,12 +5,7 @@ in pypa/distutils#2 and applies those customizations (except for scheme definitions) to those commands. -Call ``apply_customizations`` to have these customizations -take effect. Debian can do that from site.py or similar:: - - with contextlib.suppress(ImportError): - import distutils.debian - distutils.debian.apply_customizations() +Place this module somewhere in sys.path to take effect. """ import os @@ -121,3 +116,6 @@ def apply_customizations(): orig_install.install = install orig_install_egg_info.install_egg_info = install_egg_info distutils.sysconfig._posix_lib = _posix_lib + + +apply_customizations() From 27c56906766628b711a01d5dd994c89471bd22e0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Nov 2021 09:30:22 -0500 Subject: [PATCH 5/6] Remove monkeypatch, as it was for illustration purposes only. --- _distutils_system_mod.py | 121 --------------------------------------- 1 file changed, 121 deletions(-) delete mode 100644 _distutils_system_mod.py diff --git a/_distutils_system_mod.py b/_distutils_system_mod.py deleted file mode 100644 index 43e93428..00000000 --- a/_distutils_system_mod.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Apply Debian-specific patches to distutils commands and sysconfig. - -Extracts the customized behavior from patches as reported -in pypa/distutils#2 and applies those customizations (except -for scheme definitions) to those commands. - -Place this module somewhere in sys.path to take effect. -""" - -import os -import sys - -import distutils.sysconfig -import distutils.command.install as orig_install -import distutils.command.install_egg_info as orig_install_egg_info -from distutils.command.install_egg_info import ( - to_filename, - safe_name, - safe_version, - ) -from distutils.errors import DistutilsOptionError - - -class install(orig_install.install): - user_options = list(orig_install.install.user_options) + [ - ('install-layout=', None, - "installation layout to choose (known values: deb, unix)"), - ] - - def initialize_options(self): - super().initialize_options() - self.prefix_option = None - self.install_layout = None - - def finalize_unix(self): - self.prefix_option = self.prefix - super().finalize_unix() - if self.install_layout: - if self.install_layout.lower() in ['deb']: - self.select_scheme("deb_system") - elif self.install_layout.lower() in ['unix']: - self.select_scheme("posix_prefix") - else: - raise DistutilsOptionError( - "unknown value for --install-layout") - elif ((self.prefix_option and - os.path.normpath(self.prefix) != '/usr/local') - or sys.base_prefix != sys.prefix - or 'PYTHONUSERBASE' in os.environ - or 'VIRTUAL_ENV' in os.environ - or 'real_prefix' in sys.__dict__): - self.select_scheme("posix_prefix") - else: - if os.path.normpath(self.prefix) == '/usr/local': - self.prefix = self.exec_prefix = '/usr' - self.install_base = self.install_platbase = '/usr' - self.select_scheme("posix_local") - - -class install_egg_info(orig_install_egg_info.install_egg_info): - user_options = list(orig_install_egg_info.install_egg_info.user_options) + [ - ('install-layout', None, "custom installation layout"), - ] - - def initialize_options(self): - super().initialize_options() - self.prefix_option = None - self.install_layout = None - - def finalize_options(self): - self.set_undefined_options('install',('install_layout','install_layout')) - self.set_undefined_options('install',('prefix_option','prefix_option')) - super().finalize_options() - - @property - def basename(self): - if self.install_layout: - if not self.install_layout.lower() in ['deb', 'unix']: - raise DistutilsOptionError( - "unknown value for --install-layout") - no_pyver = (self.install_layout.lower() == 'deb') - elif self.prefix_option: - no_pyver = False - else: - no_pyver = True - if no_pyver: - basename = "%s-%s.egg-info" % ( - to_filename(safe_name(self.distribution.get_name())), - to_filename(safe_version(self.distribution.get_version())) - ) - else: - basename = "%s-%s-py%d.%d.egg-info" % ( - to_filename(safe_name(self.distribution.get_name())), - to_filename(safe_version(self.distribution.get_version())), - *sys.version_info[:2] - ) - return basename - - -def _posix_lib(standard_lib, libpython, early_prefix, prefix): - is_default_prefix = not early_prefix or os.path.normpath(early_prefix) in ('/usr', '/usr/local') - if standard_lib: - return libpython - elif (is_default_prefix and - 'PYTHONUSERBASE' not in os.environ and - 'VIRTUAL_ENV' not in os.environ and - 'real_prefix' not in sys.__dict__ and - sys.prefix == sys.base_prefix): - return os.path.join(prefix, "lib", "python3", "dist-packages") - else: - return os.path.join(libpython, "site-packages") - - -def apply_customizations(): - orig_install.install = install - orig_install_egg_info.install_egg_info = install_egg_info - distutils.sysconfig._posix_lib = _posix_lib - - -apply_customizations() From 4e6ed3f760528346ed8336bebd963939a8d98c8f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 17 Nov 2021 21:02:50 -0500 Subject: [PATCH 6/6] Add comments to help protect these interfaces from refactoring without consideration. --- distutils/command/install_egg_info.py | 4 ++++ distutils/sysconfig.py | 1 + 2 files changed, 5 insertions(+) diff --git a/distutils/command/install_egg_info.py b/distutils/command/install_egg_info.py index 47e50e55..adc0323f 100644 --- a/distutils/command/install_egg_info.py +++ b/distutils/command/install_egg_info.py @@ -21,6 +21,10 @@ def initialize_options(self): @property def basename(self): + """ + Allow basename to be overridden by child class. + Ref pypa/distutils#2. + """ return "%s-%s-py%d.%d.egg-info" % ( to_filename(safe_name(self.distribution.get_name())), to_filename(safe_version(self.distribution.get_version())), diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 1c025892..7bfc67ba 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -129,6 +129,7 @@ def get_python_inc(plat_specific=0, prefix=None): "on platform '%s'" % os.name) +# allow this behavior to be monkey-patched. Ref pypa/distutils#2. def _posix_lib(standard_lib, libpython, early_prefix, prefix): if standard_lib: return libpython