diff --git a/mercurial/exewrapper.c b/mercurial/exewrapper.c --- a/mercurial/exewrapper.c +++ b/mercurial/exewrapper.c @@ -29,6 +29,134 @@ #define _countof(array) (sizeof(array) / sizeof(array[0])) #endif +#if PY_MAJOR_VERSION >= 3 + +#pragma comment(lib, "Advapi32.lib") + +/* python.org installations */ +#define CORE_PATH L"SOFTWARE\\Python\\PythonCore" + +/* Microsoft Store installations */ +#define LOOKASIDE_PATH \ + L"SOFTWARE\\Microsoft\\AppModel\\Lookaside\\user\\Software\\Python\\" \ + L"PythonCore" + +static wchar_t *_locate_python_for_key(HKEY root, LPCWSTR subkey, size_t *size) +{ + wchar_t installPathKey[512]; + wchar_t *executable = NULL; + DWORD type; + DWORD sz = 0; + HKEY ip_key; + LSTATUS status; + + _snwprintf_s(installPathKey, sizeof(installPathKey), _TRUNCATE, + L"%ls\\%d.%d\\InstallPath", subkey, PY_MAJOR_VERSION, + PY_MINOR_VERSION); + + status = + RegOpenKeyExW(root, installPathKey, 0, KEY_QUERY_VALUE, &ip_key); + + if (status != ERROR_SUCCESS) + return NULL; + + status = RegQueryValueExW(ip_key, L"ExecutablePath", NULL, &type, + (LPBYTE)executable, &sz); + if (status == ERROR_SUCCESS) { + /* Allocate extra space so path\to\python.exe can be converted + * to path\to\python39.dll + NUL. + */ + *size = sz + sizeof(_T(HGPYTHONLIB ".dll")) + sizeof(wchar_t); + executable = malloc(*size); + + if (executable) { + status = + RegQueryValueExW(ip_key, L"ExecutablePath", NULL, + &type, (LPBYTE)executable, &sz); + + if (status != ERROR_SUCCESS) { + free(executable); + executable = NULL; + } else { + /* Not all values are stored NUL terminated */ + executable[sz] = L'\0'; + } + } + } + + RegCloseKey(ip_key); + return executable; +} + +static HMODULE load_system_py3(void) +{ + wchar_t *subkeys[] = {CORE_PATH, LOOKASIDE_PATH}; + HKEY roots[] = {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}; + + /* Give priority to python.org installs, because MS Store installs can + * break with user profile corruption, and also use some NTFS feature + * that MSYS doesn't understand. + */ + for (int s = 0; s < _countof(subkeys); s++) { + for (int r = 0; r < _countof(roots); r++) { + size_t size = 0; + wchar_t *py = + _locate_python_for_key(roots[r], subkeys[s], &size); + wchar_t *cut = NULL; + HMODULE pydll; + + if (!py) { + continue; + } + + /* Cut off the python executable component */ + cut = wcsrchr(py, L'\\'); + if (cut == NULL) { + free(py); + continue; + } + *cut = 0; + + wcscat_s(py, size, _T("\\" HGPYTHONLIB ".dll")); + + pydll = LoadLibrary(py); + + /* Also load python3.dll, so we don't pick up a random + * one on PATH. We don't search {sys.prefix}\DLLs like + * python.exe because this is commented as "not been a + * normal install layout for a while", and don't search + * LOAD_LIBRARY_SEARCH_APPLICATION_DIR because it's not + * clear what the use case is. + */ + if (pydll != NULL) { + HANDLE py3dll = NULL; + + *cut = 0; + wcscat_s(py, size, L"\\python3.dll"); + + py3dll = LoadLibrary(py); + if (py3dll == NULL) { + const char *err; + err = "failed to load python3.dll " + "for " HGPYTHONLIB ".dll"; + fprintf(stderr, "abort: %s (0x%X)\n", + err, GetLastError()); + exit(255); + } + } + + free(py); + + if (pydll != NULL) { + return pydll; + } + } + } + + return NULL; +} +#endif + static TCHAR pyscript[MAX_PATH + 10]; static TCHAR pyhome[MAX_PATH + 10]; static TCHAR pydllfile[MAX_PATH + 10]; @@ -108,6 +236,12 @@ Py_SetPythonHome(pyhome); } +#if PY_MAJOR_VERSION >= 3 + if (pydll == NULL) { + pydll = load_system_py3(); + } +#endif + if (pydll == NULL) { pydll = LoadLibrary(_T(HGPYTHONLIB) _T(".dll")); if (pydll == NULL) {