Skip to content

Commit d5b3d56

Browse files
authored
Build and test Python in CI (WebAssembly#667)
In general wasi-libc's test suite is quite bare-bones. In an effort to help increase test coverage this commit adds a job to CI which builds Python with a source-built version of wasi-libc and runs its test suite. Also in general I don't know Python, nor its build system, nor its WASI port, nor its CI configuration. Here the build system is copied over from Python's own CI configuration and adapted to run in this repository. Locally this job takes ~7 minutes. This will become the longest step in CI, but personally I feel that it's very much worth it to get the drastically increased test coverage.
1 parent 190651b commit d5b3d56

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

.github/workflows/main.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,43 @@ jobs:
213213
submodules: true
214214
- run: make bindings
215215
- run: git diff --exit-code
216+
217+
# Currently wasi-libc's test suite is pretty bare-bones. To try to increase
218+
# test coverage build Python in CI as well with a fresh copy of wasi-libc.
219+
# This involves a bit of configuration to get all the stars to align, but in
220+
# the end this runs at least some Python tests against a from-source-built
221+
# copy of wasi-libc.
222+
python:
223+
name: Test Python
224+
runs-on: ubuntu-latest
225+
env: ${{ matrix.env || fromJSON('{}') }}
226+
strategy:
227+
fail-fast: false
228+
matrix:
229+
include:
230+
- env:
231+
TARGET_TRIPLE: wasm32-wasip1
232+
steps:
233+
- uses: actions/[email protected]
234+
with:
235+
submodules: true
236+
- run: |
237+
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz -L | tar xzvf -
238+
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-24.0-x86_64-linux" >> $GITHUB_ENV
239+
if: runner.os == 'Linux'
240+
shell: bash
241+
working-directory: ${{ runner.tool_cache }}
242+
- name: Setup `wasmtime`
243+
uses: bytecodealliance/actions/wasmtime/setup@v1
244+
with:
245+
version: "38.0.3"
246+
- run: make -j4 CC=$WASI_SDK_PATH/bin/clang AR=$WASI_SDK_PATH/bin/llvm-ar
247+
- name: Setup testing
248+
run: |
249+
cmake -S test -B testbuild -G Ninja \
250+
-DTARGET_TRIPLE=${{ env.TARGET_TRIPLE }} \
251+
-DCMAKE_LINK_DEPENDS_USE_LINKER=OFF \
252+
-DPYTHON_TESTS=ON \
253+
-DCMAKE_C_COMPILER=clang
254+
- name: Build and test python
255+
run: ninja -C testbuild python

test/CMakeLists.txt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ include(CTest)
1515
enable_testing()
1616

1717
set(TARGET_TRIPLE "wasm32-wasi" CACHE STRING "WASI target to test")
18+
option(PYTHON_TESTS "Build Python with this wasi-libc and run its tests" OFF)
1819

1920
# ========= Sysroot sanity check ================================
2021

@@ -419,3 +420,71 @@ if (TARGET_TRIPLE MATCHES "-threads")
419420
# "poll_oneoff" can't be implemented in the browser
420421
set_tests_properties(libc_test_functional_pthread_cond.wasm PROPERTIES LABELS v8fail)
421422
endif()
423+
424+
# If enabled add a copy of Python which is built against `wasi-libc` and run
425+
# its tests.
426+
if (PYTHON_TESTS)
427+
find_program(PYTHON python3 python REQUIRED)
428+
find_program(MAKE make REQUIRED)
429+
430+
set(flags "--target=${TARGET_TRIPLE} --sysroot=${SYSROOT_DIR}")
431+
432+
ExternalProject_Add(
433+
python
434+
435+
# Pin the source to 3.14 for now, but this is fine to change later if
436+
# tests still pass.
437+
URL https://github.com/python/cpython/archive/refs/tags/v3.14.0.tar.gz
438+
439+
# Python as-is doesn't pass with the current wasi-libc. For example
440+
# wasi-libc now provides dummy pthread symbols which tricks Python into
441+
# thinking it can spawn threads, so a patch is needed for a WASI-specific
442+
# clause to disable that.
443+
#
444+
# More generally though this is an escape hatch to apply any other
445+
# changes as necessary without trying to upstream the patches to Python
446+
# itself. The patch is most "easily" generated by checking out cpython
447+
# at the `v3.14.0` tag, applying the existing patch, editing source,
448+
# and then regenerating the patch.
449+
PATCH_COMMAND
450+
patch -Np1 < ${CMAKE_CURRENT_SOURCE_DIR}/scripts/cpython3.14.patch
451+
452+
# The WASI build of Python looks to need an in-source build, or otherwise I
453+
# couldn't figure out an out-of-tree build.
454+
BUILD_IN_SOURCE ON
455+
456+
# These steps take a long time, so stream the output to the terminal instead
457+
# of capturing it by default.
458+
USES_TERMINAL_CONFIGURE ON
459+
USES_TERMINAL_BUILD ON
460+
USES_TERMINAL_TEST ON
461+
462+
# The following steps are copied from Python's own CI for managing WASI.
463+
# In general I don't know what they do. If Python's CI changes these
464+
# should change as well.
465+
CONFIGURE_COMMAND
466+
${PYTHON} <SOURCE_DIR>/Tools/wasm/wasi configure-build-python -- --config-cache --with-pydebug
467+
COMMAND
468+
${PYTHON} <SOURCE_DIR>/Tools/wasm/wasi make-build-python
469+
470+
BUILD_COMMAND
471+
${CMAKE_COMMAND}
472+
-E env CFLAGS=${flags} LDFLAGS=${flags} --
473+
${PYTHON} <SOURCE_DIR>/Tools/wasm/wasi configure-host -- --config-cache
474+
COMMAND
475+
${CMAKE_COMMAND}
476+
-E env CFLAGS=${flags} LDFLAGS=${flags} --
477+
${PYTHON} <SOURCE_DIR>/Tools/wasm/wasi make-host
478+
COMMAND
479+
${CMAKE_COMMAND}
480+
-E env CFLAGS=${flags} LDFLAGS=${flags} --
481+
${MAKE} --directory cross-build/wasm32-wasip1 pythoninfo
482+
483+
INSTALL_COMMAND ""
484+
485+
TEST_COMMAND
486+
${CMAKE_COMMAND}
487+
-E env CFLAGS=${flags} LDFLAGS=${flags} --
488+
${MAKE} --directory cross-build/wasm32-wasip1 test
489+
)
490+
endif()

test/scripts/cpython3.14.patch

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
diff --git a/Include/pyport.h b/Include/pyport.h
2+
index 3eac119bf8e..6a4df4bf75f 100644
3+
--- a/Include/pyport.h
4+
+++ b/Include/pyport.h
5+
@@ -503,6 +503,7 @@ extern "C" {
6+
* Thread support is stubbed and any attempt to create a new thread fails.
7+
*/
8+
#if (!defined(HAVE_PTHREAD_STUBS) && \
9+
+ !defined(__wasi__) && \
10+
(!defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)))
11+
# define Py_CAN_START_THREADS 1
12+
#endif
13+
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
14+
index e2117b9588d..1a14c758c55 100644
15+
--- a/Lib/test/test_sys.py
16+
+++ b/Lib/test/test_sys.py
17+
@@ -737,7 +737,7 @@ def test_thread_info(self):
18+
elif sys.platform == "emscripten":
19+
self.assertIn(info.name, {"pthread", "pthread-stubs"})
20+
elif sys.platform == "wasi":
21+
- self.assertEqual(info.name, "pthread-stubs")
22+
+ self.assertIn(info.name, {"pthread", "pthread-stubs"})
23+
24+
@unittest.skipUnless(support.is_emscripten, "only available on Emscripten")
25+
def test_emscripten_info(self):
26+
diff --git a/Tools/wasm/wasi/__main__.py b/Tools/wasm/wasi/__main__.py
27+
index 54ccc95157d..e92e7419086 100644
28+
--- a/Tools/wasm/wasi/__main__.py
29+
+++ b/Tools/wasm/wasi/__main__.py
30+
@@ -307,7 +307,7 @@ def main():
31+
# Make sure the stack size will work for a pydebug
32+
# build.
33+
# Use 16 MiB stack.
34+
- "--wasm max-wasm-stack=16777216 "
35+
+ "--wasm max-wasm-stack=33554432 "
36+
# Enable thread support; causes use of preview1.
37+
#"--wasm threads=y --wasi threads=y "
38+
# Map the checkout to / to load the stdlib from /Lib.
39+
diff --git a/Tools/wasm/wasm_build.py b/Tools/wasm/wasm_build.py
40+
index bcb80212362..95e4d91211e 100755
41+
--- a/Tools/wasm/wasm_build.py
42+
+++ b/Tools/wasm/wasm_build.py
43+
@@ -329,7 +329,7 @@ def _check_wasi() -> None:
44+
# workaround for https://github.com/python/cpython/issues/95952
45+
"HOSTRUNNER": (
46+
"wasmtime run "
47+
- "--wasm max-wasm-stack=16777216 "
48+
+ "--wasm max-wasm-stack=33554432 "
49+
"--wasi preview2 "
50+
"--dir {srcdir}::/ "
51+
"--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib"

0 commit comments

Comments
 (0)