Skip to content

Commit 3b18af9

Browse files
authored
gh-127629: Add ctypes to the Emscripten build (#127683)
Adds tooling to build libffi and add ctypes to the stdlib for Emscripten.
1 parent 5c89adf commit 3b18af9

File tree

4 files changed

+76
-11
lines changed

4 files changed

+76
-11
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Emscripten builds now include ctypes support.

Tools/wasm/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ or you can break it out into four separate steps:
6161
```shell
6262
python Tools/wasm/emscripten configure-build-python
6363
python Tools/wasm/emscripten make-build-python
64+
python Tools/wasm/emscripten make-libffi
6465
python Tools/wasm/emscripten configure-host
6566
python Tools/wasm/emscripten make-host
6667
```

Tools/wasm/emscripten/__main__.py

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010
import sysconfig
1111
import tempfile
12+
from urllib.request import urlopen
1213
from pathlib import Path
1314
from textwrap import dedent
1415

@@ -22,9 +23,13 @@
2223
CHECKOUT = EMSCRIPTEN_DIR.parent.parent.parent
2324

2425
CROSS_BUILD_DIR = CHECKOUT / "cross-build"
25-
BUILD_DIR = CROSS_BUILD_DIR / "build"
26+
NATIVE_BUILD_DIR = CROSS_BUILD_DIR / "build"
2627
HOST_TRIPLE = "wasm32-emscripten"
27-
HOST_DIR = CROSS_BUILD_DIR / HOST_TRIPLE
28+
29+
DOWNLOAD_DIR = CROSS_BUILD_DIR / HOST_TRIPLE / "build"
30+
HOST_BUILD_DIR = CROSS_BUILD_DIR / HOST_TRIPLE / "build"
31+
HOST_DIR = HOST_BUILD_DIR / "python"
32+
PREFIX_DIR = CROSS_BUILD_DIR / HOST_TRIPLE / "prefix"
2833

2934
LOCAL_SETUP = CHECKOUT / "Modules" / "Setup.local"
3035
LOCAL_SETUP_MARKER = "# Generated by Tools/wasm/emscripten.py\n".encode("utf-8")
@@ -118,16 +123,16 @@ def build_platform():
118123

119124
def build_python_path():
120125
"""The path to the build Python binary."""
121-
binary = BUILD_DIR / "python"
126+
binary = NATIVE_BUILD_DIR / "python"
122127
if not binary.is_file():
123128
binary = binary.with_suffix(".exe")
124129
if not binary.is_file():
125-
raise FileNotFoundError("Unable to find `python(.exe)` in " f"{BUILD_DIR}")
130+
raise FileNotFoundError("Unable to find `python(.exe)` in " f"{NATIVE_BUILD_DIR}")
126131

127132
return binary
128133

129134

130-
@subdir(BUILD_DIR, clean_ok=True)
135+
@subdir(NATIVE_BUILD_DIR, clean_ok=True)
131136
def configure_build_python(context, working_dir):
132137
"""Configure the build/host Python."""
133138
if LOCAL_SETUP.exists():
@@ -143,7 +148,7 @@ def configure_build_python(context, working_dir):
143148
call(configure, quiet=context.quiet)
144149

145150

146-
@subdir(BUILD_DIR)
151+
@subdir(NATIVE_BUILD_DIR)
147152
def make_build_python(context, working_dir):
148153
"""Make/build the build Python."""
149154
call(["make", "--jobs", str(cpu_count()), "all"], quiet=context.quiet)
@@ -159,6 +164,23 @@ def make_build_python(context, working_dir):
159164
print(f"🎉 {binary} {version}")
160165

161166

167+
@subdir(HOST_BUILD_DIR, clean_ok=True)
168+
def make_emscripten_libffi(context, working_dir):
169+
shutil.rmtree(working_dir / "libffi-3.4.6", ignore_errors=True)
170+
with tempfile.NamedTemporaryFile(suffix=".tar.gz") as tmp_file:
171+
with urlopen(
172+
"https://github.com/libffi/libffi/releases/download/v3.4.6/libffi-3.4.6.tar.gz"
173+
) as response:
174+
shutil.copyfileobj(response, tmp_file)
175+
shutil.unpack_archive(tmp_file.name, working_dir)
176+
call(
177+
[EMSCRIPTEN_DIR / "make_libffi.sh"],
178+
env=updated_env({"PREFIX": PREFIX_DIR}),
179+
cwd=working_dir / "libffi-3.4.6",
180+
quiet=context.quiet,
181+
)
182+
183+
162184
@subdir(HOST_DIR, clean_ok=True)
163185
def configure_emscripten_python(context, working_dir):
164186
"""Configure the emscripten/host build."""
@@ -168,7 +190,7 @@ def configure_emscripten_python(context, working_dir):
168190

169191
emscripten_build_dir = working_dir.relative_to(CHECKOUT)
170192

171-
python_build_dir = BUILD_DIR / "build"
193+
python_build_dir = NATIVE_BUILD_DIR / "build"
172194
lib_dirs = list(python_build_dir.glob("lib.*"))
173195
assert (
174196
len(lib_dirs) == 1
@@ -183,12 +205,18 @@ def configure_emscripten_python(context, working_dir):
183205
sysconfig_data += "-pydebug"
184206

185207
host_runner = context.host_runner
186-
env_additions = {"CONFIG_SITE": config_site, "HOSTRUNNER": host_runner}
208+
pkg_config_path_dir = (PREFIX_DIR / "lib/pkgconfig/").resolve()
209+
env_additions = {
210+
"CONFIG_SITE": config_site,
211+
"HOSTRUNNER": host_runner,
212+
"EM_PKG_CONFIG_PATH": str(pkg_config_path_dir),
213+
}
187214
build_python = os.fsdecode(build_python_path())
188215
configure = [
189216
"emconfigure",
190217
os.path.relpath(CHECKOUT / "configure", working_dir),
191218
"CFLAGS=-DPY_CALL_TRAMPOLINE -sUSE_BZIP2",
219+
"PKG_CONFIG=pkg-config",
192220
f"--host={HOST_TRIPLE}",
193221
f"--build={build_platform()}",
194222
f"--with-build-python={build_python}",
@@ -197,7 +225,7 @@ def configure_emscripten_python(context, working_dir):
197225
"--disable-ipv6",
198226
"--enable-big-digits=30",
199227
"--enable-wasm-dynamic-linking",
200-
f"--prefix={HOST_DIR}",
228+
f"--prefix={PREFIX_DIR}",
201229
]
202230
if pydebug:
203231
configure.append("--with-pydebug")
@@ -264,6 +292,7 @@ def build_all(context):
264292
steps = [
265293
configure_build_python,
266294
make_build_python,
295+
make_emscripten_libffi,
267296
configure_emscripten_python,
268297
make_emscripten_python,
269298
]
@@ -292,18 +321,30 @@ def main():
292321
configure_build = subcommands.add_parser(
293322
"configure-build-python", help="Run `configure` for the " "build Python"
294323
)
324+
make_libffi_cmd = subcommands.add_parser(
325+
"make-libffi", help="Clone libffi repo, configure and build it for emscripten"
326+
)
295327
make_build = subcommands.add_parser(
296328
"make-build-python", help="Run `make` for the build Python"
297329
)
298330
configure_host = subcommands.add_parser(
299331
"configure-host",
300332
help="Run `configure` for the host/emscripten (pydebug builds are inferred from the build Python)",
301333
)
302-
make_host = subcommands.add_parser("make-host", help="Run `make` for the host/emscripten")
334+
make_host = subcommands.add_parser(
335+
"make-host", help="Run `make` for the host/emscripten"
336+
)
303337
clean = subcommands.add_parser(
304338
"clean", help="Delete files and directories created by this script"
305339
)
306-
for subcommand in build, configure_build, make_build, configure_host, make_host:
340+
for subcommand in (
341+
build,
342+
configure_build,
343+
make_libffi_cmd,
344+
make_build,
345+
configure_host,
346+
make_host,
347+
):
307348
subcommand.add_argument(
308349
"--quiet",
309350
action="store_true",
@@ -336,6 +377,7 @@ def main():
336377
context = parser.parse_args()
337378

338379
dispatch = {
380+
"make-libffi": make_emscripten_libffi,
339381
"configure-build-python": configure_build_python,
340382
"make-build-python": make_build_python,
341383
"configure-host": configure_emscripten_python,

Tools/wasm/emscripten/make_libffi.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
set +e
3+
4+
export CFLAGS="-O2 -fPIC -DWASM_BIGINT"
5+
export CXXFLAGS="$CFLAGS"
6+
7+
# Build paths
8+
export CPATH="$PREFIX/include"
9+
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig"
10+
export EM_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
11+
12+
# Specific variables for cross-compilation
13+
export CHOST="wasm32-unknown-linux" # wasm32-unknown-emscripten
14+
15+
emconfigure ./configure --host=$CHOST --prefix="$PREFIX" --enable-static --disable-shared --disable-dependency-tracking \
16+
--disable-builddir --disable-multi-os-directory --disable-raw-api --disable-docs
17+
18+
make install
19+
# Some forgotten headers?
20+
cp fficonfig.h $PREFIX/include/
21+
cp include/ffi_common.h $PREFIX/include/

0 commit comments

Comments
 (0)