summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@microsoft.com>2018-12-10 08:11:34 -0800
committerGitHub <noreply@github.com>2018-12-10 08:11:34 -0800
commitb264c609853eae9dbb45c6dbee11e84ae3927e88 (patch)
tree607edf9521e3243856ee9e5804a9cf45cfef6b82
parentef1fc0d031c925416d49b407518932ccbf57a0d2 (diff)
downloadcpython-git-b264c609853eae9dbb45c6dbee11e84ae3927e88.tar.gz
[3.7] bpo-34977: Use venv redirector instead of original python.exe on Windows (GH-11029)
-rw-r--r--Doc/library/venv.rst17
-rw-r--r--Doc/whatsnew/3.7.rst10
-rw-r--r--Lib/test/test_venv.py1
-rw-r--r--Lib/venv/__init__.py69
-rw-r--r--Misc/NEWS.d/next/Windows/2018-12-07-10-00-38.bpo-34977.agQJbD.rst2
-rw-r--r--PC/getpathp.c8
-rw-r--r--PC/launcher.c223
-rw-r--r--PCbuild/pcbuild.proj2
-rw-r--r--PCbuild/venvlauncher.vcxproj85
-rw-r--r--PCbuild/venvwlauncher.vcxproj85
-rw-r--r--Tools/msi/lib/lib_files.wxs23
11 files changed, 465 insertions, 60 deletions
diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
index def926b085..53f3d26405 100644
--- a/Doc/library/venv.rst
+++ b/Doc/library/venv.rst
@@ -135,7 +135,6 @@ creation according to their needs, the :class:`EnvBuilder` class.
.. versionadded:: 3.6
Added the ``prompt`` parameter
-
Creators of third-party virtual environment tools will be free to use the
provided ``EnvBuilder`` class as a base class.
@@ -182,16 +181,15 @@ creation according to their needs, the :class:`EnvBuilder` class.
.. method:: setup_python(context)
- Creates a copy of the Python executable (and, under Windows, DLLs) in
- the environment. On a POSIX system, if a specific executable
- ``python3.x`` was used, symlinks to ``python`` and ``python3`` will be
- created pointing to that executable, unless files with those names
- already exist.
+ Creates a copy of the Python executable in the environment on POSIX
+ systems. If a specific executable ``python3.x`` was used, symlinks to
+ ``python`` and ``python3`` will be created pointing to that executable,
+ unless files with those names already exist.
.. method:: setup_scripts(context)
Installs activation scripts appropriate to the platform into the virtual
- environment.
+ environment. On Windows, also installs the ``python[w].exe`` scripts.
.. method:: post_setup(context)
@@ -199,6 +197,11 @@ creation according to their needs, the :class:`EnvBuilder` class.
implementations to pre-install packages in the virtual environment or
perform other post-creation steps.
+ .. versionchanged:: 3.7.2
+ Windows now uses redirector scripts for ``python[w].exe`` instead of
+ copying the actual binaries, and so :meth:`setup_python` does nothing
+ unless running from a build in the source tree.
+
In addition, :class:`EnvBuilder` provides this utility method that can be
called from :meth:`setup_scripts` or :meth:`post_setup` in subclasses to
assist in installing custom scripts into the virtual environment.
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index aa690aacad..7d4c4f9199 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -2528,3 +2528,13 @@ In 3.7.1 the :mod:`tokenize` module now implicitly emits a ``NEWLINE`` token
when provided with input that does not have a trailing new line. This behavior
now matches what the C tokenizer does internally.
(Contributed by Ammar Askar in :issue:`33899`.)
+
+Notable changes in Python 3.7.2
+===============================
+
+In 3.7.2, :mod:`venv` on Windows no longer copies the original binaries, but
+creates redirector scripts named ``python.exe`` and ``pythonw.exe`` instead.
+This resolves a long standing issue where all virtual environments would have
+to be upgraded or recreated with each Python update. However, note that this
+release will still require recreation of virtual environments in order to get
+the new scripts.
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 461fe7afd2..22a3b78852 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -243,6 +243,7 @@ class BasicTest(BaseTest):
self.assertIn('include-system-site-packages = %s\n' % s, data)
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
+ @unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows')
def test_symlinking(self):
"""
Test symlinking works as expected
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index 716129d139..5438b0d4e5 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -9,6 +9,7 @@ import os
import shutil
import subprocess
import sys
+import sysconfig
import types
logger = logging.getLogger(__name__)
@@ -63,10 +64,11 @@ class EnvBuilder:
self.system_site_packages = False
self.create_configuration(context)
self.setup_python(context)
+ if not self.upgrade:
+ self.setup_scripts(context)
if self.with_pip:
self._setup_pip(context)
if not self.upgrade:
- self.setup_scripts(context)
self.post_setup(context)
if true_system_site_packages:
# We had set it to False before, now
@@ -157,14 +159,6 @@ class EnvBuilder:
f.write('include-system-site-packages = %s\n' % incl)
f.write('version = %d.%d.%d\n' % sys.version_info[:3])
- if os.name == 'nt':
- def include_binary(self, f):
- if f.endswith(('.pyd', '.dll')):
- result = True
- else:
- result = f.startswith('python') and f.endswith('.exe')
- return result
-
def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
"""
Try symlinking a file, and if that fails, fall back to copying.
@@ -194,9 +188,9 @@ class EnvBuilder:
binpath = context.bin_path
path = context.env_exe
copier = self.symlink_or_copy
- copier(context.executable, path)
dirname = context.python_dir
if os.name != 'nt':
+ copier(context.executable, path)
if not os.path.islink(path):
os.chmod(path, 0o755)
for suffix in ('python', 'python3'):
@@ -208,32 +202,33 @@ class EnvBuilder:
if not os.path.islink(path):
os.chmod(path, 0o755)
else:
- subdir = 'DLLs'
- include = self.include_binary
- files = [f for f in os.listdir(dirname) if include(f)]
- for f in files:
- src = os.path.join(dirname, f)
- dst = os.path.join(binpath, f)
- if dst != context.env_exe: # already done, above
- copier(src, dst)
- dirname = os.path.join(dirname, subdir)
- if os.path.isdir(dirname):
- files = [f for f in os.listdir(dirname) if include(f)]
- for f in files:
- src = os.path.join(dirname, f)
- dst = os.path.join(binpath, f)
- copier(src, dst)
- # copy init.tcl over
- for root, dirs, files in os.walk(context.python_dir):
- if 'init.tcl' in files:
- tcldir = os.path.basename(root)
- tcldir = os.path.join(context.env_dir, 'Lib', tcldir)
- if not os.path.exists(tcldir):
- os.makedirs(tcldir)
- src = os.path.join(root, 'init.tcl')
- dst = os.path.join(tcldir, 'init.tcl')
- shutil.copyfile(src, dst)
- break
+ # For normal cases, the venvlauncher will be copied from
+ # our scripts folder. For builds, we need to copy it
+ # manually.
+ if sysconfig.is_python_build(True):
+ suffix = '.exe'
+ if context.python_exe.lower().endswith('_d.exe'):
+ suffix = '_d.exe'
+
+ src = os.path.join(dirname, "venvlauncher" + suffix)
+ dst = os.path.join(binpath, context.python_exe)
+ copier(src, dst)
+
+ src = os.path.join(dirname, "venvwlauncher" + suffix)
+ dst = os.path.join(binpath, "pythonw" + suffix)
+ copier(src, dst)
+
+ # copy init.tcl over
+ for root, dirs, files in os.walk(context.python_dir):
+ if 'init.tcl' in files:
+ tcldir = os.path.basename(root)
+ tcldir = os.path.join(context.env_dir, 'Lib', tcldir)
+ if not os.path.exists(tcldir):
+ os.makedirs(tcldir)
+ src = os.path.join(root, 'init.tcl')
+ dst = os.path.join(tcldir, 'init.tcl')
+ shutil.copyfile(src, dst)
+ break
def _setup_pip(self, context):
"""Installs or upgrades pip in a virtual environment"""
@@ -320,7 +315,7 @@ class EnvBuilder:
dstfile = os.path.join(dstdir, f)
with open(srcfile, 'rb') as f:
data = f.read()
- if not srcfile.endswith('.exe'):
+ if not srcfile.endswith(('.exe', '.pdb')):
try:
data = data.decode('utf-8')
data = self.replace_variables(data, context)
diff --git a/Misc/NEWS.d/next/Windows/2018-12-07-10-00-38.bpo-34977.agQJbD.rst b/Misc/NEWS.d/next/Windows/2018-12-07-10-00-38.bpo-34977.agQJbD.rst
new file mode 100644
index 0000000000..12d8db2ab4
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2018-12-07-10-00-38.bpo-34977.agQJbD.rst
@@ -0,0 +1,2 @@
+venv on Windows will now use a python.exe redirector rather than copying the
+actual binaries from the base environment.
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 599b41b1ef..1b553d53af 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -536,10 +536,16 @@ static _PyInitError
get_program_full_path(const _PyCoreConfig *core_config,
PyCalculatePath *calculate, _PyPathConfig *config)
{
+ const wchar_t *pyvenv_launcher;
wchar_t program_full_path[MAXPATHLEN+1];
memset(program_full_path, 0, sizeof(program_full_path));
- if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
+ /* 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]) {
+ 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");
}
diff --git a/PC/launcher.c b/PC/launcher.c
index 7d666aae4a..f3a7ddc543 100644
--- a/PC/launcher.c
+++ b/PC/launcher.c
@@ -28,7 +28,7 @@
#define RC_NO_PYTHON 103
#define RC_NO_MEMORY 104
/*
- * SCRIPT_WRAPPER is used to choose between two variants of an executable built
+ * SCRIPT_WRAPPER is used to choose one of the variants of an executable built
* from this source file. If not defined, the PEP 397 Python launcher is built;
* if defined, a script launcher of the type used by setuptools is built, which
* looks for a script name related to the executable name and runs that script
@@ -40,6 +40,15 @@
#if defined(SCRIPT_WRAPPER)
#define RC_NO_SCRIPT 105
#endif
+/*
+ * VENV_REDIRECT is used to choose the variant that looks for an adjacent or
+ * one-level-higher pyvenv.cfg, and uses its "home" property to locate and
+ * launch the original python.exe.
+ */
+#if defined(VENV_REDIRECT)
+#define RC_NO_VENV_CFG 106
+#define RC_BAD_VENV_CFG 107
+#endif
/* Just for now - static definition */
@@ -97,7 +106,7 @@ error(int rc, wchar_t * format, ... )
#if !defined(_WINDOWS)
fwprintf(stderr, L"%ls\n", message);
#else
- MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."),
+ MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
MB_OK);
#endif
exit(rc);
@@ -131,6 +140,17 @@ static wchar_t * get_env(wchar_t * key)
return buf;
}
+#if defined(_DEBUG)
+#if defined(_WINDOWS)
+
+#define PYTHON_EXECUTABLE L"pythonw_d.exe"
+
+#else
+
+#define PYTHON_EXECUTABLE L"python_d.exe"
+
+#endif
+#else
#if defined(_WINDOWS)
#define PYTHON_EXECUTABLE L"pythonw.exe"
@@ -140,6 +160,7 @@ static wchar_t * get_env(wchar_t * key)
#define PYTHON_EXECUTABLE L"python.exe"
#endif
+#endif
#define MAX_VERSION_SIZE 4
@@ -1456,6 +1477,87 @@ show_python_list(wchar_t ** argv)
return FALSE; /* If this has been called we cannot continue */
}
+#if defined(VENV_REDIRECT)
+
+static int
+find_home_value(const char *buffer, const char **start, DWORD *length)
+{
+ for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) {
+ if (*s == '\n') {
+ ++s;
+ }
+ for (int i = 4; i > 0 && *s; --i, ++s);
+
+ while (*s && iswspace(*s)) {
+ ++s;
+ }
+ if (*s != L'=') {
+ continue;
+ }
+
+ do {
+ ++s;
+ } while (*s && iswspace(*s));
+
+ *start = s;
+ char *nl = strchr(s, '\n');
+ if (nl) {
+ *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s);
+ } else {
+ *length = (DWORD)strlen(s);
+ }
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+static wchar_t *
+wcsdup_pad(const wchar_t *s, int padding, int *newlen)
+{
+ size_t len = wcslen(s);
+ len += 1 + padding;
+ wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t));
+ if (!r) {
+ return NULL;
+ }
+ if (wcscpy_s(r, len, s)) {
+ free(r);
+ return NULL;
+ }
+ *newlen = len < MAXINT ? (int)len : MAXINT;
+ return r;
+}
+
+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) {
+ error(RC_NO_MEMORY, L"out of memory");
+ return NULL;
+ }
+ len = GetModuleFileNameW(NULL, r, bufferLen);
+ if (len == 0) {
+ free(r);
+ error(0, L"Failed to get module name");
+ return NULL;
+ } else if (len == bufferLen &&
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ free(r);
+ r = NULL;
+ bufferLen *= 2;
+ }
+ }
+
+ return r;
+}
+
static int
process(int argc, wchar_t ** argv)
{
@@ -1463,21 +1565,27 @@ process(int argc, wchar_t ** argv)
wchar_t * command;
wchar_t * executable;
wchar_t * p;
+ wchar_t * argv0;
int rc = 0;
- size_t plen;
INSTALLED_PYTHON * ip;
BOOL valid;
DWORD size, attrs;
- HRESULT hr;
wchar_t message[MSGSIZE];
void * version_data;
VS_FIXEDFILEINFO * file_info;
UINT block_size;
- int index;
-#if defined(SCRIPT_WRAPPER)
+#if defined(VENV_REDIRECT)
+ wchar_t * venv_cfg_path;
int newlen;
+#elif defined(SCRIPT_WRAPPER)
wchar_t * newcommand;
wchar_t * av[2];
+ int newlen;
+ HRESULT hr;
+ int index;
+#else
+ HRESULT hr;
+ int index;
#endif
setvbuf(stderr, (char *)NULL, _IONBF, 0);
@@ -1495,6 +1603,7 @@ process(int argc, wchar_t ** argv)
#else
debug(L"launcher executable: Console\n");
#endif
+#if !defined(VENV_REDIRECT)
/* Get the local appdata folder (non-roaming) */
hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA,
NULL, 0, appdata_ini_path);
@@ -1503,9 +1612,7 @@ process(int argc, wchar_t ** argv)
appdata_ini_path[0] = L'\0';
}
else {
- plen = wcslen(appdata_ini_path);
- p = &appdata_ini_path[plen];
- wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE);
+ wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE);
attrs = GetFileAttributesW(appdata_ini_path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
debug(L"File '%ls' non-existent\n", appdata_ini_path);
@@ -1514,8 +1621,9 @@ process(int argc, wchar_t ** argv)
debug(L"Using local configuration file '%ls'\n", appdata_ini_path);
}
}
- plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH);
- size = GetFileVersionInfoSizeW(launcher_ini_path, &size);
+#endif
+ argv0 = get_process_name();
+ size = GetFileVersionInfoSizeW(argv0, &size);
if (size == 0) {
winerror(GetLastError(), message, MSGSIZE);
debug(L"GetFileVersionInfoSize failed: %ls\n", message);
@@ -1523,7 +1631,7 @@ process(int argc, wchar_t ** argv)
else {
version_data = malloc(size);
if (version_data) {
- valid = GetFileVersionInfoW(launcher_ini_path, 0, size,
+ valid = GetFileVersionInfoW(argv0, 0, size,
version_data);
if (!valid)
debug(L"GetFileVersionInfo failed: %X\n", GetLastError());
@@ -1540,15 +1648,51 @@ process(int argc, wchar_t ** argv)
free(version_data);
}
}
+
+#if defined(VENV_REDIRECT)
+ /* Allocate some extra space for new filenames */
+ venv_cfg_path = wcsdup_pad(argv0, 32, &newlen);
+ if (!venv_cfg_path) {
+ error(RC_NO_MEMORY, L"Failed to copy module name");
+ }
+ p = wcsrchr(venv_cfg_path, L'\\');
+
+ if (p == NULL) {
+ error(RC_NO_VENV_CFG, L"No pyvenv.cfg file");
+ }
+ p[0] = L'\0';
+ wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg");
+ attrs = GetFileAttributesW(venv_cfg_path);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ debug(L"File '%ls' non-existent\n", venv_cfg_path);
+ p[0] = '\0';
+ p = wcsrchr(venv_cfg_path, L'\\');
+ if (p != NULL) {
+ p[0] = '\0';
+ wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg");
+ attrs = GetFileAttributesW(venv_cfg_path);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ debug(L"File '%ls' non-existent\n", venv_cfg_path);
+ error(RC_NO_VENV_CFG, L"No pyvenv.cfg file");
+ }
+ }
+ }
+ debug(L"Using venv configuration file '%ls'\n", venv_cfg_path);
+#else
+ /* Allocate some extra space for new filenames */
+ if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) {
+ error(RC_NO_MEMORY, L"Failed to copy module name");
+ }
p = wcsrchr(launcher_ini_path, L'\\');
+
if (p == NULL) {
debug(L"GetModuleFileNameW returned value has no backslash: %ls\n",
launcher_ini_path);
launcher_ini_path[0] = L'\0';
}
else {
- wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini",
- _TRUNCATE);
+ p[0] = L'\0';
+ wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini");
attrs = GetFileAttributesW(launcher_ini_path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
debug(L"File '%ls' non-existent\n", launcher_ini_path);
@@ -1557,6 +1701,7 @@ process(int argc, wchar_t ** argv)
debug(L"Using global configuration file '%ls'\n", launcher_ini_path);
}
}
+#endif
command = skip_me(GetCommandLineW());
debug(L"Called with command line: %ls\n", command);
@@ -1592,6 +1737,55 @@ process(int argc, wchar_t ** argv)
command = newcommand;
valid = FALSE;
}
+#elif defined(VENV_REDIRECT)
+ {
+ FILE *f;
+ char buffer[4096]; /* 4KB should be enough for anybody */
+ char *start;
+ DWORD len, cch, cch_actual;
+ size_t cb;
+ if (_wfopen_s(&f, venv_cfg_path, L"r")) {
+ error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path);
+ }
+ cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]),
+ sizeof(buffer) / sizeof(buffer[0]), f);
+ fclose(f);
+
+ if (!find_home_value(buffer, &start, &len)) {
+ error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'",
+ venv_cfg_path);
+ }
+
+ cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0);
+ if (!cch) {
+ error(0, L"Cannot determine memory for home path");
+ }
+ cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */
+ executable = (wchar_t *)malloc(cch * sizeof(wchar_t));
+ if (executable == NULL) {
+ error(RC_NO_MEMORY, L"A memory allocation failed");
+ }
+ cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch);
+ if (!cch_actual) {
+ error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'",
+ venv_cfg_path);
+ }
+ if (executable[cch_actual - 1] != L'\\') {
+ executable[cch_actual++] = L'\\';
+ executable[cch_actual] = L'\0';
+ }
+ if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) {
+ error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'",
+ venv_cfg_path);
+ }
+ if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) {
+ error(RC_NO_PYTHON, L"No Python at '%ls'", executable);
+ }
+ if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) {
+ error(0, L"Failed to set launcher environment");
+ }
+ valid = 1;
+ }
#else
if (argc <= 1) {
valid = FALSE;
@@ -1599,7 +1793,6 @@ process(int argc, wchar_t ** argv)
}
else {
p = argv[1];
- plen = wcslen(p);
if ((argc == 2) && // list version args
(!wcsncmp(p, L"-0", wcslen(L"-0")) ||
!wcsncmp(p, L"--list", wcslen(L"--list"))))
diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj
index 9e103e1210..77341b44a1 100644
--- a/PCbuild/pcbuild.proj
+++ b/PCbuild/pcbuild.proj
@@ -70,6 +70,8 @@
<Projects2 Include="_freeze_importlib.vcxproj" />
<!-- python[w].exe -->
<Projects2 Include="python.vcxproj;pythonw.vcxproj" />
+ <!-- venv[w]launcher.exe -->
+ <Projects2 Include="venvlauncher.vcxproj;venvwlauncher.vcxproj" />
</ItemGroup>
<Target Name="Build">
diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj
new file mode 100644
index 0000000000..295b363047
--- /dev/null
+++ b/PCbuild/venvlauncher.vcxproj
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|Win32">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|x64">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|Win32">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|x64">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{494BAC80-A60C-43A9-99E7-ACB691CE2C4D}</ProjectGuid>
+ <RootNamespace>venvlauncher</RootNamespace>
+ <TargetName>venvlauncher</TargetName>
+ <SupportPGO>false</SupportPGO>
+ </PropertyGroup>
+ <Import Project="python.props" />
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <PropertyGroup>
+ <MakeVersionInfoBeforeTarget>ClCompile</MakeVersionInfoBeforeTarget>
+ </PropertyGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="pyproject.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <PreprocessorDefinitions>_CONSOLE;VENV_REDIRECT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>PY_ICON;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Console</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\PC\launcher.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\PC\launcher.ico" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\PC\pylauncher.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/PCbuild/venvwlauncher.vcxproj b/PCbuild/venvwlauncher.vcxproj
new file mode 100644
index 0000000000..e7ba25da41
--- /dev/null
+++ b/PCbuild/venvwlauncher.vcxproj
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|Win32">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|x64">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|Win32">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|x64">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{FDB84CBB-2FB6-47C8-A2D6-091E0833239D}</ProjectGuid>
+ <RootNamespace>venvwlauncher</RootNamespace>
+ <TargetName>venvwlauncher</TargetName>
+ <SupportPGO>false</SupportPGO>
+ </PropertyGroup>
+ <Import Project="python.props" />
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <PropertyGroup>
+ <MakeVersionInfoBeforeTarget>ClCompile</MakeVersionInfoBeforeTarget>
+ </PropertyGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="pyproject.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <PreprocessorDefinitions>_WINDOWS;VENV_REDIRECT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>PYW_ICON;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\PC\launcher.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\PC\launcher.ico" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\PC\pylauncher.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/Tools/msi/lib/lib_files.wxs b/Tools/msi/lib/lib_files.wxs
index da9d1c9f34..4bd0c57e32 100644
--- a/Tools/msi/lib/lib_files.wxs
+++ b/Tools/msi/lib/lib_files.wxs
@@ -2,6 +2,8 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_msi;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_contextvars ?>
<Fragment>
+ <DirectoryRef Id="Lib_venv_scripts_nt" />
+
<ComponentGroup Id="lib_extensions">
<?foreach ext in $(var.exts)?>
@@ -20,10 +22,25 @@
<Component Id="libssl.dll" Directory="DLLs" Guid="*">
<File Name="libssl$(var.ssltag).dll" KeyPath="yes" />
</Component>
+ <Component Id="venvlauncher.exe" Directory="Lib_venv_scripts_nt" Guid="*">
+ <File Name="python.exe" Source="venvlauncher.exe" KeyPath="yes" />
+ </Component>
+ <Component Id="venvwlauncher.exe" Directory="Lib_venv_scripts_nt" Guid="*">
+ <File Name="pythonw.exe" Source="venvwlauncher.exe" KeyPath="yes" />
+ </Component>
</ComponentGroup>
</Fragment>
<Fragment>
+ <!-- The auto-generated directory is not available when building symbols -->
+ <DirectoryRef Id="Lib">
+ <Directory Id="Lib_venv__pdbs" Name="venv">
+ <Directory Id="Lib_venv_scripts__pdbs" Name="scripts">
+ <Directory Id="Lib_venv_scripts_nt__pdbs" Name="nt" />
+ </Directory>
+ </Directory>
+ </DirectoryRef>
+
<ComponentGroup Id="lib_extensions_symbols">
<?foreach ext in $(var.exts)?>
@@ -42,6 +59,12 @@
<Component Id="libssl.pdb" Directory="DLLs" Guid="*">
<File Name="libssl$(var.ssltag).pdb" KeyPath="yes" />
</Component>
+ <Component Id="venvlauncher.pdb" Directory="Lib_venv_scripts_nt__pdbs" Guid="*">
+ <File Name="python.pdb" Source="venvlauncher.pdb" KeyPath="yes" />
+ </Component>
+ <Component Id="venvwlauncher.pdb" Directory="Lib_venv_scripts_nt__pdbs" Guid="*">
+ <File Name="pythonw.pdb" Source="venvwlauncher.pdb" KeyPath="yes" />
+ </Component>
</ComponentGroup>
</Fragment>