comparison third_party/emsdk/scripts/update_python.py @ 179:8d17f6e6e290

[ThirdParty] Added emsdk bazel rules that can be supported by bazel 9.0.0
author MrJuneJune <me@mrjunejune.com>
date Thu, 22 Jan 2026 21:23:17 -0800
parents
children
comparison
equal deleted inserted replaced
178:94705b5986b3 179:8d17f6e6e290
1 #!/usr/bin/env python3
2 # Copyright 2020 The Emscripten Authors. All rights reserved.
3 # Emscripten is available under two separate licenses, the MIT license and the
4 # University of Illinois/NCSA Open Source License. Both these licenses can be
5 # found in the LICENSE file.
6
7 """Updates the python binaries that we cache store at
8 http://storage.google.com/webassembly.
9
10 We only supply binaries for windows and macOS, but we do it very different ways for those two OSes.
11 On Linux, we depend on the system version of python.
12
13 Windows recipe:
14 1. Download precompiled version of python from NuGet package manager,
15 either the package "python" for AMD64, or "pythonarm64" for ARM64.
16 2. Set up pip and install pywin32 and psutil via pip for emrun to work.
17 3. Re-zip and upload to storage.google.com
18
19 macOS recipe:
20 1. Clone cpython
21 2. Use homebrew to install and configure openssl (for static linking!)
22 3. Build cpython from source and use `make install` to create archive.
23 """
24
25 import glob
26 import multiprocessing
27 import os
28 import platform
29 import shutil
30 import sys
31 import urllib.request
32 from subprocess import check_call
33
34 from zip import unzip_cmd, zip_cmd
35
36 version = '3.13.3'
37 major_minor_version = '.'.join(version.split('.')[:2]) # e.g. '3.9.2' -> '3.9'
38 # This is not part of official Python version, but a repackaging number appended by emsdk
39 # when a version of Python needs to be redownloaded.
40 revision = '0'
41
42 PSUTIL = 'psutil==7.0.0'
43
44 upload_base = 'gs://webassembly/emscripten-releases-builds/deps/'
45
46
47 # Detects whether current python interpreter architecture is ARM64 or AMD64
48 # If running AMD64 python on an ARM64 Windows, this still intentionally returns AMD64
49 def find_python_arch():
50 import sysconfig
51 arch = sysconfig.get_platform().lower()
52 if 'amd64' in arch:
53 return 'amd64'
54 if 'arm64' in arch:
55 return 'arm64'
56 raise f'Unknown Python sysconfig platform "{arch}" (neither AMD64 or ARM64)'
57
58
59 def make_python_patch():
60 python_arch = find_python_arch()
61 package_name = 'pythonarm64' if python_arch == 'arm64' else 'python'
62 download_url = f'https://www.nuget.org/api/v2/package/{package_name}/{version}'
63 filename = f'python-{version}-win-{python_arch}.zip'
64 out_filename = f'python-{version}-{revision}-win-{python_arch}.zip'
65
66 if not os.path.exists(filename):
67 print(f'Downloading python: {download_url} to {filename}')
68 urllib.request.urlretrieve(download_url, filename)
69
70 os.mkdir('python-nuget')
71 check_call(unzip_cmd() + [os.path.abspath(filename)], cwd='python-nuget')
72 os.remove(filename)
73
74 src_dir = os.path.join('python-nuget', 'tools')
75 python_exe = os.path.join(src_dir, 'python.exe')
76 check_call([python_exe, '-m', 'ensurepip', '--upgrade'])
77 check_call([python_exe, '-m', 'pip', 'install', 'pywin32==310', '--no-warn-script-location'])
78 check_call([python_exe, '-m', 'pip', 'install', PSUTIL])
79
80 check_call(zip_cmd() + [os.path.join('..', '..', out_filename), '.'], cwd=src_dir)
81 print('Created: %s' % out_filename)
82
83 # cleanup if everything went fine
84 shutil.rmtree('python-nuget')
85
86 if '--upload' in sys.argv:
87 upload_url = upload_base + out_filename
88 print('Uploading: ' + upload_url)
89 cmd = ['gsutil', 'cp', '-n', out_filename, upload_url]
90 print(' '.join(cmd))
91 check_call(cmd)
92
93
94 def build_python():
95 if sys.platform.startswith('darwin'):
96 # Take some rather drastic steps to link openssl and liblzma statically
97 # and avoid linking libintl completely.
98 osname = 'macos'
99 check_call(['brew', 'install', 'openssl', 'xz', 'pkg-config'])
100 if platform.machine() == 'x86_64':
101 prefix = '/usr/local'
102 min_macos_version = '11.0'
103 elif platform.machine() == 'arm64':
104 prefix = '/opt/homebrew'
105 min_macos_version = '11.0'
106
107 # Append '-x86_64' or '-arm64' depending on current arch. (TODO: Do
108 # this for Linux too, move this below?)
109 osname += '-' + platform.machine()
110
111 for f in [os.path.join(prefix, 'lib', 'libintl.dylib'),
112 os.path.join(prefix, 'include', 'libintl.h'),
113 os.path.join(prefix, 'opt', 'xz', 'lib', 'liblzma.dylib'),
114 os.path.join(prefix, 'opt', 'openssl', 'lib', 'libssl.dylib'),
115 os.path.join(prefix, 'opt', 'openssl', 'lib', 'libcrypto.dylib')]:
116 if os.path.exists(f):
117 os.remove(f)
118 os.environ['PKG_CONFIG_PATH'] = os.path.join(prefix, 'opt', 'openssl', 'lib', 'pkgconfig')
119 else:
120 osname = 'linux'
121
122 src_dir = 'cpython'
123 if os.path.exists(src_dir):
124 check_call(['git', 'fetch'], cwd=src_dir)
125 else:
126 check_call(['git', 'clone', 'https://github.com/python/cpython'])
127 check_call(['git', 'checkout', 'v' + version], cwd=src_dir)
128
129 env = os.environ
130 if sys.platform.startswith('darwin'):
131 # Specify the min OS version we want the build to work on
132 min_macos_version_line = '-mmacosx-version-min=' + min_macos_version
133 build_flags = min_macos_version_line + ' -Werror=partial-availability'
134 # Build against latest SDK, but issue an error if using any API that would not work on the min OS version
135 env = env.copy()
136 env['MACOSX_DEPLOYMENT_TARGET'] = min_macos_version
137 configure_args = ['CFLAGS=' + build_flags, 'CXXFLAGS=' + build_flags, 'LDFLAGS=' + min_macos_version_line]
138 else:
139 configure_args = []
140 check_call(['./configure'] + configure_args, cwd=src_dir, env=env)
141 check_call(['make', '-j', str(multiprocessing.cpu_count())], cwd=src_dir, env=env)
142 check_call(['make', 'install', 'DESTDIR=install'], cwd=src_dir, env=env)
143
144 install_dir = os.path.join(src_dir, 'install')
145
146 # Install requests module. This is needed in particular on macOS to ensure
147 # SSL certificates are available (certifi in installed and used by requests).
148 pybin = os.path.join(src_dir, 'install', 'usr', 'local', 'bin', 'python3')
149 pip = os.path.join(src_dir, 'install', 'usr', 'local', 'bin', 'pip3')
150 check_call([pybin, '-m', 'ensurepip', '--upgrade'])
151 check_call([pybin, pip, 'install', 'requests==2.32.3'])
152
153 # Install psutil module. This is needed by emrun to track when browser
154 # process quits.
155 check_call([pybin, pip, 'install', PSUTIL])
156
157 dirname = 'python-%s-%s' % (version, revision)
158 if os.path.isdir(dirname):
159 print('Erasing old build directory ' + dirname)
160 shutil.rmtree(dirname)
161 os.rename(os.path.join(install_dir, 'usr', 'local'), dirname)
162 tarball = 'python-%s-%s-%s.tar.gz' % (version, revision, osname)
163 shutil.rmtree(os.path.join(dirname, 'lib', 'python' + major_minor_version, 'test'))
164 shutil.rmtree(os.path.join(dirname, 'include'))
165 for lib in glob.glob(os.path.join(dirname, 'lib', 'lib*.a')):
166 os.remove(lib)
167 check_call(['tar', 'zcvf', tarball, dirname])
168
169 print('Created: %s' % tarball)
170 if '--upload' in sys.argv:
171 print('Uploading: ' + upload_base + tarball)
172 check_call(['gsutil', 'cp', '-n', tarball, upload_base + tarball])
173
174
175 def main():
176 if sys.platform.startswith('win') or '--win32' in sys.argv:
177 make_python_patch()
178 else:
179 build_python()
180 return 0
181
182
183 if __name__ == '__main__':
184 sys.exit(main())