Skip to content

Update Python to 3.13.3 and add support to Windows on ARM64. #1477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion emsdk
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
8 changes: 8 additions & 0 deletions emsdk.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
1 change: 1 addition & 0 deletions emsdk.ps1
Original file line number Diff line number Diff line change
@@ -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"
)
Expand Down
15 changes: 5 additions & 10 deletions emsdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,7 @@ def exit_with_error(msg):
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:
Expand Down Expand Up @@ -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:
Expand Down
48 changes: 43 additions & 5 deletions emsdk_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%",
Expand Down Expand Up @@ -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"],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can also delete the python-3.9.2 entries in this file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we did that, then people cannot manually choose to install the previous python 3.9.2 to e.g. compare if updating python to 3.13.3 was the root cause of a regression.

i.e. with retaining that, users can still install combinations of the individual tools that they like.

"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"
},
{
Expand All @@ -381,23 +419,23 @@
{
"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"
},
{
"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"
},
{
"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"
}
Expand Down
63 changes: 36 additions & 27 deletions scripts/update_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,27 +33,35 @@
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the second part of this comment.

Do we really want to report AMD64 if we are running amd64 python on arm64 windows?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is important because if one is running an Intel x64 version of Python (e.g. under Rosetta on Mac), then pip will install x64 versions of tools, not ARM64. So then the packager would accidentally pip install x64 versions of tools, but output an -arm64 artifact name.

So in terms of packaging, we should only report ARM64, if truly running on ARM64 version of the interpreter, to avoid any chance of mistake.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so effectively this script cannot cross build the arm64 python package.

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}')
Expand All @@ -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]

os.mkdir(os.path.join('python-nuget', 'lib'))
shutil.move(os.path.join('pywin32', 'PLATLIB'), os.path.join('python-nuget', 'toolss', 'Lib', 'site-packages'))
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])

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
Expand All @@ -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'
Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand Down