Skip to content

Commit ae1f5ca

Browse files
committed
env: override default scheme for Apple Python venv
1 parent f9a7186 commit ae1f5ca

3 files changed

Lines changed: 32 additions & 16 deletions

File tree

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Unreleased
88

99
- Add ``ProjectBuilder.metadata_path`` helper (`PR #303`_, Fixes `#301`_)
1010
- Added a ``build.__main__.build_package_via_sdist`` method (`PR #304`_)
11+
- Use appropriate installation scheme for Apple Python venvs (`PR #314`_, Fixes `#310`_)
1112

1213
Breaking Changes
1314
----------------
@@ -20,8 +21,10 @@ Breaking Changes
2021
.. _PR #303: https://github.com/pypa/build/pull/303
2122
.. _PR #304: https://github.com/pypa/build/pull/304
2223
.. _PR #312: https://github.com/pypa/build/pull/312
24+
.. _PR #314: https://github.com/pypa/build/pull/314
2325
.. _#257: https://github.com/pypa/build/issues/257
2426
.. _#301: https://github.com/pypa/build/issues/301
27+
.. _#310: https://github.com/pypa/build/issues/310
2528

2629

2730

src/build/env.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -246,20 +246,22 @@ def _find_executable_and_scripts(path): # type: (str) -> Tuple[str, str, str]
246246
"""
247247
config_vars = sysconfig.get_config_vars().copy() # globally cached, copy before altering it
248248
config_vars['base'] = path
249-
env_scripts = sysconfig.get_path('scripts', vars=config_vars)
250-
if not env_scripts:
251-
raise RuntimeError("Couldn't get environment scripts path")
252-
exe = 'pypy3' if platform.python_implementation() == 'PyPy' else 'python'
253-
if os.name == 'nt':
254-
exe = '{}.exe'.format(exe)
255-
executable = os.path.join(env_scripts, exe)
249+
# The Python that ships with the macOS developer tools varies the
250+
# default scheme depending on whether the ``sys.prefix`` is part of a framework.
251+
# The framework "osx_framework_library" scheme
252+
# can't be used to expand the paths in a venv, which
253+
# can happen if build itself is not installed in a venv.
254+
# If the Apple-custom "osx_framework_library" scheme is available
255+
# we enforce "posix_prefix", the venv scheme, for isolated envs.
256+
if 'osx_framework_library' in sysconfig.get_scheme_names():
257+
paths = sysconfig.get_paths(scheme='posix_prefix', vars=config_vars)
258+
else:
259+
paths = sysconfig.get_paths(vars=config_vars)
260+
executable = os.path.join(paths['scripts'], 'python.exe' if os.name == 'nt' else 'python')
256261
if not os.path.exists(executable):
257262
raise RuntimeError('Virtual environment creation failed, executable {} missing'.format(executable))
258263

259-
purelib = sysconfig.get_path('purelib', vars=config_vars)
260-
if not purelib:
261-
raise RuntimeError("Couldn't get environment purelib folder")
262-
return executable, env_scripts, purelib
264+
return executable, paths['scripts'], paths['purelib']
263265

264266

265267
__all__ = (

tests/test_env.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,32 @@ def test_isolated_environment_install(mocker):
5151
]
5252

5353

54+
@pytest.mark.skipif(IS_PY2, reason='venv module used on Python 3 only')
55+
@pytest.mark.skipif(IS_PYPY3, reason='PyPy3 uses get path to create and provision venv')
56+
@pytest.mark.skipif(sys.platform != 'darwin', reason='workaround for Apple Python')
57+
def test_can_get_venv_paths_with_conflicting_default_scheme(mocker):
58+
mocker.patch.object(build.env, 'virtualenv', None)
59+
get_scheme_names = mocker.patch('sysconfig.get_scheme_names', return_value=('osx_framework_library',))
60+
with build.env.IsolatedEnvBuilder():
61+
pass
62+
assert get_scheme_names.call_count == 1
63+
64+
5465
@pytest.mark.skipif(IS_PY2, reason='venv module used on Python 3 only')
5566
@pytest.mark.skipif(IS_PYPY3, reason='PyPy3 uses get path to create and provision venv')
5667
def test_executable_missing_post_creation(mocker):
5768
mocker.patch.object(build.env, 'virtualenv', None)
58-
original_get_path = sysconfig.get_path
69+
original_get_paths = sysconfig.get_paths
5970

60-
def _get_path(name, vars): # noqa
71+
def _get_paths(vars): # noqa
6172
shutil.rmtree(vars['base'])
62-
return original_get_path(name, vars=vars)
73+
return original_get_paths(vars=vars)
6374

64-
get_path = mocker.patch('sysconfig.get_path', side_effect=_get_path)
75+
get_paths = mocker.patch('sysconfig.get_paths', side_effect=_get_paths)
6576
with pytest.raises(RuntimeError, match='Virtual environment creation failed, executable .* missing'):
6677
with build.env.IsolatedEnvBuilder():
6778
pass
68-
assert get_path.call_count == 1
79+
assert get_paths.call_count == 1
6980

7081

7182
def test_isolated_env_abstract():

0 commit comments

Comments
 (0)