From e52393cea28dc864f333b350a263206e94f83344 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 14:39:51 +0200 Subject: [PATCH 01/11] [GR-52900] Enable cpyext async slots test on Windows --- .../freeze_modules.py | 4 + .../src/tests/cpyext/test_object.py | 1 - .../graal/python/builtins/Python3Core.java | 8 + .../modules/OverlappedModuleBuiltins.java | 57 +++ .../modules/WinregLegacyModuleBuiltins.java | 57 +++ .../objects/module/FrozenModules.java | 12 + graalpython/lib-graalpython/_overlapped.py | 246 ++++++++++ graalpython/lib-graalpython/_winapi.py | 396 ++++++++++++++++ graalpython/lib-graalpython/_winreg.py | 40 ++ graalpython/lib-graalpython/winreg.py | 446 ++++++++++++++++++ graalpython/lib-python/3/asyncio/__init__.py | 6 +- .../lib-python/3/asyncio/windows_events.py | 7 +- 12 files changed, 1274 insertions(+), 6 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java create mode 100644 graalpython/lib-graalpython/_overlapped.py create mode 100644 graalpython/lib-graalpython/_winapi.py create mode 100644 graalpython/lib-graalpython/_winreg.py create mode 100644 graalpython/lib-graalpython/winreg.py diff --git a/graalpython/com.oracle.graal.python.frozen/freeze_modules.py b/graalpython/com.oracle.graal.python.frozen/freeze_modules.py index 929029f5b8..311238b1b3 100644 --- a/graalpython/com.oracle.graal.python.frozen/freeze_modules.py +++ b/graalpython/com.oracle.graal.python.frozen/freeze_modules.py @@ -125,6 +125,10 @@ def add_graalpython_core(): "java", "pip_hook", "_nt", + "_winapi", + "_overlapped", + "winreg", + "_winreg", ]: modname = f"graalpy.{os.path.basename(name)}" modpath = os.path.join(lib_graalpython, f"{name}.py") diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py index 90b1542327..cafd01fee3 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py @@ -1551,7 +1551,6 @@ def test_take_ownership(self): obj.clear_value() assert value == (1, 2, 3, "hello", "world", dummy) - @skipIf(is_windows, reason="GR-52900") def test_async_slots(self): import asyncio, types, functools TestTpAsync = CPyExtType("TestTpAsync", diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index 1993cfd065..b7a8016327 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -94,6 +94,7 @@ import com.oracle.graal.python.builtins.modules.MsvcrtModuleBuiltins; import com.oracle.graal.python.builtins.modules.NtModuleBuiltins; import com.oracle.graal.python.builtins.modules.OperatorModuleBuiltins; +import com.oracle.graal.python.builtins.modules.OverlappedModuleBuiltins; import com.oracle.graal.python.builtins.modules.PolyglotModuleBuiltins; import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins; import com.oracle.graal.python.builtins.modules.PosixShMemModuleBuiltins; @@ -122,6 +123,7 @@ import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins; import com.oracle.graal.python.builtins.modules.WeakRefModuleBuiltins; import com.oracle.graal.python.builtins.modules.WinapiModuleBuiltins; +import com.oracle.graal.python.builtins.modules.WinregLegacyModuleBuiltins; import com.oracle.graal.python.builtins.modules.WinregModuleBuiltins; import com.oracle.graal.python.builtins.modules.ast.AstBuiltins; import com.oracle.graal.python.builtins.modules.ast.AstModuleBuiltins; @@ -444,6 +446,10 @@ private static TruffleString[] getCoreFiles() { if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) { coreFiles = new ArrayList<>(coreFiles); coreFiles.add(toTruffleStringUncached("_nt")); + coreFiles.add(toTruffleStringUncached("_winapi")); + coreFiles.add(toTruffleStringUncached("_overlapped")); + coreFiles.add(toTruffleStringUncached("winreg")); + coreFiles.add(toTruffleStringUncached("_winreg")); } return coreFiles.toArray(new TruffleString[0]); @@ -561,6 +567,8 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) { new WinregModuleBuiltins(), new MsvcrtModuleBuiltins(), new WinapiModuleBuiltins(), + new OverlappedModuleBuiltins(), + new WinregLegacyModuleBuiltins(), new CryptModuleBuiltins(), new ScandirIteratorBuiltins(), new DirEntryBuiltins(), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java new file mode 100644 index 0000000000..2a056005f9 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package com.oracle.graal.python.builtins.modules; + +import java.util.List; + +import com.oracle.graal.python.annotations.PythonOS; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.truffle.api.dsl.NodeFactory; + +@CoreFunctions(defineModule = "_overlapped", os = PythonOS.PLATFORM_WIN32) +public final class OverlappedModuleBuiltins extends PythonBuiltins { + @Override + protected List> getNodeFactories() { + return List.of(); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java new file mode 100644 index 0000000000..1b475f183e --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package com.oracle.graal.python.builtins.modules; + +import java.util.List; + +import com.oracle.graal.python.annotations.PythonOS; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.truffle.api.dsl.NodeFactory; + +@CoreFunctions(defineModule = "_winreg", os = PythonOS.PLATFORM_WIN32) +public final class WinregLegacyModuleBuiltins extends PythonBuiltins { + @Override + protected List> getNodeFactories() { + return List.of(); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java index c91a96b6a1..e6f01a0c4c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java @@ -104,6 +104,10 @@ private static final class Map { private static final PythonFrozenModule GRAALPY_JAVA = new PythonFrozenModule("GRAALPY_JAVA", null, false); private static final PythonFrozenModule GRAALPY_PIP_HOOK = new PythonFrozenModule("GRAALPY_PIP_HOOK", null, false); private static final PythonFrozenModule GRAALPY__NT = new PythonFrozenModule("GRAALPY__NT", null, false); + private static final PythonFrozenModule GRAALPY__WINAPI = new PythonFrozenModule("GRAALPY__WINAPI", null, false); + private static final PythonFrozenModule GRAALPY__OVERLAPPED = new PythonFrozenModule("GRAALPY__OVERLAPPED", null, false); + private static final PythonFrozenModule GRAALPY_WINREG = new PythonFrozenModule("GRAALPY_WINREG", null, false); + private static final PythonFrozenModule GRAALPY__WINREG = new PythonFrozenModule("GRAALPY__WINREG", null, false); } public static final PythonFrozenModule lookup(String name) { @@ -238,6 +242,14 @@ public static final PythonFrozenModule lookup(String name) { return Map.GRAALPY_PIP_HOOK; case "graalpy._nt": return Map.GRAALPY__NT; + case "graalpy._winapi": + return Map.GRAALPY__WINAPI; + case "graalpy._overlapped": + return Map.GRAALPY__OVERLAPPED; + case "graalpy.winreg": + return Map.GRAALPY_WINREG; + case "graalpy._winreg": + return Map.GRAALPY__WINREG; default: return null; } diff --git a/graalpython/lib-graalpython/_overlapped.py b/graalpython/lib-graalpython/_overlapped.py new file mode 100644 index 0000000000..d4188e1576 --- /dev/null +++ b/graalpython/lib-graalpython/_overlapped.py @@ -0,0 +1,246 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import sys + + +if sys.platform != "win32": + raise ImportError("win32 only") + + +from _winapi import INFINITE, INVALID_HANDLE_VALUE, NULL + + +ERROR_NETNAME_DELETED = 64 +ERROR_SEM_TIMEOUT = 121 +ERROR_OPERATION_ABORTED = 995 +ERROR_IO_PENDING = 997 +ERROR_PORT_UNREACHABLE = 1234 +ERROR_PIPE_BUSY = 231 + +SO_UPDATE_ACCEPT_CONTEXT = 0x700B +SO_UPDATE_CONNECT_CONTEXT = 0x7010 + +_ctypes = None +_wintypes = None +_kernel32 = None +_PostQueuedCompletionStatus = None +_UnregisterWait = None +_UnregisterWaitEx = None + + +def _native(): + global _ctypes, _wintypes, _kernel32 + global _PostQueuedCompletionStatus, _UnregisterWait, _UnregisterWaitEx + if _kernel32 is not None: + return _ctypes, _wintypes, _kernel32 + + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.windll.kernel32 + kernel32.CreateIoCompletionPort.argtypes = [wintypes.HANDLE, wintypes.HANDLE, ctypes.c_size_t, wintypes.DWORD] + kernel32.CreateIoCompletionPort.restype = wintypes.HANDLE + kernel32.GetQueuedCompletionStatus.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(ctypes.c_size_t), + ctypes.POINTER(ctypes.c_void_p), + wintypes.DWORD, + ] + kernel32.GetQueuedCompletionStatus.restype = wintypes.BOOL + kernel32.CreateEventW.argtypes = [wintypes.LPVOID, wintypes.BOOL, wintypes.BOOL, wintypes.LPCWSTR] + kernel32.CreateEventW.restype = wintypes.HANDLE + + try: + post_queued_completion_status = kernel32.PostQueuedCompletionStatus + except AttributeError: + post_queued_completion_status = None + else: + post_queued_completion_status.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.c_size_t, ctypes.c_void_p] + post_queued_completion_status.restype = wintypes.BOOL + + try: + unregister_wait = kernel32.UnregisterWait + unregister_wait_ex = kernel32.UnregisterWaitEx + except AttributeError: + unregister_wait = None + unregister_wait_ex = None + else: + unregister_wait.argtypes = [wintypes.HANDLE] + unregister_wait.restype = wintypes.BOOL + unregister_wait_ex.argtypes = [wintypes.HANDLE, wintypes.HANDLE] + unregister_wait_ex.restype = wintypes.BOOL + + _ctypes = ctypes + _wintypes = wintypes + _kernel32 = kernel32 + _PostQueuedCompletionStatus = post_queued_completion_status + _UnregisterWait = unregister_wait + _UnregisterWaitEx = unregister_wait_ex + return _ctypes, _wintypes, _kernel32 + + +def _as_handle(handle): + if handle is None: + return NULL + return int(handle) + + +def _winerror(code=None): + ctypes, _, kernel32 = _native() + if code is None: + code = kernel32.GetLastError() + if hasattr(ctypes, "WinError"): + return ctypes.WinError(code) + return OSError(code, f"Windows error {code}") + + +def CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, number_of_concurrent_threads): + ctypes, wintypes, kernel32 = _native() + handle = kernel32.CreateIoCompletionPort( + wintypes.HANDLE(_as_handle(file_handle)), + wintypes.HANDLE(_as_handle(existing_completion_port)), + ctypes.c_size_t(completion_key), + wintypes.DWORD(number_of_concurrent_threads), + ) + if not handle: + raise _winerror() + return _as_handle(handle) + + +def GetQueuedCompletionStatus(completion_port, milliseconds=INFINITE): + ctypes, wintypes, kernel32 = _native() + transferred = wintypes.DWORD() + key = ctypes.c_size_t() + overlapped = ctypes.c_void_p() + result = kernel32.GetQueuedCompletionStatus( + wintypes.HANDLE(_as_handle(completion_port)), + ctypes.byref(transferred), + ctypes.byref(key), + ctypes.byref(overlapped), + wintypes.DWORD(milliseconds), + ) + if not result and not overlapped.value: + code = kernel32.GetLastError() + if code == 258: + return None + raise _winerror(code) + return 0 if result else kernel32.GetLastError(), transferred.value, key.value, overlapped.value + + +def PostQueuedCompletionStatus(completion_port, transferred, completion_key, overlapped): + ctypes, wintypes, _ = _native() + if _PostQueuedCompletionStatus is None: + raise NotImplementedError("_overlapped.PostQueuedCompletionStatus is not available") + result = _PostQueuedCompletionStatus( + wintypes.HANDLE(_as_handle(completion_port)), + wintypes.DWORD(transferred), + ctypes.c_size_t(completion_key), + ctypes.c_void_p(overlapped), + ) + if not result: + raise _winerror() + + +def CreateEvent(event_attributes, manual_reset, initial_state, name): + _, wintypes, kernel32 = _native() + handle = kernel32.CreateEventW(event_attributes, bool(manual_reset), bool(initial_state), name) + if not handle: + raise _winerror() + return _as_handle(handle) + + +def UnregisterWait(wait_handle): + _, wintypes, _ = _native() + if _UnregisterWait is None: + raise NotImplementedError("_overlapped.UnregisterWait is not available") + if not _UnregisterWait(wintypes.HANDLE(_as_handle(wait_handle))): + raise _winerror() + + +def UnregisterWaitEx(wait_handle, completion_event): + _, wintypes, _ = _native() + if _UnregisterWaitEx is None: + raise NotImplementedError("_overlapped.UnregisterWaitEx is not available") + if not _UnregisterWaitEx(wintypes.HANDLE(_as_handle(wait_handle)), wintypes.HANDLE(_as_handle(completion_event))): + raise _winerror() + + +def _not_implemented(name): + def function(*args, **kwargs): + raise NotImplementedError(f"_overlapped.{name} is not implemented in GraalPy") + + function.__name__ = name + return function + + +RegisterWaitWithQueue = _not_implemented("RegisterWaitWithQueue") +WSAConnect = _not_implemented("WSAConnect") +BindLocal = _not_implemented("BindLocal") +ConnectPipe = _not_implemented("ConnectPipe") + + +class Overlapped: + def __init__(self, event): + self.event = event + self.pending = False + self.address = id(self) + + def cancel(self): + self.pending = False + + def getresult(self, wait=False): + return 0 + + GetOverlappedResult = getresult + + WSARecv = _not_implemented("Overlapped.WSARecv") + WSARecvInto = _not_implemented("Overlapped.WSARecvInto") + WSARecvFrom = _not_implemented("Overlapped.WSARecvFrom") + WSARecvFromInto = _not_implemented("Overlapped.WSARecvFromInto") + WSASend = _not_implemented("Overlapped.WSASend") + WSASendTo = _not_implemented("Overlapped.WSASendTo") + ReadFile = _not_implemented("Overlapped.ReadFile") + ReadFileInto = _not_implemented("Overlapped.ReadFileInto") + WriteFile = _not_implemented("Overlapped.WriteFile") + AcceptEx = _not_implemented("Overlapped.AcceptEx") + ConnectEx = _not_implemented("Overlapped.ConnectEx") + TransmitFile = _not_implemented("Overlapped.TransmitFile") + ConnectNamedPipe = _not_implemented("Overlapped.ConnectNamedPipe") diff --git a/graalpython/lib-graalpython/_winapi.py b/graalpython/lib-graalpython/_winapi.py new file mode 100644 index 0000000000..fd6a63f846 --- /dev/null +++ b/graalpython/lib-graalpython/_winapi.py @@ -0,0 +1,396 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import sys + + +if sys.platform != "win32": + raise ImportError("win32 only") + + +_POINTER_BITS = 64 if sys.maxsize > 2**32 else 32 + + +def _unsigned_pointer(value): + return value % (1 << _POINTER_BITS) + + +NULL = 0 +INVALID_HANDLE_VALUE = _unsigned_pointer(-1) +INFINITE = 0xFFFFFFFF +WAIT_OBJECT_0 = 0 +WAIT_ABANDONED_0 = 0x80 +WAIT_TIMEOUT = 0x102 +WAIT_FAILED = 0xFFFFFFFF +STILL_ACTIVE = 259 + +STD_INPUT_HANDLE = -10 +STD_OUTPUT_HANDLE = -11 +STD_ERROR_HANDLE = -12 + +SW_HIDE = 0 +STARTF_USESHOWWINDOW = 0x00000001 +STARTF_USESTDHANDLES = 0x00000100 + +CREATE_NEW_CONSOLE = 0x00000010 +CREATE_NEW_PROCESS_GROUP = 0x00000200 +CREATE_NO_WINDOW = 0x08000000 +DETACHED_PROCESS = 0x00000008 +CREATE_DEFAULT_ERROR_MODE = 0x04000000 +CREATE_BREAKAWAY_FROM_JOB = 0x01000000 + +ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000 +BELOW_NORMAL_PRIORITY_CLASS = 0x00004000 +HIGH_PRIORITY_CLASS = 0x00000080 +IDLE_PRIORITY_CLASS = 0x00000040 +NORMAL_PRIORITY_CLASS = 0x00000020 +REALTIME_PRIORITY_CLASS = 0x00000100 + +DUPLICATE_SAME_ACCESS = 0x00000002 + +FILE_TYPE_UNKNOWN = 0x0000 +FILE_TYPE_DISK = 0x0001 +FILE_TYPE_CHAR = 0x0002 +FILE_TYPE_PIPE = 0x0003 + +GENERIC_READ = 0x80000000 +GENERIC_WRITE = 0x40000000 +OPEN_EXISTING = 3 +FILE_ATTRIBUTE_NORMAL = 0x00000080 +FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 +FILE_FLAG_OVERLAPPED = 0x40000000 + +PIPE_ACCESS_INBOUND = 0x00000001 +PIPE_ACCESS_OUTBOUND = 0x00000002 +PIPE_ACCESS_DUPLEX = 0x00000003 +PIPE_TYPE_MESSAGE = 0x00000004 +PIPE_READMODE_MESSAGE = 0x00000002 +PIPE_WAIT = 0x00000000 +PIPE_UNLIMITED_INSTANCES = 255 +NMPWAIT_WAIT_FOREVER = 0xFFFFFFFF + +ERROR_FILE_NOT_FOUND = 2 +ERROR_PATH_NOT_FOUND = 3 +ERROR_ACCESS_DENIED = 5 +ERROR_INVALID_HANDLE = 6 +ERROR_BROKEN_PIPE = 109 +ERROR_ALREADY_EXISTS = 183 +ERROR_PIPE_BUSY = 231 +ERROR_NO_DATA = 232 +ERROR_MORE_DATA = 234 +ERROR_NO_MORE_ITEMS = 259 +ERROR_OPERATION_ABORTED = 995 +ERROR_IO_PENDING = 997 +ERROR_PRIVILEGE_NOT_HELD = 1314 + +LOCALE_NAME_INVARIANT = "" +LCMAP_LOWERCASE = 0x00000100 + +_ctypes = None +_wintypes = None +_kernel32 = None +_NeedCurrentDirectoryForExePathW = None + + +def _native(): + global _ctypes, _wintypes, _kernel32, _NeedCurrentDirectoryForExePathW + if _kernel32 is not None: + return _ctypes, _wintypes, _kernel32 + + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.windll.kernel32 + + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + kernel32.CloseHandle.restype = wintypes.BOOL + kernel32.GetCurrentProcess.argtypes = [] + kernel32.GetCurrentProcess.restype = wintypes.HANDLE + kernel32.GetStdHandle.argtypes = [wintypes.DWORD] + kernel32.GetStdHandle.restype = wintypes.HANDLE + kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] + kernel32.WaitForSingleObject.restype = wintypes.DWORD + kernel32.GetExitCodeProcess.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)] + kernel32.GetExitCodeProcess.restype = wintypes.BOOL + kernel32.TerminateProcess.argtypes = [wintypes.HANDLE, ctypes.c_uint] + kernel32.TerminateProcess.restype = wintypes.BOOL + kernel32.DuplicateHandle.argtypes = [ + wintypes.HANDLE, + wintypes.HANDLE, + wintypes.HANDLE, + ctypes.POINTER(wintypes.HANDLE), + wintypes.DWORD, + wintypes.BOOL, + wintypes.DWORD, + ] + kernel32.DuplicateHandle.restype = wintypes.BOOL + kernel32.GetFileType.argtypes = [wintypes.HANDLE] + kernel32.GetFileType.restype = wintypes.DWORD + kernel32.GetLongPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] + kernel32.GetLongPathNameW.restype = wintypes.DWORD + kernel32.GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] + kernel32.GetShortPathNameW.restype = wintypes.DWORD + kernel32.GetModuleFileNameW.argtypes = [wintypes.HMODULE, wintypes.LPWSTR, wintypes.DWORD] + kernel32.GetModuleFileNameW.restype = wintypes.DWORD + kernel32.LCMapStringEx.argtypes = [ + wintypes.LPCWSTR, + wintypes.DWORD, + wintypes.LPCWSTR, + ctypes.c_int, + wintypes.LPWSTR, + ctypes.c_int, + wintypes.LPVOID, + wintypes.LPVOID, + wintypes.LPARAM, + ] + kernel32.LCMapStringEx.restype = ctypes.c_int + + try: + need_current_directory = kernel32.NeedCurrentDirectoryForExePathW + except AttributeError: + need_current_directory = None + else: + need_current_directory.argtypes = [wintypes.LPCWSTR] + need_current_directory.restype = wintypes.BOOL + + _ctypes = ctypes + _wintypes = wintypes + _kernel32 = kernel32 + _NeedCurrentDirectoryForExePathW = need_current_directory + return _ctypes, _wintypes, _kernel32 + + +def _winerror(code=None): + ctypes, _, kernel32 = _native() + if code is None: + code = kernel32.GetLastError() + if hasattr(ctypes, "WinError"): + return ctypes.WinError(code) + return OSError(code, f"Windows error {code}") + + +def _raise_last_error(): + raise _winerror() + + +def _raise_if_zero(result): + if result == 0: + _raise_last_error() + return result + + +def _as_handle(handle): + if handle is None: + return NULL + return int(handle) + + +def _fsdecode(path): + if isinstance(path, bytes): + return path.decode(sys.getfilesystemencoding(), "surrogateescape") + return str(path) + + +def CloseHandle(handle): + _, wintypes, kernel32 = _native() + _raise_if_zero(kernel32.CloseHandle(wintypes.HANDLE(_as_handle(handle)))) + + +def GetCurrentProcess(): + _, _, kernel32 = _native() + return _as_handle(kernel32.GetCurrentProcess()) + + +def GetStdHandle(std_handle): + _, wintypes, kernel32 = _native() + handle = _as_handle(kernel32.GetStdHandle(wintypes.DWORD(std_handle))) + if handle == INVALID_HANDLE_VALUE: + _raise_last_error() + return handle + + +def WaitForSingleObject(handle, milliseconds): + _, wintypes, kernel32 = _native() + result = kernel32.WaitForSingleObject(wintypes.HANDLE(_as_handle(handle)), wintypes.DWORD(milliseconds)) + if result == WAIT_FAILED: + _raise_last_error() + return result + + +def GetExitCodeProcess(handle): + ctypes, wintypes, kernel32 = _native() + exit_code = wintypes.DWORD() + _raise_if_zero(kernel32.GetExitCodeProcess(wintypes.HANDLE(_as_handle(handle)), ctypes.byref(exit_code))) + return exit_code.value + + +def TerminateProcess(handle, exit_code): + _, wintypes, kernel32 = _native() + _raise_if_zero(kernel32.TerminateProcess(wintypes.HANDLE(_as_handle(handle)), wintypes.DWORD(exit_code))) + + +def DuplicateHandle( + source_process_handle, + source_handle, + target_process_handle, + desired_access, + inherit_handle, + options, +): + ctypes, wintypes, kernel32 = _native() + target_handle = wintypes.HANDLE() + _raise_if_zero( + kernel32.DuplicateHandle( + wintypes.HANDLE(_as_handle(source_process_handle)), + wintypes.HANDLE(_as_handle(source_handle)), + wintypes.HANDLE(_as_handle(target_process_handle)), + ctypes.byref(target_handle), + wintypes.DWORD(desired_access), + wintypes.BOOL(inherit_handle), + wintypes.DWORD(options), + ) + ) + return _as_handle(target_handle.value) + + +def GetFileType(handle): + _, wintypes, kernel32 = _native() + kernel32.SetLastError(0) + result = kernel32.GetFileType(wintypes.HANDLE(_as_handle(handle))) + if result == FILE_TYPE_UNKNOWN and kernel32.GetLastError() != 0: + _raise_last_error() + return result + + +def _get_path_name(function, path): + ctypes, _, _ = _native() + path = _fsdecode(path) + size = 260 + while True: + buffer = ctypes.create_unicode_buffer(size) + result = function(path, buffer, size) + if result == 0: + _raise_last_error() + if result < size: + return buffer.value + size = result + 1 + + +def GetLongPathName(path): + _, _, kernel32 = _native() + return _get_path_name(kernel32.GetLongPathNameW, path) + + +def GetShortPathName(path): + _, _, kernel32 = _native() + return _get_path_name(kernel32.GetShortPathNameW, path) + + +def GetModuleFileName(module_handle): + ctypes, wintypes, kernel32 = _native() + size = 260 + while True: + buffer = ctypes.create_unicode_buffer(size) + result = kernel32.GetModuleFileNameW(wintypes.HMODULE(_as_handle(module_handle)), buffer, size) + if result == 0: + _raise_last_error() + if result < size - 1: + return buffer.value + size *= 2 + + +def LCMapStringEx(locale_name, flags, src): + ctypes, _, kernel32 = _native() + if not isinstance(src, str): + raise TypeError("src must be str") + if locale_name is None: + locale_name = LOCALE_NAME_INVARIANT + buffer = ctypes.create_unicode_buffer(len(src) + 1) + result = kernel32.LCMapStringEx(locale_name, flags, src, len(src), buffer, len(buffer), None, None, 0) + if result == 0: + _raise_last_error() + return buffer.value[:result] + + +def NeedCurrentDirectoryForExePath(exe_name): + _native() + if _NeedCurrentDirectoryForExePathW is None: + return not any(sep in _fsdecode(exe_name) for sep in ("\\", "/")) + return bool(_NeedCurrentDirectoryForExePathW(_fsdecode(exe_name))) + + +def _mimetypes_read_windows_registry(add_type): + import winreg + + try: + mimedb = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, "") + except OSError: + return + with mimedb: + index = 0 + while True: + try: + subkeyname = winreg.EnumKey(mimedb, index) + except OSError: + break + index += 1 + if "\0" in subkeyname or not subkeyname.startswith("."): + continue + try: + with winreg.OpenKey(mimedb, subkeyname) as subkey: + mimetype, datatype = winreg.QueryValueEx(subkey, "Content Type") + except OSError: + continue + if datatype == winreg.REG_SZ: + add_type(mimetype, subkeyname) + + +def _not_implemented(name): + def function(*args, **kwargs): + raise NotImplementedError(f"_winapi.{name} is not implemented in GraalPy") + + function.__name__ = name + return function + + +CreatePipe = _not_implemented("CreatePipe") +CreateProcess = _not_implemented("CreateProcess") +CreateFile = _not_implemented("CreateFile") +CreateNamedPipe = _not_implemented("CreateNamedPipe") +ConnectNamedPipe = _not_implemented("ConnectNamedPipe") diff --git a/graalpython/lib-graalpython/_winreg.py b/graalpython/lib-graalpython/_winreg.py new file mode 100644 index 0000000000..d22981d912 --- /dev/null +++ b/graalpython/lib-graalpython/_winreg.py @@ -0,0 +1,40 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from winreg import * diff --git a/graalpython/lib-graalpython/winreg.py b/graalpython/lib-graalpython/winreg.py new file mode 100644 index 0000000000..863bac0623 --- /dev/null +++ b/graalpython/lib-graalpython/winreg.py @@ -0,0 +1,446 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import sys + + +if sys.platform != "win32": + raise ImportError("win32 only") + + +error = OSError + +ERROR_SUCCESS = 0 +ERROR_MORE_DATA = 234 + +KEY_QUERY_VALUE = 0x0001 +KEY_SET_VALUE = 0x0002 +KEY_CREATE_SUB_KEY = 0x0004 +KEY_ENUMERATE_SUB_KEYS = 0x0008 +KEY_NOTIFY = 0x0010 +KEY_CREATE_LINK = 0x0020 +KEY_WOW64_64KEY = 0x0100 +KEY_WOW64_32KEY = 0x0200 +KEY_WOW64_RES = 0x0300 +READ_CONTROL = 0x00020000 +STANDARD_RIGHTS_READ = READ_CONTROL +STANDARD_RIGHTS_WRITE = READ_CONTROL +STANDARD_RIGHTS_ALL = 0x001F0000 +KEY_READ = STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY +KEY_WRITE = STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY +KEY_EXECUTE = KEY_READ +KEY_ALL_ACCESS = ( + STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | + KEY_CREATE_LINK +) + +REG_NONE = 0 +REG_SZ = 1 +REG_EXPAND_SZ = 2 +REG_BINARY = 3 +REG_DWORD = 4 +REG_DWORD_LITTLE_ENDIAN = 4 +REG_DWORD_BIG_ENDIAN = 5 +REG_LINK = 6 +REG_MULTI_SZ = 7 +REG_RESOURCE_LIST = 8 +REG_FULL_RESOURCE_DESCRIPTOR = 9 +REG_RESOURCE_REQUIREMENTS_LIST = 10 +REG_QWORD = 11 +REG_QWORD_LITTLE_ENDIAN = 11 + +_POINTER_BITS = 64 if sys.maxsize > 2**32 else 32 + + +def _predefined_hkey(value): + if value & 0x80000000: + value -= 1 << 32 + return value % (1 << _POINTER_BITS) + + +HKEY_CLASSES_ROOT = _predefined_hkey(0x80000000) +HKEY_CURRENT_USER = _predefined_hkey(0x80000001) +HKEY_LOCAL_MACHINE = _predefined_hkey(0x80000002) +HKEY_USERS = _predefined_hkey(0x80000003) +HKEY_PERFORMANCE_DATA = _predefined_hkey(0x80000004) +HKEY_CURRENT_CONFIG = _predefined_hkey(0x80000005) +HKEY_DYN_DATA = _predefined_hkey(0x80000006) + +_ctypes = None +_wintypes = None +_advapi32 = None + + +def _native(): + global _ctypes, _wintypes, _advapi32 + if _advapi32 is not None: + return _ctypes, _wintypes, _advapi32 + + import ctypes + from ctypes import wintypes + + advapi32 = ctypes.windll.advapi32 + advapi32.RegCloseKey.argtypes = [wintypes.HANDLE] + advapi32.RegCloseKey.restype = wintypes.LONG + advapi32.RegOpenKeyExW.argtypes = [ + wintypes.HANDLE, + wintypes.LPCWSTR, + wintypes.DWORD, + wintypes.DWORD, + ctypes.POINTER(wintypes.HANDLE), + ] + advapi32.RegOpenKeyExW.restype = wintypes.LONG + advapi32.RegConnectRegistryW.argtypes = [wintypes.LPCWSTR, wintypes.HANDLE, ctypes.POINTER(wintypes.HANDLE)] + advapi32.RegConnectRegistryW.restype = wintypes.LONG + advapi32.RegEnumKeyExW.argtypes = [ + wintypes.HANDLE, + wintypes.DWORD, + wintypes.LPWSTR, + ctypes.POINTER(wintypes.DWORD), + wintypes.LPDWORD, + wintypes.LPWSTR, + wintypes.LPDWORD, + ctypes.c_void_p, + ] + advapi32.RegEnumKeyExW.restype = wintypes.LONG + advapi32.RegQueryValueExW.argtypes = [ + wintypes.HANDLE, + wintypes.LPCWSTR, + wintypes.LPDWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.c_void_p, + ctypes.POINTER(wintypes.DWORD), + ] + advapi32.RegQueryValueExW.restype = wintypes.LONG + advapi32.RegEnumValueW.argtypes = [ + wintypes.HANDLE, + wintypes.DWORD, + wintypes.LPWSTR, + ctypes.POINTER(wintypes.DWORD), + wintypes.LPDWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.c_void_p, + ctypes.POINTER(wintypes.DWORD), + ] + advapi32.RegEnumValueW.restype = wintypes.LONG + advapi32.RegQueryInfoKeyW.restype = wintypes.LONG + advapi32.RegFlushKey.argtypes = [wintypes.HANDLE] + advapi32.RegFlushKey.restype = wintypes.LONG + + _ctypes = ctypes + _wintypes = wintypes + _advapi32 = advapi32 + return _ctypes, _wintypes, _advapi32 + + +class HKEYType: + def __init__(self, handle): + self.handle = int(handle) + self.closed = False + + def Close(self): + CloseKey(self) + + def Detach(self): + self._check_closed() + handle = self.handle + self.closed = True + self.handle = 0 + return handle + + def _check_closed(self): + if self.closed: + raise ValueError("The object has been closed") + + def __enter__(self): + self._check_closed() + return self + + def __exit__(self, exc_type, exc, tb): + self.Close() + + def __int__(self): + self._check_closed() + return self.handle + + __index__ = __int__ + + def __repr__(self): + if self.closed: + return "" + return f"" + + def __bool__(self): + return not self.closed and self.handle != 0 + + def __del__(self): + if not self.closed and self.handle: + try: + CloseKey(self) + except OSError: + pass + + +def _winerror(code): + ctypes, _, _ = _native() + if hasattr(ctypes, "WinError"): + return ctypes.WinError(code) + return OSError(code, f"Windows error {code}") + + +def _raise_if_error(code): + if code != ERROR_SUCCESS: + raise _winerror(code) + + +def _handle(key): + if isinstance(key, HKEYType): + key._check_closed() + return key.handle + return int(key) + + +def _hkey_pointer(key): + _, wintypes, _ = _native() + return wintypes.HANDLE(_handle(key)) + + +def CloseKey(key): + _, wintypes, advapi32 = _native() + if isinstance(key, HKEYType): + if key.closed: + return + handle = key.handle + key.closed = True + key.handle = 0 + else: + handle = _handle(key) + _raise_if_error(advapi32.RegCloseKey(wintypes.HANDLE(handle))) + + +def OpenKey(key, sub_key, reserved=0, access=KEY_READ): + ctypes, wintypes, advapi32 = _native() + result = wintypes.HANDLE() + sub_key = "" if sub_key is None else str(sub_key) + code = advapi32.RegOpenKeyExW( + _hkey_pointer(key), + sub_key, + wintypes.DWORD(reserved), + wintypes.DWORD(access), + ctypes.byref(result), + ) + _raise_if_error(code) + return HKEYType(result.value) + + +OpenKeyEx = OpenKey + + +def ConnectRegistry(computer_name, key): + ctypes, wintypes, advapi32 = _native() + result = wintypes.HANDLE() + code = advapi32.RegConnectRegistryW(computer_name, _hkey_pointer(key), ctypes.byref(result)) + _raise_if_error(code) + return HKEYType(result.value) + + +def EnumKey(key, index): + ctypes, wintypes, advapi32 = _native() + size = 256 + while True: + name = ctypes.create_unicode_buffer(size + 1) + name_size = wintypes.DWORD(size + 1) + code = advapi32.RegEnumKeyExW( + _hkey_pointer(key), + wintypes.DWORD(index), + name, + ctypes.byref(name_size), + None, + None, + None, + None, + ) + if code == ERROR_MORE_DATA: + size *= 2 + continue + _raise_if_error(code) + return name.value + + +def _convert_value(regtype, data): + if regtype in (REG_SZ, REG_EXPAND_SZ, REG_LINK): + return data.decode("utf-16le", "surrogatepass").rstrip("\0") + if regtype == REG_MULTI_SZ: + value = data.decode("utf-16le", "surrogatepass").rstrip("\0") + if not value: + return [] + return value.split("\0") + if regtype == REG_DWORD: + return int.from_bytes(data[:4].ljust(4, b"\0"), "little") + if regtype == REG_DWORD_BIG_ENDIAN: + return int.from_bytes(data[:4].ljust(4, b"\0"), "big") + if regtype == REG_QWORD: + return int.from_bytes(data[:8].ljust(8, b"\0"), "little") + return data + + +def _query_value(key, value_name): + ctypes, wintypes, advapi32 = _native() + value_name = None if value_name is None else str(value_name) + regtype = wintypes.DWORD() + size = wintypes.DWORD() + code = advapi32.RegQueryValueExW( + _hkey_pointer(key), + value_name, + None, + ctypes.byref(regtype), + None, + ctypes.byref(size), + ) + _raise_if_error(code) + if size.value: + buffer = (ctypes.c_ubyte * size.value)() + else: + buffer = (ctypes.c_ubyte * 1)() + code = advapi32.RegQueryValueExW( + _hkey_pointer(key), + value_name, + None, + ctypes.byref(regtype), + buffer, + ctypes.byref(size), + ) + _raise_if_error(code) + return _convert_value(regtype.value, bytes(buffer[:size.value])), regtype.value + + +def QueryValueEx(key, value_name): + return _query_value(key, value_name) + + +def QueryValue(key, sub_key): + if sub_key is None or sub_key == "": + value, _ = _query_value(key, None) + return value + with OpenKey(key, sub_key) as subkey: + value, _ = _query_value(subkey, None) + return value + + +def EnumValue(key, index): + ctypes, wintypes, advapi32 = _native() + name_size = 256 + data_size = 256 + while True: + name = ctypes.create_unicode_buffer(name_size + 1) + name_len = wintypes.DWORD(name_size + 1) + regtype = wintypes.DWORD() + data = (ctypes.c_ubyte * data_size)() + data_len = wintypes.DWORD(data_size) + code = advapi32.RegEnumValueW( + _hkey_pointer(key), + wintypes.DWORD(index), + name, + ctypes.byref(name_len), + None, + ctypes.byref(regtype), + data, + ctypes.byref(data_len), + ) + if code == ERROR_MORE_DATA: + name_size *= 2 + data_size = max(data_size * 2, data_len.value + 1) + continue + _raise_if_error(code) + return name.value, _convert_value(regtype.value, bytes(data[:data_len.value])), regtype.value + + +def QueryInfoKey(key): + ctypes, wintypes, advapi32 = _native() + + class FILETIME(ctypes.Structure): + _fields_ = [("dwLowDateTime", wintypes.DWORD), ("dwHighDateTime", wintypes.DWORD)] + + advapi32.RegQueryInfoKeyW.argtypes = [ + wintypes.HANDLE, + wintypes.LPWSTR, + wintypes.LPDWORD, + wintypes.LPDWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(FILETIME), + ] + subkeys = wintypes.DWORD() + values = wintypes.DWORD() + last_write = FILETIME() + code = advapi32.RegQueryInfoKeyW( + _hkey_pointer(key), + None, + None, + None, + ctypes.byref(subkeys), + None, + None, + ctypes.byref(values), + None, + None, + None, + ctypes.byref(last_write), + ) + _raise_if_error(code) + timestamp = (last_write.dwHighDateTime << 32) | last_write.dwLowDateTime + return subkeys.value, values.value, timestamp + + +def FlushKey(key): + _, _, advapi32 = _native() + _raise_if_error(advapi32.RegFlushKey(_hkey_pointer(key))) + + +_module = sys.modules.get(__name__) +if _module is not None: + sys.modules.setdefault("_winreg", _module) + + +__all__ = [name for name in globals() if not name.startswith("_")] diff --git a/graalpython/lib-python/3/asyncio/__init__.py b/graalpython/lib-python/3/asyncio/__init__.py index dc1f6c93b8..03165a425e 100644 --- a/graalpython/lib-python/3/asyncio/__init__.py +++ b/graalpython/lib-python/3/asyncio/__init__.py @@ -40,10 +40,8 @@ transports.__all__) if sys.platform == 'win32': # pragma: no cover - # Truffle change: windows_events relies on features we haven't implemented yet - pass - # from .windows_events import * - # __all__ += windows_events.__all__ + from .windows_events import * + __all__ += windows_events.__all__ else: from .unix_events import * # pragma: no cover __all__ += unix_events.__all__ diff --git a/graalpython/lib-python/3/asyncio/windows_events.py b/graalpython/lib-python/3/asyncio/windows_events.py index cb613451a5..c652a9d716 100644 --- a/graalpython/lib-python/3/asyncio/windows_events.py +++ b/graalpython/lib-python/3/asyncio/windows_events.py @@ -898,4 +898,9 @@ class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy): _loop_factory = ProactorEventLoop -DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy +if sys.implementation.name == "graalpy": + # GraalPy does not implement the full _overlapped IOCP surface yet. Use + # the selector loop as the default so basic asyncio loop creation works. + DefaultEventLoopPolicy = WindowsSelectorEventLoopPolicy +else: + DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy From e7a39c02ee6f3b4a719a026d55acbb26e02dfa57 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 16:38:52 +0200 Subject: [PATCH 02/11] [GR-52900] Add ctypes-backed Windows API helpers --- graalpython/lib-graalpython/_winapi.py | 646 +++++++++++++++++- .../lib-python/3/encodings/__init__.py | 1 - 2 files changed, 636 insertions(+), 11 deletions(-) diff --git a/graalpython/lib-graalpython/_winapi.py b/graalpython/lib-graalpython/_winapi.py index fd6a63f846..25ed1095f8 100644 --- a/graalpython/lib-graalpython/_winapi.py +++ b/graalpython/lib-graalpython/_winapi.py @@ -74,6 +74,10 @@ def _unsigned_pointer(value): DETACHED_PROCESS = 0x00000008 CREATE_DEFAULT_ERROR_MODE = 0x04000000 CREATE_BREAKAWAY_FROM_JOB = 0x01000000 +CREATE_UNICODE_ENVIRONMENT = 0x00000400 + +SYNCHRONIZE = 0x00100000 +PROCESS_DUP_HANDLE = 0x00000040 ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000 BELOW_NORMAL_PRIORITY_CLASS = 0x00004000 @@ -82,6 +86,7 @@ def _unsigned_pointer(value): NORMAL_PRIORITY_CLASS = 0x00000020 REALTIME_PRIORITY_CLASS = 0x00000100 +DUPLICATE_CLOSE_SOURCE = 0x00000001 DUPLICATE_SAME_ACCESS = 0x00000002 FILE_TYPE_UNKNOWN = 0x0000 @@ -91,6 +96,9 @@ def _unsigned_pointer(value): GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 +FILE_SHARE_READ = 0x00000001 +FILE_SHARE_WRITE = 0x00000002 +FILE_SHARE_DELETE = 0x00000004 OPEN_EXISTING = 3 FILE_ATTRIBUTE_NORMAL = 0x00000080 FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 @@ -110,12 +118,15 @@ def _unsigned_pointer(value): ERROR_ACCESS_DENIED = 5 ERROR_INVALID_HANDLE = 6 ERROR_BROKEN_PIPE = 109 +ERROR_SEM_TIMEOUT = 121 ERROR_ALREADY_EXISTS = 183 ERROR_PIPE_BUSY = 231 ERROR_NO_DATA = 232 ERROR_MORE_DATA = 234 ERROR_NO_MORE_ITEMS = 259 +ERROR_PIPE_CONNECTED = 535 ERROR_OPERATION_ABORTED = 995 +ERROR_IO_INCOMPLETE = 996 ERROR_IO_PENDING = 997 ERROR_PRIVILEGE_NOT_HELD = 1314 @@ -126,10 +137,15 @@ def _unsigned_pointer(value): _wintypes = None _kernel32 = None _NeedCurrentDirectoryForExePathW = None +_CancelIoEx = None +_SECURITY_ATTRIBUTES = None +_STARTUPINFOW = None +_PROCESS_INFORMATION = None +_OVERLAPPED = None def _native(): - global _ctypes, _wintypes, _kernel32, _NeedCurrentDirectoryForExePathW + global _ctypes, _wintypes, _kernel32, _NeedCurrentDirectoryForExePathW, _CancelIoEx if _kernel32 is not None: return _ctypes, _wintypes, _kernel32 @@ -138,14 +154,29 @@ def _native(): kernel32 = ctypes.windll.kernel32 + kernel32.GetLastError.argtypes = [] + kernel32.GetLastError.restype = wintypes.DWORD + kernel32.SetLastError.argtypes = [wintypes.DWORD] + kernel32.SetLastError.restype = None kernel32.CloseHandle.argtypes = [wintypes.HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL kernel32.GetCurrentProcess.argtypes = [] kernel32.GetCurrentProcess.restype = wintypes.HANDLE + kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] + kernel32.OpenProcess.restype = wintypes.HANDLE + kernel32.ExitProcess.argtypes = [ctypes.c_uint] + kernel32.ExitProcess.restype = None kernel32.GetStdHandle.argtypes = [wintypes.DWORD] kernel32.GetStdHandle.restype = wintypes.HANDLE kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] kernel32.WaitForSingleObject.restype = wintypes.DWORD + kernel32.WaitForMultipleObjects.argtypes = [ + wintypes.DWORD, + ctypes.POINTER(wintypes.HANDLE), + wintypes.BOOL, + wintypes.DWORD, + ] + kernel32.WaitForMultipleObjects.restype = wintypes.DWORD kernel32.GetExitCodeProcess.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)] kernel32.GetExitCodeProcess.restype = wintypes.BOOL kernel32.TerminateProcess.argtypes = [wintypes.HANDLE, ctypes.c_uint] @@ -162,6 +193,94 @@ def _native(): kernel32.DuplicateHandle.restype = wintypes.BOOL kernel32.GetFileType.argtypes = [wintypes.HANDLE] kernel32.GetFileType.restype = wintypes.DWORD + kernel32.GetACP.argtypes = [] + kernel32.GetACP.restype = wintypes.UINT + kernel32.CreatePipe.argtypes = [ + ctypes.POINTER(wintypes.HANDLE), + ctypes.POINTER(wintypes.HANDLE), + wintypes.LPVOID, + wintypes.DWORD, + ] + kernel32.CreatePipe.restype = wintypes.BOOL + kernel32.CreateProcessW.argtypes = [ + wintypes.LPCWSTR, + wintypes.LPWSTR, + wintypes.LPVOID, + wintypes.LPVOID, + wintypes.BOOL, + wintypes.DWORD, + wintypes.LPVOID, + wintypes.LPCWSTR, + wintypes.LPVOID, + wintypes.LPVOID, + ] + kernel32.CreateProcessW.restype = wintypes.BOOL + kernel32.CreateFileW.argtypes = [ + wintypes.LPCWSTR, + wintypes.DWORD, + wintypes.DWORD, + wintypes.LPVOID, + wintypes.DWORD, + wintypes.DWORD, + wintypes.HANDLE, + ] + kernel32.CreateFileW.restype = wintypes.HANDLE + kernel32.CreateNamedPipeW.argtypes = [ + wintypes.LPCWSTR, + wintypes.DWORD, + wintypes.DWORD, + wintypes.DWORD, + wintypes.DWORD, + wintypes.DWORD, + wintypes.DWORD, + wintypes.LPVOID, + ] + kernel32.CreateNamedPipeW.restype = wintypes.HANDLE + kernel32.ConnectNamedPipe.argtypes = [wintypes.HANDLE, wintypes.LPVOID] + kernel32.ConnectNamedPipe.restype = wintypes.BOOL + kernel32.WaitNamedPipeW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD] + kernel32.WaitNamedPipeW.restype = wintypes.BOOL + kernel32.ReadFile.argtypes = [ + wintypes.HANDLE, + wintypes.LPVOID, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + wintypes.LPVOID, + ] + kernel32.ReadFile.restype = wintypes.BOOL + kernel32.WriteFile.argtypes = [ + wintypes.HANDLE, + wintypes.LPCVOID, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + wintypes.LPVOID, + ] + kernel32.WriteFile.restype = wintypes.BOOL + kernel32.PeekNamedPipe.argtypes = [ + wintypes.HANDLE, + wintypes.LPVOID, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ] + kernel32.PeekNamedPipe.restype = wintypes.BOOL + kernel32.SetNamedPipeHandleState.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(wintypes.DWORD), + ] + kernel32.SetNamedPipeHandleState.restype = wintypes.BOOL + kernel32.GetOverlappedResult.argtypes = [ + wintypes.HANDLE, + wintypes.LPVOID, + ctypes.POINTER(wintypes.DWORD), + wintypes.BOOL, + ] + kernel32.GetOverlappedResult.restype = wintypes.BOOL + kernel32.CreateEventW.argtypes = [wintypes.LPVOID, wintypes.BOOL, wintypes.BOOL, wintypes.LPCWSTR] + kernel32.CreateEventW.restype = wintypes.HANDLE kernel32.GetLongPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] kernel32.GetLongPathNameW.restype = wintypes.DWORD kernel32.GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] @@ -189,19 +308,98 @@ def _native(): need_current_directory.argtypes = [wintypes.LPCWSTR] need_current_directory.restype = wintypes.BOOL + try: + cancel_io_ex = kernel32.CancelIoEx + except AttributeError: + cancel_io_ex = None + else: + cancel_io_ex.argtypes = [wintypes.HANDLE, wintypes.LPVOID] + cancel_io_ex.restype = wintypes.BOOL + _ctypes = ctypes _wintypes = wintypes _kernel32 = kernel32 _NeedCurrentDirectoryForExePathW = need_current_directory + _CancelIoEx = cancel_io_ex return _ctypes, _wintypes, _kernel32 +def _structures(): + global _SECURITY_ATTRIBUTES, _STARTUPINFOW, _PROCESS_INFORMATION, _OVERLAPPED + if _SECURITY_ATTRIBUTES is not None: + return _SECURITY_ATTRIBUTES, _STARTUPINFOW, _PROCESS_INFORMATION, _OVERLAPPED + + ctypes, wintypes, _ = _native() + ulong_ptr = ctypes.c_size_t + + class SECURITY_ATTRIBUTES(ctypes.Structure): + _fields_ = [ + ("nLength", wintypes.DWORD), + ("lpSecurityDescriptor", wintypes.LPVOID), + ("bInheritHandle", wintypes.BOOL), + ] + + class STARTUPINFOW(ctypes.Structure): + _fields_ = [ + ("cb", wintypes.DWORD), + ("lpReserved", wintypes.LPWSTR), + ("lpDesktop", wintypes.LPWSTR), + ("lpTitle", wintypes.LPWSTR), + ("dwX", wintypes.DWORD), + ("dwY", wintypes.DWORD), + ("dwXSize", wintypes.DWORD), + ("dwYSize", wintypes.DWORD), + ("dwXCountChars", wintypes.DWORD), + ("dwYCountChars", wintypes.DWORD), + ("dwFillAttribute", wintypes.DWORD), + ("dwFlags", wintypes.DWORD), + ("wShowWindow", ctypes.c_ushort), + ("cbReserved2", ctypes.c_ushort), + ("lpReserved2", ctypes.POINTER(ctypes.c_byte)), + ("hStdInput", wintypes.HANDLE), + ("hStdOutput", wintypes.HANDLE), + ("hStdError", wintypes.HANDLE), + ] + + class PROCESS_INFORMATION(ctypes.Structure): + _fields_ = [ + ("hProcess", wintypes.HANDLE), + ("hThread", wintypes.HANDLE), + ("dwProcessId", wintypes.DWORD), + ("dwThreadId", wintypes.DWORD), + ] + + class OVERLAPPED(ctypes.Structure): + _fields_ = [ + ("Internal", ulong_ptr), + ("InternalHigh", ulong_ptr), + ("Offset", wintypes.DWORD), + ("OffsetHigh", wintypes.DWORD), + ("hEvent", wintypes.HANDLE), + ] + + _SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES + _STARTUPINFOW = STARTUPINFOW + _PROCESS_INFORMATION = PROCESS_INFORMATION + _OVERLAPPED = OVERLAPPED + return _SECURITY_ATTRIBUTES, _STARTUPINFOW, _PROCESS_INFORMATION, _OVERLAPPED + + def _winerror(code=None): ctypes, _, kernel32 = _native() if code is None: code = kernel32.GetLastError() if hasattr(ctypes, "WinError"): - return ctypes.WinError(code) + error = ctypes.WinError(code) + if code in (ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND): + return FileNotFoundError(error.errno, error.strerror, error.filename, code) + if code == ERROR_ACCESS_DENIED: + return PermissionError(error.errno, error.strerror, error.filename, code) + return error + if code in (ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND): + return FileNotFoundError(code, f"Windows error {code}") + if code == ERROR_ACCESS_DENIED: + return PermissionError(code, f"Windows error {code}") return OSError(code, f"Windows error {code}") @@ -227,16 +425,149 @@ def _fsdecode(path): return str(path) +def _string(path): + if not isinstance(path, str): + raise TypeError("expected str") + return path + + +def _check_nul(value, name): + if value is not None and "\0" in value: + raise ValueError(f"embedded null character in {name}") + return value + + +def _optional_path(value, name): + if value is None: + return None + return _check_nul(_fsdecode(value), name) + + +def _optional_pointer(value): + if value in (None, NULL): + return None + raise NotImplementedError("custom Windows security attributes are not implemented in GraalPy") + + +def _dword_pointer(value): + ctypes, wintypes, _ = _native() + if value is None: + return None + return ctypes.byref(wintypes.DWORD(value)) + + +def _make_environment(env_mapping): + if env_mapping is None: + return None + if isinstance(env_mapping, str): + return _check_nul(env_mapping, "environment") + + normalized = {} + for key, value in env_mapping.items(): + key = _fsdecode(key) + value = _fsdecode(value) + if not key: + raise ValueError("empty environment variable name") + if "\0" in key or "\0" in value: + raise ValueError("embedded null character in environment") + if "=" in key: + raise ValueError("illegal environment variable name") + normalized[key.upper()] = (key, value) + return "\0".join(f"{key}={value}" for _, (key, value) in sorted(normalized.items())) + "\0\0" + + +class _WinapiOverlapped: + def __init__(self, handle): + ctypes, wintypes, kernel32 = _native() + _, _, _, OVERLAPPED = _structures() + self.handle = _as_handle(handle) + self.event = _as_handle(kernel32.CreateEventW(None, True, False, None)) + if not self.event: + _raise_last_error() + self.pending = False + self._completed_result = None + self._last_result = None + self._buffer = None + self._overlapped = OVERLAPPED() + self._overlapped.hEvent = wintypes.HANDLE(self.event) + self.address = ctypes.addressof(self._overlapped) + + def cancel(self): + ctypes, wintypes, _ = _native() + if self.pending and _CancelIoEx is not None: + _CancelIoEx(wintypes.HANDLE(self.handle), ctypes.byref(self._overlapped)) + self.pending = False + + def GetOverlappedResult(self, wait=False): + ctypes, wintypes, kernel32 = _native() + if self._completed_result is not None: + self._last_result = self._completed_result + self.pending = False + return self._last_result + transferred = wintypes.DWORD() + result = kernel32.GetOverlappedResult( + wintypes.HANDLE(self.handle), + ctypes.byref(self._overlapped), + ctypes.byref(transferred), + bool(wait), + ) + if result: + code = 0 + self.pending = False + else: + code = kernel32.GetLastError() + if code != ERROR_IO_INCOMPLETE: + self.pending = False + self._last_result = (transferred.value, code) + return self._last_result + + getresult = GetOverlappedResult + + def getbuffer(self): + if self._buffer is None: + return b"" + if self._last_result is None: + self.GetOverlappedResult(False) + return self._buffer.raw[: self._last_result[0]] + + def __del__(self): + try: + if self.event: + CloseHandle(self.event) + except Exception: + pass + + def CloseHandle(handle): _, wintypes, kernel32 = _native() _raise_if_zero(kernel32.CloseHandle(wintypes.HANDLE(_as_handle(handle)))) +def GetLastError(): + _, _, kernel32 = _native() + return kernel32.GetLastError() + + def GetCurrentProcess(): _, _, kernel32 = _native() return _as_handle(kernel32.GetCurrentProcess()) +def OpenProcess(desired_access, inherit_handle, process_id): + _, wintypes, kernel32 = _native() + handle = _as_handle( + kernel32.OpenProcess(wintypes.DWORD(desired_access), wintypes.BOOL(inherit_handle), wintypes.DWORD(process_id)) + ) + if not handle: + _raise_last_error() + return handle + + +def ExitProcess(exit_code): + _, _, kernel32 = _native() + kernel32.ExitProcess(exit_code) + + def GetStdHandle(std_handle): _, wintypes, kernel32 = _native() handle = _as_handle(kernel32.GetStdHandle(wintypes.DWORD(std_handle))) @@ -253,6 +584,23 @@ def WaitForSingleObject(handle, milliseconds): return result +def WaitForMultipleObjects(handle_seq, wait_flag, milliseconds=INFINITE): + ctypes, wintypes, kernel32 = _native() + handles = tuple(handle_seq) + if not handles: + raise ValueError("handle_seq must not be empty") + handle_array = (wintypes.HANDLE * len(handles))(*(_as_handle(handle) for handle in handles)) + result = kernel32.WaitForMultipleObjects( + wintypes.DWORD(len(handles)), + handle_array, + wintypes.BOOL(wait_flag), + wintypes.DWORD(milliseconds), + ) + if result == WAIT_FAILED: + _raise_last_error() + return result + + def GetExitCodeProcess(handle): ctypes, wintypes, kernel32 = _native() exit_code = wintypes.DWORD() @@ -298,9 +646,294 @@ def GetFileType(handle): return result +def GetACP(): + _, _, kernel32 = _native() + return kernel32.GetACP() + + +def CreatePipe(pipe_attrs, size): + ctypes, wintypes, kernel32 = _native() + _optional_pointer(pipe_attrs) + read_pipe = wintypes.HANDLE() + write_pipe = wintypes.HANDLE() + _raise_if_zero( + kernel32.CreatePipe( + ctypes.byref(read_pipe), + ctypes.byref(write_pipe), + None, + wintypes.DWORD(size), + ) + ) + return _as_handle(read_pipe.value), _as_handle(write_pipe.value) + + +def CreateProcess( + application_name, + command_line, + proc_attrs, + thread_attrs, + inherit_handles, + creation_flags, + env_mapping, + current_directory, + startup_info, +): + ctypes, wintypes, kernel32 = _native() + _, STARTUPINFOW, PROCESS_INFORMATION, _ = _structures() + _optional_pointer(proc_attrs) + _optional_pointer(thread_attrs) + + application_name = _optional_path(application_name, "application_name") + command_line = _optional_path(command_line, "command_line") + current_directory = _optional_path(current_directory, "current_directory") + + startup = STARTUPINFOW() + startup.cb = ctypes.sizeof(startup) + if startup_info is not None: + startup.dwFlags = getattr(startup_info, "dwFlags", 0) + startup.wShowWindow = getattr(startup_info, "wShowWindow", 0) + startup.hStdInput = wintypes.HANDLE(_as_handle(getattr(startup_info, "hStdInput", NULL))) + startup.hStdOutput = wintypes.HANDLE(_as_handle(getattr(startup_info, "hStdOutput", NULL))) + startup.hStdError = wintypes.HANDLE(_as_handle(getattr(startup_info, "hStdError", NULL))) + + command_line_buffer = ctypes.create_unicode_buffer(command_line) if command_line is not None else None + environment = _make_environment(env_mapping) + environment_buffer = ctypes.create_unicode_buffer(environment) if environment is not None else None + if environment_buffer is not None: + creation_flags |= CREATE_UNICODE_ENVIRONMENT + + process_information = PROCESS_INFORMATION() + result = kernel32.CreateProcessW( + application_name, + command_line_buffer, + None, + None, + wintypes.BOOL(inherit_handles), + wintypes.DWORD(creation_flags), + environment_buffer, + current_directory, + ctypes.byref(startup), + ctypes.byref(process_information), + ) + if not result: + _raise_last_error() + return ( + _as_handle(process_information.hProcess), + _as_handle(process_information.hThread), + process_information.dwProcessId, + process_information.dwThreadId, + ) + + +def CreateFile( + file_name, + desired_access, + share_mode, + security_attributes, + creation_disposition, + flags_and_attributes, + template_file, +): + _, wintypes, kernel32 = _native() + _optional_pointer(security_attributes) + file_name = _optional_path(file_name, "file_name") + handle = _as_handle( + kernel32.CreateFileW( + file_name, + wintypes.DWORD(desired_access), + wintypes.DWORD(share_mode), + None, + wintypes.DWORD(creation_disposition), + wintypes.DWORD(flags_and_attributes), + wintypes.HANDLE(_as_handle(template_file)), + ) + ) + if handle == INVALID_HANDLE_VALUE: + _raise_last_error() + return handle + + +def CreateNamedPipe( + name, + open_mode, + pipe_mode, + max_instances, + out_buffer_size, + in_buffer_size, + default_timeout, + security_attributes, +): + sys.audit("_winapi.CreateNamedPipe", name, open_mode, pipe_mode) + _, wintypes, kernel32 = _native() + _optional_pointer(security_attributes) + name = _optional_path(name, "name") + handle = _as_handle( + kernel32.CreateNamedPipeW( + name, + wintypes.DWORD(open_mode), + wintypes.DWORD(pipe_mode), + wintypes.DWORD(max_instances), + wintypes.DWORD(out_buffer_size), + wintypes.DWORD(in_buffer_size), + wintypes.DWORD(default_timeout), + None, + ) + ) + if handle == INVALID_HANDLE_VALUE: + _raise_last_error() + return handle + + +def ConnectNamedPipe(handle, overlapped=False): + ctypes, wintypes, kernel32 = _native() + if overlapped: + ov = _WinapiOverlapped(handle) + result = kernel32.ConnectNamedPipe(wintypes.HANDLE(_as_handle(handle)), ctypes.byref(ov._overlapped)) + if result: + ov._completed_result = (0, 0) + return ov + code = kernel32.GetLastError() + if code == ERROR_IO_PENDING: + ov.pending = True + return ov + if code == ERROR_PIPE_CONNECTED: + ov._completed_result = (0, 0) + return ov + raise _winerror(code) + + result = kernel32.ConnectNamedPipe(wintypes.HANDLE(_as_handle(handle)), None) + if result: + return None + code = kernel32.GetLastError() + if code == ERROR_PIPE_CONNECTED: + return None + raise _winerror(code) + + +def WaitNamedPipe(name, timeout): + _, wintypes, kernel32 = _native() + name = _optional_path(name, "name") + result = kernel32.WaitNamedPipeW(name, wintypes.DWORD(timeout)) + if not result: + _raise_last_error() + + +def ReadFile(handle, size, overlapped=False): + ctypes, wintypes, kernel32 = _native() + size = int(size) + buffer = ctypes.create_string_buffer(size) + transferred = wintypes.DWORD() + if overlapped: + ov = _WinapiOverlapped(handle) + ov._buffer = buffer + result = kernel32.ReadFile( + wintypes.HANDLE(_as_handle(handle)), + buffer, + wintypes.DWORD(size), + ctypes.byref(transferred), + ctypes.byref(ov._overlapped), + ) + if result: + ov._completed_result = (transferred.value, 0) + ov._last_result = ov._completed_result + return ov, 0 + code = kernel32.GetLastError() + if code == ERROR_IO_PENDING: + ov.pending = True + return ov, code + if code == ERROR_MORE_DATA: + ov._completed_result = (transferred.value, code) + ov._last_result = ov._completed_result + return ov, code + raise _winerror(code) + + result = kernel32.ReadFile( + wintypes.HANDLE(_as_handle(handle)), + buffer, + wintypes.DWORD(size), + ctypes.byref(transferred), + None, + ) + if not result: + code = kernel32.GetLastError() + if code == ERROR_MORE_DATA: + return buffer.raw[: transferred.value], code + raise _winerror(code) + return buffer.raw[: transferred.value], 0 + + +def WriteFile(handle, buffer, overlapped=False): + ctypes, wintypes, kernel32 = _native() + data = bytes(buffer) + write_buffer = ctypes.create_string_buffer(data, len(data)) + transferred = wintypes.DWORD() + if overlapped: + ov = _WinapiOverlapped(handle) + ov._buffer = write_buffer + result = kernel32.WriteFile( + wintypes.HANDLE(_as_handle(handle)), + write_buffer, + wintypes.DWORD(len(data)), + ctypes.byref(transferred), + ctypes.byref(ov._overlapped), + ) + if result: + ov._completed_result = (transferred.value, 0) + ov._last_result = ov._completed_result + return ov, 0 + code = kernel32.GetLastError() + if code == ERROR_IO_PENDING: + ov.pending = True + return ov, code + raise _winerror(code) + + result = kernel32.WriteFile( + wintypes.HANDLE(_as_handle(handle)), + write_buffer, + wintypes.DWORD(len(data)), + ctypes.byref(transferred), + None, + ) + if not result: + _raise_last_error() + return transferred.value, 0 + + +def PeekNamedPipe(handle, size=0): + ctypes, wintypes, kernel32 = _native() + size = int(size) + buffer = ctypes.create_string_buffer(size) if size else None + bytes_read = wintypes.DWORD() + total_available = wintypes.DWORD() + bytes_left = wintypes.DWORD() + _raise_if_zero( + kernel32.PeekNamedPipe( + wintypes.HANDLE(_as_handle(handle)), + buffer, + wintypes.DWORD(size), + ctypes.byref(bytes_read), + ctypes.byref(total_available), + ctypes.byref(bytes_left), + ) + ) + return (buffer.raw[: bytes_read.value] if buffer is not None else b""), total_available.value, bytes_left.value + + +def SetNamedPipeHandleState(named_pipe, mode, max_collection_count, collect_data_timeout): + _, wintypes, kernel32 = _native() + _raise_if_zero( + kernel32.SetNamedPipeHandleState( + wintypes.HANDLE(_as_handle(named_pipe)), + _dword_pointer(mode), + _dword_pointer(max_collection_count), + _dword_pointer(collect_data_timeout), + ) + ) + + def _get_path_name(function, path): ctypes, _, _ = _native() - path = _fsdecode(path) + path = _string(path) size = 260 while True: buffer = ctypes.create_unicode_buffer(size) @@ -387,10 +1020,3 @@ def function(*args, **kwargs): function.__name__ = name return function - - -CreatePipe = _not_implemented("CreatePipe") -CreateProcess = _not_implemented("CreateProcess") -CreateFile = _not_implemented("CreateFile") -CreateNamedPipe = _not_implemented("CreateNamedPipe") -ConnectNamedPipe = _not_implemented("ConnectNamedPipe") diff --git a/graalpython/lib-python/3/encodings/__init__.py b/graalpython/lib-python/3/encodings/__init__.py index ca520136ea..f9075b8f0d 100644 --- a/graalpython/lib-python/3/encodings/__init__.py +++ b/graalpython/lib-python/3/encodings/__init__.py @@ -163,7 +163,6 @@ def search_function(encoding): def _alias_mbcs(encoding): try: import _winapi - return None # Truffle change: we don't support this method yet ansi_code_page = "cp%s" % _winapi.GetACP() if encoding == ansi_code_page: import encodings.mbcs From 41154f1d635f990859f3268f2d7b71696c8b32f2 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 17:02:12 +0200 Subject: [PATCH 03/11] [GR-52900] Load Windows overlays from source --- .../com.oracle.graal.python.frozen/freeze_modules.py | 4 ---- .../builtins/objects/module/FrozenModules.java | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/graalpython/com.oracle.graal.python.frozen/freeze_modules.py b/graalpython/com.oracle.graal.python.frozen/freeze_modules.py index 311238b1b3..929029f5b8 100644 --- a/graalpython/com.oracle.graal.python.frozen/freeze_modules.py +++ b/graalpython/com.oracle.graal.python.frozen/freeze_modules.py @@ -125,10 +125,6 @@ def add_graalpython_core(): "java", "pip_hook", "_nt", - "_winapi", - "_overlapped", - "winreg", - "_winreg", ]: modname = f"graalpy.{os.path.basename(name)}" modpath = os.path.join(lib_graalpython, f"{name}.py") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java index e6f01a0c4c..c91a96b6a1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java @@ -104,10 +104,6 @@ private static final class Map { private static final PythonFrozenModule GRAALPY_JAVA = new PythonFrozenModule("GRAALPY_JAVA", null, false); private static final PythonFrozenModule GRAALPY_PIP_HOOK = new PythonFrozenModule("GRAALPY_PIP_HOOK", null, false); private static final PythonFrozenModule GRAALPY__NT = new PythonFrozenModule("GRAALPY__NT", null, false); - private static final PythonFrozenModule GRAALPY__WINAPI = new PythonFrozenModule("GRAALPY__WINAPI", null, false); - private static final PythonFrozenModule GRAALPY__OVERLAPPED = new PythonFrozenModule("GRAALPY__OVERLAPPED", null, false); - private static final PythonFrozenModule GRAALPY_WINREG = new PythonFrozenModule("GRAALPY_WINREG", null, false); - private static final PythonFrozenModule GRAALPY__WINREG = new PythonFrozenModule("GRAALPY__WINREG", null, false); } public static final PythonFrozenModule lookup(String name) { @@ -242,14 +238,6 @@ public static final PythonFrozenModule lookup(String name) { return Map.GRAALPY_PIP_HOOK; case "graalpy._nt": return Map.GRAALPY__NT; - case "graalpy._winapi": - return Map.GRAALPY__WINAPI; - case "graalpy._overlapped": - return Map.GRAALPY__OVERLAPPED; - case "graalpy.winreg": - return Map.GRAALPY_WINREG; - case "graalpy._winreg": - return Map.GRAALPY__WINREG; default: return null; } From 67070b0de581300aa5a106cafecb86c7e6cf1dd8 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 17:37:41 +0200 Subject: [PATCH 04/11] [GR-52900] Keep Windows overlays out of frozen modules --- .../com.oracle.graal.python.frozen/freeze_modules.py | 1 - .../python/builtins/modules/OverlappedModuleBuiltins.java | 8 ++++---- .../builtins/modules/WinregLegacyModuleBuiltins.java | 8 ++++---- .../python/builtins/objects/module/FrozenModules.java | 3 --- graalpython/lib-graalpython/_overlapped.py | 2 +- graalpython/lib-graalpython/_winapi.py | 2 +- graalpython/lib-graalpython/_winreg.py | 2 +- graalpython/lib-graalpython/winreg.py | 2 +- 8 files changed, 12 insertions(+), 16 deletions(-) diff --git a/graalpython/com.oracle.graal.python.frozen/freeze_modules.py b/graalpython/com.oracle.graal.python.frozen/freeze_modules.py index 929029f5b8..937236432b 100644 --- a/graalpython/com.oracle.graal.python.frozen/freeze_modules.py +++ b/graalpython/com.oracle.graal.python.frozen/freeze_modules.py @@ -124,7 +124,6 @@ def add_graalpython_core(): "_sysconfig", "java", "pip_hook", - "_nt", ]: modname = f"graalpy.{os.path.basename(name)}" modpath = os.path.join(lib_graalpython, f"{name}.py") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java index 2a056005f9..ccc0ca451f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java @@ -26,15 +26,15 @@ * * This license is subject to the following condition: * - * The above copyright notice and either this complete permission notice or at - * a minimum a reference to the UPL must be included in all copies or - * substantial portions of the Software. + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java index 1b475f183e..fee5f7f75a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java @@ -26,15 +26,15 @@ * * This license is subject to the following condition: * - * The above copyright notice and either this complete permission notice or at - * a minimum a reference to the UPL must be included in all copies or - * substantial portions of the Software. + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java index c91a96b6a1..235a182347 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java @@ -103,7 +103,6 @@ private static final class Map { private static final PythonFrozenModule GRAALPY__SYSCONFIG = new PythonFrozenModule("GRAALPY__SYSCONFIG", null, false); private static final PythonFrozenModule GRAALPY_JAVA = new PythonFrozenModule("GRAALPY_JAVA", null, false); private static final PythonFrozenModule GRAALPY_PIP_HOOK = new PythonFrozenModule("GRAALPY_PIP_HOOK", null, false); - private static final PythonFrozenModule GRAALPY__NT = new PythonFrozenModule("GRAALPY__NT", null, false); } public static final PythonFrozenModule lookup(String name) { @@ -236,8 +235,6 @@ public static final PythonFrozenModule lookup(String name) { return Map.GRAALPY_JAVA; case "graalpy.pip_hook": return Map.GRAALPY_PIP_HOOK; - case "graalpy._nt": - return Map.GRAALPY__NT; default: return null; } diff --git a/graalpython/lib-graalpython/_overlapped.py b/graalpython/lib-graalpython/_overlapped.py index d4188e1576..aca7df6ff5 100644 --- a/graalpython/lib-graalpython/_overlapped.py +++ b/graalpython/lib-graalpython/_overlapped.py @@ -33,7 +33,7 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. diff --git a/graalpython/lib-graalpython/_winapi.py b/graalpython/lib-graalpython/_winapi.py index 25ed1095f8..c8d2572cda 100644 --- a/graalpython/lib-graalpython/_winapi.py +++ b/graalpython/lib-graalpython/_winapi.py @@ -33,7 +33,7 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. diff --git a/graalpython/lib-graalpython/_winreg.py b/graalpython/lib-graalpython/_winreg.py index d22981d912..8f46a54fcf 100644 --- a/graalpython/lib-graalpython/_winreg.py +++ b/graalpython/lib-graalpython/_winreg.py @@ -33,7 +33,7 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. diff --git a/graalpython/lib-graalpython/winreg.py b/graalpython/lib-graalpython/winreg.py index 863bac0623..4aa52c847e 100644 --- a/graalpython/lib-graalpython/winreg.py +++ b/graalpython/lib-graalpython/winreg.py @@ -33,7 +33,7 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, STRICT LIABILITY, OR OTHERWISE, +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. From a1cd9e34830ffeb2e1bf3629960211a5b93da7d8 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 18:04:07 +0200 Subject: [PATCH 05/11] [GR-52900] Fix Windows overlay style checks --- .../python/builtins/objects/type/slots/TpSlotDescrGet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java index 53b0239c54..09c6fbe747 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java @@ -101,7 +101,7 @@ private TpSlotDescrGet() { } public abstract static sealed class TpSlotDescrGetBuiltin extends TpSlotBuiltin// - permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { + permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { static final BuiltinSlotWrapperSignature SIGNATURE = BuiltinSlotWrapperSignature.of(2, J_DOLLAR_SELF, "obj", "type"); static final PExternalFunctionWrapper WRAPPER = PExternalFunctionWrapper.DESCR_GET; From d73ee6035c634ebc98c2528c817d2a95650b0447 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 11 Jun 2026 10:09:46 +0200 Subject: [PATCH 06/11] [GR-52900] Fix Windows overlay CI checks --- .../graal/python/builtins/Python3Core.java | 24 +++++++++++++------ .../modules/OverlappedModuleBuiltins.java | 6 ++--- .../modules/WinregLegacyModuleBuiltins.java | 6 ++--- .../objects/type/slots/TpSlotDescrGet.java | 2 +- .../{ => modules}/_overlapped.py | 6 ++--- .../lib-graalpython/{ => modules}/_winapi.py | 6 ++--- .../lib-graalpython/{ => modules}/_winreg.py | 6 ++--- .../lib-graalpython/{ => modules}/winreg.py | 6 ++--- 8 files changed, 36 insertions(+), 26 deletions(-) rename graalpython/lib-graalpython/{ => modules}/_overlapped.py (98%) rename graalpython/lib-graalpython/{ => modules}/_winapi.py (99%) rename graalpython/lib-graalpython/{ => modules}/_winreg.py (94%) rename graalpython/lib-graalpython/{ => modules}/winreg.py (99%) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index b7a8016327..3e879133c3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -446,10 +446,6 @@ private static TruffleString[] getCoreFiles() { if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) { coreFiles = new ArrayList<>(coreFiles); coreFiles.add(toTruffleStringUncached("_nt")); - coreFiles.add(toTruffleStringUncached("_winapi")); - coreFiles.add(toTruffleStringUncached("_overlapped")); - coreFiles.add(toTruffleStringUncached("winreg")); - coreFiles.add(toTruffleStringUncached("_winreg")); } return coreFiles.toArray(new TruffleString[0]); @@ -1056,6 +1052,12 @@ private void initializePython3Core(TruffleString coreHome) { for (TruffleString s : getCoreFiles()) { loadFile(s, coreHome); } + if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) { + loadFile(toTruffleStringUncached("_winapi"), toTruffleStringUncached("modules/_winapi"), coreHome); + loadFile(toTruffleStringUncached("_overlapped"), toTruffleStringUncached("modules/_overlapped"), coreHome); + loadFile(toTruffleStringUncached("winreg"), toTruffleStringUncached("modules/winreg"), coreHome); + loadFile(toTruffleStringUncached("_winreg"), toTruffleStringUncached("modules/_winreg"), coreHome); + } initialized = true; } @@ -1327,15 +1329,23 @@ private Source getInternalSource(TruffleString basename, TruffleString prefix) { } private void loadFile(TruffleString s, TruffleString prefix) { + loadFile(s, s, prefix); + } + + private void loadFile(TruffleString s, TruffleString sourceName, TruffleString prefix) { PythonModule mod = lookupBuiltinModule(s); if (mod == null) { // use an anonymous module for the side-effects mod = PFactory.createPythonModule(T___ANONYMOUS__); } - loadFile(s, prefix, mod); + loadFile(s, sourceName, prefix, mod); } private void loadFile(TruffleString s, TruffleString prefix, PythonModule mod) { + loadFile(s, s, prefix, mod); + } + + private void loadFile(TruffleString s, TruffleString sourceName, TruffleString prefix, PythonModule mod) { if (ImpModuleBuiltins.importFrozenModuleObjectNoRaise(this, cat(T_GRAALPYTHON, T_DOT, s), mod) != null) { LOGGER.log(Level.FINE, () -> "import '" + s + "' # "); return; @@ -1343,10 +1353,10 @@ private void loadFile(TruffleString s, TruffleString prefix, PythonModule mod) { LOGGER.log(Level.FINE, () -> "import '" + s + "'"); Supplier getCode = () -> { - Source source = getInternalSource(s, prefix); + Source source = getInternalSource(sourceName, prefix); return getLanguage().parse(getContext(), source, InputType.FILE, false, 0, false, null, EnumSet.noneOf(FutureFeature.class)); }; - RootCallTarget callTarget = (RootCallTarget) getLanguage().cacheCode(s, getCode); + RootCallTarget callTarget = (RootCallTarget) getLanguage().cacheCode(sourceName, getCode); PCode code = PFactory.createCode(language, callTarget); CallDispatchers.SimpleIndirectInvokeNode.executeUncached(callTarget, PArguments.withGlobals(code, mod)); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java index ccc0ca451f..1b8d65f11a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/OverlappedModuleBuiltins.java @@ -34,9 +34,9 @@ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ package com.oracle.graal.python.builtins.modules; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java index fee5f7f75a..bd48f6dc6f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WinregLegacyModuleBuiltins.java @@ -34,9 +34,9 @@ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ package com.oracle.graal.python.builtins.modules; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java index 09c6fbe747..53b0239c54 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotDescrGet.java @@ -101,7 +101,7 @@ private TpSlotDescrGet() { } public abstract static sealed class TpSlotDescrGetBuiltin extends TpSlotBuiltin// - permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { + permits TpSlotDescrGetBuiltinComplex, TpSlotDescrGetBuiltinSimple { static final BuiltinSlotWrapperSignature SIGNATURE = BuiltinSlotWrapperSignature.of(2, J_DOLLAR_SELF, "obj", "type"); static final PExternalFunctionWrapper WRAPPER = PExternalFunctionWrapper.DESCR_GET; diff --git a/graalpython/lib-graalpython/_overlapped.py b/graalpython/lib-graalpython/modules/_overlapped.py similarity index 98% rename from graalpython/lib-graalpython/_overlapped.py rename to graalpython/lib-graalpython/modules/_overlapped.py index aca7df6ff5..cddee92cf5 100644 --- a/graalpython/lib-graalpython/_overlapped.py +++ b/graalpython/lib-graalpython/modules/_overlapped.py @@ -33,9 +33,9 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import sys diff --git a/graalpython/lib-graalpython/_winapi.py b/graalpython/lib-graalpython/modules/_winapi.py similarity index 99% rename from graalpython/lib-graalpython/_winapi.py rename to graalpython/lib-graalpython/modules/_winapi.py index c8d2572cda..ee9f065065 100644 --- a/graalpython/lib-graalpython/_winapi.py +++ b/graalpython/lib-graalpython/modules/_winapi.py @@ -33,9 +33,9 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import sys diff --git a/graalpython/lib-graalpython/_winreg.py b/graalpython/lib-graalpython/modules/_winreg.py similarity index 94% rename from graalpython/lib-graalpython/_winreg.py rename to graalpython/lib-graalpython/modules/_winreg.py index 8f46a54fcf..81cb1c5744 100644 --- a/graalpython/lib-graalpython/_winreg.py +++ b/graalpython/lib-graalpython/modules/_winreg.py @@ -33,8 +33,8 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from winreg import * diff --git a/graalpython/lib-graalpython/winreg.py b/graalpython/lib-graalpython/modules/winreg.py similarity index 99% rename from graalpython/lib-graalpython/winreg.py rename to graalpython/lib-graalpython/modules/winreg.py index 4aa52c847e..ea40592518 100644 --- a/graalpython/lib-graalpython/winreg.py +++ b/graalpython/lib-graalpython/modules/winreg.py @@ -33,9 +33,9 @@ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import sys From a6140083c14fc0ea5ccddd546c2477db5eff22c2 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 11 Jun 2026 11:33:14 +0200 Subject: [PATCH 07/11] [GR-52900] Defer Windows overlays during native image build --- .../graal/python/builtins/Python3Core.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index 3e879133c3..0a2b28a0b8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -420,6 +420,7 @@ import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.strings.TruffleString; +import org.graalvm.nativeimage.ImageInfo; /** * The core is a historical artifact and PythonContext and Python3Core should be merged. @@ -437,18 +438,13 @@ public abstract class Python3Core { private static TruffleString[] getCoreFiles() { // Order matters! - List coreFiles = List.of( + return new TruffleString[]{ T___GRAALPYTHON__, T__SRE, T__SYSCONFIG, T_JAVA, - toTruffleStringUncached("pip_hook")); - if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) { - coreFiles = new ArrayList<>(coreFiles); - coreFiles.add(toTruffleStringUncached("_nt")); - } - - return coreFiles.toArray(new TruffleString[0]); + toTruffleStringUncached("pip_hook") + }; } private PythonBuiltins[] builtins; @@ -1052,13 +1048,17 @@ private void initializePython3Core(TruffleString coreHome) { for (TruffleString s : getCoreFiles()) { loadFile(s, coreHome); } - if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) { + initialized = true; + } + + private void initializeWindowsCoreFiles(TruffleString coreHome) { + if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32 && !ImageInfo.inImageBuildtimeCode()) { + loadFile(toTruffleStringUncached("_nt"), coreHome); loadFile(toTruffleStringUncached("_winapi"), toTruffleStringUncached("modules/_winapi"), coreHome); loadFile(toTruffleStringUncached("_overlapped"), toTruffleStringUncached("modules/_overlapped"), coreHome); loadFile(toTruffleStringUncached("winreg"), toTruffleStringUncached("modules/winreg"), coreHome); loadFile(toTruffleStringUncached("_winreg"), toTruffleStringUncached("modules/_winreg"), coreHome); } - initialized = true; } /** @@ -1069,6 +1069,7 @@ private void initializePython3Core(TruffleString coreHome) { public final void postInitialize(Env env) { if (!env.isPreInitialization()) { initialized = false; + initializeWindowsCoreFiles(getContext().getCoreHomeOrFail()); for (PythonBuiltins builtin : builtins) { if (builtin.needsPostInitialize()) { From 222f259fd022aeafcc624d12a70c9c7f377fc834 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 11 Jun 2026 13:15:29 +0200 Subject: [PATCH 08/11] [GR-52900] Assert Windows overlay runtime loading --- .../src/com/oracle/graal/python/builtins/Python3Core.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index 0a2b28a0b8..4b4671cd9f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -1052,7 +1052,8 @@ private void initializePython3Core(TruffleString coreHome) { } private void initializeWindowsCoreFiles(TruffleString coreHome) { - if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32 && !ImageInfo.inImageBuildtimeCode()) { + if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) { + assert !ImageInfo.inImageBuildtimeCode(); loadFile(toTruffleStringUncached("_nt"), coreHome); loadFile(toTruffleStringUncached("_winapi"), toTruffleStringUncached("modules/_winapi"), coreHome); loadFile(toTruffleStringUncached("_overlapped"), toTruffleStringUncached("modules/_overlapped"), coreHome); From e42d58d9b27c7576adef327d9cdc6e72051cad9e Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 11 Jun 2026 15:53:00 +0200 Subject: [PATCH 09/11] [GR-52900] Isolate Windows ctypes wrappers --- graalpython/lib-graalpython/_nt.py | 14 ++++++++++---- graalpython/lib-graalpython/modules/_overlapped.py | 4 +++- graalpython/lib-graalpython/modules/_winapi.py | 6 ++++-- graalpython/lib-graalpython/modules/winreg.py | 4 ++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/graalpython/lib-graalpython/_nt.py b/graalpython/lib-graalpython/_nt.py index 1144af40c9..3d6a1bf4b8 100644 --- a/graalpython/lib-graalpython/_nt.py +++ b/graalpython/lib-graalpython/_nt.py @@ -42,23 +42,29 @@ def _add_dll_directory(path): import ctypes, os - AddDllDirectory = ctypes.windll.kernel32['AddDllDirectory'] + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.GetLastError.argtypes = [] + kernel32.GetLastError.restype = ctypes.c_ulong + AddDllDirectory = kernel32.AddDllDirectory AddDllDirectory.argtypes = [ctypes.c_wchar_p] AddDllDirectory.restype = ctypes.c_void_p result = AddDllDirectory(os.fspath(path)) if result == 0: - raise OSError(f"add_dll_directory: {ctypes.windll.kernel32.GetLastError()}") + raise OSError(f"add_dll_directory: {kernel32.GetLastError()}") return result def _remove_dll_directory(cookie): import ctypes - RemoveDllDirectory = ctypes.windll.kernel32['RemoveDllDirectory'] + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.GetLastError.argtypes = [] + kernel32.GetLastError.restype = ctypes.c_ulong + RemoveDllDirectory = kernel32.RemoveDllDirectory RemoveDllDirectory.argtypes = [ctypes.c_void_p] RemoveDllDirectory.restype = ctypes.c_int result = RemoveDllDirectory(cookie) if result == 0: - raise OSError(f"remove_dll_directory: {ctypes.windll.kernel32.GetLastError()}") + raise OSError(f"remove_dll_directory: {kernel32.GetLastError()}") nt._add_dll_directory = _add_dll_directory diff --git a/graalpython/lib-graalpython/modules/_overlapped.py b/graalpython/lib-graalpython/modules/_overlapped.py index cddee92cf5..d3b98d4139 100644 --- a/graalpython/lib-graalpython/modules/_overlapped.py +++ b/graalpython/lib-graalpython/modules/_overlapped.py @@ -74,7 +74,9 @@ def _native(): import ctypes from ctypes import wintypes - kernel32 = ctypes.windll.kernel32 + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.GetLastError.argtypes = [] + kernel32.GetLastError.restype = wintypes.DWORD kernel32.CreateIoCompletionPort.argtypes = [wintypes.HANDLE, wintypes.HANDLE, ctypes.c_size_t, wintypes.DWORD] kernel32.CreateIoCompletionPort.restype = wintypes.HANDLE kernel32.GetQueuedCompletionStatus.argtypes = [ diff --git a/graalpython/lib-graalpython/modules/_winapi.py b/graalpython/lib-graalpython/modules/_winapi.py index ee9f065065..c5c8faef47 100644 --- a/graalpython/lib-graalpython/modules/_winapi.py +++ b/graalpython/lib-graalpython/modules/_winapi.py @@ -44,7 +44,7 @@ raise ImportError("win32 only") -_POINTER_BITS = 64 if sys.maxsize > 2**32 else 32 +_POINTER_BITS = 64 def _unsigned_pointer(value): @@ -96,6 +96,8 @@ def _unsigned_pointer(value): GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 +FILE_GENERIC_READ = 0x00120089 +FILE_GENERIC_WRITE = 0x00120116 FILE_SHARE_READ = 0x00000001 FILE_SHARE_WRITE = 0x00000002 FILE_SHARE_DELETE = 0x00000004 @@ -152,7 +154,7 @@ def _native(): import ctypes from ctypes import wintypes - kernel32 = ctypes.windll.kernel32 + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) kernel32.GetLastError.argtypes = [] kernel32.GetLastError.restype = wintypes.DWORD diff --git a/graalpython/lib-graalpython/modules/winreg.py b/graalpython/lib-graalpython/modules/winreg.py index ea40592518..b56c55928b 100644 --- a/graalpython/lib-graalpython/modules/winreg.py +++ b/graalpython/lib-graalpython/modules/winreg.py @@ -85,7 +85,7 @@ REG_QWORD = 11 REG_QWORD_LITTLE_ENDIAN = 11 -_POINTER_BITS = 64 if sys.maxsize > 2**32 else 32 +_POINTER_BITS = 64 def _predefined_hkey(value): @@ -115,7 +115,7 @@ def _native(): import ctypes from ctypes import wintypes - advapi32 = ctypes.windll.advapi32 + advapi32 = ctypes.WinDLL("advapi32", use_last_error=True) advapi32.RegCloseKey.argtypes = [wintypes.HANDLE] advapi32.RegCloseKey.restype = wintypes.LONG advapi32.RegOpenKeyExW.argtypes = [ From 97706b91d70c86ea1571178bcd129413e21918c2 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 11 Jun 2026 16:51:33 +0200 Subject: [PATCH 10/11] [GR-52900] Fix Windows startup tests --- .../src/tests/test_startup.py | 22 +++++++++++++++---- .../modules/GraalPythonModuleBuiltins.java | 7 ++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_startup.py b/graalpython/com.oracle.graal.python.test/src/tests/test_startup.py index cc1f09d552..5480ed3518 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_startup.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_startup.py @@ -44,6 +44,19 @@ import platform # Both lists should remain as small as possible to avoid adding overhead to startup +IS_WINDOWS = platform.system() == 'Windows' +WINDOWS_CORE_MODULES = ['_nt', '_winapi', '_overlapped', 'winreg', '_winreg'] if IS_WINDOWS else [] +WINDOWS_FULL_STARTUP_MODULES = [ + '_datetime', + 'datetime', + '_ctypes', + '_struct', + 'struct', + 'ctypes._endian', + 'ctypes', + 'ctypes.wintypes', +] if IS_WINDOWS else [] + expected_nosite_startup_modules = [ '_frozen_importlib', '_frozen_importlib_external', @@ -52,7 +65,7 @@ '_sysconfig', 'java', 'pip_hook', -] + (['_nt'] if platform.system() == 'Windows' else []) +] + WINDOWS_CORE_MODULES expected_full_startup_modules = expected_nosite_startup_modules + [ '_abc', @@ -63,11 +76,12 @@ 'stat', '_collections_abc', 'genericpath', - *(['_winapi', 'ntpath'] if platform.system() == 'Windows' else ['posixpath']), + *(['ntpath'] if IS_WINDOWS else ['posixpath']), 'os', '_sitebuiltins', '_io', 'io', + *WINDOWS_FULL_STARTUP_MODULES, 'site', ] @@ -76,12 +90,12 @@ class StartupTests(unittest.TestCase): def test_startup_nosite(self): result = subprocess.check_output([sys.executable, '--log.level=FINE', '-S', '-v', '-c', 'print("Hello")'], stderr=subprocess.STDOUT, text=True) assert 'Hello' in result - imports = re.findall("import '(\S+)'", result) + imports = re.findall(r"import '(\S+)'", result) self.assertEqual(expected_nosite_startup_modules, imports) @unittest.skipUnless(sys.implementation.name == 'graalpy', "GraalPy-specific test") def test_startup_full(self): result = subprocess.check_output([sys.executable, '--log.level=FINE', '-s', '-v', '-c', 'print("Hello")'], stderr=subprocess.STDOUT, text=True) assert 'Hello' in result - imports = re.findall("import '(\S+)'", result) + imports = re.findall(r"import '(\S+)'", result) self.assertEqual(expected_full_startup_modules, imports) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java index 1d555d7ee8..f9487ddb40 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java @@ -563,8 +563,11 @@ private static Object doLoadBytecodeFile(Object bytecodePath, Object sourcePath, cacheKey = ARRAY_ACCESSOR_LE.getLong(bytes, 8); } if (context.getOption(PythonOptions.VerboseFlag)) { - Object message = PyObjectCallMethodObjArgs.executeUncached(MESSAGE, T_FORMAT, bytecodePath, sourcePath); - CallNode.executeUncached(context.lookupBuiltinModule(T__BOOTSTRAP).getAttribute(T__VERBOSE_MESSAGE), message); + PythonModule bootstrap = context.lookupBuiltinModule(T__BOOTSTRAP); + if (bootstrap != null) { + Object message = PyObjectCallMethodObjArgs.executeUncached(MESSAGE, T_FORMAT, bytecodePath, sourcePath); + CallNode.executeUncached(bootstrap.getAttribute(T__VERBOSE_MESSAGE), message); + } } TruffleFile sourceFile = null; if (sourcePath != PNone.NONE) { From 42ff8c5119a9efe490f7bd79f3abe3f5126eab9a Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 11 Jun 2026 17:20:32 +0200 Subject: [PATCH 11/11] [GR-52900] Fix Windows overlay copyright --- graalpython/lib-graalpython/_nt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/lib-graalpython/_nt.py b/graalpython/lib-graalpython/_nt.py index 3d6a1bf4b8..222f31dd01 100644 --- a/graalpython/lib-graalpython/_nt.py +++ b/graalpython/lib-graalpython/_nt.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # The Universal Permissive License (UPL), Version 1.0