From d07c793415007a02f5006a3f5c749550edc75468 Mon Sep 17 00:00:00 2001 From: juj Date: Wed, 28 May 2025 23:20:03 +0300 Subject: [PATCH] Update Python to 3.13.3 and add support to Windows on ARM64. (#1477) Update Python to 3.13.0 and add support to Windows on AMD64. Rewrite packaging of pywin32 because it is no longer shipped via separate binary installers, but has migrated to use pip. --- README.md | 2 +- emsdk | 5 +++- emsdk.bat | 8 +++++ emsdk.ps1 | 1 + emsdk.py | 15 ++++------ emsdk_manifest.json | 48 ++++++++++++++++++++++++++---- scripts/update_python.py | 63 +++++++++++++++++++++++----------------- 7 files changed, 98 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index c98222e..ef6615e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ https://emscripten.org/docs/building_from_source/toolchain_what_is_needed.html. ### Linux -- `python`: Version 3.9.2 or above. +- `python`: Version 3.9.2 or above. Version 3.13.3 or a newer 3.13.x is recommended. - `java`: For running closure compiler (optional) The emsdk pre-compiled binaries are built against Ubuntu/Focal 20.04 LTS and diff --git a/emsdk b/emsdk index 78c0288..a62ab25 100755 --- a/emsdk +++ b/emsdk @@ -8,7 +8,10 @@ # First look for python bundled in Emsdk if [ -z "$EMSDK_PYTHON" ]; then - PYTHON3="$(dirname "$0")/python/3.9.2-1_64bit/bin/python3" + PYTHON3="$(dirname "$0")/python/3.13.3-0_64bit/bin/python3" + if [ ! -f "$PYTHON3" ]; then + PYTHON3="$(dirname "$0")/python/3.9.2-1_64bit/bin/python3" + fi if [ -f "$PYTHON3" ]; then EMSDK_PYTHON="$PYTHON3" diff --git a/emsdk.bat b/emsdk.bat index d0c599d..6122e91 100644 --- a/emsdk.bat +++ b/emsdk.bat @@ -7,6 +7,14 @@ setlocal :: When using our bundled python we never want the users :: PYTHONHOME or PYTHONPATH :: https://github.com/emscripten-core/emsdk/issues/598 + +if exist "%~dp0python\3.13.3-0_64bit\python.exe" ( + set EMSDK_PY="%~dp0python\3.13.3-0_64bit\python.exe" + set PYTHONHOME= + set PYTHONPATH= + goto end +) + if exist "%~dp0python\3.9.2-1_64bit\python.exe" ( set EMSDK_PY="%~dp0python\3.9.2-1_64bit\python.exe" set PYTHONHOME= diff --git a/emsdk.ps1 b/emsdk.ps1 index e9e6008..81b798a 100644 --- a/emsdk.ps1 +++ b/emsdk.ps1 @@ -1,6 +1,7 @@ $ScriptDirectory = Split-Path -parent $PSCommandPath $PythonLocations = $( + "python\3.13.3-0_64bit\python.exe", "python\3.9.2-1_64bit\python.exe", "python\3.9.2-nuget_64bit\python.exe" ) diff --git a/emsdk.py b/emsdk.py index f01ebf5..22c0838 100644 --- a/emsdk.py +++ b/emsdk.py @@ -143,11 +143,7 @@ if machine.startswith('x64') or machine.startswith('amd64') or machine.startswit elif machine.endswith('86'): ARCH = 'x86' elif machine.startswith('aarch64') or machine.lower().startswith('arm64'): - if WINDOWS: - errlog('No support for Windows on Arm, fallback to x64') - ARCH = 'x86_64' - else: - ARCH = 'arm64' + ARCH = 'arm64' elif machine.startswith('arm'): ARCH = 'arm' else: @@ -988,17 +984,16 @@ def cmake_configure(generator, build_root, src_root, build_type, extra_cmake_arg generator = [] cmdline = ['cmake'] + generator + ['-DCMAKE_BUILD_TYPE=' + build_type, '-DPYTHON_EXECUTABLE=' + sys.executable] - # Target macOS 10.14 at minimum, to support widest range of Mac devices - # from "Early 2008" and newer: - # https://en.wikipedia.org/wiki/MacBook_(2006-2012)#Supported_operating_systems - cmdline += ['-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14'] + # Target macOS 11.0 Big Sur at minimum, to support older Mac devices. + # See https://en.wikipedia.org/wiki/MacOS#Hardware_compatibility for min-spec details. + cmdline += ['-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0'] cmdline += extra_cmake_args + [src_root] print('Running CMake: ' + str(cmdline)) # Specify the deployment target also as an env. var, since some Xcode versions # read this instead of the CMake field. - os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.14' + os.environ['MACOSX_DEPLOYMENT_TARGET'] = '11.0' def quote_parens(x): if ' ' in x: diff --git a/emsdk_manifest.json b/emsdk_manifest.json index fd43f59..405f442 100644 --- a/emsdk_manifest.json +++ b/emsdk_manifest.json @@ -192,6 +192,44 @@ "activated_cfg": "PYTHON='%installation_dir%/bin/python3'", "activated_env": "EMSDK_PYTHON=%installation_dir%/bin/python3;SSL_CERT_FILE=%installation_dir%/lib/python3.9/site-packages/certifi/cacert.pem" }, + + { + "id": "python", + "version": "3.13.3", + "bitness": 64, + "arch": "x86_64", + "windows_url": "python-3.13.3-0-win-amd64.zip", + "activated_cfg": "PYTHON='%installation_dir%/python.exe'", + "activated_env": "EMSDK_PYTHON=%installation_dir%/python.exe" + }, + { + "id": "python", + "version": "3.13.3", + "bitness": 64, + "arch": "arm64", + "windows_url": "python-3.13.3-0-win-arm64.zip", + "activated_cfg": "PYTHON='%installation_dir%/python.exe'", + "activated_env": "EMSDK_PYTHON=%installation_dir%/python.exe" + }, + { + "id": "python", + "version": "3.13.3", + "bitness": 64, + "arch": "x86_64", + "macos_url": "python-3.13.3-0-macos-x86_64.tar.gz", + "activated_cfg": "PYTHON='%installation_dir%/bin/python3'", + "activated_env": "EMSDK_PYTHON=%installation_dir%/bin/python3;SSL_CERT_FILE=%installation_dir%/lib/python3.13/site-packages/certifi/cacert.pem" + }, + { + "id": "python", + "version": "3.13.3", + "bitness": 64, + "arch": "arm64", + "macos_url": "python-3.13.3-0-macos-arm64.tar.gz", + "activated_cfg": "PYTHON='%installation_dir%/bin/python3'", + "activated_env": "EMSDK_PYTHON=%installation_dir%/bin/python3;SSL_CERT_FILE=%installation_dir%/lib/python3.13/site-packages/certifi/cacert.pem" + }, + { "id": "emscripten", "version": "tag-%tag%", @@ -350,13 +388,13 @@ { "version": "main", "bitness": 64, - "uses": ["python-3.9.2-nuget-64bit", "llvm-git-main-64bit", "node-20.18.0-64bit", "emscripten-main-64bit", "binaryen-main-64bit"], + "uses": ["python-3.13.3-64bit", "llvm-git-main-64bit", "node-20.18.0-64bit", "emscripten-main-64bit", "binaryen-main-64bit"], "os": "win" }, { "version": "main", "bitness": 64, - "uses": ["python-3.9.2-64bit", "llvm-git-main-64bit", "node-20.18.0-64bit", "emscripten-main-64bit", "binaryen-main-64bit"], + "uses": ["python-3.13.3-64bit", "llvm-git-main-64bit", "node-20.18.0-64bit", "emscripten-main-64bit", "binaryen-main-64bit"], "os": "macos" }, { @@ -381,7 +419,7 @@ { "version": "releases-%releases-tag%", "bitness": 64, - "uses": ["node-20.18.0-64bit", "python-3.9.2-64bit", "releases-%releases-tag%-64bit"], + "uses": ["node-20.18.0-64bit", "python-3.13.3-64bit", "releases-%releases-tag%-64bit"], "os": "macos", "arch": "x86_64", "custom_install_script": "emscripten_npm_install" @@ -389,7 +427,7 @@ { "version": "releases-%releases-tag%", "bitness": 64, - "uses": ["node-20.18.0-64bit", "python-3.9.2-64bit", "releases-%releases-tag%-64bit"], + "uses": ["node-20.18.0-64bit", "python-3.13.3-64bit", "releases-%releases-tag%-64bit"], "os": "macos", "arch": "arm64", "custom_install_script": "emscripten_npm_install" @@ -397,7 +435,7 @@ { "version": "releases-%releases-tag%", "bitness": 64, - "uses": ["node-20.18.0-64bit", "python-3.9.2-nuget-64bit", "releases-%releases-tag%-64bit"], + "uses": ["node-20.18.0-64bit", "python-3.13.3-64bit", "releases-%releases-tag%-64bit"], "os": "win", "custom_install_script": "emscripten_npm_install" } diff --git a/scripts/update_python.py b/scripts/update_python.py index a37b701..98273f1 100755 --- a/scripts/update_python.py +++ b/scripts/update_python.py @@ -8,12 +8,13 @@ http://storage.google.com/webassembly. We only supply binaries for windows and macOS, but we do it very different ways for those two OSes. +On Linux, we depend on the system version of python. Windows recipe: - 1. Download the "embeddable zip file" version of python from python.org - 2. Remove .pth file to work around https://bugs.python.org/issue34841 - 3. Download and install pywin32 in the `site-packages` directory - 4. Re-zip and upload to storage.google.com + 1. Download precompiled version of python from NuGet package manager, + either the package "python" for AMD64, or "pythonarm64" for ARM64. + 2. Set up pip and install pywin32 and psutil via pip for emrun to work. + 3. Re-zip and upload to storage.google.com macOS recipe: 1. Clone cpython @@ -32,27 +33,35 @@ import sys from subprocess import check_call from zip import unzip_cmd, zip_cmd -version = '3.9.2' +version = '3.13.3' major_minor_version = '.'.join(version.split('.')[:2]) # e.g. '3.9.2' -> '3.9' -download_url = 'https://www.nuget.org/api/v2/package/python/%s' % version # This is not part of official Python version, but a repackaging number appended by emsdk # when a version of Python needs to be redownloaded. -revision = '4' +revision = '0' -pywin32_version = '227' -pywin32_base = 'https://github.com/mhammond/pywin32/releases/download/b%s/' % pywin32_version +PSUTIL = 'psutil==7.0.0' upload_base = 'gs://webassembly/emscripten-releases-builds/deps/' +# Detects whether current python interpreter architecture is ARM64 or AMD64 +# If running AMD64 python on an ARM64 Windows, this still intentionally returns AMD64 +def find_python_arch(): + import sysconfig + arch = sysconfig.get_platform().lower() + if 'amd64' in arch: + return 'amd64' + if 'arm64' in arch: + return 'arm64' + raise f'Unknown Python sysconfig platform "{arch}" (neither AMD64 or ARM64)' + + def make_python_patch(): - pywin32_filename = 'pywin32-%s.win-amd64-py%s.exe' % (pywin32_version, major_minor_version) - filename = 'python-%s-amd64.zip' % (version) - out_filename = 'python-%s-%s-amd64+pywin32.zip' % (version, revision) - if not os.path.exists(pywin32_filename): - url = pywin32_base + pywin32_filename - print('Downloading pywin32: ' + url) - urllib.request.urlretrieve(url, pywin32_filename) + python_arch = find_python_arch() + package_name = 'pythonarm64' if python_arch == 'arm64' else 'python' + download_url = f'https://www.nuget.org/api/v2/package/{package_name}/{version}' + filename = f'python-{version}-win-{python_arch}.zip' + out_filename = f'python-{version}-{revision}-win-{python_arch}.zip' if not os.path.exists(filename): print(f'Downloading python: {download_url} to {filename}') @@ -62,19 +71,17 @@ def make_python_patch(): check_call(unzip_cmd() + [os.path.abspath(filename)], cwd='python-nuget') os.remove(filename) - os.mkdir('pywin32') - rtn = subprocess.call(unzip_cmd() + [os.path.abspath(pywin32_filename)], cwd='pywin32') - assert rtn in [0, 1] + src_dir = os.path.join('python-nuget', 'tools') + python_exe = os.path.join(src_dir, 'python.exe') + check_call([python_exe, '-m', 'ensurepip', '--upgrade']) + check_call([python_exe, '-m', 'pip', 'install', 'pywin32==310', '--no-warn-script-location']) + check_call([python_exe, '-m', 'pip', 'install', PSUTIL]) - os.mkdir(os.path.join('python-nuget', 'lib')) - shutil.move(os.path.join('pywin32', 'PLATLIB'), os.path.join('python-nuget', 'toolss', 'Lib', 'site-packages')) - - check_call(zip_cmd() + [os.path.join('..', '..', out_filename), '.'], cwd='python-nuget/tools') + check_call(zip_cmd() + [os.path.join('..', '..', out_filename), '.'], cwd=src_dir) print('Created: %s' % out_filename) # cleanup if everything went fine shutil.rmtree('python-nuget') - shutil.rmtree('pywin32') if '--upload' in sys.argv: upload_url = upload_base + out_filename @@ -92,7 +99,7 @@ def build_python(): check_call(['brew', 'install', 'openssl', 'xz', 'pkg-config']) if platform.machine() == 'x86_64': prefix = '/usr/local' - min_macos_version = '10.11' + min_macos_version = '11.0' elif platform.machine() == 'arm64': prefix = '/opt/homebrew' min_macos_version = '11.0' @@ -113,7 +120,9 @@ def build_python(): osname = 'linux' src_dir = 'cpython' - if not os.path.exists(src_dir): + if os.path.exists(src_dir): + check_call(['git', 'fetch'], cwd=src_dir) + else: check_call(['git', 'clone', 'https://github.com/python/cpython']) check_call(['git', 'checkout', 'v' + version], cwd=src_dir) @@ -143,7 +152,7 @@ def build_python(): # Install psutil module. This is needed by emrun to track when browser # process quits. - check_call([pybin, pip, 'install', 'psutil']) + check_call([pybin, pip, 'install', PSUTIL]) dirname = 'python-%s-%s' % (version, revision) if os.path.isdir(dirname):