Skip to content

Commit

Permalink
Switch emscripten to wasi-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
olivi-r committed Jul 13, 2023
1 parent b1976e7 commit ee55dfb
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 121 deletions.
55 changes: 26 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
# WasmPy-Build

This tool can compile CPython C extension files, such as the ones created by Cython, to WebAssembly so that the extensions are platform independent.

The created `.wasm` files can be imported by [WasmPy](https://github.com/olivi-r/wasmpy) in a simmilar manner to native C extensions.

This project contains modified CPython header files as well as a build script to ease the creation of `.wasm` extension files.

Currently this project only supports CPython 3.8, 3.9 and 3.10 but I'm hoping to add support for older versions.

# Installation

To install WasmPy-Build you will first need to install the [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html#installation-instructions).

### Install WasmPy-Build from pip

```bash
$ pip install wasmpy-build
```

### ... or build from source

```bash
$ git clone --recurse-submodules https://github.com/olivi-r/wasmpy-build
$ cd wasmpy-build
$ python -m pip install -r requirements.txt
$ python patch_headers.py
$ python setup.py install
```
# wasmpy-build

This tool can compile CPython C extension files, such as the ones created by Cython, to WebAssembly so that the extensions are platform independent.

This project contains modified CPython header files as well as a build script to ease the creation of `.wasm` extension files.

Currently this project only supports CPython 3.8, 3.9, 3.10 and 3.11 but I'm hoping to add support for older and future versions.

The project will automatically download [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) on first use.

# Installation
### Install from pip

```bash
pip install wasmpy-build
```

### or build from source

```bash
git clone --recurse-submodules https://github.com/olivi-r/wasmpy-build
cd wasmpy-build
python -m pip install -r requirements.txt
python patch_headers.py
python setup.py install
```
5 changes: 1 addition & 4 deletions patch_headers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import subprocess
import shutil
import patch
import os
import os, patch, shutil, subprocess


for patch_file in os.listdir("patches"):
Expand Down
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
patch
appdirs
patch
requests
tqdm
85 changes: 40 additions & 45 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
import os, setuptools

with open("README.md") as fp:
long_description = fp.read()

def recurse_files(directory):
paths = []
for path, dirs, files in os.walk(directory):
for file in files:
paths.append(os.path.join("..", path, file))

return paths

setuptools.setup(
name="wasmpy-build",
version="0.2.1",
author="Olivia Ryan",
author_email="[email protected]",
description="Emscripten compatible build script for CPython C extensions",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/olivi-r/wasmpy-build",
packages=["wasmpy_build"],
package_data={
"wasmpy_build": recurse_files("wasmpy_build/include")
},
entry_points={
"console_scripts": [
"wasmpy-build=wasmpy_build:build"
]
},
classifiers=[
"Programming Language :: C",
"Programming Language :: Cython",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"License :: OSI Approved :: MIT License",
],
license="MIT",
python_requires=">=3.8"
)
import os, setuptools


with open("README.md") as fp:
long_description = fp.read()


def recurse_files(directory):
paths = []
for path, _, files in os.walk(directory):
for file in files:
paths.append(os.path.join("..", path, file))

return paths


setuptools.setup(
name="wasmpy-build",
version="0.3.0",
author="Olivia Ryan",
author_email="[email protected]",
description="WebAssembly build tool for CPython C extensions",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/olivi-r/wasmpy-build",
packages=["wasmpy_build"],
package_data={"wasmpy_build": recurse_files("wasmpy_build/include")},
entry_points={"console_scripts": ["wasmpy-build=wasmpy_build:build"]},
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"License :: OSI Approved :: MIT License",
],
license="MIT",
python_requires=">=3.8",
)
117 changes: 79 additions & 38 deletions wasmpy_build/__init__.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,79 @@
import os, platform, subprocess, sys


def build():
command = sys.argv[1:]

# find cpython include files
include_dir = os.path.join(os.path.dirname(__file__), "include", "cp")
version = "".join(str(i) for i in sys.version_info[:2])
include_dir += version

if "-o" not in command:
command += ["-o", f"a.out.cp{version}.wasm"]

emcc = "emcc.bat" if platform.system() == "Windows" else "emcc"
args = (
[
emcc,
"-Wno-visibility",
"-Wno-experimental",
f"-I{include_dir}",
"-sSIDE_MODULE",
"-sSTANDALONE_WASM",
"-DSIZEOF_WCHAR_T",
"-DHAVE_PTHREAD_STUBS",
"-pthread",
]
+ []
if "-o" in command
else ["-o", f"a.out.cp{version}.wasm"]
)
try:
subprocess.call(args + command)

except FileNotFoundError:
print(
"emsdk not found\nYou can install it from https://github.com/emscripten-core/emsdk\nIf it is already installed make sure to call the emsdk_env.sh/emsdk_env.bat script or add emsdk to path."
)
import os, platform, shutil, subprocess, sys, tarfile
import appdirs, requests, tqdm


sdk_dir = os.path.join(appdirs.user_data_dir("wasmpy-build", "wasmpy"))


def download_sdk():
try:
os.makedirs(sdk_dir)

except FileExistsError:
pass

url = (
"https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/"
)
if platform.system() == "Windows":
file = "wasi-sdk-20.0.m-mingw.tar.gz"

elif platform.system() == "Linux":
file = "wasi-sdk-20.0-linux.tar.gz"

url += file
if not os.path.exists(os.path.join(sdk_dir, file)):
print(f"Downloading {file}")
with requests.get(url, stream=True) as req:
req.raise_for_status()

with open(os.path.join(sdk_dir, file), "wb+") as fp, tqdm.tqdm(
desc=file,
total=int(req.headers.get("content-length", 0)),
unit="MiB",
unit_scale=True,
unit_divisor=1024,
) as progress:
for chunk in req.iter_content(4096):
progress.update(fp.write(chunk))

print(f"Extracting {file}")
with tarfile.open(os.path.join(sdk_dir, file)) as tar:
extracted = tar.getnames()[0]
tar.extractall(sdk_dir)

shutil.move(
os.path.join(sdk_dir, extracted),
os.path.join(sdk_dir, f"sdk-{platform.system()}"),
)

print(f"wasi-sdk installed at: {os.path.join(sdk_dir, 'sdk')}")
build()


def build():
command = sys.argv[1:]

# find cpython include files
include_dir = os.path.join(os.path.dirname(__file__), "include", "cp")
version = "".join(str(i) for i in sys.version_info[:2])
include_dir += version

args = [
f"{sdk_dir}/sdk-{platform.system()}/bin/clang",
"--target=wasm32-unknown-wasi",
f"--sysroot={sdk_dir}/sdk-{platform.system()}/share/wasi-sysroot",
"-nostartfiles",
"-D_POSIX_THREADS",
"-DHAVE_PTHREAD_STUBS",
"-DSIZEOF_WCHAR_T=2",
f"-I{include_dir}",
] + command

print(" ".join(args))
try:
subprocess.call(args)

except FileNotFoundError:
print("wasi-sdk not found")
download_sdk()
9 changes: 5 additions & 4 deletions wasmpy_build/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import build

if __name__ == "__main__":
build()
from . import build


if __name__ == "__main__":
build()

0 comments on commit ee55dfb

Please sign in to comment.