Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-45413: Define "posix_venv", "nt_venv" and "venv" sysconfig installation schemes #31034

Merged
merged 20 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
912f823
bpo-45413: Define a "venv" sysconfig installation scheme
hroncok Jan 31, 2022
4686893
Use the same conditional as previously in venv
hroncok Jan 31, 2022
6e98457
Use os.sep
hroncok Jan 31, 2022
602c2ad
A bit better wording of the comment
hroncok Feb 1, 2022
44f588a
Add a test that the venv scheme does not regress from the previously …
hroncok Feb 1, 2022
8af07ee
Revert "Use the same conditional as previously in venv"
hroncok Feb 1, 2022
c734658
venv: Resolve paths with env_dir as all bases
hroncok Feb 1, 2022
0a8556d
Optional: When we are in virtual environment, sysconfig.get_default_s…
hroncok Feb 1, 2022
cb2ed5b
Also return venv from get_preferred_scheme()
hroncok Feb 2, 2022
af47953
Rename binname to binpath in test for consistency with other names
hroncok Feb 2, 2022
4cc4015
Add documentation and what's new
hroncok Feb 2, 2022
5d04c22
Also mention this change in What's new for venv
hroncok Feb 2, 2022
5f35cd8
Sysconfig docs: The venv scheme is also for running virtual environments
hroncok Feb 2, 2022
e7444a2
Typo
hroncok Feb 2, 2022
30fdee3
Assert the preferred(prefix) and default sysconfig install scheme in …
hroncok Feb 2, 2022
27ce189
Instead of *venv* scheme, have *posix_venv* and *nt_venv*
hroncok Feb 17, 2022
6297449
Apply suggestions from language review
hroncok Mar 3, 2022
283db7a
Drop get_venv_scheme() in favor of *venv* scheme
hroncok Mar 17, 2022
594c4d9
Apply my own suggestions from docs review
hroncok Mar 17, 2022
9890918
Typo
hroncok Mar 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Doc/library/sysconfig.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Every new component that is installed using :mod:`distutils` or a
Distutils-based system will follow the same scheme to copy its file in the right
places.

Python currently supports six schemes:
Python currently supports nine schemes:

- *posix_prefix*: scheme for POSIX platforms like Linux or macOS. This is
the default scheme used when Python or a component is installed.
Expand All @@ -83,8 +83,14 @@ Python currently supports six schemes:
- *posix_user*: scheme for POSIX platforms used when a component is installed
through Distutils and the *user* option is used. This scheme defines paths
located under the user home directory.
- *posix_venv*: scheme for :mod:`Python virtual environments <venv>` on POSIX
platforms; by default it is the same as *posix_prefix* .
- *nt*: scheme for NT platforms like Windows.
- *nt_user*: scheme for NT platforms, when the *user* option is used.
- *nt_venv*: scheme for :mod:`Python virtual environments <venv>` on NT
platforms; by default it is the same as *nt* .
- *venv*: a scheme with values from ether *posix_venv* or *nt_venv* depending
on the platform Python runs on
- *osx_framework_user*: scheme for macOS, when the *user* option is used.

Each scheme is itself composed of a series of paths and each path has a unique
Expand Down Expand Up @@ -119,6 +125,9 @@ identifier. Python currently uses eight paths:
This function was previously named ``_get_default_scheme()`` and
considered an implementation detail.

.. versionchanged:: 3.11
When Python runs from a virtual environment,
the *venv* scheme is returned.

.. function:: get_preferred_scheme(key)

Expand All @@ -132,6 +141,10 @@ identifier. Python currently uses eight paths:

.. versionadded:: 3.10

.. versionchanged:: 3.11
When Python runs from a virtual environment and ``key="prefix"``,
the *venv* scheme is returned.


.. function:: _get_preferred_schemes()

Expand Down
5 changes: 5 additions & 0 deletions Doc/library/venv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ creation according to their needs, the :class:`EnvBuilder` class.
``clear=True``, contents of the environment directory will be cleared
and then all necessary subdirectories will be recreated.

.. versionchanged:: 3.11
The *venv*
:ref:`sysconfig installation scheme <installation_paths>`
is used to construct the paths of the created directories.

.. method:: create_configuration(context)

Creates the ``pyvenv.cfg`` configuration file in the environment.
Expand Down
32 changes: 32 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,24 @@ sys
(equivalent to ``sys.exc_info()[1]``).
(Contributed by Irit Katriel in :issue:`46328`.)


sysconfig
---------

* Two new :ref:`installation schemes <installation_paths>`
(*posix_venv*, *nt_venv* and *venv*) were added and are used when Python
creates new virtual environments or when it is running from a virtual
environment.
The first two schemes (*posix_venv* and *nt_venv*) are OS-specific
for non-Windows and Windows, the *venv* is essentially an alias to one of
them according to the OS Python runs on.
This is useful for downstream distributors who modify
:func:`sysconfig.get_preferred_scheme`.
Third party code that creates new virtual environments should use the new
*venv* installation scheme to determine the paths, as does :mod:`venv`.
(Contributed by Miro Hrončok in :issue:`45413`.)


