Skip to content
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

Update Python to 3.13.0 and add support to Windows on AMD64. #1477

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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.13.0 or above.
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 we want to drop support for python 3.9 just because emsdk bundles a bleeding edge version. I think the idea is that we still want to support the system version of python on older/stable linux releases.

- `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.0-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.0-0_64bit\python.exe" (
set EMSDK_PY="%~dp0python\3.13.0-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.0-0_64bit\python.exe",
"python\3.9.2-1_64bit\python.exe",
"python\3.9.2-nuget_64bit\python.exe"
)
Expand Down
6 changes: 1 addition & 5 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
42 changes: 40 additions & 2 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.0",
"bitness": 64,
"arch": "x86_64",
"windows_url": "python-3.13.0-0-win-amd64.zip",
"activated_cfg": "PYTHON='%installation_dir%/python.exe'",
"activated_env": "EMSDK_PYTHON=%installation_dir%/python.exe"
},
{
"id": "python",
"version": "3.13.0",
"bitness": 64,
"arch": "arm64",
"windows_url": "python-3.13.0-0-win-arm64.zip",
"activated_cfg": "PYTHON='%installation_dir%/python.exe'",
"activated_env": "EMSDK_PYTHON=%installation_dir%/python.exe"
},
{
"id": "python",
"version": "3.13.0",
"bitness": 64,
"arch": "x86_64",
"macos_url": "python-3.13.0-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.0",
"bitness": 64,
"arch": "arm64",
"macos_url": "python-3.13.0-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": "java",
"version": "8.152",
Expand Down Expand Up @@ -368,13 +406,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.0-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.0-64bit", "llvm-git-main-64bit", "node-20.18.0-64bit", "emscripten-main-64bit", "binaryen-main-64bit"],
"os": "macos"
},
{
Expand Down
60 changes: 34 additions & 26 deletions scripts/update_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
We only supply binaries for windows and macOS, but we do it very different ways for those two OSes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps add another sentence here: "On linux, we simply 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
2. Use homebrew to install and configure openssl (for static linking!)
3. Build cpython from source and use `make install` to create archive.

Raspberry Pi Debian 12 (Bookworm):
1. Before calling this script, run "sudo apt install libssl-dev", or otherwise
Python won't be able to use SSL.
"""

import glob
Expand All @@ -32,29 +36,35 @@
from subprocess import check_call
from zip import unzip_cmd, zip_cmd

version = '3.9.2'
version = '3.13.0'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to upgrade to such a recent version? i.e. what as the first version of which arm64 packages are available.

The slight downside of using a bleeding edge version of python like this is that we accidentally start depending of very new python features that folks on older/stable versions might not have.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was pondering about this too. My thinking here is that since we do these updates so rarely, maybe we would for once update to a modern version.

Python 3.9, 3.10 and 3.11 will no longer receive bugfixes to any issues one might run into. I don't think it makes sense to update to any of these versions, even if one of these would be the earliest ones to support Windows on ARM64.

Python 3.14 is in feature development. So we definitely will not want to update to that branch.

Python 3.12 and Python 3.13 have finished feature development and are actively receiving bufixes. It feels that either of these would be the ones to focus on.

I.e.

image

I picked 3.13 since it is newer, and I could not find any mentions of OS support cutoff differences between 3.12 and 3.13.

we accidentally start depending of very new python features that folks on older/stable versions might not have.

3.13 is stable. (or rather, actually, every python version is broken in different ways. But Emscripten test suite is fortunately evolved to be good at finding ways that Python works or doesn't work out)

We do bundle python for the very purpose to normalize which version we provide to people? So this covers Windows and macOS users. On Linuxes where this currently is not the case, I understand updating to a newer version should be possible easier with their package managers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I suppose the other alternative could be 3.12.7 . At the end of the day I don't feel that strongly, but generally would strive to standardize to latest version, especially if Emscripten test suite will pass with 3.13.0. (Testing that out on Windows, although I am seeing Windows failures already before this update, making it a bit more time consuming to compare)

Copy link
Collaborator

Choose a reason for hiding this comment

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

sgtm

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==6.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}')
Expand All @@ -64,19 +74,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==308'])
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 @@ -94,7 +102,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 = '10.13'
elif platform.machine() == 'arm64':
prefix = '/opt/homebrew'
min_macos_version = '11.0'
Expand Down Expand Up @@ -149,7 +157,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