[Python-checkins] bpo-37369: Fixes path for sys.executable when running from the Microsoft Store (GH-14450)

Steve Dower webhook-mailer at python.org
Fri Jun 28 13:02:19 EDT 2019


https://github.com/python/cpython/commit/db4d7ddb012ef8f087a8eb2a5b8a672d04a48e1a
commit: db4d7ddb012ef8f087a8eb2a5b8a672d04a48e1a
branch: 3.7
author: Steve Dower <steve.dower at python.org>
committer: GitHub <noreply at github.com>
date: 2019-06-28T10:02:13-07:00
summary:

bpo-37369: Fixes path for sys.executable when running from the Microsoft Store (GH-14450)

files:
A Misc/NEWS.d/next/Windows/2019-06-28-08-09-08.bpo-37369.1iVpxq.rst
M Lib/site.py
M Lib/test/test_venv.py
M Lib/venv/__init__.py
M PC/getpathp.c
M PC/python_uwp.cpp
M Python/sysmodule.c

diff --git a/Lib/site.py b/Lib/site.py
index ad1146332b0a..878658827ca5 100644
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -458,13 +458,6 @@ def venv(known_paths):
     env = os.environ
     if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env:
         executable = sys._base_executable = os.environ['__PYVENV_LAUNCHER__']
-    elif sys.platform == 'win32' and '__PYVENV_LAUNCHER__' in env:
-        executable = sys.executable
-        import _winapi
-        sys._base_executable = _winapi.GetModuleFileName(0)
-        # bpo-35873: Clear the environment variable to avoid it being
-        # inherited by child processes.
-        del os.environ['__PYVENV_LAUNCHER__']
     else:
         executable = sys.executable
     exe_dir, _ = os.path.split(os.path.abspath(executable))
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index a8dc59cb81c3..c3ccb9291381 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -127,10 +127,6 @@ def test_prefixes(self):
         """
         Test that the prefix values are as expected.
         """
-        #check our prefixes
-        self.assertEqual(sys.base_prefix, sys.prefix)
-        self.assertEqual(sys.base_exec_prefix, sys.exec_prefix)
-
         # check a venv's prefixes
         rmtree(self.env_dir)
         self.run_with_capture(venv.create, self.env_dir)
@@ -139,8 +135,8 @@ def test_prefixes(self):
         for prefix, expected in (
             ('prefix', self.env_dir),
             ('prefix', self.env_dir),
-            ('base_prefix', sys.prefix),
-            ('base_exec_prefix', sys.exec_prefix)):
+            ('base_prefix', sys.base_prefix),
+            ('base_exec_prefix', sys.base_exec_prefix)):
             cmd[2] = 'import sys; print(sys.%s)' % prefix
             out, err = check_output(cmd)
             self.assertEqual(out.strip(), expected.encode())
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index 7836dfba04ca..95c05486af10 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -243,7 +243,7 @@ def setup_python(self, context):
 
             for suffix in suffixes:
                 src = os.path.join(dirname, suffix)
-                if os.path.exists(src):
+                if os.path.lexists(src):
                     copier(src, os.path.join(binpath, suffix))
 
             if sysconfig.is_python_build(True):
diff --git a/Misc/NEWS.d/next/Windows/2019-06-28-08-09-08.bpo-37369.1iVpxq.rst b/Misc/NEWS.d/next/Windows/2019-06-28-08-09-08.bpo-37369.1iVpxq.rst
new file mode 100644
index 000000000000..5eaed61a9261
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2019-06-28-08-09-08.bpo-37369.1iVpxq.rst
@@ -0,0 +1 @@
+Fixes path for :data:`sys.executable` when running from the Microsoft Store.
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 1b553d53affa..04764c9e5a0d 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -540,14 +540,20 @@ get_program_full_path(const _PyCoreConfig *core_config,
     wchar_t program_full_path[MAXPATHLEN+1];
     memset(program_full_path, 0, sizeof(program_full_path));
 
+    if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
+        /* GetModuleFileName should never fail when passed NULL */
+        return _Py_INIT_ERR("Cannot determine program path");
+    }
+
     /* The launcher may need to force the executable path to a
      * different environment, so override it here. */
     pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__");
     if (pyvenv_launcher && pyvenv_launcher[0]) {
+        _wputenv_s(L"__PYVENV_BASE_EXECUTABLE__", program_full_path);
         wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher);
-    } else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
-        /* GetModuleFileName should never fail when passed NULL */
-        return _Py_INIT_ERR("Cannot determine program path");
+        /* bpo-35873: Clear the environment variable to avoid it being
+         * inherited by child processes. */
+        _wputenv_s(L"__PYVENV_LAUNCHER__", L"");
     }
 
     config->program_full_path = PyMem_RawMalloc(
diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp
index 5c8caa6666c4..166a977cfbd4 100644
--- a/PC/python_uwp.cpp
+++ b/PC/python_uwp.cpp
@@ -6,6 +6,9 @@
 #define WIN32_LEAN_AND_MEAN
 #include <Windows.h>
 #include <shellapi.h>
+#include <shlobj.h>
+
+#include <string>
 
 #include <winrt\Windows.ApplicationModel.h>
 #include <winrt\Windows.Storage.h>
@@ -46,170 +49,129 @@ set_user_base()
     }
 }
 