threading
---------

Expand Down Expand Up @@ -356,6 +374,20 @@ unicodedata
* The Unicode database has been updated to version 14.0.0. (:issue:`45190`).


venv
----

* When new Python virtual environments are created, the *venv*
:ref:`sysconfig installation scheme <installation_paths>` is used
to determine the paths inside the environment.
When Python runs in a virtual environment, the same installation scheme
is the default.
That means that downstream distributors can change the default sysconfig install
scheme without changing behavior of virtual environments.
Third party code that also creates new virtual environments should do the same.
(Contributed by Miro Hrončok in :issue:`45413`.)


fcntl
-----

Expand Down
47 changes: 47 additions & 0 deletions Lib/sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,53 @@
'scripts': '{base}/Scripts',
'data': '{base}',
},
# Downstream distributors can overwrite the default install scheme.
# This is done to support downstream modifications where distributors change
# the installation layout (eg. different site-packages directory).
# So, distributors will change the default scheme to one that correctly
# represents their layout.
# This presents an issue for projects/people that need to bootstrap virtual
# environments, like virtualenv. As distributors might now be customizing
# the default install scheme, there is no guarantee that the information
# returned by sysconfig.get_default_scheme/get_paths is correct for
# a virtual environment, the only guarantee we have is that it is correct
# for the *current* environment. When bootstrapping a virtual environment,
# we need to know its layout, so that we can place the files in the
# correct locations.
# The "*_venv" install scheme is a scheme to bootstrap virtual environments,
# essentially identical to the default posix_prefix/nt schemes.
# Downstream distributors who patch posix_prefix/nt scheme are encouraged to
# leave the following schemes unchanged
'posix_venv': {
'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}',
'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}',
'purelib': '{base}/lib/python{py_version_short}/site-packages',
'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages',
'include':
'{installed_base}/include/python{py_version_short}{abiflags}',
'platinclude':
'{installed_platbase}/include/python{py_version_short}{abiflags}',
'scripts': '{base}/bin',
'data': '{base}',
},
'nt_venv': {
'stdlib': '{installed_base}/Lib',
'platstdlib': '{base}/Lib',
'purelib': '{base}/Lib/site-packages',
'platlib': '{base}/Lib/site-packages',
'include': '{installed_base}/Include',
'platinclude': '{installed_base}/Include',
'scripts': '{base}/Scripts',
'data': '{base}',
},
}

# For the OS-native venv scheme, we essentially provide an alias:
if os.name == 'nt':
_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['nt_venv']
else:
_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']


# NOTE: site.py has copy of this function.
# Sync it when modify this function.
Expand Down Expand Up @@ -250,6 +295,8 @@ def _get_preferred_schemes():


