From b9d43188e93967c3695e60f5ea6d8c2ca8a62c5d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Mar 2026 00:28:21 +0100 Subject: [PATCH 1/2] gh-145410: Add _sysconfig.get_platform() function (#146145) On Windows, sysconfig.get_platform() now gets the platform from the _sysconfig module instead of parsing sys.version string. --- Lib/sysconfig/__init__.py | 10 ++--- Lib/test/test_sysconfig.py | 21 +++++------ ...-03-18-23-54-36.gh-issue-145410.NvLWj5.rst | 3 ++ Modules/_sysconfig.c | 37 +++++++++++++++++++ Modules/clinic/_sysconfig.c.h | 28 +++++++++++++- 5 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 8ff9c99435bb1a..6507a7b5b0f695 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -665,12 +665,10 @@ def get_platform(): For other non-POSIX platforms, currently just returns :data:`sys.platform`.""" if os.name == 'nt': - if 'amd64' in sys.version.lower(): - return 'win-amd64' - if '(arm)' in sys.version.lower(): - return 'win-arm32' - if '(arm64)' in sys.version.lower(): - return 'win-arm64' + import _sysconfig + platform = _sysconfig.get_platform() + if platform: + return platform return sys.platform if os.name != "posix" or not hasattr(os, 'uname'): diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 502103ce629358..e43f91eb9238f9 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -7,6 +7,7 @@ import shutil import json import textwrap +from unittest.mock import patch from copy import copy from test import support @@ -247,19 +248,15 @@ def test_get_platform(self): self.assertIsInstance(actual_platform, str) self.assertTrue(actual_platform) - # windows XP, 32bits + # Windows os.name = 'nt' - sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' - '[MSC v.1310 32 bit (Intel)]') - sys.platform = 'win32' - self.assertEqual(get_platform(), 'win32') - - # windows XP, amd64 - os.name = 'nt' - sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' - '[MSC v.1310 32 bit (Amd64)]') - sys.platform = 'win32' - self.assertEqual(get_platform(), 'win-amd64') + with patch('_sysconfig.get_platform', create=True, return_value='win32'): + self.assertEqual(get_platform(), 'win32') + with patch('_sysconfig.get_platform', create=True, return_value='win-amd64'): + self.assertEqual(get_platform(), 'win-amd64') + sys.platform = 'test-plaform' + with patch('_sysconfig.get_platform', create=True, return_value=None): + self.assertEqual(get_platform(), 'test-plaform') # macbook os.name = 'posix' diff --git a/Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst b/Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst new file mode 100644 index 00000000000000..8d84b70097d3e2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-18-23-54-36.gh-issue-145410.NvLWj5.rst @@ -0,0 +1,3 @@ +On Windows, :func:`sysconfig.get_platform` now gets the platform from the +``_sysconfig`` module instead of parsing :data:`sys.version` string. Patch +by Victor Stinner. diff --git a/Modules/_sysconfig.c b/Modules/_sysconfig.c index 345498e670dd6a..bcb9d108174f43 100644 --- a/Modules/_sysconfig.c +++ b/Modules/_sysconfig.c @@ -81,11 +81,48 @@ _sysconfig_config_vars_impl(PyObject *module) return config; } +#ifdef MS_WINDOWS +/*[clinic input] +_sysconfig.get_platform + +Return a string that identifies the current platform. +[clinic start generated code]*/ + +static PyObject * +_sysconfig_get_platform_impl(PyObject *module) +/*[clinic end generated code: output=4ecbbe2b77633f3e input=c0b43abda44f9a01]*/ +{ +#ifdef MS_WIN64 +# if defined(_M_X64) || defined(_M_AMD64) +# define SYSCONFIG_PLATFORM "win-amd64" +# elif defined(_M_ARM64) +# define SYSCONFIG_PLATFORM "win-arm64" +# endif +#endif + +#if defined(MS_WIN32) && !defined(MS_WIN64) +# if defined(_M_IX86) +# define SYSCONFIG_PLATFORM "win32" +# elif defined(_M_ARM) +# define SYSCONFIG_PLATFORM "win-arm32" +# endif +#endif + +#ifdef SYSCONFIG_PLATFORM + return PyUnicode_FromString(SYSCONFIG_PLATFORM); +#else + Py_RETURN_NONE; +#endif +} +#endif // MS_WINDOWS + + PyDoc_STRVAR(sysconfig__doc__, "A helper for the sysconfig module."); static struct PyMethodDef sysconfig_methods[] = { _SYSCONFIG_CONFIG_VARS_METHODDEF + _SYSCONFIG_GET_PLATFORM_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_sysconfig.c.h b/Modules/clinic/_sysconfig.c.h index eb3d396298bb21..031a738cf47c7b 100644 --- a/Modules/clinic/_sysconfig.c.h +++ b/Modules/clinic/_sysconfig.c.h @@ -19,4 +19,30 @@ _sysconfig_config_vars(PyObject *module, PyObject *Py_UNUSED(ignored)) { return _sysconfig_config_vars_impl(module); } -/*[clinic end generated code: output=25d395cf02eced1f input=a9049054013a1b77]*/ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_sysconfig_get_platform__doc__, +"get_platform($module, /)\n" +"--\n" +"\n" +"Return a string that identifies the current platform."); + +#define _SYSCONFIG_GET_PLATFORM_METHODDEF \ + {"get_platform", (PyCFunction)_sysconfig_get_platform, METH_NOARGS, _sysconfig_get_platform__doc__}, + +static PyObject * +_sysconfig_get_platform_impl(PyObject *module); + +static PyObject * +_sysconfig_get_platform(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _sysconfig_get_platform_impl(module); +} + +#endif /* defined(MS_WINDOWS) */ + +#ifndef _SYSCONFIG_GET_PLATFORM_METHODDEF + #define _SYSCONFIG_GET_PLATFORM_METHODDEF +#endif /* !defined(_SYSCONFIG_GET_PLATFORM_METHODDEF) */ +/*[clinic end generated code: output=558531e148f92320 input=a9049054013a1b77]*/ From abd5246305655fc09e4e3c668c8ca09a1b0fc638 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 19 Mar 2026 02:06:17 +0100 Subject: [PATCH 2/2] gh-145805: Add `python Platforms/emscripten run` subcommand (#146051) Provides a `run` command in the Emscripten build tooling, and adds environment variable configuration for EMSDK_CACHE, CROSS_BUILD_DIR and QUIET. --- Platforms/emscripten/__main__.py | 90 ++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/Platforms/emscripten/__main__.py b/Platforms/emscripten/__main__.py index 7b5f6d2ab1bdd9..78825a52fed29b 100644 --- a/Platforms/emscripten/__main__.py +++ b/Platforms/emscripten/__main__.py @@ -77,9 +77,11 @@ def get_build_paths(cross_build_dir=None, emsdk_cache=None): LOCAL_SETUP_MARKER = b"# Generated by Platforms/wasm/emscripten.py\n" +@functools.cache def validate_emsdk_version(emsdk_cache): """Validate that the emsdk cache contains the required emscripten version.""" if emsdk_cache is None: + print("Build will use EMSDK from current environment.") return required_version = required_emscripten_version() emsdk_env = emsdk_activate_path(emsdk_cache) @@ -530,7 +532,6 @@ def configure_emscripten_python(context, working_dir): @subdir("host_dir") def make_emscripten_python(context, working_dir): """Run `make` for the emscripten/host build.""" - validate_emsdk_version(context.emsdk_cache) call( ["make", "--jobs", str(cpu_count()), "all"], env=updated_env({}, context.emsdk_cache), @@ -541,6 +542,22 @@ def make_emscripten_python(context, working_dir): subprocess.check_call([exec_script, "--version"]) +def run_emscripten_python(context): + """Run the built emscripten Python.""" + host_dir = context.build_paths["host_dir"] + exec_script = host_dir / "python.sh" + if not exec_script.is_file(): + print("Emscripten not built", file=sys.stderr) + sys.exit(1) + + args = context.args + # Strip the "--" separator if present + if args and args[0] == "--": + args = args[1:] + + os.execv(str(exec_script), [str(exec_script)] + args) + + def build_target(context): """Build one or more targets.""" steps = [] @@ -581,15 +598,31 @@ def clean_contents(context): print(f"🧹 Deleting generated {LOCAL_SETUP} ...") +def add_cross_build_dir_option(subcommand): + subcommand.add_argument( + "--cross-build-dir", + action="store", + default=os.environ.get("CROSS_BUILD_DIR"), + dest="cross_build_dir", + help=( + "Path to the cross-build directory " + f"(default: {DEFAULT_CROSS_BUILD_DIR}). " + "Can also be set with the CROSS_BUILD_DIR environment variable.", + ), + ) + + def main(): default_host_runner = "node" parser = argparse.ArgumentParser() subcommands = parser.add_subparsers(dest="subcommand") + install_emscripten_cmd = subcommands.add_parser( "install-emscripten", help="Install the appropriate version of Emscripten", ) + build = subcommands.add_parser("build", help="Build everything") build.add_argument( "target", @@ -605,24 +638,46 @@ def main(): configure_build = subcommands.add_parser( "configure-build-python", help="Run `configure` for the build Python" ) + make_mpdec_cmd = subcommands.add_parser( "make-mpdec", help="Clone mpdec repo, configure and build it for emscripten", ) + make_libffi_cmd = subcommands.add_parser( "make-libffi", help="Clone libffi repo, configure and build it for emscripten", ) + make_build = subcommands.add_parser( "make-build-python", help="Run `make` for the build Python" ) + configure_host = subcommands.add_parser( "configure-host", - help="Run `configure` for the host/emscripten (pydebug builds are inferred from the build Python)", + help=( + "Run `configure` for the host/emscripten " + "(pydebug builds are inferred from the build Python)" + ), ) + make_host = subcommands.add_parser( "make-host", help="Run `make` for the host/emscripten" ) + + run = subcommands.add_parser( + "run", + help="Run the built emscripten Python", + ) + run.add_argument( + "args", + nargs=argparse.REMAINDER, + help=( + "Arguments to pass to the emscripten Python " + "(use '--' to separate from run options)", + ) + ) + add_cross_build_dir_option(run) clean = subcommands.add_parser( "clean", help="Delete files and directories created by this script" ) @@ -651,26 +706,26 @@ def main(): subcommand.add_argument( "--quiet", action="store_true", - default=False, + default="QUIET" in os.environ, dest="quiet", - help="Redirect output from subprocesses to a log file", - ) - subcommand.add_argument( - "--cross-build-dir", - action="store", - default=None, - dest="cross_build_dir", - help="Path to the cross-build directory " - f"(default: {DEFAULT_CROSS_BUILD_DIR})", + help=( + "Redirect output from subprocesses to a log file. " + "Can also be set with the QUIET environment variable." + ), ) + add_cross_build_dir_option(subcommand) subcommand.add_argument( "--emsdk-cache", action="store", - default=None, + default=os.environ.get("EMSDK_CACHE"), dest="emsdk_cache", - help="Path to emsdk cache directory. If provided, validates that " - "the required emscripten version is installed.", + help=( + "Path to emsdk cache directory. If provided, validates that " + "the required emscripten version is installed. " + "Can also be set with the EMSDK_CACHE environment variable." + ), ) + for subcommand in configure_build, configure_host: subcommand.add_argument( "--clean", @@ -679,10 +734,12 @@ def main(): dest="clean", help="Delete any relevant directories before building", ) + for subcommand in build, configure_build, configure_host: subcommand.add_argument( "args", nargs="*", help="Extra arguments to pass to `configure`" ) + for subcommand in build, configure_host: subcommand.add_argument( "--host-runner", @@ -699,8 +756,6 @@ def main(): if context.emsdk_cache: context.emsdk_cache = Path(context.emsdk_cache).absolute() - else: - print("Build will use EMSDK from current environment.") context.build_paths = get_build_paths( context.cross_build_dir, context.emsdk_cache @@ -715,6 +770,7 @@ def main(): "configure-host": configure_emscripten_python, "make-host": make_emscripten_python, "build": build_target, + "run": run_emscripten_python, "clean": clean_contents, }