-static const wchar_t *
-get_argv0(const wchar_t *argv0)
+static winrt::hstring
+get_package_family()
 {
-    winrt::hstring installPath;
-    const wchar_t *launcherPath;
-    wchar_t *buffer;
-    size_t len;
-
-    launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__");
-    if (launcherPath && launcherPath[0]) {
-        len = wcslen(launcherPath) + 1;
-        buffer = (wchar_t *)malloc(sizeof(wchar_t) * len);
-        if (!buffer) {
-            Py_FatalError("out of memory");
-            return NULL;
-        }
-        if (wcscpy_s(buffer, len, launcherPath)) {
-            Py_FatalError("failed to copy to buffer");
-            return NULL;
-        }
-        return buffer;
-    }
-
     try {
         const auto package = winrt::Windows::ApplicationModel::Package::Current();
         if (package) {
-            const auto install = package.InstalledLocation();
-            if (install) {
-                installPath = install.Path();
-            }
+            const auto id = package.Id();
+            return id ? id.FamilyName() : winrt::hstring();
         }
     }
     catch (...) {
     }
 
-    if (!installPath.empty()) {
-        len = installPath.size() + wcslen(PROGNAME) + 2;
-    } else {
-        len = wcslen(argv0) + wcslen(PROGNAME) + 1;
-    }
+    return winrt::hstring();
+}
 
-    buffer = (wchar_t *)malloc(sizeof(wchar_t) * len);
-    if (!buffer) {
-        Py_FatalError("out of memory");
-        return NULL;
-    }
+static int
+set_process_name()
+{
+    std::wstring executable, home;
 
-    if (!installPath.empty()) {
-        if (wcscpy_s(buffer, len, installPath.c_str())) {
-            Py_FatalError("failed to copy to buffer");
-            return NULL;
-        }
-        if (wcscat_s(buffer, len, L"\\")) {
-            Py_FatalError("failed to concatenate backslash");
-            return NULL;
-        }
-    } else {
-        if (wcscpy_s(buffer, len, argv0)) {
-            Py_FatalError("failed to copy argv[0]");
-            return NULL;
+    const auto family = get_package_family();
+
+    if (!family.empty()) {
+        PWSTR localAppData;
+        if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0,
+                                           NULL, &localAppData))) {
+            executable = std::wstring(localAppData)
+                         + L"\\Microsoft\\WindowsApps\\"
+                         + family
+                         + L"\\"
+                         + PROGNAME;
+
+            CoTaskMemFree(localAppData);
         }
+    }
 
