Skip to content

Commit 9983083

Browse files
build wheels for emscripten (#185)
Co-authored-by: Samuel Colvin <[email protected]>
1 parent 4cefa8b commit 9983083

File tree

5 files changed

+183
-6
lines changed

5 files changed

+183
-6
lines changed

.github/actions/build-pgo-wheel/action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ runs:
4040

4141
- name: generate pgo data
4242
run: |
43-
pip install -U pip
44-
pip install -r tests/requirements.txt
45-
pip install jiter --no-index --no-deps --find-links pgo-wheel --force-reinstall
43+
python -m pip install -U pip
44+
python -m pip install -r tests/requirements.txt
45+
python -m pip install jiter --no-index --no-deps --find-links pgo-wheel --force-reinstall
4646
python bench.py jiter jiter-cache
4747
RUST_HOST=$(rustc -Vv | grep host | cut -d ' ' -f 2)
4848
rustup run ${{ inputs.rust-toolchain }} bash -c 'echo LLVM_PROFDATA=$RUSTUP_HOME/toolchains/$RUSTUP_TOOLCHAIN/lib/rustlib/$RUST_HOST/bin/llvm-profdata >> "$GITHUB_ENV"'

.github/workflows/ci.yml

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,64 @@ jobs:
395395
name: pypi_files_${{ matrix.os }}_${{ matrix.interpreter }}
396396
path: crates/jiter-python/dist
397397

398+
build-wasm-emscripten:
399+
# only run on push to main and on release
400+
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build')
401+
runs-on: ubuntu-latest
402+
steps:
403+
- uses: actions/checkout@v4
404+
405+
- id: setup-python
406+
name: set up python
407+
uses: quansight-labs/setup-python@v5
408+
with:
409+
python-version: 3.12
410+
allow-prereleases: true
411+
412+
- name: install rust nightly
413+
uses: dtolnay/rust-toolchain@nightly
414+
with:
415+
components: rust-src
416+
targets: wasm32-unknown-emscripten
417+
418+
- name: cache rust
419+
uses: Swatinem/rust-cache@v2
420+
421+
- uses: mymindstorm/setup-emsdk@v14
422+
with:
423+
# NOTE!: as per https://github.com/pydantic/pydantic-core/pull/149 this version needs to match the version
424+
# in node_modules/pyodide/repodata.json, to get the version, run:
425+
# `cat node_modules/pyodide/repodata.json | python -m json.tool | rg platform`
426+
version: "3.1.58"
427+
actions-cache-folder: emsdk-cache
428+
429+
- name: install deps
430+
run: pip install -U pip maturin
431+
432+
- name: build wheels
433+
run: maturin build --release --target wasm32-unknown-emscripten --out dist -i 3.12
434+
working-directory: crates/jiter-python
435+
436+
- uses: actions/setup-node@v4
437+
with:
438+
node-version: "18"
439+
440+
- run: npm install
441+
working-directory: crates/jiter-python
442+
443+
- run: npm run test
444+
working-directory: crates/jiter-python
445+
446+
- run: |
447+
ls -lh dist/
448+
ls -l dist/
449+
working-directory: crates/jiter-python
450+
451+
- uses: actions/upload-artifact@v4
452+
with:
453+
name: wasm_wheels
454+
path: crates/jiter-python/dist
455+
398456
inspect-pypi-assets:
399457
needs: [build, build-sdist, build-pgo]
400458
runs-on: ubuntu-latest
@@ -524,7 +582,7 @@ jobs:
524582

525583
release:
526584
needs: [check]
527-
if: "success() && startsWith(github.ref, 'refs/tags/')"
585+
if: success() && startsWith(github.ref, 'refs/tags/')
528586
runs-on: ubuntu-latest
529587
environment: release
530588

@@ -541,8 +599,13 @@ jobs:
541599
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
542600

543601
release-python:
544-
needs: [test-builds-arch, test-builds-os, build-sdist, check]
545-
if: "success() && startsWith(github.ref, 'refs/tags/')"
602+
needs:
603+
- check
604+
- test-builds-arch
605+
- test-builds-os
606+
- build-sdist
607+
- build-wasm-emscripten
608+
if: success() && startsWith(github.ref, 'refs/tags/')
546609
runs-on: ubuntu-latest
547610
environment: release-python
548611
permissions:
@@ -573,3 +636,16 @@ jobs:
573636
uses: pypa/gh-action-pypi-publish@release/v1
574637
with:
575638
packages-dir: dist/
639+
640+
- name: get wasm dist artifacts
641+
uses: actions/download-artifact@v4
642+
with:
643+
name: wasm_wheels
644+
path: wasm
645+
646+
- name: upload to github release
647+
uses: softprops/action-gh-release@v2
648+
with:
649+
files: |
650+
wasm/*.whl
651+
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}

crates/jiter-python/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "jiter",
3+
"version": "1.0.0",
4+
"description": "for running wasm tests.",
5+
"author": "Samuel Colvin",
6+
"license": "MIT",
7+
"homepage": "https://github.com/pydantic/jiter#readme",
8+
"main": "tests/emscripten_runner.js",
9+
"dependencies": {
10+
"prettier": "^2.7.1",
11+
"pyodide": "^0.26.3"
12+
},
13+
"scripts": {
14+
"test": "node tests/emscripten_runner.js",
15+
"format": "prettier --write 'tests/emscripten_runner.js' 'wasm-preview/*.{html,js}'",
16+
"lint": "prettier --check 'tests/emscripten_runner.js' 'wasm-preview/*.{html,js}'"
17+
},
18+
"prettier": {
19+
"singleQuote": true,
20+
"trailingComma": "all",
21+
"tabWidth": 2,
22+
"printWidth": 119,
23+
"bracketSpacing": false,
24+
"arrowParens": "avoid"
25+
}
26+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const {opendir} = require('node:fs/promises');
2+
const {loadPyodide} = require('pyodide');
3+
const path = require('path');
4+
5+
async function find_wheel(dist_dir) {
6+
const dir = await opendir(dist_dir);
7+
for await (const dirent of dir) {
8+
if (dirent.name.endsWith('.whl')) {
9+
return path.join(dist_dir, dirent.name);
10+
}
11+
}
12+
}
13+
14+
async function main() {
15+
const root_dir = path.resolve(__dirname, '..');
16+
const wheel_path = await find_wheel(path.join(root_dir, 'dist'));
17+
const stdout = []
18+
const stderr = []
19+
let errcode = 1;
20+
try {
21+
const pyodide = await loadPyodide({
22+
stdout: (msg) => {
23+
stdout.push(msg)
24+
},
25+
stderr: (msg) => {
26+
stderr.push(msg)
27+
}
28+
});
29+
const FS = pyodide.FS;
30+
FS.mkdir('/test_dir');
31+
FS.mount(FS.filesystems.NODEFS, {root: path.join(root_dir, 'tests')}, '/test_dir');
32+
FS.chdir('/test_dir');
33+
34+
// mount jiter crate source for benchmark data
35+
FS.mkdir('/jiter');
36+
FS.mount(FS.filesystems.NODEFS, {root: path.resolve(root_dir, "..", "jiter")}, '/jiter');
37+
38+
await pyodide.loadPackage(['micropip', 'pytest']);
39+
// language=python
40+
errcode = await pyodide.runPythonAsync(`
41+
import micropip
42+
import importlib
43+
44+
# ugly hack to get tests to work on arm64 (my m1 mac)
45+
# see https://github.com/pyodide/pyodide/issues/2840
46+
# import sys; sys.setrecursionlimit(200)
47+
48+
await micropip.install([
49+
'dirty_equals',
50+
'file:${wheel_path}'
51+
])
52+
importlib.invalidate_caches()
53+
54+
print('installed packages:', micropip.list())
55+
56+
import pytest
57+
pytest.main()
58+
`);
59+
} catch (e) {
60+
console.error(e);
61+
process.exit(1);
62+
}
63+
let out = stdout.join('\n')
64+
let err = stderr.join('\n')
65+
console.log('stdout:\n', out)
66+
console.log('stderr:\n', err)
67+
68+
process.exit(errcode);
69+
}
70+
71+
main();

crates/jiter-python/tests/test_jiter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
from decimal import Decimal
44
from pathlib import Path
5+
import sys
56
from typing import Any
67

78
import jiter
@@ -352,6 +353,9 @@ def test_against_json():
352353
assert jiter.from_json(data) == json.loads(data)
353354

354355

356+
@pytest.mark.skipif(
357+
sys.platform == 'emscripten', reason='threads not supported on pyodide'
358+
)
355359
def test_multithreaded_parsing():
356360
"""Basic sanity check that running a parse in multiple threads is fine."""
357361
expected_datas = [json.loads(data) for data in JITER_BENCH_DATAS]

0 commit comments

Comments
 (0)