summaryrefslogtreecommitdiffstats
path: root/PC
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2024-01-17 21:52:23 (GMT)
committerGitHub <noreply@github.com>2024-01-17 21:52:23 (GMT)
commitf56d132deb9fff861439ed56ed7414d22e4e4bb9 (patch)
tree69e70221cd40f134de9abf1292f228ec7d5c9c11 /PC
parent78fcde039a33d8463e34356d5462fecee0f2831a (diff)
downloadcpython-f56d132deb9fff861439ed56ed7414d22e4e4bb9.zip
cpython-f56d132deb9fff861439ed56ed7414d22e4e4bb9.tar.gz
cpython-f56d132deb9fff861439ed56ed7414d22e4e4bb9.tar.bz2
gh-112984 Update Windows build and installer for free-threaded builds (GH-113129)
Diffstat (limited to 'PC')
-rw-r--r--PC/layout/main.py110
-rw-r--r--PC/layout/support/constants.py3
-rw-r--r--PC/layout/support/nuspec.py9
-rw-r--r--PC/layout/support/options.py19
-rw-r--r--PC/pyconfig.h.in19
-rw-r--r--PC/venvlauncher.c510
6 files changed, 639 insertions, 31 deletions
diff --git a/PC/layout/main.py b/PC/layout/main.py
index accfd51..d176b27 100644
--- a/PC/layout/main.py
+++ b/PC/layout/main.py
@@ -41,7 +41,7 @@ TCLTK_FILES_ONLY = FileNameSet("turtle.py")
VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip")
-EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext", "vcruntime*")
+EXCLUDE_FROM_DLLS = FileStemSet("python*", "pyshellext", "vcruntime*")
EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle")
EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt")
EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*")
@@ -126,9 +126,9 @@ def get_layout(ns):
n = new_name or n
src = ns.build / f
if ns.debug and src not in REQUIRED_DLLS:
- if not src.stem.endswith("_d"):
+ if not "_d." in src.name:
src = src.parent / (src.stem + "_d" + src.suffix)
- if not n.endswith("_d"):
+ if "_d." not in f:
n += "_d"
f = n + "." + x
yield dest + n + "." + x, src
@@ -141,17 +141,45 @@ def get_layout(ns):
if lib.is_file():
yield "libs/" + n + ".lib", lib
+ source = "python.exe"
+ sourcew = "pythonw.exe"
+ alias = [
+ "python",
+ "python{}".format(VER_MAJOR) if ns.include_alias3 else "",
+ "python{}".format(VER_DOT) if ns.include_alias3x else "",
+ ]
+ aliasw = [
+ "pythonw",
+ "pythonw{}".format(VER_MAJOR) if ns.include_alias3 else "",
+ "pythonw{}".format(VER_DOT) if ns.include_alias3x else "",
+ ]
if ns.include_appxmanifest:
- yield from in_build("python_uwp.exe", new_name="python{}".format(VER_DOT))
- yield from in_build("pythonw_uwp.exe", new_name="pythonw{}".format(VER_DOT))
- # For backwards compatibility, but we don't reference these ourselves.
- yield from in_build("python_uwp.exe", new_name="python")
- yield from in_build("pythonw_uwp.exe", new_name="pythonw")
+ source = "python_uwp.exe"
+ sourcew = "pythonw_uwp.exe"
+ elif ns.include_freethreaded:
+ source = "python{}t.exe".format(VER_DOT)
+ sourcew = "pythonw{}t.exe".format(VER_DOT)
+ if not ns.include_alias:
+ alias = []
+ aliasw = []
+ alias.extend([
+ "python{}t".format(VER_DOT),
+ "python{}t".format(VER_MAJOR) if ns.include_alias3 else None,
+ ])
+ aliasw.extend([
+ "pythonw{}t".format(VER_DOT),
+ "pythonw{}t".format(VER_MAJOR) if ns.include_alias3 else None,
+ ])
+
+ for a in filter(None, alias):
+ yield from in_build(source, new_name=a)
+ for a in filter(None, aliasw):
+ yield from in_build(sourcew, new_name=a)
+
+ if ns.include_freethreaded:
+ yield from in_build(FREETHREADED_PYTHON_DLL_NAME)
else:
- yield from in_build("python.exe", new_name="python")
- yield from in_build("pythonw.exe", new_name="pythonw")
-
- yield from in_build(PYTHON_DLL_NAME)
+ yield from in_build(PYTHON_DLL_NAME)
if ns.include_launchers and ns.include_appxmanifest:
if ns.include_pip:
@@ -160,7 +188,10 @@ def get_layout(ns):
yield from in_build("pythonw_uwp.exe", new_name="idle{}".format(VER_DOT))
if ns.include_stable:
- yield from in_build(PYTHON_STABLE_DLL_NAME)
+ if ns.include_freethreaded:
+ yield from in_build(FREETHREADED_PYTHON_STABLE_DLL_NAME)
+ else:
+ yield from in_build(PYTHON_STABLE_DLL_NAME)
found_any = False
for dest, src in rglob(ns.build, "vcruntime*.dll"):
@@ -171,16 +202,28 @@ def get_layout(ns):
yield "LICENSE.txt", ns.build / "LICENSE.txt"
- for dest, src in rglob(ns.build, ("*.pyd", "*.dll")):
- if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS:
- continue
- if src in EXCLUDE_FROM_PYDS:
- continue
+ for dest, src in rglob(ns.build, "*.pyd"):
+ if ns.include_freethreaded:
+ if not src.match("*.cp*t-win*.pyd"):
+ continue
+ if bool(src.match("*_d.cp*.pyd")) != bool(ns.debug):
+ continue
+ else:
+ if src.match("*.cp*t-win*.pyd"):
+ continue
+ if bool(src.match("*_d.pyd")) != bool(ns.debug):
+ continue
if src in TEST_PYDS_ONLY and not ns.include_tests:
continue
if src in TCLTK_PYDS_ONLY and not ns.include_tcltk:
continue
+ yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/")
+ for dest, src in rglob(ns.build, "*.dll"):
+ if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS:
+ continue
+ if src in EXCLUDE_FROM_DLLS:
+ continue
yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/")
if ns.zip_lib:
@@ -191,8 +234,12 @@ def get_layout(ns):
yield "Lib/{}".format(dest), src
if ns.include_venv:
- yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python")
- yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw")
+ if ns.include_freethreaded:
+ yield from in_build("venvlaunchert.exe", "Lib/venv/scripts/nt/")
+ yield from in_build("venvwlaunchert.exe", "Lib/venv/scripts/nt/")
+ else:
+ yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/")
+ yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/")
if ns.include_tools:
@@ -208,7 +255,6 @@ def get_layout(ns):
yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME
if ns.include_dev:
-
for dest, src in rglob(ns.source / "Include", "**/*.h"):
yield "include/{}".format(dest), src
yield "include/pyconfig.h", ns.build / "pyconfig.h"
@@ -552,7 +598,6 @@ def main():
ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent)
ns.build = ns.build or Path(sys.executable).parent
- ns.temp = ns.temp or Path(tempfile.mkdtemp())
ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build")
if not ns.source.is_absolute():
ns.source = (Path.cwd() / ns.source).resolve()
@@ -565,7 +610,12 @@ def main():
if ns.include_cat and not ns.include_cat.is_absolute():
ns.include_cat = (Path.cwd() / ns.include_cat).resolve()
if not ns.arch:
- ns.arch = "amd64" if sys.maxsize > 2 ** 32 else "win32"
+ if sys.winver.endswith("-arm64"):
+ ns.arch = "arm64"
+ elif sys.winver.endswith("-32"):
+ ns.arch = "win32"
+ else:
+ ns.arch = "amd64"
if ns.copy and not ns.copy.is_absolute():
ns.copy = (Path.cwd() / ns.copy).resolve()
@@ -574,6 +624,14 @@ def main():
if ns.catalog and not ns.catalog.is_absolute():
ns.catalog = (Path.cwd() / ns.catalog).resolve()
+ if not ns.temp:
+ # Put temp on a Dev Drive for speed if we're copying to one.
+ # If not, the regular temp dir will have to do.
+ if ns.copy and getattr(os.path, "isdevdrive", lambda d: False)(ns.copy):
+ ns.temp = ns.copy.with_name(ns.copy.name + "_temp")
+ else:
+ ns.temp = Path(tempfile.mkdtemp())
+
configure_logger(ns)
log_info(
@@ -602,6 +660,12 @@ Catalog: {ns.catalog}""",
log_warning("Assuming --include-tcltk to support --include-idle")
ns.include_tcltk = True
+ if not (ns.include_alias or ns.include_alias3 or ns.include_alias3x):
+ if ns.include_freethreaded:
+ ns.include_alias3x = True
+ else:
+ ns.include_alias = True
+
try:
generate_source_files(ns)
files = list(get_layout(ns))
diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py
index 8195c3d..ae22aa1 100644
--- a/PC/layout/support/constants.py
+++ b/PC/layout/support/constants.py
@@ -39,3 +39,6 @@ PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR)
PYTHON_CHM_NAME = "python{}{}{}{}.chm".format(
VER_MAJOR, VER_MINOR, VER_MICRO, VER_SUFFIX
)
+
+FREETHREADED_PYTHON_DLL_NAME = "python{}{}t.dll".format(VER_MAJOR, VER_MINOR)
+FREETHREADED_PYTHON_STABLE_DLL_NAME = "python{}t.dll".format(VER_MAJOR)
diff --git a/PC/layout/support/nuspec.py b/PC/layout/support/nuspec.py
index dbcb713..a87e0be 100644
--- a/PC/layout/support/nuspec.py
+++ b/PC/layout/support/nuspec.py
@@ -24,6 +24,10 @@ NUSPEC_PLATFORM_DATA = dict(
amd64=("64-bit", "python", "Python"),
arm32=("ARM", "pythonarm", "Python (ARM)"),
arm64=("ARM64", "pythonarm64", "Python (ARM64)"),
+ win32t=("32-bit free-threaded", "pythonx86-freethreaded", "Python (32-bit, free-threaded)"),
+ amd64t=("64-bit free-threaded", "python-freethreaded", "Python (free-threaded)"),
+ arm32t=("ARM free-threaded", "pythonarm-freethreaded", "Python (ARM, free-threaded)"),
+ arm64t=("ARM64 free-threaded", "pythonarm64-freethreaded", "Python (ARM64, free-threaded)"),
)
if not NUSPEC_DATA["PYTHON_VERSION"]:
@@ -58,7 +62,10 @@ NUSPEC_TEMPLATE = r"""<?xml version="1.0"?>
def _get_nuspec_data_overrides(ns):
- for k, v in zip(NUSPEC_PLATFORM_DATA["_keys"], NUSPEC_PLATFORM_DATA[ns.arch]):
+ arch = ns.arch
+ if ns.include_freethreaded:
+ arch += "t"
+ for k, v in zip(NUSPEC_PLATFORM_DATA["_keys"], NUSPEC_PLATFORM_DATA[arch]):
ev = os.getenv("PYTHON_NUSPEC_" + k)
if ev:
yield k, ev
diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py
index 60256fb..f1a8eb0 100644
--- a/PC/layout/support/options.py
+++ b/PC/layout/support/options.py
@@ -32,6 +32,10 @@ OPTIONS = {
"nuspec": {"help": "a python.nuspec file"},
"chm": {"help": "the CHM documentation"},
"html-doc": {"help": "the HTML documentation"},
+ "freethreaded": {"help": "freethreaded binaries", "not-in-all": True},
+ "alias": {"help": "aliased python.exe entry-point binaries"},
+ "alias3": {"help": "aliased python3.exe entry-point binaries"},
+ "alias3x": {"help": "aliased python3.x.exe entry-point binaries"},
}
@@ -47,6 +51,8 @@ PRESETS = {
"dev",
"launchers",
"appxmanifest",
+ "alias",
+ "alias3x",
# XXX: Disabled for now "precompile",
],
},
@@ -59,9 +65,10 @@ PRESETS = {
"venv",
"props",
"nuspec",
+ "alias",
],
},
- "iot": {"help": "Windows IoT Core", "options": ["stable", "pip"]},
+ "iot": {"help": "Windows IoT Core", "options": ["alias", "stable", "pip"]},
"default": {
"help": "development kit package",
"options": [
@@ -74,11 +81,19 @@ PRESETS = {
"dev",
"symbols",
"html-doc",
+ "alias",
],
},
"embed": {
"help": "embeddable package",
- "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"],
+ "options": [
+ "alias",
+ "stable",
+ "zip-lib",
+ "flat-dlls",
+ "underpth",
+ "precompile",
+ ],
},
}
diff --git a/PC/pyconfig.h.in b/PC/pyconfig.h.in
index d8f0a6b..8bbf877 100644
--- a/PC/pyconfig.h.in
+++ b/PC/pyconfig.h.in
@@ -94,6 +94,9 @@ WIN32 is still required for the locale module.
#endif
#endif /* Py_BUILD_CORE || Py_BUILD_CORE_BUILTIN || Py_BUILD_CORE_MODULE */
+/* Define to 1 if you want to disable the GIL */
+#undef Py_GIL_DISABLED
+
/* Compiler specific defines */
/* ------------------------------------------------------------------------*/
@@ -305,8 +308,16 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
/* not building the core - must be an ext */
# if defined(_MSC_VER)
/* So MSVC users need not specify the .lib
- file in their Makefile (other compilers are
- generally taken care of by distutils.) */
+ file in their Makefile */
+# if defined(Py_GIL_DISABLED)
+# if defined(_DEBUG)
+# pragma comment(lib,"python313t_d.lib")
+# elif defined(Py_LIMITED_API)
+# pragma comment(lib,"python3t.lib")
+# else
+# pragma comment(lib,"python313t.lib")
+# endif /* _DEBUG */
+# else /* Py_GIL_DISABLED */
# if defined(_DEBUG)
# pragma comment(lib,"python313_d.lib")
# elif defined(Py_LIMITED_API)
@@ -314,6 +325,7 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
# else
# pragma comment(lib,"python313.lib")
# endif /* _DEBUG */
+# endif /* Py_GIL_DISABLED */
# endif /* _MSC_VER */
# endif /* Py_BUILD_CORE */
#endif /* MS_COREDLL */
@@ -739,7 +751,4 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
/* Define if libssl has X509_VERIFY_PARAM_set1_host and related function */
#define HAVE_X509_VERIFY_PARAM_SET1_HOST 1
-/* Define if you want to disable the GIL */
-#undef Py_GIL_DISABLED
-
#endif /* !Py_CONFIG_H */
diff --git a/PC/venvlauncher.c b/PC/venvlauncher.c
new file mode 100644
index 0000000..fe97d32
--- /dev/null
+++ b/PC/venvlauncher.c
@@ -0,0 +1,510 @@
+/*
+ * venv redirector for Windows
+ *
+ * This launcher looks for a nearby pyvenv.cfg to find the correct home
+ * directory, and then launches the original Python executable from it.
+ * The name of this executable is passed as argv[0].
+ */
+
+#define __STDC_WANT_LIB_EXT1__ 1
+
+#include <windows.h>
+#include <pathcch.h>
+#include <fcntl.h>
+#include <io.h>
+#include <shlobj.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <tchar.h>
+#include <assert.h>
+
+#define MS_WINDOWS
+#include "patchlevel.h"
+
+#define MAXLEN PATHCCH_MAX_CCH
+#define MSGSIZE 1024
+
+#define RC_NO_STD_HANDLES 100
+#define RC_CREATE_PROCESS 101
+#define RC_NO_PYTHON 103
+#define RC_NO_MEMORY 104
+#define RC_NO_VENV_CFG 106
+#define RC_BAD_VENV_CFG 107
+#define RC_NO_COMMANDLINE 108
+#define RC_INTERNAL_ERROR 109
+
+// This should always be defined when we build for real,
+// but it's handy to have a definition for quick testing
+#ifndef EXENAME
+#define EXENAME L"python.exe"
+#endif
+
+#ifndef CFGNAME
+#define CFGNAME L"pyvenv.cfg"
+#endif
+
+static FILE * log_fp = NULL;
+
+void
+debug(wchar_t * format, ...)
+{
+ va_list va;
+
+ if (log_fp != NULL) {
+ wchar_t buffer[MAXLEN];
+ int r = 0;
+ va_start(va, format);
+ r = vswprintf_s(buffer, MAXLEN, format, va);
+ va_end(va);
+
+ if (r <= 0) {
+ return;
+ }
+ fwprintf(log_fp, L"%ls\n", buffer);
+ while (r && isspace(buffer[r])) {
+ buffer[r--] = L'\0';
+ }
+ if (buffer[0]) {
+ OutputDebugStringW(buffer);
+ }
+ }
+}
+
+
+void
+formatWinerror(int rc, wchar_t * message, int size)
+{
+ FormatMessageW(
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ message, size, NULL);
+}
+
+
+void
+winerror(int err, wchar_t * format, ... )
+{
+ va_list va;
+ wchar_t message[MSGSIZE];
+ wchar_t win_message[MSGSIZE];
+ int len;
+
+ if (err == 0) {
+ err = GetLastError();
+ }
+
+ va_start(va, format);
+ len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
+ va_end(va);
+
+ formatWinerror(err, win_message, MSGSIZE);
+ if (len >= 0) {
+ _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls",
+ win_message);
+ }
+
+#if !defined(_WINDOWS)
+ fwprintf(stderr, L"%ls\n", message);
+#else
+ MessageBoxW(NULL, message, L"Python venv launcher is sorry to say ...",
+ MB_OK);
+#endif
+}
+
+
+void
+error(wchar_t * format, ... )
+{
+ va_list va;
+ wchar_t message[MSGSIZE];
+
+ va_start(va, format);
+ _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
+ va_end(va);
+
+#if !defined(_WINDOWS)
+ fwprintf(stderr, L"%ls\n", message);
+#else
+ MessageBoxW(NULL, message, L"Python venv launcher is sorry to say ...",
+ MB_OK);
+#endif
+}
+
+
+bool
+isEnvVarSet(const wchar_t *name)
+{
+ /* only looking for non-empty, which means at least one character
+ and the null terminator */
+ return GetEnvironmentVariableW(name, NULL, 0) >= 2;
+}
+
+
+bool
+join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment)
+{
+ if (SUCCEEDED(PathCchCombineEx(buffer, bufferLength, buffer, fragment, PATHCCH_ALLOW_LONG_PATHS))) {
+ return true;
+ }
+ return false;
+}
+
+
+bool
+split_parent(wchar_t *buffer, size_t bufferLength)
+{
+ return SUCCEEDED(PathCchRemoveFileSpec(buffer, bufferLength));
+}
+
+
+/*
+ * Path calculation
+ */
+
+int
+calculate_pyvenvcfg_path(wchar_t *pyvenvcfg_path, size_t maxlen)
+{
+ if (!pyvenvcfg_path) {
+ error(L"invalid buffer provided");
+ return RC_INTERNAL_ERROR;
+ }
+ if ((DWORD)maxlen != maxlen) {
+ error(L"path buffer is too large");
+ return RC_INTERNAL_ERROR;
+ }
+ if (!GetModuleFileNameW(NULL, pyvenvcfg_path, (DWORD)maxlen)) {
+ winerror(GetLastError(), L"failed to read executable directory");
+ return RC_NO_COMMANDLINE;
+ }
+ // Remove 'python.exe' from our path
+ if (!split_parent(pyvenvcfg_path, maxlen)) {
+ error(L"failed to remove segment from '%ls'", pyvenvcfg_path);
+ return RC_NO_COMMANDLINE;
+ }
+ // Replace with 'pyvenv.cfg'
+ if (!join(pyvenvcfg_path, maxlen, CFGNAME)) {
+ error(L"failed to append '%ls' to '%ls'", CFGNAME, pyvenvcfg_path);
+ return RC_NO_MEMORY;
+ }
+ // If it exists, return
+ if (GetFileAttributesW(pyvenvcfg_path) != INVALID_FILE_ATTRIBUTES) {
+ return 0;
+ }
+ // Otherwise, remove 'pyvenv.cfg' and (probably) 'Scripts'
+ if (!split_parent(pyvenvcfg_path, maxlen) ||
+ !split_parent(pyvenvcfg_path, maxlen)) {
+ error(L"failed to remove segments from '%ls'", pyvenvcfg_path);
+ return RC_NO_COMMANDLINE;
+ }
+ // Replace 'pyvenv.cfg'
+ if (!join(pyvenvcfg_path, maxlen, CFGNAME)) {
+ error(L"failed to append '%ls' to '%ls'", CFGNAME, pyvenvcfg_path);
+ return RC_NO_MEMORY;
+ }
+ // If it exists, return
+ if (GetFileAttributesW(pyvenvcfg_path) != INVALID_FILE_ATTRIBUTES) {
+ return 0;
+ }
+ // Otherwise, we fail
+ winerror(GetLastError(), L"failed to locate %ls", CFGNAME);
+ return RC_NO_VENV_CFG;
+}
+
+
+/*
+ * pyvenv.cfg parsing
+ */
+
+static int
+find_home_value(const char *buffer, DWORD maxlen, const char **start, DWORD *length)
+{
+ if (!buffer || !start || !length) {
+ error(L"invalid find_home_value parameters()");
+ return 0;
+ }
+ for (const char *s = strstr(buffer, "home");
+ s && ((ptrdiff_t)s - (ptrdiff_t)buffer) < maxlen;
+ 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) {
+ while (nl != s && iswspace(nl[-1])) {
+ --nl;
+ }
+ *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s);
+ } else {
+ *length = (DWORD)strlen(s);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+int
+read_home(const wchar_t *pyvenv_cfg, wchar_t *home_path, size_t maxlen)
+{
+ HANDLE hFile = CreateFileW(pyvenv_cfg, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, 0, NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ winerror(GetLastError(), L"failed to open '%ls'", pyvenv_cfg);
+ return RC_BAD_VENV_CFG;
+ }
+
+ // 8192 characters ought to be enough for anyone
+ // (doubled compared to the old implementation!)
+ char buffer[8192];
+ DWORD len;
+ if (!ReadFile(hFile, buffer, sizeof(buffer) - 1, &len, NULL)) {
+ winerror(GetLastError(), L"failed to read '%ls'", pyvenv_cfg);
+ CloseHandle(hFile);
+ return RC_BAD_VENV_CFG;
+ }
+ CloseHandle(hFile);
+ // Ensure null termination
+ buffer[len] = '\0';
+
+ char *home;
+ DWORD home_len;
+ if (!find_home_value(buffer, sizeof(buffer), &home, &home_len)) {
+ error(L"no home= specified in '%ls'", pyvenv_cfg);
+ return RC_BAD_VENV_CFG;
+ }
+
+ if ((DWORD)maxlen != maxlen) {
+ maxlen = 8192;
+ }
+ len = MultiByteToWideChar(CP_UTF8, 0, home, home_len, home_path, (DWORD)maxlen);
+ if (!len) {
+ winerror(GetLastError(), L"failed to decode home setting in '%ls'", pyvenv_cfg);
+ return RC_BAD_VENV_CFG;
+ }
+ home_path[len] = L'\0';
+
+ return 0;
+}
+
+
+int
+locate_python(wchar_t *path, size_t maxlen)
+{
+ if (!join(path, maxlen, EXENAME)) {
+ error(L"failed to append %ls to '%ls'", EXENAME, path);
+ return RC_NO_MEMORY;
+ }
+
+ if (GetFileAttributesW(path) == INVALID_FILE_ATTRIBUTES) {
+ winerror(GetLastError(), L"did not find executable at '%ls'", path);
+ return RC_NO_PYTHON;
+ }
+
+ return 0;
+}
+
+
+int
+smuggle_path()
+{
+ wchar_t buffer[MAXLEN];
+ // We could use argv[0], but that may be wrong in certain rare cases (if the
+ // user is doing something weird like symlinks to venv redirectors), and
+ // what we _really_ want is the directory of the venv. We always copy the
+ // redirectors, so if we've made the venv, this will be correct.
+ DWORD len = GetModuleFileNameW(NULL, buffer, MAXLEN);
+ if (!len) {
+ winerror(GetLastError(), L"Failed to get own executable path");
+ return RC_INTERNAL_ERROR;
+ }
+ buffer[len] = L'\0';
+ debug(L"Setting __PYVENV_LAUNCHER__ = '%s'", buffer);
+
+ if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", buffer)) {
+ winerror(GetLastError(), L"Failed to set launcher environment");
+ return RC_INTERNAL_ERROR;
+ }
+
+ return 0;
+}
+
+/*
+ * Process creation
+ */
+
+static BOOL
+safe_duplicate_handle(HANDLE in, HANDLE * pout, const wchar_t *name)
+{
+ BOOL ok;
+ HANDLE process = GetCurrentProcess();
+ DWORD rc;
+
+ *pout = NULL;
+ ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
+ DUPLICATE_SAME_ACCESS);
+ if (!ok) {
+ rc = GetLastError();
+ if (rc == ERROR_INVALID_HANDLE) {
+ debug(L"DuplicateHandle(%ls) returned ERROR_INVALID_HANDLE\n", name);
+ ok = TRUE;
+ }
+ else {
+ debug(L"DuplicateHandle(%ls) returned %d\n", name, rc);
+ }
+ }
+ return ok;
+}
+
+static BOOL WINAPI
+ctrl_c_handler(DWORD code)
+{
+ return TRUE; /* We just ignore all control events. */
+}
+
+static int
+launch(const wchar_t *executable, wchar_t *cmdline)
+{
+ HANDLE job;
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
+ DWORD rc;
+ BOOL ok;
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+
+#if defined(_WINDOWS)
+ /*
+ When explorer launches a Windows (GUI) application, it displays
+ the "app starting" (the "pointer + hourglass") cursor for a number
+ of seconds, or until the app does something UI-ish (eg, creating a
+ window, or fetching a message). As this launcher doesn't do this
+ directly, that cursor remains even after the child process does these
+ things. We avoid that by doing a simple post+get message.
+ See http://bugs.python.org/issue17290
+ */
+ MSG msg;
+
+ PostMessage(0, 0, 0, 0);
+ GetMessage(&msg, 0, 0, 0);
+#endif
+
+ debug(L"run_child: about to run '%ls' with '%ls'\n", executable, cmdline);
+ job = CreateJobObject(NULL, NULL);
+ ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
+ &info, sizeof(info), &rc);
+ if (!ok || (rc != sizeof(info)) || !job) {
+ winerror(GetLastError(), L"Job information querying failed");
+ return RC_CREATE_PROCESS;
+ }
+ info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
+ JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
+ ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
+ sizeof(info));
+ if (!ok) {
+ winerror(GetLastError(), L"Job information setting failed");
+ return RC_CREATE_PROCESS;
+ }
+ memset(&si, 0, sizeof(si));
+ GetStartupInfoW(&si);
+ ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput, L"stdin");
+ if (!ok) {
+ return RC_NO_STD_HANDLES;
+ }
+ ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput, L"stdout");
+ if (!ok) {
+ return RC_NO_STD_HANDLES;
+ }
+ ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError, L"stderr");
+ if (!ok) {
+ return RC_NO_STD_HANDLES;
+ }
+
+ ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
+ if (!ok) {
+ winerror(GetLastError(), L"control handler setting failed");
+ return RC_CREATE_PROCESS;
+ }
+
+ si.dwFlags = STARTF_USESTDHANDLES;
+ ok = CreateProcessW(executable, cmdline, NULL, NULL, TRUE,
+ 0, NULL, NULL, &si, &pi);
+ if (!ok) {
+ winerror(GetLastError(), L"Unable to create process using '%ls'", cmdline);
+ return RC_CREATE_PROCESS;
+ }
+ AssignProcessToJobObject(job, pi.hProcess);
+ CloseHandle(pi.hThread);
+ WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
+ ok = GetExitCodeProcess(pi.hProcess, &rc);
+ if (!ok) {
+ winerror(GetLastError(), L"Failed to get exit code of process");
+ return RC_CREATE_PROCESS;
+ }
+ debug(L"child process exit code: %d", rc);
+ return rc;
+}
+
+
+int
+process(int argc, wchar_t ** argv)
+{
+ int exitCode;
+ wchar_t pyvenvcfg_path[MAXLEN];
+ wchar_t home_path[MAXLEN];
+
+ if (isEnvVarSet(L"PYLAUNCHER_DEBUG")) {
+ setvbuf(stderr, (char *)NULL, _IONBF, 0);
+ log_fp = stderr;
+ }
+
+ exitCode = calculate_pyvenvcfg_path(pyvenvcfg_path, MAXLEN);
+ if (exitCode) return exitCode;
+
+ exitCode = read_home(pyvenvcfg_path, home_path, MAXLEN);
+ if (exitCode) return exitCode;
+
+ exitCode = locate_python(home_path, MAXLEN);
+ if (exitCode) return exitCode;
+
+ // We do not update argv[0] to point at the target runtime, and so we do not
+ // pass through our original argv[0] in an environment variable.
+ //exitCode = smuggle_path();
+ //if (exitCode) return exitCode;
+
+ exitCode = launch(home_path, GetCommandLineW());
+ return exitCode;
+}
+
+
+#if defined(_WINDOWS)
+
+int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPWSTR lpstrCmd, int nShow)
+{
+ return process(__argc, __wargv);
+}
+
+#else
+
+int cdecl wmain(int argc, wchar_t ** argv)
+{
+ return process(argc, argv);
+}
+
+#endif