diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 028a7861f36798..e0778000b1c817 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -648,8 +648,11 @@ functions. .. note:: If specified, *env* must provide any variables required for the program to - execute. On Windows, in order to run a `side-by-side assembly`_ the - specified *env* **must** include a valid :envvar:`SystemRoot`. + execute. On Windows, in order to run a `side-by-side assembly`_, or a + Python program using the :mod:`socket` module (or another module that + depends on it, such as :mod:`asyncio`), the specified *env* **must** + include a valid :envvar:`!SystemRoot`; omitting it may emit a + :exc:`RuntimeWarning`. .. _side-by-side assembly: https://en.wikipedia.org/wiki/Side-by-Side_Assembly diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 54c2eb515b60da..1fcf8d8f224bdc 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1545,6 +1545,10 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, if cwd is not None: cwd = os.fsdecode(cwd) + if env is not None and not any( + k.upper() == 'SYSTEMROOT' and v for k, v in env.items()): + warnings.warn("env lacks a valid 'SystemRoot'.", RuntimeWarning) + sys.audit("subprocess.Popen", executable, args, cwd, env) # Start the process diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index ca35804fb36076..f94fe1e0dab60b 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -857,13 +857,14 @@ def test_one_environment_variable(self): 'sys.stdout.write("fruit="+os.getenv("fruit"))'] if sys.platform == "win32": cmd = ["CMD", "/c", "SET", "fruit"] - with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=newenv) as p: - stdout, stderr = p.communicate() - if p.returncode and support.verbose: - print("STDOUT:", stdout.decode("ascii", "replace")) - print("STDERR:", stderr.decode("ascii", "replace")) - self.assertEqual(p.returncode, 0) - self.assertEqual(stdout.strip(), b"fruit=orange") + with warnings_helper.check_warnings(('env.*SystemRoot', RuntimeWarning), quiet=True): + with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=newenv) as p: + stdout, stderr = p.communicate() + if p.returncode and support.verbose: + print("STDOUT:", stdout.decode("ascii", "replace")) + print("STDERR:", stderr.decode("ascii", "replace")) + self.assertEqual(p.returncode, 0) + self.assertEqual(stdout.strip(), b"fruit=orange") def test_invalid_cmd(self): # null character in the command name @@ -1764,7 +1765,8 @@ def test_run_with_an_empty_env(self): args = [sys.executable, "-c", 'pass'] # Ignore subprocess errors - we only care that the API doesn't # raise an OSError - subprocess.run(args, env={}) + with warnings_helper.check_warnings(('env.*SystemRoot', RuntimeWarning), quiet=True): + subprocess.run(args, env={}) def test_capture_output(self): cp = self.run_python(("import sys;" @@ -3567,7 +3569,8 @@ def test_issue31471(self): class BadEnv(dict): keys = None with self.assertRaises(TypeError): - subprocess.Popen(ZERO_RETURN_CMD, env=BadEnv()) + with warnings_helper.check_warnings(('env.*SystemRoot', RuntimeWarning), quiet=True): + subprocess.Popen(ZERO_RETURN_CMD, env=BadEnv()) def test_close_fds(self): # close file descriptors diff --git a/Misc/NEWS.d/next/Windows/2025-05-21-10-48-38.gh-issue-118234.x1N6xT.rst b/Misc/NEWS.d/next/Windows/2025-05-21-10-48-38.gh-issue-118234.x1N6xT.rst new file mode 100644 index 00000000000000..241be957c57d13 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-05-21-10-48-38.gh-issue-118234.x1N6xT.rst @@ -0,0 +1,4 @@ +Document that :envvar:`!SystemRoot` is also required for a Python subprocess +that uses :mod:`socket` or :mod:`asyncio` on Windows, and add a +:exc:`RuntimeWarning` if an *env* supplied to :class:`subprocess.Popen` +lacks it. Patch by John Keith Hohm.