Skip to content

Commit 3c34ea9

Browse files
zoobaned-deily
authored andcommitted
bpo-37369: Fixes path for sys.executable when running from the Microsoft Store (GH-14450)
1 parent cc0bf97 commit 3c34ea9

7 files changed

Lines changed: 115 additions & 147 deletions

File tree

Lib/site.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -458,13 +458,6 @@ def venv(known_paths):
458458
env = os.environ
459459
if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env:
460460
executable = sys._base_executable = os.environ['__PYVENV_LAUNCHER__']
461-
elif sys.platform == 'win32' and '__PYVENV_LAUNCHER__' in env:
462-
executable = sys.executable
463-
import _winapi
464-
sys._base_executable = _winapi.GetModuleFileName(0)
465-
# bpo-35873: Clear the environment variable to avoid it being
466-
# inherited by child processes.
467-
del os.environ['__PYVENV_LAUNCHER__']
468461
else:
469462
executable = sys.executable
470463
exe_dir, _ = os.path.split(os.path.abspath(executable))

Lib/test/test_venv.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,6 @@ def test_prefixes(self):
127127
"""
128128
Test that the prefix values are as expected.
129129
"""
130-
#check our prefixes
131-
self.assertEqual(sys.base_prefix, sys.prefix)
132-
self.assertEqual(sys.base_exec_prefix, sys.exec_prefix)
133-
134130
# check a venv's prefixes
135131
rmtree(self.env_dir)
136132
self.run_with_capture(venv.create, self.env_dir)
@@ -139,8 +135,8 @@ def test_prefixes(self):
139135
for prefix, expected in (
140136
('prefix', self.env_dir),
141137
('prefix', self.env_dir),
142-
('base_prefix', sys.prefix),
143-
('base_exec_prefix', sys.exec_prefix)):
138+
('base_prefix', sys.base_prefix),
139+
('base_exec_prefix', sys.base_exec_prefix)):
144140
cmd[2] = 'import sys; print(sys.%s)' % prefix
145141
out, err = check_output(cmd)
146142
self.assertEqual(out.strip(), expected.encode())

Lib/venv/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def setup_python(self, context):
243243

244244
for suffix in suffixes:
245245
src = os.path.join(dirname, suffix)
246-
if os.path.exists(src):
246+
if os.path.lexists(src):
247247
copier(src, os.path.join(binpath, suffix))
248248

249249
if sysconfig.is_python_build(True):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes path for :data:`sys.executable` when running from the Microsoft Store.

PC/getpathp.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,14 +540,20 @@ get_program_full_path(const _PyCoreConfig *core_config,
540540
wchar_t program_full_path[MAXPATHLEN+1];
541541
memset(program_full_path, 0, sizeof(program_full_path));
542542

543+
if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
544+
/* GetModuleFileName should never fail when passed NULL */
545+
return _Py_INIT_ERR("Cannot determine program path");
546+
}
547+
543548
/* The launcher may need to force the executable path to a
544549
* different environment, so override it here. */
545550
pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__");
546551
if (pyvenv_launcher && pyvenv_launcher[0]) {
552+
_wputenv_s(L"__PYVENV_BASE_EXECUTABLE__", program_full_path);
547553
wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher);
548-
} else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
549-
/* GetModuleFileName should never fail when passed NULL */
550-
return _Py_INIT_ERR("Cannot determine program path");
554+
/* bpo-35873: Clear the environment variable to avoid it being
555+
* inherited by child processes. */
556+
_wputenv_s(L"__PYVENV_LAUNCHER__", L"");
551557
}
552558

553559
config->program_full_path = PyMem_RawMalloc(

PC/python_uwp.cpp

Lines changed: 90 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#define WIN32_LEAN_AND_MEAN
77
#include <Windows.h>
88
#include <shellapi.h>
9+
#include <shlobj.h>
10+
11+
#include <string>
912

1013
#include <winrt\Windows.ApplicationModel.h>
1114
#include <winrt\Windows.Storage.h>
@@ -46,170 +49,129 @@ set_user_base()
4649
}
4750
}
4851

49-
static const wchar_t *
50-
get_argv0(const wchar_t *argv0)
52+
static winrt::hstring
53+
get_package_family()
5154
{
52-
winrt::hstring installPath;
53-
const wchar_t *launcherPath;
54-
wchar_t *buffer;
55-
size_t len;
56-
57-
launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__");
58-
if (launcherPath && launcherPath[0]) {
59-
len = wcslen(launcherPath) + 1;
60-
buffer = (wchar_t *)malloc(sizeof(wchar_t) * len);
61-
if (!buffer) {
62-
Py_FatalError("out of memory");
63-
return NULL;
64-
}
65-
if (wcscpy_s(buffer, len, launcherPath)) {
66-
Py_FatalError("failed to copy to buffer");
67-
return NULL;
68-
}
69-
return buffer;
70-
}
71-
7255
try {
7356
const auto package = winrt::Windows::ApplicationModel::Package::Current();
7457
if (package) {
75-
const auto install = package.InstalledLocation();
76-
if (install) {
77-
installPath = install.Path();
78-
}
58+
const auto id = package.Id();
59+
return id ? id.FamilyName() : winrt::hstring();
7960
}
8061
}
8162
catch (...) {
8263
}
8364

84-
if (!installPath.empty()) {
85-
len = installPath.size() + wcslen(PROGNAME) + 2;
86-
} else {
87-
len = wcslen(argv0) + wcslen(PROGNAME) + 1;
88-
}
65+
return winrt::hstring();
66+
}
8967

90-
buffer = (wchar_t *)malloc(sizeof(wchar_t) * len);
91-
if (!buffer) {
92-
Py_FatalError("out of memory");
93-
return NULL;
94-
}
68+
static int
69+
set_process_name()
70+
{
71+
std::wstring executable, home;
9572

96-
if (!installPath.empty()) {
97-
if (wcscpy_s(buffer, len, installPath.c_str())) {
98-
Py_FatalError("failed to copy to buffer");
99-
return NULL;
100-
}
101-
if (wcscat_s(buffer, len, L"\\")) {
102-
Py_FatalError("failed to concatenate backslash");
103-
return NULL;
104-
}
105-
} else {
106-
if (wcscpy_s(buffer, len, argv0)) {
107-
Py_FatalError("failed to copy argv[0]");
108-
return NULL;
73+
const auto family = get_package_family();
74+
75+
if (!family.empty()) {
76+
PWSTR localAppData;
77+
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0,
78+
NULL, &localAppData))) {
79+
executable = std::wstring(localAppData)
80+
+ L"\\Microsoft\\WindowsApps\\"
81+
+ family
82+
+ L"\\"
83+
+ PROGNAME;
84+
85+
CoTaskMemFree(localAppData);
10986
}
87+
}
11088

111-
wchar_t *name = wcsrchr(buffer, L'\\');
112-
if (name) {
113-
name[1] = L'\0';
89+
home.resize(MAX_PATH);
90+
while (true) {
91+
DWORD len = GetModuleFileNameW(
92+
NULL, home.data(), (DWORD)home.size());
93+
if (len == 0) {
94+
home.clear();
95+
break;
96+
} else if (len == home.size() &&
97+
GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
98+
home.resize(len * 2);
11499
} else {
115-
buffer[0] = L'\0';
100+
home.resize(len);
101+
size_t bslash = home.find_last_of(L"/\\");
102+
if (bslash != std::wstring::npos) {
103+
home.erase(bslash);
104+
}
105+
break;
116106
}
117107
}
118108

119-
if (wcscat_s(buffer, len, PROGNAME)) {
120-
Py_FatalError("failed to concatenate program name");
121-
return NULL;
109+
if (executable.empty() && !home.empty()) {
110+
executable = home + L"\\" + PROGNAME;
122111
}
123112

124-
return buffer;
125-
}
126-
127-
static wchar_t *
128-
get_process_name()
129-
{
130-
DWORD bufferLen = MAX_PATH;
131-
DWORD len = bufferLen;
132-
wchar_t *r = NULL;
133-
134-
while (!r) {
135-
r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t));
136-
if (!r) {
137-
Py_FatalError("out of memory");
138-
return NULL;
139-
}
140-
len = GetModuleFileNameW(NULL, r, bufferLen);
141-
if (len == 0) {
142-
free((void *)r);
143-
return NULL;
144-
} else if (len == bufferLen &&
145-
GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
146-
free(r);
147-
r = NULL;
148-
bufferLen *= 2;
113+
if (!home.empty()) {
114+
Py_SetPythonHome(home.c_str());
115+
}
116+
if (!executable.empty()) {
117+
const wchar_t *launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__");
118+
if (launcherPath) {
119+
_wputenv_s(L"__PYVENV_BASE_EXECUTABLE__", executable.c_str());
120+
_Py_SetProgramFullPath(launcherPath);
121+
/* bpo-35873: Clear the environment variable to avoid it being
122+
* inherited by child processes. */
123+
_wputenv_s(L"__PYVENV_LAUNCHER__", L"");
124+
} else {
125+
_Py_SetProgramFullPath(executable.c_str());
149126
}
150127
}
151128

152-
return r;
129+
return 1;
153130
}
154131

155132
int
156133
wmain(int argc, wchar_t **argv)
157134
{
158-
const wchar_t **new_argv;
159-
int new_argc;
160-
const wchar_t *exeName;
161-
162-
new_argc = argc;
163-
new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2));
164-
if (new_argv == NULL) {
165-
Py_FatalError("out of memory");
166-
return -1;
135+
if (!set_process_name()) {
136+
return 121;
167137
}
138+
set_user_base();
168139

169-
exeName = get_process_name();
170-
171-
new_argv[0] = get_argv0(exeName ? exeName : argv[0]);
172-
for (int i = 1; i < argc; ++i) {
173-
new_argv[i] = argv[i];
140+
const wchar_t *p = wcsrchr(argv[0], L'\\');
141+
if (!p) {
142+
p = argv[0];
174143
}
144+
if (p) {
145+
if (*p == L'\\') {
146+
p++;
147+
}
175148

176-
set_user_base();
177-
178-
if (exeName) {
179-
const wchar_t *p = wcsrchr(exeName, L'\\');
180-
if (p) {
181-
const wchar_t *moduleName = NULL;
182-
if (*p++ == L'\\') {
183-
if (wcsnicmp(p, L"pip", 3) == 0) {
184-
moduleName = L"pip";
185-
_wputenv_s(L"PIP_USER", L"true");
186-
}
187-
else if (wcsnicmp(p, L"idle", 4) == 0) {
188-
moduleName = L"idlelib";
189-
}
190-
}
149+
const wchar_t *moduleName = NULL;
150+
if (wcsnicmp(p, L"pip", 3) == 0) {
151+
moduleName = L"pip";
152+
/* No longer required when pip 19.1 is added */
153+
_wputenv_s(L"PIP_USER", L"true");
154+
} else if (wcsnicmp(p, L"idle", 4) == 0) {
155+
moduleName = L"idlelib";
156+
}
191157

192-
if (moduleName) {
193-
new_argc += 2;
194-
for (int i = argc; i >= 1; --i) {
195-
new_argv[i + 2] = new_argv[i];
196-
}
197-
new_argv[1] = L"-m";
198-
new_argv[2] = moduleName;
158+
if (moduleName) {
159+
/* Not even pretending we're going to free this memory.
160+
* The OS will clean it all up when our process exits
161+
*/
162+
wchar_t **new_argv = (wchar_t **)PyMem_RawMalloc((argc + 2) * sizeof(wchar_t *));
163+
new_argv[0] = argv[0];
164+
new_argv[1] = _PyMem_RawWcsdup(L"-m");
165+
new_argv[2] = _PyMem_RawWcsdup(moduleName);
166+
for (int i = 1; i < argc; ++i) {
167+
new_argv[i + 2] = argv[i];
199168
}
169+
argv = new_argv;
170+
argc += 2;
200171
}
201172
}
202173

203-
/* Override program_full_path from here so that
204-
sys.executable is set correctly. */
205-
_Py_SetProgramFullPath(new_argv[0]);
206-
207-
int result = Py_Main(new_argc, (wchar_t **)new_argv);
208-
209-
free((void *)exeName);
210-
free((void *)new_argv);
211-
212-
return result;
174+
return Py_Main(argc, (wchar_t**)argv);
213175
}
214176

215177
#ifdef PYTHONW

Python/sysmodule.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2448,8 +2448,6 @@ _PySys_BeginInit(PyObject **sysmod)
24482448
return _Py_INIT_ERR("can't initialize sys module");
24492449
}
24502450

2451-
#undef SET_SYS_FROM_STRING
2452-
24532451
/* Updating the sys namespace, returning integer error codes */
24542452
#define SET_SYS_FROM_STRING_INT_RESULT(key, value) \
24552453
do { \
@@ -2483,6 +2481,17 @@ _PySys_EndInit(PyObject *sysdict, _PyMainInterpreterConfig *config)
24832481
SET_SYS_FROM_STRING_BORROW("exec_prefix", config->exec_prefix);
24842482
SET_SYS_FROM_STRING_BORROW("base_exec_prefix", config->base_exec_prefix);
24852483

2484+
#ifdef MS_WINDOWS
2485+
const wchar_t *baseExecutable = _wgetenv(L"__PYVENV_BASE_EXECUTABLE__");
2486+
if (baseExecutable) {
2487+
SET_SYS_FROM_STRING("_base_executable",
2488+
PyUnicode_FromWideChar(baseExecutable, -1));
2489+
_wputenv_s(L"__PYVENV_BASE_EXECUTABLE__", L"");
2490+
} else {
2491+
SET_SYS_FROM_STRING_BORROW("_base_executable", config->executable);
2492+
}
2493+
#endif
2494+
24862495
if (config->argv != NULL) {
24872496
SET_SYS_FROM_STRING_BORROW("argv", config->argv);
24882497
}
@@ -2528,6 +2537,7 @@ _PySys_EndInit(PyObject *sysdict, _PyMainInterpreterConfig *config)
25282537
return -1;
25292538
}
25302539

2540+
#undef SET_SYS_FROM_STRING
25312541
#undef SET_SYS_FROM_STRING_BORROW
25322542
#undef SET_SYS_FROM_STRING_INT_RESULT
25332543

0 commit comments

Comments
 (0)