From f1eb1d26a45dd02bb1bb06b2d979596a5ef5a92f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 Jan 2021 16:11:37 -0500 Subject: [PATCH 01/16] Allow sysconfig to override install schemes and provide some compatibility overrides for debian and fedora until they patch sysconfig. --- distutils/command/install.py | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/distutils/command/install.py b/distutils/command/install.py index 13feeb89..f3dce84b 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -35,6 +35,13 @@ 'scripts': '$base/bin', 'data' : '$base', }, + 'unix_local': { + 'purelib': '$base/local/lib/python$py_version_short/site-packages', + 'platlib': '$platbase/local/$platlibdir/python$py_version_short/site-packages', + 'headers': '$base/local/include/python$py_version_short$abiflags/$dist_name', + 'scripts': '$base/local/bin', + 'data' : '$base/local', + }, 'unix_home': { 'purelib': '$base/lib/python', 'platlib': '$base/$platlibdir/python', @@ -282,6 +289,7 @@ def finalize_options(self): self.dump_dirs("pre-finalize_{unix,other}") + self._load_schemes() if os.name == 'posix': self.finalize_unix() else: @@ -404,6 +412,48 @@ def dump_dirs(self, msg): val = getattr(self, opt_name) log.debug(" %s: %s", opt_name, val) + def _load_schemes(self): + """ + Allow sysconfig and runtime behaviors to alter schemes. + """ + + try: + import sysconfig + INSTALL_SCHEMES.update(sysconfig.INSTALL_SCHEMES) + return + except Exception: + pass + + def is_virtualenv(): + return ( + hasattr(sys, 'real_prefix') or + sys.prefix != sys.base_prefix or + 'VIRTUAL_ENV' in os.environ + ) + + # fedora: + def is_rpm_build(): + return 'RPM_BUILD_ROOT' in os.environ + + if not is_virtualenv() and not is_rpm_build(): + INSTALL_SCHEMES['unix_prefix'] = INSTALL_SCHEMES['unix_local'] + + # debian: + def is_deb_system(): + # TODO: how to solicit without an additional parameter to build? + return self.install_layout.lower() == 'deb' + + if is_deb_system(): + INSTALL_SCHEMES['unix_prefix'] = { + 'purelib': '$base/lib/python3/dist-packages', + 'platlib': '$platbase/lib/python3/dist-packages', + 'headers': '$base/include/python$py_version_short/$dist_name', + 'scripts': '$base/bin', + 'data' : '$base', + } + if not is_virtualenv(): + INSTALL_SCHEMES['unix_prefix'] = INSTALL_SCHEMES['unix_local'] + def finalize_unix(self): """Finalizes options for posix platforms.""" if self.install_base is not None or self.install_platbase is not None: From 87c3a984741ab91023a86be28f7145fbf4901862 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Sep 2021 14:10:17 -0400 Subject: [PATCH 02/16] Only catch relevant exeptions. Co-authored-by: Petr Viktorin --- distutils/command/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index f3dce84b..80165778 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -421,7 +421,7 @@ def _load_schemes(self): import sysconfig INSTALL_SCHEMES.update(sysconfig.INSTALL_SCHEMES) return - except Exception: + except (ImportError, AttributeError): pass def is_virtualenv(): From fbbc98f569c135add8f95a5a1e2f8f6b7ad53750 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Sep 2021 14:23:22 -0400 Subject: [PATCH 03/16] Remove is_rpm_build, unneeded. --- distutils/command/install.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 80165778..15c21df5 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -431,11 +431,7 @@ def is_virtualenv(): 'VIRTUAL_ENV' in os.environ ) - # fedora: - def is_rpm_build(): - return 'RPM_BUILD_ROOT' in os.environ - - if not is_virtualenv() and not is_rpm_build(): + if not is_virtualenv(): INSTALL_SCHEMES['unix_prefix'] = INSTALL_SCHEMES['unix_local'] # debian: From 968d2984b116fe1c4e556ebd811ec0e0420307b3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Sep 2021 14:44:34 -0400 Subject: [PATCH 04/16] In _load_schemes, actually return the schemes and avoid mutating the global state. --- distutils/command/install.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 15c21df5..796d3791 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -285,11 +285,10 @@ def finalize_options(self): # input a heady brew of prefix, exec_prefix, home, install_base, # install_platbase, user-supplied versions of # install_{purelib,platlib,lib,scripts,data,...}, and the - # INSTALL_SCHEME dictionary above. Phew! + # install schemes. Phew! self.dump_dirs("pre-finalize_{unix,other}") - self._load_schemes() if os.name == 'posix': self.finalize_unix() else: @@ -417,10 +416,12 @@ def _load_schemes(self): Allow sysconfig and runtime behaviors to alter schemes. """ + schemes = dict(INSTALL_SCHEMES) + try: import sysconfig - INSTALL_SCHEMES.update(sysconfig.INSTALL_SCHEMES) - return + schemes.update(sysconfig.INSTALL_SCHEMES) + return schemes except (ImportError, AttributeError): pass @@ -432,7 +433,7 @@ def is_virtualenv(): ) if not is_virtualenv(): - INSTALL_SCHEMES['unix_prefix'] = INSTALL_SCHEMES['unix_local'] + schemes['unix_prefix'] = schemes['unix_local'] # debian: def is_deb_system(): @@ -440,7 +441,7 @@ def is_deb_system(): return self.install_layout.lower() == 'deb' if is_deb_system(): - INSTALL_SCHEMES['unix_prefix'] = { + schemes['unix_prefix'] = { 'purelib': '$base/lib/python3/dist-packages', 'platlib': '$platbase/lib/python3/dist-packages', 'headers': '$base/include/python$py_version_short/$dist_name', @@ -448,7 +449,9 @@ def is_deb_system(): 'data' : '$base', } if not is_virtualenv(): - INSTALL_SCHEMES['unix_prefix'] = INSTALL_SCHEMES['unix_local'] + schemes['unix_prefix'] = schemes['unix_local'] + + return schemes def finalize_unix(self): """Finalizes options for posix platforms.""" @@ -521,7 +524,7 @@ def select_scheme(self, name): name = 'pypy_nt' else: name = 'pypy' - scheme = INSTALL_SCHEMES[name] + scheme = self._load_schemes()[name] for key in SCHEME_KEYS: attrname = 'install_' + key if getattr(self, attrname) is None: From da44ac095ad1a611959082df5e3f934772b5041c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Sep 2021 14:53:00 -0400 Subject: [PATCH 05/16] Remove 'unix_local' install scheme. --- distutils/command/install.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 796d3791..b005c3a9 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -35,13 +35,6 @@ 'scripts': '$base/bin', 'data' : '$base', }, - 'unix_local': { - 'purelib': '$base/local/lib/python$py_version_short/site-packages', - 'platlib': '$platbase/local/$platlibdir/python$py_version_short/site-packages', - 'headers': '$base/local/include/python$py_version_short$abiflags/$dist_name', - 'scripts': '$base/local/bin', - 'data' : '$base/local', - }, 'unix_home': { 'purelib': '$base/lib/python', 'platlib': '$base/$platlibdir/python', @@ -425,16 +418,6 @@ def _load_schemes(self): except (ImportError, AttributeError): pass - def is_virtualenv(): - return ( - hasattr(sys, 'real_prefix') or - sys.prefix != sys.base_prefix or - 'VIRTUAL_ENV' in os.environ - ) - - if not is_virtualenv(): - schemes['unix_prefix'] = schemes['unix_local'] - # debian: def is_deb_system(): # TODO: how to solicit without an additional parameter to build? @@ -448,8 +431,6 @@ def is_deb_system(): 'scripts': '$base/bin', 'data' : '$base', } - if not is_virtualenv(): - schemes['unix_prefix'] = schemes['unix_local'] return schemes From dcafd577d3bc0a5a87e3278d976bea0ca4b43084 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Sep 2021 14:54:59 -0400 Subject: [PATCH 06/16] Remove special casing for Debian. Instead, Debian should make sure the schemes are updated in sysconfig. --- distutils/command/install.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index b005c3a9..848388bb 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -406,7 +406,7 @@ def dump_dirs(self, msg): def _load_schemes(self): """ - Allow sysconfig and runtime behaviors to alter schemes. + Allow sysconfig to alter schemes. """ schemes = dict(INSTALL_SCHEMES) @@ -414,24 +414,9 @@ def _load_schemes(self): try: import sysconfig schemes.update(sysconfig.INSTALL_SCHEMES) - return schemes except (ImportError, AttributeError): pass - # debian: - def is_deb_system(): - # TODO: how to solicit without an additional parameter to build? - return self.install_layout.lower() == 'deb' - - if is_deb_system(): - schemes['unix_prefix'] = { - 'purelib': '$base/lib/python3/dist-packages', - 'platlib': '$platbase/lib/python3/dist-packages', - 'headers': '$base/include/python$py_version_short/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', - } - return schemes def finalize_unix(self): From 044f77190bb457f35222044e5a455cfb11a4455e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Sep 2021 20:53:59 -0400 Subject: [PATCH 07/16] Replace instance method with global function. --- distutils/command/install.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 848388bb..026c609a 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -84,6 +84,22 @@ SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data') +def _load_schemes(): + """ + Extend default schemes with schemes from sysconfig. + """ + + schemes = dict(INSTALL_SCHEMES) + + try: + import sysconfig + schemes.update(sysconfig.INSTALL_SCHEMES) + except (ImportError, AttributeError): + pass + + return schemes + + class install(Command): description = "install everything from build directory" @@ -404,21 +420,6 @@ def dump_dirs(self, msg): val = getattr(self, opt_name) log.debug(" %s: %s", opt_name, val) - def _load_schemes(self): - """ - Allow sysconfig to alter schemes. - """ - - schemes = dict(INSTALL_SCHEMES) - - try: - import sysconfig - schemes.update(sysconfig.INSTALL_SCHEMES) - except (ImportError, AttributeError): - pass - - return schemes - def finalize_unix(self): """Finalizes options for posix platforms.""" if self.install_base is not None or self.install_platbase is not None: @@ -490,7 +491,7 @@ def select_scheme(self, name): name = 'pypy_nt' else: name = 'pypy' - scheme = self._load_schemes()[name] + scheme = _load_schemes()[name] for key in SCHEME_KEYS: attrname = 'install_' + key if getattr(self, attrname) is None: From 1d7c74836b725af874f975503568053f0b95e26c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 11:00:07 -0400 Subject: [PATCH 08/16] Prefer public APIs for sysconfig schemes. --- distutils/command/install.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 026c609a..0db71e94 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -93,7 +93,11 @@ def _load_schemes(): try: import sysconfig - schemes.update(sysconfig.INSTALL_SCHEMES) + sysconfig_schemes = { + scheme: sysconfig.get_paths(scheme, expand=False) + for scheme in sysconfig.get_scheme_names() + } + schemes.update(sysconfig_schemes) except (ImportError, AttributeError): pass From 28e643959a4208f86fb3b37ae9b78eb07ad5b6d5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 11:01:18 -0400 Subject: [PATCH 09/16] Prefer contextlib.suppress to except/pass --- distutils/command/install.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 0db71e94..b1876748 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -4,6 +4,7 @@ import sys import os +import contextlib from distutils import log from distutils.core import Command @@ -91,15 +92,13 @@ def _load_schemes(): schemes = dict(INSTALL_SCHEMES) - try: + with contextlib.suppress(ImportError, AttributeError): import sysconfig sysconfig_schemes = { scheme: sysconfig.get_paths(scheme, expand=False) for scheme in sysconfig.get_scheme_names() } schemes.update(sysconfig_schemes) - except (ImportError, AttributeError): - pass return schemes From e4466cd3436c6b331fac85bd28d1fdaa658772f7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 13:02:35 -0400 Subject: [PATCH 10/16] Rename 'headers' to 'include' to match sysconfig. --- distutils/command/install.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index b1876748..1267a51e 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -23,7 +23,7 @@ WINDOWS_SCHEME = { 'purelib': '$base/Lib/site-packages', 'platlib': '$base/Lib/site-packages', - 'headers': '$base/Include/$dist_name', + 'include': '$base/Include/$dist_name', 'scripts': '$base/Scripts', 'data' : '$base', } @@ -32,14 +32,14 @@ 'unix_prefix': { 'purelib': '$base/lib/python$py_version_short/site-packages', 'platlib': '$platbase/$platlibdir/python$py_version_short/site-packages', - 'headers': '$base/include/python$py_version_short$abiflags/$dist_name', + 'include': '$base/include/python$py_version_short$abiflags/$dist_name', 'scripts': '$base/bin', 'data' : '$base', }, 'unix_home': { 'purelib': '$base/lib/python', 'platlib': '$base/$platlibdir/python', - 'headers': '$base/include/python/$dist_name', + 'include': '$base/include/python/$dist_name', 'scripts': '$base/bin', 'data' : '$base', }, @@ -47,14 +47,14 @@ 'pypy': { 'purelib': '$base/site-packages', 'platlib': '$base/site-packages', - 'headers': '$base/include/$dist_name', + 'include': '$base/include/$dist_name', 'scripts': '$base/bin', 'data' : '$base', }, 'pypy_nt': { 'purelib': '$base/site-packages', 'platlib': '$base/site-packages', - 'headers': '$base/include/$dist_name', + 'include': '$base/include/$dist_name', 'scripts': '$base/Scripts', 'data' : '$base', }, @@ -65,7 +65,7 @@ INSTALL_SCHEMES['nt_user'] = { 'purelib': '$usersite', 'platlib': '$usersite', - 'headers': '$userbase/Python$py_version_nodot/Include/$dist_name', + 'include': '$userbase/Python$py_version_nodot/Include/$dist_name', 'scripts': '$userbase/Python$py_version_nodot/Scripts', 'data' : '$userbase', } @@ -73,7 +73,7 @@ INSTALL_SCHEMES['unix_user'] = { 'purelib': '$usersite', 'platlib': '$usersite', - 'headers': + 'include': '$userbase/include/python$py_version_short$abiflags/$dist_name', 'scripts': '$userbase/bin', 'data' : '$userbase', @@ -82,7 +82,7 @@ # The keys to an installation scheme; if any new types of files are to be # installed, be sure to add an entry to every installation scheme above, # and to SCHEME_KEYS here. -SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data') +SCHEME_KEYS = ('purelib', 'platlib', 'include', 'scripts', 'data') def _load_schemes(): @@ -198,7 +198,7 @@ def initialize_options(self): # that installation scheme. self.install_purelib = None # for pure module distributions self.install_platlib = None # non-pure (dists w/ extensions) - self.install_headers = None # for C/C++ headers + self.install_include = None # for C/C++ headers self.install_lib = None # set to either purelib or platlib self.install_scripts = None self.install_data = None @@ -247,6 +247,10 @@ def initialize_options(self): self.record = None + @property + def install_headers(self): + # for compatibility + return self.install_include # -- Option finalizing methods ------------------------------------- # (This is rather more involved than for most commands, @@ -376,7 +380,7 @@ def finalize_options(self): # Convert directories from Unix /-separated syntax to the local # convention. self.convert_paths('lib', 'purelib', 'platlib', - 'scripts', 'data', 'headers', + 'scripts', 'data', 'include', 'userbase', 'usersite') # Deprecated @@ -392,7 +396,7 @@ def finalize_options(self): # dirs relative to it. if self.root is not None: self.change_roots('libbase', 'lib', 'purelib', 'platlib', - 'scripts', 'data', 'headers') + 'scripts', 'data', 'include') self.dump_dirs("after prepending root") @@ -429,7 +433,7 @@ def finalize_unix(self): if ((self.install_lib is None and self.install_purelib is None and self.install_platlib is None) or - self.install_headers is None or + self.install_include is None or self.install_scripts is None or self.install_data is None): raise DistutilsOptionError( @@ -517,7 +521,7 @@ def expand_basedirs(self): def expand_dirs(self): """Calls `os.path.expanduser` on install dirs.""" self._expand_attrs(['install_purelib', 'install_platlib', - 'install_lib', 'install_headers', + 'install_lib', 'install_include', 'install_scripts', 'install_data',]) def convert_paths(self, *names): From af44971ccc28f5151c896eec66b801944cf8545b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 13:22:51 -0400 Subject: [PATCH 11/16] Replace shell/Perl-style variables in install schemes with format-style. --- distutils/command/install.py | 70 ++++++++++++++++++------------------ distutils/util.py | 37 ++++++++++++------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 1267a51e..0f4ff70c 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -21,62 +21,62 @@ HAS_USER_SITE = True WINDOWS_SCHEME = { - 'purelib': '$base/Lib/site-packages', - 'platlib': '$base/Lib/site-packages', - 'include': '$base/Include/$dist_name', - 'scripts': '$base/Scripts', - 'data' : '$base', + 'purelib': '{base}/Lib/site-packages', + 'platlib': '{base}/Lib/site-packages', + 'include': '{base}/Include/{dist_name}', + 'scripts': '{base}/Scripts', + 'data' : '{base}', } INSTALL_SCHEMES = { 'unix_prefix': { - 'purelib': '$base/lib/python$py_version_short/site-packages', - 'platlib': '$platbase/$platlibdir/python$py_version_short/site-packages', - 'include': '$base/include/python$py_version_short$abiflags/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', + 'purelib': '{base}/lib/python{py_version_short}/site-packages', + 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', + 'include': '{base}/include/python{py_version_short}{abiflags}/{dist_name}', + 'scripts': '{base}/bin', + 'data' : '{base}', }, 'unix_home': { - 'purelib': '$base/lib/python', - 'platlib': '$base/$platlibdir/python', - 'include': '$base/include/python/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', + 'purelib': '{base}/lib/python', + 'platlib': '{base}/{platlibdir}/python', + 'include': '{base}/include/python/{dist_name}', + 'scripts': '{base}/bin', + 'data' : '{base}', }, 'nt': WINDOWS_SCHEME, 'pypy': { - 'purelib': '$base/site-packages', - 'platlib': '$base/site-packages', - 'include': '$base/include/$dist_name', - 'scripts': '$base/bin', - 'data' : '$base', + 'purelib': '{base}/site-packages', + 'platlib': '{base}/site-packages', + 'include': '{base}/include/{dist_name}', + 'scripts': '{base}/bin', + 'data' : '{base}', }, 'pypy_nt': { - 'purelib': '$base/site-packages', - 'platlib': '$base/site-packages', - 'include': '$base/include/$dist_name', - 'scripts': '$base/Scripts', - 'data' : '$base', + 'purelib': '{base}/site-packages', + 'platlib': '{base}/site-packages', + 'include': '{base}/include/{dist_name}', + 'scripts': '{base}/Scripts', + 'data' : '{base}', }, } # user site schemes if HAS_USER_SITE: INSTALL_SCHEMES['nt_user'] = { - 'purelib': '$usersite', - 'platlib': '$usersite', - 'include': '$userbase/Python$py_version_nodot/Include/$dist_name', - 'scripts': '$userbase/Python$py_version_nodot/Scripts', - 'data' : '$userbase', + 'purelib': '{usersite}', + 'platlib': '{usersite}', + 'include': '{userbase}/Python{py_version_nodot}/Include/{dist_name}', + 'scripts': '{userbase}/Python{py_version_nodot}/Scripts', + 'data' : '{userbase}', } INSTALL_SCHEMES['unix_user'] = { - 'purelib': '$usersite', - 'platlib': '$usersite', + 'purelib': '{usersite}', + 'platlib': '{usersite}', 'include': - '$userbase/include/python$py_version_short$abiflags/$dist_name', - 'scripts': '$userbase/bin', - 'data' : '$userbase', + '{userbase}/include/python{py_version_short}{abiflags}/{dist_name}', + 'scripts': '{userbase}/bin', + 'data' : '{userbase}', } # The keys to an installation scheme; if any new types of files are to be diff --git a/distutils/util.py b/distutils/util.py index f5aca794..81d6869f 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -187,30 +187,43 @@ def check_environ (): def subst_vars (s, local_vars): - """Perform shell/Perl-style variable substitution on 'string'. Every - occurrence of '$' followed by a name is considered a variable, and - variable is substituted by the value found in the 'local_vars' - dictionary, or in 'os.environ' if it's not in 'local_vars'. + """ + Perform variable substitution on 'string'. + Variables are indicated by format-style braces ("{var}"). + Variable is substituted by the value found in the 'local_vars' + dictionary or in 'os.environ' if it's not in 'local_vars'. 'os.environ' is first checked/augmented to guarantee that it contains certain values: see 'check_environ()'. Raise ValueError for any variables not found in either 'local_vars' or 'os.environ'. """ check_environ() - def _subst (match, local_vars=local_vars): - var_name = match.group(1) - if var_name in local_vars: - return str(local_vars[var_name]) - else: - return os.environ[var_name] - + lookup = dict(os.environ) + lookup.update((name, str(value)) for name, value in local_vars.items()) try: - return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s) + return _subst_compat(s).format_map(lookup) except KeyError as var: raise ValueError("invalid variable '$%s'" % var) # subst_vars () +def _subst_compat(s): + """ + Replace shell/Perl-style variable substitution with + format-style. For compatibility. + """ + def _subst(match): + return f'{{{match.group(1)}}}' + repl = re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s) + if repl != s: + import warnings + warnings.warn( + "shell/Perl-style substitions are deprecated", + DeprecationWarning, + ) + return repl + + def grok_environment_error (exc, prefix="error: "): # Function kept for backward compatibility. # Used to try clever things with EnvironmentErrors, From f1a6ea01ffef107b0ff20aea5e6b5716608bde2a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 13:36:37 -0400 Subject: [PATCH 12/16] Clean up 'invalid variable'. A KeyError already includes the quotes when rendered. --- distutils/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/util.py b/distutils/util.py index d9916a0a..ac6d446d 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -257,7 +257,7 @@ def subst_vars (s, local_vars): try: return _subst_compat(s).format_map(lookup) except KeyError as var: - raise ValueError("invalid variable '$%s'" % var) + raise ValueError(f"invalid variable {var}") # subst_vars () From 341c9087e64dabaaef2718e229fd940c78d9b86f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 13:47:22 -0400 Subject: [PATCH 13/16] Copy installed_base from sysconfig.get_config_vars. --- distutils/command/install.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 8161aa9e..23d48ba3 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -5,6 +5,7 @@ import sys import os import contextlib +import sysconfig from distutils import log from distutils.core import Command @@ -92,8 +93,7 @@ def _load_schemes(): schemes = dict(INSTALL_SCHEMES) - with contextlib.suppress(ImportError, AttributeError): - import sysconfig + with contextlib.suppress(AttributeError): sysconfig_schemes = { scheme: sysconfig.get_paths(scheme, expand=False) for scheme in sysconfig.get_scheme_names() @@ -102,6 +102,7 @@ def _load_schemes(): return schemes + def _get_implementation(): if hasattr(sys, 'pypy_version_info'): return 'PyPy' @@ -358,6 +359,8 @@ def finalize_options(self): # everything else. self.config_vars['base'] = self.install_base self.config_vars['platbase'] = self.install_platbase + self.config_vars['installed_base'] = ( + sysconfig.get_config_vars()['installed_base']) if DEBUG: from pprint import pprint From 5c5aed1004472ecec994620fec43743f0528a254 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Nov 2021 18:53:22 -0400 Subject: [PATCH 14/16] Prefer 'posix_*' to 'unix_*' --- distutils/command/install.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 23d48ba3..6ba07ffd 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -457,10 +457,10 @@ def finalize_unix(self): raise DistutilsPlatformError( "User base directory is not specified") self.install_base = self.install_platbase = self.install_userbase - self.select_scheme("unix_user") + self.select_scheme("posix_user") elif self.home is not None: self.install_base = self.install_platbase = self.home - self.select_scheme("unix_home") + self.select_scheme("posix_home") else: if self.prefix is None: if self.exec_prefix is not None: @@ -476,7 +476,7 @@ def finalize_unix(self): self.install_base = self.prefix self.install_platbase = self.exec_prefix - self.select_scheme("unix_prefix") + self.select_scheme("posix_prefix") def finalize_other(self): """Finalizes options for non-posix platforms""" @@ -488,7 +488,7 @@ def finalize_other(self): self.select_scheme(os.name + "_user") elif self.home is not None: self.install_base = self.install_platbase = self.home - self.select_scheme("unix_home") + self.select_scheme("posix_home") else: if self.prefix is None: self.prefix = os.path.normpath(sys.prefix) From 69b850303420cf47d41d6e6f47ed07a3e2dd4516 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 12 Nov 2021 15:46:00 -0500 Subject: [PATCH 15/16] Revert "Rename 'headers' to 'include' to match sysconfig." This reverts commit e4466cd3436c6b331fac85bd28d1fdaa658772f7. --- distutils/command/install.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index 6ba07ffd..dd313d8a 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -24,7 +24,7 @@ WINDOWS_SCHEME = { 'purelib': '{base}/Lib/site-packages', 'platlib': '{base}/Lib/site-packages', - 'include': '{base}/Include/{dist_name}', + 'headers': '{base}/Include/{dist_name}', 'scripts': '{base}/Scripts', 'data' : '{base}', } @@ -33,14 +33,14 @@ 'unix_prefix': { 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', - 'include': '{base}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', + 'headers': '{base}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', 'scripts': '{base}/bin', 'data' : '{base}', }, 'unix_home': { 'purelib': '{base}/lib/{implementation_lower}', 'platlib': '{base}/{platlibdir}/{implementation_lower}', - 'include': '{base}/include/{implementation_lower}/{dist_name}', + 'headers': '{base}/include/{implementation_lower}/{dist_name}', 'scripts': '{base}/bin', 'data' : '{base}', }, @@ -48,14 +48,14 @@ 'pypy': { 'purelib': '{base}/site-packages', 'platlib': '{base}/site-packages', - 'include': '{base}/include/{dist_name}', + 'headers': '{base}/include/{dist_name}', 'scripts': '{base}/bin', 'data' : '{base}', }, 'pypy_nt': { 'purelib': '{base}/site-packages', 'platlib': '{base}/site-packages', - 'include': '{base}/include/{dist_name}', + 'headers': '{base}/include/{dist_name}', 'scripts': '{base}/Scripts', 'data' : '{base}', }, @@ -66,7 +66,7 @@ INSTALL_SCHEMES['nt_user'] = { 'purelib': '{usersite}', 'platlib': '{usersite}', - 'include': '{userbase}/{implementation}{py_version_nodot}/Include/{dist_name}', + 'headers': '{userbase}/{implementation}{py_version_nodot}/Include/{dist_name}', 'scripts': '{userbase}/{implementation}{py_version_nodot}/Scripts', 'data' : '{userbase}', } @@ -74,7 +74,7 @@ INSTALL_SCHEMES['unix_user'] = { 'purelib': '{usersite}', 'platlib': '{usersite}', - 'include': + 'headers': '{userbase}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', 'scripts': '{userbase}/bin', 'data' : '{userbase}', @@ -83,7 +83,7 @@ # The keys to an installation scheme; if any new types of files are to be # installed, be sure to add an entry to every installation scheme above, # and to SCHEME_KEYS here. -SCHEME_KEYS = ('purelib', 'platlib', 'include', 'scripts', 'data') +SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data') def _load_schemes(): @@ -205,7 +205,7 @@ def initialize_options(self): # that installation scheme. self.install_purelib = None # for pure module distributions self.install_platlib = None # non-pure (dists w/ extensions) - self.install_include = None # for C/C++ headers + self.install_headers = None # for C/C++ headers self.install_lib = None # set to either purelib or platlib self.install_scripts = None self.install_data = None @@ -254,10 +254,6 @@ def initialize_options(self): self.record = None - @property - def install_headers(self): - # for compatibility - return self.install_include # -- Option finalizing methods ------------------------------------- # (This is rather more involved than for most commands, @@ -391,7 +387,7 @@ def finalize_options(self): # Convert directories from Unix /-separated syntax to the local # convention. self.convert_paths('lib', 'purelib', 'platlib', - 'scripts', 'data', 'include', + 'scripts', 'data', 'headers', 'userbase', 'usersite') # Deprecated @@ -407,7 +403,7 @@ def finalize_options(self): # dirs relative to it. if self.root is not None: self.change_roots('libbase', 'lib', 'purelib', 'platlib', - 'scripts', 'data', 'include') + 'scripts', 'data', 'headers') self.dump_dirs("after prepending root") @@ -444,7 +440,7 @@ def finalize_unix(self): if ((self.install_lib is None and self.install_purelib is None and self.install_platlib is None) or - self.install_include is None or + self.install_headers is None or self.install_scripts is None or self.install_data is None): raise DistutilsOptionError( @@ -533,7 +529,7 @@ def expand_basedirs(self): def expand_dirs(self): """Calls `os.path.expanduser` on install dirs.""" self._expand_attrs(['install_purelib', 'install_platlib', - 'install_lib', 'install_include', + 'install_lib', 'install_headers', 'install_scripts', 'install_data',]) def convert_paths(self, *names): From 342e02e7a4dbedf0e3a04c4d2d213b5340d56010 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 12 Nov 2021 16:06:47 -0500 Subject: [PATCH 16/16] Rename 'posix' to 'unix' to match sysconfig. Merge both definitions with sysconfig. --- distutils/command/install.py | 30 +++++++++++++++++++----------- distutils/tests/test_install.py | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/distutils/command/install.py b/distutils/command/install.py index dd313d8a..c756b6db 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -6,6 +6,7 @@ import os import contextlib import sysconfig +import itertools from distutils import log from distutils.core import Command @@ -30,14 +31,14 @@ } INSTALL_SCHEMES = { - 'unix_prefix': { + 'posix_prefix': { 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', 'headers': '{base}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', 'scripts': '{base}/bin', 'data' : '{base}', }, - 'unix_home': { + 'posix_home': { 'purelib': '{base}/lib/{implementation_lower}', 'platlib': '{base}/{platlibdir}/{implementation_lower}', 'headers': '{base}/include/{implementation_lower}/{dist_name}', @@ -71,7 +72,7 @@ 'data' : '{userbase}', } - INSTALL_SCHEMES['unix_user'] = { + INSTALL_SCHEMES['posix_user'] = { 'purelib': '{usersite}', 'platlib': '{usersite}', 'headers': @@ -86,21 +87,28 @@ SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data') +def _load_sysconfig_schemes(): + with contextlib.suppress(AttributeError): + return { + scheme: sysconfig.get_paths(scheme, expand=False) + for scheme in sysconfig.get_scheme_names() + } + + def _load_schemes(): """ Extend default schemes with schemes from sysconfig. """ - schemes = dict(INSTALL_SCHEMES) + sysconfig_schemes = _load_sysconfig_schemes() or {} - with contextlib.suppress(AttributeError): - sysconfig_schemes = { - scheme: sysconfig.get_paths(scheme, expand=False) - for scheme in sysconfig.get_scheme_names() + return { + scheme: { + **INSTALL_SCHEMES.get(scheme, {}), + **sysconfig_schemes.get(scheme, {}), } - schemes.update(sysconfig_schemes) - - return schemes + for scheme in set(itertools.chain(INSTALL_SCHEMES, sysconfig_schemes)) + } def _get_implementation(): diff --git a/distutils/tests/test_install.py b/distutils/tests/test_install.py index eb684a09..cce973dc 100644 --- a/distutils/tests/test_install.py +++ b/distutils/tests/test_install.py @@ -94,7 +94,7 @@ def cleanup(): self.addCleanup(cleanup) - for key in ('nt_user', 'unix_user'): + for key in ('nt_user', 'posix_user'): self.assertIn(key, INSTALL_SCHEMES) dist = Distribution({'name': 'xx'})