Commit 9048c493 authored by Steve Dower's avatar Steve Dower Committed by GitHub

bpo-37369: Fix initialization of sys members when launched via an app container (GH-14428)

sys._base_executable is now always defined on all platforms, and can be overridden through configuration.
Also adds test.support.PythonSymlink to encapsulate platform-specific logic for symlinking sys.executable
parent 80097e08
...@@ -374,6 +374,7 @@ typedef struct { ...@@ -374,6 +374,7 @@ typedef struct {
to zero. */ to zero. */
wchar_t *executable; /* sys.executable */ wchar_t *executable; /* sys.executable */
wchar_t *base_executable; /* sys._base_executable */
wchar_t *prefix; /* sys.prefix */ wchar_t *prefix; /* sys.prefix */
wchar_t *base_prefix; /* sys.base_prefix */ wchar_t *base_prefix; /* sys.base_prefix */
wchar_t *exec_prefix; /* sys.exec_prefix */ wchar_t *exec_prefix; /* sys.exec_prefix */
......
...@@ -27,6 +27,8 @@ typedef struct _PyPathConfig { ...@@ -27,6 +27,8 @@ typedef struct _PyPathConfig {
are ignored when their value are equal to -1 (unset). */ are ignored when their value are equal to -1 (unset). */
int isolated; int isolated;
int site_import; int site_import;
/* Set when a venv is detected */
wchar_t *base_executable;
} _PyPathConfig; } _PyPathConfig;
#define _PyPathConfig_INIT \ #define _PyPathConfig_INIT \
......
...@@ -22,8 +22,7 @@ WINSERVICE = sys.executable.lower().endswith("pythonservice.exe") ...@@ -22,8 +22,7 @@ WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
def _path_eq(p1, p2): def _path_eq(p1, p2):
return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2) return p1 == p2 or os.path.normcase(p1) == os.path.normcase(p2)
WINENV = (hasattr(sys, '_base_executable') and WINENV = not _path_eq(sys.executable, sys._base_executable)
not _path_eq(sys.executable, sys._base_executable))
def _close_handles(*handles): def _close_handles(*handles):
......
...@@ -459,13 +459,6 @@ def venv(known_paths): ...@@ -459,13 +459,6 @@ def venv(known_paths):
env = os.environ env = os.environ
if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env: if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env:
executable = sys._base_executable = os.environ['__PYVENV_LAUNCHER__'] 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: else:
executable = sys.executable executable = sys.executable
exe_dir, _ = os.path.split(os.path.abspath(executable)) exe_dir, _ = os.path.split(os.path.abspath(executable))
......
...@@ -12,6 +12,7 @@ import faulthandler ...@@ -12,6 +12,7 @@ import faulthandler
import fnmatch import fnmatch
import functools import functools
import gc import gc
import glob
import importlib import importlib
import importlib.util import importlib.util
import io import io
...@@ -2500,6 +2501,84 @@ def skip_unless_symlink(test): ...@@ -2500,6 +2501,84 @@ def skip_unless_symlink(test):
msg = "Requires functional symlink implementation" msg = "Requires functional symlink implementation"
return test if ok else unittest.skip(msg)(test) return test if ok else unittest.skip(msg)(test)
class PythonSymlink:
"""Creates a symlink for the current Python executable"""
def __init__(self, link=None):
self.link = link or os.path.abspath(TESTFN)
self._linked = []
self.real = os.path.realpath(sys.executable)
self._also_link = []
self._env = None
self._platform_specific()
def _platform_specific(self):
pass
if sys.platform == "win32":
def _platform_specific(self):
import _winapi
if os.path.lexists(self.real) and not os.path.exists(self.real):
# App symlink appears to not exist, but we want the
# real executable here anyway
self.real = _winapi.GetModuleFileName(0)
dll = _winapi.GetModuleFileName(sys.dllhandle)
src_dir = os.path.dirname(dll)
dest_dir = os.path.dirname(self.link)
self._also_link.append((
dll,
os.path.join(dest_dir, os.path.basename(dll))
))
for runtime in glob.glob(os.path.join(src_dir, "vcruntime*.dll")):
self._also_link.append((
runtime,
os.path.join(dest_dir, os.path.basename(runtime))
))
self._env = {k.upper(): os.getenv(k) for k in os.environ}
self._env["PYTHONHOME"] = os.path.dirname(self.real)
if sysconfig.is_python_build(True):
self._env["PYTHONPATH"] = os.path.dirname(os.__file__)
def __enter__(self):
os.symlink(self.real, self.link)
self._linked.append(self.link)
for real, link in self._also_link:
os.symlink(real, link)
self._linked.append(link)
return self
def __exit__(self, exc_type, exc_value, exc_tb):
for link in self._linked:
try:
os.remove(link)
except IOError as ex:
if verbose:
print("failed to clean up {}: {}".format(link, ex))
def _call(self, python, args, env, returncode):
cmd = [python, *args]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=env)
r = p.communicate()
if p.returncode != returncode:
if verbose:
print(repr(r[0]))
print(repr(r[1]), file=sys.stderr)
raise RuntimeError(
'unexpected return code: {0} (0x{0:08X})'.format(p.returncode))
return r
def call_real(self, *args, returncode=0):
return self._call(self.real, args, None, returncode)
def call_link(self, *args, returncode=0):
return self._call(self.link, args, self._env, returncode)
_can_xattr = None _can_xattr = None
def can_xattr(): def can_xattr():
global _can_xattr global _can_xattr
......
...@@ -362,6 +362,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): ...@@ -362,6 +362,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'pythonpath_env': None, 'pythonpath_env': None,
'home': None, 'home': None,
'executable': GET_DEFAULT_CONFIG, 'executable': GET_DEFAULT_CONFIG,
'base_executable': GET_DEFAULT_CONFIG,
'prefix': GET_DEFAULT_CONFIG, 'prefix': GET_DEFAULT_CONFIG,
'base_prefix': GET_DEFAULT_CONFIG, 'base_prefix': GET_DEFAULT_CONFIG,
...@@ -534,14 +535,16 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): ...@@ -534,14 +535,16 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG: if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
expected['stdio_errors'] = 'surrogateescape' expected['stdio_errors'] = 'surrogateescape'
if expected['executable'] is self.GET_DEFAULT_CONFIG:
if sys.platform == 'win32': if sys.platform == 'win32':
expected['executable'] = self.test_exe default_executable = self.test_exe
else: elif expected['program_name'] is not self.GET_DEFAULT_CONFIG:
if expected['program_name'] is not self.GET_DEFAULT_CONFIG: default_executable = os.path.abspath(expected['program_name'])
expected['executable'] = os.path.abspath(expected['program_name'])
else: else:
expected['executable'] = os.path.join(os.getcwd(), '_testembed') default_executable = os.path.join(os.getcwd(), '_testembed')
if expected['executable'] is self.GET_DEFAULT_CONFIG:
expected['executable'] = default_executable
if expected['base_executable'] is self.GET_DEFAULT_CONFIG:
expected['base_executable'] = default_executable
if expected['program_name'] is self.GET_DEFAULT_CONFIG: if expected['program_name'] is self.GET_DEFAULT_CONFIG:
expected['program_name'] = './_testembed' expected['program_name'] = './_testembed'
......
...@@ -610,9 +610,10 @@ class CGIHTTPServerTestCase(BaseTestCase): ...@@ -610,9 +610,10 @@ class CGIHTTPServerTestCase(BaseTestCase):
# The shebang line should be pure ASCII: use symlink if possible. # The shebang line should be pure ASCII: use symlink if possible.
# See issue #7668. # See issue #7668.
self._pythonexe_symlink = None
if support.can_symlink(): if support.can_symlink():
self.pythonexe = os.path.join(self.parent_dir, 'python') self.pythonexe = os.path.join(self.parent_dir, 'python')
os.symlink(sys.executable, self.pythonexe) self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__()
else: else:
self.pythonexe = sys.executable self.pythonexe = sys.executable
...@@ -655,8 +656,8 @@ class CGIHTTPServerTestCase(BaseTestCase): ...@@ -655,8 +656,8 @@ class CGIHTTPServerTestCase(BaseTestCase):
def tearDown(self): def tearDown(self):
try: try:
os.chdir(self.cwd) os.chdir(self.cwd)
if self.pythonexe != sys.executable: if self._pythonexe_symlink:
os.remove(self.pythonexe) self._pythonexe_symlink.__exit__(None, None, None)
if self.nocgi_path: if self.nocgi_path:
os.remove(self.nocgi_path) os.remove(self.nocgi_path)
if self.file1_path: if self.file1_path:
......
...@@ -20,37 +20,9 @@ class PlatformTest(unittest.TestCase): ...@@ -20,37 +20,9 @@ class PlatformTest(unittest.TestCase):
@support.skip_unless_symlink @support.skip_unless_symlink
def test_architecture_via_symlink(self): # issue3762 def test_architecture_via_symlink(self): # issue3762
# On Windows, the EXE needs to know where pythonXY.dll and *.pyd is at with support.PythonSymlink() as py:
# so we add the directory to the path, PYTHONHOME and PYTHONPATH. cmd = "-c", "import platform; print(platform.architecture())"
env = None self.assertEqual(py.call_real(*cmd), py.call_link(*cmd))
if sys.platform == "win32":
env = {k.upper(): os.environ[k] for k in os.environ}
env["PATH"] = "{};{}".format(
os.path.dirname(sys.executable), env.get("PATH", ""))
env["PYTHONHOME"] = os.path.dirname(sys.executable)
if sysconfig.is_python_build(True):
env["PYTHONPATH"] = os.path.dirname(os.__file__)
def get(python, env=None):
cmd = [python, '-c',
'import platform; print(platform.architecture())']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=env)
r = p.communicate()
if p.returncode:
print(repr(r[0]))
print(repr(r[1]), file=sys.stderr)
self.fail('unexpected return code: {0} (0x{0:08X})'
.format(p.returncode))
return r
real = os.path.realpath(sys.executable)
link = os.path.abspath(support.TESTFN)
os.symlink(real, link)
try:
self.assertEqual(get(real), get(link, env=env))
finally:
os.remove(link)
def test_platform(self): def test_platform(self):
for aliased in (False, True): for aliased in (False, True):
...@@ -275,6 +247,11 @@ class PlatformTest(unittest.TestCase): ...@@ -275,6 +247,11 @@ class PlatformTest(unittest.TestCase):
os.path.exists(sys.executable+'.exe'): os.path.exists(sys.executable+'.exe'):
# Cygwin horror # Cygwin horror
executable = sys.executable + '.exe' executable = sys.executable + '.exe'
elif sys.platform == "win32" and not os.path.exists(sys.executable):
# App symlink appears to not exist, but we want the
# real executable here anyway
import _winapi
executable = _winapi.GetModuleFileName(0)
else: else:
executable = sys.executable executable = sys.executable
platform.libc_ver(executable) platform.libc_ver(executable)
......
...@@ -6,7 +6,8 @@ import shutil ...@@ -6,7 +6,8 @@ import shutil
from copy import copy from copy import copy
from test.support import (import_module, TESTFN, unlink, check_warnings, from test.support import (import_module, TESTFN, unlink, check_warnings,
captured_stdout, skip_unless_symlink, change_cwd) captured_stdout, skip_unless_symlink, change_cwd,
PythonSymlink)
import sysconfig import sysconfig
from sysconfig import (get_paths, get_platform, get_config_vars, from sysconfig import (get_paths, get_platform, get_config_vars,
...@@ -232,39 +233,10 @@ class TestSysConfig(unittest.TestCase): ...@@ -232,39 +233,10 @@ class TestSysConfig(unittest.TestCase):
self.assertEqual(get_scheme_names(), wanted) self.assertEqual(get_scheme_names(), wanted)
@skip_unless_symlink @skip_unless_symlink
def test_symlink(self): def test_symlink(self): # Issue 7880
# On Windows, the EXE needs to know where pythonXY.dll is at so we have with PythonSymlink() as py:
# to add the directory to the path. cmd = "-c", "import sysconfig; print(sysconfig.get_platform())"
env = None self.assertEqual(py.call_real(*cmd), py.call_link(*cmd))
if sys.platform == "win32":
env = {k.upper(): os.environ[k] for k in os.environ}
env["PATH"] = "{};{}".format(
os.path.dirname(sys.executable), env.get("PATH", ""))
# Requires PYTHONHOME as well since we locate stdlib from the
# EXE path and not the DLL path (which should be fixed)
env["PYTHONHOME"] = os.path.dirname(sys.executable)
if sysconfig.is_python_build(True):
env["PYTHONPATH"] = os.path.dirname(os.__file__)
# Issue 7880
def get(python, env=None):
cmd = [python, '-c',
'import sysconfig; print(sysconfig.get_platform())']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=env)
out, err = p.communicate()
if p.returncode:
print((out, err))
self.fail('Non-zero return code {0} (0x{0:08X})'
.format(p.returncode))
return out, err
real = os.path.realpath(sys.executable)
link = os.path.abspath(TESTFN)
os.symlink(real, link)
try:
self.assertEqual(get(real), get(link, env))
finally:
unlink(link)
def test_user_similar(self): def test_user_similar(self):
# Issue #8759: make sure the posix scheme for the users # Issue #8759: make sure the posix scheme for the users
......
...@@ -28,8 +28,8 @@ except ImportError: ...@@ -28,8 +28,8 @@ except ImportError:
# Platforms that set sys._base_executable can create venvs from within # Platforms that set sys._base_executable can create venvs from within
# another venv, so no need to skip tests that require venv.create(). # another venv, so no need to skip tests that require venv.create().
requireVenvCreate = unittest.skipUnless( requireVenvCreate = unittest.skipUnless(
hasattr(sys, '_base_executable') sys.prefix == sys.base_prefix
or sys.prefix == sys.base_prefix, or sys._base_executable != sys.executable,
'cannot run venv.create from within a venv on this platform') 'cannot run venv.create from within a venv on this platform')
def check_output(cmd, encoding=None): def check_output(cmd, encoding=None):
...@@ -57,8 +57,14 @@ class BaseTest(unittest.TestCase): ...@@ -57,8 +57,14 @@ class BaseTest(unittest.TestCase):
self.bindir = 'bin' self.bindir = 'bin'
self.lib = ('lib', 'python%d.%d' % sys.version_info[:2]) self.lib = ('lib', 'python%d.%d' % sys.version_info[:2])
self.include = 'include' self.include = 'include'
executable = getattr(sys, '_base_executable', sys.executable) executable = sys._base_executable
self.exe = os.path.split(executable)[-1] self.exe = os.path.split(executable)[-1]
if (sys.platform == 'win32'
and os.path.lexists(executable)
and not os.path.exists(executable)):
self.cannot_link_exe = True
else:
self.cannot_link_exe = False
def tearDown(self): def tearDown(self):
rmtree(self.env_dir) rmtree(self.env_dir)
...@@ -102,7 +108,7 @@ class BasicTest(BaseTest): ...@@ -102,7 +108,7 @@ class BasicTest(BaseTest):
else: else:
self.assertFalse(os.path.exists(p)) self.assertFalse(os.path.exists(p))
data = self.get_text_file_contents('pyvenv.cfg') data = self.get_text_file_contents('pyvenv.cfg')
executable = getattr(sys, '_base_executable', sys.executable) executable = sys._base_executable
path = os.path.dirname(executable) path = os.path.dirname(executable)
self.assertIn('home = %s' % path, data) self.assertIn('home = %s' % path, data)
fn = self.get_env_file(self.bindir, self.exe) fn = self.get_env_file(self.bindir, self.exe)
...@@ -158,10 +164,6 @@ class BasicTest(BaseTest): ...@@ -158,10 +164,6 @@ class BasicTest(BaseTest):
""" """
Test that the prefix values are as expected. 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 # check a venv's prefixes
rmtree(self.env_dir) rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir) self.run_with_capture(venv.create, self.env_dir)
...@@ -169,9 +171,9 @@ class BasicTest(BaseTest): ...@@ -169,9 +171,9 @@ class BasicTest(BaseTest):
cmd = [envpy, '-c', None] cmd = [envpy, '-c', None]
for prefix, expected in ( for prefix, expected in (
('prefix', self.env_dir), ('prefix', self.env_dir),
('prefix', self.env_dir), ('exec_prefix', self.env_dir),
('base_prefix', sys.prefix), ('base_prefix', sys.base_prefix),
('base_exec_prefix', sys.exec_prefix)): ('base_exec_prefix', sys.base_exec_prefix)):
cmd[2] = 'import sys; print(sys.%s)' % prefix cmd[2] = 'import sys; print(sys.%s)' % prefix
out, err = check_output(cmd) out, err = check_output(cmd)
self.assertEqual(out.strip(), expected.encode()) self.assertEqual(out.strip(), expected.encode())
...@@ -283,6 +285,11 @@ class BasicTest(BaseTest): ...@@ -283,6 +285,11 @@ class BasicTest(BaseTest):
# symlinked to 'python3.3' in the env, even when symlinking in # symlinked to 'python3.3' in the env, even when symlinking in
# general isn't wanted. # general isn't wanted.
if usl: if usl:
if self.cannot_link_exe:
# Symlinking is skipped when our executable is already a
# special app symlink
self.assertFalse(os.path.islink(fn))
else:
self.assertTrue(os.path.islink(fn)) self.assertTrue(os.path.islink(fn))
# If a venv is created from a source build and that venv is used to # If a venv is created from a source build and that venv is used to
......
...@@ -112,7 +112,7 @@ class EnvBuilder: ...@@ -112,7 +112,7 @@ class EnvBuilder:
prompt = self.prompt if self.prompt is not None else context.env_name prompt = self.prompt if self.prompt is not None else context.env_name
context.prompt = '(%s) ' % prompt context.prompt = '(%s) ' % prompt
create_if_needed(env_dir) create_if_needed(env_dir)
executable = getattr(sys, '_base_executable', sys.executable) executable = sys._base_executable
dirname, exename = os.path.split(os.path.abspath(executable)) dirname, exename = os.path.split(os.path.abspath(executable))
context.executable = executable context.executable = executable
context.python_dir = dirname context.python_dir = dirname
...@@ -163,6 +163,7 @@ class EnvBuilder: ...@@ -163,6 +163,7 @@ class EnvBuilder:
if self.prompt is not None: if self.prompt is not None:
f.write(f'prompt = {self.prompt!r}\n') f.write(f'prompt = {self.prompt!r}\n')
if os.name != 'nt':
def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
""" """
Try symlinking a file, and if that fails, fall back to copying. Try symlinking a file, and if that fails, fall back to copying.
...@@ -180,7 +181,24 @@ class EnvBuilder: ...@@ -180,7 +181,24 @@ class EnvBuilder:
logger.warning('Unable to symlink %r to %r', src, dst) logger.warning('Unable to symlink %r to %r', src, dst)
force_copy = True force_copy = True
if force_copy: if force_copy:
if os.name == 'nt': shutil.copyfile(src, dst)
else:
def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
"""
Try symlinking a file, and if that fails, fall back to copying.
"""
bad_src = os.path.lexists(src) and not os.path.exists(src)
if self.symlinks and not bad_src and not os.path.islink(dst):
try:
if relative_symlinks_ok:
assert os.path.dirname(src) == os.path.dirname(dst)
os.symlink(os.path.basename(src), dst)
else:
os.symlink(src, dst)
return
except Exception: # may need to use a more specific exception
logger.warning('Unable to symlink %r to %r', src, dst)
# On Windows, we rewrite symlinks to our base python.exe into # On Windows, we rewrite symlinks to our base python.exe into
# copies of venvlauncher.exe # copies of venvlauncher.exe
basename, ext = os.path.splitext(os.path.basename(src)) basename, ext = os.path.splitext(os.path.basename(src))
...@@ -202,6 +220,7 @@ class EnvBuilder: ...@@ -202,6 +220,7 @@ class EnvBuilder:
else: else:
src = srcfn src = srcfn
if not os.path.exists(src): if not os.path.exists(src):
if not bad_src:
logger.warning('Unable to copy %r', src) logger.warning('Unable to copy %r', src)
return return
...@@ -251,7 +270,7 @@ class EnvBuilder: ...@@ -251,7 +270,7 @@ class EnvBuilder:
for suffix in suffixes: for suffix in suffixes:
src = os.path.join(dirname, suffix) src = os.path.join(dirname, suffix)
if os.path.exists(src): if os.path.lexists(src):
copier(src, os.path.join(binpath, suffix)) copier(src, os.path.join(binpath, suffix))
if sysconfig.is_python_build(True): if sysconfig.is_python_build(True):
......
Fixes path for :data:`sys.executable` when running from the Microsoft Store.
...@@ -537,14 +537,28 @@ get_program_full_path(const PyConfig *config, ...@@ -537,14 +537,28 @@ get_program_full_path(const PyConfig *config,
wchar_t program_full_path[MAXPATHLEN+1]; wchar_t program_full_path[MAXPATHLEN+1];
memset(program_full_path, 0, sizeof(program_full_path)); memset(program_full_path, 0, sizeof(program_full_path));
if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
/* GetModuleFileName should never fail when passed NULL */
return _PyStatus_ERR("Cannot determine program path");
}
/* The launcher may need to force the executable path to a /* The launcher may need to force the executable path to a
* different environment, so override it here. */ * different environment, so override it here. */
pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__"); pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__");
if (pyvenv_launcher && pyvenv_launcher[0]) { if (pyvenv_launcher && pyvenv_launcher[0]) {
/* If overridden, preserve the original full path */
pathconfig->base_executable = PyMem_RawMalloc(
sizeof(wchar_t) * (MAXPATHLEN + 1));
PyStatus status = canonicalize(pathconfig->base_executable,
program_full_path);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher); wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher);
} else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { /* bpo-35873: Clear the environment variable to avoid it being
/* GetModuleFileName should never fail when passed NULL */ * inherited by child processes. */
return _PyStatus_ERR("Cannot determine program path"); _wputenv_s(L"__PYVENV_LAUNCHER__", L"");
} }
pathconfig->program_full_path = PyMem_RawMalloc( pathconfig->program_full_path = PyMem_RawMalloc(
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
#include <shellapi.h> #include <shellapi.h>
#include <shlobj.h>
#include <string>
#include <winrt\Windows.ApplicationModel.h> #include <winrt\Windows.ApplicationModel.h>
#include <winrt\Windows.Storage.h> #include <winrt\Windows.Storage.h>
...@@ -24,162 +27,178 @@ const wchar_t *PROGNAME = L"python.exe"; ...@@ -24,162 +27,178 @@ const wchar_t *PROGNAME = L"python.exe";
#endif #endif
#endif #endif
static void static std::wstring
set_user_base() get_user_base()
{ {
wchar_t envBuffer[2048];
try { try {
const auto appData = winrt::Windows::Storage::ApplicationData::Current(); const auto appData = winrt::Windows::Storage::ApplicationData::Current();
if (appData) { if (appData) {
const auto localCache = appData.LocalCacheFolder(); const auto localCache = appData.LocalCacheFolder();
if (localCache) { if (localCache) {
auto path = localCache.Path(); auto path = localCache.Path();
if (!path.empty() && if (!path.empty()) {
!wcscpy_s(envBuffer, path.c_str()) && return std::wstring(path) + L"\\local-packages";
!wcscat_s(envBuffer, L"\\local-packages")
) {
_wputenv_s(L"PYTHONUSERBASE", envBuffer);
} }
} }
} }
} catch (...) { } catch (...) {
} }
return std::wstring();
} }
static const wchar_t * static std::wstring
get_argv0(const wchar_t *argv0) get_package_family()
{ {
winrt::hstring installPath; try {
const wchar_t *launcherPath; const auto package = winrt::Windows::ApplicationModel::Package::Current();
wchar_t *buffer; if (package) {
size_t len; const auto id = package.Id();
if (id) {
launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__"); return std::wstring(id.FamilyName());
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;
} }
catch (...) {
}
return std::wstring();
}
static std::wstring
get_package_home()
{
try { try {
const auto package = winrt::Windows::ApplicationModel::Package::Current(); const auto package = winrt::Windows::ApplicationModel::Package::Current();
if (package) { if (package) {
const auto install = package.InstalledLocation(); const auto path = package.InstalledLocation();
if (install) { if (path) {
installPath = install.Path(); return std::wstring(path.Path());
} }
} }
} }
catch (...) { catch (...) {
} }
if (!installPath.empty()) { return std::wstring();
len = installPath.size() + wcslen(PROGNAME) + 2; }
} else {
len = wcslen(argv0) + wcslen(PROGNAME) + 1;
}
buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); static PyStatus
if (!buffer) { set_process_name(PyConfig *config)
Py_FatalError("out of memory"); {
return NULL; PyStatus status = PyStatus_Ok();
} std::wstring executable;
if (!installPath.empty()) { const auto home = get_package_home();
if (wcscpy_s(buffer, len, installPath.c_str())) { const auto family = get_package_family();
Py_FatalError("failed to copy to buffer");
return NULL; if (!family.empty()) {
} PWSTR localAppData;
if (wcscat_s(buffer, len, L"\\")) { if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0,
Py_FatalError("failed to concatenate backslash"); NULL, &localAppData))) {
return NULL; executable = std::wstring(localAppData)
+ L"\\Microsoft\\WindowsApps\\"
+ family
+ L"\\"
+ PROGNAME;
CoTaskMemFree(localAppData);
} }
} else {
if (wcscpy_s(buffer, len, argv0)) {
Py_FatalError("failed to copy argv[0]");
return NULL;
} }
wchar_t *name = wcsrchr(buffer, L'\\'); /* Only use module filename if we don't have a home */
if (name) { if (home.empty() && executable.empty()) {
name[1] = L'\0'; executable.resize(MAX_PATH);
while (true) {
DWORD len = GetModuleFileNameW(
NULL, executable.data(), (DWORD)executable.size());
if (len == 0) {
executable.clear();
break;
} else if (len == executable.size() &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
executable.resize(len * 2);
} else { } else {
buffer[0] = L'\0'; executable.resize(len);
break;
} }
} }
if (wcscat_s(buffer, len, PROGNAME)) {
Py_FatalError("failed to concatenate program name");
return NULL;
} }
return buffer; if (!home.empty()) {
} status = PyConfig_SetString(config, &config->home, home.c_str());
if (PyStatus_Exception(status)) {
static wchar_t * return status;
get_process_name() }
{ }
DWORD bufferLen = MAX_PATH;
DWORD len = bufferLen;
wchar_t *r = NULL;
while (!r) { const wchar_t *launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__");
r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); if (launcherPath) {
if (!r) { if (!executable.empty()) {
Py_FatalError("out of memory"); status = PyConfig_SetString(config, &config->base_executable,
return NULL; executable.c_str());
if (PyStatus_Exception(status)) {
return status;
} }
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;
} }
status = PyConfig_SetString(
config, &config->executable, launcherPath);
/* bpo-35873: Clear the environment variable to avoid it being
* inherited by child processes. */
_wputenv_s(L"__PYVENV_LAUNCHER__", L"");
} else if (!executable.empty()) {
status = PyConfig_SetString(
config, &config->executable, executable.c_str());
} }
return r; return status;
} }
int int
wmain(int argc, wchar_t **argv) wmain(int argc, wchar_t **argv)
{ {
const wchar_t **new_argv; PyStatus status;
int new_argc;
const wchar_t *exeName; PyPreConfig preconfig;
PyConfig config;
PyPreConfig_InitPythonConfig(&preconfig);
status = Py_PreInitializeFromArgs(&preconfig, argc, argv);
if (PyStatus_Exception(status)) {
goto fail;
}
new_argc = argc; status = PyConfig_InitPythonConfig(&config);
new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2)); if (PyStatus_Exception(status)) {
if (new_argv == NULL) { goto fail;
Py_FatalError("out of memory");
return -1;
} }
exeName = get_process_name(); status = PyConfig_SetArgv(&config, argc, argv);
if (PyStatus_Exception(status)) {
goto fail;
}
new_argv[0] = get_argv0(exeName ? exeName : argv[0]); status = set_process_name(&config);
for (int i = 1; i < argc; ++i) { if (PyStatus_Exception(status)) {
new_argv[i] = argv[i]; goto fail;
} }
set_user_base(); const wchar_t *p = _wgetenv(L"PYTHONUSERBASE");
if (!p || !*p) {
_wputenv_s(L"PYTHONUSERBASE", get_user_base().c_str());
}
if (exeName) { p = wcsrchr(argv[0], L'\\');
const wchar_t *p = wcsrchr(exeName, L'\\'); if (!p) {
p = argv[0];
}
if (p) { if (p) {
if (*p == L'\\') {
p++;
}
const wchar_t *moduleName = NULL; const wchar_t *moduleName = NULL;
if (*p++ == L'\\') {
if (wcsnicmp(p, L"pip", 3) == 0) { if (wcsnicmp(p, L"pip", 3) == 0) {
moduleName = L"pip"; moduleName = L"pip";
/* No longer required when pip 19.1 is added */ /* No longer required when pip 19.1 is added */
...@@ -187,29 +206,40 @@ wmain(int argc, wchar_t **argv) ...@@ -187,29 +206,40 @@ wmain(int argc, wchar_t **argv)
} else if (wcsnicmp(p, L"idle", 4) == 0) { } else if (wcsnicmp(p, L"idle", 4) == 0) {
moduleName = L"idlelib"; moduleName = L"idlelib";
} }
}
if (moduleName) { if (moduleName) {
new_argc += 2; status = PyConfig_SetString(&config, &config.run_module, moduleName);
for (int i = argc; i >= 1; --i) { if (PyStatus_Exception(status)) {
new_argv[i + 2] = new_argv[i]; goto fail;
} }
new_argv[1] = L"-m"; status = PyConfig_SetString(&config, &config.run_filename, NULL);
new_argv[2] = moduleName; if (PyStatus_Exception(status)) {
goto fail;
}
status = PyConfig_SetString(&config, &config.run_command, NULL);
if (PyStatus_Exception(status)) {
goto fail;
} }
} }
} }
/* Override program_full_path from here so that status = Py_InitializeFromConfig(&config);
sys.executable is set correctly. */ if (PyStatus_Exception(status)) {
_Py_SetProgramFullPath(new_argv[0]); goto fail;
}
int result = Py_Main(new_argc, (wchar_t **)new_argv); PyConfig_Clear(&config);
free((void *)exeName); return Py_RunMain();
free((void *)new_argv);
return result; fail:
PyConfig_Clear(&config);
if (PyStatus_IsExit(status)) {
return status.exitcode;
}
assert(PyStatus_Exception(status));
Py_ExitStatusException(status);
/* Unreachable code */
return 0;
} }
#ifdef PYTHONW #ifdef PYTHONW
......
...@@ -528,6 +528,7 @@ PyConfig_Clear(PyConfig *config) ...@@ -528,6 +528,7 @@ PyConfig_Clear(PyConfig *config)
config->module_search_paths_set = 0; config->module_search_paths_set = 0;
CLEAR(config->executable); CLEAR(config->executable);
CLEAR(config->base_executable);
CLEAR(config->prefix); CLEAR(config->prefix);
CLEAR(config->base_prefix); CLEAR(config->base_prefix);
CLEAR(config->exec_prefix); CLEAR(config->exec_prefix);
...@@ -765,6 +766,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) ...@@ -765,6 +766,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_ATTR(module_search_paths_set); COPY_ATTR(module_search_paths_set);
COPY_WSTR_ATTR(executable); COPY_WSTR_ATTR(executable);
COPY_WSTR_ATTR(base_executable);
COPY_WSTR_ATTR(prefix); COPY_WSTR_ATTR(prefix);
COPY_WSTR_ATTR(base_prefix); COPY_WSTR_ATTR(base_prefix);
COPY_WSTR_ATTR(exec_prefix); COPY_WSTR_ATTR(exec_prefix);
...@@ -865,6 +867,7 @@ config_as_dict(const PyConfig *config) ...@@ -865,6 +867,7 @@ config_as_dict(const PyConfig *config)
SET_ITEM_WSTR(home); SET_ITEM_WSTR(home);
SET_ITEM_WSTRLIST(module_search_paths); SET_ITEM_WSTRLIST(module_search_paths);
SET_ITEM_WSTR(executable); SET_ITEM_WSTR(executable);
SET_ITEM_WSTR(base_executable);
SET_ITEM_WSTR(prefix); SET_ITEM_WSTR(prefix);
SET_ITEM_WSTR(base_prefix); SET_ITEM_WSTR(base_prefix);
SET_ITEM_WSTR(exec_prefix); SET_ITEM_WSTR(exec_prefix);
...@@ -2404,6 +2407,7 @@ PyConfig_Read(PyConfig *config) ...@@ -2404,6 +2407,7 @@ PyConfig_Read(PyConfig *config)
assert(config->module_search_paths_set != 0); assert(config->module_search_paths_set != 0);
/* don't check config->module_search_paths */ /* don't check config->module_search_paths */
assert(config->executable != NULL); assert(config->executable != NULL);
assert(config->base_executable != NULL);
assert(config->prefix != NULL); assert(config->prefix != NULL);
assert(config->base_prefix != NULL); assert(config->base_prefix != NULL);
assert(config->exec_prefix != NULL); assert(config->exec_prefix != NULL);
......
...@@ -57,6 +57,7 @@ pathconfig_clear(_PyPathConfig *config) ...@@ -57,6 +57,7 @@ pathconfig_clear(_PyPathConfig *config)
CLEAR(config->module_search_path); CLEAR(config->module_search_path);
CLEAR(config->home); CLEAR(config->home);
CLEAR(config->program_name); CLEAR(config->program_name);
CLEAR(config->base_executable);
#undef CLEAR #undef CLEAR
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
...@@ -89,6 +90,14 @@ pathconfig_calculate(_PyPathConfig *pathconfig, const PyConfig *config) ...@@ -89,6 +90,14 @@ pathconfig_calculate(_PyPathConfig *pathconfig, const PyConfig *config)
status = _PyStatus_NO_MEMORY(); status = _PyStatus_NO_MEMORY();
goto error; goto error;
} }
if (config->base_executable) {
PyMem_RawFree(new_config.base_executable);
if (copy_wstr(&new_config.base_executable,
config->base_executable) < 0) {
status = _PyStatus_NO_MEMORY();
goto error;
}
}
pathconfig_clear(pathconfig); pathconfig_clear(pathconfig);
*pathconfig = new_config; *pathconfig = new_config;
...@@ -132,6 +141,7 @@ _PyPathConfig_SetGlobal(const _PyPathConfig *config) ...@@ -132,6 +141,7 @@ _PyPathConfig_SetGlobal(const _PyPathConfig *config)
COPY_ATTR(module_search_path); COPY_ATTR(module_search_path);
COPY_ATTR(program_name); COPY_ATTR(program_name);
COPY_ATTR(home); COPY_ATTR(home);
COPY_ATTR(base_executable);
pathconfig_clear(&_Py_path_config); pathconfig_clear(&_Py_path_config);
/* Steal new_config strings; don't clear new_config */ /* Steal new_config strings; don't clear new_config */
...@@ -224,6 +234,9 @@ _PyConfig_SetPathConfig(const PyConfig *config) ...@@ -224,6 +234,9 @@ _PyConfig_SetPathConfig(const PyConfig *config)
if (copy_wstr(&pathconfig.home, config->home) < 0) { if (copy_wstr(&pathconfig.home, config->home) < 0) {
goto no_memory; goto no_memory;
} }
if (copy_wstr(&pathconfig.base_executable, config->base_executable) < 0) {
goto no_memory;
}
status = _PyPathConfig_SetGlobal(&pathconfig); status = _PyPathConfig_SetGlobal(&pathconfig);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
...@@ -321,6 +334,13 @@ config_calculate_pathconfig(PyConfig *config) ...@@ -321,6 +334,13 @@ config_calculate_pathconfig(PyConfig *config)
} }
} }
if (config->base_executable == NULL) {
if (copy_wstr(&config->base_executable,
pathconfig.base_executable) < 0) {
goto no_memory;
}
}
if (pathconfig.isolated != -1) { if (pathconfig.isolated != -1) {
config->isolated = pathconfig.isolated; config->isolated = pathconfig.isolated;
} }
...@@ -367,6 +387,14 @@ _PyConfig_InitPathConfig(PyConfig *config) ...@@ -367,6 +387,14 @@ _PyConfig_InitPathConfig(PyConfig *config)
return _PyStatus_NO_MEMORY(); return _PyStatus_NO_MEMORY();
} }
} }
if (config->base_executable == NULL) {
if (copy_wstr(&config->base_executable,
config->executable) < 0) {
return _PyStatus_NO_MEMORY();
}
}
return _PyStatus_OK(); return _PyStatus_OK();
} }
...@@ -434,6 +462,8 @@ Py_SetPath(const wchar_t *path) ...@@ -434,6 +462,8 @@ Py_SetPath(const wchar_t *path)
_Py_path_config.home = NULL; _Py_path_config.home = NULL;
new_config.program_name = _Py_path_config.program_name; new_config.program_name = _Py_path_config.program_name;
_Py_path_config.program_name = NULL; _Py_path_config.program_name = NULL;
new_config.base_executable = _Py_path_config.base_executable;
_Py_path_config.base_executable = NULL;
pathconfig_clear(&_Py_path_config); pathconfig_clear(&_Py_path_config);
_Py_path_config = new_config; _Py_path_config = new_config;
......
...@@ -2850,6 +2850,7 @@ _PySys_InitMain(_PyRuntimeState *runtime, PyThreadState *tstate) ...@@ -2850,6 +2850,7 @@ _PySys_InitMain(_PyRuntimeState *runtime, PyThreadState *tstate)
COPY_LIST("path", config->module_search_paths); COPY_LIST("path", config->module_search_paths);
SET_SYS_FROM_WSTR("executable", config->executable); SET_SYS_FROM_WSTR("executable", config->executable);
SET_SYS_FROM_WSTR("_base_executable", config->base_executable);
SET_SYS_FROM_WSTR("prefix", config->prefix); SET_SYS_FROM_WSTR("prefix", config->prefix);
SET_SYS_FROM_WSTR("base_prefix", config->base_prefix); SET_SYS_FROM_WSTR("base_prefix", config->base_prefix);
SET_SYS_FROM_WSTR("exec_prefix", config->exec_prefix); SET_SYS_FROM_WSTR("exec_prefix", config->exec_prefix);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment