Skip to content

install: correct sysconfig scheme lookup on Windows - #394

Open
lazka wants to merge 1 commit into
pypa:mainfrom
msys2-contrib:fix-nt-select-scheme
Open

lazka wants to merge 1 commit into
pypa:mainfrom
msys2-contrib:fix-nt-select-scheme

Conversation

@lazka

@lazka lazka commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

distutils by default passes os.name, so "nt", to _resolve_scheme() which results in the layout key being "", which results in get_preferred_scheme() raising, and "nt" being returned as as a fallback.

This is not correct in case sysconfig has a different scheme configuration, or in case we are installing into a venv, where the "venv" scheme should be selected and not "nt".

Fix by defaulting to the "prefix" layout in case no layout is given. This gives us "nt" from sysconfig, and "venv" in case we are in a venv. With CPython the change from "nt" to "venv" doesn't change anything for distutils since that only means "headers" are missing, which distutils injects later, resulting in the same scheme paths.

For MINGW Python which defaults to the "posix" scheme this fixes the default
install location (up until now MINGW Python patched the "nt" scheme to work
around this but we want to fix the real issue now)

@lazka

lazka commented Jan 11, 2026

Copy link
Copy Markdown
Contributor Author

Since it might not be obvious, here is the reason sysconfig returns "venv" in case "prefix" is passed: https://github.com/python/cpython/blob/23b93770f6e6c6db2eef9d9b9e6ca7741489ae99/Lib/sysconfig/__init__.py#L300-L301

distutils by default passes os.name, so "nt", to _resolve_scheme()
which results in the layout key being "", which results in
get_preferred_scheme() raising, and "nt" being returned as as a fallback.

This is not correct in case sysconfig has a different scheme configuration,
or in case we are installing into a venv, where the "venv" scheme should be
selected and not "nt".

Fix by defaulting to the "prefix" layout in case no layout is given.
This gives us "nt" from sysconfig, and "venv" in case we are in a venv.
With CPython the change from "nt" to "venv" doesn't change anything
for distutils since that only means "headers" are missing, which distutils
injects later, resulting in the same scheme paths.

For MINGW Python which defaults to the "posix" scheme this fixes the default
install location (up until now MINGW Python patched the "nt" scheme to work
around this but we want to fix the real issue now)
@MohammedAlkindi

Copy link
Copy Markdown

The premise holds, and the mechanism is narrower than the description suggests. _resolve_scheme passes key from name.partition('_'), which is the empty string for "nt" because that name has no underscore, and sysconfig.get_preferred_scheme('') raises KeyError(''). So every "nt" lookup takes the except path. With key or "prefix" it returns 'nt' outside a venv and 'venv' inside one.

Worth stating in the PR: this narrows the silent fallback rather than removing it. osx_framework_library gives key='framework_library', which still raises both before and after.

Test suite on Windows: 4 failed, 234 passed, identical failing set with and without the patch.

Measurements

Python 3.14.7, per scheme name, key taken from name.partition('_'):

name='nt'                    key=''                  before=KeyError('')                  after='nt'
name='posix_prefix'          key='prefix'            before='nt'                          after='nt'
name='nt_user'               key='user'              before='nt_user'                     after='nt_user'
name='posix_home'            key='home'              before='posix_home'                  after='posix_home'
name='osx_framework_library' key='framework_library' before=KeyError('framework_library') after=KeyError('framework_library')

Inside a venv (3.13.13): sys.prefix != sys.base_prefix is True and get_preferred_scheme('prefix') returns 'venv'.

Suite, unpatched and patched, identical set:
4 failed, 234 passed, 28 skipped, 2 xfailed
The 4 are machine-local, not this patch: two test_mingw.py cases fail FileNotFoundError [WinError 2] (no MinGW on this box) and two test_extension.py cases fail ImportError: DLL load failed ... Application Control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants