├── .devcontainer └── devcontainer.json ├── .github ├── actions │ └── prepare │ │ └── action.yml └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── browser-ui ├── index.html ├── index.js ├── server.py ├── styles.css ├── wasm-terminal.js ├── worker-manager.js └── worker │ ├── .gitignore │ └── worker.js ├── build-python-build.sh ├── build-python-emscripten-browser.sh ├── build-python-emscripten-node.sh ├── build-python-wasi.sh ├── clean-host.sh ├── fetch-python.sh ├── run-python-browser.sh ├── run-python-node.sh ├── run-python-wasi.sh ├── test-emscripten-node.sh └── test-wasi.sh /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "quay.io/tiran/cpythonbuild:emsdk3" 3 | } 4 | -------------------------------------------------------------------------------- /.github/actions/prepare/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Common setup' 2 | description: 'Common setup routines' 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Create container cache directory 7 | shell: bash 8 | run: mkdir -p /tmp/container-cache 9 | # container is fetched once a day 10 | - name: Get date cache key 11 | id: get-date 12 | shell: bash 13 | run: | 14 | echo "TODAY=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_ENV 15 | - name: Get container cache 16 | id: container-cache 17 | uses: actions/cache@v1 18 | with: 19 | path: /tmp/container-cache 20 | key: image-cache-${{ env.TODAY }} 21 | - name: "Pull build image" 22 | if: ${{ steps.container-cache.outputs.cache-hit != 'true' }} 23 | shell: bash 24 | run: | 25 | docker pull quay.io/tiran/cpythonbuild:emsdk3 26 | docker save -o /tmp/container-cache/cpemsdk3.tar quay.io/tiran/cpythonbuild:emsdk3 27 | - name: "Load container from cache" 28 | if: ${{ steps.container-cache.outputs.cache-hit == 'true' }} 29 | shell: bash 30 | run: | 31 | docker load -i /tmp/container-cache/cpemsdk3.tar 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | types: [opened, synchronize, reopened, closed] 12 | schedule: 13 | - cron: '30 2 * * *' 14 | workflow_dispatch: 15 | inputs: 16 | git-ref: 17 | required: false 18 | 19 | jobs: 20 | pull-buildcontainer: 21 | name: "Pull & cache build container" 22 | runs-on: "ubuntu-latest" 23 | steps: 24 | - name: "checkout python-wasm" 25 | uses: "actions/checkout@v2" 26 | - name: "Common prepare step" 27 | uses: ./.github/actions/prepare 28 | build-python: 29 | name: "Build build Python ${{ matrix.pythonbranch }}" 30 | runs-on: "ubuntu-latest" 31 | needs: pull-buildcontainer 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | pythonbranch: [main, 3.11] 36 | steps: 37 | - name: "checkout python-wasm" 38 | uses: "actions/checkout@v2" 39 | - name: "checkout CPython" 40 | uses: "actions/checkout@v2" 41 | with: 42 | repository: python/cpython 43 | path: cpython 44 | ref: ${{ matrix.pythonbranch }} 45 | - name: "Verify checkout" 46 | shell: bash 47 | run: | 48 | test -x build-python-build.sh || exit 1 49 | test -x cpython/configure || exit 2 50 | - name: "Common prepare step" 51 | uses: ./.github/actions/prepare 52 | - name: "Build build Python" 53 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./build-python-build.sh 54 | - name: "Store CPython commit hash" 55 | run: git --git-dir=cpython/.git rev-parse HEAD > cpython/commit.txt 56 | - name: "Cache build Python" 57 | uses: actions/cache@v2 58 | with: 59 | path: cpython 60 | key: cpython-${{ matrix.pythonbranch }}-${{ runner.os }}-${{ env.TODAY }}-${{ github.sha }} 61 | emscripte-node: 62 | name: "Build Emscripten node ${{ matrix.pythonbranch }}" 63 | runs-on: "ubuntu-latest" 64 | needs: build-python 65 | strategy: 66 | fail-fast: false 67 | matrix: 68 | pythonbranch: [main, 3.11] 69 | steps: 70 | - name: "checkout python-wasm" 71 | uses: "actions/checkout@v2" 72 | - name: "Common prepare step" 73 | uses: ./.github/actions/prepare 74 | - name: "Fetch cached build Python" 75 | uses: actions/cache@v2 76 | with: 77 | path: cpython 78 | key: cpython-${{ matrix.pythonbranch }}-${{ runner.os }}-${{ env.TODAY }}-${{ github.sha }} 79 | - name: "Check build Python" 80 | run: | 81 | test -e cpython/builddir/build/python || exit 1 82 | test -e cpython/configure || exit 2 83 | - name: "Build emscripten Python for node" 84 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./build-python-emscripten-node.sh 85 | - name: "Check artifacts" 86 | run: | 87 | ls -la --si cpython/builddir/emscripten-node/python* 88 | test -e cpython/builddir/emscripten-node/python.wasm || exit 1 89 | - name: "Print test.pythoninfo" 90 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./run-python-node.sh -m test.pythoninfo 91 | - name: "Run tests" 92 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./test-emscripten-node.sh -u all -W --slowest --fail-env-changed 93 | - name: "Copy stdlib" 94 | run: | 95 | sudo chown $(id -u):$(id -g) -R cpython 96 | cp cpython/commit.txt cpython/builddir/emscripten-node/ 97 | cp cpython/LICENSE cpython/builddir/emscripten-node/ 98 | cp -R cpython/Lib cpython/builddir/emscripten-node/ 99 | pushd cpython/builddir/emscripten-node/ 100 | rm -rf Lib/curses Lib/ensurepip/ Lib/distutils/ Lib/idlelib/ Lib/test/ Lib/tkinter/ Lib/turtledemo/ Lib/venv/ 101 | find -name __pycache__ | xargs rm -rf 102 | popd 103 | - name: "Upload node build artifacts" 104 | uses: actions/upload-artifact@v2 105 | with: 106 | name: emscripten-node-${{ matrix.pythonbranch }} 107 | path: | 108 | cpython/builddir/emscripten-node/commit.txt 109 | cpython/builddir/emscripten-node/LICENSE 110 | cpython/builddir/emscripten-node/python.wasm 111 | cpython/builddir/emscripten-node/python.worker.js 112 | cpython/builddir/emscripten-node/python.js 113 | cpython/builddir/emscripten-node/pybuilddir.txt 114 | cpython/builddir/emscripten-node/build/lib.emscripten-wasm32-3.*/_sysconfigdata__emscripten_wasm32-emscripten.py 115 | cpython/builddir/emscripten-node/Lib/ 116 | if-no-files-found: error 117 | - name: "Upload build artifacts" 118 | uses: actions/upload-artifact@v2 119 | with: 120 | name: build-node-${{ matrix.pythonbranch }} 121 | path: | 122 | cpython/builddir/emscripten-node/config.log 123 | cpython/builddir/emscripten-node/config.cache 124 | cpython/builddir/emscripten-node/Makefile 125 | cpython/builddir/emscripten-node/pyconfig.h 126 | cpython/builddir/emscripten-node/libpython*.a 127 | cpython/builddir/emscripten-node/Modules/Setup.local 128 | cpython/builddir/emscripten-node/Modules/Setup.stdlib 129 | cpython/builddir/emscripten-node/Modules/config.c 130 | cpython/builddir/emscripten-node/Modules/_decimal/libmpdec/libmpdec.a 131 | cpython/builddir/emscripten-node/Modules/expat/libexpat.a 132 | cpython/builddir/emscripten-node/Programs/python.o 133 | if-no-files-found: error 134 | emscripte-browser: 135 | name: "Build Emscripten browser ${{ matrix.pythonbranch }}" 136 | runs-on: "ubuntu-latest" 137 | needs: build-python 138 | strategy: 139 | fail-fast: false 140 | matrix: 141 | pythonbranch: [main, 3.11] 142 | steps: 143 | - name: "checkout python-wasm" 144 | uses: "actions/checkout@v2" 145 | - name: "Common prepare step" 146 | uses: ./.github/actions/prepare 147 | - name: "Fetch cached build Python" 148 | uses: actions/cache@v2 149 | with: 150 | path: cpython 151 | key: cpython-${{ matrix.pythonbranch }}-${{ runner.os }}-${{ env.TODAY }}-${{ github.sha }} 152 | - name: "Check build Python" 153 | run: | 154 | test -e cpython/builddir/build/python || exit 1 155 | test -e cpython/configure || exit 2 156 | - name: "Build emscripten Python for browser" 157 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./build-python-emscripten-browser.sh 158 | - name: "Check artifacts" 159 | run: | 160 | ls -la --si cpython/builddir/emscripten-browser/python* 161 | ls -la cpython/builddir/emscripten-browser/Modules/ 162 | test -e cpython/builddir/emscripten-browser/python.data || exit 1 163 | - name: "Copy commit.txt and LICENSE" 164 | run: | 165 | sudo chown $(id -u):$(id -g) -R cpython 166 | cp cpython/commit.txt cpython/builddir/emscripten-browser/ 167 | cp cpython/LICENSE cpython/builddir/emscripten-browser/ 168 | - name: "Upload browser build artifacts" 169 | uses: actions/upload-artifact@v2 170 | with: 171 | name: emscripten-browser-${{ matrix.pythonbranch }} 172 | path: | 173 | cpython/builddir/emscripten-browser/commit.txt 174 | cpython/builddir/emscripten-browser/LICENSE 175 | cpython/builddir/emscripten-browser/python.wasm 176 | cpython/builddir/emscripten-browser/python.html 177 | cpython/builddir/emscripten-browser/python.js 178 | cpython/builddir/emscripten-browser/python.worker.js 179 | cpython/builddir/emscripten-browser/python.data 180 | if-no-files-found: error 181 | - name: "Upload build artifacts" 182 | uses: actions/upload-artifact@v2 183 | with: 184 | name: build-browser-${{ matrix.pythonbranch }} 185 | path: | 186 | cpython/builddir/emscripten-browser/config.log 187 | cpython/builddir/emscripten-browser/config.cache 188 | cpython/builddir/emscripten-browser/Makefile 189 | cpython/builddir/emscripten-browser/pyconfig.h 190 | cpython/builddir/emscripten-browser/pybuilddir.txt 191 | cpython/builddir/emscripten-browser/libpython*.a 192 | cpython/builddir/emscripten-browser/Modules/Setup.local 193 | cpython/builddir/emscripten-browser/Modules/Setup.stdlib 194 | cpython/builddir/emscripten-browser/Modules/config.c 195 | cpython/builddir/emscripten-browser/Modules/_decimal/libmpdec/libmpdec.a 196 | cpython/builddir/emscripten-browser/Modules/expat/libexpat.a 197 | cpython/builddir/emscripten-browser/Programs/python.o 198 | if-no-files-found: error 199 | wasi: 200 | name: "Build WASI ${{ matrix.pythonbranch }}" 201 | runs-on: "ubuntu-latest" 202 | needs: build-python 203 | strategy: 204 | fail-fast: false 205 | matrix: 206 | pythonbranch: [main, 3.11] 207 | steps: 208 | - name: "checkout python-wasm" 209 | uses: "actions/checkout@v2" 210 | - name: "Common prepare step" 211 | uses: ./.github/actions/prepare 212 | - name: "Fetch cached build Python" 213 | uses: actions/cache@v2 214 | with: 215 | path: cpython 216 | key: cpython-${{ matrix.pythonbranch }}-${{ runner.os }}-${{ env.TODAY }}-${{ github.sha }} 217 | - name: "Check build Python" 218 | run: | 219 | test -e cpython/builddir/build/python || exit 1 220 | test -e cpython/configure || exit 2 221 | - name: "Build WASI Python" 222 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./build-python-wasi.sh 223 | - name: "Check artifacts" 224 | run: | 225 | ls -la --si cpython/builddir/wasi/python* 226 | test -e cpython/builddir/wasi/python.wasm || exit 1 227 | - name: "Print test.pythoninfo" 228 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./run-python-wasi.sh -m test.pythoninfo 229 | - name: "Run tests" 230 | run: docker run --rm -v $(pwd):/build -w /build quay.io/tiran/cpythonbuild:emsdk3 ./test-wasi.sh -u all -W --slowest --fail-env-changed 231 | # some WASI tests are failing 232 | continue-on-error: true 233 | - name: "Copy stdlib" 234 | run: | 235 | sudo chown $(id -u):$(id -g) -R cpython 236 | cp cpython/commit.txt cpython/builddir/wasi/ 237 | cp cpython/LICENSE cpython/builddir/wasi/ 238 | cp -R cpython/Lib cpython/builddir/wasi/ 239 | pushd cpython/builddir/wasi/ 240 | rm -rf Lib/curses Lib/ensurepip/ Lib/distutils/ Lib/idlelib/ Lib/test/ Lib/tkinter/ Lib/turtledemo/ Lib/venv/ 241 | find -name __pycache__ | xargs rm -rf 242 | popd 243 | - name: "Upload WASI artifacts" 244 | uses: actions/upload-artifact@v2 245 | with: 246 | name: wasi-${{ matrix.pythonbranch }} 247 | path: | 248 | cpython/builddir/wasi/LICENSE 249 | cpython/builddir/wasi/commit.txt 250 | cpython/builddir/wasi/python.wasm 251 | cpython/builddir/wasi/pybuilddir.txt 252 | cpython/builddir/wasi/build/lib.wasi-wasm32-3.*/_sysconfigdata__wasi_wasm32-wasi.py 253 | cpython/builddir/wasi/Lib/ 254 | if-no-files-found: error 255 | - name: "Upload build artifacts" 256 | uses: actions/upload-artifact@v2 257 | with: 258 | name: build-wasi-${{ matrix.pythonbranch }} 259 | path: | 260 | cpython/builddir/wasi/config.log 261 | cpython/builddir/wasi/config.cache 262 | cpython/builddir/wasi/Makefile 263 | cpython/builddir/wasi/pyconfig.h 264 | cpython/builddir/wasi/libpython*.a 265 | cpython/builddir/wasi/Modules/Setup.local 266 | cpython/builddir/wasi/Modules/Setup.stdlib 267 | cpython/builddir/wasi/Modules/config.c 268 | cpython/builddir/wasi/Modules/_decimal/libmpdec/libmpdec.a 269 | cpython/builddir/wasi/Modules/expat/libexpat.a 270 | cpython/builddir/wasi/Programs/python.o 271 | if-no-files-found: error 272 | ghpages: 273 | name: "Upload to GitHub pages" 274 | runs-on: "ubuntu-latest" 275 | needs: emscripte-browser 276 | # Relies on `on` restricting which branches trigger this job. 277 | if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }} 278 | steps: 279 | - uses: actions/checkout@v2 280 | - uses: actions/download-artifact@v2 281 | with: 282 | name: emscripten-browser-main 283 | path: wasm 284 | - name: "Prepare artifacts for Github Pages" 285 | run: | 286 | cp -r browser-ui/* wasm/ 287 | - name: Deploy CPython on WASM 🚀 288 | uses: JamesIves/github-pages-deploy-action@4.1.7 289 | with: 290 | branch: gh-pages 291 | folder: wasm 292 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cpython/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPython on WASM 2 | 3 | Build scripts and configuration for building CPython for Emscripten. 4 | 5 | Check out Christian Heimes' talk about the effort at PyConDE: https://www.youtube.com/watch?v=oa2LllRZUlU 6 | 7 | Pretty straight forward. First, [install emscripten](https://emscripten.org/docs/getting_started/downloads.html). 8 | Then, run the following commands: 9 | 10 | ```shell 11 | # get the Python sources 12 | ./fetch-python.sh 13 | # build Python for the machine we are building on, needed before cross compiling for emscripten 14 | ./build-python-build.sh 15 | # build Python cross-compiling to emscripten 16 | ./build-python-emscripten-browser.sh 17 | ``` 18 | 19 | There will probably be errors, but that's just part of the fun of experimental platforms. 20 | 21 | Assuming things compiled correctly, you can have emscripten serve the Python executable and then open http://localhost:8000/python.html in your browser: 22 | 23 | ``` 24 | ./run-python-browser.sh 25 | ``` 26 | 27 | The CLI input is done via an input modal which is rather annoying. Also to get output you need to click `Cancel` on the modal... 28 | 29 | ## Developing 30 | Once you've built the Emscripten'd Python, you can rebuild it via 31 | 32 | ``` 33 | ./clean-host.sh 34 | ./build-python-emscripten-browser.sh 35 | ``` 36 | which will rebuild Python targeting emscripten and re-generate the `python.{html, wasm, js}` 37 | 38 | ## Test build artifacts 39 | 40 | You can also download builds from our [CI workflow](https://github.com/ethanhs/python-wasm/actions?query=branch%3Amain) 41 | and test WASM builds locally. 42 | 43 | ### Emscripten browser build 44 | 45 | * download and unzip the ``emscripten-browser-main.zip`` build artifact 46 | * run a local webserver in the same directory as ``python.html``, 47 | e.g. ``python3 -m http.server`` 48 | * open http://localhost:8000/python.html 49 | * enter commands into the browser modal window and check the web developer 50 | console (*F12*) for output. You may need to hit "Cancel" on the modal after sending input for output to appear. 51 | 52 | ### Emscripten NodeJS build 53 | 54 | * download and unzip the ``emscripten-node-main.zip`` build artifact 55 | * run ``node python.js`` (older versions may need ``--experimental-wasm-bigint``) 56 | 57 | ### WASI 58 | 59 | * download and unzip the ``wasi-main.zip`` build artifact 60 | * install [wasmtime](https://wasmtime.dev/) 61 | * run ``wasmtime run --dir . -- python.wasm`` 62 | -------------------------------------------------------------------------------- /browser-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |