mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
gh-112984 Update Windows build and installer for free-threaded builds (GH-113129)
This commit is contained in:
parent
78fcde039a
commit
f56d132deb
76 changed files with 1437 additions and 245 deletions
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
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 in_build(f, dest="", new_name=None):
|
|||
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 in_build(f, dest="", new_name=None):
|
|||
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")
|
||||
else:
|
||||
yield from in_build("python.exe", new_name="python")
|
||||
yield from in_build("pythonw.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,
|
||||
])
|
||||
|
||||
yield from in_build(PYTHON_DLL_NAME)
|
||||
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_DLL_NAME)
|
||||
|
||||
if ns.include_launchers and ns.include_appxmanifest:
|
||||
if ns.include_pip:
|
||||
|
|
@ -160,7 +188,10 @@ def in_build(f, dest="", new_name=None):
|
|||
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 in_build(f, dest="", new_name=None):
|
|||
|
||||
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 in_build(f, dest="", new_name=None):
|
|||
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 _c(d):
|
|||
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 @@ def main():
|
|||
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))
|
||||
|
|
|
|||
|
|
@ -39,3 +39,6 @@ def _get_suffix(field4):
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@
|
|||
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 @@
|
|||
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ def public(f):
|
|||
"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 @@ def public(f):
|
|||
"dev",
|
||||
"launchers",
|
||||
"appxmanifest",
|
||||
"alias",
|
||||
"alias3x",
|
||||
# XXX: Disabled for now "precompile",
|
||||
],
|
||||
},
|
||||
|
|
@ -59,9 +65,10 @@ def public(f):
|
|||
"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 @@ def public(f):
|
|||
"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",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
510
PC/venvlauncher.c
Normal file
510
PC/venvlauncher.c
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue