From 90dca23fee85b8d365128311c082dde431d1f961 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 11:19:48 +0200 Subject: [PATCH 1/7] [GR-46281] Re-enable two ignored tests with relaxed semantics for foreign executables. --- .../python/test/integration/interop/JavaInteropTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/interop/JavaInteropTest.java b/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/interop/JavaInteropTest.java index a78d825b0f..7958946fe6 100644 --- a/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/interop/JavaInteropTest.java +++ b/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/interop/JavaInteropTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -70,7 +70,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -485,7 +484,6 @@ public Object execute(Value... arguments) { } } - @Ignore // blocked by GR-46281 @Test public void runAForeignExecutable() throws IOException { Source suitePy = Source.newBuilder("python", @@ -493,7 +491,7 @@ public void runAForeignExecutable() throws IOException { def foo(obj): try: obj() - except TypeError as e: + except: pass else: assert False @@ -504,7 +502,6 @@ def foo(obj): foo.execute(new AForeignExecutable()); } - @Ignore // blocked by GR-46281 @Test public void invokeAForeignMember() throws IOException { Source suitePy = Source.newBuilder("python", @@ -512,7 +509,7 @@ public void invokeAForeignMember() throws IOException { def foo(obj): try: obj.fun() - except TypeError as e: + except: pass else: assert False From cff5052ffb27da573b7a5c942fa05482acb7bd10 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 11:29:06 +0200 Subject: [PATCH 2/7] [GR-48550] Update Known Windows limitations section. --- docs/user/Standalone-Getting-Started.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/user/Standalone-Getting-Started.md b/docs/user/Standalone-Getting-Started.md index 20a5f320f1..a448fd9c19 100644 --- a/docs/user/Standalone-Getting-Started.md +++ b/docs/user/Standalone-Getting-Started.md @@ -119,13 +119,6 @@ pyenv shell graalpy-25.0.3 #### Known Windows Limitations -- JLine treats Windows as a dumb terminal (no autocomplete, limited REPL editing) -- Interactive `help()` in REPL doesn't work -- Virtual environment issues: - - `graalpy.cmd` and `graalpy.exe` are broken inside `venv` - - `pip.exe` cannot be used directly - - Use `myvenv/Scripts/python.exe -m pip --no-cache-dir install ` - - Only pure Python binary wheels supported - PowerShell works better than CMD ## Using Virtual Environments From 1469120db0e84a956e31a747bcbbc371713ad932 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 13:23:01 +0200 Subject: [PATCH 3/7] [GR-55096] Use normal helper lib node for interop member access --- .../python/test/interop/ArgsKwArgsTest.java | 32 ++++++++++++++++- .../objects/PythonAbstractObject.java | 35 +++++++------------ 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/ArgsKwArgsTest.java b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/ArgsKwArgsTest.java index b81ccd74d7..fefe2f9209 100644 --- a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/ArgsKwArgsTest.java +++ b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/interop/ArgsKwArgsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -41,6 +41,7 @@ package com.oracle.graal.python.test.interop; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -77,6 +78,35 @@ private Value run(String evalString) { return context.eval("python", evalString); } + @Test + public void testLazyModuleMemberInvoke() { + Value module = run(""" + import types + + class LazyModule(types.ModuleType): + def __getattr__(self, name): + if name == "set_seed": + def set_seed(seed): + self.seed = seed + return seed + 1 + self.__dict__[name] = set_seed + return set_seed + raise AttributeError(name) + + direct = LazyModule("direct") + probe = LazyModule("probe") + """); + + Value direct = module.getMember("direct"); + assertEquals(44, direct.invokeMember("set_seed", 43).asInt()); + assertEquals(43, direct.getMember("seed").asInt()); + + Value probe = module.getMember("probe"); + assertTrue(probe.hasMember("set_seed")); + assertFalse(probe.hasMember("missing")); + assertEquals(12, probe.invokeMember("set_seed", 11).asInt()); + } + @Test public void testPositionalArgs01() { // @formatter:off diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/PythonAbstractObject.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/PythonAbstractObject.java index 5a04dbef56..ee391fe12e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/PythonAbstractObject.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/PythonAbstractObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -40,7 +40,6 @@ */ package com.oracle.graal.python.builtins.objects; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.AttributeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; @@ -48,7 +47,6 @@ import static com.oracle.graal.python.nodes.ErrorMessages.S_MUST_BE_A_S_TUPLE; import static com.oracle.graal.python.nodes.SpecialMethodNames.T_KEYS; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___DELETE__; -import static com.oracle.graal.python.nodes.SpecialMethodNames.T___GETATTRIBUTE__; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___GET__; import static com.oracle.graal.python.nodes.SpecialMethodNames.T___SET__; import static com.oracle.graal.python.nodes.StringLiterals.T_LBRACKET; @@ -125,7 +123,6 @@ import com.oracle.graal.python.nodes.attributes.ReadAttributeFromModuleNode; import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNode; import com.oracle.graal.python.nodes.call.CallNode; -import com.oracle.graal.python.nodes.call.special.CallBinaryMethodNode; import com.oracle.graal.python.nodes.expression.CastToListExpressionNode.CastToListInteropNode; import com.oracle.graal.python.nodes.interop.GetInteropBehaviorNode; import com.oracle.graal.python.nodes.interop.GetInteropBehaviorValueNode; @@ -606,29 +603,15 @@ public boolean hasMemberWriteSideEffects(String member, public Object invokeMember(String member, Object[] arguments, @Bind Node inliningTarget, @Exclusive @Cached TruffleString.FromJavaStringNode fromJavaStringNode, - @Exclusive @Cached LookupInheritedAttributeNode.Dynamic lookupGetattributeNode, - @Exclusive @Cached CallBinaryMethodNode callGetattributeNode, + @Exclusive @Cached PyObjectLookupAttr lookup, @Exclusive @Cached PExecuteNode executeNode, - @Exclusive @Cached InlinedConditionProfile profileGetattribute, @Exclusive @Cached InlinedConditionProfile profileMember, - // GR-44020: make shared: - @Exclusive @Cached IsBuiltinObjectProfile attributeErrorProfile, @Exclusive @Cached GilNode gil) throws UnknownIdentifierException, UnsupportedMessageException { boolean mustRelease = gil.acquire(); try { - Object memberObj; - try { - Object attrGetattribute = lookupGetattributeNode.execute(inliningTarget, this, T___GETATTRIBUTE__); - if (profileGetattribute.profile(inliningTarget, attrGetattribute == PNone.NO_VALUE)) { - throw UnknownIdentifierException.create(member); - } - memberObj = callGetattributeNode.executeObject(attrGetattribute, this, fromJavaStringNode.execute(member, TS_ENCODING)); - if (profileMember.profile(inliningTarget, memberObj == PNone.NO_VALUE)) { - throw UnknownIdentifierException.create(member); - } - } catch (PException e) { - e.expect(inliningTarget, AttributeError, attributeErrorProfile); + Object memberObj = lookup.execute(null, inliningTarget, this, fromJavaStringNode.execute(member, TS_ENCODING)); + if (profileMember.profile(inliningTarget, memberObj == PNone.NO_VALUE)) { throw UnknownIdentifierException.create(member); } return executeNode.execute(memberObj, arguments); @@ -1166,6 +1149,7 @@ static boolean access(Object object, TruffleString attrKeyName, int type, @Cached GetClassNode getClassNode, @Cached IsImmutable isImmutable, @Cached GetMroNode getMroNode, + @Cached PyObjectLookupAttr lookupAttr, @Cached GilNode gil) { boolean mustRelease = gil.acquire(); try { @@ -1185,10 +1169,14 @@ static boolean access(Object object, TruffleString attrKeyName, int type, if (attr == PNone.NO_VALUE) { attr = readObjectAttrNode.execute(owner, attrKeyName); } + Object dynamicAttr = PNone.NO_VALUE; + if (attr == PNone.NO_VALUE && (type == READABLE || type == INVOCABLE)) { + dynamicAttr = lookupAttr.execute(null, inliningTarget, object, attrKeyName); + } switch (type) { case READABLE: - return attr != PNone.NO_VALUE; + return attr != PNone.NO_VALUE || dynamicAttr != PNone.NO_VALUE; case INSERTABLE: return attr == PNone.NO_VALUE && !isImmutable.execute(inliningTarget, object); case REMOVABLE: @@ -1222,6 +1210,9 @@ static boolean access(Object object, TruffleString attrKeyName, int type, } return callableCheck.execute(inliningTarget, attr); } + if (dynamicAttr != PNone.NO_VALUE) { + return callableCheck.execute(inliningTarget, dynamicAttr); + } return false; case READ_SIDE_EFFECTS: if (attr != PNone.NO_VALUE && owner != object && !(attr instanceof PFunction || attr instanceof PBuiltinFunction)) { From 611b2d159cc34aea61043b965fccb502f67aa21f Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 13:26:41 +0200 Subject: [PATCH 4/7] [GR-53509] Support pgid argument to assign subprocesses to process groups --- .../src/tests/test_subprocess.py | 8 ++++++++ .../src/tests/unittest_tags/test_subprocess.txt | 1 + .../modules/PosixSubprocessModuleBuiltins.java | 2 +- .../graal/python/runtime/EmulatedPosixSupport.java | 5 ++++- .../graal/python/runtime/LoggingPosixSupport.java | 9 +++++---- .../graal/python/runtime/NativePosixSupport.java | 7 ++++--- .../graal/python/runtime/PosixSupportLibrary.java | 2 +- .../graal/python/runtime/PreInitPosixSupport.java | 4 ++-- graalpython/python-libposix/src/fork_exec.c | 12 +++++++++++- 9 files changed, 37 insertions(+), 13 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py b/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py index 0b9b89f51e..40d9e7f70d 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py @@ -120,6 +120,14 @@ def test_waitpid(self): p.kill() p.wait() + @unittest.skipIf(sys.platform == 'win32' or POSIX_BACKEND_IS_JAVA or not hasattr(os, 'getpgid'), "Posix-specific") + def test_process_group_0(self): + p = subprocess.Popen([sys.executable, "-c", "import os; print(os.getpgid(0))"], + stdout=subprocess.PIPE, process_group=0) + stdout, _ = p.communicate(timeout=10) + self.assertEqual(p.returncode, 0) + self.assertEqual(int(stdout), p.pid) + def _run_in_new_group(self, code): def safe_decode(x): return '' if not x else x.decode().strip() diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_subprocess.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_subprocess.txt index d6598dac7b..475953d465 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_subprocess.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_subprocess.txt @@ -38,6 +38,7 @@ test.test_subprocess.POSIXProcessTestCase.test_group_error @ darwin-arm64,linux- test.test_subprocess.POSIXProcessTestCase.test_invalid_args @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github test.test_subprocess.POSIXProcessTestCase.test_kill @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github test.test_subprocess.POSIXProcessTestCase.test_kill_dead @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github +test.test_subprocess.POSIXProcessTestCase.test_process_group_0 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github test.test_subprocess.POSIXProcessTestCase.test_remapping_std_fds @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github test.test_subprocess.POSIXProcessTestCase.test_select_unbuffered @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github test.test_subprocess.POSIXProcessTestCase.test_send_signal @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixSubprocessModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixSubprocessModuleBuiltins.java index 5a0585e80f..3092560d74 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixSubprocessModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixSubprocessModuleBuiltins.java @@ -303,7 +303,7 @@ static int forkExec(VirtualFrame frame, Object[] args, Object executableList, bo gil.release(true); try { return posixLib.forkExec(context.getPosixSupport(), executables, processArgs, cwd, env == null ? null : (Object[]) env, stdinRead, stdinWrite, stdoutRead, stdoutWrite, stderrRead, - stderrWrite, errPipeRead, errPipeWrite, closeFds, restoreSignals, callSetsid, fdsToKeep, allowVFork); + stderrWrite, errPipeRead, errPipeWrite, closeFds, restoreSignals, callSetsid, pgidToSet, fdsToKeep, allowVFork); } catch (PosixException e) { gil.acquire(); throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java index a4df98074a..1e1b09297d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java @@ -2445,11 +2445,14 @@ public void unsetenv(Object name) { @ExportMessage @TruffleBoundary public int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd, - int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep, boolean allowVFork, + int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int pgidToSet, int[] fdsToKeep, boolean allowVFork, @Shared("js2ts") @Cached TruffleString.FromJavaStringNode fromJavaStringNode) throws PosixException { if (PythonImageBuildOptions.WITHOUT_PLATFORM_ACCESS) { throw new UnsupportedPosixFeatureException("forkExec was excluded"); } + if (pgidToSet >= 0) { + throw createUnsupportedFeature("process_group in fork_exec"); + } // TODO there are a few arguments we ignore, we should throw an exception or report a // compatibility warning diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java index 5b469bf5d3..396f647f60 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java @@ -1114,13 +1114,14 @@ public void mmapWriteBytes(Object mmap, long index, byte[] bytes, int length, @ExportMessage final int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd, - int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep, boolean allowVFork, + int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int pgidToSet, int[] fdsToKeep, boolean allowVFork, @CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException { - logEnter("forkExec", "%s, %s, %s, %s, %d, %d, %d, %d, %d, %d, %d, %d, %b, %b, %b, %s, %b", executables, args, cwd, env, stdinReadFd, stdinWriteFd, stdoutReadFd, stdoutWriteFd, stderrReadFd, - stderrWriteFd, errPipeReadFd, errPipeWriteFd, closeFds, restoreSignals, callSetsid, fdsToKeep, allowVFork); + logEnter("forkExec", "%s, %s, %s, %s, %d, %d, %d, %d, %d, %d, %d, %d, %b, %b, %b, %d, %s, %b", executables, args, cwd, env, stdinReadFd, stdinWriteFd, stdoutReadFd, stdoutWriteFd, + stderrReadFd, + stderrWriteFd, errPipeReadFd, errPipeWriteFd, closeFds, restoreSignals, callSetsid, pgidToSet, fdsToKeep, allowVFork); try { return logExit("forkExec", "%d", lib.forkExec(delegate, executables, args, cwd, env, stdinReadFd, stdinWriteFd, stdoutReadFd, stdoutWriteFd, stderrReadFd, stderrWriteFd, errPipeReadFd, - errPipeWriteFd, closeFds, restoreSignals, callSetsid, fdsToKeep, allowVFork)); + errPipeWriteFd, closeFds, restoreSignals, callSetsid, pgidToSet, fdsToKeep, allowVFork)); } catch (PosixException e) { throw logException("forkExec", e); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java index 28dba24183..3c87508303 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java @@ -442,10 +442,10 @@ abstract static class PosixNativeFunctionInvoker { @DowncallSignature(returnType = SINT32, argumentTypes = { POINTER, POINTER, SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, - SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, POINTER, SINT64 + SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, SINT32, POINTER, SINT64 }) abstract int fork_exec(long data, long offsets, int offsetsLen, int argsPos, int envPos, int cwdPos, int stdinRdFd, int stdinWrFd, int stdoutRdFd, int stdoutWrFd, int stderrRdFd, - int stderrWrFd, int errPipeRdFd, int errPipeWrFd, int closeFds, int restoreSignals, int callSetsid, int allowVFork, long fdsToKeep, long fdsToKeepLen); + int stderrWrFd, int errPipeRdFd, int errPipeWrFd, int closeFds, int restoreSignals, int callSetsid, int pgidToSet, int allowVFork, long fdsToKeep, long fdsToKeepLen); @DowncallSignature(returnType = VOID, argumentTypes = {POINTER, POINTER, SINT32}) abstract void call_execv(long data, long offsets, int offsetsLen); @@ -1845,7 +1845,7 @@ public void unsetenv(Object name) throws PosixException { @ExportMessage public int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd, - int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep, boolean allowVFork) throws PosixException { + int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int pgidToSet, int[] fdsToKeep, boolean allowVFork) throws PosixException { // The following strings and string arrays need to be present in the native function: // - char** of executable names ('\0'-terminated strings with an extra NULL at the end) @@ -1943,6 +1943,7 @@ public int forkExec(Object[] executables, Object[] args, Object cwd, Object[] en closeFds ? 1 : 0, restoreSignals ? 1 : 0, callSetsid ? 1 : 0, + pgidToSet, allowVFork ? 1 : 0, nativeFdsToKeep, fdsToKeep.length); if (res == -1) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java index 9c671d0757..728dbf8770 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java @@ -355,7 +355,7 @@ public record OpenPtyResult(int masterFd, int slaveFd) { public abstract void unsetenv(Object receiver, Object name) throws PosixException; public abstract int forkExec(Object receiver, Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, - int stderrReadFd, int stderrWriteFd, int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep, + int stderrReadFd, int stderrWriteFd, int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int pgidToSet, int[] fdsToKeep, boolean allowVFork) throws PosixException; // args.length must be > 0 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java index ad34385ab3..8df18530a9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java @@ -807,11 +807,11 @@ final void unsetenv(Object name, @ExportMessage final int forkExec(Object[] executables, Object[] args, Object cwd, Object[] env, int stdinReadFd, int stdinWriteFd, int stdoutReadFd, int stdoutWriteFd, int stderrReadFd, int stderrWriteFd, - int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int[] fdsToKeep, boolean allowVFork, + int errPipeReadFd, int errPipeWriteFd, boolean closeFds, boolean restoreSignals, boolean callSetsid, int pgidToSet, int[] fdsToKeep, boolean allowVFork, @CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException { checkNotInPreInitialization(); return nativeLib.forkExec(nativePosixSupport, executables, args, cwd, env, stdinReadFd, stdinWriteFd, stdoutReadFd, stdoutWriteFd, stderrReadFd, stderrWriteFd, errPipeReadFd, errPipeWriteFd, - closeFds, restoreSignals, callSetsid, fdsToKeep, allowVFork); + closeFds, restoreSignals, callSetsid, pgidToSet, fdsToKeep, allowVFork); } @ExportMessage diff --git a/graalpython/python-libposix/src/fork_exec.c b/graalpython/python-libposix/src/fork_exec.c index 51cde1464a..476f72c987 100644 --- a/graalpython/python-libposix/src/fork_exec.c +++ b/graalpython/python-libposix/src/fork_exec.c @@ -357,6 +357,7 @@ child_exec(char *const exec_array[], int errpipe_read, int errpipe_write, int close_fds, int restore_signals, int call_setsid, + int pgid_to_set, const void *child_sigmask, int *fds_to_keep, ssize_t fds_to_keep_len) @@ -446,6 +447,9 @@ child_exec(char *const exec_array[], POSIX_CALL(setsid()); #endif + if (pgid_to_set >= 0) + POSIX_CALL(setpgid(0, pgid_to_set)); + err_msg = ""; /* close FDs after executing preexec_fn, which might open FDs */ @@ -508,6 +512,7 @@ do_fork_exec(char *const exec_list[], int errPipeRdFd, int errPipeWrFd, int closeFds, int restoreSignals, int callSetsid, + int pgidToSet, const void *childSigmask, int *fdsToKeep, int64_t fdsToKeepLen) { @@ -534,6 +539,7 @@ do_fork_exec(char *const exec_list[], closeFds, restoreSignals, callSetsid, + pgidToSet, childSigmask, fdsToKeep, fdsToKeepLen ); @@ -558,6 +564,7 @@ do_fork_exec(char *const exec_list[], * (if nonzero, then errPipeWrFd must be in fdsToKeep) * restoreSignals - currently not used * callSetsid - if nonzero, the child calls setsid before exec() + * pgidToSet - if non-negative, the child calls setpgid(0, pgidToSet) before exec() * allowVFork - if nonzero, use vfork() instead of fork() where it is safe and supported * fdsToKeep, fdsToKeepLen - a sorted list of fds to keep open (the child clears their O_CLOEXEC) */ @@ -570,6 +577,7 @@ int32_t fork_exec( int32_t closeFds, int32_t restoreSignals, int32_t callSetsid, + int32_t pgidToSet, int32_t allowVFork, int32_t *fdsToKeep, int64_t fdsToKeepLen ) { @@ -588,7 +596,8 @@ int32_t fork_exec( #ifdef VFORK_USABLE const void *oldSigmask = NULL; sigset_t oldSigs; - if (allowVFork) { + /* Keep process-group setup on the normal fork path. */ + if (allowVFork && pgidToSet < 0) { sigset_t allSigs; sigfillset(&allSigs); int err = pthread_sigmask(SIG_BLOCK, &allSigs, &oldSigs); @@ -611,6 +620,7 @@ int32_t fork_exec( closeFds, restoreSignals, callSetsid, + pgidToSet, oldSigmask, fdsToKeep, fdsToKeepLen ); From 00f3a25a814a28404ca0e2294de6cd561511bbf2 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 13:30:30 +0200 Subject: [PATCH 5/7] [GR-52892] A few documentation cleanups. --- docs/user/Embedding-Build-Tools.md | 10 +-- docs/user/Embedding-Getting-Started.md | 117 +++++++++++++++++-------- docs/user/Native-Images-with-Python.md | 2 +- 3 files changed, 86 insertions(+), 43 deletions(-) diff --git a/docs/user/Embedding-Build-Tools.md b/docs/user/Embedding-Build-Tools.md index 30e6f5c01a..0526fb473b 100644 --- a/docs/user/Embedding-Build-Tools.md +++ b/docs/user/Embedding-Build-Tools.md @@ -89,7 +89,7 @@ This approach involves the following steps: - Smaller JAR/executable size since Python resources aren't embedded To use an external directory, create your GraalPy context with: -- `GraalPyResources.createContextBuilder(Path)` - Creates a context builder pointing to your external directory path +- `GraalPyResources.contextBuilder(Path)` - Creates a context builder pointing to your external directory path ## Directory Structure @@ -229,10 +229,9 @@ To customize the lock file path, configure _graalPyLockFile_ : ``` -> **Note:** This only changes the path (defaults to _${basedir}/graalpy.lock_).To generate the lock file, run the `lock-packages` goal. +> **Note:** This only changes the path (defaults to _${basedir}/graalpy.lock_). To generate the lock file, run the `lock-packages` goal. -For more information of this feature, please see the -[Python Dependency Management for Reproducible Builds](#python-dependency-management-for-reproducible-builds) section. +For more information about dependency locking, see the [Dependency Management](#dependency-management) section. ## GraalPy Gradle Plugin @@ -295,8 +294,7 @@ To customize the lock file path, configure _graalPyLockFile_: > **Note:** This only changes the path (defaults to _$rootDir/graalpy.lock_). To generate the lock file, run the `graalPyLockPackages` task. -For more information of this feature, please see the -[Python Dependency Management for Reproducible Builds](#python-dependency-management-for-reproducible-builds) section. +For more information about dependency locking, see the [Dependency Management](#dependency-management) section. ### Related Documentation diff --git a/docs/user/Embedding-Getting-Started.md b/docs/user/Embedding-Getting-Started.md index 7ce3c82c37..e97c96ec07 100644 --- a/docs/user/Embedding-Getting-Started.md +++ b/docs/user/Embedding-Getting-Started.md @@ -118,45 +118,90 @@ If you prefer Gradle, here is how to set up a new project with GraalPy embedding > **Note**: GraalPy's performance depends on the JDK you are using. For optimal performance, see the [Runtime Optimization Support](https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support) guide. -### Adding Python Dependencies +## Adding Python Dependencies To use third-party Python packages like NumPy or Requests in your embedded application: -1. Add the GraalPy Gradle plugin and configure dependencies in _app/build.gradle_: - - ```gradle - plugins { - id "java" - id "application" - id "org.graalvm.python" version "25.0.3" - } - - graalPy { - packages = ["termcolor==2.2"] - } - ``` - -2. Update your Java code to use the Python package: - - ```java - package interop; - - import org.graalvm.polyglot.*; - import org.graalvm.python.embedding.GraalPyResources; - - class App { - public static void main(String[] args) { - try (Context context = GraalPyResources.contextBuilder().build()) { - String src = """ - from termcolor import colored - colored_text = colored("hello java", "red", attrs=["reverse", "blink"]) - print(colored_text) - """; - context.eval("python", src); - } - } - } - ``` +Configure the GraalPy Maven or Gradle plugin with the packages your application needs. +The plugin installs packages into a managed virtual environment, and `GraalPyResources` configures the Python context to import from it. + +### Maven + +Add the Python embedding dependency and GraalPy Maven plugin configuration to your _pom.xml_ file: + +```xml + + + org.graalvm.python + python-embedding + 25.0.3 + + + + + + + org.graalvm.python + graalpy-maven-plugin + 25.0.3 + + + + + termcolor==2.2 + + + + process-graalpy-resources + + + + + + +``` + +### Gradle + +Add the GraalPy Gradle plugin and configure dependencies in _app/build.gradle_: + +```gradle +plugins { + id "java" + id "application" + id "org.graalvm.python" version "25.0.3" +} + +dependencies { + implementation("org.graalvm.python:python-embedding:25.0.3") +} + +graalPy { + packages = ["termcolor==2.2"] +} +``` + +Then use the Python package from Java: + +```java +package interop; + +import org.graalvm.polyglot.*; +import org.graalvm.python.embedding.GraalPyResources; + +class App { + public static void main(String[] args) { + try (Context context = GraalPyResources.contextBuilder().build()) { + String src = """ + from termcolor import colored + colored_text = colored("hello java", "red", attrs=["reverse", "blink"]) + print(colored_text) + """; + context.eval("python", src); + } + } +} +``` For complete plugin configuration options, deployment strategies, and dependency management, see [Embedding Build Tools](Embedding-Build-Tools.md). diff --git a/docs/user/Native-Images-with-Python.md b/docs/user/Native-Images-with-Python.md index d897c6dfbe..2abd76988c 100644 --- a/docs/user/Native-Images-with-Python.md +++ b/docs/user/Native-Images-with-Python.md @@ -4,7 +4,7 @@ GraalPy supports GraalVM Native Image to generate native binaries of Java applic ## Building Executables with Python -If you started with the [Maven archetype](Embedding-Getting-Started.md#maven), the generated _pom.xml_ file already includes the necessary configuration for creating a native executable using the [Maven plugin for Native Image building](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html). +If you started with the [Maven archetype](Embedding-Getting-Started.md#maven-quick-start), the generated _pom.xml_ file already includes the necessary configuration for creating a native executable using the [Maven plugin for Native Image building](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html). To build the application, run: From 67a48c06f371b6ff0819217532ef5eb19834339a Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 10 Jun 2026 14:33:52 +0200 Subject: [PATCH 6/7] [GR-48526] Stop using UnlockExperimentalVMOptions in graalpy standalone module --- graalpython/lib-graalpython/modules/standalone/__main__.py | 5 ++--- .../{native-image-resources.json => resource-config.json} | 0 2 files changed, 2 insertions(+), 3 deletions(-) rename graalpython/lib-graalpython/modules/standalone/resources/{native-image-resources.json => resource-config.json} (100%) diff --git a/graalpython/lib-graalpython/modules/standalone/__main__.py b/graalpython/lib-graalpython/modules/standalone/__main__.py index ea91502e34..b5951d505d 100644 --- a/graalpython/lib-graalpython/modules/standalone/__main__.py +++ b/graalpython/lib-graalpython/modules/standalone/__main__.py @@ -79,7 +79,7 @@ NATIVE_EXEC_LAUNCHER_FILE = f"{NATIVE_EXEC_LAUNCHER}.java" NATIVE_EXEC_LAUNCHER_TEMPLATE_PATH = f"resources/{NATIVE_EXEC_LAUNCHER_FILE}" -NATIVE_IMAGE_RESOURCES_FILE = "native-image-resources.json" +NATIVE_IMAGE_RESOURCES_FILE = "resource-config.json" NATIVE_IMAGE_RESOURCES_PATH = f"resources/{NATIVE_IMAGE_RESOURCES_FILE}" GRAALVM_URL_BASE = "https://download.oracle.com/graalvm/" @@ -436,9 +436,8 @@ def build_binary(target_dir, ni, jc, modules_path, legacy_embedding_path, launch ] cmd += [ "--no-fallback", - "-H:+UnlockExperimentalVMOptions", "-H:-CopyLanguageResources", - f"-H:ResourceConfigurationFiles={target_dir}/{NATIVE_IMAGE_RESOURCES_FILE}", + f"-H:ConfigurationFileDirectories={target_dir}", "-o", output, f"{NATIVE_EXEC_LAUNCHER}", diff --git a/graalpython/lib-graalpython/modules/standalone/resources/native-image-resources.json b/graalpython/lib-graalpython/modules/standalone/resources/resource-config.json similarity index 100% rename from graalpython/lib-graalpython/modules/standalone/resources/native-image-resources.json rename to graalpython/lib-graalpython/modules/standalone/resources/resource-config.json From 64e43e0096b1e8a4a7e002189d50168b038ade59 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 15 Jun 2026 11:43:19 +0200 Subject: [PATCH 7/7] [GR-53509] Relax process group subprocess timeout --- .../com.oracle.graal.python.test/src/tests/test_subprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py b/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py index 40d9e7f70d..80d7f523a6 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py @@ -124,7 +124,7 @@ def test_waitpid(self): def test_process_group_0(self): p = subprocess.Popen([sys.executable, "-c", "import os; print(os.getpgid(0))"], stdout=subprocess.PIPE, process_group=0) - stdout, _ = p.communicate(timeout=10) + stdout, _ = p.communicate(timeout=60) self.assertEqual(p.returncode, 0) self.assertEqual(int(stdout), p.pid)