def get_preferred_scheme(key):
if key == 'prefix' and sys.prefix != sys.base_prefix:
return 'venv'
scheme = _get_preferred_schemes()[key]
if scheme not in _INSTALL_SCHEMES:
raise ValueError(
Expand Down
68 changes: 67 additions & 1 deletion Lib/test/test_sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,72 @@ def test_get_preferred_schemes(self):
self.assertIsInstance(schemes, dict)
self.assertEqual(set(schemes), expected_schemes)

def test_posix_venv_scheme(self):
# The following directories were hardcoded in the venv module
# before bpo-45413, here we assert the posix_venv scheme does not regress
binpath = 'bin'
incpath = 'include'
libpath = os.path.join('lib',
'python%d.%d' % sys.version_info[:2],
'site-packages')

# Resolve the paths in prefix
binpath = os.path.join(sys.prefix, binpath)
incpath = os.path.join(sys.prefix, incpath)
libpath = os.path.join(sys.prefix, libpath)

self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv'))
self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv'))

# The include directory on POSIX isn't exactly the same as before,
# but it is "within"
sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv')
self.assertTrue(sysconfig_includedir.startswith(incpath + os.sep))

def test_nt_venv_scheme(self):
# The following directories were hardcoded in the venv module
# before bpo-45413, here we assert the posix_venv scheme does not regress
binpath = 'Scripts'
incpath = 'Include'
libpath = os.path.join('Lib', 'site-packages')

# Resolve the paths in prefix
binpath = os.path.join(sys.prefix, binpath)
incpath = os.path.join(sys.prefix, incpath)
libpath = os.path.join(sys.prefix, libpath)

self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv'))
self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv'))
self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv'))

def test_venv_scheme(self):
if sys.platform == 'win32':
self.assertEqual(
sysconfig.get_path('scripts', scheme='venv'),
sysconfig.get_path('scripts', scheme='nt_venv')
)
self.assertEqual(
sysconfig.get_path('include', scheme='venv'),
sysconfig.get_path('include', scheme='nt_venv')
)
self.assertEqual(
sysconfig.get_path('purelib', scheme='venv'),
sysconfig.get_path('purelib', scheme='nt_venv')
)
else:
self.assertEqual(
sysconfig.get_path('scripts', scheme='venv'),
sysconfig.get_path('scripts', scheme='posix_venv')
)
self.assertEqual(
sysconfig.get_path('include', scheme='venv'),
sysconfig.get_path('include', scheme='posix_venv')
)
self.assertEqual(
sysconfig.get_path('purelib', scheme='venv'),
sysconfig.get_path('purelib', scheme='posix_venv')
)

def test_get_config_vars(self):
cvars = get_config_vars()
self.assertIsInstance(cvars, dict)
Expand Down Expand Up @@ -267,7 +333,7 @@ def test_get_config_h_filename(self):
self.assertTrue(os.path.isfile(config_h), config_h)

def test_get_scheme_names(self):
wanted = ['nt', 'posix_home', 'posix_prefix']
wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv']
if HAS_USER_BASE:
wanted.extend(['nt_user', 'osx_framework_user', 'posix_user'])
self.assertEqual(get_scheme_names(), tuple(sorted(wanted)))
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,20 @@ def test_prefixes(self):
out, err = check_output(cmd)
self.assertEqual(out.strip(), expected.encode(), prefix)

@requireVenvCreate
def test_sysconfig_preferred_and_default_scheme(self):
"""
Test that the sysconfig preferred(prefix) and default scheme is venv.
"""
rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
cmd = [envpy, '-c', None]
for call in ('get_preferred_scheme("prefix")', 'get_default_scheme()'):
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
out, err = check_output(cmd)
self.assertEqual(out.strip(), b'venv', err)

if sys.platform == 'win32':
ENV_SUBDIRS = (
('Scripts',),
Expand Down
31 changes: 17 additions & 14 deletions Lib/venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ def clear_directory(self, path):
elif os.path.isdir(fn):
shutil.rmtree(fn)

def _venv_path(self, env_dir, name):
vars = {
'base': env_dir,
'platbase': env_dir,
'installed_base': env_dir,
'installed_platbase': env_dir,
}
return sysconfig.get_path(name, scheme='venv', vars=vars)

def ensure_directories(self, env_dir):
"""
Create the directories for the environment.
Expand Down Expand Up @@ -120,27 +129,21 @@ def create_if_needed(d):
context.executable = executable
context.python_dir = dirname
context.python_exe = exename
if sys.platform == 'win32':
binname = 'Scripts'
incpath = 'Include'
libpath = os.path.join(env_dir, 'Lib', 'site-packages')
else:
binname = 'bin'
incpath = 'include'
libpath = os.path.join(env_dir, 'lib',
'python%d.%d' % sys.version_info[:2],
'site-packages')
context.inc_path = path = os.path.join(env_dir, incpath)
create_if_needed(path)
binpath = self._venv_path(env_dir, 'scripts')
incpath = self._venv_path(env_dir, 'include')
libpath = self._venv_path(env_dir, 'purelib')

context.inc_path = incpath
create_if_needed(incpath)
create_if_needed(libpath)
# Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX
if ((sys.maxsize > 2**32) and (os.name == 'posix') and
(sys.platform != 'darwin')):
link_path = os.path.join(env_dir, 'lib64')
if not os.path.exists(link_path): # Issue #21643
os.symlink('lib', link_path)
context.bin_path = binpath = os.path.join(env_dir, binname)
context.bin_name = binname
context.bin_path = binpath
context.bin_name = os.path.relpath(binpath, env_dir)
context.env_exe = os.path.join(binpath, exename)
create_if_needed(binpath)
# Assign and update the command to use when launching the newly created
Expand Down
15 changes: 15 additions & 0 deletions Misc/NEWS.d/next/Library/2022-01-31-15-19-38.bpo-45413.1vaS0V.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Define *posix_venv* and *nt_venv*
:ref:`sysconfig installation schemes <installation_paths>`
to be used for bootstrapping new virtual environments.
Add *venv* sysconfig installation scheme to get the appropriate one of the above.
The schemes are identical to the pre-existing
*posix_prefix* and *nt* install schemes.
The :mod:`venv` module now uses the *venv* scheme to create new virtual environments
instead of hardcoding the paths depending only on the platform. Downstream
Python distributors customizing the *posix_prefix* or *nt* install
scheme in a way that is not compatible with the install scheme used in
virtual environments are encouraged not to customize the *venv* schemes.
When Python itself runs in a virtual environment,
:func:`sysconfig.get_default_scheme` and
:func:`sysconfig.get_preferred_scheme` with ``key="prefix"`` returns
*venv*.