-        wchar_t *name = wcsrchr(buffer, L'\\');
-        if (name) {
-            name[1] = L'\0';
+    home.resize(MAX_PATH);
+    while (true) {
+        DWORD len = GetModuleFileNameW(
+            NULL, home.data(), (DWORD)home.size());
+        if (len == 0) {
+            home.clear();
+            break;
+        } else if (len == home.size() &&
+                   GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+            home.resize(len * 2);
         } else {
-            buffer[0] = L'\0';
+            home.resize(len);
+            size_t bslash = home.find_last_of(L"/\\");
+            if (bslash != std::wstring::npos) {
+                home.erase(bslash);
+            }
+            break;
         }
     }
 
-    if (wcscat_s(buffer, len, PROGNAME)) {
-        Py_FatalError("failed to concatenate program name");
-        return NULL;
+    if (executable.empty() && !home.empty()) {
+        executable = home + L"\\" + PROGNAME;
     }
 
-    return buffer;
-}
-
-static wchar_t *
-get_process_name()
-{
-    DWORD bufferLen = MAX_PATH;
-    DWORD len = bufferLen;
-    wchar_t *r = NULL;
-
-    while (!r) {
-        r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t));
-        if (!r) {
-            Py_FatalError("out of memory");
-            return NULL;
-        }
-        len = GetModuleFileNameW(NULL, r, bufferLen);
-        if (len == 0) {
-            free((void *)r);
-            return NULL;
-        } else if (len == bufferLen &&
-                   GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
-            free(r);
-            r = NULL;
-            bufferLen *= 2;
+    if (!home.empty()) {
+        Py_SetPythonHome(home.c_str());
+    }
+    if (!executable.empty()) {
+        const wchar_t *launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__");
+        if (launcherPath) {
+            _wputenv_s(L"__PYVENV_BASE_EXECUTABLE__", executable.c_str());
+            _Py_SetProgramFullPath(launcherPath);
+            /* bpo-35873: Clear the environment variable to avoid it being
+             * inherited by child processes. */
+            _wputenv_s(L"__PYVENV_LAUNCHER__", L"");
+        } else {
+            _Py_SetProgramFullPath(executable.c_str());
         }
     }
 
-    return r;
+    return 1;
 }
 
 int
 wmain(int argc, wchar_t **argv)
 {
-    const wchar_t **new_argv;
-    int new_argc;
-    const wchar_t *exeName;
-
-    new_argc = argc;
-    new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2));
-    if (new_argv == NULL) {
-        Py_FatalError("out of memory");
-        return -1;
+    if (!set_process_name()) {
+        return 121;
     }
+    set_user_base();
 
-    exeName = get_process_name();
-
-    new_argv[0] = get_argv0(exeName ? exeName : argv[0]);
-    for (int i = 1; i < argc; ++i) {
-        new_argv[i] = argv[i];
+    const wchar_t *p = wcsrchr(argv[0], L'\\');
+    if (!p) {
+        p = argv[0];
     }
+    if (p) {
+        if (*p == L'\\') {
+            p++;
+        }
 
-    set_user_base();
-
-    if (exeName) {
-        const wchar_t *p = wcsrchr(exeName, L'\\');
-        if (p) {
-            const wchar_t *moduleName = NULL;
-            if (*p++ == L'\\') {
-                if (wcsnicmp(p, L"pip", 3) == 0) {
-                    moduleName = L"pip";
-                    _wputenv_s(L"PIP_USER", L"true");
-                }
-                else if (wcsnicmp(p, L"idle", 4) == 0) {
-                    moduleName = L"idlelib";
-                }
-            }
+        const wchar_t *moduleName = NULL;
+        if (wcsnicmp(p, L"pip", 3) == 0) {
+            moduleName = L"pip";
+            /* No longer required when pip 19.1 is added */
+            _wputenv_s(L"PIP_USER", L"true");
+        } else if (wcsnicmp(p, L"idle", 4) == 0) {
+            moduleName = L"idlelib";
+        }
 
-            if (moduleName) {
-                new_argc += 2;
-                for (int i = argc; i >= 1; --i) {
-                    new_argv[i + 2] = new_argv[i];
-                }
-                new_argv[1] = L"-m";
-                new_argv[2] = moduleName;
+        if (moduleName) {
+            /* Not even pretending we're going to free this memory.
+             * The OS will clean it all up when our process exits
+             */
+            wchar_t **new_argv = (wchar_t **)PyMem_RawMalloc((argc + 2) * sizeof(wchar_t *));
+            new_argv[0] = argv[0];
+            new_argv[1] = _PyMem_RawWcsdup(L"-m");
+            new_argv[2] = _PyMem_RawWcsdup(moduleName);
+            for (int i = 1; i < argc; ++i) {
+                new_argv[i + 2] = argv[i];
             }
+            argv = new_argv;
+            argc += 2;
         }
     }
 
-    /* Override program_full_path from here so that
-       sys.executable is set correctly. */
-    _Py_SetProgramFullPath(new_argv[0]);
-
-    int result = Py_Main(new_argc, (wchar_t **)new_argv);
-
-    free((void *)exeName);
-    free((void *)new_argv);
-
-    return result;
+    return Py_Main(argc, (wchar_t**)argv);
 }
 
 #ifdef PYTHONW
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index d87b4e2c01b3..942a8b6becd9 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2448,8 +2448,6 @@ _PySys_BeginInit(PyObject **sysmod)
     return _Py_INIT_ERR("can't initialize sys module");
 }
 
-#undef SET_SYS_FROM_STRING
-
 /* Updating the sys namespace, returning integer error codes */
 #define SET_SYS_FROM_STRING_INT_RESULT(key, value)         \
     do {                                                   \
@@ -2483,6 +2481,17 @@ _PySys_EndInit(PyObject *sysdict, _PyMainInterpreterConfig *config)
     SET_SYS_FROM_STRING_BORROW("exec_prefix", config->exec_prefix);
     SET_SYS_FROM_STRING_BORROW("base_exec_prefix", config->base_exec_prefix);
 
+#ifdef MS_WINDOWS
+    const wchar_t *baseExecutable = _wgetenv(L"__PYVENV_BASE_EXECUTABLE__");
+    if (baseExecutable) {
+        SET_SYS_FROM_STRING("_base_executable",
+                            PyUnicode_FromWideChar(baseExecutable, -1));
+        _wputenv_s(L"__PYVENV_BASE_EXECUTABLE__", L"");
+    } else {
+        SET_SYS_FROM_STRING_BORROW("_base_executable", config->executable);
+    }
+#endif
+
     if (config->argv != NULL) {
         SET_SYS_FROM_STRING_BORROW("argv", config->argv);
     }
@@ -2528,6 +2537,7 @@ _PySys_EndInit(PyObject *sysdict, _PyMainInterpreterConfig *config)
     return -1;
 }
 
+#undef SET_SYS_FROM_STRING
 #undef SET_SYS_FROM_STRING_BORROW
 #undef SET_SYS_FROM_STRING_INT_RESULT
 



More information about the Python-checkins mailing list